;;;; This file is part of LilyPond, the GNU music typesetter.
;;;;
-;;;; Copyright (C) 2003--2012 Han-Wen Nienhuys <hanwen@xs4all.nl>
+;;;; Copyright (C) 2003--2015 Han-Wen Nienhuys <hanwen@xs4all.nl>
;;;;
;;;; LilyPond is free software: you can redistribute it and/or modify
;;;; it under the terms of the GNU General Public License as published by
empty-stencil
(fold
(lambda (next padding front)
- (ly:stencil-stack front axis dir next padding))
+ (let ((offset (+ (- (interval-end (ly:stencil-extent front axis))
+ (interval-start (ly:stencil-extent next axis)))
+ padding)))
+ (ly:stencil-add
+ front
+ (ly:stencil-translate-axis next offset axis))))
(car stils)
(cdr stils)
paddings)))
upper-end-point
;; Step 1: move to lower end point.
lower-end-point)
- line-width
+ (min (* 2 half-thickness) line-width)
(interval-widen x-extent (/ line-width 2))
(interval-widen y-extent (/ line-width 2)))))
\n((0.0 . 2) (PI/2 . 3) (PI . -2) (3*PI/2 . -3)\
\n(2*PI . 2) (5*PI/2 . 3) (3*PI . -2) (7*PI/2 . -3))
"
- (apply append
- (map (lambda (adder)
- (map (lambda (quadrant)
- (cons (+ adder (car quadrant))
- (cdr quadrant)))
- `((0.0 . (,x-radius . 0.0))
- (,PI-OVER-TWO . (0.0 . ,y-radius))
- (,PI . (,(- x-radius) . 0.0))
- (,THREE-PI-OVER-TWO . (0.0 . ,(- y-radius))))))
- `(0.0 ,TWO-PI))))
+ (append-map
+ (lambda (adder)
+ (map (lambda (quadrant)
+ (cons (+ adder (car quadrant))
+ (cdr quadrant)))
+ `((0.0 . (,x-radius . 0.0))
+ (,PI-OVER-TWO . (0.0 . ,y-radius))
+ (,PI . (,(- x-radius) . 0.0))
+ (,THREE-PI-OVER-TWO . (0.0 . ,(- y-radius))))))
+ `(0.0 ,TWO-PI)))
(define
(insert-in-ordered-list ordering-function value inlist cutl? cutr?)
"
(reduce min-max
(if (eq? min-max min) 100000 -100000)
- (map (lambda (x) (side x)) l)))
+ (map side l)))
(let*
(;; the outside limit of the x-radius
(append (list origin)
(reverse (cdr (reverse pointlist)))) pointlist))))
-(define-public (make-connected-path-stencil pointlist thickness
- x-scale y-scale connect fill)
- "Make a connected path described by the list @var{pointlist}, with
-thickness @var{thickness}, and scaled by @var{x-scale} in the X direction
-and @var{y-scale} in the Y direction. @var{connect} and @var{fill} are
-boolean arguments that specify if the path should be connected or filled,
-respectively."
-
- ;; paths using this routine are designed to begin at point '(0 . 0)
- (let* ((origin (list 0 0))
- (boundlist (path-min-max origin pointlist))
- ;; modify pointlist to scale the coordinates
- (path (map (lambda (x)
- (apply
- (if (= 6 (length x))
- (lambda (x1 x2 x3 x4 x5 x6)
- (list 'curveto
- (* x1 x-scale)
- (* x2 y-scale)
- (* x3 x-scale)
- (* x4 y-scale)
- (* x5 x-scale)
- (* x6 y-scale)))
- (lambda (x1 x2)
- (list 'lineto
- (* x1 x-scale)
- (* x2 y-scale))))
- x))
- pointlist))
- ;; a path must begin with a `moveto'
- (prepend-origin (apply list (cons 'moveto origin) path))
- ;; if this path is connected, add closepath to the end
- (final-path (if connect
- (append prepend-origin (list 'closepath))
- prepend-origin))
- (command-list (fold-right append '() final-path)))
+(define-public (make-path-stencil path thickness x-scale y-scale fill)
+ "Make a stencil based on the path described by the list @var{path},
+with thickness @var{thickness}, and scaled by @var{x-scale} in the X
+direction and @var{y-scale} in the Y direction. @var{fill} is a boolean
+argument that specifies if the path should be filled. Valid path
+commands are: moveto rmoveto lineto rlineto curveto rcurveto closepath,
+and their standard SVG single letter equivalents: M m L l C c Z z."
+
+ (define (convert-path path origin previous-point)
+ "Recursive function to standardize command names and
+convert any relative path expressions (in @var{path}) to absolute
+values. Returns a list of lists. @var{origin} is a pair of x and y
+coordinates for the origin point of the path (used for closepath and
+reset by moveto commands). @var{previous-point} is a pair of x and y
+coordinates for the previous point in the path."
+ (if (pair? path)
+ (let*
+ ((head-raw (car path))
+ (rest (cdr path))
+ (head (cond
+ ((memq head-raw '(rmoveto M m)) 'moveto)
+ ((memq head-raw '(rlineto L l)) 'lineto)
+ ((memq head-raw '(rcurveto C c)) 'curveto)
+ ((memq head-raw '(Z z)) 'closepath)
+ (else head-raw)))
+ (arity (cond
+ ((memq head '(lineto moveto)) 2)
+ ((eq? head 'curveto) 6)
+ (else 0)))
+ (coordinates-raw (take rest arity))
+ (is-absolute (if (memq head-raw
+ '(rmoveto m rlineto l rcurveto c)) #f #t))
+ (coordinates (if is-absolute
+ coordinates-raw
+ ;; convert relative coordinates to absolute by
+ ;; adding them to previous point values
+ (map (lambda (c n)
+ (if (even? n)
+ (+ c (car previous-point))
+ (+ c (cdr previous-point))))
+ coordinates-raw
+ (iota arity))))
+ (new-point (if (eq? head 'closepath)
+ origin
+ (cons
+ (list-ref coordinates (- arity 2))
+ (list-ref coordinates (- arity 1)))))
+ (new-origin (if (eq? head 'moveto)
+ new-point
+ origin)))
+ (cons (cons head coordinates)
+ (convert-path (drop rest arity) new-origin new-point)))
+ '()))
+
+ (let* ((path-absolute (convert-path path (cons 0 0) (cons 0 0)))
+ ;; scale coordinates
+ (path-scaled (if (and (= 1 x-scale) (= 1 y-scale))
+ path-absolute
+ (map (lambda (path-unit)
+ (map (lambda (c n)
+ (cond
+ ((= 0 n) c)
+ ((odd? n) (* c x-scale))
+ (else (* c y-scale))))
+ path-unit
+ (iota (length path-unit))))
+ path-absolute)))
+ ;; a path must begin with a 'moveto'
+ (path-final (if (eq? 'moveto (car (car path-scaled)))
+ path-scaled
+ (append (list (list 'moveto 0 0)) path-scaled)))
+ ;; remove all commands in order to calculate bounds
+ (path-headless (map cdr (delete (list 'closepath) path-final)))
+ (bound-list (path-min-max
+ (car path-headless)
+ (cdr path-headless))))
(ly:make-stencil
`(path ,thickness
- `(,@',command-list)
- 'round
- 'round
- ,(if fill #t #f))
+ `(,@',(concatenate path-final))
+ 'round
+ 'round
+ ,(if fill #t #f))
(coord-translate
((if (< x-scale 0) reverse-interval identity)
- (cons (* x-scale (list-ref boundlist 0))
- (* x-scale (list-ref boundlist 1))))
+ (cons
+ (list-ref bound-list 0)
+ (list-ref bound-list 1)))
`(,(/ thickness -2) . ,(/ thickness 2)))
(coord-translate
((if (< y-scale 0) reverse-interval identity)
- (cons (* y-scale (list-ref boundlist 2))
- (* y-scale (list-ref boundlist 3))))
+ (cons
+ (list-ref bound-list 2)
+ (list-ref bound-list 3)))
`(,(/ thickness -2) . ,(/ thickness 2))))))
+(define-public (make-connected-path-stencil pointlist thickness
+ x-scale y-scale connect fill)
+ "Make a connected path described by the list @var{pointlist}, beginning
+at point '(0 . 0), with thickness @var{thickness}, and scaled by
+@var{x-scale} in the X direction and @var{y-scale} in the Y direction.
+@var{connect} and @var{fill} are boolean arguments that specify if the
+path should be connected or filled, respectively."
+ (make-path-stencil
+ (concatenate
+ (append
+ (map (lambda (path-unit)
+ (case (length path-unit)
+ ((2) (append (list 'lineto) path-unit))
+ ((6) (append (list 'curveto) path-unit))))
+ pointlist)
+ ;; if this path is connected, add closepath to the end
+ (if connect (list '(closepath)) '())))
+ thickness x-scale y-scale fill))
+
(define-public (make-ellipse-stencil x-radius y-radius thickness fill)
"Make an ellipse of x@tie{}radius @var{x-radius}, y@tie{}radius
@code{y-radius}, and thickness @var{thickness} with fill defined by
(set! stencil (ly:stencil-add outer inner))
stencil))
+(define-public (flip-stencil axis stil)
+ "Flip stencil @var{stil} in the direction of @var{axis}.
+Value @code{X} (or @code{0}) for @var{axis} flips it horizontally.
+Value @code{Y} (or @code{1}) flips it vertically. @var{stil} is
+flipped in place; its position, the coordinates of its bounding
+box, remains the same."
+ (let* (
+ ;; scale stencil using -1 to flip it and
+ ;; then restore it to its original position
+ (xy (if (= axis X) '(-1 . 1) '(1 . -1)))
+ (flipped-stil (ly:stencil-scale stil (car xy) (cdr xy)))
+ (flipped-ext (ly:stencil-extent flipped-stil axis))
+ (original-ext (ly:stencil-extent stil axis))
+ (offset (- (car original-ext) (car flipped-ext)))
+ (replaced-stil (ly:stencil-translate-axis flipped-stil offset axis)))
+ replaced-stil))
+
(define-public (stencil-with-color stencil color)
(ly:make-stencil
(list 'color color (ly:stencil-expr stencil))
(ly:stencil-extent stencil X)
(ly:stencil-extent stencil Y)))
-(define-public (stencil-whiteout stencil)
+(define*-public (stencil-whiteout
+ stil #:optional (thickness 0.3) (color white)
+ (angle-increments 16) (radial-increments 1))
+ "This function works by creating a series of white or @var{color}
+stencils radially offset from the original stencil with angles from
+0 to 2*pi, at an increment of @code{angle-inc}, and with radii
+from @code{radial-inc} to @var{thickness}. @var{thickness} is how big
+the white outline is in staff-spaces. @var{radial-increments} is how
+many copies of the white stencil we make on our way out to thickness.
+@var{angle-increments} is how many copies of the white stencil
+we make between 0 and 2*pi."
+ (if (or (not (positive? angle-increments))
+ (not (positive? radial-increments)))
+ (begin
+ (ly:warning "Both angle-increments and radial-increments must be positive numbers.")
+ stil)
+ (let* ((2pi 6.283185307)
+ (angle-inc (/ 2pi angle-increments))
+ (radial-inc (/ thickness radial-increments)))
+
+ (define (circle-plot ang dec radius original-stil new-stil)
+ ;; ang (angle) and dec (decrement) are in radians, not degrees
+ (if (<= ang 0)
+ new-stil
+ (circle-plot (- ang dec) dec radius original-stil
+ (ly:stencil-add
+ new-stil
+ (ly:stencil-translate original-stil
+ (cons
+ (* radius (cos ang))
+ (* radius (sin ang))))))))
+
+ (define (radial-plot radius original-stil new-stil)
+ (if (<= radius 0)
+ new-stil
+ (ly:stencil-add new-stil
+ (radial-plot
+ (- radius radial-inc)
+ original-stil
+ (circle-plot 2pi angle-inc
+ radius original-stil empty-stencil)))))
+
+ (let ((whiteout-expr
+ (ly:stencil-expr
+ (stencil-with-color
+ (radial-plot thickness stil empty-stencil)
+ color))))
+ (ly:stencil-add
+ (ly:make-stencil
+ `(delay-stencil-evaluation ,(delay whiteout-expr)))
+ stil)))))
+
+(define-public (stencil-whiteout-box stencil)
(let*
((x-ext (ly:stencil-extent stencil X))
- (y-ext (ly:stencil-extent stencil Y))
-
- )
+ (y-ext (ly:stencil-extent stencil Y)))
(ly:stencil-add
(stencil-with-color (ly:round-filled-box x-ext y-ext 0.0)
white)
- stencil)
- ))
+ stencil)))
(define-public (arrow-stencil-maker start? end?)
"Return a function drawing a line from current point to @code{destination},
(null (cons 0 0))
(arrow-1
(ly:make-stencil
- `(polygon (quote ,(concatenate (map complex-to-offset p1s)))
+ `(polygon (quote ,(append-map complex-to-offset p1s))
0.0
#t) null null))
(arrow-2
(ly:make-stencil
- `(polygon (quote ,(concatenate (map complex-to-offset p2s)))
+ `(polygon (quote ,(append-map complex-to-offset p2s))
0.0
#t) null null ) )
(thickness (min (/ distance 12) 0.1))
(make-simple-markup (simple-format #f "~a: NaN/inf" name))))
(let ((text-stencil (interpret-markup
layout text-props
- (markup #:whiteout #:simple name)))
+ (markup #:whiteout-box #:simple name)))
(dim-stencil (interpret-markup
layout text-props
- (markup #:whiteout
+ (markup #:whiteout-box
#:simple (cond
((interval-empty? extent)
"empty")
;; TODO: figure out how to annotate padding nicely
;; TODO: emphasize either padding or min-dist depending on which constraint was active
-(define*-public (annotate-spacing-spec layout spacing-spec start-Y-offset next-staff-Y
+(define*-public (annotate-spacing-spec layout name spacing-spec
+ start-Y-offset next-staff-Y
#:key (base-color blue))
(let* ((get-spacing-var (lambda (sym) (assoc-get sym spacing-spec 0.0)))
(space (get-spacing-var 'basic-distance))
(contrast-color (append (cdr base-color) (list (car base-color))))
(min-dist-blocks (<= (- start-Y-offset min-dist) next-staff-Y))
(min-dist-color (if min-dist-blocks contrast-color base-color))
- (basic-annotation (annotate-y-interval layout
- "basic-dist"
- (cons (- start-Y-offset space) start-Y-offset)
- #t
- #:color (map (lambda (x) (* x 0.25)) base-color)))
- (min-annotation (annotate-y-interval layout
- "min-dist"
- (cons (- start-Y-offset min-dist) start-Y-offset)
- #t
- #:color min-dist-color))
- (extra-annotation (annotate-y-interval layout
- "extra dist"
- (cons next-staff-Y (- start-Y-offset min-dist))
- #t
- #:color (map (lambda (x) (* x 0.5)) min-dist-color))))
+ (name-string (if (string-null? name)
+ ""
+ (simple-format #f " (~a)" name)))
+ (basic-annotation
+ (annotate-y-interval layout
+ (simple-format #f "basic-dist~a" name-string)
+ (cons (- start-Y-offset space) start-Y-offset)
+ #t
+ #:color (map (lambda (x) (* x 0.25)) base-color)))
+ (min-annotation
+ (annotate-y-interval layout
+ (simple-format #f "min-dist~a" name-string)
+ (cons (- start-Y-offset min-dist) start-Y-offset)
+ #t
+ #:color min-dist-color))
+ (extra-annotation
+ (annotate-y-interval layout
+ (simple-format #f "extra dist~a" name-string)
+ (cons next-staff-Y (- start-Y-offset min-dist))
+ #t
+ #:color (map (lambda (x) (* x 0.5)) min-dist-color))))
(stack-stencils X RIGHT 0.0
(list