(use-modules (ice-9 regex))
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; utility functions
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
(define-public empty-stencil (ly:make-stencil '() '(1 . -1) '(1 . -1)))
(define-public point-stencil (ly:make-stencil "" '(0 . 0) '(0 . 0)))
-(def-markup-command (stencil layout props stil) (ly:stencil?)
- "Stencil as markup"
- stil)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; geometric shapes
(cons (+ (- half) (car yext))
(+ half (cdr yext))))))
-
(def-markup-command (box layout props arg) (markup?)
"Draw a box round @var{arg}. Looks at @code{thickness},
@code{box-padding} and @code{font-size} properties to determine line
(m (interpret-markup layout props arg)))
(box-stencil m th pad)))
-
-
(def-markup-command (filled-box layout props xext yext blot)
(number-pair? number-pair? number?)
"Draw a box with rounded corners of dimensions @var{xext} and @var{yext}."
(ly:stencil-add white stil)))
+(def-markup-command (pad-markup layout props padding arg) (number? markup?)
+ "Add space around a markup object."
+
+ (let*
+ ((stil (interpret-markup layout props arg))
+ (xext (ly:stencil-extent stil X))
+ (yext (ly:stencil-extent stil Y)))
+
+ (ly:make-stencil
+ (ly:stencil-expr stil)
+ (interval-widen xext padding)
+ (interval-widen yext padding))))
+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; space
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; importing graphics.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+(def-markup-command (stencil layout props stil) (ly:stencil?)
+ "Stencil as markup"
+ stil)
(define bbox-regexp
(make-regexp "%%BoundingBox: ([0-9-]+) ([0-9-]+) ([0-9-]+) ([0-9-]+)"))
(define-public empty-markup
(make-simple-markup ""))
-
-(def-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.
-"
-
-
- (define (get-fill-space word-count line-width text-widths)
- "Calculate the necessary paddings between each two adjacent texts.
+;; helper for justifying lines.
+(define (get-fill-space word-count line-width 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
whole length of the first or last text.
Return a list of paddings.
"
- (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))))))
-
+ (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))))))
+(def-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))
(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-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))
(word-space (chain-assoc-get 'word-space props))
(line-width (chain-assoc-get 'linewidth props))
(remove ly:stencil-empty? stencils))))
+(define (wordwrap-stencils stencils
+ justify base-space line-width
+ )
+
+ "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
+ (reverse line-stencils))))
+
+ (if (pair? (cdr line-break))
+ (loop (cons line lines)
+ (cdr line-break))
+
+ (reverse (cons line lines))
+ ))
+ ))
+
+
+(define (wordwrap-markups layout props args justify)
+ (let*
+ ((baseline-skip (chain-assoc-get 'baseline-skip props))
+ (line-width (chain-assoc-get 'linewidth props))
+ (word-space (chain-assoc-get 'word-space props))
+ (lines (wordwrap-stencils
+ (remove ly:stencil-empty?
+ (map (lambda (m) (interpret-markup layout props m)) args))
+ justify word-space line-width)
+ ))
+
+ (stack-lines DOWN 0.0 baseline-skip lines)))
+
+(def-markup-command (justify layout props args) (markup-list?)
+ "Simple wordwrap"
+
+ (wordwrap-markups layout props args #t))
+
+(def-markup-command (wordwrap layout props args) (markup-list?)
+ "Like wordwrap, but with lines stretched to justify the margins."
+
+ (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 'linewidth props))
+ (word-space (chain-assoc-get 'word-space props))
+ (para-strings (regexp-split arg "\n[ \t\n]*\n[ \t\n]*"))
+
+ (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)))
+
+ lines))
+
+ list-para-words)))
+
+ (stack-lines DOWN 0.0 baseline-skip (apply append para-lines))))
+
+
+(def-markup-command (wordwrap-string layout props arg) (string?)
+ "Wordwrap a string. Paragraphs may be separated with double newlines"
+ (wordwrap-string layout props #f arg))
+
+(def-markup-command (justify-string layout props arg) (string?)
+ "Justify a string. Paragraphs may be separated with double newlines"
+ (wordwrap-string layout props #t arg))
+
(def-markup-command (combine layout props m1 m2) (markup? markup?)
"Print two markups on top of each other."
(let* ((s1 (interpret-markup layout props m1))
(apply min (map (lambda (x) (car (ly:stencil-extent x axis))) ss))
(apply max (map (lambda (x) (cdr (ly:stencil-extent x axis))) ss))))
- (define (stack-stencils stencils bskip last-stencil)
+
+ (define (stack-stencils-vertically stencils bskip last-stencil)
(cond
((null? stencils) '())
((not (ly:stencil? last-stencil))
(cons (car stencils)
- (stack-stencils (cdr stencils) bskip (car stencils))))
+ (stack-stencils-vertically (cdr stencils) bskip (car stencils))))
(else
(let* ((orig (car stencils))
(dir (chain-assoc-get 'direction props DOWN))
orig
0.1 bskip)))
- (cons new (stack-stencils (cdr stencils) bskip new))))))
+ (cons new (stack-stencils-vertically (cdr stencils) bskip new))))))
(define (make-brackets stencils indices acc)
(if (and stencils
x)) args))
(leading
(chain-assoc-get 'baseline-skip props))
- (stacked (stack-stencils
+ (stacked (stack-stencils-vertically
(remove ly:stencil-empty? stencils) 1.25 #f))
(brackets (make-brackets stacked indices '())))