[Top][All Lists]

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: SVG in \markup Block?

From: Aaron Hill
Subject: Re: SVG in \markup Block?
Date: Sun, 19 Jan 2020 20:55:22 -0800
User-agent: Roundcube Webmail/1.3.8

On 2020-01-19 7:08 pm, Arle Lommel wrote:
Thanks, Aaron. This is *tremendously* helpful. It gives me a good
start to go on. I noticed that your version rotated the symbol 180
degrees, so if I prefixed all of the numbers with a negative, it made
it appear right (although very large, which was the fault of the
sizing of my original description).

SVG coordinate space is "upside-down" compared to LilyPond's. But you can easily use \scale to flip a \path vertically without needing to adjust the numbers.

Is there a handy list somewhere of how the SVG primitives map to the
native Lilypond commands? I could generate such a list from studying
the SVG specification, but if someone has already documented this, it
would be easier and more efficient to stand on their shoulders.

The Notation Reference lists, under the description of \path, the commands that are supported. It does not match the full list of SVG path commands, however.

Consider the following sketch of an SVG-style path string to \path command list converter:

\version "2.19.83"

#(use-modules (ice-9 regex))

svgPath = #(define-scheme-function (str) (string?)
  "Converts an SVG-style path string into one that the
\\path markup command can handle."
  (define command-regex "([A-Za-z])([0-9 ,.+-]*)")
  (define number-regex "[+-]?[0-9]+([.][0-9]+)?")
  (define commands `(
    (M . (moveto 2)) (m . (rmoveto 2))
    (L . (lineto 2)) (l . (rlineto 2))
    (C . (curveto 6)) (c . (rcurveto 6))
    (Z . (closepath 0)) (z . (closepath 0))
    ; h and v can be easily translated to l...
    (h . (rlineto ,(lambda (x) (list x 0))))
    (v . (rlineto ,(lambda (x) (list 0 x))))
    ; H, V, Ss, Qq, Tt, and Aa all lack underlying support.
    ; They would need to be emulated and/or approximated.
  (define (command cmd args)
    (let ((entry (assoc cmd commands)))
      (if (pair? entry)
        (cons (cadr entry)
          (cond ((procedure? (caddr entry)) (apply (caddr entry) args))
                ((number? (caddr entry))
                  (if (<= (caddr entry) (length args))
                    (take args (caddr entry))
(ly:error "Insufficient arguments: ~a needs ~a, got ~a"
                      cmd (caddr entry) (length args))))
                (else '())))
        (ly:error "Unsupported SVG command: ~a" cmd))))
  (define (match-proc m)
    (let ((cmd (string->symbol (match:substring m 1)))
          (args (map (lambda (m) (string->number (match:substring m)))
                  (list-matches number-regex (match:substring m 2)))))
      (command cmd args)))
  (map match-proc (list-matches command-regex str)))

testSvgPath = #(define-scheme-function (str) (string?)
  #{ \markup \center-column { #str
    \center-align \vcenter
    \scale #'(1 . -1) % SVG coordinate space is "upside-down"
    \path #1 \svgPath #str } #})

\testSvgPath #"M0,0 L8,8 h-8 Z m5,0 l8,8 v-8 z"
\testSvgPath #"M 0,8 C 0,0 6,0 6,6 c 0,-6 6,-6 6,2"
\testSvgPath #"M8 4 h4 v4 h4 v4 h-4 v4 h-4 v-4 h-4 v-4 h4 z"

As I mention in the comments, some of the unsupported SVG path commands could be emulated; but it would be better to implement them properly behind the scenes.

Interestingly, this test code appears to have uncovered a bug in the bounding box logic for \path. The first test case involves two sub-paths. I suspect LilyPond is not considering that closepath (Zz) moves the pen back to the start of the path. rmoveto (m) looks to be using the most recent coordinate instead, and as a result it thinks the shape is taller than it actually is, hence the extra white-space.

-- Aaron Hill

Attachment: svg-string.cropped.png
Description: PNG image

reply via email to

[Prev in Thread] Current Thread [Next in Thread]