wiki:FontSelection

Font Selection

First, let’s introduce the classes involved.

platform/graphics/

At the lowest level, FontPlatformData is a class which simply represents the platform’s concept of a font. On the Cocoa ports, this class holds a CTFontRef. (Actually, it holds two CTFontRefs and one CGFontRef, but conceptually these three all represent the same thing with slight variations.) The implementation of this class is highly platform-dependent.

One step higher, we have a Font class which owns a FontPlatformData. This class represents a platform-independent notion of a font, in addition to caching various aspects about the font such as its metrics, glyph widths, what the space glyph is, etc. These cached values are populated at construction time, by the platform-dependent function Font::platformInit().

I’m going to skip a few levels now and describe FontCascade. This is a notion of a font which represents an entire fallback list of fonts, and each RenderStyle owns one of these objects. A Font can only map a maximum of 216 code points to unique glyphs, so in order to cover the entire 1M+ Unicode code points, you must have a fallback list of fonts. This is conceptually the fallback list you specify in the “font-family” CSS property, except that the CSS property only lists strings of families, while the FontCascade represents a list of actual fonts.

Because each RenderStyle owns one of these objects, any given renderer has exactly one of these. This is the entry point to paint-time font code, and has methods like width(TextRun), offsetForPosition(TextRun) (which is used for finding the coordinates of the caret), drawText(GraphicsContext, TextRun), etc.

That TextRun thing is simply a collection of all parameters necessary for performing measurement or painting. It primarily contains a StringView, but also flags for things like “allows spaces” or “should whitespace be expanded for justification.”

A FontCascade has two pieces to it - a FontDescription object and a FontCascadeFonts object.

FontDescription is a dumping ground for all information related to font lookup. All CSS properties which affect font selection are backed by state in the FontDescription. It doesn’t hold any information about the selected fonts themselves; instead it’s just a bunch of raw data. So, it holds things like weight(), computedSize(), smallCaps(), etc. It also holds the contents of the font-family information which is accessible via familyCount() and familyAt().

FontCascadeFonts is the mediator between the high-level FontCascade and the low-level Font. This is where the actual sequence of Fonts are kept, in a Vector<FontRanges>. This FontRanges thing is just a set of associations of ranges of Unicode code points with Fonts, and is necessary because of the unicode-range CSS property (more on that later). When a FontCascadeFonts object is created, it is empty, however, as it receives requests it will “realize” (e.g. populate) each FontRanges object one by one.

There is also a singleton data structure called FontCache. All font lookups for preinstalled fonts go through this object at the lowest level. The API for FontCache produces Font objects, and internally it does this by using two caches: One for FontPlatformData objects, and one for Font objects. When a FontPlatformData cache miss occurs, this object knows how to (finally) create platform-dependent font objects, and construct a FontPlatformData out of them. If you’re looking for CTFontCreate*(), this is where you’ll find it.

So that’s about half of the picture. The other half is about web fonts.

css/

In CSS, you describe a web font by writing a @font-face rule which includes a font-family and a src. Then, you can style your other elements as if that font-family exists. If the font is used, the browser will download the src url and use it.

There are a few classes at play here. First up, we have CSSFontFace, which represents the @font-face declaration itself. It owns a collection of CSSFontFaceSource objects, which represent each of the src values inside the @font-face.

Each CSSFontFaceSource has a couple of things of interest in it. It first has the string for the URI it targets (of course). In addition, it owns a CachedResource subclass named CachedFont. (CachedResource subclasses are the interface to the network subsystem in WebKit.) When triggered, this is how CSSFontFaceSource initiates downloading the url. When the font finishes downloading, the CachedFont uses the platform-dependent FontCustomPlatformData class to build up the Front from a PlatformFont, and hands it back to CSSFontFaceSource where it is remembered. When the CSSFontFaceSource is asked for a font, if the font has completed downloading, it responds with the newly downloaded font; otherwise, it responds with a fallback font. This fallback font has a particular bit set, isLoading, which means FontCascade::drawText() will not actually draw any text, which is why you see invisible text when fonts are loading.

Going back to CSSFontFace, there is actually a higher-level class on top of it, named CSSSegmentedFontFace. This class is used because you can have multiple @font-face declarations which all have the same attributes / values in them but differ only by the “unicode-range” property. In this situation, elements which target those shared attributes should see both @font-faces together, and use one for code points inside its unicode-range and the other for code points inside its respective unicode-range.

There is an interesting monkey-wrench thrown in here. In particular, if you have an @font-face with a font-family name which is the same name as a preinstalled font, the browser is expected to consider the @font-face and the preinstalled font together (just like multiple @font-faces are considered together if they have the same family names). Therefore, we actually create CSSFontFace objects which represent preinstalled fonts. We do this by emulating “src: local()”. These preinstalled CSSFontFace objects are not specific to font traits (which CSSSegmentedFontFaces are) which means that the same preinstalled CSSFontFace object might actually be added under two different CSSSegmentedFontFaces. Therefore, the relationship between CSSSegmentedFontFaces and CSSFontFaces is both fan-out and fan-in (i.e. from many to many).

The owner of all of these CSS font-related classes is CSSFontSelector. The Document owns exactly one of these classes, and the act of asking the Document for it causes it to be created. This object is in charge of building up all of these CSS objects when an @font-face object is parsed, as well as answering font lookup queries from FontCascadeFonts (when it is trying to realize its FontRanges). The entry point for building up all these objects is CSSFontSelector::addFontFaceRule() and the entry point for answering lookup queries is CSSFontSelector::fontRangesForFamily(). Note that the return type of fontRangesForFamily is a FontRanges object, which comes from the unicode-range property stored in CSSFontFace.

Also note that, because of lifecycle issues explained below, the FontCascadeFonts object holds a reference to the CSSFontSelector. However, FontCascadeFonts is inside platform/, and nothing inside platform/ is supposed to know about anything outside of platform/. The way this is done is by having an abstract FontSelector interface inside platform/ which CSSFontSelector implements (and there are no other implementing classes). The FontSelector reference inside FontFascadeFonts is populated at construction time.

Extra considerations

First of all, the Document’s CSSFontSelector reference can be cleared and recreated at any time. There are certain style changes (such as setting the “media” attribute on a <style> element) which can cause this, but it also occurs in our memory pressure handler. FontCascadeFonts, however, needs access to the FontSelector in order to answer font selection queries, so FontCascadeFonts objects keep their given FontSelector alive with a retain. We don’t have to worry about stale data, though, because FontCascade objects have an update() method which takes the Document’s FontSelector as an argument and will destroy their FontCascadeFonts object if its FontSelector doesn’t match the argument. FontCascade::update() will also recreate the FontCascadeFonts if any part of the FontDescription changes either. If you create a FontCascade and don’t call update() on it, its FontCascadeFonts pointer will be null, and you won’t be able to perform any lookups with the font.

There is another consideration, which is that the Document needs to be re-laid out when a font completes downloading, because any element in the document may have that font somewhere in its fallback list. This means that there has to be some way for the signal to get from CachedFont all the way up to the Document. This is done through a series of weak pointers (actually, raw pointers, not WeakPtrs). CSSFontFaceSource is a CachedFontClient, so it receives a delegate callback. It has a raw pointer to its owning CSSFontFace, who has a raw pointer to each of its owning CSSSegmentedFontFaces, who has a raw pointer to its owning CSSFontSelector. The CSSFontSelector has a client interface, FontSelectorClient, which contains a single function, fontsNeedUpdate(). The Document implements this interface (along with CanvasRenderingContext2D::Font, which is needs to own a collection of FontCascades for canvas’s save() and restore() functionality). The Document implements fontsNeedUpdate() with the giant hammer recalcStyle().

Tour of font selection

So, when you want to measure or draw some text, you first perform a font lookup for a particular character. The flow goes through:

FontCascade::glyphDataForCharacter()

FontCascadeFonts::realizeNextFallback()

CSSFontSelector::fontRangesForFamily()

CSSSegmentedFontFace::fontRanges()

CSSFontFace::font()

CSSFontFaceSource::font()

CachedFont::CreateFont()

FontPlatformData::FontPlatformData()

Literally each one of these steps is cached.

(Orange means platform/ code)

Last modified 21 months ago Last modified on Sep 8, 2015 11:05:54 AM

Attachments (3)

Download all attachments as: .zip