[ << Interfaces for programmers ] | [Top][Contents][Index] | [ LilyPond Scheme interfaces >> ] |
[ < How markups work internally ] | [ Up : Markup functions ] | [ Markup command definition syntax > ] |
2.5.3 New markup command definition
This section discusses the definition of new markup commands.
Markup command definition syntax | ||
On properties | ||
A complete example | ||
Adapting builtin commands | ||
Converting markups to strings |
[ << Interfaces for programmers ] | [Top][Contents][Index] | [ LilyPond Scheme interfaces >> ] |
[ < New markup command definition ] | [ Up : New markup command definition ] | [ On properties > ] |
Markup command definition syntax
New markup commands can be defined using the
define-markup-command
Scheme macro, at top-level.
(define-markup-command (command-name layout props arg1 arg2 …) (arg1-type? arg2-type? …) [ #:properties ((property1 default-value1) …) ] [ #:as-string expression ] …command body…) |
The arguments are
-
command-name
the markup command name
-
layout
the ‘layout’ definition.
-
props
a list of associative lists, containing all active properties.
-
argi
ith command argument
-
argi-type?
a type predicate for the ith argument
If the command uses properties from the props
arguments,
the #:properties
keyword can be used to specify which
properties are used along with their default values.
Arguments are distinguished according to their type:
- a markup, corresponding to type predicate
markup?
; - a list of markups, corresponding to type predicate
markup-list?
; - any other scheme object, corresponding to type predicates such as
list?
,number?
,boolean?
, etc.
There is no limitation on the order of arguments (after the
standard layout
and props
arguments). However,
markup functions taking a markup as their last argument are
somewhat special as you can apply them to a markup list, and the
result is a markup list where the markup function (with the
specified leading arguments) has been applied to every element of
the original markup list.
Since replicating the leading arguments for applying a markup function to a markup list is cheap mostly for Scheme arguments, you avoid performance pitfalls by just using Scheme arguments for the leading arguments of markup functions that take a markup as their last argument.
Markup commands have a rather complex life cycle. The body of a
markup command definition is responsible for converting the
arguments of the markup command into a stencil expression which is
returned. Quite often this is accomplished by calling the
interpret-markup
function on a markup expression, passing
the layout and props arguments on to it. Those
arguments are usually only known at a very late stage in
typesetting. Markup expressions have their components assembled
into markup expressions already when \markup
in a LilyPond
expression or the markup
macro in Scheme is expanded. The
evaluation and typechecking of markup command arguments happens at
the time \markup
/markup
are interpreted.
But the actual conversion of markup expressions into stencil
expressions by executing the markup function bodies only happens
when interpret-markup
is called on a markup expression.
[ << Interfaces for programmers ] | [Top][Contents][Index] | [ LilyPond Scheme interfaces >> ] |
[ < Markup command definition syntax ] | [ Up : New markup command definition ] | [ A complete example > ] |
On properties
The layout
and props
arguments of markup commands bring a
context for the markup interpretation: font size, line width, etc.
The layout
argument allows access to properties defined in
paper
blocks, using the ly:output-def-lookup
function.
For instance, the line width (the same as the one used in scores) is
read using:
(ly:output-def-lookup layout 'line-width)
The props
argument makes some properties accessible to markup
commands. For instance, when a book title markup is interpreted, all
the variables defined in the \header
block are automatically
added to props
, so that the book title markup can access the book
title, composer, etc. It is also a way to configure the behavior of a
markup command: for example, when a command uses font size during
processing, the font size is read from props
rather than having a
font-size
argument. The caller of a markup command may change
the value of the font size property in order to change the behavior.
Use the #:properties
keyword of define-markup-command
to
specify which properties shall be read from the props
arguments.
The example in next section illustrates how to access and override properties in a markup command.
[ << Interfaces for programmers ] | [Top][Contents][Index] | [ LilyPond Scheme interfaces >> ] |
[ < On properties ] | [ Up : New markup command definition ] | [ Adapting builtin commands > ] |
A complete example
The following example defines a markup command to draw a double box around a piece of text.
Firstly, we need to build an approximative result using markups.
Consulting the
Text markup commands shows us the \box
command is useful:
\markup \box \box HELLO
Now, we consider that more padding between the text and the boxes is
preferable. According to the \box
documentation, this command
uses a box-padding
property, which defaults to 0.2. The
documentation also mentions how to override it:
\markup \box \override #'(box-padding . 0.6) \box A
Then, the padding between the two boxes is considered too small, so we override it too:
\markup \override #'(box-padding . 0.4) \box \override #'(box-padding . 0.6) \box A
Repeating this lengthy markup would be painful. This is where a markup
command is needed. Thus, we write a double-box
markup command,
taking one argument (the text). This draws the two boxes, with some
padding.
#(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 }#})) |
or, equivalently
#(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
is the name of the command argument, and markup?
its
type: it identifies it as a markup. The interpret-markup
function is used in most of markup commands: it builds a stencil, using
layout
, props
, and a markup. In the second case, this
markup is built using the markup
scheme macro, see Markup construction in Scheme. The transformation from \markup
expression to scheme markup expression is straight-forward.
The new command can be used as follow:
\markup \double-box A
It would be nice to make the double-box
command customizable:
here, the box-padding
values are hard coded, and cannot be
changed by the user. Also, it would be better to distinguish the
padding between the two boxes, from the padding between the inner box
and the text. So we will introduce a new property,
inter-box-padding
, for the padding between the two boxes. The
box-padding
will be used for the inner padding. The new code is
now as follows:
#(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 } #})) |
Again, the equivalent version using the markup macro would be:
#(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))) |
Here, the #:properties
keyword is used so that the
inter-box-padding
and box-padding
properties are read from
the props
argument, and default values are given to them if the
properties are not defined.
Then, these values are used to override the box-padding
properties used by the two \box
commands. Note the backquote and
the comma in the \override
argument: they allow you to introduce
a variable value into a literal expression.
Now, the command can be used in a markup, and the boxes padding be customized:
#(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
[ << Interfaces for programmers ] | [Top][Contents][Index] | [ LilyPond Scheme interfaces >> ] |
[ < A complete example ] | [ Up : New markup command definition ] | [ Converting markups to strings > ] |
Adapting builtin commands
A good way to start writing a new markup command, is to take example on a builtin one. Most of the markup commands provided with LilyPond can be found in file ‘scm/define-markup-commands.scm’.
For instance, we would like to adapt the \draw-line
command, to
draw a double line instead. The \draw-line
command is defined as
follow (documentation stripped):
(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))) |
To define a new command based on an existing one, copy the definition,
and change the command name. The #:category
keyword can be
safely removed, as it is only used for generating LilyPond
documentation, and is of no use for user-defined markup commands.
(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))) |
Then, a property for setting the gap between two lines is added, called
line-gap
, defaulting, e.g., to 0.6:
(define-markup-command (draw-double-line layout props dest) (number-pair?) #:properties ((thickness 1) (line-gap 0.6)) "…documentation…" … |
Finally, the code for drawing two lines is added. Two calls to
make-line-stencil
are used to draw the lines, and the resulting
stencils are combined using ly:stencil-add
:
#(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)
[ << Interfaces for programmers ] | [Top][Contents][Index] | [ LilyPond Scheme interfaces >> ] |
[ < Adapting builtin commands ] | [ Up : New markup command definition ] | [ New markup list command definition > ] |
Converting markups to strings
Markups are occasionally converted to plain strings, such as when
outputting PDF metadata based on the title
header field or
for converting lyrics to MIDI. This conversion is inherently
lossy, but tries to as accurate as feasible. The function used
for this is markup->string
.
composerName = \markup \box "Arnold Schönberg" \markup \composerName \markup \typewriter #(markup->string composerName)
For custom markup commands, the default behavior is to convert all markup or markup list arguments first, and join the results by spaces.
#(define-markup-command (authors-and layout props authorA authorB) (markup? markup?) (interpret-markup layout props #{ \markup \fill-line { \box #authorA and \box #authorB } #})) defaultBehavior = \markup \authors-and "Bertolt Brecht" "Kurt Weill" \markup \defaultBehavior \markup \typewriter #(markup->string defaultBehavior)
markup->string
can also receive the named arguments
#:layout layout
and #:props props
, with
the same meaning as in the definition of a markup command.
However, they are optional because they cannot always be provided
(such as the layout
argument when converting to MIDI).
To support special conversions in custom markup commands, the
#:as-string
parameter can be given to
define-markup-command
. It expects an expression, which is
evaluated by markup->string
in order to yield the string
result.
#(define-markup-command (authors-and layout props authorA authorB) (markup? markup?) #:as-string (format #f "~a and ~a" (markup->string authorA #:layout layout #:props props) (markup->string authorB #:layout layout #:props props)) (interpret-markup layout props #{ \markup \fill-line { \box #authorA and \box #authorB } #})) customized = \markup \authors-and "Bertolt Brecht" "Kurt Weill" \markup \customized \markup \typewriter #(markup->string customized)
Within the expression, the same bindings are available as in the
main markup command body, namely layout
and props
,
the command argument, and optionally the properties.
#(define-markup-command (authors-and layout props authorA authorB) (markup? markup?) #:properties ((author-separator " and ")) #:as-string (format #f "~a~a~a" (markup->string authorA #:layout layout #:props props) (markup->string author-separator #:layout layout #:props props) (markup->string authorB #:layout layout #:props props)) (interpret-markup layout props #{ \markup { \box #authorA #author-separator \box #authorB } #})) customized = \markup \override #'(author-separator . ", ") \authors-and "Bertolt Brecht" "Kurt Weill" \markup \customized \markup \typewriter #(markup->string customized)
Most often, a custom handler only needs to call
markup->string
recursively on certain arguments, as
demonstrated above. However, it can also make use of the
layout
and props
directly, in the same way as in the
main body. Care must be taken that the layout
argument is
#f
if no #:layout
has been given to
markup->string
. The props
argument defaults to the
empty list.
[ << Interfaces for programmers ] | [Top][Contents][Index] | [ LilyPond Scheme interfaces >> ] |
[ < Adapting builtin commands ] | [ Up : New markup command definition ] | [ New markup list command definition > ] |