Skip to content
Matthew Blode
Writing
Product & craft3 min read

What it takes to turn separately drawn static weights into one variable font

Going from a variable font to statics is one command in a loop. Going the other way means making hundreds of glyphs structurally identical across every weight before anything can merge, and almost everything written about variable fonts answers the easy direction.

Based on building static-to-variable, an open-source CLI for converting static font families into variable fonts.

Converting a font counts as modifying it, and most commercial licences forbid modification outright. Open licences like the SIL OFL, which covers most of Google Fonts, allow it. Your own fonts are fine.

That’s the first thing to check, because the pipeline works just as well on a font you have no right to convert.

A variable font stores one set of outlines plus deltas that move each point. For that to work, every weight needs the same points, in the same order, with the same winding, starting in the same place. I built static-to-variable to get there from a folder of separately drawn weights.

Fonts drawn as independent statics have no reason to satisfy any of that, and mostly don’t. The designer drew a Bold. They didn’t draw a Bold whose e has the same node count as the Regular’s e.

The hard direction

The merge was never the problem. fontmake does the merge. What takes the time is making 700-odd glyphs structurally identical across weights without changing how any of them look.

Some of what breaks is obvious once you see it. A glyph exists in the Light and not in the Black. An outer contour winds clockwise in one master and counter-clockwise in another. Both are cheap to detect and cheap to fix.

The ones that cost me days looked fine. B has two counters, and at ExtraBlack they swap places. Same contour count, same point counts, same winding, so every naive structural check passes. Then it interpolates one counter into the other and the letter collapses halfway along the axis.

Topology changes too. A ¢ loses a contour at heavy weights because the counter fills in, so the lighter masters need a near-zero synthetic ring for the hole to grow out of rather than appear from nowhere.

Four thousand rotations

The worst class is start points, because a wrong one is invisible to structural comparison. An o is all curves from any starting node, so the operation sequence reads the same no matter where the contour begins. Compare the sequences and nothing looks wrong. Interpolate it and node maps to the wrong node.

One 8-weight family needed 4,242 start-point rotations in the roman alone. That is not a handful of broken glyphs. That is the normal condition of independently drawn statics.

Where node counts disagree the contour has to be resampled, anchored at the corners so the shape doesn’t drift. Only the contours that disagree get touched, because resampling is lossy and the less of it the better.

Before any repair, that family reported 27 problem glyphs and 89 issues across about 750 glyphs. Which sounds manageable until you notice two things. A single broken glyph fails the whole build, and the 4,242 rotations had already happened silently before the report was generated.

After the rebuild pass, the same family reported 755 glyphs taken straight from the donors and 641 reconstructed. Nearly half the glyph set needed structural work to produce a font that, visually, is identical to what the designer drew.

A frozen cent sign

Every reconstruction has to be checked, because a bad one is worse than no reconstruction at all. Structural sameness isn’t enough on its own, and neither is area: a twist that folds a contour through itself conserves ink.

The threshold I landed on is 10% area deviation. Good reconstructions come in under 6% and genuinely deformed ones sit above 20%, with almost nothing in between.

A glyph that fails every gate gets frozen to the default master’s outline. It renders correctly at every weight, it just doesn’t vary. Turns out that’s the tool working rather than the tool failing. A frozen ¢ is fine. A ¢ that folds inside out at 550 is not.

The loop around all of it runs up to 40 times: build, instantiate the font at every master and every midpoint, freeze whatever collapses, rebuild, until it converges.

Then you open it in Figma, where variables bind named styles rather than axes, so a variable font is effectively a static font with extra steps.

It took a week to get a cent sign to sit still.

Get new posts by email

New writing on shipping with AI agents, sent when it is published. Nothing else, and you can leave any time.