Making Software is Dan Hollick’s in-progress illustrated book on how software works under the hood, and its chapter How to make a font. spends six thousand words walking from vector outlines to hinting. On the surface a font is just a collection of vectors for each character, and making one is just drawing those vectors, letter by letter. But drawing the glyphs turns out to be the easy part — the hard part is making hundreds of them agree with each other across sizes, cases, and weights.
The chapter splits naturally into three layers: how machines store and render fonts, the vocabulary designers use to describe letterforms, and where the real craft actually goes. These notes follow the same three layers.
The machine side: a font file is a database
Terminology first. A typeface is the whole family — Garamond, across every weight and style it ships in — while a font is one specific instance, like Garamond Roman at 12pt. Everyday usage blurs the two, but type designers care about the distinction.
The more counterintuitive part is technical: a font file is really a structured database made of tables. What we think of as “the font” — the glyph outlines — occupies exactly one of those tables. The rest hold everything else that makes text render correctly: spacing metrics for how much room each character takes up, kerning tables that nudge specific letter pairs, hinting instructions that keep small sizes crisp, and metadata like the name, license, and supported languages.
Forty years of format evolution
- PostScript Type 1 (Adobe, 1984): described outlines with cubic Bézier curves and became the professional print standard. Limited to 256 glyphs per file — nowhere near enough for many languages — and needed two separate files to work.
- TrueType (Apple, late 1980s): switched to quadratic Bézier curves — one fewer control point (three instead of four), simpler, but needing more points to hit the same shapes. Its real selling point was built-in hinting, giving designers precise control over how glyphs snapped to the pixel grid at small sizes. Microsoft adopted it for Windows and it took over.
- OpenType (Microsoft + Adobe, late 1990s): a unified container that can hold either TrueType or PostScript outlines. Up to 65,536 glyphs per file — enough for full Unicode coverage across dozens of languages — plus layout tables that bake ligatures, small caps, stylistic alternates, and contextual substitutions directly into the font file.
- Variable fonts: the latest step, compressing an entire design space into a single file. Instead of shipping separate files for Light, Regular, and Bold, a variable font defines one or more axes — weight, width, slant, optical size — and interpolates between masters at runtime, at any point along those axes. Especially good for the web, where five weights used to mean five separate network requests.
Opening up an OpenType file
The outline table’s format depends on the font’s “flavor.” TrueType flavor (.ttf) uses two tables, glyf and loca. glyf is a series of points describing the glyph with quadratic Béziers: each point carries an x,y position and a flag marking it on-curve or off-curve (a control point). The rule is clever — two consecutive on-curve points make a straight line; on-curve → off-curve → on-curve forms a quadratic Bézier segment; and when two off-curve points land next to each other, the rasterizer inserts a virtual on-curve point at their midpoint, so not every anchor point has to be stored explicitly. loca just stores the byte offset of each glyph, marking where one glyph’s data ends and the next begins.
OpenType flavor (.otf) uses a CFF or CFF2 table instead. Rather than points, it stores a sequence of drawing operators (rmoveto, rlineto, rrcurveto) that pull numbers off a stack to move the pen — very close to SVG path data. Because the numbers are relative, the result is smaller and compresses better. CFF2 is the modern variant built for variable fonts: it stores extra offsets for how the base glyph shifts with weight, width, or optical size, so rendering processes the base points first and then layers the offsets for the current settings on top.
A few other tables worth knowing:
cmap: maps Unicode code points to glyphs. Type an A, the computer reads it asU+0041, and looks up the matching glyph in this table.head: the file header — version, global bounding box, and the grid scale expressed in UPM (Units Per em). UPM sets how many font units make up one em square; CFF fonts typically use 1000, TrueType typically 1024.hhea: global metrics for horizontal text — maximum ascender/descender, recommended leading, and Max Advance Width for the widest glyph in the file; vertical text gets an optionalvheacounterpart.hmtx(vmtxfor vertical text): per-glyph spacing data, defining Advance Width, LSB (left sidebearing), and RSB (right sidebearing) for every glyph. Advance Width is basically each letter’s bounding box, used to allocate space when laying out a line; RSB is usually derived from the width and the LSB.- There’s also
maxp, which stores memory requirements,name, which holds textual info like the font’s name, and theOS/2table — named after the long-gone OS/2 operating system from the late 1980s — which now stores weight and width ranges, family style (Serif, Sans-Serif, Script, Monospace), x-height, cap height, and a set of cross-platform metrics that paper over rendering differences between Windows, macOS, and the web.
Text shaping: from string to positioned glyphs
Laying out text on a computer runs through a chain of steps collectively called text shaping. Before any glyph gets drawn, the text is sliced into runs — segments sharing the exact same font, size, color, and language. Any change (an italicized word mid-sentence, say) ends the current run and starts a new one. Characters split into strong and weak: strong characters belong firmly to one alphabet (Latin “A”, Greek “Δ”) and tell the renderer which language it’s dealing with; weak characters — spaces, digits, punctuation — belong to no language and never break a run. Runs are treated as fully isolated from each other: kerning never crosses a run boundary, so a Greek letter and a Latin letter split across two runs simply won’t be spaced against one another.
Once sliced, each run goes through four steps:
- Lookup: convert the character’s Unicode value (A is
U+0041) into a Glyph ID viacmap. - Substitution (
GSUB): swap glyphs based on context — merging an adjacent “f” and “i” into a single “fi” ligature, for instance. - Positioning (
GPOS): nudge the spacing between specific letter pairs — sliding an “o” left so it tucks under a capital T’s crossbar. This is context-aware kerning. - Render: the first three steps only ever moved invisible, empty boxes around. This is where each Glyph ID finally fetches its outline data and draws it at scale, with each glyph settling into its box according to its own sidebearings.
With a variable font, all that changes is that the weight gets pinned down before the text is even sliced into runs. The whole pipeline maps neatly onto how a browser lays out HTML and CSS: Unicode text is the content, font and color choices are the CSS, and the four-step process is building layout boxes and painting them to the screen.
Hinting: bending to the pixel grid
Glyphs are drawn on a grid of roughly 1000 units, and at small sizes that whole coordinate system gets squeezed into a handful of pixels. At 11px, a stem 100 units wide works out to just over a pixel, and it almost never lands cleanly on the pixel grid: one stem of an H might rasterize into one crisp column of pixels while the other smears into two gray ones, leaving the letter blurry and lopsided.
Hinting is a set of instructions stored in the font that distorts outlines at specific sizes so they snap to the pixel grid before rasterizing. The goal isn’t to preserve a letter’s true shape — it’s to betray that shape slightly in exchange for crispness: rounding stems to whole pixel widths, forcing both stems of an H to match, keeping the x-height level across a whole line.
The two flavors handle this very differently. TrueType hints are essentially tiny programs: bytecode stored in the fpgm, prep, and glyf tables, run by a little virtual machine inside the rasterizer, giving the designer pixel-by-pixel, size-by-size control. That control really mattered in the era of low-resolution CRTs — Verdana and Georgia were hand-hinted pixel by pixel, a big reason they read so cleanly on screens of the time. CFF takes a declarative route: instead of choreographing the exact result, it just marks the position and width of stems, plus a set of horizontal alignment bands called blue zones (for the baseline, x-height, and cap height), and leaves the actual grid-fitting to the rasterizer. Hinting matters far less today — high-DPI screens give stems several pixels of width to begin with, anti-aliasing smooths over the rest, and macOS ignores most hinting and just renders the faithful outline. Most fonts today are hinted automatically by font editors or tools like ttfautohint, and hand-hinting has become close to a lost art.
The design side: the language of letters
That’s the machine side. Type design is a genuinely old craft with its own vocabulary, most of it inherited straight from physical, lead-type typesetting: font comes from the French *fondre*, “to melt/cast,” and originally meant a complete cast of a typeface at one particular size. Each letter was called a sort, stored in a case — capitals in the upper case, the rest (used more often, kept closer to hand) in the lower case, which is exactly where uppercase and lowercase get their names. Typesetters spaced out rows with thin strips of lead, which is why line spacing is called leading; a kern was a piece of metal overhanging the letter’s body to compensate for spacing, which is why adjusting letter spacing is called kerning.
Size and proportion
Every glyph is drawn inside a bounding box called an em, scaled by that same UPM value (usually 1000), and everything that follows is measured against this coordinate system. Set the font size to 14px and it’s the em box that occupies those 14 pixels of height; bump the size up and the box just scales — the coordinates themselves don’t change, because they’re relative. The origin isn’t in a corner either: it’s set by the baseline, which anchors the alignment for the whole typeface and, for Western scripts, usually sits about three-quarters of the way down the em grid.
Above the baseline, a typeface needs a series of height lines defined in order:
- Cap height: set using the flat-topped H (E or I work too). It isn’t a hard ceiling — round letters (O, C, G, Q, S) and pointed ones (A, V) routinely overshoot both cap height and the baseline slightly, a move called overshoot, done for optical rather than mathematical alignment.
- x-height: set using the x (occasionally o or n), and possibly the single most consequential decision in making a typeface — it determines the font’s apparent visual size, usually set at 60–75% of cap height. A taller x-height reads more legibly and looks bigger at the same point size: Zuzana Licko’s Mrs. Eaves for Emigre used a famously small 57%, and the later Mrs. Eaves XL raised it to 72% — the difference at identical sizes is striking. But the gains taper off: push it too high and ascenders and descenders become hard to tell apart. One study puts the optimum at around 0.3° of visual arc (arc depends on both size and distance; the experiment used 40cm), beyond which reading speed starts dropping.
- Ascender / descender: the alignment lines for the parts of lowercase letters that reach above the x-height or below the baseline. Ascender height is set with clean-stemmed letters like h, d, b, l; descender height with p, q — curved letters like f and g overshoot these as usual.
- There’s also figure height for numerals (usually equal to cap height), small cap height (somewhere between cap height and x-height), accent height, and the baseline shift and scale used for superscripts and subscripts.
On width: proportional fonts give every letter a different width, monospaced fonts give them all the same one. The total space a letter takes up is its advance width = LSB + glyph width + RSB. A sidebearing is like a margin built into each letter to keep distance from its neighbors — don’t confuse it with kerning. The smaller the x-height, the more sidebearing lowercase letters typically need to stay legible; condensed fonts, on the other hand, narrow everything across the board.
Weight, contrast, and stress
Weight is intuitively just how thick the strokes are, but there’s no standard for how thick regular, semi-bold, or bold actually should be — or even whether those are the right names. The closest thing to a standard lives on the web: CSS pins regular at 400 and bold at 700. Beyond that, it’s a free-for-all.
Weight can still be quantified, though. Type designer Charles Bigelow measures it as the ratio between x-height and vertical stem thickness — a typical regular sits around 1:5–1:6. On the em grid, if x-height is 500 units (0.5em), a regular stem comes out to about 100 units; neighboring weights need to differ by 1.3–1.5× in stroke thickness to read as distinct, roughly 130 units for the step up and 70 for the step down. That’s a linear scale; another designer, Luc(as) de Groot, argues for a non-linear progression instead, more like an easing curve (the original illustrates this with Inter’s weight sequence). None of this is a hard rule, of course.
Vertical and horizontal strokes are usually not the same thickness, and the gap between them is called contrast: a big gap is high contrast, a small one is low contrast, and equal thickness everywhere is called monolinear. But even monolinear typefaces vary stroke thickness slightly by direction and curvature, because of the thickness illusion: a horizontal line looks thicker than a vertical or diagonal one of identical weight, so horizontal stems are usually shaved down a bit to compensate. The same correction applies to bowls (the closed, curved strokes): the thickest part of the curve needs to be thicker than the vertical stem, and the thinnest part thinner than the horizontal stem, for it to read as consistent. Contrast describes how big the difference is; stress describes which direction it happens in — a holdover from calligraphy, where a pen’s nib varies stroke thickness with pressure and speed. Within one typeface, the stress axis should stay consistent, as if every letter came from the same pen.
Optical correction shows up everywhere. Double-story letters (E, B, S) look too low if their crossbar sits at the true mathematical center, so it gets nudged up; and because that shift is obvious on E and B, the lower story is then widened slightly to balance it back out.
The anatomy of a letter
Every part of a letter has its own name. A serif is the little foot at the end of a stroke: one whose horizontal segment curves inward to counteract an optical illusion is called a cupped serif (EB Garamond has these). By shape, serifs are further classed as bracketed (joined to the stem with a curved support), wedged (tapering toward the end), slab (uniform thickness), or hairline (uniform but very thin). A stroke ending that isn’t a serif is a terminal, which can carry embellishments like a teardrop, ball, or flare; a vertical serif that appears only at the top of a letter is a beak, a horizontal serif to one side of a stem (like on the numeral 1) is a flag, a stroke that dips below the baseline is a tail, and a decorative flourish is a swash.
The fully or partially enclosed space inside a letter is a counter — the one in a lowercase e is specifically an eye, and the lower loop of a double-story g is a loop. There’s a long list of others too: the dot on an i or j is a tittle, the curved top of an f is a hook, the curved joint in n or m is a shoulder, the diagonal stroke in k or R is a leg, the horizontal stroke in E is an arm, and the curved spine of s or 8 is, fittingly, a spine. A horizontal stroke joining two stems is a crossbar, and where diagonal strokes meet at the bottom is a vertex, at the top an apex. The original has an even longer glossary — this keeps only the most common ones.
The craft side: negative space and getting started
Spacing: negative space is the protagonist
A large share of the work in making a typeface has nothing to do with drawing glyphs — it’s tuning the negative space between them. Spacing affects legibility more than you’d expect: too loose and words develop whitespace rivers that are hard to parse; too tight and letters blur into each other. The ideal is equal optical space between every letter pair, so a line reads as an even gray at arm’s length and a predictable, stripey rhythm up close. Letters with more negative space (usually tied to counter size) need tighter spacing to compensate — the trade phrase for this is “spacing matches counters.”
There are only two tools for this: sidebearings, built into every letter, and kerning, an extra correction for specific letter pairs (like AV) that ideally only gets used once sidebearings have failed. Setting sidebearings letter by letter is tedious enough that Walter Tracy worked out a method: group letters by shape and share values across the group. Start with H and O — one extremely straight, one extremely round — adjusting both while testing combinations like “HHHOOHHH” and “OOOHHOOO” until the stem rhythm looks even and unified, then repeat with lowercase o and n. Every other letter borrows from whichever shape is closest: B takes its left sidebearing from H (flat edge) and its right sidebearing from O (round edge); diagonal letters like A or w break the formula entirely and just get adjusted by hand.
You verify it by setting every letter next to every other letter and looking for breaks in the visual rhythm. There’ll always be pairs sidebearings can’t fix alone — Ti, for instance, where the i has to tuck in under the T’s overhang. That’s what kerning is for: a spacing offset for a specific letter pair, stored in the GPOS table and pulled up when a run gets laid out. Hand-kerning every pair would be an enormous amount of work, so OpenType supports class-based kerning instead: letters with similar shapes get grouped by edge — D, E, and F all share a straight left edge, so they can share the same kerning rules against whatever comes before them.
Where to start drawing a typeface
Once you actually get to drawing letterforms, the original admits it’s hit the “draw the rest of the owl” part of the process and has to speed through it. Designers don’t work from A straight through to Z — they start with a handful of control letters, forcing themselves to lock in decisions about stress, contrast, proportion, and personality that will carry over to every other glyph. For capitals, that’s usually O and H/E.
O matters enormously: it establishes the stress (oblique or vertical), contrast (thick-thin ratio), and proportion (wide circle or narrow oval) that every other round letter has to follow — C is roughly an O with a slice cut out, G adds a crossbar, Q adds a tail, D closes off the right side with a stem. Get the O wrong and the whole typeface falls apart. Some designers use H as the square reference, but E is more useful — it comes with its own terminal and is narrower than O or H, which helps establish the proportion system. E’s three horizontal arms are often deliberately uneven: the bottom arm longest, the top medium, the middle shortest, with the middle arm nudged slightly above true center. Capitals then get worked through in structural groups: round (O, Q, C, G, S), square (E, F, H, I, L, T), diagonal (V, A, W, X), plus the round-square combination (D, B, P, R) and diagonal-square combination (M, N, K, Z, Y).
Lowercase usually starts from a, e, g, n, and o, for the same reason as O and E. The classic test word “hamburgefontsiv” packs in most of the high-frequency lowercase letters and exposes how different shapes interact with each other. o is the foundation for every bowl-shaped letter (a, c, e, p, d, b, q, g), which all need to keep the same contrast, stress, and bowl proportions: o has the widest bowl, while c and e, being more open with more negative space, need to run narrower; where a bowl meets a stem there’s usually a small notch left, called a relief. n is the foundation for m, h, and u, running slightly narrower than o; r is like n but with its notch pushed even lower. The hardest to classify is the double-story lowercase a: its arch can borrow the shoulder from n, but the way it finishes is different, and the height of its lower bowl typically sits at 55–65% of the x-height.
A few things worth remembering
- A font file is a database, not a pile of shapes. Outlines occupy just one table — it’s
cmap,hmtx,GSUB/GPOS, andOS/2that let text be looked up, laid out, and rendered consistently across platforms. - Two curves, two technical lineages. TrueType uses quadratic Bézier (
glyf/loca), OpenType/CFF uses cubic Bézier plus SVG-like drawing instructions; variable fonts interpolate between masters, packing an entire design space into one file. - Typesetting is a pipeline. Text shaping slices runs first, then routes each one through
cmaplookup →GSUBsubstitution →GPOSpositioning → rendering — closely mirroring how a browser lays out HTML and CSS. - Type design is the art of visual deception. Overshoot, thickness-illusion compensation, crossbars nudged upward — mathematical alignment gives way to optical alignment almost everywhere you look.
- Spacing takes more effort than the letterforms. Negative space governs legibility: set the baseline with sidebearings grouped by shape, then clean up exceptions with kerning. Hinting used to be the lifeline for small-size clarity; high-DPI screens and anti-aliasing have mostly retired it.