]> git.donarmstrong.com Git - lilypond.git/blobdiff - scm/define-markup-commands.scm
[notation reference] One more line length fix.
[lilypond.git] / scm / define-markup-commands.scm
index 690eef10785fcfc5df3207e9b0ce2bac44588834..d39cfab0ca69e33c91aa2fa6cb1a88aff304681e 100644 (file)
@@ -1,6 +1,6 @@
 ;;;; This file is part of LilyPond, the GNU music typesetter.
 ;;;;
-;;;; Copyright (C) 2000--2009  Han-Wen Nienhuys <hanwen@xs4all.nl>
+;;;; Copyright (C) 2000--2011  Han-Wen Nienhuys <hanwen@xs4all.nl>
 ;;;;                  Jan Nieuwenhuizen <janneke@gnu.org>
 ;;;;
 ;;;; LilyPond is free software: you can redistribute it and/or modify
 ;;;
 ;;; Markup commands which are part of LilyPond, are defined
 ;;; in the (lily) module, which is the current module in this file,
-;;; using the `define-builtin-markup-command' macro.
+;;; using the `define-markup-command' macro.
 ;;;
 ;;; Usage:
 ;;;
-;;; (define-builtin-markup-command (command-name layout props args...)
+;;; (define-markup-command (command-name layout props args...)
 ;;;   args-signature
-;;;   category
-;;;   property-bindings
+;;;   [ #:category category ]
+;;;   [ #:properties property-bindings ]
 ;;;   documentation-string
 ;;;   ..body..)
 ;;;
 ;;;     using `chain-assoc-get' (more on that below)
 ;;;
 ;;;   args...
-;;;     the command arguments. There are restrictions on the
-;;;     possible arguments for a markup command.
-;;;     First, arguments are distingued according to their type:
-;;;       1) a markup (or a string), corresponding to type predicate `markup?'
-;;;       2) a list of markups, corresponding to type predicate `markup-list?'
-;;;       3) any scheme object, corresponding to type predicates such as
-;;;       `list?', 'number?', 'boolean?', etc.
-;;;     The supported arrangements of arguments, according to their type, are:
-;;;       - no argument
-;;;       - markup
-;;;       - scheme
-;;;       - markup, markup
-;;;       - markup-list
-;;;       - scheme, scheme
-;;;       - scheme, markup
-;;;       - scheme, scheme, markup
-;;;       - scheme, scheme, markup, markup
-;;;       - scheme, markup, markup
-;;;       - scheme, scheme, scheme
-;;;     This combinations are hard-coded in the lexer and in the parser
-;;;     (lily/lexer.ll and lily/parser.yy)
+;;;     the command arguments.
+;;;     There is no limitation on the order of command 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.
 ;;;
 ;;;   args-signature
-;;;     the arguments signature, i.e. a list of type predicates which
+;;;     the arguments signature, i.e., a list of type predicates which
 ;;;     are used to type check the arguments, and also to define the general
 ;;;     argument types (markup, markup-list, scheme) that the command is
 ;;;     expecting.
 ;;;
 ;;;   category
 ;;;     for documentation purpose, builtin markup commands are grouped by
-;;;     category. This can be any symbol. When documentation is generated,
+;;;     category.  This can be any symbol.  When documentation is generated,
 ;;;     the symbol is converted to a capitalized string, where hyphens are
 ;;;     replaced by spaces.
 ;;;
 ;;;   property-bindings
 ;;;     this is used both for documentation generation, and to ease
-;;;     programming the command itself. It is list of
+;;;     programming the command itself.  It is list of
 ;;;        (property-name default-value)
 ;;;     or (property-name)
-;;;     elements. Each property is looked-up in the `props' argument, and
+;;;     elements.  Each property is looked-up in the `props' argument, and
 ;;;     the symbol naming the property is bound to its value.
 ;;;     When the property is not found in `props', then the symbol is bound
-;;;     to the given default value. When no default value is given, #f is
+;;;     to the given default value.  When no default value is given, #f is
 ;;;     used instead.
 ;;;     Thus, using the following property bindings:
 ;;;       ((thickness 0.1)
 ;;;         ..body..)
 ;;;     When a command `B' internally calls an other command `A', it may
 ;;;     desirable to see in `B' documentation all the properties and
-;;;     default values used by `A'. In that case, add `A-markup' to the
-;;;     property-bindings of B. (This is used when generating
+;;;     default values used by `A'.  In that case, add `A-markup' to the
+;;;     property-bindings of B.  (This is used when generating
 ;;;     documentation, but won't create bindings.)
 ;;;
 ;;;   documentation-string
 ;;;     the command documentation string (used to generate manuals)
 ;;;
 ;;;   body
-;;;     the command body. The function is supposed to return a stencil.
+;;;     the command body.  The function is supposed to return a stencil.
 ;;;
 ;;; Each markup command definition shall have a documentation string
 ;;; with description, syntax and example.
 ;; geometric shapes
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
-(define-builtin-markup-command (draw-line layout props dest)
+(define-markup-command (draw-line layout props dest)
   (number-pair?)
-  graphic
-  ((thickness 1))
+  #:category graphic
+  #:properties ((thickness 1))
   "
 @cindex drawing lines within text
 
@@ -147,10 +139,35 @@ A simple line.
         (y (cdr dest)))
     (make-line-stencil th 0 0 x y)))
 
-(define-builtin-markup-command (draw-circle layout props radius thickness filled)
-  (number? number? boolean?)
-  graphic
+(define-markup-command (draw-hline layout props)
   ()
+  #:category graphic
+  #:properties ((draw-line-markup)
+                (line-width)
+                (span-factor 1))
+  "
+@cindex drawing a line across a page
+
+Draws a line across a page, where the property @code{span-factor}
+controls what fraction of the page is taken up.
+@lilypond[verbatim,quote]
+\\markup {
+  \\column {
+    \\draw-hline
+    \\override #'(span-factor . 1/3)
+    \\draw-hline
+  }
+}
+@end lilypond"
+  (interpret-markup layout
+                    props
+                    (markup #:draw-line (cons (* line-width
+                                                  span-factor)
+                                               0))))
+
+(define-markup-command (draw-circle layout props radius thickness filled)
+  (number? number? boolean?)
+  #:category graphic
   "
 @cindex drawing circles within text
 
@@ -166,12 +183,12 @@ optionally filled.
 @end lilypond"
   (make-circle-stencil radius thickness filled))
 
-(define-builtin-markup-command (triangle layout props filled)
+(define-markup-command (triangle layout props filled)
   (boolean?)
-  graphic
-  ((thickness 0.1)
-   (font-size 0)
-   (baseline-skip 2))
+  #:category graphic
+  #:properties ((thickness 0.1)
+               (font-size 0)
+               (baseline-skip 2))
   "
 @cindex drawing triangles within text
 
@@ -195,12 +212,12 @@ A triangle, either filled or empty.
      (cons 0 ex)
      (cons 0 (* .86 ex)))))
 
-(define-builtin-markup-command (circle layout props arg)
+(define-markup-command (circle layout props arg)
   (markup?)
-  graphic
-  ((thickness 1)
-   (font-size 0)
-   (circle-padding 0.2))
+  #:category graphic
+  #:properties ((thickness 1)
+               (font-size 0)
+               (circle-padding 0.2))
   "
 @cindex circling text
 
@@ -221,10 +238,9 @@ thickness and padding around the markup.
          (m (interpret-markup layout props arg)))
     (circle-stencil m th pad)))
 
-(define-builtin-markup-command (with-url layout props url arg)
+(define-markup-command (with-url layout props url arg)
   (string? markup?)
-  graphic
-  ()
+  #:category graphic
   "
 @cindex inserting URL links into text
 
@@ -248,10 +264,65 @@ the PDF backend.
 
     (ly:stencil-add (ly:make-stencil url-expr xextent yextent) stil)))
 
-(define-builtin-markup-command (beam layout props width slope thickness)
+(define-markup-command (page-link layout props page-number arg)
+  (number? markup?)
+  #:category other
+  "
+@cindex referencing page numbers in text
+
+Add a link to the page @var{page-number} around @var{arg}.  This only works
+in the PDF backend.
+
+@lilypond[verbatim,quote]
+\\markup {
+  \\page-link #2  { \\italic { This links to page 2... } }
+}
+@end lilypond"
+  (let* ((stil (interpret-markup layout props arg))
+        (xextent (ly:stencil-extent stil X))
+        (yextent (ly:stencil-extent stil Y))
+        (old-expr (ly:stencil-expr stil))
+        (link-expr (list 'page-link page-number `(quote ,xextent) `(quote ,yextent))))
+
+    (ly:stencil-add (ly:make-stencil link-expr xextent yextent) stil)))
+
+(define-markup-command (with-link layout props label arg)
+  (symbol? markup?)
+  #:category other
+  "
+@cindex referencing page labels in text
+
+Add a link to the page holding label @var{label} around @var{arg}.  This
+only works in the PDF backend.
+
+@lilypond[verbatim,quote]
+\\markup {
+  \\with-link #\"label\" {
+    \\italic { This links to the page containing the label... }
+  }
+}
+@end lilypond"
+  (let* ((arg-stencil (interpret-markup layout props arg))
+         (x-ext (ly:stencil-extent arg-stencil X))
+         (y-ext (ly:stencil-extent arg-stencil Y)))
+    (ly:make-stencil
+     `(delay-stencil-evaluation
+       ,(delay (ly:stencil-expr
+                (let* ((table (ly:output-def-lookup layout 'label-page-table))
+                       (page-number (if (list? table)
+                                        (assoc-get label table)
+                                        #f))
+                       (link-expr (list 'page-link page-number
+                                        `(quote ,x-ext) `(quote ,y-ext))))
+                  (ly:stencil-add (ly:make-stencil link-expr x-ext y-ext)
+arg-stencil)))))
+     x-ext
+     y-ext)))
+
+
+(define-markup-command (beam layout props width slope thickness)
   (number? number? number?)
-  graphic
-  ()
+  #:category graphic
   "
 @cindex drawing beams within text
 
@@ -277,41 +348,42 @@ Create a beam with the specified parameters.
      (cons (+ (- half) (car yext))
           (+ half (cdr yext))))))
 
-(define-builtin-markup-command (underline layout props arg)
+(define-markup-command (underline layout props arg)
   (markup?)
-  font
-  ((thickness 1))
+  #:category font
+  #:properties ((thickness 1) (offset 2))
   "
 @cindex underlining text
 
 Underline @var{arg}.  Looks at @code{thickness} to determine line
-thickness and y-offset.
+thickness, and @code{offset} to determine line y-offset.
 
 @lilypond[verbatim,quote]
-\\markup {
-  default
-  \\hspace #2
-  \\override #'(thickness . 2)
-  \\underline {
-    underline
-  }
+\\markup \\fill-line {
+  \\underline \"underlined\"
+  \\override #'(offset . 5)
+  \\override #'(thickness . 1)
+  \\underline \"underlined\"
+  \\override #'(offset . 1)
+  \\override #'(thickness . 5)
+  \\underline \"underlined\"
 }
 @end lilypond"
-  (let* ((thick (* (ly:output-def-lookup layout 'line-thickness)
-                   thickness))
+  (let* ((thick (ly:output-def-lookup layout 'line-thickness))
+         (underline-thick (* thickness thick))
          (markup (interpret-markup layout props arg))
          (x1 (car (ly:stencil-extent markup X)))
          (x2 (cdr (ly:stencil-extent markup X)))
-         (y (* thick -2))
-         (line (make-line-stencil thick x1 y x2 y)))
+         (y (* thick (- offset)))
+         (line (make-line-stencil underline-thick x1 y x2 y)))
     (ly:stencil-add markup line)))
 
-(define-builtin-markup-command (box layout props arg)
+(define-markup-command (box layout props arg)
   (markup?)
-  font
-  ((thickness 1)
-   (font-size 0)
-   (box-padding 0.2))
+  #:category font
+  #:properties ((thickness 1)
+               (font-size 0)
+               (box-padding 0.2))
   "
 @cindex enclosing text within a box
 
@@ -332,10 +404,9 @@ thickness and padding around the markup.
          (m (interpret-markup layout props arg)))
     (box-stencil m th pad)))
 
-(define-builtin-markup-command (filled-box layout props xext yext blot)
+(define-markup-command (filled-box layout props xext yext blot)
   (number-pair? number-pair? number?)
-  graphic
-  ()
+  #:category graphic
   "
 @cindex drawing solid boxes within text
 @cindex drawing boxes with rounded corners
@@ -361,13 +432,13 @@ circle of diameter@tie{}0 (i.e., sharp corners).
   (ly:round-filled-box
    xext yext blot))
 
-(define-builtin-markup-command (rounded-box layout props arg)
+(define-markup-command (rounded-box layout props arg)
   (markup?)
-  graphic
-  ((thickness 1)
-   (corner-radius 1)
-   (font-size 0)
-   (box-padding 0.5))
+  #:category graphic
+  #:properties ((thickness 1)
+               (corner-radius 1)
+               (font-size 0)
+               (box-padding 0.5))
   "@cindex enclosing text in a box with rounded corners
    @cindex drawing boxes with rounded corners around text
 Draw a box with rounded corners around @var{arg}.  Looks at @code{thickness},
@@ -390,10 +461,9 @@ c,8. c16 c4 r
     (ly:stencil-add (rounded-box-stencil m th pad corner-radius)
                     m)))
 
-(define-builtin-markup-command (rotate layout props ang arg)
+(define-markup-command (rotate layout props ang arg)
   (number? markup?)
-  align
-  ()
+  #:category align
   "
 @cindex rotating text
 
@@ -412,10 +482,9 @@ Rotate object with @var{ang} degrees around its center.
   (let* ((stil (interpret-markup layout props arg)))
     (ly:stencil-rotate stil ang 0 0)))
 
-(define-builtin-markup-command (whiteout layout props arg)
+(define-markup-command (whiteout layout props arg)
   (markup?)
-  other
-  ()
+  #:category other
   "
 @cindex adding a white background to text
 
@@ -430,10 +499,9 @@ Provide a white background for @var{arg}.
 @end lilypond"
   (stencil-whiteout (interpret-markup layout props arg)))
 
-(define-builtin-markup-command (pad-markup layout props amount arg)
+(define-markup-command (pad-markup layout props amount arg)
   (number? markup?)
-  align
-  ()
+  #:category align
   "
 @cindex padding text
 @cindex putting space around text
@@ -467,10 +535,9 @@ Add space around a markup object.
 ;; space
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
-(define-builtin-markup-command (strut layout props)
-  ()
-  other
+(define-markup-command (strut layout props)
   ()
+  #:category other
   "
 @cindex creating vertical spaces in text
 
@@ -482,10 +549,10 @@ Create a box of the same height as the space in the current font."
                     )))
 
 ;; todo: fix negative space
-(define-builtin-markup-command (hspace layout props amount)
+(define-markup-command (hspace layout props amount)
   (number?)
-  align
-  ()
+  #:category align
+  #:properties ((word-space))
   "
 @cindex creating horizontal spaces in text
 
@@ -500,15 +567,15 @@ Create an invisible object taking up horizontal space @var{amount}.
   three
 }
 @end lilypond"
-  (if (> amount 0)
-      (ly:make-stencil "" (cons 0 amount) '(-1 . 1))
-      (ly:make-stencil "" (cons amount amount) '(-1 . 1))))
+  (let ((corrected-space (- amount word-space)))
+    (if (> corrected-space 0)
+       (ly:make-stencil "" (cons 0 corrected-space) '(0 . 0))
+       (ly:make-stencil "" (cons corrected-space corrected-space) '(0 . 0)))))
 
 ;; todo: fix negative space
-(define-builtin-markup-command (vspace layout props amount)
+(define-markup-command (vspace layout props amount)
  (number?)
- align
- ()
+ #:category align
  "
 @cindex creating vertical spaces in text
 
@@ -528,18 +595,17 @@ of @var{amount} multiplied by 3.
 @end lilypond"
   (let ((amount (* amount 3.0)))
     (if (> amount 0)
-        (ly:make-stencil "" (cons -1 1) (cons 0 amount))
-        (ly:make-stencil "" (cons -1 1) (cons amount amount)))))
+        (ly:make-stencil "" (cons 0 0) (cons 0 amount))
+        (ly:make-stencil "" (cons 0 0) (cons amount amount)))))
 
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;; importing graphics.
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
-(define-builtin-markup-command (stencil layout props stil)
+(define-markup-command (stencil layout props stil)
   (ly:stencil?)
-  other
-  ()
+  #:category other
   "
 @cindex importing stencils into text
 
@@ -567,10 +633,9 @@ Use a stencil as markup.
 
        #f)))
 
-(define-builtin-markup-command (epsfile layout props axis size file-name)
+(define-markup-command (epsfile layout props axis size file-name)
   (number? number? string?)
-  graphic
-  ()
+  #:category graphic
   "
 @cindex inlining an Encapsulated PostScript image
 
@@ -590,10 +655,9 @@ Inline an EPS image.  The image is scaled along @var{axis} to
       (eps-file->stencil axis size file-name)
       ))
 
-(define-builtin-markup-command (postscript layout props str)
+(define-markup-command (postscript layout props str)
   (string?)
-  graphic
-  ()
+  #:category graphic
   "
 @cindex inserting PostScript directly into text
 This inserts @var{str} directly into the output as a PostScript
@@ -622,7 +686,7 @@ rings = \\markup {
   ;; FIXME
   (ly:make-stencil
    (list 'embedded-ps
-        (format "
+        (format #f "
 gsave currentpoint translate
 0.1 setlinewidth
  ~a
@@ -631,10 +695,141 @@ grestore
                 str))
    '(0 . 0) '(0 . 0)))
 
-(define-builtin-markup-command (score layout props score)
+(define-markup-command (path layout props thickness commands) (number? list?)
+  #:category graphic
+  #:properties ((line-cap-style 'round)
+               (line-join-style 'round)
+               (filled #f))
+  "
+@cindex paths, drawing
+@cindex drawing paths
+Draws a path with line thickness @var{thickness} according to the
+directions given in @var{commands}.  @var{commands} is a list of
+lists where the @code{car} of each sublist is a drawing command and
+the @code{cdr} comprises the associated arguments for each command.
+
+Line-cap styles and line-join styles may be customized by
+overriding the @code{line-cap-style} and @code{line-join-style}
+properties, respectively.  Available line-cap styles are
+@code{'butt}, @code{'round}, and @code{'square}.  Available
+line-join styles are @code{'miter}, @code{'round}, and
+@code{'bevel}.
+
+The property @code{filled} specifies whether or not the path is
+filled with color.
+
+There are seven commands available to use in the list
+@code{commands}: @code{moveto}, @code{rmoveto}, @code{lineto},
+@code{rlineto}, @code{curveto}, @code{rcurveto}, and
+@code{closepath}.  Note that the commands that begin with @emph{r}
+are the relative variants of the other three commands.
+
+The commands @code{moveto}, @code{rmoveto}, @code{lineto}, and
+@code{rlineto} take 2 arguments; they are the X and Y coordinates
+for the destination point.
+
+The commands @code{curveto} and @code{rcurveto} create cubic
+Bézier curves, and take 6 arguments; the first two are the X and Y
+coordinates for the first control point, the second two are the X
+and Y coordinates for the second control point, and the last two
+are the X and Y coordinates for the destination point.
+
+The @code{closepath} command takes zero arguments and closes the
+current subpath in the active path.
+
+Note that a sequence of commands @emph{must} begin with a
+@code{moveto} or @code{rmoveto} to work with the SVG output.
+
+@lilypond[verbatim,quote]
+samplePath =
+  #'((moveto 0 0)
+     (lineto -1 1)
+     (lineto 1 1)
+     (lineto 1 -1)
+     (curveto -5 -5 -5 5 -1 0)
+     (closepath))
+
+\\markup {
+  \\path #0.25 #samplePath
+}
+@end lilypond"
+  (let* ((half-thickness (/ thickness 2))
+        (current-point '(0 . 0))
+        (set-point (lambda (lst) (set! current-point lst)))
+        (relative? (lambda (x)
+                     (string-prefix? "r" (symbol->string (car x)))))
+        ;; For calculating extents, we want to modify the command
+        ;; list so that all coordinates are absolute.
+        (new-commands (map (lambda (x)
+                             (cond
+                               ;; for rmoveto, rlineto
+                               ((and (relative? x) (eq? 3 (length x)))
+                                (let ((cp (cons
+                                            (+ (car current-point)
+                                               (second x))
+                                            (+ (cdr current-point)
+                                               (third x)))))
+                                  (set-point cp)
+                                  (list (car cp)
+                                        (cdr cp))))
+                               ;; for rcurveto
+                               ((and (relative? x) (eq? 7 (length x)))
+                                (let* ((old-cp current-point)
+                                       (cp (cons
+                                             (+ (car old-cp)
+                                                (sixth x))
+                                             (+ (cdr old-cp)
+                                                (seventh x)))))
+                                  (set-point cp)
+                                  (list (+ (car old-cp) (second x))
+                                        (+ (cdr old-cp) (third x))
+                                        (+ (car old-cp) (fourth x))
+                                        (+ (cdr old-cp) (fifth x))
+                                        (car cp)
+                                        (cdr cp))))
+                               ;; for moveto, lineto
+                               ((eq? 3 (length x))
+                                (set-point (cons (second x)
+                                                 (third x)))
+                                (drop x 1))
+                               ;; for curveto
+                               ((eq? 7 (length x))
+                                (set-point (cons (sixth x)
+                                                 (seventh x)))
+                                (drop x 1))
+                               ;; keep closepath for filtering;
+                               ;; see `without-closepath'.
+                               (else x)))
+                           commands))
+        ;; path-min-max does not accept 0-arg lists,
+        ;; and since closepath does not affect extents, filter
+        ;; out those commands here.
+        (without-closepath (filter (lambda (x)
+                                     (not (equal? 'closepath (car x))))
+                                   new-commands))
+        (extents (path-min-max
+                   ;; set the origin to the first moveto
+                   (list (list-ref (car without-closepath) 0)
+                         (list-ref (car without-closepath) 1))
+                   without-closepath))
+        (X-extent (cons (list-ref extents 0) (list-ref extents 1)))
+        (Y-extent (cons (list-ref extents 2) (list-ref extents 3)))
+        (command-list (fold-right append '() commands)))
+
+    ;; account for line thickness
+    (set! X-extent (interval-widen X-extent half-thickness))
+    (set! Y-extent (interval-widen Y-extent half-thickness))
+
+    (ly:make-stencil
+      `(path ,thickness `(,@',command-list)
+            ',line-cap-style ',line-join-style ,filled)
+      X-extent
+      Y-extent)))
+
+(define-markup-command (score layout props score)
   (ly:score?)
-  music
-  ((baseline-skip))
+  #:category music
+  #:properties ((baseline-skip))
   "
 @cindex inserting music into text
 
@@ -667,13 +862,15 @@ Inline an image of music.
       indent = 0.0\\cm
       \\context {
         \\Score
-        \\override RehearsalMark #'break-align-symbols =
-          #'(time-signature key-signature)
-        \\override RehearsalMark #'self-alignment-X = #LEFT
+        \\override RehearsalMark
+          #'break-align-symbols = #'(time-signature key-signature)
+        \\override RehearsalMark
+          #'self-alignment-X = #LEFT
       }
       \\context {
         \\Staff
-        \\override TimeSignature #'break-align-anchor-alignment = #LEFT
+        \\override TimeSignature
+          #'break-align-anchor-alignment = #LEFT
       }
     }
   }
@@ -690,10 +887,9 @@ Inline an image of music.
          (ly:warning (_"no systems found in \\score markup, does it have a \\layout block?"))
          empty-stencil))))
 
-(define-builtin-markup-command (null layout props)
-  ()
-  other
+(define-markup-command (null layout props)
   ()
+  #:category other
   "
 @cindex creating empty text objects
 
@@ -710,10 +906,9 @@ An empty markup with extents of a single point.
 ;; basic formatting.
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
-(define-builtin-markup-command (simple layout props str)
+(define-markup-command (simple layout props str)
   (string?)
-  font
-  ()
+  #:category font
   "
 @cindex simple text strings
 
@@ -732,10 +927,10 @@ the use of @code{\\simple} is unnecessary.
 @end lilypond"
   (interpret-markup layout props str))
 
-(define-builtin-markup-command (tied-lyric layout props str)
+(define-markup-command (tied-lyric layout props str)
   (string?)
-  music
-  ()
+  #:category music
+  #:properties ((word-space))
   "
 @cindex simple text strings with tie characters
 
@@ -748,33 +943,34 @@ Like simple-markup, but use tie characters for @q{~} tilde symbols.
 @end lilypond"
   (if (string-contains str "~")
       (let*
-         ((parts (string-split str #\~))
-          (tie-str (ly:wide-char->utf-8 #x203f))
+         ((half-space (/ word-space 2))
+          (parts (string-split str #\~))
+          (tie-str (markup #:hspace half-space
+                           #:musicglyph "ties.lyric"
+                           #:hspace half-space))
           (joined  (list-join parts tie-str))
           (join-stencil (interpret-markup layout props tie-str))
           )
 
        (interpret-markup layout
-                         (prepend-alist-chain
-                          'word-space
-                          (/ (interval-length (ly:stencil-extent join-stencil X)) -3.5)
-                          props)
-                         (make-line-markup joined)))
-                          ;(map (lambda (s) (interpret-markup layout props s)) parts))
+                         props
+                         (make-concat-markup joined)))
       (interpret-markup layout props str)))
 
 (define-public empty-markup
   (make-simple-markup ""))
 
 ;; helper for justifying lines.
-(define (get-fill-space word-count line-width text-widths)
+(define (get-fill-space word-count line-width word-space text-widths)
   "Calculate the necessary paddings between each two adjacent texts.
-       The lengths of all texts are stored in @var{text-widths}.
-       The normal formula for the padding between texts a and b is:
-       padding = line-width/(word-count - 1) - (length(a) + length(b))/2
-       The first and last padding have to be calculated specially using the
-       whole length of the first or last text.
-       Return a list of paddings."
+  The lengths of all texts are stored in @var{text-widths}.
+  The normal formula for the padding between texts a and b is:
+  padding = line-width/(word-count - 1) - (length(a) + length(b))/2
+  The first and last padding have to be calculated specially using the
+  whole length of the first or last text.
+  All paddings are checked to be at least word-space, to ensure that
+  no texts collide.
+  Return a list of paddings."
   (cond
    ((null? text-widths) '())
 
@@ -783,23 +979,27 @@ Like simple-markup, but use tie characters for @q{~} tilde symbols.
     (cons
      (- (- (/ line-width (1- word-count)) (car text-widths))
        (/ (car (cdr text-widths)) 2))
-     (get-fill-space word-count line-width (cdr text-widths))))
+     (get-fill-space word-count line-width word-space (cdr text-widths))))
    ;; special case last padding
    ((= (length text-widths) 2)
     (list (- (/ line-width (1- word-count))
             (+ (/ (car text-widths) 2) (car (cdr text-widths)))) 0))
    (else
-    (cons
-     (- (/ line-width (1- word-count))
-       (/ (+ (car text-widths) (car (cdr text-widths))) 2))
-     (get-fill-space word-count line-width (cdr text-widths))))))
-
-(define-builtin-markup-command (fill-line layout props args)
+    (let ((default-padding
+            (- (/ line-width (1- word-count))
+               (/ (+ (car text-widths) (car (cdr text-widths))) 2))))
+      (cons
+       (if (> word-space default-padding)
+           word-space
+           default-padding)
+       (get-fill-space word-count line-width word-space (cdr text-widths)))))))
+
+(define-markup-command (fill-line layout props args)
   (markup-list?)
-  align
-  ((text-direction RIGHT)
-   (word-space 1)
-   (line-width #f))
+  #:category align
+  #:properties ((text-direction RIGHT)
+                (word-space 0.6)
+                (line-width #f))
   "Put @var{markups} in a horizontal line of width @var{line-width}.
 The markups are spaced or flushed to fill the entire line.
 If there are no arguments, return an empty stencil.
@@ -822,58 +1022,64 @@ If there are no arguments, return an empty stencil.
 }
 @end lilypond"
   (let* ((orig-stencils (interpret-markup-list layout props args))
-        (stencils
-         (map (lambda (stc)
-                (if (ly:stencil-empty? stc)
-                    point-stencil
-                    stc)) orig-stencils))
-        (text-widths
-         (map (lambda (stc)
-                (if (ly:stencil-empty? stc)
-                    0.0
-                    (interval-length (ly:stencil-extent stc X))))
-              stencils))
-        (text-width (apply + text-widths))
-        (word-count (length stencils))
-        (line-width (or line-width (ly:output-def-lookup layout 'line-width)))
-        (fill-space
-               (cond
-                       ((= word-count 1)
-                               (list
-                                       (/ (- line-width text-width) 2)
-                                       (/ (- line-width text-width) 2)))
-                       ((= word-count 2)
-                               (list
-                                       (- line-width text-width)))
-                       (else
-                               (get-fill-space word-count line-width text-widths))))
-        (fill-space-normal
-         (map (lambda (x)
-                (if (< x word-space)
-                    word-space
-                    x))
-              fill-space))
-
-        (line-stencils (if (= word-count 1)
-                           (list
-                            point-stencil
-                            (car stencils)
-                            point-stencil)
-                           stencils)))
-
-    (if (= text-direction LEFT)
-       (set! line-stencils (reverse line-stencils)))
+         (stencils
+          (map (lambda (stc)
+                 (if (ly:stencil-empty? stc)
+                     point-stencil
+                     stc)) orig-stencils))
+         (text-widths
+          (map (lambda (stc)
+                 (if (ly:stencil-empty? stc)
+                     0.0
+                     (interval-length (ly:stencil-extent stc X))))
+               stencils))
+         (text-width (apply + text-widths))
+         (word-count (length stencils))
+         (line-width (or line-width (ly:output-def-lookup layout 'line-width)))
+         (fill-space
+          (cond
+           ((= word-count 1)
+            (list
+             (/ (- line-width text-width) 2)
+             (/ (- line-width text-width) 2)))
+           ((= word-count 2)
+            (list
+             (- line-width text-width)))
+           (else
+            (get-fill-space word-count line-width word-space text-widths))))
+
+         (line-contents (if (= word-count 1)
+                            (list
+                             point-stencil
+                             (car stencils)
+                             point-stencil)
+                            stencils)))
 
     (if (null? (remove ly:stencil-empty? orig-stencils))
-       empty-stencil
-       (stack-stencils-padding-list X
-                                    RIGHT fill-space-normal line-stencils))))
-
-(define-builtin-markup-command (line layout props args)
+        empty-stencil
+        (begin
+          (if (= text-direction LEFT)
+              (set! line-contents (reverse line-contents)))
+          (set! line-contents
+                (stack-stencils-padding-list
+                 X RIGHT fill-space line-contents))
+          (if (> word-count 1)
+              ;; shift s.t. stencils align on the left edge, even if
+              ;; first stencil had negative X-extent (e.g. center-column)
+              ;; (if word-count = 1, X-extents are already normalized in
+              ;; the definition of line-contents)
+              (set! line-contents
+                    (ly:stencil-translate-axis
+                     line-contents
+                     (- (car (ly:stencil-extent (car stencils) X)))
+                     X)))
+          line-contents))))
+
+(define-markup-command (line layout props args)
   (markup-list?)
-  align
-  ((word-space)
-   (text-direction RIGHT))
+  #:category align
+  #:properties ((word-space)
+               (text-direction RIGHT))
   "Put @var{args} in a horizontal line.  The property @code{word-space}
 determines the space between markups in @var{args}.
 
@@ -891,10 +1097,9 @@ determines the space between markups in @var{args}.
      word-space
      (remove ly:stencil-empty? stencils))))
 
-(define-builtin-markup-command (concat layout props args)
+(define-markup-command (concat layout props args)
   (markup-list?)
-  align
-  ()
+  #:category align
   "
 @cindex concatenating text
 @cindex ligatures in text
@@ -986,11 +1191,11 @@ equivalent to @code{\"fi\"}.
                        X)))
             (reverse (cons line lines)))))))
 
-(define-builtin-markup-list-command (wordwrap-internal layout props justify args)
+(define-markup-list-command (wordwrap-internal layout props justify args)
   (boolean? markup-list?)
-  ((line-width #f)
-   (word-space)
-   (text-direction RIGHT))
+  #:properties ((line-width #f)
+               (word-space)
+               (text-direction RIGHT))
   "Internal markup list command used to define @code{\\justify} and @code{\\wordwrap}."
   (wordwrap-stencils (remove ly:stencil-empty?
                              (interpret-markup-list layout props args))
@@ -1000,11 +1205,11 @@ equivalent to @code{\"fi\"}.
                          (ly:output-def-lookup layout 'line-width))
                      text-direction))
 
-(define-builtin-markup-command (justify layout props args)
+(define-markup-command (justify layout props args)
   (markup-list?)
-  align
-  ((baseline-skip)
-   wordwrap-internal-markup-list)
+  #:category align
+  #:properties ((baseline-skip)
+               wordwrap-internal-markup-list)
   "
 @cindex justifying text
 
@@ -1025,11 +1230,11 @@ Use @code{\\override #'(line-width . @var{X})} to set the line width;
   (stack-lines DOWN 0.0 baseline-skip
                (wordwrap-internal-markup-list layout props #t args)))
 
-(define-builtin-markup-command (wordwrap layout props args)
+(define-markup-command (wordwrap layout props args)
   (markup-list?)
-  align
-  ((baseline-skip)
-   wordwrap-internal-markup-list)
+  #:category align
+  #:properties ((baseline-skip)
+               wordwrap-internal-markup-list)
   "Simple wordwrap.  Use @code{\\override #'(line-width . @var{X})} to set
 the line width, where @var{X} is the number of staff spaces.
 
@@ -1046,11 +1251,11 @@ the line width, where @var{X} is the number of staff spaces.
   (stack-lines DOWN 0.0 baseline-skip
               (wordwrap-internal-markup-list layout props #f args)))
 
-(define-builtin-markup-list-command (wordwrap-string-internal layout props justify arg)
+(define-markup-list-command (wordwrap-string-internal layout props justify arg)
   (boolean? string?)
-  ((line-width)
-   (word-space)
-   (text-direction RIGHT))
+  #:properties ((line-width)
+               (word-space)
+               (text-direction RIGHT))
   "Internal markup list command used to define @code{\\justify-string} and
 @code{\\wordwrap-string}."
   (let* ((para-strings (regexp-split
@@ -1073,11 +1278,11 @@ the line width, where @var{X} is the number of staff spaces.
                           list-para-words)))
     (apply append para-lines)))
 
-(define-builtin-markup-command (wordwrap-string layout props arg)
+(define-markup-command (wordwrap-string layout props arg)
   (string?)
-  align
-  ((baseline-skip)
-   wordwrap-string-internal-markup-list)
+  #:category align
+  #:properties ((baseline-skip)
+               wordwrap-string-internal-markup-list)
   "Wordwrap a string.  Paragraphs may be separated with double newlines.
 
 @lilypond[verbatim,quote]
@@ -1099,11 +1304,11 @@ the line width, where @var{X} is the number of staff spaces.
   (stack-lines DOWN 0.0 baseline-skip
                (wordwrap-string-internal-markup-list layout props #f arg)))
 
-(define-builtin-markup-command (justify-string layout props arg)
+(define-markup-command (justify-string layout props arg)
   (string?)
-  align
-  ((baseline-skip)
-   wordwrap-string-internal-markup-list)
+  #:category align
+  #:properties ((baseline-skip)
+               wordwrap-string-internal-markup-list)
   "Justify a string.  Paragraphs may be separated with double newlines
 
 @lilypond[verbatim,quote]
@@ -1125,19 +1330,19 @@ the line width, where @var{X} is the number of staff spaces.
   (stack-lines DOWN 0.0 baseline-skip
                (wordwrap-string-internal-markup-list layout props #t arg)))
 
-(define-builtin-markup-command (wordwrap-field layout props symbol)
+(define-markup-command (wordwrap-field layout props symbol)
   (symbol?)
-  align
-  ()
+  #:category align
   "Wordwrap the data which has been assigned to @var{symbol}.
 
 @lilypond[verbatim,quote]
 \\header {
   title = \"My title\"
   myText = \"Lorem ipsum dolor sit amet, consectetur adipisicing
-    elit, sed do eiusmod tempor incididunt ut labore et dolore magna
-    aliqua.  Ut enim ad minim veniam, quis nostrud exercitation ullamco
-    laboris nisi ut aliquip ex ea commodo consequat.\"
+    elit, sed do eiusmod tempor incididunt ut labore et dolore
+    magna aliqua.  Ut enim ad minim veniam, quis nostrud
+    exercitation ullamco laboris nisi ut aliquip ex ea commodo
+    consequat.\"
 }
 
 \\paper {
@@ -1159,10 +1364,9 @@ the line width, where @var{X} is the number of staff spaces.
         (wordwrap-string-markup layout props m)
         empty-stencil)))
 
-(define-builtin-markup-command (justify-field layout props symbol)
+(define-markup-command (justify-field layout props symbol)
   (symbol?)
-  align
-  ()
+  #:category align
   "Justify the data which has been assigned to @var{symbol}.
 
 @lilypond[verbatim,quote]
@@ -1193,10 +1397,9 @@ the line width, where @var{X} is the number of staff spaces.
         (justify-string-markup layout props m)
         empty-stencil)))
 
-(define-builtin-markup-command (combine layout props arg1 arg2)
+(define-markup-command (combine layout props arg1 arg2)
   (markup? markup?)
-  align
-  ()
+  #:category align
   "
 @cindex merging text
 
@@ -1225,10 +1428,10 @@ curly braces as an argument; the follow example will not compile:
 ;;
 ;; TODO: should extract baseline-skip from each argument somehow..
 ;;
-(define-builtin-markup-command (column layout props args)
+(define-markup-command (column layout props args)
   (markup-list?)
-  align
-  ((baseline-skip))
+  #:category align
+  #:properties ((baseline-skip))
   "
 @cindex stacking text in a column
 
@@ -1249,11 +1452,11 @@ in @var{args}.
     (stack-lines -1 0.0 baseline-skip
                  (remove ly:stencil-empty? arg-stencils))))
 
-(define-builtin-markup-command (dir-column layout props args)
+(define-markup-command (dir-column layout props args)
   (markup-list?)
-  align
-  ((direction)
-   (baseline-skip))
+  #:category align
+  #:properties ((direction)
+               (baseline-skip))
   "
 @cindex changing direction of text columns
 
@@ -1287,13 +1490,15 @@ setting of the @code{direction} layout property.
 (define (general-column align-dir baseline mols)
   "Stack @var{mols} vertically, aligned to  @var{align-dir} horizontally."
 
-  (let* ((aligned-mols (map (lambda (x) (ly:stencil-aligned-to x X align-dir)) mols)))
-    (stack-lines -1 0.0 baseline aligned-mols)))
+  (let* ((aligned-mols (map (lambda (x) (ly:stencil-aligned-to x X align-dir)) mols))
+         (stacked-stencil (stack-lines -1 0.0 baseline aligned-mols))
+         (stacked-extent (ly:stencil-extent stacked-stencil X)))
+    (ly:stencil-translate-axis stacked-stencil (- (car stacked-extent)) X )))
 
-(define-builtin-markup-command (center-column layout props args)
+(define-markup-command (center-column layout props args)
   (markup-list?)
-  align
-  ((baseline-skip))
+  #:category align
+  #:properties ((baseline-skip))
   "
 @cindex centering a column of text
 
@@ -1310,10 +1515,10 @@ Put @code{args} in a centered column.
 @end lilypond"
   (general-column CENTER baseline-skip (interpret-markup-list layout props args)))
 
-(define-builtin-markup-command (left-column layout props args)
+(define-markup-command (left-column layout props args)
   (markup-list?)
-  align
-  ((baseline-skip))
+  #:category align
+  #:properties ((baseline-skip))
  "
 @cindex text columns, left-aligned
 
@@ -1330,10 +1535,10 @@ Put @code{args} in a left-aligned column.
 @end lilypond"
   (general-column LEFT baseline-skip (interpret-markup-list layout props args)))
 
-(define-builtin-markup-command (right-column layout props args)
+(define-markup-command (right-column layout props args)
   (markup-list?)
-  align
-  ((baseline-skip))
+  #:category align
+  #:properties ((baseline-skip))
  "
 @cindex text columns, right-aligned
 
@@ -1350,10 +1555,9 @@ Put @code{args} in a right-aligned column.
 @end lilypond"
   (general-column RIGHT baseline-skip (interpret-markup-list layout props args)))
 
-(define-builtin-markup-command (vcenter layout props arg)
+(define-markup-command (vcenter layout props arg)
   (markup?)
-  align
-  ()
+  #:category align
   "
 @cindex vertically centering text
 
@@ -1370,10 +1574,9 @@ Align @code{arg} to its Y@tie{}center.
   (let* ((mol (interpret-markup layout props arg)))
     (ly:stencil-aligned-to mol Y CENTER)))
 
-(define-builtin-markup-command (center-align layout props arg)
+(define-markup-command (center-align layout props arg)
   (markup?)
-  align
-  ()
+  #:category align
   "
 @cindex horizontally centering text
 
@@ -1392,10 +1595,9 @@ Align @code{arg} to its X@tie{}center.
   (let* ((mol (interpret-markup layout props arg)))
     (ly:stencil-aligned-to mol X CENTER)))
 
-(define-builtin-markup-command (right-align layout props arg)
+(define-markup-command (right-align layout props arg)
   (markup?)
-  align
-  ()
+  #:category align
   "
 @cindex right aligning text
 
@@ -1414,10 +1616,9 @@ Align @var{arg} on its right edge.
   (let* ((m (interpret-markup layout props arg)))
     (ly:stencil-aligned-to m X RIGHT)))
 
-(define-builtin-markup-command (left-align layout props arg)
+(define-markup-command (left-align layout props arg)
   (markup?)
-  align
-  ()
+  #:category align
   "
 @cindex left aligning text
 
@@ -1436,10 +1637,9 @@ Align @var{arg} on its left edge.
   (let* ((m (interpret-markup layout props arg)))
     (ly:stencil-aligned-to m X LEFT)))
 
-(define-builtin-markup-command (general-align layout props axis dir arg)
+(define-markup-command (general-align layout props axis dir arg)
   (integer? number? markup?)
-  align
-  ()
+  #:category align
   "
 @cindex controlling general text alignment
 
@@ -1477,10 +1677,9 @@ Align @var{arg} in @var{axis} direction to the @var{dir} side.
   (let* ((m (interpret-markup layout props arg)))
     (ly:stencil-aligned-to m axis dir)))
 
-(define-builtin-markup-command (halign layout props dir arg)
+(define-markup-command (halign layout props dir arg)
   (number? markup?)
-  align
-  ()
+  #:category align
   "
 @cindex setting horizontal text alignment
 
@@ -1516,10 +1715,9 @@ alignment accordingly.
   (let* ((m (interpret-markup layout props arg)))
     (ly:stencil-aligned-to m X dir)))
 
-(define-builtin-markup-command (with-dimensions layout props x y arg)
+(define-markup-command (with-dimensions layout props x y arg)
   (number-pair? number-pair? markup?)
-  other
-  ()
+  #:category other
   "
 @cindex setting extent of text objects
 
@@ -1527,10 +1725,9 @@ Set the dimensions of @var{arg} to @var{x} and@tie{}@var{y}."
   (let* ((m (interpret-markup layout props arg)))
     (ly:make-stencil (ly:stencil-expr m) x y)))
 
-(define-builtin-markup-command (pad-around layout props amount arg)
+(define-markup-command (pad-around layout props amount arg)
   (number? markup?)
-  align
-  ()
+  #:category align
   "Add padding @var{amount} all around @var{arg}.
 
 @lilypond[verbatim,quote]
@@ -1553,10 +1750,9 @@ Set the dimensions of @var{arg} to @var{x} and@tie{}@var{y}."
                      (interval-widen x amount)
                      (interval-widen y amount))))
 
-(define-builtin-markup-command (pad-x layout props amount arg)
+(define-markup-command (pad-x layout props amount arg)
   (number? markup?)
-  align
-  ()
+  #:category align
   "
 @cindex padding text horizontally
 
@@ -1582,19 +1778,17 @@ Add padding @var{amount} around @var{arg} in the X@tie{}direction.
                      (interval-widen x amount)
                      y)))
 
-(define-builtin-markup-command (put-adjacent layout props axis dir arg1 arg2)
+(define-markup-command (put-adjacent layout props axis dir arg1 arg2)
   (integer? ly:dir? markup? markup?)
-  align
-  ()
+  #:category align
   "Put @var{arg2} next to @var{arg1}, without moving @var{arg1}."
   (let ((m1 (interpret-markup layout props arg1))
         (m2 (interpret-markup layout props arg2)))
     (ly:stencil-combine-at-edge m1 axis dir m2 0.0)))
 
-(define-builtin-markup-command (transparent layout props arg)
+(define-markup-command (transparent layout props arg)
   (markup?)
-  other
-  ()
+  #:category other
   "Make @var{arg} transparent.
 
 @lilypond[verbatim,quote]
@@ -1609,10 +1803,9 @@ Add padding @var{amount} around @var{arg} in the X@tie{}direction.
          (y (ly:stencil-extent m Y)))
     (ly:make-stencil "" x y)))
 
-(define-builtin-markup-command (pad-to-box layout props x-ext y-ext arg)
+(define-markup-command (pad-to-box layout props x-ext y-ext arg)
   (number-pair? number-pair? markup?)
-  align
-  ()
+  #:category align
   "Make @var{arg} take at least @var{x-ext}, @var{y-ext} space.
 
 @lilypond[verbatim,quote]
@@ -1635,10 +1828,9 @@ Add padding @var{amount} around @var{arg} in the X@tie{}direction.
                      (interval-union x-ext x)
                      (interval-union y-ext y))))
 
-(define-builtin-markup-command (hcenter-in layout props length arg)
+(define-markup-command (hcenter-in layout props length arg)
   (number? markup?)
-  align
-  ()
+  #:category align
   "Center @var{arg} horizontally within a box of extending
 @var{length}/2 to the left and right.
 
@@ -1671,10 +1863,9 @@ Add padding @var{amount} around @var{arg} in the X@tie{}direction.
 ;; property
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
-(define-builtin-markup-command (fromproperty layout props symbol)
+(define-markup-command (fromproperty layout props symbol)
   (symbol?)
-  other
-  ()
+  #:category other
   "Read the @var{symbol} from property settings, and produce a stencil
 from the markup contained within.  If @var{symbol} is not defined, it
 returns an empty markup.
@@ -1697,10 +1888,9 @@ returns an empty markup.
         (interpret-markup layout props m)
         empty-stencil)))
 
-(define-builtin-markup-command (on-the-fly layout props procedure arg)
+(define-markup-command (on-the-fly layout props procedure arg)
   (symbol? markup?)
-  other
-  ()
+  #:category other
   "Apply the @var{procedure} markup command to @var{arg}.
 @var{procedure} should take a single argument."
   (let ((anonymous-with-signature (lambda (layout props arg) (procedure layout props arg))))
@@ -1709,10 +1899,84 @@ returns an empty markup.
                          (list markup?))
     (interpret-markup layout props (list anonymous-with-signature arg))))
 
-(define-builtin-markup-command (override layout props new-prop arg)
+(define-markup-command (footnote layout props mkup note)
+  (markup? markup?)
+  #:category other
+  #:properties ((raise 0.5)
+                (padding 0.0))
+  "Have footnote @var{note} act as an annotation to the markup @var{mkup}.
+
+@lilypond[verbatim,quote]
+\\markup {
+  \\footnote a b
+  \\override #'(padding . 0.2)
+  \\footnote c d
+}
+@end lilypond"
+  (let* ((markup-stencil (interpret-markup layout props mkup))
+         (auto-numbering (ly:output-def-lookup layout
+                                               'footnote-auto-numbering))
+         (footnote-hash (gensym "footnote"))
+         (stencil-seed 0)
+         (gauge-stencil (if auto-numbering
+                            (interpret-markup
+                              layout
+                              props
+                              ((ly:output-def-lookup
+                                 layout
+                                 'footnote-numbering-function)
+                                stencil-seed))
+                            empty-stencil))
+         (x-ext (if auto-numbering
+                    (ly:stencil-extent gauge-stencil X)
+                    '(0 . 0)))
+        (y-ext (if auto-numbering
+                    (ly:stencil-extent gauge-stencil Y)
+                    '(0 . 0)))
+         (footnote-number
+           (if auto-numbering
+             `(delay-stencil-evaluation
+                ,(delay
+                  (ly:stencil-expr
+                    (let* ((table
+                            (ly:output-def-lookup layout
+                                                  'number-footnote-table))
+                           (footnote-stencil (if (list? table)
+                                                (assoc-get footnote-hash
+                                                            table)
+                                                empty-stencil))
+                           (footnote-stencil (if (ly:stencil? footnote-stencil)
+                                                 footnote-stencil
+                                                 (begin
+                                                   (ly:programming-error
+"Cannot find correct footnote for a markup object.")
+                                                   empty-stencil)))
+                           (gap (- (interval-length x-ext)
+                                  (interval-length
+                                     (ly:stencil-extent footnote-stencil X))))
+                           (y-trans (- (+ (cdr y-ext)
+                                          raise)
+                                       (cdr (ly:stencil-extent footnote-stencil
+                                                               Y)))))
+                     (ly:stencil-translate footnote-stencil
+                                            (cons gap y-trans))))))
+             '()))
+         (main-stencil (ly:stencil-combine-at-edge
+                         markup-stencil
+                         X
+                         RIGHT
+                         (ly:make-stencil footnote-number x-ext y-ext)
+                         padding)))
+  (ly:stencil-add
+    main-stencil
+    (ly:make-stencil
+      `(footnote ,footnote-hash ,(interpret-markup layout props note))
+      '(0 . 0)
+      '(0 . 0)))))
+
+(define-markup-command (override layout props new-prop arg)
   (pair? markup?)
-  other
-  ()
+  #:category other
   "
 @cindex overriding properties within text markup
 
@@ -1744,10 +2008,9 @@ may be any property supported by @rinternals{font-interface},
 ;; files
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
-(define-builtin-markup-command (verbatim-file layout props name)
+(define-markup-command (verbatim-file layout props name)
   (string?)
-  other
-  ()
+  #:category other
   "Read the contents of file @var{name}, and include it verbatim.
 
 @lilypond[verbatim,quote]
@@ -1768,10 +2031,9 @@ may be any property supported by @rinternals{font-interface},
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
 
-(define-builtin-markup-command (smaller layout props arg)
+(define-markup-command (smaller layout props arg)
   (markup?)
-  font
-  ()
+  #:category font
   "Decrease the font size relative to the current setting.
 
 @lilypond[verbatim,quote]
@@ -1790,10 +2052,9 @@ may be any property supported by @rinternals{font-interface},
   (interpret-markup layout props
    `(,fontsize-markup -1 ,arg)))
 
-(define-builtin-markup-command (larger layout props arg)
+(define-markup-command (larger layout props arg)
   (markup?)
-  font
-  ()
+  #:category font
   "Increase the font size relative to the current setting.
 
 @lilypond[verbatim,quote]
@@ -1807,10 +2068,9 @@ may be any property supported by @rinternals{font-interface},
   (interpret-markup layout props
    `(,fontsize-markup 1 ,arg)))
 
-(define-builtin-markup-command (finger layout props arg)
+(define-markup-command (finger layout props arg)
   (markup?)
-  font
-  ()
+  #:category font
   "Set @var{arg} as small numbers.
 
 @lilypond[verbatim,quote]
@@ -1821,13 +2081,12 @@ may be any property supported by @rinternals{font-interface},
 }
 @end lilypond"
   (interpret-markup layout
-                    (cons '((font-size . -5) (font-encoding . fetaNumber)) props)
+                    (cons '((font-size . -5) (font-encoding . fetaText)) props)
                     arg))
 
-(define-builtin-markup-command (abs-fontsize layout props size arg)
+(define-markup-command (abs-fontsize layout props size arg)
   (number? markup?)
-  font
-  ()
+  #:category font
   "Use @var{size} as the absolute font size to display @var{arg}.
 Adjusts @code{baseline-skip} and @code{word-space} accordingly.
 
@@ -1852,12 +2111,12 @@ Adjusts @code{baseline-skip} and @code{word-space} accordingly.
                            props)
                      arg)))
 
-(define-builtin-markup-command (fontsize layout props increment arg)
+(define-markup-command (fontsize layout props increment arg)
   (number? markup?)
-  font
-  ((font-size 0)
-   (word-space 1)
-   (baseline-skip 2))
+  #:category font
+  #:properties ((font-size 0)
+               (word-space 1)
+               (baseline-skip 2))
   "Add @var{increment} to the font-size.  Adjusts @code{baseline-skip}
 accordingly.
 
@@ -1875,10 +2134,9 @@ accordingly.
                   (cons 'font-size (+ font-size increment)))))
     (interpret-markup layout (cons entries props) arg)))
 
-(define-builtin-markup-command (magnify layout props sz arg)
+(define-markup-command (magnify layout props sz arg)
   (number? markup?)
-  font
-  ()
+  #:category font
   "
 @cindex magnifying text
 
@@ -1906,10 +2164,9 @@ Use @code{\\fontsize} otherwise.
    (prepend-alist-chain 'font-size (magnification->font-size sz) props)
    arg))
 
-(define-builtin-markup-command (bold layout props arg)
+(define-markup-command (bold layout props arg)
   (markup?)
-  font
-  ()
+  #:category font
   "Switch to bold font-series.
 
 @lilypond[verbatim,quote]
@@ -1922,10 +2179,9 @@ Use @code{\\fontsize} otherwise.
 @end lilypond"
   (interpret-markup layout (prepend-alist-chain 'font-series 'bold props) arg))
 
-(define-builtin-markup-command (sans layout props arg)
+(define-markup-command (sans layout props arg)
   (markup?)
-  font
-  ()
+  #:category font
   "Switch to the sans serif font family.
 
 @lilypond[verbatim,quote]
@@ -1939,10 +2195,9 @@ Use @code{\\fontsize} otherwise.
 @end lilypond"
   (interpret-markup layout (prepend-alist-chain 'font-family 'sans props) arg))
 
-(define-builtin-markup-command (number layout props arg)
+(define-markup-command (number layout props arg)
   (markup?)
-  font
-  ()
+  #:category font
   "Set font family to @code{number}, which yields the font used for
 time signatures and fingerings.  This font contains numbers and
 some punctuation; it has no letters.
@@ -1954,12 +2209,11 @@ some punctuation; it has no letters.
   }
 }
 @end lilypond"
-  (interpret-markup layout (prepend-alist-chain 'font-encoding 'fetaNumber props) arg))
+  (interpret-markup layout (prepend-alist-chain 'font-encoding 'fetaText props) arg))
 
-(define-builtin-markup-command (roman layout props arg)
+(define-markup-command (roman layout props arg)
   (markup?)
-  font
-  ()
+  #:category font
   "Set font family to @code{roman}.
 
 @lilypond[verbatim,quote]
@@ -1977,10 +2231,9 @@ some punctuation; it has no letters.
 @end lilypond"
   (interpret-markup layout (prepend-alist-chain 'font-family 'roman props) arg))
 
-(define-builtin-markup-command (huge layout props arg)
+(define-markup-command (huge layout props arg)
   (markup?)
-  font
-  ()
+  #:category font
   "Set font size to +2.
 
 @lilypond[verbatim,quote]
@@ -1993,10 +2246,9 @@ some punctuation; it has no letters.
 @end lilypond"
   (interpret-markup layout (prepend-alist-chain 'font-size 2 props) arg))
 
-(define-builtin-markup-command (large layout props arg)
+(define-markup-command (large layout props arg)
   (markup?)
-  font
-  ()
+  #:category font
   "Set font size to +1.
 
 @lilypond[verbatim,quote]
@@ -2009,10 +2261,9 @@ some punctuation; it has no letters.
 @end lilypond"
   (interpret-markup layout (prepend-alist-chain 'font-size 1 props) arg))
 
-(define-builtin-markup-command (normalsize layout props arg)
+(define-markup-command (normalsize layout props arg)
   (markup?)
-  font
-  ()
+  #:category font
   "Set font size to default.
 
 @lilypond[verbatim,quote]
@@ -2030,10 +2281,9 @@ some punctuation; it has no letters.
 @end lilypond"
   (interpret-markup layout (prepend-alist-chain 'font-size 0 props) arg))
 
-(define-builtin-markup-command (small layout props arg)
+(define-markup-command (small layout props arg)
   (markup?)
-  font
-  ()
+  #:category font
   "Set font size to -1.
 
 @lilypond[verbatim,quote]
@@ -2046,10 +2296,9 @@ some punctuation; it has no letters.
 @end lilypond"
   (interpret-markup layout (prepend-alist-chain 'font-size -1 props) arg))
 
-(define-builtin-markup-command (tiny layout props arg)
+(define-markup-command (tiny layout props arg)
   (markup?)
-  font
-  ()
+  #:category font
   "Set font size to -2.
 
 @lilypond[verbatim,quote]
@@ -2062,10 +2311,9 @@ some punctuation; it has no letters.
 @end lilypond"
   (interpret-markup layout (prepend-alist-chain 'font-size -2 props) arg))
 
-(define-builtin-markup-command (teeny layout props arg)
+(define-markup-command (teeny layout props arg)
   (markup?)
-  font
-  ()
+  #:category font
   "Set font size to -3.
 
 @lilypond[verbatim,quote]
@@ -2078,10 +2326,9 @@ some punctuation; it has no letters.
 @end lilypond"
   (interpret-markup layout (prepend-alist-chain 'font-size -3 props) arg))
 
-(define-builtin-markup-command (fontCaps layout props arg)
+(define-markup-command (fontCaps layout props arg)
   (markup?)
-  font
-  ()
+  #:category font
   "Set @code{font-shape} to @code{caps}
 
 Note: @code{\\fontCaps} requires the installation and selection of
@@ -2089,10 +2336,9 @@ fonts which support the @code{caps} font shape."
   (interpret-markup layout (prepend-alist-chain 'font-shape 'caps props) arg))
 
 ;; Poor man's caps
-(define-builtin-markup-command (smallCaps layout props arg)
+(define-markup-command (smallCaps layout props arg)
   (markup?)
-  font
-  ()
+  #:category font
   "Emit @var{arg} as small caps.
 
 Note: @code{\\smallCaps} does not support accented characters.
@@ -2137,10 +2383,9 @@ Note: @code{\\smallCaps} does not support accented characters.
        (make-small-caps (string->list arg) (list) #f (list))
        arg)))
 
-(define-builtin-markup-command (caps layout props arg)
+(define-markup-command (caps layout props arg)
   (markup?)
-  font
-  ()
+  #:category font
   "Copy of the @code{\\smallCaps} command.
 
 @lilypond[verbatim,quote]
@@ -2154,10 +2399,9 @@ Note: @code{\\smallCaps} does not support accented characters.
 @end lilypond"
   (interpret-markup layout props (make-smallCaps-markup arg)))
 
-(define-builtin-markup-command (dynamic layout props arg)
+(define-markup-command (dynamic layout props arg)
   (markup?)
-  font
-  ()
+  #:category font
   "Use the dynamic font.  This font only contains @b{s}, @b{f}, @b{m},
 @b{z}, @b{p}, and @b{r}.  When producing phrases, like
 @q{pi@`{u}@tie{}@b{f}}, the normal words (like @q{pi@`{u}}) should be
@@ -2170,12 +2414,11 @@ done in a different font.  The recommended font for this is bold and italic.
 }
 @end lilypond"
   (interpret-markup
-   layout (prepend-alist-chain 'font-encoding 'fetaDynamic props) arg))
+   layout (prepend-alist-chain 'font-encoding 'fetaText props) arg))
 
-(define-builtin-markup-command (text layout props arg)
+(define-markup-command (text layout props arg)
   (markup?)
-  font
-  ()
+  #:category font
   "Use a text font instead of music symbol or music alphabet font.
 
 @lilypond[verbatim,quote]
@@ -2194,10 +2437,9 @@ done in a different font.  The recommended font for this is bold and italic.
   (interpret-markup layout (prepend-alist-chain 'font-encoding 'latin1 props)
                    arg))
 
-(define-builtin-markup-command (italic layout props arg)
+(define-markup-command (italic layout props arg)
   (markup?)
-  font
-  ()
+  #:category font
   "Use italic @code{font-shape} for @var{arg}.
 
 @lilypond[verbatim,quote]
@@ -2210,10 +2452,9 @@ done in a different font.  The recommended font for this is bold and italic.
 @end lilypond"
   (interpret-markup layout (prepend-alist-chain 'font-shape 'italic props) arg))
 
-(define-builtin-markup-command (typewriter layout props arg)
+(define-markup-command (typewriter layout props arg)
   (markup?)
-  font
-  ()
+  #:category font
   "Use @code{font-family} typewriter for @var{arg}.
 
 @lilypond[verbatim,quote]
@@ -2227,10 +2468,9 @@ done in a different font.  The recommended font for this is bold and italic.
   (interpret-markup
    layout (prepend-alist-chain 'font-family 'typewriter props) arg))
 
-(define-builtin-markup-command (upright layout props arg)
+(define-markup-command (upright layout props arg)
   (markup?)
-  font
-  ()
+  #:category font
   "Set @code{font-shape} to @code{upright}.  This is the opposite
 of @code{italic}.
 
@@ -2250,10 +2490,9 @@ of @code{italic}.
   (interpret-markup
    layout (prepend-alist-chain 'font-shape 'upright props) arg))
 
-(define-builtin-markup-command (medium layout props arg)
+(define-markup-command (medium layout props arg)
   (markup?)
-  font
-  ()
+  #:category font
   "Switch to medium font-series (in contrast to bold).
 
 @lilypond[verbatim,quote]
@@ -2272,23 +2511,22 @@ of @code{italic}.
   (interpret-markup layout (prepend-alist-chain 'font-series 'medium props)
                    arg))
 
-(define-builtin-markup-command (normal-text layout props arg)
+(define-markup-command (normal-text layout props arg)
   (markup?)
-  font
-  ()
+  #:category font
   "Set all font related properties (except the size) to get the default
 normal text font, no matter what font was used earlier.
 
 @lilypond[verbatim,quote]
 \\markup {
   \\huge \\bold \\sans \\caps {
-    Some text with font overrides
+    huge bold sans caps
     \\hspace #2
     \\normal-text {
-      Default text, same font-size
+      huge normal
     }
     \\hspace #2
-    More text as before
+    as before
   }
 }
 @end lilypond"
@@ -2303,10 +2541,35 @@ normal text font, no matter what font was used earlier.
 ;; symbols.
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
-(define-builtin-markup-command (doublesharp layout props)
-  ()
-  music
+(define-markup-command (musicglyph layout props glyph-name)
+  (string?)
+  #:category music
+  "@var{glyph-name} is converted to a musical symbol; for example,
+@code{\\musicglyph #\"accidentals.natural\"} selects the natural sign from
+the music font.  See @ruser{The Feta font} for a complete listing of
+the possible glyphs.
+
+@lilypond[verbatim,quote]
+\\markup {
+  \\musicglyph #\"f\"
+  \\musicglyph #\"rests.2\"
+  \\musicglyph #\"clefs.G_change\"
+}
+@end lilypond"
+  (let* ((font (ly:paper-get-font layout
+                                 (cons '((font-encoding . fetaMusic)
+                                         (font-name . #f))
+
+                                                props)))
+        (glyph (ly:font-get-glyph font glyph-name)))
+    (if (null? (ly:stencil-expr glyph))
+       (ly:warning (_ "Cannot find glyph ~a") glyph-name))
+
+    glyph))
+
+(define-markup-command (doublesharp layout props)
   ()
+  #:category music
   "Draw a double sharp symbol.
 
 @lilypond[verbatim,quote]
@@ -2316,10 +2579,9 @@ normal text font, no matter what font was used earlier.
 @end lilypond"
   (interpret-markup layout props (markup #:musicglyph (assoc-get 1 standard-alteration-glyph-name-alist ""))))
 
-(define-builtin-markup-command (sesquisharp layout props)
-  ()
-  music
+(define-markup-command (sesquisharp layout props)
   ()
+  #:category music
   "Draw a 3/2 sharp symbol.
 
 @lilypond[verbatim,quote]
@@ -2329,10 +2591,9 @@ normal text font, no matter what font was used earlier.
 @end lilypond"
   (interpret-markup layout props (markup #:musicglyph (assoc-get 3/4 standard-alteration-glyph-name-alist ""))))
 
-(define-builtin-markup-command (sharp layout props)
-  ()
-  music
+(define-markup-command (sharp layout props)
   ()
+  #:category music
   "Draw a sharp symbol.
 
 @lilypond[verbatim,quote]
@@ -2342,10 +2603,9 @@ normal text font, no matter what font was used earlier.
 @end lilypond"
   (interpret-markup layout props (markup #:musicglyph (assoc-get 1/2 standard-alteration-glyph-name-alist ""))))
 
-(define-builtin-markup-command (semisharp layout props)
-  ()
-  music
+(define-markup-command (semisharp layout props)
   ()
+  #:category music
   "Draw a semisharp symbol.
 
 @lilypond[verbatim,quote]
@@ -2355,10 +2615,9 @@ normal text font, no matter what font was used earlier.
 @end lilypond"
   (interpret-markup layout props (markup #:musicglyph (assoc-get 1/4 standard-alteration-glyph-name-alist ""))))
 
-(define-builtin-markup-command (natural layout props)
-  ()
-  music
+(define-markup-command (natural layout props)
   ()
+  #:category music
   "Draw a natural symbol.
 
 @lilypond[verbatim,quote]
@@ -2368,10 +2627,9 @@ normal text font, no matter what font was used earlier.
 @end lilypond"
   (interpret-markup layout props (markup #:musicglyph (assoc-get 0 standard-alteration-glyph-name-alist ""))))
 
-(define-builtin-markup-command (semiflat layout props)
-  ()
-  music
+(define-markup-command (semiflat layout props)
   ()
+  #:category music
   "Draw a semiflat symbol.
 
 @lilypond[verbatim,quote]
@@ -2381,10 +2639,9 @@ normal text font, no matter what font was used earlier.
 @end lilypond"
   (interpret-markup layout props (markup #:musicglyph (assoc-get -1/4 standard-alteration-glyph-name-alist ""))))
 
-(define-builtin-markup-command (flat layout props)
-  ()
-  music
+(define-markup-command (flat layout props)
   ()
+  #:category music
   "Draw a flat symbol.
 
 @lilypond[verbatim,quote]
@@ -2394,10 +2651,9 @@ normal text font, no matter what font was used earlier.
 @end lilypond"
   (interpret-markup layout props (markup #:musicglyph (assoc-get -1/2 standard-alteration-glyph-name-alist ""))))
 
-(define-builtin-markup-command (sesquiflat layout props)
-  ()
-  music
+(define-markup-command (sesquiflat layout props)
   ()
+  #:category music
   "Draw a 3/2 flat symbol.
 
 @lilypond[verbatim,quote]
@@ -2407,10 +2663,9 @@ normal text font, no matter what font was used earlier.
 @end lilypond"
   (interpret-markup layout props (markup #:musicglyph (assoc-get -3/4 standard-alteration-glyph-name-alist ""))))
 
-(define-builtin-markup-command (doubleflat layout props)
-  ()
-  music
+(define-markup-command (doubleflat layout props)
   ()
+  #:category music
   "Draw a double flat symbol.
 
 @lilypond[verbatim,quote]
@@ -2420,10 +2675,9 @@ normal text font, no matter what font was used earlier.
 @end lilypond"
   (interpret-markup layout props (markup #:musicglyph (assoc-get -1 standard-alteration-glyph-name-alist ""))))
 
-(define-builtin-markup-command (with-color layout props color arg)
+(define-markup-command (with-color layout props color arg)
   (color? markup?)
-  other
-  ()
+  #:category other
   "
 @cindex coloring text
 
@@ -2450,10 +2704,9 @@ Draw @var{arg} in color specified by @var{color}.
 ;; glyphs
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
-(define-builtin-markup-command (arrow-head layout props axis dir filled)
+(define-markup-command (arrow-head layout props axis dir filled)
   (integer? ly:dir? boolean?)
-  graphic
-  ()
+  #:category graphic
   "Produce an arrow head in specified direction and axis.
 Use the filled head if @var{filled} is specified.
 @lilypond[verbatim,quote]
@@ -2470,7 +2723,7 @@ Use the filled head if @var{filled} is specified.
 }
 @end lilypond"
   (let*
-      ((name (format "arrowheads.~a.~a~a"
+      ((name (format #f "arrowheads.~a.~a~a"
                     (if filled
                         "close"
                         "open")
@@ -2481,38 +2734,9 @@ Use the filled head if @var{filled} is specified.
                                     props))
      name)))
 
-(define-builtin-markup-command (musicglyph layout props glyph-name)
+(define-markup-command (lookup layout props glyph-name)
   (string?)
-  music
-  ()
-  "@var{glyph-name} is converted to a musical symbol; for example,
-@code{\\musicglyph #\"accidentals.natural\"} selects the natural sign from
-the music font.  See @ruser{The Feta font} for a complete listing of
-the possible glyphs.
-
-@lilypond[verbatim,quote]
-\\markup {
-  \\musicglyph #\"f\"
-  \\musicglyph #\"rests.2\"
-  \\musicglyph #\"clefs.G_change\"
-}
-@end lilypond"
-  (let* ((font (ly:paper-get-font layout
-                                 (cons '((font-encoding . fetaMusic)
-                                         (font-name . #f))
-
-                                                props)))
-        (glyph (ly:font-get-glyph font glyph-name)))
-    (if (null? (ly:stencil-expr glyph))
-       (ly:warning (_ "Cannot find glyph ~a") glyph-name))
-
-    glyph))
-
-
-(define-builtin-markup-command (lookup layout props glyph-name)
-  (string?)
-  other
-  ()
+  #:category other
   "Lookup a glyph by name.
 
 @lilypond[verbatim,quote]
@@ -2528,10 +2752,9 @@ the possible glyphs.
   (ly:font-get-glyph (ly:paper-get-font layout props)
                     glyph-name))
 
-(define-builtin-markup-command (char layout props num)
+(define-markup-command (char layout props num)
   (integer?)
-  other
-  ()
+  #:category other
   "Produce a single character.  Characters encoded in hexadecimal
 format require the prefix @code{#x}.
 
@@ -2564,10 +2787,9 @@ format require the prefix @code{#x}.
                       (number->markletter-string vec (remainder n lst)))
        (make-string 1 (vector-ref vec n)))))
 
-(define-builtin-markup-command (markletter layout props num)
+(define-markup-command (markletter layout props num)
   (integer?)
-  other
-  ()
+  #:category other
   "Make a markup letter for @var{num}.  The letters start with A to@tie{}Z
 (skipping letter@tie{}I), and continue with double letters.
 
@@ -2581,10 +2803,9 @@ format require the prefix @code{#x}.
   (ly:text-interface::interpret-markup layout props
     (number->markletter-string number->mark-letter-vector num)))
 
-(define-builtin-markup-command (markalphabet layout props num)
+(define-markup-command (markalphabet layout props num)
   (integer?)
-  other
-  ()
+  #:category other
    "Make a markup letter for @var{num}.  The letters start with A to@tie{}Z
 and continue with double letters.
 
@@ -2635,7 +2856,7 @@ and continue with double letters.
          ; backward slashes might use slope and point in the other direction!
          (dy (* mag (if forward 0.4 -0.4)))
          (number-stencil (interpret-markup layout
-                                           (prepend-alist-chain 'font-encoding 'fetaNumber props)
+                                           (prepend-alist-chain 'font-encoding 'fetaText props)
                                            (number->string num)))
          (num-x (horizontal-slash-interval num forward (ly:stencil-extent number-stencil X) mag))
          (center (interval-center (ly:stencil-extent number-stencil Y)))
@@ -2659,11 +2880,11 @@ and continue with double letters.
     number-stencil))
 
 
-(define-builtin-markup-command (slashed-digit layout props num)
+(define-markup-command (slashed-digit layout props num)
   (integer?)
-  other
-  ((font-size 0)
-   (thickness 1.6))
+  #:category other
+  #:properties ((font-size 0)
+               (thickness 1.6))
   "
 @cindex slashed digits
 
@@ -2679,11 +2900,11 @@ figured bass notation.
 @end lilypond"
   (slashed-digit-internal layout props num #t font-size thickness))
 
-(define-builtin-markup-command (backslashed-digit layout props num)
+(define-markup-command (backslashed-digit layout props num)
   (integer?)
-  other
-  ((font-size 0)
-   (thickness 1.6))
+  #:category other
+  #:properties ((font-size 0)
+               (thickness 1.6))
   "
 @cindex backslashed digits
 
@@ -2700,39 +2921,43 @@ figured bass notation.
   (slashed-digit-internal layout props num #f font-size thickness))
 
 ;; eyeglasses
-(define eyeglassesps
-     "0.15 setlinewidth
-      -0.9 0 translate
-      1.1 1.1 scale
-      1.2 0.7 moveto
-      0.7 0.7 0.5 0 361 arc
-      stroke
-      2.20 0.70 0.50 0 361 arc
-      stroke
-      1.45 0.85 0.30 0 180 arc
-      stroke
-      0.20 0.70 moveto
-      0.80 2.00 lineto
-      0.92 2.26 1.30 2.40 1.15 1.70 curveto
-      stroke
-      2.70 0.70 moveto
-      3.30 2.00 lineto
-      3.42 2.26 3.80 2.40 3.65 1.70 curveto
-      stroke")
-
-(define-builtin-markup-command (eyeglasses layout props) () other ()
+(define eyeglassespath
+  '((moveto 0.42 0.77)
+    (rcurveto 0 0.304 -0.246 0.55 -0.55 0.55)
+    (rcurveto -0.304 0 -0.55 -0.246 -0.55 -0.55)
+    (rcurveto 0 -0.304 0.246 -0.55 0.55 -0.55)
+    (rcurveto 0.304 0 0.55 0.246 0.55 0.55)
+    (closepath)
+    (moveto 2.07 0.77)
+    (rcurveto 0 0.304 -0.246 0.55 -0.55 0.55)
+    (rcurveto -0.304 0 -0.55 -0.246 -0.55 -0.55)
+    (rcurveto 0 -0.304 0.246 -0.55 0.55 -0.55)
+    (rcurveto 0.304 0 0.55 0.246 0.55 0.55)
+    (closepath)
+    (moveto 1.025 0.935)
+    (rcurveto 0 0.182 -0.148 0.33 -0.33 0.33)
+    (rcurveto -0.182 0 -0.33 -0.148 -0.33 -0.33)
+    (moveto -0.68 0.77)
+    (rlineto 0.66 1.43)
+    (rcurveto 0.132 0.286 0.55 0.44 0.385 -0.33)
+    (moveto 2.07 0.77)
+    (rlineto 0.66 1.43)
+    (rcurveto 0.132 0.286 0.55 0.44 0.385 -0.33)))
+
+(define-markup-command (eyeglasses layout props)
+  ()
+  #:category other
   "Prints out eyeglasses, indicating strongly to look at the conductor.
 @lilypond[verbatim,quote]
 \\markup { \\eyeglasses }
 @end lilypond"
   (interpret-markup layout props
-    (make-with-dimensions-markup '(-0.61 . 3.22) '(0.2 . 2.41)
-      (make-postscript-markup eyeglassesps))))
+    (make-override-markup '(line-cap-style . butt)
+      (make-path-markup 0.15 eyeglassespath))))
 
-(define-builtin-markup-command (left-brace layout props size)
+(define-markup-command (left-brace layout props size)
   (number?)
-  other
-  ()
+  #:category other
   "
 A feta brace in point size @var{size}.
 
@@ -2771,10 +2996,9 @@ A feta brace in point size @var{size}.
                         (ly:pt 1)))))
     glyph-found))
 
-(define-builtin-markup-command (right-brace layout props size)
+(define-markup-command (right-brace layout props size)
   (number?)
-  other
-  ()
+  #:category other
   "
 A feta brace in point size @var{size}, rotated 180 degrees.
 
@@ -2793,11 +3017,11 @@ A feta brace in point size @var{size}, rotated 180 degrees.
 
 ;; TODO: better syntax.
 
-(define-builtin-markup-command (note-by-number layout props log dot-count dir)
+(define-markup-command (note-by-number layout props log dot-count dir)
   (number? number? number?)
-  music
-  ((font-size 0)
-   (style '()))
+  #:category music
+  #:properties ((font-size 0)
+               (style '()))
   "
 @cindex notes within text by log and dot-count
 
@@ -2813,7 +3037,7 @@ Construct a note symbol, with stem.  By using fractional values for
 @end lilypond"
   (define (get-glyph-name-candidates dir log style)
     (map (lambda (dir-name)
-          (format "noteheads.~a~a" dir-name
+          (format #f "noteheads.~a~a" dir-name
                   (if (and (symbol? style)
                            (not (equal? 'default style)))
                       (select-head-glyph style (min log 2))
@@ -2921,10 +3145,10 @@ and return a (log dots) list."
                 (if dots (string-length dots) 0)))
         (ly:error (_ "not a valid duration string: ~a") duration-string))))
 
-(define-builtin-markup-command (note layout props duration dir)
+(define-markup-command (note layout props duration dir)
   (string? number?)
-  music
-  (note-by-number-markup)
+  #:category music
+  #:properties (note-by-number-markup)
   "
 @cindex notes within text by string
 
@@ -2949,10 +3173,9 @@ a shortened down stem.
 ;; translating.
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
-(define-builtin-markup-command (lower layout props amount arg)
+(define-markup-command (lower layout props amount arg)
   (number? markup?)
-  align
-  ()
+  #:category align
   "
 @cindex lowering text
 
@@ -2970,10 +3193,10 @@ A negative @var{amount} indicates raising; see also @code{\\raise}.
   (ly:stencil-translate-axis (interpret-markup layout props arg)
                             (- amount) Y))
 
-(define-builtin-markup-command (translate-scaled layout props offset arg)
+(define-markup-command (translate-scaled layout props offset arg)
   (number-pair? markup?)
-  align
-  ((font-size 0))
+  #:category align
+  #:properties ((font-size 0))
   "
 @cindex translating text
 @cindex scaling text
@@ -2996,10 +3219,9 @@ Translate @var{arg} by @var{offset}, scaling the offset by the
     (ly:stencil-translate (interpret-markup layout props arg)
                           scaled)))
 
-(define-builtin-markup-command (raise layout props amount arg)
+(define-markup-command (raise layout props amount arg)
   (number? markup?)
-  align
-  ()
+  #:category align
   "
 @cindex raising text
 
@@ -3027,10 +3249,10 @@ and/or @code{extra-offset} properties.
 @end lilypond"
   (ly:stencil-translate-axis (interpret-markup layout props arg) amount Y))
 
-(define-builtin-markup-command (fraction layout props arg1 arg2)
+(define-markup-command (fraction layout props arg1 arg2)
   (markup? markup?)
-  other
-  ((font-size 0))
+  #:category other
+  #:properties ((font-size 0))
   "
 @cindex creating text fractions
 
@@ -3063,10 +3285,10 @@ Make a fraction of two markups.
       ;; empirical anyway
       (ly:stencil-translate-axis stack offset Y))))
 
-(define-builtin-markup-command (normal-size-super layout props arg)
+(define-markup-command (normal-size-super layout props arg)
   (markup?)
-  font
-  ((baseline-skip))
+  #:category font
+  #:properties ((baseline-skip))
   "
 @cindex setting superscript in standard font size
 
@@ -3084,11 +3306,11 @@ Set @var{arg} in superscript with a normal font size.
    (interpret-markup layout props arg)
    (* 0.5 baseline-skip) Y))
 
-(define-builtin-markup-command (super layout props arg)
+(define-markup-command (super layout props arg)
   (markup?)
-  font
-  ((font-size 0)
-   (baseline-skip))
+  #:category font
+  #:properties ((font-size 0)
+               (baseline-skip))
   "
 @cindex superscript text
 
@@ -3112,10 +3334,9 @@ Set @var{arg} in superscript.
    (* 0.5 baseline-skip)
    Y))
 
-(define-builtin-markup-command (translate layout props offset arg)
+(define-markup-command (translate layout props offset arg)
   (number-pair? markup?)
-  align
-  ()
+  #:category align
   "
 @cindex translating text
 
@@ -3132,11 +3353,11 @@ is a pair of numbers representing the displacement in the X and Y axis.
   (ly:stencil-translate (interpret-markup layout props arg)
                        offset))
 
-(define-builtin-markup-command (sub layout props arg)
+(define-markup-command (sub layout props arg)
   (markup?)
-  font
-  ((font-size 0)
-   (baseline-skip))
+  #:category font
+  #:properties ((font-size 0)
+               (baseline-skip))
   "
 @cindex subscript text
 
@@ -3161,10 +3382,10 @@ Set @var{arg} in subscript.
    (* -0.5 baseline-skip)
    Y))
 
-(define-builtin-markup-command (normal-size-sub layout props arg)
+(define-markup-command (normal-size-sub layout props arg)
   (markup?)
-  font
-  ((baseline-skip))
+  #:category font
+  #:properties ((baseline-skip))
   "
 @cindex setting subscript in standard font size
 
@@ -3187,10 +3408,9 @@ Set @var{arg} in subscript with a normal font size.
 ;; brackets.
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
-(define-builtin-markup-command (hbracket layout props arg)
+(define-markup-command (hbracket layout props arg)
   (markup?)
-  graphic
-  ()
+  #:category graphic
   "
 @cindex placing horizontal brackets around text
 
@@ -3209,10 +3429,9 @@ Draw horizontal brackets around @var{arg}.
         (m (interpret-markup layout props arg)))
     (bracketify-stencil m X th (* 2.5 th) th)))
 
-(define-builtin-markup-command (bracket layout props arg)
+(define-markup-command (bracket layout props arg)
   (markup?)
-  graphic
-  ()
+  #:category graphic
   "
 @cindex placing vertical brackets around text
 
@@ -3229,18 +3448,64 @@ Draw vertical brackets around @var{arg}.
         (m (interpret-markup layout props arg)))
     (bracketify-stencil m Y th (* 2.5 th) th)))
 
+(define-markup-command (parenthesize layout props arg)
+  (markup?)
+  #:category graphic
+  #:properties ((angularity 0)
+               (padding)
+               (size 1)
+               (thickness 1)
+               (width 0.25))
+  "
+@cindex placing parentheses around text
+
+Draw parentheses around @var{arg}.  This is useful for parenthesizing
+a column containing several lines of text.
+
+@lilypond[verbatim,quote]
+\\markup {
+  \\line {
+    \\parenthesize {
+      \\column {
+        foo
+        bar
+      }
+    }
+    \\override #'(angularity . 2) {
+      \\parenthesize {
+        \\column {
+          bah
+          baz
+        }
+      }
+    }
+  }
+}
+@end lilypond"
+  (let* ((markup (interpret-markup layout props arg))
+        (scaled-width (* size width))
+        (scaled-thickness
+         (* (chain-assoc-get 'line-thickness props 0.1)
+            thickness))
+        (half-thickness
+         (min (* size 0.5 scaled-thickness)
+              (* (/ 4 3.0) scaled-width)))
+        (padding (chain-assoc-get 'padding props half-thickness)))
+    (parenthesize-stencil
+     markup half-thickness scaled-width angularity padding)))
+
+
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;; Delayed markup evaluation
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
-(define-builtin-markup-command (page-ref layout props label gauge default)
+(define-markup-command (page-ref layout props label gauge default)
   (symbol? markup? markup?)
-  other
-  ()
+  #:category other
   "
 @cindex referencing page numbers in text
 
-Reference to a page number. @var{label} is the label set on the referenced
+Reference to a page number.  @var{label} is the label set on the referenced
 page (using the @code{\\label} command), @var{gauge} a markup used to estimate
 the maximum width of the page number, and @var{default} the value to display
 when @var{label} is not found."
@@ -3254,7 +3519,7 @@ when @var{label} is not found."
                       (page-number (if (list? table)
                                        (assoc-get label table)
                                        #f))
-                      (page-markup (if page-number (format "~a" page-number) default))
+                      (page-markup (if page-number (format #f "~a" page-number) default))
                       (page-stencil (interpret-markup layout props page-markup))
                       (gap (- (interval-length x-ext)
                               (interval-length (ly:stencil-extent page-stencil X)))))
@@ -3263,6 +3528,115 @@ when @var{label} is not found."
      x-ext
      y-ext)))
 
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; scaling
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(define-markup-command (scale layout props factor-pair arg)
+  (number-pair? markup?)
+  #:category graphic
+  "
+@cindex scaling markup
+@cindex mirroring markup
+
+Scale @var{arg}.  @var{factor-pair} is a pair of numbers
+representing the scaling-factor in the X and Y axes.
+Negative values may be used to produce mirror images.
+
+@lilypond[verbatim,quote]
+\\markup {
+  \\line {
+    \\scale #'(2 . 1)
+    stretched
+    \\scale #'(1 . -1)
+    mirrored
+  }
+}
+@end lilypond"
+  (let ((stil (interpret-markup layout props arg))
+       (sx (car factor-pair))
+       (sy (cdr factor-pair)))
+    (ly:stencil-scale stil sx sy)))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Repeating
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(define-markup-command (pattern layout props count axis space pattern)
+  (integer? integer? number? markup?)
+  #:category other
+  "
+Prints @var{count} times a @var{pattern} markup.
+Patterns are spaced apart by @var{space}.
+Patterns are distributed on @var{axis}.
+
+@lilypond[verbatim, quote]
+\\markup \\column {
+  \"Horizontally repeated :\"
+  \\pattern #7 #X #2 \\flat
+  \\null
+  \"Vertically repeated :\"
+  \\pattern #3 #Y #0.5 \\flat
+}
+@end lilypond"
+  (let ((pattern-width (interval-length
+                         (ly:stencil-extent (interpret-markup layout props pattern) X)))
+        (new-props (prepend-alist-chain 'word-space 0 (prepend-alist-chain 'baseline-skip 0 props))))
+    (let loop ((i (1- count)) (patterns (markup)))
+      (if (zero? i)
+          (interpret-markup
+            layout
+            new-props
+            (if (= axis X)
+                (markup patterns pattern)
+                (markup #:column (patterns pattern))))
+          (loop (1- i)
+            (if (= axis X)
+                (markup patterns pattern #:hspace space)
+                (markup #:column (patterns pattern #:vspace space))))))))
+
+(define-markup-command (fill-with-pattern layout props space dir pattern left right)
+  (number? ly:dir? markup? markup? markup?)
+  #:category align
+  #:properties ((word-space)
+                (line-width))
+  "
+Put @var{left} and @var{right} in a horizontal line of width @code{line-width}
+with a line of markups @var{pattern} in between.
+Patterns are spaced apart by @var{space}.
+Patterns are aligned to the @var{dir} markup.
+
+@lilypond[verbatim, quote]
+\\markup \\column {
+  \"right-aligned :\"
+  \\fill-with-pattern #1 #RIGHT . first right
+  \\fill-with-pattern #1 #RIGHT . second right
+  \\null
+  \"center-aligned :\"
+  \\fill-with-pattern #1.5 #CENTER - left right
+  \\null
+  \"left-aligned :\"
+  \\override #'(line-width . 50)
+  \\fill-with-pattern #2 #LEFT : left first
+  \\override #'(line-width . 50)
+  \\fill-with-pattern #2 #LEFT : left second
+}
+@end lilypond"
+  (let* ((pattern-x-extent (ly:stencil-extent (interpret-markup layout props pattern) X))
+         (pattern-width (interval-length pattern-x-extent))
+         (left-width (interval-length (ly:stencil-extent (interpret-markup layout props left) X)))
+         (right-width (interval-length (ly:stencil-extent (interpret-markup layout props right) X)))
+         (middle-width (max 0 (- line-width (+ (+ left-width right-width) (* word-space 2)))))
+         (period (+ space pattern-width))
+         (count (truncate (/ (- middle-width pattern-width) period)))
+         (x-offset (+ (* (- (- middle-width (* count period)) pattern-width) (/ (1+ dir) 2)) (abs (car pattern-x-extent)))))
+    (interpret-markup layout props
+                      (markup left
+                              #:with-dimensions (cons 0 middle-width) '(0 . 0)
+                              #:translate (cons x-offset 0)
+                              #:pattern (1+ count) X space pattern
+                              right))))
+
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;; Markup list commands
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -3288,10 +3662,10 @@ when @var{label} is not found."
                                   dy-top)))))
          (space-stil (cdr stils) (cons new-stil result))))))
 
-(define-builtin-markup-list-command (justified-lines layout props args)
+(define-markup-list-command (justified-lines layout props args)
   (markup-list?)
-  ((baseline-skip)
-   wordwrap-internal-markup-list)
+  #:properties ((baseline-skip)
+               wordwrap-internal-markup-list)
   "
 @cindex justifying lines of text
 
@@ -3302,10 +3676,10 @@ Use @code{\\override-lines #'(line-width . @var{X})} to set the line width;
                (interpret-markup-list layout props
                                       (make-wordwrap-internal-markup-list #t args))))
 
-(define-builtin-markup-list-command (wordwrap-lines layout props args)
+(define-markup-list-command (wordwrap-lines layout props args)
   (markup-list?)
-  ((baseline-skip)
-   wordwrap-internal-markup-list)
+  #:properties ((baseline-skip)
+               wordwrap-internal-markup-list)
   "Like @code{\\wordwrap}, but return a list of lines instead of a single markup.
 Use @code{\\override-lines #'(line-width . @var{X})} to set the line width,
 where @var{X} is the number of staff spaces."
@@ -3313,16 +3687,15 @@ where @var{X} is the number of staff spaces."
                (interpret-markup-list layout props
                                       (make-wordwrap-internal-markup-list #f args))))
 
-(define-builtin-markup-list-command (column-lines layout props args)
+(define-markup-list-command (column-lines layout props args)
   (markup-list?)
-  ((baseline-skip))
+  #:properties ((baseline-skip))
   "Like @code{\\column}, but return a list of lines instead of a single markup.
 @code{baseline-skip} determines the space between each markup in @var{args}."
   (space-lines baseline-skip
               (interpret-markup-list layout props args)))
 
-(define-builtin-markup-list-command (override-lines layout props new-prop args)
+(define-markup-list-command (override-lines layout props new-prop args)
   (pair? markup-list?)
-  ()
   "Like @code{\\override}, for markup lists."
   (interpret-markup-list layout (cons (list new-prop) props) args))