Loading fonts
editLoad a font
FontLoader reads a whole font file into memory: the tables of a font refer to each other by offsets, and a face reads them in place.
| Method | Reads |
|---|---|
FontLoader.Load(ReadOnlySpan<byte>, environment) | The bytes of a file |
FontLoader.Load(Stream, environment) | A stream, to its end |
FontLoader.LoadAsync(Stream, environment, useEmbeddedBitmaps, cancellationToken) | A stream, asynchronously |
The result is an array of FontFace: one for a font, one per font of a collection. The faces of a collection share the bytes of the file.
- What every use of the font needs is validated when it is loaded: the table directory, the required tables, their headers, the sizes of the metrics tables and the structure of the character map. The data of a glyph is validated when the glyph is decoded.
useEmbeddedBitmapsmakes the one-bit bitmaps of the font (EBLCandEBDT), made for screens of low resolution, replace its outlines at the sizes they are made for. It is off by default, so that the rendering of a text does not change with the tables a font happens to have.
The environment
The font code takes what it needs from its caller through a FontEnvironment: the limits of its work (FontResourceLimits), and the allocators of its temporary buffers. A face keeps its environment (FontFace.Data.Environment), and the operations on the face rent their buffers from it.
PooledFontEnvironment is the environment of an application that has no allocator of its own: the temporary buffers are rented from the shared array pool, and the bytes the buffers of one operation hold at the same time are bounded by maxLiveAllocationBytes. Create one environment and reuse it for every font.
Limits
A font is untrusted input, like an image: its composite glyphs can refer to each other and its subroutines call each other, to describe an outline far larger than the file, and its lookups can multiply glyphs. The limits bound this work:
| FontResourceLimits | Default | What it bounds |
|---|---|---|
| MaxEncodedBytes | 256 MiB | The bytes read from a font file |
| MaxFontBytes | 256 MiB | The bytes a font keeps in memory, checked before a WOFF or WOFF2 file is expanded |
| MaxGlyphComplexity | 1,000,000 | The elementary operations (outline points, components, charstring operators, instructions) to produce one glyph |
| MaxTextGlyphs | 1,000,000 | The glyphs of a shaped text |
| MaxCollectionFaces | 1,024 | The faces of a font collection (.ttc, .otc), checked before they are read: each face is read when the collection is loaded |
The limit of the temporary buffers is the maxLiveAllocationBytes of the environment. Lower the limits when you accept fonts from users.
Errors
The exceptions of the font code derive from FontException:
| Exception | When |
|---|---|
| UnknownFontFormatException | The data does not start with the signature of a font format |
| InvalidFontException | The font does not follow the rules of its format |
| UnsupportedFontFeatureException | A valid font uses a feature that is not supported; Feature names it |
| FontResourceLimitException | A limit is exceeded; Kind, Limit and Requested say which and by how much |
var environment = new PooledFontEnvironment(FontResourceLimits.Default, maxLiveAllocationBytes: 64 * 1024 * 1024);
// The format is detected from the content: this is not a font
Assert.Throws<UnknownFontFormatException>(() => FontLoader.Load("<!DOCTYPE html>"u8, environment));
// A limit is exceeded: the file is larger than what the application accepts
var limits = new FontResourceLimits { MaxEncodedBytes = 1024 };
var exception = Assert.Throws<FontResourceLimitException>(() => FontLoader.Load(new byte[4096], new PooledFontEnvironment(limits, 1024 * 1024)));
Assert.Equal(FontResourceLimitKind.EncodedBytes, exception.Kind);A glyph is validated when it is decoded: DecodeOutline and the shaping of a text can also throw InvalidFontException and FontResourceLimitException for a font that loaded without error.