\version "2.19" %% Based on the rewrite of Text_spanner_engraver in %% input/regression/scheme-text-spanner.ly #(define (add-bound-item spanner item) (if (null? (ly:spanner-bound spanner LEFT)) (ly:spanner-set-bound! spanner LEFT item) (ly:spanner-set-bound! spanner RIGHT item))) #(define (axis-offset-symbol axis) (if (eq? axis X) 'X-offset 'Y-offset)) #(define (set-axis! grob axis) (if (not (number? (ly:grob-property grob 'side-axis))) (begin (set! (ly:grob-property grob 'side-axis) axis) (ly:grob-chain-callback grob (if (eq? axis X) ly:side-position-interface::x-aligned-side side-position-interface::y-aligned-side) (axis-offset-symbol axis))))) schemeTextSpannerEngraver = #(lambda (context) (let ((span '()) ; list of started spanners (finished '()) ; list of spanners in completion stage (event-start '()) ; list of START events (event-stop '())) ; list of STOP events (make-engraver ;; \startTextSpan, \stopTextSpan, and the like create events ;; which we collect here. (listeners ((text-span-event engraver event) (if (= START (ly:event-property event 'span-direction)) (set! event-start (cons event event-start)) (set! event-stop (cons event event-stop))))) ;; Populate 'note-columns property of spanners. Bounds are ;; set to note columns, and each spanner keeps a record of ;; the note columns it traverses. (acknowledgers ((note-column-interface engraver grob source-engraver) (for-each (lambda (s) (ly:pointer-group-interface::add-grob s 'note-columns grob) (add-bound-item s grob)) span) (for-each (lambda (f) (ly:pointer-group-interface::add-grob f 'note-columns grob) (add-bound-item f grob)) finished))) ((process-music trans) ;; Move begun spanners from 'span' to 'finished'. We do this ;; on the basis of 'spanner-id. If we find a match--either ;; the strings are the same, or both are unset--a transfer ;; can be made. Return a warning if we find no match: spanner ;; hasn't been properly begun. (for-each (lambda (es) (let ((es-id (ly:event-property es 'spanner-id))) (let loop ((sp span)) (let ((sp-id (ly:event-property (event-cause (car sp)) 'spanner-id))) (cond ((null? sp) (ly:warning "No spanner to end!!")) ((and (string? sp-id) (string? es-id) (string=? sp-id es-id)) (set! finished (cons (car sp) finished)) (set! span (remove (lambda (s) (eq? s (car sp))) span))) ((and (null? sp-id) (null? es-id)) (set! finished (cons (car sp) finished)) (set! span (remove (lambda (s) (eq? s (car sp))) span))) (else (loop (cdr sp)))))))) event-stop) ;; The end of our spanners can be acknowledged by other engravers. (for-each (lambda (f) (ly:engraver-announce-end-grob trans f (event-cause f))) finished) ;; Make spanners called for by START events. To each new spanner, ;; add any existing spanners to the 'side-support-elements array. ;; This ensures correct ordering over line breaks when 'outside- ;; staff-priority is set to #f. Ordinarily, for TextSpanner this ;; is 350. (for-each (lambda (es) (let ((new (ly:engraver-make-grob trans 'TextSpanner es))) (for-each (lambda (sp) (ly:pointer-group-interface::add-grob new 'side-support-elements sp)) span) (set! span (cons new span)) (set-axis! (car span) Y))) event-start) ;; Events have served their purpose for this timestep. Clear ;; the way for new events in later timesteps. (set! event-start '()) (set! event-stop '())) ((stop-translation-timestep trans) ;; Set bounds of spanners to PaperColumns if they haven't been set. ;; This allows spanners to be drawn between spacers. Other uses? ;; Doesn't appear to affect whether spanners can de drawn between ;; rests. (for-each (lambda (s) (if (null? (ly:spanner-bound s LEFT)) (ly:spanner-set-bound! s LEFT (ly:context-property context 'currentMusicalColumn)))) span) (for-each (lambda (f) (if (null? (ly:spanner-bound f RIGHT)) (ly:spanner-set-bound! f RIGHT (ly:context-property context 'currentMusicalColumn)))) finished) (set! finished '())) ((finalize trans) ;; If spanner ends on spacer at end of context? (for-each (lambda (f) (if (null? (ly:spanner-bound f RIGHT)) (ly:spanner-set-bound! f RIGHT (ly:context-property context 'currentMusicalColumn)))) finished) (set! finished '()) ;; User didn't end spanner. (for-each (lambda (sp) (ly:warning "incomplete spanner removed!") (ly:grob-suicide! sp)) span) (set! span '()))))) startTextSpanOne = #(make-music 'TextSpanEvent 'span-direction START 'spanner-id "1") stopTextSpanOne = #(make-music 'TextSpanEvent 'span-direction STOP 'spanner-id "1") startTextSpanTwo = #(make-music 'TextSpanEvent 'span-direction START 'spanner-id "2") stopTextSpanTwo = #(make-music 'TextSpanEvent 'span-direction STOP 'spanner-id "2") startTextSpanThree = #(make-music 'TextSpanEvent 'span-direction START 'spanner-id "3") stopTextSpanThree = #(make-music 'TextSpanEvent 'span-direction STOP 'spanner-id "3") %%%%%%%%%%%%%%%%%%%%%%%%%%%%% EXAMPLE %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \layout { \context { \Voice \remove #"Text_spanner_engraver" \consists \schemeTextSpannerEngraver } } \relative c' { %\override TextSpanner.outside-staff-priority = ##f %% This line alone will produce correct order!! %% 0.46 is the value of default_outside_staff_padding_, set in %% cc/axis-group-interface,cc for objects that don't have a %% setting for this property. TextSpanner doesn't. By %% setting 'padding slightly higher, we signal a collision %% which (perhaps) forces a rearrangement in %% Axis_group_interface::avoid_outside_staff_collisions. \override TextSpanner.padding = 0.460000000000001 a4 -\tweak style ##f -\tweak color #red -\tweak thickness 10 \startTextSpan -\tweak style ##f -\tweak color #green -\tweak thickness 10 \startTextSpanOne b c -\tweak style ##f -\tweak thickness 10 \startTextSpanThree d %1 -\tweak style ##f -\tweak color #blue -\tweak thickness 10 \startTextSpanTwo a4 b c d %2 a4 b c d %3 \break a4 b c d %4 a4 b c d %5 a4 b c d \stopTextSpan\stopTextSpanOne\stopTextSpanTwo\stopTextSpanThree %6 }