\version "2.19.25" %% Only needed for versions before 2.19.27. #(define (make-dashed-line-stencil width startx starty endx endy on off phase) (let ((xext (cons (min startx endx) (max startx endx))) (yext (cons (min starty endy) (max starty endy)))) (ly:make-stencil (list 'dashed-line ; thick on off dx dy phase width on off (- endx startx) (- endy starty) phase) ; Since the line has rounded edges, we have to / can safely add half the ; width to all coordinates! (interval-widen xext (/ width 2)) (interval-widen yext (/ width 2))))) %% The following is taken from scm/define-markup-commands.scm. It is %% needed to emulate \justify-line, so that texts may be spread %% evenly along TextSpanner. #(define (get-fill-space word-count line-width word-space text-widths constant-space?) "Calculate the necessary paddings between adjacent texts in a single justified line. The lengths of all texts are stored in @var{text-widths}. When @var{constant-space?} is @code{#t}, the formula for the padding between texts is: padding = (line-width - total-text-width)/(word-count - 1) When @var{constant-space?} is @code{#f}, the formula for the padding between interior texts a and b is: padding = line-width/(word-count - 1) - (length(a) + length(b))/2 In this case, 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) '()) (constant-space? (make-list (1- word-count) ;; Ensure that space between words cannot be ;; less than word-space. (max word-space (/ (- line-width (apply + text-widths)) (1- word-count))))) ;; special case first padding ((= (length text-widths) word-count) (cons (- (- (/ line-width (1- word-count)) (car text-widths)) (/ (cadr text-widths) 2)) (get-fill-space word-count line-width word-space (cdr text-widths) constant-space?))) ;; special case last padding ((= (length text-widths) 2) (list (- (/ line-width (1- word-count)) (+ (/ (car text-widths) 2) (cadr text-widths))) 0)) (else (let ((default-padding (- (/ line-width (1- word-count)) (/ (+ (car text-widths) (cadr 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) constant-space?)))))) %% Adapted from scm/define-markup-commands.scm. #(define (justify-line-helper grob args word-space line-width constant-space? padding) "Return a stencil which spreads @var{args} along a line of width @var{line-width}, with spaces filled by a line. If @var{constant-space?} is set to @code{#t}, the space between words is constant. If @code{#f}, the distance between words varies according to their relative lengths." (let* ((orig-stencils (map (lambda (a) (grob-interpret-markup grob a)) args)) (stencils (map (lambda (stc) (if (ly:stencil-empty? stc X) point-stencil stc)) orig-stencils)) (text-widths (map (lambda (stc) (interval-length (ly:stencil-extent stc X))) stencils)) (text-width (apply + text-widths)) (word-count (length stencils)) (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 constant-space?)))) (line-contents (if (= word-count 1) (list point-stencil (car stencils) point-stencil) stencils))) (if (null? (remove ly:stencil-empty? orig-stencils)) empty-stencil (begin (set! line-contents (let loop ((contents line-contents) ; stencils to distribute (space fill-space) ; areas to fill with lines (X-coord 0.0) ; where to place segments of spanner (accum 0.0) ; for drawing lines though spacers (left-text (car line-contents)) ; how segment starts (stil empty-stencil)) ; the stencil to return ; Since segments are built text + line below, we need a ; special case for line endings, which consist of a stencil ; and no continuation. (if (null? (cdr contents)) ; no need to draw break marker (if (eq? (car contents) point-stencil) stil (ly:stencil-add stil (ly:stencil-translate-axis (car contents) X-coord X))) (let ((current (car contents)) (next (cadr contents))) (cond ; don't draw individual lines for successive "" ; rather keep a tally so that a single line will ; be drawn once a visible stencil (or the end ; of the line) is encountered ((and (not (eq? next (last contents))) (eq? next point-stencil)) (loop (cdr contents) (cdr space) X-coord (+ accum (car space)) left-text ; keep track of beginning of segment stil)) (else (let* ((start-offset (if (eq? left-text point-stencil) 0.0 padding)) (end-offset (if (eq? next point-stencil) 0.0 (- padding))) (line (make-line-stencil 0.1 start-offset 0.0 (+ (car space) accum end-offset) 0.0)) ; inferior dashed line! ;(line (make-dashed-line-stencil ; 0.1 ; thickness ; start-offset 0.0 ; (+ (car space) accum end-offset) 0.0 ; 1 ; on ; 1 ; off ; 0 ; phase ; )) ; Uncomment the following definition of line ; for use with versions 2.19.27 and higher. ; This allows different line styles and ; modifications through overrides of ; TextSpanner grob. ; (line (ly:line-interface::line ; grob ; start-offset 0.0 ; (+ (car space) accum end-offset) 0.0)) (text-width (interval-length (ly:stencil-extent left-text X))) (text-and-line (ly:stencil-add left-text (ly:stencil-translate-axis line text-width X)))) (loop (cdr contents) (cdr space) (+ X-coord text-width (car space) accum) 0.0 next (ly:stencil-add stil (ly:stencil-translate-axis text-and-line X-coord X)))))))))))) (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)) %% Based on addTextSpannerText, by Thomas Morley. See %% http://www.mail-archive.com/lilypond-user%40gnu.org/msg81685.html addT = #(define-music-function (text) (list?) #{ % The following two overrides are needed to give the text spanner % stencil the correct length. Temporary solution. We shouldn't % need to calculate a default stencil, then replace it. \once \override TextSpanner.bound-details.right.text = #(last text) \once \override TextSpanner.bound-details.right-broken.text = ##f \once \override TextSpanner.stencil = #(lambda (grob) (let* ((stil (ly:line-spanner::print grob)) ;; have we been split? (orig (ly:grob-original grob)) ;; if yes, get the split pieces (our siblings) (siblings (if (ly:grob? orig) (ly:spanner-broken-into orig) '()))) (if (> (length siblings) 2) (begin (ly:warning "More than two linebreaks are not supported.") stil) (let* (;; split the input-string into parts (text-lst text) ;; how many parts did we get? (txt-lst-length (length text-lst)) ;; get a divisor to split `text-lst' into parts ;; to be printed before and after the line break. ;; TODO: find better method (partial (if (null? siblings) 1 (floor (/ txt-lst-length (length siblings))))) ;; get the parts ;; adding an empty string at end of first line, ;; and start of second line (head (append (list-head text-lst partial) (list (make-null-markup)))) (tail (cons (make-null-markup) (list-tail text-lst partial))) (stil-ext-X (ly:stencil-extent stil X)) (line-width (interval-length stil-ext-X)) (text-to-use (cond ((null? siblings) text-lst) ((eq? grob (car siblings)) head) ((eq? grob (cadr siblings)) tail))) (padding (ly:grob-property grob 'padding 0.0))) (ly:stencil-translate-axis (justify-line-helper grob text-to-use 0.1 line-width #t padding) (car stil-ext-X) X))))) #}) %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \relative c'' { \addT #(list "ral" "len" "tan" "do") c,1\startTextSpan d'2 d\stopTextSpan \override TextSpanner.padding = 0.5 \addT #(list #{ \markup \raise #-0.5 "ral" #} #{ \markup \raise #-0.5 "len" #} #{ \markup \raise #-0.5 "tan" #} #{ \markup \raise #-0.5 "do" #} ) c,1\startTextSpan \break d'2 d\stopTextSpan \addT #'("ral" "len" "" "" "" "tan" "" "" "" "" "do") \break c,1\startTextSpan d'2 d\stopTextSpan \override TextSpanner.bound-details.left.padding = -0.5 \override TextSpanner.bound-details.right.padding = 1 \addT #(list #{ \markup \left-align \draw-circle #1 #0.2 ##t #} #{ \markup \left-align \with-color #grey \draw-circle #1 #0.2 ##t #} #{ \markup \left-align \draw-circle #1 #0.2 ##f #} ) \revert TextSpanner.padding \override TextSpanner.bound-details.right-broken.padding = 0.0 c1~\startTextSpan c1\stopTextSpan \addT #(list #{ \markup \left-align \draw-circle #1 #0.2 ##t #} #{ \markup \left-align \with-color #grey \draw-circle #1 #0.2 ##t #} #{ \markup \left-align \draw-circle #1 #0.2 ##f #} ) c1\startTextSpan \break d'2 d\stopTextSpan \break } \layout { ragged-right = ##f }