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