Jump to content

Using path expressions to override stencils

From LilyPond wiki

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
  >>
}