Search guides and the APICtrl K
  1. API reference
  2. Native GUI

gfx/font_match.h

FontMatcher: a CSS font-family list, a weight and a style, to a typeface.

#include <sonore/gfx/font_match.h>
On this page

Design notes

Why this exists

system_font.h answers "a face for the interface" and "a face that has this character". Text in an SVG asks a third question, the one a browser answers: font-family="Inter, 'Helvetica Neue', Arial, sans-serif", font-weight 600, italic -- which face on this machine is that? Without an answer every <text> in a logo was dropped, and the row said text "looks different per machine". It does, and that is now said precisely below, along with what to do about it.

How a family list is matched

As CSS Fonts says, in its simplest complete form:

1. The families in order. Each is looked for first among the faces a
   plugin REGISTERED -- its own font, compiled in -- and then among the
   machine's fonts. The first family that has any face wins; later ones
   are fallbacks for a family that is absent, not for a glyph.
2. A generic family -- serif, sans-serif, monospace, cursive, fantasy,
   system-ui -- is a list of concrete families per platform (Arial for
   sans-serif on Windows, as the browsers there use), and can be pointed
   at registered faces with setGenericFamily.
3. Within the family, the style first (italic wanted: an italic face if
   the family has one), then the weight by CSS's rule: for 400 to 500,
   the nearest at or above up to 500, then below, then above 500; under
   400 the nearest at or below first; over 500 the nearest at or above
   first. A VARIABLE face with a weight axis covering the request is an
   exact match, drawn at that weight on the axis.
4. Nothing named exists: the default family (serif, as browsers and
   resvg default), and failing that the interface face.

No bold or italic is synthesised -- a family with no bold face draws its regular one, as resvg does -- and a character the matched face lacks is drawn from the system's fallback faces, as everywhere else in Font.

The machine's faces are found by reading the name, OS/2 and fvar tables of every font file in the system's font folders, once, the first time a family is asked for that was not registered: the files are mapped, so this touches a few pages of each rather than reading them -- on Windows only the bytes of those tables are read, on several threads, which a file system that is slow to open a file allows to overlap (buildIndexLocked).

Text that is the same everywhere

A family list matched against the machine's fonts gives the machine's answer, and two machines answer differently -- the reason this was left out. A plugin whose art must look the same everywhere REGISTERS the face the art was designed with (registerTypeface, from bytes compiled into the plugin) and parses with a matcher that does not look at the system at all (FontMatcher(FontMatcher::EmbeddedOnly)). Then every family resolves to a registered face or to the first one registered, and no character falls back to a system face: the same file draws the same pixels on every machine. Converting the text to outlines before export remains the other way, and the right one for a logo nobody will edit.

Types 2

A face a FontMatcher chose, and how to draw it.

Resolves a CSS font-family list, a weight and a slope to a typeface: the question an SVG's text asks. How a list is matched, and how to get text that is the same on every machine, is in the file comment.

Faces a plugin registers (registerTypeface, registerFile) are found before the machine's; an EmbeddedOnly matcher never looks at the machine at all. The system's font folders are indexed the first time a family is asked for that was not registered, which takes some tens of milliseconds and reads files: never from the audio thread. Every public call takes the matcher's lock, so one matcher may be shared between threads; shared() is the process-wide one Drawable::parse uses when it is given none. Not copyable.

↑↓ to move↵ to openEvery public name in the headers, and every guide