Using path expressions to override stencils

The stencil expression path can be used to override stencils for any printed object. The advantage of using path instead of embedded-ps is that path is supported by both the PostScript and SVG backends of LilyPond, and uses the same syntax.

There are six commands available to use in a path expression, and all commands use prefix notation. The six commands are moveto, rmoveto, lineto, rlineto, curveto, and rcurveto. Note that the commands that begin with r are the relative variants of the other three commands.

The commands moveto, rmoveto, lineto, and rlineto take two arguments; they are the X- and Y-coordinates for the destination point.

The commands curveto and rcurveto create cubic Bézier curves and take six arguments; the first two are the X- and Y-coordinates for the first control point, the second two are the X- and Y-coordinates for the second control point, and the last two are the X- and Y-coordinates for the destination point.

This snippet also shows how to create a ‘filled’ stencil using path, adding a Scheme function to automatically resize custom stencils when individual staves are resized.

Note that replacing the stencil of a grob doesn't change its dimensions. In case you experience strange spacing you have to adjust the X-extent and Y-extent properties. If you experience cropping, you might use the \with-true-dimensions trick shown below.

See also snippet Generate special note head shapes.

\version "2.24"

%{
  Note: since SVG path data needs an "M" or "m" instruction at
  the beginning to start a new subpath, every path expression
  must begin with "rmoveto" (or "moveto") to work with the SVG
  backend.
%}

customClefStencilOne =
#(ly:make-stencil
  '(path 0.2
         (rmoveto 0 0
          rcurveto 0 0.75 1 0.75 1 0
          rcurveto 0 -0.75 -1 -0.75 -1 0
          rcurveto -1 0 -1 1.5 -0.5 1.5
          rmoveto 0.5 -1.5
          rcurveto -1 0 -1 -1.5 -0.5 -1.5
          rmoveto 0.5 1.5
          rmoveto 1 0
          rcurveto 2.5 0 2.5 4 4 4
          rmoveto -4 -4
          rcurveto 2.5 0 2.5 -4 4 -4))
   (cons -0.5 1)
   (cons -3 5))

% A filled custom stencil.
customClefStencilTwo =
#(ly:make-stencil
  ;; Set path line thickness to 0.1.
  '(path
    ;; Set path line thickness.
    0.1
    ;; Path coordinates.
    (moveto 0 0
     curveto 0 1 0.7 2.5 1.5 1.5
     lineto 1.5 -3
     closepath)
    ;; Path cap style.
    round
    ;; Path join style.
    round
    ;; Is path filled?  `#t` or `#f`.
    #t)
  ;; Horizontal extent.
  (cons 0 1.5)
  ;; Vertical extent.
  (cons -3 2))

% A Scheme function to automatically resize a custom stencil
% when an individual staff is resized (uses `font-size`).
scaleCustomClefStencilTwo =
#(lambda (grob)
   (let* ((sz (ly:grob-property grob 'font-size 0.0))
          (mult (magstep sz)))
     (ly:stencil-scale customClefStencilTwo mult mult)))

customClefOne = \override Staff.Clef.stencil = \customClefStencilOne
customClefTwo = \override Staff.Clef.stencil = \scaleCustomClefStencilTwo
normalClefs = \revert Staff.Clef.stencil

music = \relative c' {
  \hide Staff.TimeSignature
  \customClefOne
  \clef "alto"
  c1 g1
  \customClefTwo
  \clef "bass"
  c1 g1
  \normalClefs
  \clef "alto"
  c1 g1
  \clef "bass"
  c1 g1
}

\markup \with-true-dimensions % work around a cropping issue
\score {
  <<
    \new Staff \with {
      fontSize = #-3
      \override StaffSymbol.staff-space = #(magstep -3)
      \override StaffSymbol.thickness = #(magstep -3)
    } \music

    \new Staff \music

    \new Staff \with {
      fontSize = #2
      \override StaffSymbol.staff-space = #(magstep 2)
      \override StaffSymbol.thickness = #(magstep 2)
    } \music
  >>
}