\version "2.23.10" #(define (get-markup-width grob text) (let* ((layout (ly:grob-layout grob)) (props (ly:grob-alist-chain grob (ly:output-def-lookup layout 'text-font-defaults)))) (interval-length (ly:stencil-extent (interpret-markup layout props (markup text)) X)))) #(define (text-spanner-minimum-length grob padding) (let* ((details (ly:grob-property grob 'bound-details)) (lprops (assoc-get 'left details '())) (ltext (assoc-get 'text lprops '())) (lwidth (get-markup-width grob ltext)) (rprops (assoc-get 'right details '())) (rtext (assoc-get 'text rprops '())) (rwidth (get-markup-width grob rtext)) (minlength (+ lwidth rwidth padding))) ;; (pretty-print (list lwidth rwidth minlength)) (ly:grob-set-property! grob 'minimum-length minlength) ;; TODO ideally we could set a property `minimum-length-before-break' (ly:grob-set-property! grob 'minimum-length-after-break rwidth))) transitionSpanner = #(define-music-function (padding from to) ((number? 2) markup? markup?) (_i "Set up a TextSpanner to indicate transition from one technique to another. Spanner is forced to a @code{minimum-length} of the combined widths of the markups @var{from} and @var{to} plus the extra value of @var{padding}. @var{to} markup is right-aligned.") ;; TODO recalculate minimum-length when broken, otherwise individual parts are stretched unnecessarily ;; PROBLEM: springs-and-rods has already run by the time the spanner is broken #{ \override TextSpanner.style = #'dashed-line \override TextSpanner.dash-fraction = 0.35 \override TextSpanner.dash-period = 1.2 \override TextSpanner.bound-details.left.text = \markup \concat { #from \hspace #0.5 } \override TextSpanner.bound-details.right.text = \markup \halign #RIGHT \concat { \hspace #0.5 #to } \override TextSpanner.bound-details.left-broken.text = ##f \override TextSpanner.bound-details.right-broken.text = ##f \override TextSpanner.bound-details.left.padding = #0 \override TextSpanner.bound-details.right.padding = #0 \override TextSpanner.bound-details.right.attach-dir = #RIGHT \override TextSpanner.bound-details.left.stencil-offset = #'(0 . -0.5) \override TextSpanner.bound-details.right.stencil-offset = #'(0 . -0.5) \override TextSpanner.bound-details.right.arrow = ##t \override TextSpanner.before-line-breaking = #(lambda (grob) (text-spanner-minimum-length grob padding)) \override TextSpanner.springs-and-rods = #ly:spanner::set-spacing-rods #}) % { %% EXAMPLES AND TESTS { c'1 \once \transitionSpanner "sul pont" "sul tasto" \once \override TextSpanner.before-line-breaking = ##f c'4\startTextSpan^\markup \with-color #red \box \column { "without adjustment" "BAD" } bes'4 bes'4 f''4\stopTextSpan \repeat unfold 16 c'16 \once \transitionSpanner "sul pont" "sul tasto" c'4\startTextSpan^\markup \with-color #blue \box \column { "with adjustment" "GOOD" } bes'4 bes'4 f''4\stopTextSpan c'1 c'1 \repeat unfold 16 c'16 c'4 4 \once \transitionSpanner "sul pont" "sul tasto" c'4\startTextSpan^\markup \with-color #red \box \column { "but too long" "when broken" "BAD" } bes'4 \break bes'4 f''4\stopTextSpan c'2 \repeat unfold 16 c'16 c'4 4 \once \transitionSpanner "sul pont" "sul tasto" \once \override TextSpanner.before-line-breaking = ##f c'4\startTextSpan^\markup \with-color #blue \box \column { "without" "adjustment" "GOOD" } bes'4 \break bes'4 f''4\stopTextSpan c'2 \repeat unfold 24 c'16 2 } %}