1 ;;;; This file is part of LilyPond, the GNU music typesetter.
3 ;;;; Copyright (C) 2003--2012 Han-Wen Nienhuys <hanwen@xs4all.nl>
5 ;;;; LilyPond is free software: you can redistribute it and/or modify
6 ;;;; it under the terms of the GNU General Public License as published by
7 ;;;; the Free Software Foundation, either version 3 of the License, or
8 ;;;; (at your option) any later version.
10 ;;;; LilyPond is distributed in the hope that it will be useful,
11 ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
12 ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 ;;;; GNU General Public License for more details.
15 ;;;; You should have received a copy of the GNU General Public License
16 ;;;; along with LilyPond. If not, see <http://www.gnu.org/licenses/>.
18 (define (make-bezier-sandwich-stencil coords thick xext yext)
19 (let* ((command-list `(moveto
20 ,(car (list-ref coords 3))
21 ,(cdr (list-ref coords 3))
23 ,(car (list-ref coords 0))
24 ,(cdr (list-ref coords 0))
25 ,(car (list-ref coords 1))
26 ,(cdr (list-ref coords 1))
27 ,(car (list-ref coords 2))
28 ,(cdr (list-ref coords 2))
30 ,(car (list-ref coords 4))
31 ,(cdr (list-ref coords 4))
32 ,(car (list-ref coords 5))
33 ,(cdr (list-ref coords 5))
34 ,(car (list-ref coords 6))
35 ,(cdr (list-ref coords 6))
38 `(path ,thick `(,@' ,command-list) 'round 'round #t)
42 (define-public (stack-stencils axis dir padding stils)
43 "Stack stencils @var{stils} in direction @var{axis}, @var{dir}, using
46 ((null? stils) empty-stencil)
47 ((null? (cdr stils)) (car stils))
48 (else (ly:stencil-combine-at-edge
49 (car stils) axis dir (stack-stencils axis dir padding (cdr stils))
52 (define-public (stack-stencils-padding-list axis dir padding stils)
53 "Stack stencils @var{stils} in direction @var{axis}, @var{dir}, using
54 a list of @var{padding}."
56 ((null? stils) empty-stencil)
57 ((null? (cdr stils)) (car stils))
58 (else (ly:stencil-combine-at-edge
61 (stack-stencils-padding-list axis dir (cdr padding) (cdr stils))
64 (define-public (centered-stencil stencil)
65 "Center stencil @var{stencil} in both the X and Y directions."
66 (ly:stencil-aligned-to (ly:stencil-aligned-to stencil X CENTER) Y CENTER))
68 (define-public (stack-lines dir padding baseline stils)
69 "Stack vertically with a baseline skip."
70 (define result empty-stencil)
73 ((last-stencil #f (car p))
80 (let* ((dy (max (+ (* dir (interval-bound (ly:stencil-extent last-stencil Y) dir))
82 (* (- dir) (interval-bound (ly:stencil-extent (car p) Y) (- dir))))
84 (y (+ last-y (* dir dy))))
89 (ly:stencil-add result (ly:stencil-translate-axis (car p) y Y)))
93 (set! result (car p)))))
98 (define-public (bracketify-stencil stil axis thick protrusion padding)
99 "Add brackets around @var{stil}, producing a new stencil."
101 (let* ((ext (ly:stencil-extent stil axis))
102 (lb (ly:bracket axis ext thick protrusion))
103 (rb (ly:bracket axis ext thick (- protrusion))))
105 (ly:stencil-combine-at-edge stil (other-axis axis) 1 rb padding))
107 (ly:stencil-combine-at-edge lb (other-axis axis) 1 stil padding))
110 (define (make-parenthesis-stencil
111 y-extent half-thickness width angularity)
112 "Create a parenthesis stencil.
113 @var{y-extent} is the Y extent of the markup inside the parenthesis.
114 @var{half-thickness} is the half thickness of the parenthesis.
115 @var{width} is the width of a parenthesis.
116 The higher the value of number @var{angularity},
117 the more angular the shape of the parenthesis."
118 (let* ((line-width 0.1)
119 ;; Horizontal position of baseline that end points run through.
124 ;; X value farthest from baseline on outside of curve
125 (outer-x (+ base-x width))
126 ;; X extent of bezier sandwich centerline curves
127 (x-extent (ordered-cons base-x outer-x))
128 (bottom-y (interval-start y-extent))
129 (top-y (interval-end y-extent))
131 (lower-end-point (cons base-x bottom-y))
132 (upper-end-point (cons base-x top-y))
134 (outer-control-x (+ base-x (* 4/3 width)))
135 (inner-control-x (+ outer-control-x
138 (- half-thickness))))
140 ;; Vertical distance between a control point
141 ;; and the end point it connects to.
142 (offset-index (- (* 0.6 angularity) 0.8))
143 (lower-control-y (interval-index y-extent offset-index))
144 (upper-control-y (interval-index y-extent (- offset-index)))
146 (lower-outer-control-point
147 (cons outer-control-x lower-control-y))
148 (upper-outer-control-point
149 (cons outer-control-x upper-control-y))
150 (upper-inner-control-point
151 (cons inner-control-x upper-control-y))
152 (lower-inner-control-point
153 (cons inner-control-x lower-control-y)))
155 (make-bezier-sandwich-stencil
157 ;; Step 4: curve through inner control points
158 ;; to lower end point.
159 upper-inner-control-point
160 lower-inner-control-point
162 ;; Step 3: move to upper end point.
164 ;; Step 2: curve through outer control points
165 ;; to upper end point.
166 lower-outer-control-point
167 upper-outer-control-point
169 ;; Step 1: move to lower end point.
172 (interval-widen x-extent (/ line-width 2))
173 (interval-widen y-extent (/ line-width 2)))))
175 (define-public (parenthesize-stencil
176 stencil half-thickness width angularity padding)
177 "Add parentheses around @var{stencil}, returning a new stencil."
178 (let* ((y-extent (ly:stencil-extent stencil Y))
179 (lp (make-parenthesis-stencil
180 y-extent half-thickness (- width) angularity))
181 (rp (make-parenthesis-stencil
182 y-extent half-thickness width angularity)))
183 (set! stencil (ly:stencil-combine-at-edge lp X RIGHT stencil padding))
184 (set! stencil (ly:stencil-combine-at-edge stencil X RIGHT rp padding))
187 (define-public (make-line-stencil width startx starty endx endy)
188 "Make a line stencil of given linewidth and set its extents accordingly."
189 (let ((xext (cons (min startx endx) (max startx endx)))
190 (yext (cons (min starty endy) (max starty endy))))
192 (list 'draw-line width startx starty endx endy)
193 ; Since the line has rounded edges, we have to / can safely add half the
194 ; width to all coordinates!
195 (interval-widen xext (/ width 2))
196 (interval-widen yext (/ width 2)))))
199 (define-public (make-filled-box-stencil xext yext)
203 (list 'round-filled-box (- (car xext)) (cdr xext)
204 (- (car yext)) (cdr yext) 0.0)
207 (define-public (make-circle-stencil radius thickness fill)
208 "Make a circle of radius @var{radius} and thickness @var{thickness}."
210 ((out-radius (+ radius (/ thickness 2.0))))
213 (list 'circle radius thickness fill)
214 (cons (- out-radius) out-radius)
215 (cons (- out-radius) out-radius))))
217 (define-public (make-oval-stencil x-radius y-radius thickness fill)
218 "Make an oval from two Bezier curves, of x@tie{}radius @var{x-radius},
219 y@tie{}radius @code{y-radius}, and thickness @var{thickness} with fill
220 defined by @code{fill}."
222 ((x-out-radius (+ x-radius (/ thickness 2.0)))
223 (y-out-radius (+ y-radius (/ thickness 2.0)))
228 (commands `(,(list 'moveto x-max 0)
229 ,(list 'curveto x-max y-max x-min y-max x-min 0)
230 ,(list 'curveto x-min y-min x-max y-min x-max 0)
232 (command-list (fold-right append '() commands)))
234 `(path ,thickness `(,@',command-list) 'round 'round ,fill)
235 (cons (- x-out-radius) x-out-radius)
236 (cons (- y-out-radius) y-out-radius))))
239 (make-partial-ellipse-stencil
240 x-radius y-radius start-angle end-angle thick connect fill)
241 "Create an elliptical arc
242 @var{x-radius} is the X radius of the arc.
243 @var{y-radius} is the Y radius of the arc.
244 @var{start-angle} is the starting angle of the arc in degrees.
245 @var{end-angle} is the ending angle of the arc in degrees.
246 @var{thick} is the thickness of the line.
247 @var{connect} is a boolean flag indicating if the end should
248 be connected to the start by a line.
249 @var{fill} is a boolean flag indicating if the shape should be filled."
250 (define (make-radius-list x-radius y-radius)
251 "Makes a list of angle/radius pairs at intervals of PI/2 for
252 the partial ellipse until 7*PI/2. For example, in pseudo-code:
253 > (make-radius-list 2 3)
254 ((0.0 . 2) (PI/2 . 3) (PI . -2) (3*PI/2 . -3)
255 (2*PI . 2) (5*PI/2 . 3) (3*PI . -2) (7*PI/2 . -3))
259 (map (lambda (quadrant)
260 (cons (+ adder (car quadrant))
262 `((0.0 . (,x-radius . 0.0))
263 (,PI-OVER-TWO . (0.0 . ,y-radius))
264 (,PI . (,(- x-radius) . 0.0))
265 (,THREE-PI-OVER-TWO . (0.0 . ,(- y-radius))))))
269 (insert-in-ordered-list ordering-function value inlist cutl? cutr?)
270 "Insert @var{value} in ordered list @var{inlist}. If @var{cutl?}, we
271 cut away any parts of @var{inlist} before @var{value}. @var{cutr?} works
272 the same way but for the right side. For example:
273 > (insert-in-ordered-list < 4 '(1 2 3 6 7) #f #f)
275 > (insert-in-ordered-list < 4 '(1 2 3 6 7) #t #f)
277 > (insert-in-ordered-list < 4 '(1 2 3 6 7) #f #t)
281 (helper ordering-function value left-list right-list cutl? cutr?)
282 (if (null? right-list)
284 (if cutl? '() left-list)
286 (if cutr? '() right-list))
287 (if (ordering-function value (car right-list))
289 (if cutl? '() left-list)
291 (if cutr? '() right-list))
295 (append left-list (list (car right-list)))
299 (helper ordering-function value '() inlist cutl? cutr?))
301 (define (ordering-function-1 a b) (car< a b))
303 (define (ordering-function-2 a b) (car<= a b))
305 (define (min-max-crawler min-max side l)
306 "Apply function @var{side} to each member of list and
307 then reduce using @var{min-max}:
308 > (min-max-crawler min car '((0 . 3) (-1 . 4) (1 . 2)))
310 > (min-max-crawler min cdr '((0 . 3) (-1 . 4) (1 . 2)))
314 (if (eq? min-max min) 100000 -100000)
315 (map (lambda (x) (side x)) l)))
318 (;; the outside limit of the x-radius
319 (x-out-radius (+ x-radius (/ thick 2.0)))
320 ;; the outside limit of the y-radius
321 (y-out-radius (+ y-radius (/ thick 2.0)))
322 ;; end angle to radians
323 (new-end-angle (angle-0-2pi (degrees->radians end-angle)))
324 ;; length of the radius at the end angle
325 (end-radius (ellipse-radius x-out-radius y-out-radius new-end-angle))
326 ;; start angle to radians
327 (new-start-angle (angle-0-2pi (degrees->radians start-angle)))
328 ;; length of the radius at the start angle
329 (start-radius (ellipse-radius x-out-radius y-out-radius new-start-angle))
330 ;; points that the arc passes through at 90 degree intervals
331 (radius-list (make-radius-list x-out-radius y-out-radius))
332 ;; rectangular coordinates of arc endpoint
333 (rectangular-end-radius (polar->rectangular end-radius end-angle))
334 ;; rectangular coordinates of arc begin point
335 (rectangular-start-radius (polar->rectangular start-radius start-angle))
336 ;; we want the end angle to always be bigger than the start angle
337 ;; so we redefine it here just in case it is less
339 (if (<= new-end-angle new-start-angle)
340 (+ TWO-PI new-end-angle)
342 ;; all the points that may be extrema of the arc
343 ;; this is the 90 degree points plus the beginning and end points
344 ;; we use this to calculate extents
346 (insert-in-ordered-list
348 (cons new-end-angle rectangular-end-radius)
349 (insert-in-ordered-list
351 (cons new-start-angle rectangular-start-radius)
367 ; we know the extrema points by crawling through the
368 ; list of possible extrema and finding the min and max
370 (cons (min-max-crawler min cadr possible-extrema)
371 (min-max-crawler max cadr possible-extrema))
372 (cons (min-max-crawler min cddr possible-extrema)
373 (min-max-crawler max cddr possible-extrema)))))
375 (define (line-part-min-max x1 x2)
376 (list (min x1 x2) (max x1 x2)))
378 (define (bezier-part-min-max x1 x2 x3 x4)
379 ((lambda (x) (list (reduce min 10000 x) (reduce max -10000 x)))
382 (+ (* x1 (expt (- 1 x) 3))
383 (+ (* 3 (* x2 (* (expt (- 1 x) 2) x)))
384 (+ (* 3 (* x3 (* (- 1 x) (expt x 2))))
385 (* x4 (expt x 3))))))
386 (if (< (+ (expt x2 2) (+ (expt x3 2) (* x1 x4)))
387 (+ (* x1 x3) (+ (* x2 x4) (* x2 x3))))
390 (lambda (x) (and (>= x 0) (<= x 1)))
395 (exact->inexact (- (+ x1 (* 3 x3)) (+ x4 (* 3 x2))))))
396 ;; Zeros of the bezier curve
397 (/ (+ (- x1 (* 2 x2))
399 (sqrt (- (+ (expt x2 2)
400 (+ (expt x3 2) (* x1 x4)))
402 (+ (* x2 x4) (* x2 x3)))))))
403 (- (+ x1 (* 3 x3)) (+ x4 (* 3 x2))))
404 ;; Apply L'hopital's rule to get the zeros if 0/0
407 (sqrt (- (+ (* x2 x2)
408 (+ (* x3 x3) (* x1 x4)))
410 (+ (* x2 x4) (* x2 x3)))))))))
413 (define (bezier-min-max x1 y1 x2 y2 x3 y3 x4 y4)
415 (apply bezier-part-min-max x))
416 `((,x1 ,x2 ,x3 ,x4) (,y1 ,y2 ,y3 ,y4))))
418 (define (line-min-max x1 y1 x2 y2)
420 (apply line-part-min-max x))
421 `((,x1 ,x2) (,y1 ,y2))))
423 (define (path-min-max origin pointlist)
427 (reduce min +inf.0 (map caar x))
428 (reduce max -inf.0 (map cadar x))
429 (reduce min +inf.0 (map caadr x))
430 (reduce max -inf.0 (map cadadr x))))
433 (apply bezier-min-max x)
434 (apply line-min-max x)))
436 (append (list (cadr (reverse x)) (car (reverse x))) y))
437 (append (list origin)
438 (reverse (cdr (reverse pointlist)))) pointlist))))
440 (define-public (make-connected-path-stencil pointlist thickness
441 x-scale y-scale connect fill)
442 "Make a connected path described by the list @var{pointlist}, with
443 thickness @var{thickness}, and scaled by @var{x-scale} in the X direction
444 and @var{y-scale} in the Y direction. @var{connect} and @var{fill} are
445 boolean arguments that specify if the path should be connected or filled,
448 ;; paths using this routine are designed to begin at point '(0 . 0)
449 (let* ((origin (list 0 0))
450 (boundlist (path-min-max origin pointlist))
451 ;; modify pointlist to scale the coordinates
452 (path (map (lambda (x)
455 (lambda (x1 x2 x3 x4 x5 x6)
469 ;; a path must begin with a `moveto'
470 (prepend-origin (apply list (cons 'moveto origin) path))
471 ;; if this path is connected, add closepath to the end
472 (final-path (if connect
473 (append prepend-origin (list 'closepath))
475 (command-list (fold-right append '() final-path)))
483 ((if (< x-scale 0) reverse-interval identity)
484 (cons (* x-scale (list-ref boundlist 0))
485 (* x-scale (list-ref boundlist 1))))
486 `(,(/ thickness -2) . ,(/ thickness 2)))
488 ((if (< y-scale 0) reverse-interval identity)
489 (cons (* y-scale (list-ref boundlist 2))
490 (* y-scale (list-ref boundlist 3))))
491 `(,(/ thickness -2) . ,(/ thickness 2))))))
493 (define-public (make-ellipse-stencil x-radius y-radius thickness fill)
494 "Make an ellipse of x@tie{}radius @var{x-radius}, y@tie{}radius
495 @code{y-radius}, and thickness @var{thickness} with fill defined by
498 ((x-out-radius (+ x-radius (/ thickness 2.0)))
499 (y-out-radius (+ y-radius (/ thickness 2.0))) )
502 (list 'ellipse x-radius y-radius thickness fill)
503 (cons (- x-out-radius) x-out-radius)
504 (cons (- y-out-radius) y-out-radius))))
506 (define-public (box-grob-stencil grob)
507 "Make a box of exactly the extents of the grob. The box precisely
508 encloses the contents."
509 (let* ((xext (ly:grob-extent grob grob 0))
510 (yext (ly:grob-extent grob grob 1))
514 (make-filled-box-stencil xext (cons (- (car yext) thick) (car yext)))
515 (make-filled-box-stencil xext (cons (cdr yext) (+ (cdr yext) thick)))
516 (make-filled-box-stencil (cons (cdr xext) (+ (cdr xext) thick)) yext)
517 (make-filled-box-stencil (cons (- (car xext) thick) (car xext)) yext))))
519 ;; TODO merge this and prev function.
520 (define-public (box-stencil stencil thickness padding)
521 "Add a box around @var{stencil}, producing a new stencil."
522 (let* ((x-ext (interval-widen (ly:stencil-extent stencil 0) padding))
523 (y-ext (interval-widen (ly:stencil-extent stencil 1) padding))
524 (y-rule (make-filled-box-stencil (cons 0 thickness) y-ext))
525 (x-rule (make-filled-box-stencil
526 (interval-widen x-ext thickness) (cons 0 thickness))))
527 (set! stencil (ly:stencil-combine-at-edge stencil X 1 y-rule padding))
528 (set! stencil (ly:stencil-combine-at-edge stencil X -1 y-rule padding))
529 (set! stencil (ly:stencil-combine-at-edge stencil Y 1 x-rule 0.0))
530 (set! stencil (ly:stencil-combine-at-edge stencil Y -1 x-rule 0.0))
533 (define-public (circle-stencil stencil thickness padding)
534 "Add a circle around @var{stencil}, producing a new stencil."
535 (let* ((x-ext (ly:stencil-extent stencil X))
536 (y-ext (ly:stencil-extent stencil Y))
537 (diameter (max (interval-length x-ext)
538 (interval-length y-ext)))
539 (radius (+ (/ diameter 2) padding thickness))
540 (circle (make-circle-stencil radius thickness #f)))
544 (ly:stencil-translate circle
546 (interval-center x-ext)
547 (interval-center y-ext))))))
549 (define-public (oval-stencil stencil thickness x-padding y-padding)
550 "Add an oval around @code{stencil}, padded by the padding pair,
551 producing a new stencil."
552 (let* ((x-ext (ly:stencil-extent stencil X))
553 (y-ext (ly:stencil-extent stencil Y))
554 (x-length (+ (interval-length x-ext) x-padding thickness))
555 (y-length (+ (interval-length y-ext) y-padding thickness))
556 (x-radius (* 0.707 x-length) )
557 (y-radius (* 0.707 y-length) )
558 (oval (make-oval-stencil x-radius y-radius thickness #f)))
562 (ly:stencil-translate oval
564 (interval-center x-ext)
565 (interval-center y-ext))))))
567 (define-public (ellipse-stencil stencil thickness x-padding y-padding)
568 "Add an ellipse around @var{stencil}, padded by the padding pair,
569 producing a new stencil."
570 (let* ((x-ext (ly:stencil-extent stencil X))
571 (y-ext (ly:stencil-extent stencil Y))
572 (x-length (+ (interval-length x-ext) x-padding thickness))
573 (y-length (+ (interval-length y-ext) y-padding thickness))
574 ;(aspect-ratio (/ x-length y-length))
575 (x-radius (* 0.707 x-length) )
576 (y-radius (* 0.707 y-length) )
577 ;(diameter (max (- (cdr x-ext) (car x-ext))
578 ; (- (cdr y-ext) (car y-ext))))
579 ;(radius (+ (/ diameter 2) padding thickness))
580 (ellipse (make-ellipse-stencil x-radius y-radius thickness #f)))
584 (ly:stencil-translate ellipse
586 (interval-center x-ext)
587 (interval-center y-ext))))))
589 (define-public (rounded-box-stencil stencil thickness padding blot)
590 "Add a rounded box around @var{stencil}, producing a new stencil."
592 (let* ((xext (interval-widen (ly:stencil-extent stencil 0) padding))
593 (yext (interval-widen (ly:stencil-extent stencil 1) padding))
594 (min-ext (min (-(cdr xext) (car xext)) (-(cdr yext) (car yext))))
595 (ideal-blot (min blot (/ min-ext 2)))
596 (ideal-thickness (min thickness (/ min-ext 2)))
597 (outer (ly:round-filled-box
598 (interval-widen xext ideal-thickness)
599 (interval-widen yext ideal-thickness)
601 (inner (ly:make-stencil (list 'color (x11-color 'white)
602 (ly:stencil-expr (ly:round-filled-box
603 xext yext (- ideal-blot ideal-thickness)))))))
604 (set! stencil (ly:stencil-add outer inner))
607 (define-public (stencil-with-color stencil color)
609 (list 'color color (ly:stencil-expr stencil))
610 (ly:stencil-extent stencil X)
611 (ly:stencil-extent stencil Y)))
613 (define-public (stencil-whiteout stencil)
615 ((x-ext (ly:stencil-extent stencil X))
616 (y-ext (ly:stencil-extent stencil Y))
621 (stencil-with-color (ly:round-filled-box x-ext y-ext 0.0)
626 (define-public (arrow-stencil-maker start? end?)
627 "Return a function drawing a line from current point to @code{destination},
628 with optional arrows of @code{max-size} on start and end controlled by
629 @var{start?} and @var{end?}."
630 (lambda (destination max-size)
634 (distance (sqrt (+ (* (car destination) (car destination))
635 (* (cdr destination) (cdr destination)))))
636 (size (min max-size (/ distance 3)))
637 (rotate (lambda (z ang)
638 (* (make-polar 1 ang)
640 (complex-to-offset (lambda (z)
641 (list (real-part z) (imag-part z))))
643 (z-dest (+ (* e_x (car destination)) (* e_y (cdr destination))))
644 (e_z (/ z-dest (magnitude z-dest)))
645 (triangle-points (list
649 (p1s (map (lambda (z)
650 (+ z-dest (rotate z (angle z-dest))))
652 (p2s (map (lambda (z)
653 (rotate z (angle (- z-dest))))
658 `(polygon (quote ,(concatenate (map complex-to-offset p1s)))
663 `(polygon (quote ,(concatenate (map complex-to-offset p2s)))
666 (thickness (min (/ distance 12) 0.1))
667 (shorten-line (min (/ distance 3) 0.5))
668 (start (complex-to-offset (/ (* e_z shorten-line) 2)))
669 (end (complex-to-offset (- z-dest (/ (* e_z shorten-line) 2))))
671 (line (ly:make-stencil
672 `(draw-line ,thickness
673 ,(car start) ,(cadr start)
674 ,(car end) ,(cadr end)
676 (cons (min 0 (car destination))
677 (min 0 (cdr destination)))
678 (cons (max 0 (car destination))
679 (max 0 (cdr destination)))))
683 (if start? arrow-2 empty-stencil)
684 (if end? arrow-1 empty-stencil)
689 (define-public dimension-arrows (arrow-stencil-maker #t #t))
691 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
694 ;; annotations are arrows indicating the numerical value of
696 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
698 (define*-public (annotate-y-interval layout name extent is-length
699 #:key (color darkblue))
700 (let ((text-props (cons '((font-size . -3)
701 (font-family . typewriter))
702 (layout-extract-page-properties layout)))
704 (define (center-stencil-on-extent stil)
705 (ly:stencil-translate (ly:stencil-aligned-to stil Y CENTER)
706 (cons 0 (interval-center extent))))
707 ;; do something sensible for 0,0 intervals.
708 (set! extent (interval-widen extent 0.001))
709 (if (not (interval-sane? extent))
710 (set! annotation (interpret-markup
712 (make-simple-markup (simple-format #f "~a: NaN/inf" name))))
713 (let ((text-stencil (interpret-markup
715 (markup #:whiteout #:simple name)))
716 (dim-stencil (interpret-markup
720 ((interval-empty? extent)
723 (ly:format "~$" (interval-length extent)))
726 (car extent) (cdr extent)))))))
727 (arrows (ly:stencil-translate-axis
728 (dimension-arrows (cons 0 (interval-length extent)) 1.0)
729 (interval-start extent) Y)))
731 (center-stencil-on-extent text-stencil))
733 (ly:stencil-combine-at-edge arrows X RIGHT annotation 0.5))
735 (ly:stencil-combine-at-edge annotation X LEFT
736 (center-stencil-on-extent dim-stencil)
739 (stencil-with-color annotation color))))
743 ;; TODO: figure out how to annotate padding nicely
744 ;; TODO: emphasize either padding or min-dist depending on which constraint was active
745 (define*-public (annotate-spacing-spec layout spacing-spec start-Y-offset next-staff-Y
746 #:key (base-color blue))
747 (let* ((get-spacing-var (lambda (sym) (assoc-get sym spacing-spec 0.0)))
748 (space (get-spacing-var 'basic-distance))
749 (padding (get-spacing-var 'padding))
750 (min-dist (get-spacing-var 'minimum-distance))
751 (contrast-color (append (cdr base-color) (list (car base-color))))
752 (min-dist-blocks (<= (- start-Y-offset min-dist) next-staff-Y))
753 (min-dist-color (if min-dist-blocks contrast-color base-color))
754 (basic-annotation (annotate-y-interval layout
756 (cons (- start-Y-offset space) start-Y-offset)
758 #:color (map (lambda (x) (* x 0.25)) base-color)))
759 (min-annotation (annotate-y-interval layout
761 (cons (- start-Y-offset min-dist) start-Y-offset)
763 #:color min-dist-color))
764 (extra-annotation (annotate-y-interval layout
766 (cons next-staff-Y (- start-Y-offset min-dist))
768 #:color (map (lambda (x) (* x 0.5)) min-dist-color))))
770 (stack-stencils X RIGHT 0.0
775 (ly:stencil-add min-annotation extra-annotation))))))
777 (define-public (eps-file->stencil axis size file-name)
779 ((contents (ly:gulp-file file-name))
780 (bbox (get-postscript-bbox (car (string-split contents #\nul))))
781 (bbox-size (if (= axis X)
782 (- (list-ref bbox 2) (list-ref bbox 0))
783 (- (list-ref bbox 3) (list-ref bbox 1))
785 (factor (if (< 0 bbox-size)
786 (exact->inexact (/ size bbox-size))
789 (map (lambda (x) (* factor x)) bbox))
790 ; We need to shift the whole eps to (0,0), otherwise it will appear
791 ; displaced in lilypond (displacement will depend on the scaling!)
792 (translate-string (ly:format "~a ~a translate" (- (list-ref bbox 0)) (- (list-ref bbox 1))))
793 (clip-rect-string (ly:format
794 "~a ~a ~a ~a rectclip"
797 (- (list-ref bbox 2) (list-ref bbox 0))
798 (- (list-ref bbox 3) (list-ref bbox 1)))))
809 currentpoint translate
815 " factor translate-string clip-rect-string
824 ; Stencil starts at (0,0), since we have shifted the eps, and its
825 ; size is exactly the size of the scaled bounding box
826 (cons 0 (- (list-ref scaled-bbox 2) (list-ref scaled-bbox 0)))
827 (cons 0 (- (list-ref scaled-bbox 3) (list-ref scaled-bbox 1))))
829 (ly:make-stencil "" '(0 . 0) '(0 . 0)))
832 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
833 ;; output signatures.
835 (define-public (write-system-signatures basename paper-systems count)
836 (if (pair? paper-systems)
839 ((outname (simple-format #f "~a-~a.signature" basename count)) )
841 (ly:message "Writing ~a" outname)
842 (write-system-signature outname (car paper-systems))
843 (write-system-signatures basename (cdr paper-systems) (1+ count))))))
845 (use-modules (scm paper-system))
846 (define-public (write-system-signature filename paper-system)
848 (and (number? x) (inexact? x)))
851 (paper-system-system-grob paper-system))
853 (define output (open-output-file filename))
855 ;; todo: optionally use a command line flag? Or just junk this?
856 (define compare-expressions #f)
857 (define (strip-floats expr)
858 "Replace floats by #f"
861 ((ly:font-metric? expr) (ly:font-name expr))
862 ((pair? expr) (cons (strip-floats (car expr))
863 (strip-floats (cdr expr))))
866 (define (fold-false-pairs expr)
867 "Try to remove lists of #f as much as possible."
871 (rest (fold-false-pairs (cdr expr))))
874 (cons (fold-false-pairs first) rest)
878 (define (raw-string expr)
879 "escape quotes and slashes for python consumption"
880 (regexp-substitute/global #f "[@\n]" (simple-format #f "~a" expr) 'pre " " 'post))
882 (define (raw-pair expr)
883 (simple-format #f "~a ~a"
884 (car expr) (cdr expr)))
886 (define (found-grob expr)
891 (cause (event-cause grob))
892 (input (if (ly:stream-event? cause) (ly:event-property cause 'origin) #f))
893 (location (if (ly:input-location? input) (ly:input-file-line-char-column input) '()))
895 ;; todo: use stencil extent if available.
896 (x-ext (ly:grob-extent grob system-grob X))
897 (y-ext (ly:grob-extent grob system-grob Y))
899 (if compare-expressions
900 (interpret-for-signature
902 (set! collected (cons e collected)))
906 (simple-format output
908 (cdr (assq 'name (ly:grob-property grob 'meta) ))
909 (raw-string location)
910 (raw-pair (if (interval-empty? x-ext) '(1 . -1) x-ext))
911 (raw-pair (if (interval-empty? y-ext) '(1 . -1) y-ext))
912 (raw-string collected))
915 (define (interpret-for-signature escape collect expr)
916 (define (interpret expr)
918 ((head (if (pair? expr)
923 ((eq? head 'grob-cause) (escape (cdr expr)))
924 ((eq? head 'color) (interpret (caddr expr)))
925 ((eq? head 'rotate-stencil) (interpret (caddr expr)))
926 ((eq? head 'translate-stencil) (interpret (caddr expr)))
927 ((eq? head 'combine-stencil)
928 (for-each (lambda (e) (interpret e)) (cdr expr)))
930 (collect (fold-false-pairs (strip-floats expr))))
936 (if (ly:grob? system-grob)
938 (display (simple-format #f "# Output signature\n# Generated by LilyPond ~a\n" (lilypond-version))
940 (interpret-for-signature found-grob (lambda (x) #f)
942 (paper-system-stencil paper-system)))))
944 ;; should be superfluous, but leaking "too many open files"?