single-stroke glyphs
rendering maths as animated pen strokes
For a number of years now I’ve wished to be able to typeset single-stroke glyphs, including maths. This would let us turn
$$
\mathbf{G}_{\mu \nu }+\Lambda \mathbf{g}_{\mu \nu } + i\hbar {\frac {d}{dt}}\vert \Psi (t)\rangle =\kappa \mathbf{T}_{\mu \nu } +{\mathbf{\hat H}}\vert \Psi (t)\rangle
$$
into (wait for it)
or
or
etc.
What do I mean by single-stroke glyphs?
Single-stroke fonts define glyph shapes with, well, a single stroke, as opposed to a filled outline (closed polygons) that are used in practically every font format.
They are a niche topic in typography with specific use-cases (pen-plotters, laser cutters, etc.) and basically a hack around existing font technologies and software. ttf
, otf
, etc don’t really support it; only svg
fonts, which were called deprecated before they found widespread use, could in principle support it, but in practice SVG interpreters tend to automatically close the paths if left open. Even Metafont switched from pen strokes to filled outlines early on (not to mention its focus on raster output).
What applications could this possibly have?
I can think of 3 compelling use-cases:
Pen-plotters – Drawing normal text is already possible with ad hoc conversion software, turning letters into their single-stroke equivalent, but nothing at all is available for maths (and other symbols). The ability to use TeX glyphs and layout would open interesting new possibilities.
Animated text rendering – one successful idea to make the virtual whiteboard experience more engaging with online lectures is to animate the slides as if someone was drawing or writing it in real time. This is widely used, e.g. in the RSA animate series or minutephysics – albeit with different technologies and professional artists. Some technologies are available for standard text (most proprietary, unfortunately) but again no attention has been given to content requiring math and complex typesetting.
Short of hand-drawing every single stroke every time and recording the process, the ideal solution would be to implement something like this, where the single-stroke path defines a SVG mask to progressively reveal the text as if being written. Attempting this without a single-stroke font is hopeless, as the animated “pen” passes over each glyph multiple times following the outline, giving a very unnatural effect.
- Path deformation – there’s plenty of artistic potential in being able to alter the shape of letterforms programmatically say, like this
With outlined glyphs this is a bit problematic because the shape cannot be easily transformed without losing its integrity (both sides of a stroke need to remain connected in some way)
With single-stroke fonts this is no longer a problem, and the path can be deformed, calligraphic brushes can be applied, noise added, etc. This is commonly done with roman characters, but never with maths characters, for sheer lack of availability.
What’s the technical difficulty?
Pretty much every font-related tool I find assumes, explicitly or not, filled outlines. There does not seem to exist a good strategy to convert filled outlines to strokes (though I’m sure some clever AI could get most of the way), especially if one is also interested in a natural (calligraphic) drawing motion.
How about dvisvgm?
This seemed promising, but no. The paths are still outlines.
How about Metafont/Metapost?
Again, seemed like a promising route, but no. The paths are still outlines (or rasters).
The Mathjax route
After quite a few experiments and research, the best strategy I could find would be to replace the glyphs used in Mathjax’ SVG output with single-stroke ones, manually drawn for this purpose. Since Mathjax doesn’t rely on the deprecated SVG font format like dvisvgm but instead on <defs>
’d and <use>
’d paths there is no risk that the open paths be closed by the rendering software/browser. If the new glyphs closely follow the original shapes (the current ones) all the spacing, kerning etc. attributes don’t need to change much; it’d be a direct swap of the glyphs only.
Creating strokes
There are a lot of characters, 2286 if I’m counting it right, so this is going to be a time-consuming process.
I currently have the following workflow:
- export original svg paths for each glyph as individual files, as a locked background layer. Wrap them in a
<g>
element withtransform scale(1,-1)
so that the y coordinates are shown in a natural way. - use a SVG editor — I’m going with Boxy-svg (ideally it would run on my ipad to be able to use a pen!). I draw new path(s) on top of the original, in a separate
<g>
element (also with flipped y axis) - Once I have a complete collection of SVG files I extract the paths from the second
<g>
element and collapse them in the required format
This brings me paths that I can store in a dictionary, e.g. in JSON,
[
{
"code": "0x1D5BA",
"box": [0.75, 0, 0.55],
"p": "73.506 383.217 C 152.169 424.383 267.286 454.607 321.478 389.986 C 387.739 310.973 355.954 245.726 360.463 2.578 M 364.21 232.827 C 364.631 228.618 174.57 206.265 165.062 203.095 C 113.857 186.035 63.172 162.238 87.734 88.6 C 123.86 -19.804 283.66 37.178 364.21 108.219-"
},
{
"code": "0x1D5BB",
"box": [0.75, 0, 0.55],
"p": "118.756 691.217 C 118.756 625.993 114.967 448.997 115.386 385.332 C 115.867 312.271 115.157 131.007 115.157 9.574 M 118.756 350.996 C 151.33 373.1 227.031 406.786 239.519 411.291 C 310.037 436.885 428.123 414.249 445.688 272.775 C 467.071 100.551 341.059 -83.796 118.756 110.375-"
},
{
"code": "0x1D5BC",
"box": [0.75, 0, 0.55],
"p": "400.34 383.958 C 224.131 474.509 69.728 406.822 71.978 225.315 C 74.977 -16.602 311.063 1.627 409.603 77.698-"
},
[...]
{
"code": "0x1D5D3",
"box": [0.75, 0, 0.55],
"p": "42.1 410.509 L 204.448 409.895 L 366.795 409.282 L 212.203 220.946 L 57.612 32.61 L 229.886 33.041 L 402.16 33.472-"
}
]
These definitions are used by an ad hoc Mathjax hook that maps characters to the right paths. See example at this test repo. With the glyphs at hand, we go from
$$
\mathbf{G}_{\mu \nu }+\Lambda \mathbf{g}_{\mu \nu } + i\hbar {\frac {d}{dt}}\vert \Psi (t)\rangle =\kappa \mathbf{T}_{\mu \nu } +{\mathbf{\hat H}}\vert \Psi (t)\rangle
$$
\[ \mathbf{G}_{\mu \nu }+\Lambda \mathbf{g}_{\mu \nu } + i\hbar {\frac {d}{dt}}\vert \Psi (t)\rangle =\kappa \mathbf{T}_{\mu \nu } +{\mathbf{\hat H}}\vert \Psi (t)\rangle \]
to
Let me know if you’d like to contribute (particularly: drawing paths, or making an online tool to draw them collaboratively).