]> git.donarmstrong.com Git - lilypond.git/blob - scm/output-svg.scm
*** empty log message ***
[lilypond.git] / scm / output-svg.scm
1 ;;;; output-svg.scm -- implement Scheme output routines for SVG1
2 ;;;;
3 ;;;;  source file of the GNU LilyPond music typesetter
4 ;;;;
5 ;;;; (c) 2002--2005 Jan Nieuwenhuizen <janneke@gnu.org>
6
7 ;;;; http://www.w3.org/TR/SVG11
8 ;;;; http://www.w3.org/TR/SVG12/ -- page, pageSet in draft
9
10 ;;;; TODO:
11 ;;;;  * .cff MUST NOT be in fc's fontpath.
12 ;;;;    - workaround: remove mf/out from ~/.fonts.conf,
13 ;;;;      instead add ~/.fonts and symlink all /mf/out/*otf there.
14 ;;;;    - bug in fontconfig/freetype/pango?
15
16 ;;;;  * inkscape page/pageSet support
17 ;;;;  * inkscape SVG-font support
18 ;;;;    - use fontconfig/fc-cache for now, see output-gnome.scm
19
20 (debug-enable 'backtrace)
21 (define-module (scm output-svg))
22 (define this-module (current-module))
23
24 (use-modules
25  (guile)
26  (ice-9 regex)
27  (lily)
28  (srfi srfi-13))
29
30
31 (define lily-unit-length 1.75)
32
33 (define (dispatch expr)
34   (let ((keyword (car expr)))
35     (cond
36      ((eq? keyword 'some-func) "")
37      ;;((eq? keyword 'placebox) (dispatch (cadddr expr)))
38      (else
39       (if (module-defined? this-module keyword)
40           (apply (eval keyword this-module) (cdr expr))
41           (begin
42             (display
43              (string-append "undefined: " (symbol->string keyword) "\n"))
44             ""))))))
45
46 ;; Helper functions
47 (define-public (attributes attributes-alist)
48   (apply string-append
49          (map (lambda (x) (format #f " ~s=\"~a\"" (car x) (cdr x)))
50               attributes-alist)))
51
52 (define-public (eo entity . attributes-alist)
53   "o = open"
54   (format #f "<~S~a>\n" entity (attributes attributes-alist)))
55
56 (define-public (eoc entity . attributes-alist)
57   " oc = open/close"
58   (format #f "<~S~a/>\n" entity (attributes attributes-alist)))
59
60 (define-public (ec entity)
61   "c = close"
62   (format #f "</~S>\n" entity))
63
64 (define-public (entity entity string . attributes-alist)
65   (if (equal? string "")
66       (apply eoc entity attributes-alist)
67       (string-append
68        (apply eo (cons entity attributes-alist)) string (ec entity))))
69
70 (define (offset->point o)
71   (format #f " ~S,~S" (car o)  (- (cdr o))))
72
73 (define (svg-bezier lst close)
74   (let* ((c0 (car (list-tail lst 3)))
75          (c123 (list-head lst 3)))
76     (string-append
77      (if (not close) "M " "L ")
78      (offset->point c0)
79      "C " (apply string-append (map offset->point c123))
80      (if (not close) "" (string-append
81                          "L " (offset->point close))))))
82
83 (define (sqr x)
84   (* x x))
85
86 (define (integer->entity integer)
87   (format #f "&#x~x;" integer))
88
89 (define (char->entity char)
90   (integer->entity (char->integer char)))
91
92 (define (string->entities string)
93   (apply string-append
94          (map (lambda (x) (char->entity x)) (string->list string))))
95
96 (define pango-description-regexp-comma
97   (make-regexp "^([^,]+), ?([-a-zA-Z_]*) ([0-9.]+)$"))
98
99 (define pango-description-regexp-nocomma
100   (make-regexp "^([^ ]+) ([-a-zA-Z_]*) ?([0-9.]+)$"))
101
102 (define (pango-description-to-svg-font str)
103   (let*
104       ((size 4.0)
105        (family "Helvetica")
106        (style #f)
107        (match-1 (regexp-exec pango-description-regexp-comma str))
108        (match-2 (regexp-exec pango-description-regexp-nocomma str))
109        (match (if match-1
110                   match-1
111                   match-2)))
112
113     (if (regexp-match? match)
114         (begin
115           (set! family (match:substring match 1))
116           (if (< 0 (string-length (match:substring match 2)))
117               (set! style (match:substring match 2)))
118           (set! size
119                 (string->number (match:substring match 3))))
120
121         (display (format "Cannot decypher Pango description:  ~a\n" str)))
122
123     (set! style
124           (if (string? style)
125               (format "font-style:~a;" style)
126               ""))
127     
128     (format "font-family:~a;~afont-size:~a;text-anchor:west"
129             family
130             style
131             (/ size lily-unit-length))
132     ))
133
134 ;;; FONT may be font smob, or pango font string
135 (define (svg-font font)
136   (if (string? font)
137       (pango-description-to-svg-font font)
138       (let ((name-style (font-name-style font))
139             (size (modified-font-metric-font-scaling font))
140             (anchor "west"))
141
142         (format #f "font-family:~a;font-style:~a;font-size:~a;text-anchor:~a;"
143                 (car name-style) (cadr name-style)
144                 size anchor))))
145
146 (define (fontify font expr)
147   (entity 'text expr
148           `(style . ,(svg-font font))
149           ))
150
151 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
152 ;;; stencil outputters
153 ;;;
154
155 ;;; catch-all for missing stuff
156 ;;; comment this out to see find out what functions you miss :-)
157
158 (if #f
159     (begin
160       (define (dummy . foo) "")
161       (map (lambda (x) (module-define! this-module x dummy))
162            (append
163             (ly:all-stencil-expressions)
164             (ly:all-output-backend-commands)))
165       ))
166
167 (define (url-link url x y)
168   (string-append
169    (eo 'a `(xlink:href . ,url))
170    (eoc 'rect
171         `(x . ,(car x))
172         `(y . ,(car y))
173         `(width . ,(- (cdr x) (car x)))
174         `(height . ,(- (cdr y) (car y)))
175         '(fill . "none")
176         '(stroke . "none")
177         '(stroke-width . "0.0"))
178    (ec 'a)))
179
180 (define (grob-cause offset grob)
181   "")
182
183 (define (no-origin)
184   "")
185
186
187 (define (rect-beam width slope thick blot-diameter)
188   (let* ((x width)
189          (y (* slope width))
190          (z (/ y x)))
191     (entity 'rect ""
192             ;; The stroke will stick out.  To use stroke,
193             ;; the stroke-width must be subtracted from all other dimensions.
194             ;;'(stroke-linejoin . "round")
195             ;;'(stroke-linecap . "round")
196             ;;`(stroke-width . ,blot-diameter)
197             ;;'(stroke . "red")
198             ;;'(fill . "orange")
199
200             `(x . 0)
201             `(y . ,(- (/ thick 2)))
202             `(width . ,width)
203             `(height . ,(+ thick (* (abs z) (/ thick 2))))
204             `(rx . ,(/ blot-diameter 2))
205             `(transform . ,(format #f "matrix (1, ~f, 0, 1, 0, 0)"  z)
206                         ))))
207
208 (define (beam width slope thick blot-diameter)
209   (let* ((b blot-diameter)
210          (t (- thick b))
211          (w (- width b))
212          (h (* w slope)))
213     (entity 'polygon ""
214             '(stroke-linejoin . "round")
215             '(stroke-linecap . "round")
216             `(stroke-width . ,blot-diameter)
217             '(stroke . "black")
218             '(fill . "black")
219             `(points . ,(string-join
220                          (map offset->point
221                               (list (cons (/ b 2) (/ t 2))
222                                     (cons (+ w (/ b 2)) (+ h (/ t 2)))
223                                     (cons (+ w (/ b 2)) (+ h (- (/ t 2))))
224                                     (cons (/ b 2) (- (/ t 2)))))))
225             )))
226
227 (define (path-beam width slope thick blot-diameter)
228   (let* ((b blot-diameter)
229          (t (- thick b))
230          (w (- width b))
231          (h (* w slope)))
232     (entity 'path ""
233             '(stroke-linejoin . "round")
234             '(stroke-linecap . "round")
235             `(stroke-width . ,blot-diameter)
236             '(stroke . "black")
237             '(fill . "black")
238             `(d . ,(format #f "M ~S,~S l ~S,~S l ~S,~S l ~S,~S l ~S,~S"
239                            (/ b 2) (/ t 2)
240                            w (- h)
241                            0 (- t)
242                            (- w) h
243                            0 t))
244             )))
245
246 (define (bezier-sandwich lst thick)
247   (let* ((first (list-tail lst 4))
248          (first-c0 (car (list-tail first 3)))
249          (second (list-head lst 4)))
250     (entity 'path ""
251             '(stroke-linejoin . "round")
252             '(stroke-linecap . "round")
253             `(stroke-width . ,thick)
254             '(stroke . "black")
255             '(fill . "black")
256             `(d . ,(string-append (svg-bezier first #f)
257                                   (svg-bezier second first-c0)))
258             )))
259
260 (define (char font i)
261   (dispatch
262    `(fontify ,font ,(entity 'tspan (char->entity (integer->char i))))))
263
264 (define-public (comment s)
265   (string-append "<!-- " s " !-->\n"))
266
267 (define (draw-line thick x1 y1 x2 y2 . alist)
268   
269   (apply entity 'line ""
270          (append
271           `((stroke-linejoin . "round")
272             (stroke-linecap . "round")
273             (stroke-width . ,thick)
274             (stroke . "black")
275             ;;'(fill . "black")
276             (x1 . ,x1)
277             (y1 . ,y1)
278             (x2 . ,x2)
279             (y2 . ,y2))
280           alist)))
281
282 (define (dashed-line thick on off dx dy)
283   (draw-line thick 0 0 dx dy `(style . ,(format "stroke-dasharray:~a,~a;" on off))))
284
285 ;; WTF is this in every backend?
286 (define (horizontal-line x1 x2 th)
287   (filledbox (- x1) (- x2 x1) (* .5 th) (* .5 th)))
288
289 (define (filledbox breapth width depth height)
290   (round-filled-box breapth width depth height 0))
291
292 (define (named-glyph font name)
293   (dispatch
294    `(fontify ,font ,(entity 'tspan
295                             (integer->entity
296                              (ly:font-glyph-name-to-charcode font name))))))
297
298 (define (placebox x y expr)
299   (entity 'g
300           ;; FIXME -- JCN
301           ;;(dispatch expr)
302           expr
303           `(transform . ,(format #f "translate (~f, ~f)"
304                                  x (- y)))))
305
306
307 (define (polygon coords blot-diameter)
308   (entity 'polygon ""
309           '(stroke-linejoin . "round")
310           '(stroke-linecap . "round")
311           `(stroke-width . ,blot-diameter)
312           '(stroke . "black")
313           ;;'(fill . "black")
314           `(points . ,(string-join
315                        (map offset->point (ly:list->offsets '() coords))))
316   ))
317
318 (define (round-filled-box breapth width depth height blot-diameter)
319   (entity 'rect ""
320           ;; The stroke will stick out.  To use stroke,
321           ;; the stroke-width must be subtracted from all other dimensions.
322           ;;'(stroke-linejoin . "round")
323           ;;'(stroke-linecap . "round")
324           ;;`(stroke-width . ,blot)
325           ;;'(stroke . "red")
326           ;;'(fill . "orange")
327
328           `(x . ,(- breapth))
329           `(y . ,(- height))
330           `(width . ,(+ breapth width))
331           `(height . ,(+ depth height))
332           `(ry . ,(/ blot-diameter 2))
333           ))
334
335 (define (text font string)
336   (dispatch `(fontify ,font ,(entity 'tspan (string->entities string)))))
337
338 (define (utf8-string pango-font-description string)
339   (dispatch `(fontify ,pango-font-description ,(entity 'tspan string))))