The per-character advance table, greedy wrapping with pre-wrap semantics, and the size ladder that fits text to its box without ever clamping.
text.ts replaced ceil(len / 18) * size, which overflowed the artboard on long hooks. Canvas measureText was rejected deliberately: "a layout that differs between test and production is worse than one that is slightly conservative in both."
| Class | Characters | Advance |
|---|---|---|
| space | " " | 0.26 |
| thin | iljI|!.,;:' ` `` | 0.28 |
| narrow | ft()[]{}/\-r | 0.36 |
| wide | mwMW@ | 0.86 |
| uppercase | A–Z | 0.68 |
| digits | 0–9 | 0.56 |
| everything else | 0.53 |
FAMILY_SCALE = { sans: 1, display: 1, serif: 0.97, mono: 1.15 }, MONO_ADVANCE = 0.6, SAFETY = 1.02 — a deliberate 2% over-estimate, because predicting one line too many costs a slightly smaller font and predicting one too few puts text off the slide.
Greedy, with pre-wrap semantics. Split on \n (hard breaks), then on /(\s+)/ keeping whitespace runs as tokens. If a word does not fit, flush the line; if the token was whitespace, drop it (browsers drop leading whitespace on a wrapped line); an over-wide single word is broken mid-word by scanning for the longest prefix that fits.
lineCount is Math.max(1, lines) — never zero.
fitToBox sorts the ladder descending regardless of input order and returns the first size where lines × fontSize × lineHeight <= maxHeight. If none fit it returns the smallest with overflows: true and its real height. It never clamps; callers must.
ladder(max, min, steps = 12) produces evenly spaced integers, deduped. ladder(104, 40) → 104, 98, 92, 87, 81, 75, 69, 63, 57, 52, 46, 40.
clampY pins an oversized block to the top of its region rather than centring it, so the start stays readable.
This guarantee currently holds for sans and serif only. See defect D1 — it is the most consequential defect in the reference.