Hi David,

Am Sa., 2. Feb. 2019 um 21:35 Uhr schrieb David Nalesnik
<david.nales...@gmail.com>:
>
> Hi,
>
> On Sat, Feb 2, 2019 at 2:13 PM Thomas Morley <thomasmorle...@gmail.com> wrote:
> >
> > Am Sa., 2. Feb. 2019 um 21:09 Uhr schrieb Thomas Morley
> > <thomasmorle...@gmail.com>:
> >
> > > A bold workaround is to define the whole infrastructure for
> > > TextSpanners with new/renamed events (class and type), grobs,
> > > engravers and
> > > start/stop-commands.
> >
> > I forgot to point to a limitation
> > The provided 'text-spanner->text' doesn't care for line-breaks.

To clarify, 'text-spanner->text' has nothing to do with multiple TextSpanners.
It's a stencil-override. special line-breaking behaviour is not coded, though.

> There is this: 
> https://www.mail-archive.com/lilypond-user@gnu.org/msg105826.html

Looks we have the same conversation every few years lol
Your approach is far less invasive, so preferable.

> Not sure if anything needs to be updated.

The fix mentioned here
http://lists.gnu.org/archive/html/lilypond-user/2017-11/msg00016.html
should be applied, then it still works.

Though, I noticed a little problem here and in scheme-text-spanner
from our regtests, too:
Usually one can set the direction via direction-modifiers, i.e. for
_\startTextSpan the TextSpanner is printed below. This does not work.

The direction may be catched in listener and applied to the grob in
process-music.
So I did in the attached file, not sure whether it's the best fix ...

Thanks,
  Harm
\version "2.19.82"

%% by David Nalesnik
%% https://www.mail-archive.com/lilypond-user@gnu.org/msg105826.html
%% see also:
%% http://lists.gnu.org/archive/html/lilypond-user/2017-11/msg00016.html
%% http://lists.gnu.org/archive/html/lilypond-user/2019-02/msg00034.html

%% Incorporating some code from the rewrite in Scheme 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)))))

#(define (assign-spanner-index spanner orig-ls)
   "Determine the position of a new spanner in an ordered sequence
of spanners.  The goal is for the sequence to begin with zero and
contain no gaps.  Return the index representing the spanner's position."
   (if (null? orig-ls)
       0
       (let loop ((ls orig-ls) (insert? #t) (result 0))
         (cond
          ((null? ls) result)
          ;; position at head of list
          ((and insert? (> (caar orig-ls) 0))
           (loop ls #f 0))
          ;; no gaps, put at end of list
          ((and insert? (null? (cdr ls)))
           (loop (cdr ls) #f (1+ (caar ls))))
          ;; fill lowest position of gap
          ((and insert?
                (> (caadr ls) (1+ (caar ls))))
           (loop (cdr ls) #f (1+ (caar ls))))
          (else (loop (cdr ls) insert? result))))))

alternateTextSpannerEngraver =
#(lambda (context)
   (let (;; a list of pairs comprising a spanner index
          ;;  (not spanner-id) and a spanner which has been begun
          (spanners '())
          (finished '()) ; list of spanners in completion stage
          (ev-direction '()) 
          (start-events '()) ; list of START events
          (stop-events '())) ; 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))
            (begin
              (set! start-events (cons event start-events))
              (set! ev-direction (ly:event-property event 'direction 1)))
            (set! stop-events (cons event stop-events)))))
      ;; 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
                     (cdr s) 'note-columns grob)
                    (add-bound-item (cdr s) grob))
          spanners)
        ;; finished only contains spanners, no indices
        (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 'spanners' 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 spanners))
              (if (null? sp)
                  (ly:warning "No spanner to end!!")
                  (let ((sp-id (ly:event-property
                                (event-cause (cdar sp)) 'spanner-id)))
                    (cond
                     ((or
                       ;(and
                       ; (string? sp-id)
                       ; (string? es-id)
                       ; (string=? sp-id es-id)) 
                       (and 
                        (index? sp-id)
                        (index? es-id)
                        (eqv? sp-id es-id))
                       ;; deal with \startTextSpan, \stopTextSpan
                       (and
                        (null? sp-id)
                        (null? es-id)))
                      (set! finished (cons (cdar sp) finished))
                      (set! spanners (remove (lambda (s) (eq? s (car sp))) spanners)))
                     (else (loop (cdr sp)))))))))
        stop-events)

       ;; 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 in response to START events.
       ;;
       ;; Each new spanner is assigned an index denoting its position relative to
       ;; other active spanners.  This is enforced (for the moment) by adding
       ;; a small amount to the spanner's 'outside-staff-priority proportional to
       ;; this index.  This is unlikely to result in conflicts, though a better
       ;; solution may be to organize the spanners by a new alignment grob.
       ;;
       ;; Also, add any existing spanners to the 'side-support-elements array of
       ;; the new spanner.  This ensures correct ordering over line breaks when
       ;; 'outside-staff-priority is set to #f (which means that it is no longer
       ;; an outside-staff-object--not the default).
       (for-each
        (lambda (es)
          (let* ((new (ly:engraver-make-grob trans 'TextSpanner es))
                 (new-idx (assign-spanner-index new spanners))
                 (new-osp (ly:grob-property new 'outside-staff-priority))
                 (new-osp (if (number? new-osp)
                              (+ new-osp (/ new-idx 1000.0))
                              new-osp)))
            (set! (ly:grob-property new 'outside-staff-priority) new-osp)
            (ly:grob-set-property! new 'direction ev-direction)
            (set-axis! new Y)
            ;; Add spanners with a lower index than new spanner to
            ;; its 'side-support-elements.  This allows new spanners
            ;; to fill gaps under the topmost spanner.
            (for-each
             (lambda (sp)
               (if (< (car sp) new-idx)
                   (ly:pointer-group-interface::add-grob new
                     'side-support-elements (cdr sp))))
             spanners)
            (set! spanners (cons (cons new-idx new) spanners))
            (set! spanners
                  (sort spanners (lambda (x y) (< (car x) (car y)))))))
        start-events)
       ;; Events have served their purpose for this timestep.  Clear
       ;; the way for new events in later timesteps.
       (set! start-events '())
       (set! ev-direction '())
       (set! stop-events '()))

      ((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 (cdr s) LEFT))
              (ly:spanner-set-bound! (cdr s) LEFT
                (ly:context-property context 'currentMusicalColumn))))
        spanners)

       (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! (cdr sp)))
        spanners)
       (set! spanners '())))))

%%%%%%%%%%%%%%%%%%%%%%%%%%%%% EXAMPLE %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\layout {
  \context {
    \Voice
    \remove #"Text_spanner_engraver"
    \consists \alternateTextSpannerEngraver
  }
}

\relative c' {
  \override TextSpanner.style = ##f
  \override TextSpanner.thickness = 10
  \override TextSpanner.outside-staff-padding = 1.5
  a4
  -\tweak color #red
  \startTextSpan
  b
  -\tweak color #green
  \=1\startTextSpan
  c
  -\tweak color #blue
  \=2_\startTextSpan
  d
  \=3\startTextSpan

  a4\stopTextSpan
  b
  \=1\stopTextSpan
  -\tweak color #red
  \startTextSpan
  c
  \=2\stopTextSpan
  -\tweak color #green
  \=1\startTextSpan
  d
  \=3\stopTextSpan
  -\tweak color #blue
  \=2\startTextSpan

  a4
  \=3\startTextSpan
  b c d
  \break

  a4 b c d

  a1\stopTextSpan\=1\stopTextSpan\=2\stopTextSpan\=3\stopTextSpan
}
_______________________________________________
lilypond-user mailing list
lilypond-user@gnu.org
https://lists.gnu.org/mailman/listinfo/lilypond-user

Reply via email to