Think of a font as a stack of layers, each referring only to the one above it. You can't do layer N properly until layer N−1 is settled. Let's walk down.
Before anything, pick units per em (UPM). Usually 1000 or 2048. Every coordinate in the entire font is in these units. The em is a conceptual square; at font-size 16px, 1 em = 16px, and 1 UPM unit = 16/1000 px.
Everything below is measured in UPM. Change it late and every number in the font gets rescaled. Pick once, early.
Decide what is allowed to vary. Each axis is a named dimension with a min, max, and default:
wdth, slnt, custom axesThat's it — your font lives inside an N-dimensional rectangle. Every glyph, every metric, every kern value will exist at every point in this rectangle, by interpolation.
A master is a coordinate in the designspace, e.g. (100, 16), where you commit to hand-authoring everything. At each master you provide outlines, widths, anchors, a full kerning table, vertical metrics, and export-level OS/2 values — for every glyph.
Minimum masters = enough to span the space. One axis = 2 masters (the ends). Two axes = 4 masters (the corners). Extra masters correct non-linearity, so 400 isn't just a blend of the extremes.
Each master is a full parallel copy of the font. That's the cost of interpolation.
Inside each master, set horizontal lines that act as reference rails for drawing:
These are guides for you, the designer. They don't render directly; they exist so outlines align across glyphs and across masters. Keep them pinned so weights and optical sizes stay compatible.
Finally, the letter itself. For each glyph × master: nodes, curves, components. This is the part everyone thinks of as font design, but it's actually layer 4. The canvas above was drawn in the previous three.
Invariant: all masters must have compatible outlines for a glyph — same contours, same nodes, same order. Otherwise interpolation breaks. This is the single biggest ongoing chore.
Per glyph × master, three related numbers: the left sidebearing (x=0 to the leftmost point), the right sidebearing (rightmost point to the advance), and the advance width — their sum, the total space the glyph consumes.
Sidebearings are rhythm. n n n n should look evenly spaced. Tune n once, then derive other letters from it via metric keys. Spacing scales with weight — Black needs tighter sidebearings than Thin, because thicker stems occupy more room. That's why spacing is per-master.
Sidebearings give average spacing. Some pairs still look wrong: AV, To, Yo — the shapes lean into each other. Kerning is an adjustment applied only when two glyphs are adjacent.
It lives per master, and interpolates across the space — a Thin may barely kern, a Black may need −400 on AV. Groups let you kern classes instead of individual pairs, so you don't write ten thousand rules.
Substitutions and positioning that fire at render time:
Written in feature code, a tiny DSL. Often auto-generated from naming conventions, then hand-tuned.
An instance is a pin in the designspace with a name: Regular lives at (400, 16). No drawings, just a coordinate and a label. Exports produce one static file per instance, or the variable font uses them as named stops.
Separately, export-level metrics decide line height in apps. typoAscender, hheaAscender, winAscent — apps add ascent + descent + lineGap for the default. If a font has weird line spacing in Word, you fix it here, not in the drawings.
Work top-down, edit bottom-up.
Establish layers 0–3 before you draw seriously. Then spacing. Then kerning. Every layer assumes the one above is stable.