LilyPond erweitern: 2.5.3 Neue Definitionen von Beschriftungsbefehlen

### 2.5.3 Neue Definitionen von Beschriftungsbefehlen

Dieser Abschnitt behandelt die Definition von neuen Textbeschriftungsbefehlen.

#### Syntax der Definition von Textbeschriftungsbefehlen

Neue Textbeschriftungsbefehle können mit dem `define-markup-command`-Scheme-Makro definiert werden.

    (define-markup-command (befehl-bezeichnung layout props Arg1 Arg2 ...)
                (Arg1-typ? Arg2-typ? ...)
        [ #:properties ((Eigenschaft1 Standard-Wert1)
                        ...) ]
      ..Befehlkörper..)

Die Argumente sind:

befehl-bezeichnung

  die Bezeichnung des Befehls

layout

  die ‚layout‘-Definition

props

  eine Liste an assoziativen Listen, in der alle aktiven Eigenschaften enthalten sind

argi

  das ite Befehlsargument

argi-type?

  eine Eigenschaft für das ite Argument

Wenn der Befehl Eigenschaften des `props`-Arguments benutzt, kann das `#:properties`-Schlüsselwort benutzt werden um zu bestimmen, welche Eigenschaften mit welchen Standard-Werten benutzt werden.

Argumente werden nach ihrem Typ unterschieden:

- eine Textbeschriftung entspricht einem Typenprädikat `markup?`;
- eine Textbeschriftungsliste entspricht einem Typenprädikat `markup-list?`;
- jedes andere Scheme-Objekt entspricht Typenprädikaten wie etwa `list?`, `number?`, `boolean?`, usw.

Es gibt keine Einschränkung in der Reihenfolge der Argumente (nach den Standard-Argumenten `layout` und `props`). Textbeschriftungsfunktionen, die als letztes Argument eine Textbeschriftung haben, haben die Besonderheit, dass sie auf Textbeschriftungslisten angewendet werden können, und das Resultat ist eine Textbeschriftungsliste, in der die Textbeschriftungsfuktion (mit den angegebenen Argumenten am Anfang) auf jedes Element der originalen Textbeschriftungsliste angewendet wurde.

Da das Wiederholen der Argumente am Anfang bei der Anwendung einer Textbeschriftungsfunktion auf eine Textbeschriftungsliste for allem für Scheme-Argumente sparsam ist, kann man Leistungseinbußen vermeiden, indem man nur Scheme-Argumente für die Argumente am Anfang einsetzt, wenn es sich um Textbeschriftungsfunktionen handelt, die eine Textbeschriftung als letztes Argument haben.

#### Über Eigenschaften

Die `layout`- und `props`-Argumente der Textbeschriftungsbefehle bringen einen Kontext für die Interpretation der Beschriftung: Schriftgröße, Zeilenlänge usw.

Das `layout`-Argument greift auf Eigenschaften zu, die in der `paper`-Umgebung definiert werden, indem man die `ly:output-def-lookup`-Funktion benutzt. Beispielsweise liest man die Zeilenlänge (die gleiche, die auch in Partituren benutzt wird) aus mit:

>     (ly:output-def-lookup layout 'line-width)

Das `props`-Argument stellt einige Eigenschaften für die Textbeschriftungsbefehle zur Verfügung. Beispielsweise wenn der Überschrifttext einer `book`-Umgebung interpretiert wird, werden alle Variablen, die in der `\header`-Umgebung definiert werden, automatisch zu `props` hinzugefügt, sodass die Beschriftung auf Titel, Komponist usw. der `book`-Umgebung zugreifen kann. Das ist auch eine Möglichkeit, das Verhalten eines Beschriftungsbefehls zu konfigurieren: Wenn etwa ein Befehl die Schriftgröße während der Verarbeitung einsetzt, wird die Schriftgröße aus den `props` ausgelesen und nicht mit einem eigenen `font-size`-Argument definiert. Beim Aufruf des Beschriftungsbefehls kann der Wert der Schriftgröße geändert werden, womit sich auch das Verhalten des Befehls verändert. Benutzen Sie das `#:properties`-Schlüsselwort von `define-markup-command` um zu definieren, welche Eigenschaften aus den `props`-Argumenten ausgelesen werden sollen.

Das Beispiel im nächsten Abschnitt illustriert, wie man auf Eigenschaften in einem Beschriftungsbefehl zugreifen und sie verändern kann.

#### Ein vollständiges Bespiel

Das folgende Beispiel definiert einen Beschriftungsbefehl, der einen doppelten Kasten um einen Text zeichnet.

Zuerst wollen wir ein annäherndes Ergebnis mit Textbeschriftungen definieren. Nach Stöbern in Textbeschriftungsbefehle finden wir den Befehl `\box`:

>     \markup \box \box HELLO

Wir wollen aber etwas mehr Abstand (engl. padding) zwischen dem Text und dem Kasten. Nach der Dokumentation von `\box` hat der Befehl eine `box-padding`-Eigenschaft, die den Standardwert von 0.2 hat. Die Dokumentation zeit auch, wir man den Wert verändert:

>     \markup \box \override #'(box-padding . 0.6) \box A

Auch der Abstand zwischen den zwei Kästen ist uns zu klein und soll auch vergrößert werden:

>     \markup \override #'(box-padding . 0.4) \box
>         \override #'(box-padding . 0.6) \box A

Diese lange Textbeschriftung immer wieder schreiben zu müssen, ist anstrengend. Hier kömmt ein Textbeschriftungsbefehl ins Spiel. Wir schreiben uns alle einen `double-box`-Beschriftungsbefehl, der ein Argument annimmt (den Text). Er zeichnet zwei Kästen mit genügend Abstand:

    #(define-markup-command (double-box layout props text) (markup?)
      "Draw a double box around text."
      (interpret-markup layout props
        #{\markup \override #'(box-padding . 0.4) \box
                \override #'(box-padding . 0.6) \box { #text }#}))

oder äquivalent

    #(define-markup-command (double-box layout props text) (markup?)
      "Draw a double box around text."
      (interpret-markup layout props
        (markup #:override '(box-padding . 0.4) #:box
                #:override '(box-padding . 0.6) #:box text)))

`text` ist die Bezeichnung des Arguments dieses Befehls, und `markup?` ist seine Art: hiermit wird der Befehl als Beschriftungsbefehl identifiziert. Die `interpret-markup`-Funktion wird in den meisten Beschriftungsbefehlen benutzt: sie erstellt einen Stencil, wobei `layout`, `props` und eine Beschriftung benutzt werden. Im zweiten Fall wird diese Beschriftung durch das `markup`-Scheme-Makro erstellt, siehe auche Beschriftungskonstruktionen in Scheme. Die Transformation des `\markup`-Ausdrucks in einen Scheme-Beschriftungsausdruck geschieht durch Umschreiben des LilyPond-Codes in Scheme-Code.

Der neue Befehl kann wie folgt benutzt werden:

>     \markup \double-box A

Es wäre schön, den `double-box`-Befehl noch konfigurierbar zu gestalten: in unserem Fall sind die Werte von `box-padding` direkt definiert und können nicht mehr vom Benutzer verändert werden. Es wäre auch besser, wenn der Abstand zwischen den beiden Kästen vom Abstand zwischen dem inneren Kasten und dem Text unterschieden werden könnte. Eine neue Eigenschaft muss also definiert werden: `inter-box-padding` für den Abstand zwischen den Kästen. `box-padding` wird für den inneren Abstand benutzt. Der neue Befehl wird so definiert:

    #(define-markup-command (double-box layout props text) (markup?)
      #:properties ((inter-box-padding 0.4)
                    (box-padding 0.6))
      "Draw a double box around text."
      (interpret-markup layout props
        #{\markup \override #`(box-padding . ,inter-box-padding) \box
                   \override #`(box-padding . ,box-padding) \box
                   { #text } #}))

Wiederum wäre die entsprechende Version mit dem `markup`-Makro so aussehen:

    #(define-markup-command (double-box layout props text) (markup?)
      #:properties ((inter-box-padding 0.4)
                    (box-padding 0.6))
      "Draw a double box around text."
      (interpret-markup layout props
        (markup #:override `(box-padding . ,inter-box-padding) #:box
                #:override `(box-padding . ,box-padding) #:box text)))

In diesem Code wird das `#:properties`-Schlüsselwort benutzt, sodass die Eigenschaften `inter-box-padding` und `box-padding` aus dem `props`-Argument ausgelesen werden, und Standardwerte werden gegeben, falls die Eigenschaften nicht definiert sein sollten.

Dann werden diese Werte benutzt, um die `box-padding`-Eigenschaft zu verändert, die von beiden `\box`-Befehlen benutzt wird. Beachten Sie Akzent und das Komma des `\override`-Arguments: hiermit kann man einen Variablenwert in einen wörtlichen Ausdruck überführen.

Jetzt kann der Befehl in Beschriftungen benutzt werden und der Abstand der Kästen kann angepasst werden:

>     #(define-markup-command (double-box layout props text) (markup?)
>       #:properties ((inter-box-padding 0.4)
>                     (box-padding 0.6))
>       "Draw a double box around text."
>       (interpret-markup layout props
>         #{\markup \override #`(box-padding . ,inter-box-padding) \box
>                   \override #`(box-padding . ,box-padding) \box
>                   { #text } #}))

>     \markup \double-box A
>     \markup \override #'(inter-box-padding . 0.8) \double-box A
>     \markup \override #'(box-padding . 1.0) \double-box A

#### Eingebaute Befehle anpassen

Ein guter Weg, einen neuen Beschriftungsbefehl zu schreiben, ist es, als Vorbild einen existierenden zu nehmen. Die meisten Beschriftungsbefehle, die LilyPond mitbringt, finden sich in der Datei ‘`scm/define-markup-commands.scm`’.

Man könnte beispielsweise den Befehl `\draw-line`, der eine Linie zeichnet, anpassen, sodass er eine Doppellinie zeichnet. Der Befehl `\draw-line` ist wie folgend definiert (Dokumentation entfernt):

    (define-markup-command (draw-line layout props dest)
      (number-pair?)
      #:category graphic
      #:properties ((thickness 1))
      "..documentation.."
      (let ((th (* (ly:output-def-lookup layout 'line-thickness)
                   thickness))
            (x (car dest))
            (y (cdr dest)))
        (make-line-stencil th 0 0 x y)))

Um einen neuen Befehl, der auf einem existierenden basiert, zu definieren, wird die Befehlsdefinition kopiert und die Bezeichnung des Befehls geändert. Das `#:category`-Schlagwort kann entfernt werden, weil es nur zur Erstellung der LilyPond-Dokumentation eingesetzt wird und keine Bedeutung für selbstdefinierte Befehle hat.

    (define-markup-command (draw-double-line layout props dest)
      (number-pair?)
      #:properties ((thickness 1))
      "..documentation.."
      (let ((th (* (ly:output-def-lookup layout 'line-thickness)
                   thickness))
            (x (car dest))
            (y (cdr dest)))
        (make-line-stencil th 0 0 x y)))

Dann braucht man eine Eigenschaft, um den Abstand zwischen den zwei Linien zu definieren, als `line-gap` bezeichnet und etwa mit dem Standardwert 0.6:

    (define-markup-command (draw-double-line layout props dest)
      (number-pair?)
      #:properties ((thickness 1)
                    (line-gap 0.6))
      "..documentation.."
      ...

Schließlich wird der Code, der die zwei Linien zeichnet, hinzugefügt. Zwei Aufrufe an `make-line-stencil` werden benutzt, um beide Linien zu zeichnen, und die beiden sich daraus ergebenden Stencils werden mit `ly:stencil-add` kombiniert:

>     #(define-markup-command (my-draw-line layout props dest)
>       (number-pair?)
>       #:properties ((thickness 1)
>                     (line-gap 0.6))
>       "..documentation.."
>       (let* ((th (* (ly:output-def-lookup layout 'line-thickness)
>                     thickness))
>              (dx (car dest))
>              (dy (cdr dest))
>              (w (/ line-gap 2.0))
>              (x (cond ((= dx 0) w)
>                       ((= dy 0) 0)
>                       (else (/ w (sqrt (+ 1 (* (/ dx dy) (/ dx dy))))))))
>              (y (* (if (< (* dx dy) 0) 1 -1)
>                    (cond ((= dy 0) w)
>                          ((= dx 0) 0)
>                          (else (/ w (sqrt (+ 1 (* (/ dy dx) (/ dy dx))))))))))
>          (ly:stencil-add (make-line-stencil th x y (+ dx x) (+ dy y))
>                          (make-line-stencil th (- x) (- y) (- dx x) (- dy y)))))

>     \markup \my-draw-line #'(4 . 3)
>     \markup \override #'(line-gap . 1.2) \my-draw-line #'(4 . 3)

#### LilyPond – Extending v2.22.2 (stabiler Zweig).