k-l-lambda's picture
added node-addon-lilypond
f65fe85
;;;; This file is part of LilyPond, the GNU music typesetter.
;;;;
;;;; Copyright (C) 2009--2020 Marc Hohl <[email protected]>
;;;;
;;;; LilyPond is free software: you can redistribute it and/or modify
;;;; it under the terms of the GNU General Public License as published by
;;;; the Free Software Foundation, either version 3 of the License, or
;;;; (at your option) any later version.
;;;;
;;;; LilyPond is distributed in the hope that it will be useful,
;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;;;; GNU General Public License for more details.
;;;;
;;;; You should have received a copy of the GNU General Public License
;;;; along with LilyPond. If not, see <http://www.gnu.org/licenses/>.
;; TODO:
;; (1) Dashed bar lines may stick out above and below the staff lines
;;
;; (2) Dashed and dotted lines look ugly in combination with span bars
;;
;; (This was the case in the c++-version of (span) bar stuff)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; helper functions for staff and layout properties
(define (bar-line::calc-blot thickness extent grob)
"Calculate the blot diameter by taking @code{'rounded}
and the dimensions of the extent into account."
(let* ((rounded (ly:grob-property grob 'rounded #f))
(blot (if rounded
(let ((blot-diameter (layout-blot-diameter grob))
(height (interval-length extent)))
(cond ((< thickness blot-diameter) thickness)
((< height blot-diameter) height)
(else blot-diameter)))
0)))
blot))
(define-public (bar-line::draw-filled-box x-ext y-ext thickness extent grob)
"Return a straight bar-line created by @code{ly:round-filled-box} looking at
@var{x-ext}, @var{y-ext}, @var{thickness}. The blot is calculated by
@code{bar-line::calc-blot}, which needs @var{extent} and @var{grob}.
@var{y-ext} is not necessarily of same value as @var{extent}."
(ly:round-filled-box
x-ext
y-ext
(bar-line::calc-blot thickness extent grob)))
(define (get-span-glyph bar-glyph)
"Get the corresponding span glyph from the @code{span-glyph-bar-alist}.
Pad the string with @code{annotation-char}s to the length of the
@var{bar-glyph} string."
(let ((span-glyph (assoc-get bar-glyph span-bar-glyph-alist bar-glyph)))
(if (string? span-glyph)
(set! span-glyph (string-pad-right
span-glyph
(string-length bar-glyph)
replacement-char)))
span-glyph))
(define (get-staff-symbol grob)
"Return the staff symbol corresponding to Grob @var{grob}."
(if (grob::has-interface grob 'staff-symbol-interface)
grob
(ly:grob-object grob 'staff-symbol)))
(define (layout-blot-diameter grob)
"Get the blot diameter of the @var{grob}'s corresponding layout."
(let* ((layout (ly:grob-layout grob))
(blot-diameter (ly:output-def-lookup layout 'blot-diameter)))
blot-diameter))
(define (staff-symbol-line-count staff)
"Get or compute the number of lines of staff @var{staff}."
(let ((line-count 0))
(if (ly:grob? staff)
(let ((line-pos (ly:grob-property staff 'line-positions '())))
(set! line-count (if (pair? line-pos)
(length line-pos)
(ly:grob-property staff 'line-count 0)))))
line-count))
(define (staff-symbol-line-span grob)
(let ((line-pos (ly:grob-property grob 'line-positions '()))
(iv (cons 0.0 0.0)))
(if (pair? line-pos)
(begin
(set! iv (cons (car line-pos) (car line-pos)))
(for-each (lambda (x)
(set! iv (cons (min (car iv) x)
(max (cdr iv) x))))
(cdr line-pos)))
(let ((line-count (ly:grob-property grob 'line-count 0)))
(set! iv (cons (- 1 line-count)
(- line-count 1)))))
iv))
(define (staff-symbol-line-positions grob)
"Get or compute the @code{'line-positions} list from @var{grob}."
(let ((line-pos (ly:grob-property grob 'line-positions '())))
(if (not (pair? line-pos))
(let* ((line-count (ly:grob-property grob 'line-count 0))
(height (- line-count 1.0)))
(set! line-pos (map (lambda (x)
(- height (* x 2)))
(iota line-count)))))
line-pos))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; internal helper functions
(define annotation-char #\-)
(define replacement-char #\ )
(define dummy-extent (cons -1 1))
(define (glyph->stencil glyph grob extent)
"Return a stencil computed by the procedure associated with
glyph @var{glyph}. The arguments @var{grob} and @var{extent} are
mandatory to the procedures stored in @code{bar-glyph-print-procedures}."
(let ((proc (assoc-get glyph bar-glyph-print-procedures))
(stencil empty-stencil))
(if (procedure? proc)
(set! stencil (proc grob extent))
(ly:warning (_ "Bar glyph ~a not known. Ignoring.") glyph))
stencil))
(define (string->string-list str)
"Convert a string into a list of strings with length 1.
@code{\"aBc\"} will be converted to @code{(\"a\" \"B\" \"c\")}.
An empty string will be converted to a list containing @code{\"\"}."
(if (and (string? str)
(not (zero? (string-length str))))
(map (lambda (s)
(string s))
(string->list str))
(list "")))
(define (strip-string-annotation str)
"Strip annotations starting with and including the
annotation char from string @var{str}."
(let ((pos (string-index str annotation-char)))
(if pos
(substring str 0 pos)
str)))
(define (check-for-annotation str)
"Check whether the annotation char is present in string @var{str}."
(if (string? str)
(if (string-index str annotation-char)
(ly:warning
(_ "Annotation '~a' is allowed in the first argument of a bar line definition only.")
str))))
(define (check-for-replacement str)
"Check whether the replacement char is present in string @var{str}."
(if (string? str)
(if (string-index str replacement-char)
(ly:warning
(_ "Replacement '~a' is allowed in the last argument of a bar line definition only.")
str))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Line break decisions.
(define-public (define-bar-line bar-glyph eol-glyph bol-glyph span-glyph)
"Define a bar glyph @var{bar-glyph} and its substitute at the end of
a line (@var{eol-glyph}), at the beginning of a new line (@var{bol-glyph})
and as a span bar (@var{span-glyph}) respectively."
;; the last argument may not include annotations
(check-for-annotation span-glyph)
;; only the last argument may call for replacements
(for-each (lambda (s)
(check-for-replacement s))
(list bar-glyph eol-glyph bol-glyph))
;; the bar-glyph-alist has entries like
;; (bar-glyph . ( eol-glyph . bol-glyph))
(set! bar-glyph-alist
(acons bar-glyph (cons eol-glyph bol-glyph) bar-glyph-alist))
;; the span-bar-glyph-alist has entries like
;; (bar-glyph . span-glyph)
(set! span-bar-glyph-alist
(acons bar-glyph span-glyph span-bar-glyph-alist)))
(define-session bar-glyph-alist '())
(define-session span-bar-glyph-alist '())
(define-public (add-bar-glyph-print-procedure glyph proc)
"Specify the single glyph @var{glyph} that calls print procedure @var{proc}.
The procedure @var{proc} has to be defined in the form
@code{(make-...-bar-line grob extent)} even if the @var{extent}
is not used within the routine."
(if (or (not (string? glyph))
(> (string-length glyph) 1))
(ly:warning
(_ "add-bar-glyph-print-procedure: glyph '~a' has to be a single ASCII character.")
glyph)
(set! bar-glyph-print-procedures
(acons glyph proc bar-glyph-print-procedures))))
(define-session bar-glyph-print-procedures `())
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; drawing functions for various bar line types
;; to include them and other user-defined functions,
;; all of them have the form
;; (make-...-bar-line grob extent)
;; even if the extent is not used.
(define (make-empty-bar-line grob extent)
"Draw an empty bar line."
(ly:make-stencil "" (cons 0 0) extent))
(define (make-simple-bar-line grob extent)
"Draw a simple bar line."
(let* ((line-thickness (layout-line-thickness grob))
(thickness (* (ly:grob-property grob 'hair-thickness 1)
line-thickness))
(extent (bar-line::widen-bar-extent-on-span grob extent)))
(bar-line::draw-filled-box
(cons 0 thickness)
extent
thickness
extent
grob)))
(define (make-thick-bar-line grob extent)
"Draw a thick bar line."
(let* ((line-thickness (layout-line-thickness grob))
(thickness (* (ly:grob-property grob 'thick-thickness 1)
line-thickness))
(extent (bar-line::widen-bar-extent-on-span grob extent)))
(bar-line::draw-filled-box
(cons 0 thickness)
extent
thickness
extent
grob)))
(define (make-tick-bar-line grob extent)
"Draw a tick bar line."
(let* ((half-staff (* 1/2 (ly:staff-symbol-staff-space grob)))
(staff-line-thickness (ly:staff-symbol-line-thickness grob))
(height (interval-end extent)))
(bar-line::draw-filled-box
(cons 0 staff-line-thickness)
(cons (- height half-staff) (+ height half-staff))
staff-line-thickness
extent
grob)))
(define (make-colon-bar-line grob extent)
"Draw repeat dots."
(let* ((staff-space (ly:staff-symbol-staff-space grob))
(line-thickness (ly:staff-symbol-line-thickness grob))
(dot (ly:font-get-glyph (ly:grob-default-font grob) "dots.dot"))
(dot-y-length (interval-length (ly:stencil-extent dot Y)))
(stencil empty-stencil)
;; the two dots of the repeat sign should be centred at the
;; middle of the staff and neither should collide with staff
;; lines.
;; the required space is measured in line positions,
;; i.e. in half staff spaces.
;; dots are to fall into distict spaces, except when there's
;; only one space (and it's big enough to hold two dots and
;; some space between them)
;; choose defaults working without any staff
(center 0.0)
(dist (* 4 dot-y-length)))
(if (> staff-space 0)
(begin
(set! dist (/ dist staff-space))
(let ((staff-symbol (get-staff-symbol grob)))
(if (ly:grob? staff-symbol)
(let ((line-pos (staff-symbol-line-positions staff-symbol)))
(if (pair? line-pos)
(begin
(set! center
(interval-center (staff-symbol-line-span
staff-symbol)))
;; fold the staff into two at center
(let* ((folded-staff
(sort (map (lambda (lp) (abs (- lp center)))
line-pos) <))
(gap-to-find (/ (+ dot-y-length line-thickness)
(/ staff-space 2)))
(first (car folded-staff)))
;; find the first space big enough
;; to hold a dot and a staff line
;; (a space in the folded staff may be
;; narrower but can't be wider than the
;; corresponding original spaces)
(set! dist
(or
(any (lambda (x y)
(and (> (- y x) gap-to-find)
(+ x y)))
folded-staff (cdr folded-staff))
(if (< gap-to-find first)
;; there's a central space big
;; enough to hold both dots
first
;; dots should go outside
(+ (* 2 (last folded-staff))
(/ (* 4 dot-y-length)
staff-space))))))))))))
(set! staff-space 1.0))
(let* ((stencil empty-stencil)
(stencil (ly:stencil-add stencil dot))
(stencil (ly:stencil-translate-axis
stencil (* dist (/ staff-space 2)) Y))
(stencil (ly:stencil-add stencil dot))
(stencil (ly:stencil-translate-axis
stencil (* (- center (/ dist 2))
(/ staff-space 2)) Y)))
stencil)))
(define (make-dotted-bar-line grob extent)
"Draw a dotted bar line."
(let* ((position (round (* (interval-end extent) 2)))
(correction (if (even? position) 0.5 0.0))
(dot (ly:font-get-glyph (ly:grob-default-font grob) "dots.dot"))
(i (round (+ (interval-start extent)
(- 0.5 correction))))
(e (round (+ (interval-end extent)
(- 0.5 correction))))
(counting (interval-length (cons i e)))
(stil-list (map
(lambda (x)
(ly:stencil-translate-axis
dot (+ x correction) Y))
(iota counting i 1))))
(define (add-stencils! stil l)
(if (null? l)
stil
(if (null? (cdr l))
(ly:stencil-add stil (car l))
(add-stencils! (ly:stencil-add stil (car l)) (cdr l)))))
(add-stencils! empty-stencil stil-list)))
(define (make-dashed-bar-line grob extent)
"Draw a dashed bar line."
(let* ((height (interval-length extent))
(staff-symbol (get-staff-symbol grob))
(staff-space (ly:staff-symbol-staff-space grob))
(line-thickness (layout-line-thickness grob))
(thickness (* (ly:grob-property grob 'hair-thickness 1)
line-thickness))
(dash-size (- 1.0 (ly:grob-property grob 'gap 0.3)))
(line-count (staff-symbol-line-count staff-symbol)))
(if (< (abs (+ line-thickness
(* (1- line-count) staff-space)
(- height)))
0.1)
(let ((blot (layout-blot-diameter grob))
(half-space (/ staff-space 2.0))
(half-thick (/ line-thickness 2.0))
(stencil empty-stencil))
(for-each (lambda (i)
(let ((top-y (min (* (+ i dash-size) half-space)
(+ (* (1- line-count) half-space)
half-thick)))
(bot-y (max (* (- i dash-size) half-space)
(- 0 (* (1- line-count) half-space)
half-thick))))
(set! stencil
(ly:stencil-add
stencil
(ly:round-filled-box (cons 0 thickness)
(cons bot-y top-y)
blot)))))
(iota line-count (1- line-count) (- 2)))
stencil)
(let* ((dashes (/ height staff-space))
(total-dash-size (/ height dashes))
(factor (/ (- dash-size thickness) staff-space))
(stencil (ly:stencil-translate-axis
(ly:make-stencil (list 'dashed-line
thickness
(* factor total-dash-size)
(* (- 1 factor) total-dash-size)
0
height
(* factor total-dash-size 0.5))
(cons (/ thickness -2) (/ thickness 2))
(cons 0 height))
(interval-start extent)
Y)))
(ly:stencil-translate-axis stencil (/ thickness 2) X)))))
(define ((make-segno-bar-line show-segno) grob extent)
"Draw a segno bar line. If @var{show-segno} is set to @code{#t},
the segno sign is drawn over the double bar line; otherwise, it
draws the span bar variant, i.e. without the segno sign."
(let* ((line-thickness (layout-line-thickness grob))
(segno-kern (* (ly:grob-property grob 'segno-kern 1) line-thickness))
(thin-stil (make-simple-bar-line grob extent))
(double-line-stil (ly:stencil-combine-at-edge
thin-stil
X
LEFT
thin-stil
segno-kern))
(segno (ly:font-get-glyph (ly:grob-default-font grob)
"scripts.varsegno"))
(stencil (ly:stencil-add
(if show-segno
segno
(ly:make-stencil
""
(ly:stencil-extent segno X)
(cons 0 0)))
(ly:stencil-translate-axis
double-line-stil
(* 1/2 segno-kern)
X))))
stencil))
(define (make-kievan-bar-line grob extent)
"Draw a kievan bar line."
(let* ((font (ly:grob-default-font grob))
(stencil (stencil-whiteout-box
(ly:font-get-glyph font "scripts.barline.kievan"))))
;; the kievan bar line has no staff lines underneath,
;; so we whiteout-box them and move the grob to a higher layer
(ly:grob-set-property! grob 'layer 1)
stencil))
(define ((make-bracket-bar-line dir) grob extent)
"Draw a bracket-style bar line. If @var{dir} is set to @code{LEFT}, the
opening bracket will be drawn, for @code{RIGHT} we get the closing bracket."
(let* ((thick-stil (make-thick-bar-line grob extent))
(brackettips-up (ly:font-get-glyph (ly:grob-default-font grob)
"brackettips.up"))
(brackettips-down (ly:font-get-glyph (ly:grob-default-font grob)
"brackettips.down"))
;; the x-extent of the brackettips must not be taken into account
;; for bar line constructs like "[|:", so we set new bounds:
(tip-up-stil (ly:make-stencil (ly:stencil-expr brackettips-up)
(cons 0 0)
(ly:stencil-extent brackettips-up Y)))
(tip-down-stil (ly:make-stencil (ly:stencil-expr brackettips-down)
(cons 0 0)
(ly:stencil-extent brackettips-down Y)))
(stencil (ly:stencil-add
thick-stil
(ly:stencil-translate-axis tip-up-stil
(interval-end extent)
Y)
(ly:stencil-translate-axis tip-down-stil
(interval-start extent)
Y))))
(if (eqv? dir LEFT)
stencil
(flip-stencil X stencil))))
(define ((make-spacer-bar-line glyph) grob extent)
"Draw an invisible bar line which has the same dimensions as the one
drawn by the procedure associated with glyph @var{glyph}."
(let* ((stil (glyph->stencil glyph grob extent))
(stil-x-extent (ly:stencil-extent stil X)))
(ly:make-stencil "" stil-x-extent extent)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; bar line callbacks
(define-public (ly:bar-line::calc-bar-extent grob)
(let ((staff-symbol (get-staff-symbol grob))
(staff-extent (cons 0 0)))
(if (ly:grob? staff-symbol)
(let ((bar-line-color (ly:grob-property grob 'color))
(staff-color (ly:grob-property staff-symbol 'color))
(half-staff-line-thickness (/ (ly:staff-symbol-line-thickness grob) 2))
(staff-space (ly:staff-symbol-staff-space grob)))
(set! staff-extent (ly:staff-symbol::height staff-symbol))
(if (zero? staff-space)
(set! staff-space 1.0))
(if (< (interval-length staff-extent) staff-space)
;; staff is too small (perhaps consists of a single line);
;; extend the bar line to make it visible
(set! staff-extent
(interval-widen staff-extent staff-space))
;; Due to rounding problems, bar lines extending to the outermost edges
;; of the staff lines appear wrongly in on-screen display
;; (and, to a lesser extent, in print) - they stick out a pixel.
;; The solution is to extend bar lines only to the middle
;; of the staff line - unless they have different colors,
;; when it would be undesirable.
;;
;; This reduction should not influence whether the bar is to be
;; expanded later, so length is not updated on purpose.
(if (eq? bar-line-color staff-color)
(set! staff-extent
(interval-widen staff-extent
(- half-staff-line-thickness)))))))
staff-extent))
;; this function may come in handy when defining new bar line glyphs, so
;; we make it public.
;; This code should not be included in ly:bar-line::calc-bar-extent, because
;; this may confuse the drawing functions for dashed and dotted bar lines.
(define-public (bar-line::widen-bar-extent-on-span grob extent)
"Widens the bar line @var{extent} towards span bars adjacent to grob @var{grob}."
(let ((staff-symbol (get-staff-symbol grob))
(has-span-bar (ly:grob-object grob 'has-span-bar)))
(if (and (ly:grob? staff-symbol)
(pair? has-span-bar))
(let ((bar-line-color (ly:grob-property grob 'color))
(staff-color (ly:grob-property staff-symbol 'color))
(half-staff-line-thickness (/ (ly:staff-symbol-line-thickness grob) 2)))
(if (eq? bar-line-color staff-color)
;; if the colors are equal, ly:bar-line::calc-bar-extent has
;; shortened the bar line extent by a half-staff-line-thickness
;; this is reverted on the interval bounds where span bars appear:
(begin
(and (ly:grob? (car has-span-bar))
(set! extent (cons (- (car extent) half-staff-line-thickness)
(cdr extent))))
(and (ly:grob? (cdr has-span-bar))
(set! extent (cons (car extent)
(+ (cdr extent) half-staff-line-thickness))))))))
extent))
(define (bar-line::bar-y-extent grob refpoint)
"Compute the y-extent of the bar line relative to @var{refpoint}."
(let* ((extent (ly:grob-property grob 'bar-extent '(0 . 0)))
(rel-y (ly:grob-relative-coordinate grob refpoint Y))
(y-extent (coord-translate extent rel-y)))
y-extent))
(define-public (ly:bar-line::print grob)
"The print routine for bar lines."
(let ((glyph-name (ly:grob-property grob 'glyph-name))
(extent (ly:grob-property grob 'bar-extent '(0 . 0))))
(if (and glyph-name
(> (interval-length extent) 0))
(bar-line::compound-bar-line grob glyph-name extent)
#f)))
(define-public (bar-line::compound-bar-line grob bar-glyph extent)
"Build the bar line stencil."
(let* ((line-thickness (layout-line-thickness grob))
(kern (* (ly:grob-property grob 'kern 1) line-thickness))
(bar-glyph-list (string->string-list
(strip-string-annotation bar-glyph)))
(span-glyph (get-span-glyph bar-glyph))
(span-glyph-list (string->string-list span-glyph))
(neg-stencil empty-stencil)
(stencil empty-stencil)
(is-first-neg-stencil #t)
(is-first-stencil #t))
;; We build up two separate stencils first:
;; (1) the neg-stencil is built from all glyphs that have
;; a replacement-char in the span bar
;; (2) the main stencil is built from all remaining glyphs
;;
;; Afterwards the neg-stencil is attached left to the
;; stencil; this ensures that the main stencil starts
;; at x = 0.
;;
;; For both routines holds:
;; we stack the stencils obtained by the corresponding
;; single glyphs with spacing 'kern' except for the
;; first stencil
;; (Thanks to Harm who came up with this idea!)
(for-each (lambda (bar span)
(if (and (string=? span (string replacement-char))
is-first-stencil)
(begin
(set! neg-stencil
(ly:stencil-combine-at-edge
neg-stencil
X
RIGHT
(glyph->stencil bar grob extent)
(if is-first-neg-stencil 0 kern)))
(set! is-first-neg-stencil #f))
(begin
(set! stencil
(ly:stencil-combine-at-edge
stencil
X
RIGHT
(glyph->stencil bar grob extent)
(if is-first-stencil 0 kern)))
(set! is-first-stencil #f))))
bar-glyph-list span-glyph-list)
;; if we have a non-empty neg-stencil,
;; we attach it to the left side of the stencil
(and (not is-first-neg-stencil)
(set! stencil
(ly:stencil-combine-at-edge
stencil
X
LEFT
neg-stencil
kern)))
stencil))
(define-public (ly:bar-line::calc-anchor grob)
"Calculate the anchor position of a bar line. The anchor is used for
the correct placement of bar numbers etc."
(let* ((bar-glyph (ly:grob-property grob 'glyph-name ""))
(bar-glyph-list (string->string-list (strip-string-annotation bar-glyph)))
(span-glyph (assoc-get bar-glyph span-bar-glyph-alist bar-glyph))
(x-extent (ly:grob-extent grob grob X))
(anchor 0.0))
(and (> (interval-length x-extent) 0)
(if (or (= (length bar-glyph-list) 1)
(string=? bar-glyph span-glyph)
(string=? span-glyph ""))
;; We use the x-extent of the stencil if either
;; - we have a single bar-glyph
;; - bar-glyph and span-glyph are identical
;; - we have no span-glyph
(set! anchor (interval-center x-extent))
;; If the conditions above do not hold,the anchor is the
;; center of the corresponding span bar stencil extent
(set! anchor (interval-center
(ly:stencil-extent
(span-bar::compound-bar-line grob bar-glyph dummy-extent)
X)))))
anchor))
(define-public (bar-line::calc-glyph-name grob)
"Determine the @code{glyph-name} of the bar line depending on the
line break status."
(let* ((glyph (ly:grob-property grob 'glyph))
(dir (ly:item-break-dir grob))
(result (assoc-get glyph bar-glyph-alist))
(glyph-name (if (= dir CENTER)
glyph
(if (and result
(string? (index-cell result dir)))
(index-cell result dir)
#f))))
glyph-name))
(define-public (bar-line::calc-break-visibility grob)
"Calculate the visibility of a bar line at line breaks."
(let* ((glyph (ly:grob-property grob 'glyph))
(result (assoc-get glyph bar-glyph-alist)))
(if result
(vector (string? (car result)) #t (string? (cdr result)))
all-invisible)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; span bar callbacks
(define-public (ly:span-bar::calc-glyph-name grob)
"Return the @code{'glyph-name} of the corresponding BarLine grob.
The corresponding SpanBar glyph is computed within
@code{span-bar::compound-bar-line}."
(let* ((elts (ly:grob-object grob 'elements))
(pos (1- (ly:grob-array-length elts)))
(glyph-name '()))
(while (and (eq? glyph-name '())
(> pos -1))
(begin (set! glyph-name
(ly:grob-property (ly:grob-array-ref elts pos)
'glyph-name))
(set! pos (1- pos))))
(if (eq? glyph-name '())
(begin (ly:grob-suicide! grob)
(set! glyph-name "")))
glyph-name))
(define-public (ly:span-bar::width grob)
"Compute the width of the SpanBar stencil."
(let ((width (cons 0 0)))
(if (grob::is-live? grob)
(let* ((glyph-name (ly:grob-property grob 'glyph-name))
(stencil (span-bar::compound-bar-line grob
glyph-name
dummy-extent)))
(set! width (ly:stencil-extent stencil X))))
width))
(define-public (ly:span-bar::before-line-breaking grob)
"A dummy callback that kills the Grob @var{grob} if it contains
no elements."
(let ((elts (ly:grob-object grob 'elements)))
(if (zero? (ly:grob-array-length elts))
(ly:grob-suicide! grob))))
(define-public (span-bar::compound-bar-line grob bar-glyph extent)
"Build the stencil of the span bar."
(let* ((line-thickness (layout-line-thickness grob))
(kern (* (ly:grob-property grob 'kern 1) line-thickness))
(bar-glyph-list (string->string-list
(strip-string-annotation bar-glyph)))
(span-glyph (assoc-get bar-glyph span-bar-glyph-alist 'undefined))
(stencil empty-stencil))
(if (string? span-glyph)
(let ((span-glyph-list (string->string-list span-glyph))
(is-first-stencil #t))
(for-each (lambda (bar span)
;; the stencil stack routine is similar to the one
;; used in bar-line::compound-bar-line, but here,
;; leading replacement-chars are discarded.
(if (not (and (string=? span (string replacement-char))
is-first-stencil))
(begin
(set! stencil
(ly:stencil-combine-at-edge
stencil
X
RIGHT
;; if the current glyph is the replacement-char,
;; we take the corresponding glyph from the
;; bar-glyph-list and insert an empty stencil
;; with the appropriate width.
;; (this method would fail if the bar-glyph-list
;; were shorter than the span-glyph-list,
;; but this makes hardly any sense from a
;; typographical point of view
(if (string=? span (string replacement-char))
((make-spacer-bar-line bar) grob extent)
(glyph->stencil span grob extent))
(if is-first-stencil 0 kern)))
(set! is-first-stencil #f))))
bar-glyph-list span-glyph-list))
;; if span-glyph is not a string, it may be #f or 'undefined;
;; the latter signals that the span bar for the current bar-glyph
;; is undefined, so we raise a warning.
(if (eq? span-glyph 'undefined)
(ly:warning
(_ "No span bar glyph defined for bar glyph '~a'; ignoring.")
bar-glyph)))
stencil))
;; The method used in the following routine depends on bar_engraver
;; not being removed from staff context. If bar_engraver is removed,
;; the size of the staff lines is evaluated as 0, which results in a
;; solid span bar line with faulty y coordinate.
;;
;; This routine was originally by Juergen Reuter, but it was on the
;; bulky side. Rewritten by Han-Wen. Ported from c++ to Scheme by Marc Hohl.
(define-public (ly:span-bar::print grob)
"The print routine for span bars."
(let* ((elts-array (ly:grob-object grob 'elements))
(refp (ly:grob-common-refpoint-of-array grob elts-array Y))
(elts (reverse (sort (ly:grob-array->list elts-array)
ly:grob-vertical<?)))
;; Elements must be ordered according to their y coordinates
;; relative to their common axis group parent.
;; Otherwise, the computation goes mad.
(bar-glyph (ly:grob-property grob 'glyph-name))
(span-bar empty-stencil))
(if (string? bar-glyph)
(let ((extents '())
(make-span-bars '())
(model-bar #f))
;; we compute the extents of each system and store them
;; in a list; dito for the 'allow-span-bar property.
;; model-bar takes the bar grob, if given.
(for-each (lambda (bar)
(let ((ext (bar-line::bar-y-extent bar refp))
(staff-symbol (ly:grob-object bar 'staff-symbol)))
(if (ly:grob? staff-symbol)
(let ((refp-extent (ly:grob-extent staff-symbol refp Y)))
(set! ext (interval-union ext refp-extent))
(if (> (interval-length ext) 0)
(begin
(set! extents (append extents (list ext)))
(set! model-bar bar)
(set! make-span-bars
(append make-span-bars
(list (ly:grob-property
bar
'allow-span-bar
#t))))))))))
elts)
;; if there is no bar grob, we use the callback argument
(if (not model-bar)
(set! model-bar grob))
;; we discard the first entry in make-span-bars,
;; because its corresponding bar line is the
;; uppermost and therefore not connected to
;; another bar line
(if (pair? make-span-bars)
(set! make-span-bars (cdr make-span-bars)))
;; the span bar reaches from the lower end of the upper staff
;; to the upper end of the lower staff - when allow-span-bar is #t
(reduce (lambda (curr prev)
(let ((span-extent (cons 0 0))
(allow-span-bar (car make-span-bars)))
(set! make-span-bars (cdr make-span-bars))
(if (> (interval-length prev) 0)
(begin
(set! span-extent (cons (cdr prev)
(car curr)))
;; draw the span bar only when the staff lines
;; don't overlap and allow-span-bar is #t:
(and (> (interval-length span-extent) 0)
allow-span-bar
(set! span-bar
(ly:stencil-add
span-bar
(span-bar::compound-bar-line
model-bar
bar-glyph
span-extent))))))
curr))
"" extents)
(set! span-bar (ly:stencil-translate-axis
span-bar
(- (ly:grob-relative-coordinate grob refp Y))
Y))))
span-bar))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; volta bracket functions
(define-public (allow-volta-hook bar-glyph)
"Allow the volta bracket hook being drawn over bar line @var{bar-glyph}."
(if (string? bar-glyph)
(set! volta-bracket-allow-volta-hook-list
(append volta-bracket-allow-volta-hook-list
(list bar-glyph)))
(ly:warning (_ ("Volta hook bar line must be a string; ignoring '~a'.")
bar-glyph))))
(define-session volta-bracket-allow-volta-hook-list '())
(define-public (volta-bracket::calc-hook-visibility bar-glyph)
"Determine the visibility of the volta bracket hook. It is called in
@code{lily/volta-bracket.cc} and returns @code{#t} if @emph{no} hook
should be drawn."
(not (member bar-glyph volta-bracket-allow-volta-hook-list)))
(define-public (ly:volta-bracket::calc-shorten-pair grob)
"Calculate the @code{shorten-pair} values for an ideal placement
of the volta brackets relative to the bar lines."
(let* ((line-thickness (layout-line-thickness grob))
(volta-half-line-thickness (* (ly:grob-property grob 'thickness 1.6)
line-thickness
1/2))
(bar-array (ly:grob-object grob 'bars))
;; the bar-array starts with the uppermost bar line grob that is
;; covered by the left edge of the volta bracket; more (span)
;; bar line grobs from other staves may follow
(left-bar-line (and (ly:grob-array? bar-array)
(positive? (ly:grob-array-length bar-array))
(ly:grob-array-ref bar-array 0)))
;; we need the vertical-axis-group-index of the left-bar-line
;; to find the corresponding right-bar-line
(vag-index (and left-bar-line
(ly:grob-get-vertical-axis-group-index left-bar-line)))
;; the bar line corresponding to the right edge of the volta bracket
;; is the last entry with the same vag-index, so we transform the array to a list,
;; reverse it and search for the first suitable entry from
;; the back
(right-bar-line (and left-bar-line
(find (lambda (e)
(eqv? (ly:grob-get-vertical-axis-group-index e)
vag-index))
(reverse (ly:grob-array->list bar-array)))))
;; the left-bar-line may be a #'<Grob Item >,
;; so we add "" as a fallback return value
(left-bar-glyph-name (if left-bar-line
(ly:grob-property left-bar-line 'glyph-name "")
(string annotation-char)))
(right-bar-glyph-name (if right-bar-line
(ly:grob-property right-bar-line 'glyph-name "")
(string annotation-char)))
;; This is the original logic. It flags left-bar-broken if
;; there is no left-bar-line. That seems strange.
(left-bar-broken (not (and left-bar-line
(zero? (ly:item-break-dir left-bar-line)))))
(right-bar-broken (not (and right-bar-line
(zero? (ly:item-break-dir
right-bar-line)))))
;; Revert to current grob for getting layout info if no
;; left-bar-line available
(left-span-stencil-extent (ly:stencil-extent
(span-bar::compound-bar-line
(or left-bar-line grob)
left-bar-glyph-name
dummy-extent)
X))
(right-span-stencil-extent (ly:stencil-extent
(span-bar::compound-bar-line
(or right-bar-line grob)
right-bar-glyph-name
dummy-extent)
X))
(left-shorten 0.0)
(right-shorten 0.0))
;; since "empty" intervals may look like (1.0 . -1.0), we use the
;; min/max functions to make sure that the placement is not corrupted
;; in case of empty bar lines
(set! left-shorten
(if left-bar-broken
(- (max 0 (interval-end left-span-stencil-extent))
(max 0 (interval-end (ly:stencil-extent
(bar-line::compound-bar-line
(or left-bar-line grob)
left-bar-glyph-name
dummy-extent)
X)))
volta-half-line-thickness)
(- (max 0 (interval-end left-span-stencil-extent))
volta-half-line-thickness)))
(set! right-shorten
(if right-bar-broken
(+ (- (max 0 (interval-end right-span-stencil-extent)))
volta-half-line-thickness)
(- (min 0 (interval-start right-span-stencil-extent))
volta-half-line-thickness)))
(cons left-shorten right-shorten)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; predefined bar glyph print procedures
(add-bar-glyph-print-procedure "" make-empty-bar-line)
(add-bar-glyph-print-procedure "|" make-simple-bar-line)
(add-bar-glyph-print-procedure "." make-thick-bar-line)
(add-bar-glyph-print-procedure "!" make-dashed-bar-line)
(add-bar-glyph-print-procedure "'" make-tick-bar-line)
(add-bar-glyph-print-procedure ":" make-colon-bar-line)
(add-bar-glyph-print-procedure ";" make-dotted-bar-line)
(add-bar-glyph-print-procedure "k" make-kievan-bar-line)
(add-bar-glyph-print-procedure "S" (make-segno-bar-line #t))
(add-bar-glyph-print-procedure "=" (make-segno-bar-line #f))
(add-bar-glyph-print-procedure "[" (make-bracket-bar-line LEFT))
(add-bar-glyph-print-procedure "]" (make-bracket-bar-line RIGHT))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; predefined bar lines
;;
;; definition of bar lines goes as follows:
;;
;; (define-bar-line "normal bar[-annotation]" "end of line" "start of line" "span bar")
;;
;; each entry has to be a string or #f.
;; The empty string "" is allowed and yields in an invisible bar line,
;; whereas #f reads 'no stencil'.
;;
;; Convention: if two bar lines would be identical in their
;; unbroken bar glyph, we use annotations to make them distinct;
;; as a general rule of thumb the main difference in their
;; behavior at the end of a line is used as annotation, cf.
;;
;; (define-bar-line ".|:" "|" ".|:" ".|")
;; (define-bar-line ".|:-||" "||" ".|:" ".|")
;;
;; or
;;
;; (define-bar-line "S-|" "|" "S" "=")
;; (define-bar-line "S-S" "S" "" "=")
;; common bar lines
(define-bar-line "" "" "" #f)
(define-bar-line "-" #f #f #f)
(define-bar-line "|" "|" #f "|")
(define-bar-line "|-s" #f "|" "|")
(define-bar-line "." "." #f ".")
(define-bar-line ".|" "|" ".|" ".|")
(define-bar-line "|." "|." #f "|.")
(define-bar-line "||" "||" #f "||")
(define-bar-line ".." ".." #f "..")
(define-bar-line "|.|" "|.|" #f "|.|")
(define-bar-line "!" "!" #f "!")
(define-bar-line ";" ";" #f ";")
(define-bar-line "'" "'" #f #f)
;; repeats
(define-bar-line ":|.:" ":|." ".|:" " |.")
(define-bar-line ":..:" ":|." ".|:" " ..")
(define-bar-line ":|.|:" ":|." ".|:" " |.|")
(define-bar-line ":.|.:" ":|." ".|:" " .|.")
(define-bar-line ":|." ":|." #f " |.")
(define-bar-line ".|:" "|" ".|:" ".|")
(define-bar-line "[|:" "|" "[|:" " |")
(define-bar-line ":|]" ":|]" #f " | ")
(define-bar-line ":|][|:" ":|]" "[|:" " | |")
(define-bar-line ".|:-||" "||" ".|:" ".|")
;; segno bar lines
(define-bar-line "S" "||" "S" "=")
(define-bar-line "S-|" "|" "S" "=")
(define-bar-line "S-S" "S" #f "=")
(define-bar-line ":|.S" ":|." "S" " |.")
(define-bar-line ":|.S-S" ":|.S" "" " |.")
(define-bar-line "S.|:" "|" "S.|:" " .|")
(define-bar-line "S.|:-S" "S" ".|:" " .|")
(define-bar-line ":|.S.|:" ":|." "S.|:" " |. .|")
(define-bar-line ":|.S.|:-S" ":|.S" ".|:" " |. .|")
;; ancient bar lines
(define-bar-line "k" "k" #f #f) ;; kievan style
;; volta hook settings
(allow-volta-hook ":|.")
(allow-volta-hook ".|:")
(allow-volta-hook "|.")
(allow-volta-hook ":..:")
(allow-volta-hook ":|.|:")
(allow-volta-hook ":|.:")
(allow-volta-hook ".|")
(allow-volta-hook ":|.S")
(allow-volta-hook ":|.S-S")
(allow-volta-hook ":|.S.|:")
(allow-volta-hook ":|.S.|:-S")
(allow-volta-hook ":|]")
(allow-volta-hook ":|][|:")