Loading fonts

edit

Load 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.

MethodReads
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.
  • useEmbeddedBitmaps makes the one-bit bitmaps of the font (EBLC and EBDT), 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:

FontResourceLimitsDefaultWhat it bounds
MaxEncodedBytes256 MiBThe bytes read from a font file
MaxFontBytes256 MiBThe bytes a font keeps in memory, checked before a WOFF or WOFF2 file is expanded
MaxGlyphComplexity1,000,000The elementary operations (outline points, components, charstring operators, instructions) to produce one glyph
MaxTextGlyphs1,000,000The glyphs of a shaped text
MaxCollectionFaces1,024The 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:

ExceptionWhen
UnknownFontFormatExceptionThe data does not start with the signature of a font format
InvalidFontExceptionThe font does not follow the rules of its format
UnsupportedFontFeatureExceptionA valid font uses a feature that is not supported; Feature names it
FontResourceLimitExceptionA 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.