I'm adding a "save as poster" button to a side project. The page draws a headline onto a canvas in a serif web font and exports a PNG. A web font is a file the browser has to download first, and I already knew that if you draw before it arrives you silently get the fallback font, meaning the generic family at the end of the font string. So my plan was to put await document.fonts.ready in front of the drawing code. That is a promise the browser gives you, and I had always read it as "all my fonts are loaded". Before building on that I wrote a small test page.
The page declares one font with @font-face, the CSS rule that gives a font file a name you can use. For Chinese I used a 3,755-character subset of Noto Serif SC, 1,046,216 bytes as WOFF2, and for English I used Pacifico. It draws "秋季新品 全场八折" or "Autumn Sale 2026" at 48px. To decide which font actually landed I read the canvas pixels back, computed a checksum, and compared it with two reference drawings: one made after the font was definitely loaded, and one made with only the generic family. I ran it in Chromium 149, Chrome 155, Firefox 151 and WebKit 26.5 on an Apple M4 Mac.
In the first version nothing on the page used the font. document.fonts.ready came back in under 20 ms, and the text I drew right after it matched the fallback reference in all four browsers.
The pink rows are what a too-early draw looks like, and it is also what I got after ready on that page. Compare the stroke ends of the Chinese characters in the first two rows.
Then I changed one thing. I put a line of ordinary text on the page and styled it with the same font. On that page, drawing after ready gave the custom font in all four browsers. So in this test ready waited for my font only when some element was using it. From the outside it behaved like a waiter who tells you "that's everything" once the dishes already ordered are on the table. My font was on the menu, and nobody had ordered it. I didn't look up why it works this way, and I haven't read the spec closely enough to quote it, so all I have is what these four browsers did.
The other "the page is done" moments were no safer. With the styled text present, drawing at DOMContentLoaded gave the fallback font in all four. Drawing in the load event gave the custom font in Chromium, Chrome and WebKit, and the fallback font in Firefox. Without the styled text, load gave the fallback font everywhere, and a second draw 1500 ms later was fine. For a while I thought about keeping ready and leaving a bit of text on the page in that font just to make it wait. It passed here, but my canvas code would then depend on a piece of markup that somebody can delete, and I didn't test whether a hidden element would count.
What I use now is document.fonts.load(). You hand it a font string, and it resolves when that particular font can be used.
// before: resolved in under 20 ms, text came out in sans-serif
await document.fonts.ready;
pen.font = '48px "HeadlineSerif", sans-serif';
pen.fillText(headline, 16, 60);
// after: custom font in all four browsers
await document.fonts.load('48px "HeadlineSerif"');
pen.font = '48px "HeadlineSerif", sans-serif';
pen.fillText(headline, 16, 60);
After that call the drawing matched the custom reference in all four browsers, for the Chinese subset, for Pacifico, and for the full 11.6 MB Noto Serif SC file too.
I was curious how an existing editor handles a font that hasn't arrived yet. I used ImgIng (https://imging.ai/), whose online design editor has text layers with fonts that are fetched on demand, and the panel says the Korean one is about 4.5 MB on first selection. I slowed the font response down on purpose, picked Noto Sans Korean, and clicked export about 0.2 seconds later. The export file was written only after the font response had arrived. That PNG was byte-for-byte identical to one I exported after the font had settled, 39,765 bytes each, and the one exported with the system font was a different file of 39,854 bytes. This was one flow in Chromium 149 on a desktop with one font, and I didn't look at how it is written inside.
Look at the status bar at the bottom of the upper capture, and at the Width field on the right. The Korean line starts in a system font at 648, then changes glyphs and grows to 677 once the font is in.
I haven't tested phones, Windows, Safari itself or Edge. In my poster code I now wait for the exact font by name right before fillText, and I read document.fonts.ready as a statement about fonts the page content is already using. Noto Serif SC and Pacifico are both open fonts under SIL OFL 1.1.

Top comments (0)