]> git.donarmstrong.com Git - lilypond.git/blob - scm/stencil.scm
Doc-de: update macros.itely and nitpicks
[lilypond.git] / scm / stencil.scm
1 ;;;; stencil.scm -- 
2 ;;;;
3 ;;;;  source file of the GNU LilyPond music typesetter
4 ;;;; 
5 ;;;; (c) 2003--2009 Han-Wen Nienhuys <hanwen@xs4all.nl>
6
7 (define-public (stack-stencils axis dir padding stils)
8   "Stack stencils STILS in direction AXIS, DIR, using PADDING."
9   (cond
10    ((null? stils) empty-stencil)
11    ((null? (cdr stils)) (car stils))
12    (else (ly:stencil-combine-at-edge
13           (car stils) axis dir (stack-stencils axis dir padding (cdr stils))
14           padding))))
15
16 (define-public (stack-stencils-padding-list axis dir padding stils)
17   "Stack stencils STILS in direction AXIS, DIR, using a list of PADDING."
18   (cond
19    ((null? stils) empty-stencil)
20    ((null? (cdr stils)) (car stils))
21    (else (ly:stencil-combine-at-edge
22           (car stils)
23           axis dir
24           (stack-stencils-padding-list axis dir (cdr padding) (cdr stils))
25           (car padding)))))
26
27 (define-public (centered-stencil stencil)
28   "Center stencil @var{stencil} in both the X and Y directions"
29   (ly:stencil-aligned-to (ly:stencil-aligned-to stencil X CENTER) Y CENTER))
30
31 (define-public (stack-lines dir padding baseline stils)
32   "Stack vertically with a baseline-skip."
33   (define result empty-stencil)
34   (define last-y #f)
35   (do
36       ((last-stencil #f (car p))
37        (p stils (cdr p)))
38       
39       ((null? p))
40
41     (if (number? last-y)
42         (begin
43           (let* ((dy (max (+ (* dir (interval-bound (ly:stencil-extent last-stencil Y) dir))
44                              padding
45                              (* (- dir) (interval-bound (ly:stencil-extent (car p) Y) (- dir))))
46                           baseline))
47                  (y (+ last-y  (* dir dy))))
48             
49                           
50             
51             (set! result
52                   (ly:stencil-add result (ly:stencil-translate-axis (car p) y Y)))
53             (set! last-y y)))
54         (begin
55           (set! last-y 0)
56           (set! result (car p)))))
57
58   result)
59     
60
61 (define-public (bracketify-stencil stil axis thick protusion padding)
62   "Add brackets around STIL, producing a new stencil."
63
64   (let* ((ext (ly:stencil-extent stil axis))
65          (lb (ly:bracket axis ext thick (- protusion)))
66          (rb (ly:bracket axis ext thick protusion)))
67     (set! stil
68           (ly:stencil-combine-at-edge stil (other-axis axis) 1 lb padding))
69     (set! stil
70           (ly:stencil-combine-at-edge stil (other-axis axis) -1 rb padding))
71     stil))
72
73 (define-public (make-line-stencil width startx starty endx endy)
74   "Make a line stencil of given linewidth and set its extents accordingly"
75   (let ((xext (cons (min startx endx) (max startx endx)))
76         (yext (cons (min starty endy) (max starty endy))))
77     (ly:make-stencil
78       (list 'draw-line width startx starty endx endy)
79       ; Since the line has rounded edges, we have to / can safely add half the 
80       ; width to all coordinates!
81       (interval-widen xext (/ width 2))
82       (interval-widen yext (/ width 2)))))
83
84
85 (define-public (make-filled-box-stencil xext yext)
86   "Make a filled box."
87   
88   (ly:make-stencil
89       (list 'round-filled-box (- (car xext)) (cdr xext)
90                        (- (car yext)) (cdr yext) 0.0)
91       xext yext))
92
93 (define-public (make-circle-stencil radius thickness fill)
94   "Make a circle of radius @var{radius} and thickness @var{thickness}"
95   (let*
96       ((out-radius (+ radius (/ thickness 2.0))))
97     
98   (ly:make-stencil
99    (list 'circle radius thickness fill) 
100    (cons (- out-radius) out-radius)
101    (cons (- out-radius) out-radius))))
102
103 (define-public (make-oval-stencil x-radius y-radius thickness fill)
104   "Make an oval from two Bezier curves, of x radius @var{x-radius}, 
105     y radius @code{y-radius},
106     and thickness @var{thickness} with fill defined by @code{fill}."
107   (let*
108       ((x-out-radius (+ x-radius (/ thickness 2.0))) 
109        (y-out-radius (+ y-radius (/ thickness 2.0))) )
110     
111   (ly:make-stencil
112    (list 'oval x-radius y-radius thickness fill) 
113    (cons (- x-out-radius) x-out-radius)
114    (cons (- y-out-radius) y-out-radius))))
115
116 (define-public (make-ellipse-stencil x-radius y-radius thickness fill)
117   "Make an ellipse of x radius @var{x-radius}, y radius @code{y-radius},
118     and thickness @var{thickness} with fill defined by @code{fill}."
119   (let*
120       ((x-out-radius (+ x-radius (/ thickness 2.0))) 
121        (y-out-radius (+ y-radius (/ thickness 2.0))) )
122     
123   (ly:make-stencil
124    (list 'ellipse x-radius y-radius thickness fill) 
125    (cons (- x-out-radius) x-out-radius)
126    (cons (- y-out-radius) y-out-radius))))
127
128 (define-public (box-grob-stencil grob)
129   "Make a box of exactly the extents of the grob.  The box precisely
130 encloses the contents.
131 "
132   (let* ((xext (ly:grob-extent grob grob 0))
133          (yext (ly:grob-extent grob grob 1))
134          (thick 0.01))
135
136     (ly:stencil-add
137      (make-filled-box-stencil xext (cons (- (car yext) thick) (car yext)))
138      (make-filled-box-stencil xext (cons (cdr yext) (+ (cdr yext) thick)))
139      (make-filled-box-stencil (cons (cdr xext) (+ (cdr xext) thick)) yext)
140      (make-filled-box-stencil (cons (- (car xext) thick) (car xext)) yext))))
141
142 ;; TODO merge this and prev function. 
143 (define-public (box-stencil stencil thickness padding)
144   "Add a box around STENCIL, producing a new stencil."
145   (let* ((x-ext (interval-widen (ly:stencil-extent stencil 0) padding))
146          (y-ext (interval-widen (ly:stencil-extent stencil 1) padding))
147          (y-rule (make-filled-box-stencil (cons 0 thickness) y-ext))
148          (x-rule (make-filled-box-stencil
149                   (interval-widen x-ext thickness) (cons 0 thickness))))
150     (set! stencil (ly:stencil-combine-at-edge stencil X 1 y-rule padding))
151     (set! stencil (ly:stencil-combine-at-edge stencil X -1 y-rule padding))
152     (set! stencil (ly:stencil-combine-at-edge stencil Y 1 x-rule 0.0))  
153     (set! stencil (ly:stencil-combine-at-edge stencil Y -1 x-rule 0.0))
154     stencil))
155
156 (define-public (circle-stencil stencil thickness padding)
157   "Add a circle around STENCIL, producing a new stencil."
158   (let* ((x-ext (ly:stencil-extent stencil X))
159          (y-ext (ly:stencil-extent stencil Y))
160          (diameter (max (interval-length x-ext)
161                         (interval-length y-ext))) 
162          (radius (+ (/ diameter 2) padding thickness))
163          (circle (make-circle-stencil radius thickness #f)))
164
165     (ly:stencil-add
166      stencil
167      (ly:stencil-translate circle
168                            (cons
169                             (interval-center x-ext)
170                             (interval-center y-ext))))))
171
172 (define-public (oval-stencil stencil thickness x-padding y-padding)
173   "Add an oval around @code{stencil}, padded by the padding pair, 
174    producing a new stencil."
175   (let* ((x-ext (ly:stencil-extent stencil X))
176          (y-ext (ly:stencil-extent stencil Y))
177          (x-length (+ (interval-length x-ext) x-padding thickness))
178          (y-length (+ (interval-length y-ext) y-padding thickness))
179          (x-radius (* 0.707 x-length) )
180          (y-radius (* 0.707 y-length) )
181          (oval (make-oval-stencil x-radius y-radius thickness #f)))
182
183     (ly:stencil-add
184      stencil
185      (ly:stencil-translate oval
186                            (cons
187                             (interval-center x-ext)
188                             (interval-center y-ext))))))
189
190 (define-public (ellipse-stencil stencil thickness x-padding y-padding)
191   "Add an ellipse around STENCIL, padded by the padding pair, 
192    producing a new stencil."
193   (let* ((x-ext (ly:stencil-extent stencil X))
194          (y-ext (ly:stencil-extent stencil Y))
195          (x-length (+ (interval-length x-ext) x-padding thickness))
196          (y-length (+ (interval-length y-ext) y-padding thickness))
197          ;(aspect-ratio (/ x-length y-length))
198          (x-radius (* 0.707 x-length) )
199          (y-radius (* 0.707 y-length) )
200          ;(diameter (max (- (cdr x-ext) (car x-ext))
201          ;              (- (cdr y-ext) (car y-ext))))
202          ;(radius (+ (/ diameter 2) padding thickness))
203          (ellipse (make-ellipse-stencil x-radius y-radius thickness #f)))
204
205     (ly:stencil-add
206      stencil
207      (ly:stencil-translate ellipse
208                            (cons
209                             (interval-center x-ext)
210                             (interval-center y-ext))))))
211
212 (define-public (rounded-box-stencil stencil thickness padding blot)
213    "Add a rounded box around STENCIL, producing a new stencil."  
214
215   (let* ((xext (interval-widen (ly:stencil-extent stencil 0) padding))
216          (yext (interval-widen (ly:stencil-extent stencil 1) padding))
217    (min-ext (min (-(cdr xext) (car xext)) (-(cdr yext) (car yext))))
218    (ideal-blot (min blot (/ min-ext 2)))
219    (ideal-thickness (min thickness (/ min-ext 2)))
220          (outer (ly:round-filled-box
221             (interval-widen xext ideal-thickness) 
222             (interval-widen yext ideal-thickness) 
223                ideal-blot))
224          (inner (ly:make-stencil (list 'color (x11-color 'white) 
225             (ly:stencil-expr (ly:round-filled-box 
226                xext yext (- ideal-blot ideal-thickness)))))))
227     (set! stencil (ly:stencil-add outer inner))
228     stencil))
229
230
231 (define-public (fontify-text font-metric text)
232   "Set TEXT with font FONT-METRIC, returning a stencil."
233   (let* ((b (ly:text-dimension font-metric text)))
234     (ly:make-stencil
235      `(text ,font-metric ,text) (car b) (cdr b))))
236      
237 (define-public (fontify-text-white scale font-metric text)
238   "Set TEXT with scale factor SCALE"
239   (let* ((b (ly:text-dimension font-metric text))
240          ;;urg -- workaround for using ps font
241          (c `(white-text ,(* 2 scale) ,text)))
242     ;;urg -- extent is not from ps font, but we hope it's close
243     (ly:make-stencil c (car b) (cdr b))))
244
245 (define-public (stencil-with-color stencil color)
246   (ly:make-stencil
247    (list 'color color (ly:stencil-expr stencil))
248    (ly:stencil-extent stencil X)
249    (ly:stencil-extent stencil Y)))
250   
251 (define-public (stencil-whiteout stencil)
252   (let*
253       ((x-ext (ly:stencil-extent stencil X))
254        (y-ext (ly:stencil-extent stencil Y))
255
256        )
257     
258     (ly:stencil-add
259      (stencil-with-color (ly:round-filled-box x-ext y-ext 0.0)
260                          white)
261      stencil)
262     ))
263
264 (define-public (dimension-arrows destination) 
265   "Draw twosided arrow from here to @var{destination}"
266   
267   (let*
268       ((e_x 1+0i)
269        (e_y 0+1i)
270        (rotate (lambda (z ang)
271                  (* (make-polar 1 ang)
272                     z)))
273        (complex-to-offset (lambda (z)
274                             (list (real-part z) (imag-part z))))
275        
276        (z-dest (+ (* e_x (car destination)) (* e_y (cdr destination))))
277        (e_z (/ z-dest (magnitude z-dest)))
278        (triangle-points '(-1+0.25i
279                           0
280                           -1-0.25i))
281        (p1s (map (lambda (z)
282                    (+ z-dest (rotate z (angle z-dest))))
283                  triangle-points))
284        (p2s (map (lambda (z)
285                    (rotate z (angle (- z-dest))))
286                    triangle-points))
287        (null (cons 0 0)) 
288        (arrow-1  
289         (ly:make-stencil
290          `(polygon (quote ,(concatenate (map complex-to-offset p1s)))
291                    0.0
292                    #t) null null))
293        (arrow-2
294         (ly:make-stencil
295          `(polygon (quote ,(concatenate (map complex-to-offset p2s)))
296                    0.0
297                    #t) null null ) )
298        (thickness 0.1)
299        (shorten-line 0.5)
300        (start (complex-to-offset (/ (* e_z shorten-line) 2)))
301        (end (complex-to-offset (- z-dest (/ (* e_z shorten-line) 2))))
302        
303        (line (ly:make-stencil
304               `(draw-line ,thickness
305                           ,(car start) ,(cadr start)
306                           ,(car end) ,(cadr end)
307                           )
308               (cons (min 0 (car destination))
309                     (min 0 (cdr destination)))
310               (cons (max 0 (car destination))
311                     (max 0 (cdr destination)))))
312                     
313        (result (ly:stencil-add arrow-2 arrow-1 line)))
314
315
316     result))
317
318 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
319 ;; ANNOTATIONS
320 ;;
321 ;; annotations are arrows indicating the numerical value of
322 ;; spacing variables 
323 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
324
325 (define*-public (annotate-y-interval layout name extent is-length
326                                      #:key (color darkblue))
327   (let ((text-props (cons '((font-size . -3)
328                             (font-family . typewriter))
329                           (layout-extract-page-properties layout)))
330         (annotation #f))
331     (define (center-stencil-on-extent stil)
332       (ly:stencil-translate (ly:stencil-aligned-to stil Y CENTER)
333                             (cons 0 (interval-center extent))))
334     ;; do something sensible for 0,0 intervals. 
335     (set! extent (interval-widen extent 0.001))
336     (if (not (interval-sane? extent))
337         (set! annotation (interpret-markup
338                           layout text-props
339                           (make-simple-markup (simple-format #f "~a: NaN/inf" name))))
340         (let ((text-stencil (interpret-markup
341                              layout text-props
342                              (markup #:whiteout #:simple name)))
343               (dim-stencil (interpret-markup
344                             layout text-props
345                             (markup #:whiteout
346                                     #:simple (cond
347                                               ((interval-empty? extent)
348                                                (format "empty"))
349                                               (is-length
350                                                (ly:format "~$" (interval-length extent)))
351                                               (else
352                                                (ly:format "(~$,~$)"
353                                                        (car extent) (cdr extent)))))))
354               (arrows (ly:stencil-translate-axis 
355                        (dimension-arrows (cons 0 (interval-length extent)))
356                        (interval-start extent) Y)))
357           (set! annotation
358                 (center-stencil-on-extent text-stencil))
359           (set! annotation
360                 (ly:stencil-combine-at-edge arrows X RIGHT annotation 0.5))
361           (set! annotation
362                 (ly:stencil-combine-at-edge annotation X LEFT
363                                             (center-stencil-on-extent dim-stencil)
364                                             0.5))
365           (set! annotation
366                 (ly:make-stencil (list 'color color (ly:stencil-expr annotation))
367                                  (ly:stencil-extent annotation X)
368                                  (cons 10000 -10000)))))
369     annotation))
370
371
372 (define-public (eps-file->stencil axis size file-name)
373   (let*
374       ((contents (ly:gulp-file file-name))
375        (bbox (get-postscript-bbox (car (string-split contents #\nul))))
376        (bbox-size (if (= axis X)
377                       (- (list-ref bbox 2) (list-ref bbox 0))
378                       (- (list-ref bbox 3) (list-ref bbox 1))
379                       ))
380        (factor (if (< 0 bbox-size)
381                    (exact->inexact (/ size bbox-size))
382                    0))
383        (scaled-bbox
384         (map (lambda (x) (* factor x)) bbox))
385        (clip-rect-string (ly:format
386                           "~a ~a ~a ~a rectclip"
387                           (list-ref bbox 0) 
388                           (list-ref bbox 1) 
389                           (- (list-ref bbox 2) (list-ref bbox 0))
390                           (- (list-ref bbox 3) (list-ref bbox 1)))))
391     
392
393     (if bbox
394         (ly:make-stencil
395          (list
396           'embedded-ps
397           (string-append
398            (ly:format
399            "
400 gsave
401 currentpoint translate
402 BeginEPSF
403 ~a dup scale
404 ~a 
405 %%BeginDocument: ~a
406 "          factor clip-rect-string
407
408            file-name
409            )
410            contents
411            "%%EndDocument
412 EndEPSF
413 grestore
414 "))
415         
416          (cons (list-ref scaled-bbox 0) (list-ref scaled-bbox 2))
417          (cons (list-ref scaled-bbox 1) (list-ref scaled-bbox 3)))
418         
419         (ly:make-stencil "" '(0 . 0) '(0 . 0)))
420     ))
421
422 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
423 ;; output signatures.
424
425 (define-public (write-system-signatures basename paper-systems count)
426   (if (pair? paper-systems)
427       (begin
428         (let*
429             ((outname (simple-format #f "~a-~a.signature" basename count)) )
430              
431           (ly:message "Writing ~a" outname)
432           (write-system-signature outname (car paper-systems))
433           (write-system-signatures basename (cdr paper-systems) (1+ count))))))
434
435 (use-modules (scm paper-system))
436 (define-public (write-system-signature filename paper-system)
437   (define (float? x)
438     (and (number? x) (inexact? x)))
439
440   (define system-grob
441     (paper-system-system-grob paper-system))
442   
443   (define output (open-output-file filename))
444
445   ;; todo: optionally use a command line flag? Or just junk this?
446   (define compare-expressions #f)
447   (define (strip-floats expr)
448     "Replace floats by #f"
449     (cond
450      ((float? expr) #f)
451      ((ly:font-metric? expr) (ly:font-name expr))
452      ((pair? expr) (cons (strip-floats (car expr))
453                          (strip-floats (cdr expr))))
454      (else expr)))
455
456   (define (fold-false-pairs expr)
457     "Try to remove lists of #f as much as possible."
458     (if (pair? expr)
459         (let*
460             ((first (car expr))
461              (rest (fold-false-pairs (cdr expr))))
462
463           (if first
464               (cons (fold-false-pairs first) rest)
465               rest))
466         expr))
467   
468   (define (raw-string expr)
469     "escape quotes and slashes for python consumption"
470     (regexp-substitute/global #f "[@\n]" (simple-format #f "~a" expr) 'pre " " 'post))
471
472   (define (raw-pair expr)
473     (simple-format #f "~a ~a"
474             (car expr) (cdr expr)))
475   
476   (define (found-grob expr)
477     (let*
478         ((grob (car expr))
479          (rest (cdr expr))
480          (collected '())
481          (cause (event-cause grob))
482          (input (if (ly:stream-event? cause) (ly:event-property cause 'origin) #f))
483          (location (if (ly:input-location? input) (ly:input-file-line-char-column input) '()))
484
485          ;; todo: use stencil extent if available.
486          (x-ext (ly:grob-extent grob system-grob X))
487          (y-ext (ly:grob-extent grob system-grob Y))
488          (expression-skeleton
489           (if compare-expressions
490               (interpret-for-signature
491                #f (lambda (e)
492                     (set! collected (cons e collected)))
493                rest)
494              "")))
495
496       (simple-format output
497               "~a@~a@~a@~a@~a\n"
498               (cdr (assq 'name (ly:grob-property grob 'meta) ))
499               (raw-string location)
500               (raw-pair (if (interval-empty? x-ext) '(1 . -1) x-ext))
501               (raw-pair (if (interval-empty? y-ext) '(1 . -1) y-ext))
502               (raw-string collected))
503       ))
504
505   (define (interpret-for-signature escape collect expr)
506     (define (interpret expr)
507       (let*
508           ((head (if (pair? expr)
509                      (car expr)
510                      #f)))
511
512         (cond
513          ((eq? head 'grob-cause) (escape (cdr expr)))
514          ((eq? head 'color) (interpret (caddr expr)))
515          ((eq? head 'rotate-stencil) (interpret (caddr expr)))
516          ((eq? head 'translate-stencil) (interpret (caddr expr)))
517          ((eq? head 'combine-stencil)
518           (for-each (lambda (e) (interpret e))  (cdr expr)))
519          (else
520           (collect (fold-false-pairs (strip-floats expr))))
521          
522          )))
523
524     (interpret expr))
525
526   (if (ly:grob? system-grob)
527       (begin
528         (display (simple-format #f "# Output signature\n# Generated by LilyPond ~a\n" (lilypond-version))
529                  output)
530         (interpret-for-signature found-grob (lambda (x) #f)
531                                  (ly:stencil-expr
532                                   (paper-system-stencil paper-system)))))
533
534   ;; should be superfluous, but leaking "too many open files"?
535   (close-port output))
536