What does Vietnamese lose when Google Fonts cuts a font in two?
🌱 seedlingplanted #css#font#vietnamese#lessons
Written for People who build Vietnamese web pages, self-host or subset web fonts, and are counting every KB.
This morning I was reading a fix log about fonts, nodding along until almost the end, and stopped at one example. The example was wrong, and the reason for the fix rested on it.
I measured the numbers below on 4 November on a Mac, in WebKit (the engine inside Safari) and Chrome, and the two matched exactly. Page weights come from the log. AI worked on the measuring with me.
Tiếng Việt: Google Fonts cắt đôi font thì chữ Việt mất gì?.
Why Vietnamese lives in two files
This garden loads no web font, but a small directory site I work on does, and its fonts come from Google Fonts. Google doesn't send the whole font. It cuts by unicode-range: one latin file, one vietnamese file. The browser fetches whichever file holds a letter it needs, so a Vietnamese page almost always fetches both.
The cut doesn't fall between Vietnamese and English. It falls right through the middle of Vietnamese. The letters á à ã â é è ê í ì ó ò õ ô ú ù ý are in Latin-1, so they sit in the latin file. ă đ ĩ ũ ơ ư and every letter with a stacked mark or a dot below, like ạ ả ấ ệ ộ ự, sit in the vietnamese file. Across this garden's Vietnamese notes, 16.4% of letters live in the vietnamese file, and 32.7% of neighbouring letter pairs step over that line.
Kerning is a nudge for one specific pair of letters, like pulling o in under the arm of a T. The table lives in the font file, so a pair only works when both letters are in the same file. The log has it right up to here.
Why the log dropped kerning
That site was trimming its fonts: 12 files, 87.2 KB. The log saw kerning at about a quarter of that and dropped it, for two reasons. One: the pair that matters most, cá, sits in two files, so kerning never worked where it was needed. Two: screenshots before and after looked the same, and the largest type on the page did not move.
Reason one fails at the code chart: á is U+00E1, in the latin file, the same file as c. I also measured that pair, and it has no kerning at all in either font the site uses.
Reason two sounds firmer, but I measured the three letter pairs in that big line of type and all three are 0, in both fonts. It stayed put because there was nothing to move. That test looked only at the one place that could not change, and concluded for the whole site.
What is left when you measure
My reflex was to count pairs in the kerning table. But a pair in the table may never appear in text, so I measured in the browser instead. Take every letter pair in my real notes, measure its width with font-kerning: normal and with none, in two setups: two files as Google sends them, and one merged file requested with the text= parameter. A pair moving by at least half a thousandth of an em counts as kerned.
| font, notes | kerned pair occurrences | left when split in two files | lost |
|---|---|---|---|
| Newsreader, Vietnamese notes | 5,238 | 3,870 | 26% |
| Be Vietnam Pro, Vietnamese notes | 8,366 | 4,877 | 42% |
| Newsreader, English notes | 8,483 | 8,466 | 0.2% |
| Be Vietnam Pro, English notes | 12,008 | 11,968 | 0.3% |
So "never worked" is wrong: more than half the kerning in Vietnamese text works fine. But the loss is real too. The pairs lost equal the kerned pairs crossing the two files, not one occurrence more or fewer, so the log guessed the cause correctly.
The loss lands on letters you see all the time. In Newsreader, ấy, ạy, về, vớ each lose about 31 thousandths of an em. In Be Vietnam Pro, đi loses 40 and đó loses 20, and those are nudges outward that keep đ from bumping the next letter. A font drawn with care for Vietnamese drops that care between two files on its way to the reader.
Try it below. These words use your machine's system font, all in one file, so no pair is cut, and the number is the real shift at this size.
- To
- AV
- Và
- Vậy
- về
- với
- đi
- đó
So was dropping kerning wrong?
I don't think so, and this is where I changed my mind halfway. The decision stands, it just needs a different reason: a few px traded for 21.7 KB. Dropping only kerning from the 12 old files goes from 87,196 to 65,480 bytes, 24.9%. The log once said 25.3 KB, but that came from two builds with different encoders, so it can't be reproduced.
What does it cost? At 16 px, 31 thousandths of an em is half a px. Add every shift on a 70-letter Vietnamese line and you get about 2 to 4 px. Hard to see in body text. But To shifts by 106 thousandths, which is 5 px in a 48 px heading, and that kerning really worked before and is really gone now, especially on the English pages.
The reason matters because the next person leans on it. Someone reading "kerning never worked" will drop it everywhere with a clear conscience, English headings included. Someone reading "a few px for 21.7 KB" can weigh it again once the page is already light. I sent these numbers back to that site, and within the day the log corrected both the reason and the figure, and kept the decision.
There is a third road: merge into one file and keep every pair. The log measured a merged file at about the same as the two together, within 100 bytes. Redoing it with the site's 182 characters I got something else: Newsreader 20.4 KB against 17.2 KB, Be Vietnam Pro 12.1 KB against 10.8 KB. So merging costs about 12 to 19% more, not nothing. For a logo or a fixed heading text= is still neat, because it only needs a few letters.
How your own page is doing
Paste this in your page's console, and change the pairs to the letters you use most. It prints each shift in thousandths of an em. If a two-file font gives 0 for ấy and not 0 for To, you have exactly this.
const s = document.createElement("span");
s.style.cssText = "position:absolute;white-space:pre;font-size:1000px;font-variant-ligatures:none";
document.body.append(s);
const w = (k, t) => { s.style.fontKerning = k; s.textContent = t; return s.getBoundingClientRect().width; };
for (const p of ["To", "Va", "ấy", "về", "vớ", "đi", "đó"]) console.log(p, w("normal", p) - w("none", p));
s.remove();
What I still don't know
- Whether a reader's eye notices, I haven't tested with anyone. I only have px, and px aren't the feeling of reading.
- I can't explain why the log once had the merged file at 17.1 KB when I get 20.4 KB. I guessed that build had no kerning, but without kerning the merged file is 10.8 KB, so it isn't that. The log now uses my number, so the two agreeing proves nothing more.
- The sample is my own notes, one writer, about 51 thousand letters. Other text may give other shares.
- I measured two fonts, at regular weight. A font with a thicker or thinner kerning table will read very differently, so measure yours.
Have you ever dropped something for the wrong reason and still landed right? Write me a letter below.
Write papa dragon a letter
If this note stirred something, write to me here. A letter reaches only me and never shows up on the page by itself. If one is lovely and you allow it, I may quote it under the note, without your name.
Works from a terminal too: curl -d 'msg=…' olragon.com/garden/kern-split-en/letter