/
bar-line.scm
1346 lines (1222 loc) · 58.9 KB
/
bar-line.scm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
;;;; This file is part of LilyPond, the GNU music typesetter.
;;;;
;;;; Copyright (C) 2009--2023 Marc Hohl <marc@hohlart.de>
;;;;
;;;; 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/>.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; helper functions for staff and layout properties
(define (bar-line::calc-blot thickness extent grob)
"Calculate the blot diameter for a rounded box, taking the extent
into account."
(let ((blot-diameter (layout-blot-diameter grob))
(height (interval-length extent)))
(cond ((< thickness blot-diameter) thickness)
((< height blot-diameter) height)
(else blot-diameter))))
(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}, and @var{thickness}. The blot is
calculated from @var{extent} and @var{grob}. @var{y-ext} is not
necessarily equal to @var{extent}."
(let* ((rounded (ly:grob-property grob 'rounded #f))
(blot (if rounded (bar-line::calc-blot thickness extent grob) 0)))
(ly:round-filled-box x-ext y-ext blot)))
(define (get-span-glyph bar-glyph)
"Get the corresponding span glyph from the @code{span-glyph-bar-alist}.
Return the found span glyph unchanged if it is not of type @code{string?},
otherwise 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)
(string-pad-right
span-glyph
(string-length bar-glyph)
replacement-char)
span-glyph)))
(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}."
(if (ly:grob? staff)
(let ((line-pos (ly:grob-property staff 'line-positions '())))
(length line-pos))
0))
(define (staff-symbol-line-span grob)
(let ((line-positions (ly:grob-property grob 'line-positions)))
(if (pair? line-positions)
(fold
(lambda (p iv)
(add-point iv p))
empty-interval
line-positions)
;; See similar code in staff-symbol.cc.
'(1.0 . -1.0))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; internal helper functions
(define annotation-char #\-)
(define replacement-char #\ )
(define dummy-extent (cons -1 1))
(define (glyph->stencil glyph is-span 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 is-span grob extent))
(ly:warning (G_ "Bar glyph ~a not known. Ignoring.") glyph))
stencil))
(define-public (string->string-list str)
"Convert string @var{str} into a list of strings with length@tie{}1.
@code{\"aBc\"} will be converted to @code{(\"a\" \"B\" \"c\")}.
For an empty string or if @var{str} is not of type @code{string?}, return a
list containing @code{\"\"}."
(if (and (string? str)
(not (string-null? str)))
(map string (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
(G_ "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
(G_ "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 substitutes at the end of
a line (@var{eol-glyph}), at the beginning of a line (@var{bol-glyph})
and as a span bar (@var{span-glyph}). The substitute glyphs may be
either strings or booleans: @code{#t} calls for the same value as
@var{bar-glyph} and @code{#f} calls for no glyph."
;; #t means copy the mid-line glyph
(when (eq? eol-glyph #t)
(set! eol-glyph bar-glyph))
(when (eq? bol-glyph #t)
(set! bol-glyph bar-glyph))
(when (eq? span-glyph #t)
(set! span-glyph bar-glyph))
;; The last argument should not include annotations.
;; But do not check this if identical to first argument,
;; (as when called with #t) to avoid a useless warning
;; while keeping the meaning of #t consistent in the interface.
(when (not (equal? span-glyph bar-glyph))
(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-public bar-glyph-alist '())
(define-session-public 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
(G_ "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-no-bar-line is-span grob extent)
"Return an empty stencil."
empty-stencil)
(define (make-empty-bar-line is-span grob extent)
"Draw an empty bar line."
(ly:make-stencil "" (cons 0 0) extent))
(define (make-simple-bar-line is-span 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 is-span 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-short-bar-line is-span grob extent)
"Draw a short bar line."
(let* ((line-thickness (layout-line-thickness grob))
(thickness (* (ly:grob-property grob 'hair-thickness 1)
line-thickness))
(short-extent (ly:grob-property grob 'short-bar-extent extent)))
;; Bypass bar-line::draw-filled-box to ignore the 'roundness property.
;; Full-height bar lines are squared off to meet the edge of the outer staff
;; lines, but that is not a concern for shorter bar lines.
(ly:round-filled-box
(cons 0 thickness) ; x
short-extent ; y
(bar-line::calc-blot thickness short-extent grob))))
(define (make-tick-bar-line is-span grob extent)
"Draw a tick bar line."
(let* ((line-thickness (layout-line-thickness grob))
(thickness (* (ly:grob-property grob 'hair-thickness 1)
line-thickness))
(staff-symbol (ly:grob-object grob 'staff-symbol))
(line-pos (ly:grob-property staff-symbol 'line-positions))
(line-count (length line-pos))
(half-staff (* 1/2 (ly:staff-symbol-staff-space grob)))
(center (interval-end extent)))
;; The provided extent is for normal bar lines, which do not
;; necessarily end at the top staff line: they are expected to be
;; extended if the staff is vertically short, and it is always
;; possible for BarLine.bar-extent to be overridden. The tick is
;; expected to cross the top staff line, so we must find it. If
;; there are fewer than two lines, we allow floating ticks so that
;; they are not confused with short bar lines.
(if (>= line-count 2)
(set! center (* (apply max line-pos) half-staff)))
;; Bypass bar-line::draw-filled-box to ignore the 'roundness property.
;; Full-height bar lines are squared off to meet the edge of the outer staff
;; lines, but that is not a concern for ticks.
(ly:round-filled-box
(cons 0 thickness) ; x
(coord-translate (symmetric-interval half-staff) center) ; y
(bar-line::calc-blot thickness extent grob))))
(define (make-colon-bar-line is-span 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 (ly:grob-object grob 'staff-symbol)))
(if (ly:grob? staff-symbol)
(let ((line-pos (ly:grob-property staff-symbol 'line-positions)))
(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)))
;; A dotted line obviously contains a lined up amount of dots with space
;; inbetween, and probably before and behind.
;; Caused by different sized staves and user settings like \magnifyStaff,
;; layout-set-staff-size etc., the space between dots and the even the size of
;; the dots itself may differ, making a dotted bar line a beasty thing...
(define (make-dotted-bar-line is-span grob extent)
"Draw a dotted bar line."
(let* ((staff-space (ly:staff-symbol-staff-space grob))
(top-pos (round (* (/ (interval-end extent) staff-space) 2)))
(bottom-pos (round (* (/ (interval-start extent) staff-space) 2)))
(dots-pos '())
(dot (ly:font-get-glyph (ly:grob-default-font grob) "dots.dot")))
(define (calc-dot-positions dot-position-interval)
(if (not (interval-empty? dot-position-interval))
(iota (1+ (/ (interval-length dot-position-interval) 2))
(interval-start dot-position-interval)
2)
'()))
(define (add! init ls rl)
;; Returns the list @code{rl} where the elements from @code{ls} are added to
;; the previous one, starting at @code{(+ init (car ls))}. Example:
;; (add! 1.23 '(1 2 3) '())
;; --> '(2.23 4.23 7.23)
(if (null? ls)
(reverse rl)
(let ((new-init ((if (negative? init) - +) init (car ls))))
(add! new-init (cdr ls) (cons new-init rl)))))
;; A staff group may contain staves with different staff space, like
;; TabStaff and Staff. If the SpanBar would use the staff space from above,
;; i.e. from the topmost staff, all other span bars would use these staff
;; space, even if not appropriate.
;; Instead we use the staff space from layout. Covers the majority of cases.
(when is-span
(let* ((height (interval-length extent))
(grob-layout (ly:grob-layout grob))
(layout-staff-space (ly:output-def-lookup grob-layout 'staff-space))
(dot-count (round (/ height layout-staff-space)))
(space (/ height dot-count))
;; Distribute dots evenly
;;
;; - For no dots we return an empty list.
;; - For one dot we want the same space from upper staff to dot
;; and from the dot to lower staff, i.e (/ space 2).
;; - For multiple dots we want said (/ space 2) from upper staff to
;; the topmost dot and from bottommost dot to lower staff.
;; Between the dots we go for `space'.
;;
;; Actually it is unneeded to add (/ space 2) at the end of `ls'
;; below (we also drop the result later), it makes understanding
;; easier, though.
;; -Harm
(ls
(if (zero? dot-count)
'()
(cons
(/ space 2)
(append
(make-list
(inexact->exact (1- dot-count))
space)
(list (/ space 2)))))))
(set! dots-pos
(if (pair? ls)
(map
(lambda (x) (* (/ x staff-space) 2))
(drop-right (add! (interval-end extent) ls '()) 1))
'()))))
(when (not is-span)
;; Narrow the extent by half a dot on each end; if the
;; center of a dot is within the reduced interval, then the
;; whole dot is within the original interval.
;; If `layout-set-staff-size` is used, the thickness of staff lines change,
;; take it into account. `blot-diamter` as well
(let* ((extent-less-dot
(interval-widen
extent
(/ (interval-length (ly:stencil-extent dot Y)) -2)))
(staff-line-thick (ly:staff-symbol-line-thickness grob))
(bar-thick (ly:grob-property grob 'thick-thickness))
(blot (bar-line::calc-blot bar-thick extent grob)))
;; If a dot is out of bounds due to rounding, bring it in.
(when (> (+ top-pos staff-line-thick blot)
(/ (interval-end extent-less-dot) staff-space))
(set! top-pos (1- top-pos)))
(when (< (- bottom-pos staff-line-thick blot)
(/ (interval-start extent-less-dot) staff-space))
(set! bottom-pos (1+ bottom-pos))))
;; The dots will be separated by one staff space center to
;; center, so they will be placed all in even or all in odd
;; positions. Choose the alternative with more dots not
;; colliding with staff lines.
(let* ((staff-symbol (ly:grob-object grob 'staff-symbol))
(lines-pos (if (ly:grob? staff-symbol)
(ly:grob-property staff-symbol 'line-positions)
'()))
(even-interval (cons
(if (even? bottom-pos)
bottom-pos
(1+ bottom-pos))
(if (even? top-pos)
top-pos
(1- top-pos))))
(even-dots-pos (calc-dot-positions even-interval))
(even-score (length (lset-difference
= even-dots-pos lines-pos)))
(odd-interval (cons
(if (odd? bottom-pos)
bottom-pos
(1+ bottom-pos))
(if (odd? top-pos)
top-pos
(1- top-pos))))
(odd-dots-pos (calc-dot-positions odd-interval))
(odd-score (length (lset-difference
= odd-dots-pos lines-pos))))
;; choose the even or the odd configuration
(if (= even-score odd-score)
;; break ties by choosing more dots
(if (> (length odd-dots-pos) (length even-dots-pos))
(set! dots-pos odd-dots-pos)
(set! dots-pos even-dots-pos))
;; usually, take the config with more dots in spaces
(if (> odd-score even-score)
(set! dots-pos odd-dots-pos)
(set! dots-pos even-dots-pos)))))
(apply
ly:stencil-add
empty-stencil
(map
(lambda (pos)
(ly:stencil-translate-axis dot (/ (* staff-space pos) 2) Y))
dots-pos))))
(define (make-dashed-bar-line is-span grob extent)
"Draw a dashed bar line."
(let* ((height (interval-length extent))
(staff-symbol (ly:grob-object grob 'staff-symbol grob))
(staff-space (ly:staff-symbol-staff-space grob))
(grob-layout (ly:grob-layout grob))
(layout-staff-space (ly:output-def-lookup grob-layout 'staff-space))
(half-space (/ (if is-span
layout-staff-space
staff-space)
2.0))
(blot (layout-blot-diameter grob))
(line-thickness (layout-line-thickness grob))
(thickness (* (ly:grob-property grob 'hair-thickness 1)
line-thickness))
(half-thick (/ line-thickness 2.0))
(dash-size (- 1.0 (ly:grob-property grob 'gap 0.3)))
(amount-of-dashes (/ height
(if is-span
layout-staff-space
staff-space)))
(rounded-amount (round amount-of-dashes))
(stencil empty-stencil))
(for-each (lambda (i)
(let ((top-y (min (* (+ i dash-size) half-space)
(+ (* amount-of-dashes half-space)
half-thick)))
(bot-y (max (* (- i dash-size) half-space)
(- 0 (* amount-of-dashes half-space)
half-thick))))
(set! stencil
(ly:stencil-add
stencil
(ly:round-filled-box (cons 0 thickness)
(cons bot-y top-y)
blot)))))
(iota (1+ rounded-amount) rounded-amount (- 2)))
(ly:stencil-translate-axis
stencil
(interval-center extent)
Y)))
(define ((make-segno-bar-line show-segno) is-span 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 is-span 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 is-span 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) is-span 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 is-span 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) is-span 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 is-span 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 (ly:grob-object grob 'staff-symbol)))
(if (ly:grob? staff-symbol)
(let ((staff-extent (ly:grob-property staff-symbol 'widened-extent))
(bar-line-color (ly:grob-property grob 'color))
(staff-color (ly:grob-property staff-symbol 'color)))
;; FIXME: "red" not eq? to #(rgb-color 1 0 0)
(if (eq? bar-line-color staff-color)
;; 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.
(let ((half-staff-line-thickness
(* 1/2 (ly:staff-symbol-line-thickness grob))))
(interval-widen staff-extent (- half-staff-line-thickness)))
staff-extent))
'(0 . 0))))
(define-public (ly:bar-line::calc-short-bar-extent grob)
(let ((staff-symbol (ly:grob-object grob 'staff-symbol)))
(if (ly:grob? staff-symbol)
(let* ((staff-extent (ly:grob-property staff-symbol 'widened-extent))
(normal-height (interval-length staff-extent))
(staff-space (ly:staff-symbol-staff-space grob))
(normal-height-spaces (/ normal-height staff-space))
;; Use half the height of the staff, rounded up to an integer
;; number of staff spaces.
(short-height-spaces (truncate (/ (1+ normal-height-spaces) 2)))
(center (interval-center staff-extent))
(line-thickness (layout-line-thickness grob))
(thickness (* (ly:grob-property grob 'hair-thickness 1)
line-thickness)))
;; If the normal bar lines are quite short, short bar lines will be
;; hard to distinguish or hard to see. Render them like anti-ticks.
(if (< normal-height-spaces 2)
(begin
(set! short-height-spaces 1)
(set! center (interval-start staff-extent))))
(coord-translate
(symmetric-interval (/ (* short-height-spaces staff-space) 2))
center))
'(0 . 0))))
;; 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)
"Widen the bar line @var{extent} towards span bars adjacent to grob @var{grob}."
(let ((staff-symbol (ly:grob-object grob 'staff-symbol))
(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
(cond ((string? span-glyph)
(string->string-list span-glyph))
((boolean? span-glyph)
(make-list (length bar-glyph-list) span-glyph))
(else
(ly:warning
"Setting for span bar needs to be a boolean or string: ~a. Using #f instead."
span-glyph)
(make-list (length bar-glyph-list) #f))))
(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 span
(string=? span (string replacement-char))
is-first-stencil)
(begin
(set! neg-stencil
(ly:stencil-combine-at-edge
neg-stencil
X
RIGHT
(glyph->stencil bar #f 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 #f 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)))
(if (and (ly:grob-property grob 'right-justified #f)
(< (ly:item-break-dir grob) 1))
(ly:stencil-translate-axis
stencil
(- (cdr (ly:stencil-extent stencil X)))
X)
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)
;; 'span-glyph' may be #f, thus use equal? and not string=? for
;; comparing equality
(equal? bar-glyph span-glyph)
(equal? 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
;; if a mid-line bar-line is right-justified, the anchor
;; needs to be adjusted
(if (and (ly:grob-property grob 'right-justified #f)
(= (ly:item-break-dir grob) 0))
(- (interval-end x-extent))
(interval-center x-extent)))
;; If the conditions above do not hold,the anchor is the
;; center of the corresponding span bar stencil extent
(let* ((span-bar-stencil
(span-bar::compound-bar-line grob bar-glyph dummy-extent))
(span-stil-x-extent (ly:stencil-extent span-bar-stencil X)))
(set! anchor
;; if a mid-line bar-line is right-justified, the anchor
;; needs to be adjusted
(if (and (ly:grob-property grob 'right-justified #f)
(= (ly:item-break-dir grob) 0))
(interval-end span-stil-x-extent)
(interval-center span-stil-x-extent))))))
anchor))
(define-public (bar-line::calc-glyph-name-for-direction glyphs dir)
"Find the glyph name for a bar line. @code{glyphs} is the list of
bar-line types to consider in order. Each must have been defined with
@code{define-bar-line}. @var{dir} is the break direction to consider:
@code{LEFT} = end of line, @code{CENTER} = middle of line,
@code{RIGHT} = start of line."
(define (get-glyph-name glyph)
"Given a particular @code{glyph} value, get the entry for the relevant
direction from the alist."
(let ((alist-entry (assoc-get glyph bar-glyph-alist)))
(and alist-entry
(begin
(when (!= dir CENTER)
(set! glyph (index-cell alist-entry dir)))
;; We want to ignore glyphs that do not yield a stencil.
;; Checking the result of glyph->stencil would probably
;; be ideal, but the difficulty is that glyph->stencil
;; requires a grob, which does not exist yet. Instead,
;; we look for glyph "x" as a special case.
(and (string? glyph)
(not (equal? "x" (strip-string-annotation glyph)))
glyph)))))
(define (find-first-glyph-name glyphs)
(and (pair? glyphs)
(string? (car glyphs)) ; stop at #f for \noBar
(or (get-glyph-name (car glyphs))
(find-first-glyph-name (cdr glyphs)))))
;; If we return #f regardless of direction, Bar_engraver does not
;; create a BarLine. If we don't have at least one string in the
;; glyph list, we don't want to create a BarLine.
(and (pair? glyphs)
(string? (car glyphs)) ; stop at #f for \noBar
(or (find-first-glyph-name glyphs)
;; Bar line definitions can have "x" and #f in them to
;; allow lower layers to show through, but weird alignment
;; and spacing issues can occur when we create BarLine
;; grobs with no mid-line or end-of-line stencils, so we
;; default to "" in these cases. We could investigate
;; whether we can eliminate these special cases. (It isn't
;; clear that there are better alternatives.)
(if (= dir RIGHT)
#f
""))))
(define-public (bar-line::calc-glyph-name grob)
"Return the name of the bar line glyph printed by @var{grob} for the
actual break direction."
(let ((dir (ly:item-break-dir grob)))
(cond
((= dir LEFT) (ly:grob-property grob 'glyph-left))
((= dir RIGHT) (ly:grob-property grob 'glyph-right))
(else (ly:grob-property grob 'glyph)))))
(define-public (bar-line::calc-break-visibility grob)
"Calculate the visibility of a bar line at line breaks."
(vector
(string? (ly:grob-property grob 'glyph-left))
(string? (ly:grob-property grob 'glyph))
(string? (ly:grob-property grob 'glyph-right))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; span bar callbacks
(define-public (ly:span-bar::calc-anchor grob)
"Calculate the anchor position of the @code{SpanBar}. The anchor is
used for the correct placement of bar numbers, etc."
(interval-center (ly:span-bar::width grob)))
(define-public (ly:span-bar::calc-glyph-name grob)
"Return the @code{'glyph-name} of the corresponding @code{BarLine} grob.
The corresponding @code{SpanBar} glyph is computed within
@code{span-bar::@/compound-bar-line}."
(let ((model-bar (ly:span-bar::choose-model-bar-line grob)))
(if (grob::is-live? grob)
(ly:grob-property model-bar 'glyph-name)
;; avoid recursion: model-bar is a dead span bar
'())))
(define (ly:span-bar::choose-model-bar-line grob)
"Choose one of the bar lines to use as a template for the span bar.
Ideally, this chooses the same bar line whether it is called from
ly:span-bar::width, ly:span-bar::print, etc.
N.B. If no model bar line is found, this function kills the span bar
and returns the dead span bar."
(define (bar-acceptable? bar)
"return bar if it is acceptable, otherwise return #f"
(let ((staff-symbol (ly:grob-object bar 'staff-symbol #f)))
(and staff-symbol
(let* ((refp (ly:grob-common-refpoint bar staff-symbol Y))
(ext (interval-union
(bar-line::bar-y-extent bar refp)
(ly:grob-extent staff-symbol refp Y))))
(and (positive? (interval-length ext)) bar)))))
(let ((model-bar
(find bar-acceptable?
(ly:grob-array->list (ly:grob-object grob 'elements)))))
(if model-bar
model-bar
(begin
(ly:grob-suicide! grob)
grob))))
(define-public (ly:span-bar::width grob)
"Compute the width of the @code{SpanBar} stencil."
;; We don't use the grob's actual stencil because it would trigger
;; Y-axis calculations.
(let ((model-bar (ly:span-bar::choose-model-bar-line grob))
;; note: choose-model-bar-line might have killed the span bar
(glyph-name (ly:grob-property grob 'glyph-name)))
(if (string? glyph-name)
(let ((stencil (span-bar::compound-bar-line
model-bar
glyph-name
dummy-extent)))
(ly:stencil-extent stencil X))
empty-interval)))
(define-public (ly:span-bar::before-line-breaking grob)
"A dummy callback that kills the @code{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) #t grob extent)
(glyph->stencil span #t 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