gfx/font_match.h
FontMatcher: a CSS font-family list, a weight and a style, to a typeface.
#include <sonore/gfx/font_match.h>- 2types
- Guides: The native editor
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.