+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; 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))))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Replacements
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(define-markup-command (replace layout props replacements arg)
+ (list? markup?)
+ #:category font
+ "
+Used to automatically replace a string by another in the markup @var{arg}.
+Each pair of the alist @var{replacements} specifies what should be replaced.
+The @code{key} is the string to be replaced by the @code{value} string.
+
+@lilypond[verbatim, quote]
+\\markup \\replace #'((\"thx\" . \"Thanks!\")) thx
+@end lilypond"
+ (interpret-markup
+ layout
+ (internal-add-text-replacements
+ props
+ replacements)
+ (markup arg)))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Markup list commands
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(define-public (space-lines baseline stils)
+ (let space-stil ((stils stils)
+ (result (list)))
+ (if (null? stils)
+ (reverse! result)
+ (let* ((stil (car stils))
+ (dy-top (max (- (/ baseline 1.5)
+ (interval-bound (ly:stencil-extent stil Y) UP))
+ 0.0))
+ (dy-bottom (max (+ (/ baseline 3.0)
+ (interval-bound (ly:stencil-extent stil Y) DOWN))
+ 0.0))
+ (new-stil (ly:make-stencil
+ (ly:stencil-expr stil)
+ (ly:stencil-extent stil X)
+ (cons (- (interval-bound (ly:stencil-extent stil Y) DOWN)
+ dy-bottom)
+ (+ (interval-bound (ly:stencil-extent stil Y) UP)
+ dy-top)))))
+ (space-stil (cdr stils) (cons new-stil result))))))
+
+(define-markup-list-command (justified-lines layout props args)
+ (markup-list?)
+ #:properties ((baseline-skip)
+ wordwrap-internal-markup-list)
+ "
+@cindex justifying lines of text
+
+Like @code{\\justify}, but return a list of lines instead of a single markup.
+Use @code{\\override-lines #'(line-width . @var{X})} to set the line width;
+@var{X}@tie{}is the number of staff spaces."
+ (space-lines baseline-skip
+ (interpret-markup-list layout props
+ (make-wordwrap-internal-markup-list #t args))))
+
+(define-markup-list-command (wordwrap-lines layout props args)
+ (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."
+ (space-lines baseline-skip
+ (interpret-markup-list layout props
+ (make-wordwrap-internal-markup-list #f args))))
+
+(define-markup-list-command (column-lines layout props args)
+ (markup-list?)
+ #: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-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))
+
+(define-markup-list-command (table layout props column-align lst)
+ (number-list? markup-list?)
+ #:properties ((padding 0)
+ (baseline-skip))
+ "@cindex creating a table.
+
+Returns a table.
+
+@var{column-align} specifies how each column is aligned, possible values are
+-1, 0, 1. The number of elements in @var{column-align} determines how many
+columns will be printed.
+The entries to print are given by @var{lst}, a markup-list. If needed, the last
+row is filled up with @code{point-stencil}s.
+Overriding @code{padding} may be used to increase columns horizontal distance.
+Overriding @code{baseline-skip} to increase rows vertical distance.
+@lilypond[verbatim,quote]
+\\markuplist {
+ \\override #'(padding . 2)
+ \\table
+ #'(0 1 0 -1)
+ {
+ \\underline { center-aligned right-aligned center-aligned left-aligned }
+ one \\number 1 thousandth \\number 0.001
+ eleven \\number 11 hundredth \\number 0.01
+ twenty \\number 20 tenth \\number 0.1
+ thousand \\number 1000 one \\number 1.0
+ }
+}
+@end lilypond
+"
+
+ (define (split-lst initial-lst lngth result-lst)
+ ;; split a list into a list of sublists of length lngth
+ ;; eg. (split-lst '(1 2 3 4 5 6) 2 '())
+ ;; -> ((1 2) (3 4) (5 6))
+ (cond ((not (integer? (/ (length initial-lst) lngth)))
+ (ly:warning
+ "Can't split list of length ~a into ~a parts, returning empty list"
+ (length initial-lst) lngth)
+ '())
+ ((null? initial-lst)
+ (reverse result-lst))
+ (else
+ (split-lst
+ (drop initial-lst lngth)
+ lngth
+ (cons (take initial-lst lngth) result-lst)))))
+
+ (define (dists-list init padding lst)
+ ;; Returns a list, where each element of `lst' is
+ ;; added to the sum of the previous elements of `lst' plus padding.
+ ;; `init' will be the first element of the resulting list. The addition
+ ;; starts with the values of `init', `padding' and `(car lst)'.
+ ;; eg. (dists-list 0.01 0.1 '(1 2 3 4)))
+ ;; -> (0.01 1.11 3.21 6.31 10.41)
+ (if (or (not (number? init))
+ (not (number? padding))
+ (not (number-list? lst)))
+ (begin
+ (ly:warning
+ "not fitting argument for `dists-list', return empty lst ")
+ '())
+ (reverse
+ (fold (lambda (elem rl) (cons (+ elem padding (car rl)) rl))
+ (list init)
+ lst))))
+
+ (let* (;; get the number of columns
+ (columns (length column-align))
+ (init-stils (interpret-markup-list layout props lst))
+ ;; If the given markup-list is the result of a markup-list call, their
+ ;; length may not be easily predictable, thus we add point-stencils
+ ;; to fill last row of the table.
+ (rem (remainder (length init-stils) columns))
+ (filled-stils
+ (if (zero? rem)
+ init-stils
+ (append init-stils (make-list (- columns rem) point-stencil))))
+ ;; get the stencils in sublists of length `columns'
+ (stils
+ (split-lst filled-stils columns '()))
+ ;; procedure to return stencil-length
+ ;; If it is nan, return 0
+ (lengths-proc
+ (lambda (m)
+ (let ((lngth (interval-length (ly:stencil-extent m X))))
+ (if (nan? lngth) 0 lngth))))
+ ;; get the max width of each column in a list
+ (columns-max-x-lengths
+ (map
+ (lambda (x)
+ (apply max 0
+ (map
+ lengths-proc
+ (map (lambda (l) (list-ref l x)) stils))))
+ (iota columns)))
+ ;; create a list of (basic) distances, which each column should
+ ;; moved, using `dists-list'. Some padding may be added.
+ (dist-sequence
+ (dists-list 0 padding columns-max-x-lengths))
+ ;; Get all stencils of a row, moved accurately to build columns.
+ ;; If the items of a column are aligned other than left, we need to
+ ;; move them to avoid collisions:
+ ;; center aligned: move all items half the width of the widest item
+ ;; right aligned: move all items the full width of the widest item.
+ ;; Added to the default-offset calculated in `dist-sequence'.
+ ;; `stencils-for-row-proc' needs four arguments:
+ ;; stil - a stencil
+ ;; dist - a numerical value as basic offset in X direction
+ ;; column - a numerical value for the column we're in
+ ;; x-align - a numerical value how current column should be
+ ;; aligned, where (-1, 0, 1) means (LEFT, CENTER, RIGHT)
+ (stencils-for-row-proc
+ (lambda (stil dist column x-align)
+ (ly:stencil-translate-axis
+ (ly:stencil-aligned-to stil X x-align)
+ (cond ((member x-align '(0 1))
+ (let* (;; get the stuff for relevant column
+ (stuff-for-column
+ (map
+ (lambda (s) (list-ref s column))
+ stils))
+ ;; get length of every column-item
+ (lengths-for-column
+ (map lengths-proc stuff-for-column))
+ (widest
+ (apply max 0 lengths-for-column)))
+ (+ dist (/ widest (if (= x-align 0) 2 1)))))
+ (else dist))
+ X)))
+ ;; get a list of rows using `ly:stencil-add' on a list of stencils
+ (rows
+ (map
+ (lambda (stil-list)
+ (apply ly:stencil-add
+ (map
+ ;; the procedure creating the stencils:
+ stencils-for-row-proc
+ ;; the procedure's args:
+ stil-list
+ dist-sequence
+ (iota columns)
+ column-align)))
+ stils)))
+ (space-lines baseline-skip rows)))
+
+(define-markup-list-command (map-markup-commands layout props compose args)
+ (procedure? markup-list?)
+ "This applies the function @var{compose} to every markup in
+@var{args} (including elements of markup list command calls) in order
+to produce a new markup list. Since the return value from a markup
+list command call is not a markup list but rather a list of stencils,
+this requires passing those stencils off as the results of individual
+markup calls. That way, the results should work out as long as no
+markups rely on side effects."
+ (let ((key (make-symbol "key")))
+ (catch
+ key
+ (lambda ()
+ ;; if `compose' does not actually interpret its markup
+ ;; argument, we still need to return a list of stencils,
+ ;; created from the single returned stencil
+ (list
+ (interpret-markup layout props
+ (compose
+ (make-on-the-fly-markup
+ (lambda (layout props m)
+ ;; here all effects of `compose' on the
+ ;; properties should be visible, so we
+ ;; call interpret-markup-list at this
+ ;; point of time and harvest its
+ ;; stencils
+ (throw key
+ (interpret-markup-list
+ layout props args)))
+ (make-null-markup))))))
+ (lambda (key stencils)
+ (map
+ (lambda (sten)
+ (interpret-markup layout props
+ (compose (make-stencil-markup sten))))
+ stencils)))))