Making font initialization lazier

October 5th, 2009 by John Daggett

Last week I spent some time looking at font system initialization on the Mac (bugs 517045 and 519445). At startup, code in gfx on Mac and Windows currently enumerates all fonts on the system. This information is used for matching font names and for system font fallback (i.e. when a glyph is needed for a character not in the fonts specified by the page). It’s also used to workaround problems with font APIs on various platforms.

Running on a 2.2GHz Mac Mini running vanilla 10.5.8, startup takes roughly 650ms before content is shown. This “warm” startup time is after a couple previous runs, a newly installed or recently rebooted system startup takes longer. Roughly 37ms (or 5% of startup time) is spent in InitFontList, which enumerates fonts on the system. Most of this is spent in Appkit routines, 13ms in [NSFontManager availableFamilies] pulling the complete list of font families and then 22ms in [NSFontManager availableMembersOfFontFamily] to enumerate the styles available for a given font family. Increase the number of fonts on the system and these numbers start to grow dramatically; with the full contents of Adobe’s Font Folio 11 font package installed for a total of 2600 fonts, getting the family list takes 350ms and enumerating all the styles for each family takes 5.7 seconds(!!). That means a user on that machine sees nothing for six seconds!

It would be nice to do this lazily but to draw text font matching needs to take place so it’s tricky to push this all out of the startup process. Jonathan Kew wrote up a patch to use platform routines to lookup font names adhoc until the font list is completely enumerated later but this turns out to be tricky, platform lookups don’t always match as CSS guidelines dictate. For example, CSS allows font names to be used without case sensitivity and lots of sites use this:

body { font-family: arial, helvetica, sans-serif; }

The NSFontManager routine for looking up a font family name expects a case-sensitive name, so lookups of names like ‘arial’ will fail. Plus, this routine has lousy performance when a name lookup miss occurs, it can take 8-12ms to figure out that a font with a given name doesn’t exist. Compare that with our case-insensitive lookup code which only takes a couple microseconds to lookup a name, regardless of whether it exists or not. For the average user, doing adhoc lookups is going to increase startup time, not decrease it, since the failed lookups will end up taking longer than initializing at least the list of families.

So how can we decrease the startup time here? Probably through a combination of enumerating the styles for font families more lazily and investiagating whether more recent font API’s have better performance.

Another aspect of font system initialization is the reading of font character maps (cmaps) for all fonts on the system. This generally doesn’t occur at startup, it only occurs if system font fallback occurs. Consider a simple example:

body { font-family: arial, helvetica, sans-serif; }

<p>Nattō (納豆) is made from fermented soybeans.</p>

For the Japanese characters in the text above, code in gfx looks at all the fonts in the system and tries to pick the one that contains glyphs for the characters and matches the style characteristics of the font used for other text. The process is fairly simple, the code looks at the cmaps of fonts on the system to conclude which fonts have glyphs for a given character. But this could definitely happen more efficiently.

The first thing to note is that fonts rarely change, every once in a while a user adds a font to their fonts folder or fonts are added when installing a given application. So these could be cached. But even this is probably a lot more information than is needed, the nature of fonts is that there are a large number of fonts that support a small range of characters for European languages and a much smaller set of fonts that include glyphs for other codepoint ranges. For characters in common ranges (e.g. U+000-2FF), the font matching could actually be precomputed and cached and only updated when fonts changes occur. For other ranges, a simple precomputed list of fonts with glyphs for that range would reduce the system fallback process to iterating over a small set of fonts as needed. This would be especially helpful to users with large numbers of fonts on their system but it could also speed up text rendering in general since text rendering is typically critical path code.

Note: downloadable fonts are never used for system fallback so they do not affect this process.

Comments are closed.