Skip to content

SheetArticle

Measure68ch · bone, not white

Filed underTypography · Tooling · Testing

Published2026-08-29

29 August 2026 · 10 min

Eight typefaces gave me the same number, and I read it as consistency

I published a finding about vertical Japanese text, backed by measurements in three browsers. Every one of those measurements was taken against a font that had never loaded, and the checks I ran to confirm it shared the same blind spot. Here's how a control that can't fail agrees with you three times in a row.

The tool I've been building puts web pages on a baseline grid, and the whole reason it needs to exist is that every font hides asymmetric space above its capitals. The browser splits leftover leading evenly, so it centres the box rather than the letters, and where the baseline lands depends on the ascent, which lives in a table inside a font file that the designer has never opened.

Then I pointed it sideways. Japanese, Chinese and Korean have been set vertically for most of their history and still are in novels and newspapers, so I wanted to know whether the same correction applied turned ninety degrees.

It doesn't, and the reason is better than the answer. In vertical writing the dominant baseline is the central one, and central means centred. There's no ascent in the sum at all. So the whole correction collapses into arithmetic on numbers the designer already wrote down: every leading a whole number of grid rows, every leading the same parity, every space a whole number of rows. No cap height, no font table, no browser probe.

I measured it in three engines at several leadings, for a Japanese sample and a Latin one, and shipped it.

Then ruby, which is where it started coming apart

Furigana is the small reading gloss printed alongside kanji, and it's completely ordinary in Japanese typesetting. A tool that claims to fit vertical Japanese and falls over on furigana isn't really claiming anything, so I went and measured it.

Ruby does break it. An annotation is a second, smaller run of type beside the base text, and if the leading can't hold both, the engine reserves the difference at the leading edge of the block. The first baseline moves in and the block grows by the same amount. Horizontally there's a CSS property that trims exactly that away and the damage stops at the block. Vertically there's nothing, so it reaches everything below.

No CSS fixes it either. I tried zeroing the line-height on the annotation, on the ruby element, on both at once, and switching the ruby position to inter-character. All four left it precisely where it was. The only thing that restored the grid was hiding the annotation, which is deleting the content rather than fitting it.

But give the line enough leading and the reservation goes to zero. So I measured the threshold across sixteen combinations of base size and annotation size, found it scaled cleanly, and checked it against three different typefaces to see whether the typeface mattered. Inter, Playfair Display and a manufactured face with deliberately extreme vertical metrics. All three gave the same threshold, so the answer was no, and I wrote the rule as a constant.

1.107 is Times New Roman

The thing that saved this was an unrelated tidy-up. I widened the font comparison from three faces to eight, added a monospace and a script face and a display face with famously enormous metrics, and printed each font's ascent-plus-descent next to its threshold so the correlation would be visible.

Every one of the eight reported 1.107.

That number is impossible. Anton and Sacramento and JetBrains Mono do not have identical vertical metrics, and if they did, type design would be a much duller field. What 1.107 actually is, once I went and looked it up, is the ascent and descent of Times New Roman, which is what a browser falls back to when the font you asked for hasn't arrived.

None of them had arrived. My test pages were built with setContent, which leaves the document sitting on about:blank, and the stylesheet asked for the font with a root-relative URL. A relative URL on about:blank resolves to nothing. No request is issued, so the interceptor that was supposed to serve the file never fires, and the browser quietly falls back with no error, no console warning and no failed request in the network panel. The page renders. The measurement returns a number. The number is real and it's about a font I never asked for.

/* Under setContent, this resolves to nothing and fails in silence. */
@font-face { src: url(/font.ttf) }

/* This issues a request an interceptor can answer. */
@font-face { src: url(https://fixtures.test/font.ttf) }

Switching to an absolute URL changed the reading from 1.107 to 1.448, which is Noto Sans JP, which is the font the test had been naming all along.

Three checks, one blind spot

What makes this worth writing down isn't the mistake. It's that I checked it three times and all three checks were the same mistake wearing different clothes.

  1. Three faces, same threshold

    What it concluded
    The floor doesn't depend on the typeface
    What it measured
    One fallback font, three times
  2. Eight faces, identical metrics

    What it concluded
    Read as consistency, briefly
    What it measured
    The same fallback font, eight times
  3. The CI browser job, green for weeks

    What it concluded
    The vertical suite passes
    What it measured
    Tofu boxes, because CI never downloads that font
The checkWhat it concludedWhat it measured
Three faces, same thresholdThe floor doesn't depend on the typefaceOne fallback font, three times
Eight faces, identical metricsRead as consistency, brieflyThe same fallback font, eight times
The CI browser job, green for weeksThe vertical suite passesTofu boxes, because CI never downloads that font
The third one is the worst. Fixture fonts are fetched rather than committed, and only one CI job fetches them, which isn't the job that runs these tests. So the suite was rendering Japanese with no Japanese font, measuring the width of the missing-glyph boxes, and reporting success.

I've written before about controls that can't fail, and I clearly hadn't learned it well enough, because the shape here is identical. A control only tells you something if it's capable of disagreeing with you. Comparing three fonts is a real test of font-dependence only when the three fonts are actually different, and I never checked that they were. I checked the thing I was curious about and took the apparatus on trust.

Eight typefaces agreeing to three decimal places isn't consistency. It's one typeface.

The finding changed, and got better

With the font actually loading, one of the published readings moved. Firefox honours a CJK font's own vertical metrics, and Noto Sans JP doesn't declare a centred vertical origin, so its baseline sits 0.9px off centre there. Chromium and WebKit centre it. Firefox centres Inter and EB Garamond quite happily. It's specific to that combination.

So the claim I'd shipped, that the vertical baseline sits at exactly half the leading, is false in one browser for one class of font. That sounds like the finding collapsing, and it didn't, for a reason worth the detour.

The offset is constant. It's 0.9px at a 24px leading, at 32, at 40 and at 48. And a constant offset shifts every baseline on the page by the same amount, which means solving for where the grid starts absorbs it completely. That's why the fitted test pages still read twenty-nine lines out of twenty-nine in the engine that isn't centring anything. The property the method actually depends on was never centring. It was constancy, and I'd been asserting the stronger claim because the stronger claim happened to be true in the fallback font.

The test asserts the constant now. It collects the offset at four leadings and fails if they differ from each other, which is the thing that would genuinely break a page, rather than failing if the offset isn't zero.

There's a real limit underneath it, and it's in the documentation rather than buried here: two typefaces on one vertical page, where one declares vertical metrics and the other doesn't, won't share a grid in Firefox. I'd never have found that with a fallback font, because a fallback font is only ever one typeface.

And ruby puts the font back

The corrected ruby floor turned out to be the interesting half of all this. Once the fonts were loading, the threshold moved with the typeface exactly as you'd expect, and it lands on the font's own ascent plus descent:

leading >= (size + 2 x ruby) x (ascent + descent) / em

That held at fifty-eight of sixty combinations of face and size that I'd held back from the fitting, with the other two short by half a pixel, so the tool adds one and moves on. And look at what that formula contains. A font metric.

The entire appeal of vertical writing was that the baseline is font-free, which is what makes the correction pure arithmetic. That's still true. But an annotation isn't a baseline, it's a box, and a box is font-sized. Ruby hands back exactly the dependence that vertical writing takes away, and only for the designs that carry it. So the fitter takes the annotation size and the font's ratio together, and when it's given the first without the second it says so and leaves the leading alone, which is the same refusal it makes for a font that declares no cap height. A guess there puts every line on the wrong row while looking like it worked.

What I changed so it can't happen quietly again

There's now a test whose entire job is to assert that the typeface which arrived is the one that was asked for, by its metrics and by the browser's own font registry. It runs before everything else in the file. If it fails, the metrics are a fallback and nothing below it means anything.

And when the fixture font isn't on disk at all, which is the situation on CI, the whole suite skips and says why, rather than running against tofu and going green. I moved it into the job that does download the fonts, and I checked both halves rather than assuming: thirty-six pass with the font present, twelve skip with it removed.

That last check is the one I'd have skipped a year ago. Writing a guard and not testing that it fires is how you end up with a second thing that can't fail, sitting on top of the first.

The published finding survived all of this, slightly narrowed and considerably better supported. What didn't survive is my confidence that three agreeing measurements mean anything at all about the thing being measured. They agreed because they were the same measurement. I'd taken it once, and looked at it three times.

No share buttons, no claps, no newsletter thing sliding up from the bottom. If it's any good you'll send it to somebody yourself, and if it isn't then a row of icons was never going to fix that.