Back in January 2020, I released TimeStory 1.7, which extended the set of built-in icons you could use to mark points in time on a timeline. The previous set of shapes had all been implemented as simple Core Graphics drawing code, but to make the process smoother, I switched to using SVGs for all new shapes.
This, of course, required an SVG rendering library1. So, naturally, I wrote one, named Yass. It was pretty minimal; I basically opened my SVGs up in a text editor, opened the SVG2 spec, and implemented exactly what was needed and no more.
Over the last month, I’ve been working towards TimeStory 2.5, with a major new (and often-requested) feature: custom event icons. You should be able to add any image you’d like, in any of several common formats, including SVGs. There are tons of free SVG icons all over the Web that people might want to use. So I set about grabbing a bunch of them and trying them out with my rendering library. And wow, did almost none of them work!
SVG is a very large spec to implement, even if you focus purely on drawing paths and shapes, and ignore things like animation and scriptability. There are a few predefined shapes, plus the ability to compose arbitrary paths from lines, Bézier curves, and arcs. These can all be grouped, transformed, and styled in a few different ways. The structure is all XML, but much of a typical SVG file is non-XML syntax embedded in attributes, including its own path-drawing commands and a bunch of CSS syntax. Most significantly to an implementor, many parts of SVG also have variant forms designed to make it more compact or easier to hand-author.
The net result is that you can draw the same shape, with the same style, in many different ways. And if you look at SVGs around the Web, you’ll find that you have to support quite a few of them—different authoring tools make different choices. For example:
- Every path command has two forms: one for absolute coordinates, and one for coordinates relative to the prior end point.
- When the same path command is repeated, you can omit its name, just stacking up sets of parameters.
- When one Bézier curve follows another, an alternate command lets you choose to omit the first control point if it’s just a reflection of the prior curve’s last one.
- CSS-based attributes tend to have many allowable syntaxes. For example,
translateX(1)all mean the same thing, and are all found in the wild.
- Many values can be given as XML attributes (
<rect fill="red") or CSS inline styles (
<rect style="fill:red; ..."). The SVG2 spec recommends the latter, but it looks to me like the former is more common; it’s certainly easier to parse!
- Path commands allow omission of any unnecessary whitespace. If you see “10-10” where you’re expecting two numbers, it means (10, -10), but if you see it where you’re expecting two Boolean flags and a number (as in the
Acommand), it means (1, 0, -10). That surprised me; I’d initially used one token-splitting pass followed by a parsing pass, but you can’t split on tokens without knowing the command you’re parsing.
I had also been totally missing a couple of path commands which I hadn’t needed at all at first. Of interest is the elliptical arc path segment command, which requests a rotated elliptical arc with a notably complex set of parameters, requiring a bit of math to map into the primitives offered by Core Graphics. (Everything else—lines, cubic curves, quadratic curves—map pretty much directly.)
After some iteration, Yass now correctly renders every SVG I’ve tried from FontAwesome, from GitHub Octicons, from Material Design Icons, and a bunch I’ve tried from Web resources like Flaticon. It’s still far from implementing the full SVG and CSS specs, but it seems to me like the current set is a good match for what common authoring tools use. It was a fun project, with a very visual and satisfying payoff for each fix.
It looks solid enough to tag Yass at version 1.2 and build the TimeStory 2.5 release atop it. (I hope to have it out soon; it contains more changes than just this, of course.) The good news is, even though SVG is a large standard, it’s still possible to handle a lot of what’s out there with a fairly simple implementation.
Since Xcode 12, you’ve been able to put SVGs into asset catalogs; I’ve never used it, as it requires a minimum of Catalina, and TimeStory still supports Mojave. In any case, I knew I was ultimately going to need to add user-imported SVGs, which makes asset catalogs irrelevant. ↩