+ (cond
+ ((null? text-widths) '())
+
+ ;; special case first padding
+ ((= (length text-widths) word-count)
+ (cons
+ (- (- (/ line-width (1- word-count)) (car text-widths))
+ (/ (car (cdr text-widths)) 2))
+ (get-fill-space word-count line-width (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-markup-command (fill-line layout props markups)
+ (markup-list?)
+ "Put @var{markups} in a horizontal line of width @var{line-width}.
+ The markups are spaced/flushed to fill the entire line.
+ If there are no arguments, return an empty stencil."
+
+ (let* ((orig-stencils
+ (map (lambda (x) (interpret-markup layout props x))
+ markups))
+ (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))
+ (text-dir (chain-assoc-get 'text-direction props RIGHT))
+ (word-count (length stencils))
+ (word-space (chain-assoc-get 'word-space props 1))
+ (prop-line-width (chain-assoc-get 'line-width props #f))
+ (line-width (if prop-line-width prop-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-dir LEFT)
+ (set! line-stencils (reverse line-stencils)))
+
+ (if (null? (remove ly:stencil-empty? orig-stencils))
+ empty-stencil
+ (stack-stencils-padding-list X
+ RIGHT fill-space-normal line-stencils))))
+
+(define-markup-command (line layout props args) (markup-list?)
+ "Put @var{args} in a horizontal line. The property @code{word-space}
+determines the space between each markup in @var{args}."
+ (let*
+ ((stencils (map (lambda (m) (interpret-markup layout props m)) args))
+ (space (chain-assoc-get 'word-space props))
+ (text-dir (chain-assoc-get 'text-direction props RIGHT))
+ )
+
+ (if (= text-dir LEFT)
+ (set! stencils (reverse stencils)))
+
+
+ (stack-stencil-line
+ space
+ (remove ly:stencil-empty? stencils))))
+
+(define-markup-command (concat layout props args) (markup-list?)
+ "Concatenate @var{args} in a horizontal line, without spaces inbetween.
+Strings and simple markups are concatenated on the input level, allowing
+ligatures. For example, @code{\\concat @{ \"f\" \\simple #\"i\" @}} is
+equivalent to @code{\"fi\"}."
+
+ (define (concat-string-args arg-list)
+ (fold-right (lambda (arg result-list)
+ (let ((result (if (pair? result-list)
+ (car result-list)
+ '())))
+ (if (and (pair? arg) (eqv? (car arg) simple-markup))
+ (set! arg (cadr arg)))
+ (if (and (string? result) (string? arg))
+ (cons (string-append arg result) (cdr result-list))
+ (cons arg result-list))))
+ '()
+ arg-list))
+
+ (interpret-markup layout
+ (prepend-alist-chain 'word-space 0 props)
+ (make-line-markup (concat-string-args args))))
+
+(define (wordwrap-stencils stencils
+ justify base-space line-width text-dir)
+
+ "Perform simple wordwrap, return stencil of each line."
+
+ (define space (if justify
+
+ ;; justify only stretches lines.
+ (* 0.7 base-space)
+ base-space))
+
+ (define (take-list width space stencils
+ accumulator accumulated-width)
+ "Return (head-list . tail) pair, with head-list fitting into width"
+ (if (null? stencils)
+ (cons accumulator stencils)
+ (let*
+ ((first (car stencils))
+ (first-wid (cdr (ly:stencil-extent (car stencils) X)))
+ (newwid (+ space first-wid accumulated-width))
+ )
+
+ (if
+ (or (null? accumulator)
+ (< newwid width))
+
+ (take-list width space
+ (cdr stencils)
+ (cons first accumulator)
+ newwid)
+ (cons accumulator stencils))
+ )))
+
+ (let loop
+ ((lines '())
+ (todo stencils))
+
+ (let*
+ ((line-break (take-list line-width space todo
+ '() 0.0))
+ (line-stencils (car line-break))
+ (space-left (- line-width (apply + (map (lambda (x) (cdr (ly:stencil-extent x X)))
+ line-stencils))))
+
+ (line-word-space (cond
+ ((not justify) space)
+
+ ;; don't stretch last line of paragraph.
+ ;; hmmm . bug - will overstretch the last line in some case.
+ ((null? (cdr line-break))
+ base-space)
+ ((null? line-stencils) 0.0)
+ ((null? (cdr line-stencils)) 0.0)
+ (else (/ space-left (1- (length line-stencils))))))
+
+ (line (stack-stencil-line
+ line-word-space
+ (if (= text-dir RIGHT)
+ (reverse line-stencils)
+ line-stencils))))
+
+ (if (pair? (cdr line-break))
+ (loop (cons line lines)
+ (cdr line-break))
+
+ (begin
+ (if (= text-dir LEFT)
+ (set! line
+ (ly:stencil-translate-axis line
+ (- line-width (interval-end (ly:stencil-extent line X)))
+ X)))
+ (reverse (cons line lines))
+
+ )))
+
+ ))
+
+
+(define (wordwrap-markups layout props args justify)
+ (let*
+ ((baseline-skip (chain-assoc-get 'baseline-skip props))
+ (prop-line-width (chain-assoc-get 'line-width props #f))
+ (line-width (if prop-line-width prop-line-width
+ (ly:output-def-lookup layout 'line-width)))
+ (word-space (chain-assoc-get 'word-space props))
+ (text-dir (chain-assoc-get 'text-direction props RIGHT))
+ (lines (wordwrap-stencils
+ (remove ly:stencil-empty?
+ (map (lambda (m) (interpret-markup layout props m)) args))
+ justify word-space line-width
+ text-dir)
+ ))
+
+ (stack-lines DOWN 0.0 baseline-skip lines)))
+
+(define-markup-command (justify layout props args) (markup-list?)
+ "Like wordwrap, but with lines stretched to justify the margins.
+Use @code{\\override #'(line-width . X)} to set line-width, where X
+is the number of staff spaces."
+
+ (wordwrap-markups layout props args #t))
+
+(define-markup-command (wordwrap layout props args) (markup-list?)
+ "Simple wordwrap. Use @code{\\override #'(line-width . X)} to set
+line-width, where X is the number of staff spaces."
+
+ (wordwrap-markups layout props args #f))
+
+(define (wordwrap-string layout props justify arg)
+ (let*
+ ((baseline-skip (chain-assoc-get 'baseline-skip props))
+ (line-width (chain-assoc-get 'line-width props))
+ (word-space (chain-assoc-get 'word-space props))
+
+ (para-strings (regexp-split
+ (string-regexp-substitute "\r" "\n"
+ (string-regexp-substitute "\r\n" "\n" arg))
+ "\n[ \t\n]*\n[ \t\n]*"))
+
+ (text-dir (chain-assoc-get 'text-direction props RIGHT))
+ (list-para-words (map (lambda (str)
+ (regexp-split str "[ \t\n]+"))
+ para-strings))
+ (para-lines (map (lambda (words)
+ (let*
+ ((stencils
+ (remove
+ ly:stencil-empty? (map
+ (lambda (x)
+ (interpret-markup layout props x))
+ words)))
+ (lines (wordwrap-stencils stencils
+ justify word-space
+ line-width text-dir
+ )))
+
+ lines))
+
+ list-para-words)))
+
+ (stack-lines DOWN 0.0 baseline-skip (apply append para-lines))))
+
+
+(define-markup-command (wordwrap-string layout props arg) (string?)
+ "Wordwrap a string. Paragraphs may be separated with double newlines"
+ (wordwrap-string layout props #f arg))
+
+(define-markup-command (justify-string layout props arg) (string?)
+ "Justify a string. Paragraphs may be separated with double newlines"
+ (wordwrap-string layout props #t arg))
+
+
+(define-markup-command (wordwrap-field layout props symbol) (symbol?)
+ (let* ((m (chain-assoc-get symbol props)))
+ (if (string? m)
+ (interpret-markup layout props
+ (list wordwrap-string-markup m))
+ (ly:make-stencil '() '(1 . -1) '(1 . -1)))))