Notebook · Note 002 · Typography

Ten languages, three font files

When you open a site to ten languages, the first thing that breaks is the font budget. It did not break. But we paid another cost for Cyrillic and Arabic letters, and we did not hide it.

Serhat Belen

Founder, BLN Global · · 6 min read

Short answer

If the same three fonts came from Google Fonts, a Turkish page would download 250 KB across 6 files from two external origins. From our server, it downloads 165 KB across 3 files from one origin. Most of the saving comes from fixing unused axes, not subsetting. We had to choose separate families for Cyrillic and Arabic characters. In Arabic, the trade-off is that some symbols come from a fallback font.

The site's typography used three families: Archivo for headings, Instrument Sans for body text, Martian Mono for numbers and labels. All three were variable fonts. Five languages posed no problem. At ten, two issues grew at once: download size and script coverage.

What we compare with what

Comparing raw TTF files with the WOFF2 files we ship would not be fair. Most of the gap comes from compression, not subsetting. The useful comparison is with the real alternative: what would download if the same three families came from Google Fonts.

The browser does not download all nine files. It unicode-range field and fetches only the subsets the page needs. For a Turkish page, that means latin + latin-ext per family:

latin 88.0 KB <- latin-ext 83.8 KB <- vietnamese 33.6 KB latin 29.2 KB <- latin-ext 10.8 KB <- latin 23.0 KB <- latin-ext 15.5 KB <- cyrillic 10.3 KB cyrillic-ext 3.1 KB Turkish page download: 250 KB (6 files)
We fetched the Google Fonts CSS, read unicode-range in every @font-face block, and measured each file. Arrows mark the ones a Turkish page actually downloads.
250 KB / 6 files / 2 external origins165 KB / 3 files / one origin

Font load on the Turkish page

How it was measured The left is the measurement above. The right is from this repo archivo.woff2 + instrument.woff2 + martian.woff2 the actual size of the files on disk.

Bytes were not the only win: six requests fell to three, and the DNS + TLS handshake to two external origins disappeared. How the render-blocking resource issue was closed in the last 18-point note is described.

Three traps

One: the latin-ext subset lacks basic Latin letters

On the first try, we downloaded and subset the Google Fonts latin-ext file. The result had 130 glyphs and no digits. The reason is simple: latin-ext is a suffix set layered on top of latin, not a complete alphabet by itself. Download the full variable font from the google/fonts repository and subset it yourself.

Two: if you do not pin unused axes, the file grows fivefold

Every variable font axis adds weight to the file. The design uses only weight and width. Unless you pin the other axes to their defaults, their data stays in the file.

full = {a.axisTag: a.defaultValue for a in t['fvar'].axes if a.axisTag not in axis} full.update(axis) # axes other than wght/wdth are pinned to default, # otherwise the file gets five times bigger
Pinning the axis before subsetting saves more space than the subset itself.

Three: gvar loads lazily

after pinning the axis with fontTools, when we tried to subset directly KeyError: space is returned. The cause is lazy loading of variable glyph data (gvar): after instancing, the object looks valid in memory, but the subsetter cannot read it. The fix is awkward but takes one line: save the file as a temporary TTF, reopen it, then subset it.

Cyrillic and Arabic: the price you pay

Archivo has no Cyrillic, and neither does Instrument Sans. Neither has Arabic. We had to choose separate families for these two languages, and we did not choose at random.

  • Russian: Roboto Flex. That was the only structural match: it has a width axis. So all the CSS font-variation-settings values work unchanged. One file serves both heading and body, so the Russian page stays lighter than the Latin page.
  • Arabic: Noto Sans Arabic. The only candidate with a width axis. One 144 KB file fills all three roles, so the Arabic page downloads less than the Latin page.

Here is the cost: in Noto Sans Arabic and icons are missing. On the Arabic page, those two marks come from a fallback font and look slightly different from their neighbors. We wrote it into the CSS as a comment instead of hiding it, because the next person will spot the same difference and think it is a bug.

Right-to-left layout was a separate job: arrow directions flipped, position properties changed to their logical counterparts (margin-inline-start, inset-inline-start), so the same CSS works in both directions.

A small tweak: why Martian Mono is optional

Two of the three families font-display:swap uses. Martian Mono optional. The reason was measured: with swap, Martian arrived late, remeasured the menu button, and caused a 0,015 layout shift. Since it is preloaded, it arrives in time anyway. In the one case it does not, the page stays put and keeps the fallback font.

The font folder totals 404 KB across ten languages. No visitor downloads it all. Each page fetches only its language's files.

FAQ

Should you self-host Google Fonts?

Not always in bytes, but usually in connections. Google Fonts connects to two separate origins, and each new origin requires a DNS lookup and TLS handshake. We measured 165 KB / 3 files / one origin instead of 250 KB / 6 files / 2 origins.

Which delivers the bigger gain: subsetting or pinning the axis?

Lock the axis. Every axis you do not use in a variable font takes space in the file. If you do not lock it, the file grows several times over. Subsetting comes after that.

Is the latin-ext subset enough for Turkish?

No, it is not enough for any language on its own. latin-ext is an extra set on top of latin. It does not include basic letters or numbers. The right path is to download the full variable font and extract the subset yourself.

swap or optional for font-display?

If the text makes up the page body and the font is preloaded, optional is safer. If the font arrives late, the page keeps the fallback instead of shifting. On our Arabic pages, swap caused a layout shift of 0,287. optional brought it down to zero.

Which typeface did you choose for Arabic and Russian, and why?

Roboto Flex for Russian, Noto Sans Arabic for Arabic. The choice was structural, not aesthetic: both have a width axis, so the existing CSS font-variation-settings works without changing any values.

Sources

Related work