]> git.donarmstrong.com Git - lilypond.git/blobdiff - scm/define-markup-commands.scm
* scm/define-grobs.scm (all-grob-descriptions): reorganize in
[lilypond.git] / scm / define-markup-commands.scm
index 21186cd074395f4875e0cddff7b2e3463e198caf..9a61ba87e699b890ffa2ab5c0643aac6c025537c 100644 (file)
 
 (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
@@ -89,7 +90,6 @@ the PDF backend."
      (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
@@ -101,8 +101,6 @@ thickness and padding around the markup."
         (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}."
@@ -124,6 +122,19 @@ thickness and padding around the markup."
 
     (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
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -155,6 +166,9 @@ normally inserted before elements on a line.
 ;; 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-]+)"))
@@ -272,17 +286,9 @@ gsave /ecrm10 findfont
 (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
@@ -290,26 +296,31 @@ gsave /ecrm10 findfont
        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))
@@ -318,13 +329,13 @@ gsave /ecrm10 findfont
                 (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))
@@ -369,7 +380,134 @@ determines the space between each markup in @var{args}."
    (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))
@@ -941,12 +1079,13 @@ the elements marked in @var{indices}, which is a list of numbers."
      (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))
@@ -954,7 +1093,7 @@ the elements marked in @var{indices}, which is a list of numbers."
                                            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
@@ -989,7 +1128,7 @@ the elements marked in @var{indices}, which is a list of numbers."
                  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 '())))