%\version "2.18.0" % 2.19.8 works, 2.19.15 fails \version "2.19.15" % courtesy of David Nalesnik %%%%%%%%%%%%%%%% USAGE: %%%%%%%%%%%%%%%%%%%% % add the following in \layout: %{ \context { \Global \grobdescriptions #all-grob-descriptions } \context { \Lyrics \consists \collectlyricwordEngraver \wordcompress 0.3 % or another value between ca. 0.1 and 0.5 } %} %%%%%%%%%% ADD NEW GROB INTERFACE %%%%%%%%%%%%%%% #(ly:add-interface 'lyric-word-interface "A word of lyrics. Includes syllables and hyphens." '(text-items)) %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%% CREATE NEW GROB PROPERTY %%%%%%%%%%% #(define (define-grob-property symbol type? description) (if (not (equal? (object-property symbol 'backend-doc) #f)) (ly:error (_ "symbol ~S redefined") symbol)) (set-object-property! symbol 'backend-type? type?) (set-object-property! symbol 'backend-doc description) symbol) #(map (lambda (x) (apply define-grob-property x)) `( (text-items ,list? "Syllables and hyphens of a word of lyrics"))) %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%% ADD DEFINITION OF GROB %%%%%%%%%%%%%% #(define (add-grob-definition grob-name grob-entry) (let* ((meta-entry (assoc-get 'meta grob-entry)) (class (assoc-get 'class meta-entry)) (ifaces-entry (assoc-get 'interfaces meta-entry))) (set-object-property! grob-name 'translation-type? list?) (set-object-property! grob-name 'is-grob? #t) (set! ifaces-entry (append (case class ((Item) '(item-interface)) ((Spanner) '(spanner-interface)) ((Paper_column) '((item-interface paper-column-interface))) ((System) '((system-interface spanner-interface))) (else '(unknown-interface))) ifaces-entry)) (set! ifaces-entry (uniq-list (sort ifaces-entry symbol= (interval-length hyphen-ex) threshold) ;; >= : fix broken-word bug '() ; no compression--DO NOTHING! (let* ((syl-a-text (ly:grob-property syl-a 'text)) (syl-a-text (if (markup? syl-a-text) syl-a-text (markup syl-a-text))) (syl-b-text (ly:grob-property syl-b 'text)) (syl-b-text (if (markup? syl-b-text) syl-b-text (markup syl-b-text))) (full-text (make-concat-markup (list syl-a-text syl-b-text)))) (set! (ly:grob-property syl-a 'text) full-text) (set! (ly:grob-property syl-b 'text) empty-markup) (set! (ly:grob-property syl-a 'stencil) lyric-text::print) (set! (ly:grob-property syl-b 'stencil) lyric-text::print) (set! (ly:grob-property hyphen 'stencil) empty-stencil))))) #(define (lyric-word-compressor threshold) (lambda (grob) ; LyricWord (let* ((items (ly:grob-object grob 'text-items)) (item-list (ly:grob-array->list items))) (if (> (length item-list) 1) ; do nothing to monosyllabic words (let* ((text-grobs (filter (lambda (item) (grob::has-interface item 'lyric-syllable-interface)) item-list)) (hyphen-grobs (filter (lambda (item) (grob::has-interface item 'lyric-hyphen-interface)) item-list))) (define (helper seed tx-list hy-list) (if (and (pair? (cdr tx-list)) (pair? hy-list)) (let ((next-syl (cadr tx-list)) (hyphen (car hy-list))) (compress-pair seed hyphen next-syl threshold) (if (equal? empty-markup (ly:grob-property next-syl 'text)) (helper seed (cdr tx-list) (cdr hy-list)) (helper (cadr tx-list) (cdr tx-list) (cdr hy-list)))))) (helper (car text-grobs) text-grobs hyphen-grobs)))))) %%%%%%%%%%%%%% SOME OTHER FUNCTIONS %%%%%%%%%%%%%% #(define (dim-hack grob ax) (let* ((elts (ly:grob-object grob 'text-items)) (common (ly:grob-common-refpoint-of-array grob elts ax)) (rel (ly:relative-group-extent elts common ax)) (off (ly:grob-relative-coordinate grob common ax))) (coord-translate rel (- off)))) #(define (height-hack grob) (dim-hack grob Y)) #(define (width-hack grob) (dim-hack grob X)) #(define (ly:lyric-word::underline grob) (let* ((height (height-hack grob)) (width (width-hack grob))) (make-line-stencil 0.1 (car width) 0 (cdr width) 0))) #(define (ly:lyric-word::boxer grob) (let* ((yext (height-hack grob)) (xext (width-hack grob)) (thick 0.1)) (ly:stencil-add (make-filled-box-stencil xext (cons (- (car yext) thick) (car yext))) (make-filled-box-stencil xext (cons (cdr yext) (+ (cdr yext) thick))) (make-filled-box-stencil (cons (cdr xext) (+ (cdr xext) thick)) yext) (make-filled-box-stencil (cons (- (car xext) thick) (car xext)) yext)))) wordunderline = \once \override LyricWord.stencil = #ly:lyric-word::underline wordbox = \once \override LyricWord.stencil = #ly:lyric-word::boxer %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%% HELPER FUNCTION %%%%%%%%%%%%%% wordcompress = #(define-music-function (parser location num) (number?) (cond ((< num 0) (ly:error "\\wordcompress should get a non-negative value.")) (else (begin (if (>= num 0.5) (ly:warning "Attention: 0.4 is a rather large value for \\wordcompress already!")) #{ \override LyricWord.after-line-breaking = #(lyric-word-compressor num) \override LyricHyphen.minimum-distance = #0 \override LyricSpace.minimum-distance = #1 #})))) %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \layout { \context { \Global \grobdescriptions #all-grob-descriptions } \context { \Lyrics \consists \collectLyricWordEngraver \wordcompress 0.3 % or another value between ca. 0.1 and 0.5 } } %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%% TEST %%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \score { << \relative { c' d e f g a b c g1 c,2. r4 } \addlyrics { Here I test the Ly -- ric Word Com -- pres -- sor. } >> }