Skip to content
font_discovery

font_discovery

Mojo module 🡭

font_discovery

Font discovery: resolves a family/slant/weight request to a font file path on disk, with no linked library. One of the three parts of text rendering, alongside glyph resolution and metrics (ttf.mojo) and rasterization (fill_path_aa, see path.mojo).

Four steps, matching what libfontconfig does for a drawing library:

  1. Enumerate. Walk each platform’s font directories (_font_directories), collect every sfnt container (.ttf/.ttc/.otf/.otc), and read each one’s identity from its name/OS/2/head/post tables (_parse_face). No XML; the font files are the database. The result is written to a cache file (see below), since it is the same on every run until a font is installed or removed.
  2. Expand generic families. “sans-serif”/“serif”/“monospace” and the metric aliases (Helvetica, Arial, Times, Courier) are ordered preference lists, not real families (_family_candidates) – the job fontconfig’s /etc/fonts/conf.d/*.conf rules do.
  3. Score, don’t filter. Every installed face is ranked and the best wins, so a request resolves as long as one font is installed (_score). Terms run in fontconfig’s priority order (FcCompare*): family, spacing, slant, weight, width.
  4. Fall back per character. resolve_font_file_for_char ranks a font that maps the codepoint above every other term, reading candidate cmap tables in score order (_face_covers_codepoint) – fontconfig’s FC_CHARSET constraint.

Not covered: fontconfig’s XML rule engine, per-language coverage matching, and named-instance expansion of variable fonts (a variable font matches as its default instance).

Where it looks: on Linux ~/.local/share/fonts and ~/.fonts plus /usr/share/fonts, /usr/local/share/fonts and /usr/share/X11/fonts; on macOS ~/Library/Fonts, /Library/Fonts, /System/Library/Fonts (and its Supplemental) plus Homebrew’s font prefixes. CANVAS_MOJO_FONT_PATH (colon-separated directories) is searched ahead of those. Fonts have to be installed for text to render; this package bundles none.

A scan costs a few milliseconds, most of it the directory walk, and its result is a pure function of which font files are installed – the same table on every run of every program. FontDatabase() therefore reads it from a cache file when one is valid and writes one when it scans (_cache_path, _read_cache, _write_cache), which takes a first text call from tens of milliseconds to about one. The cache is an accelerator and never a source of truth: unreadable, stale, corrupt or unwritable, the scan runs exactly as it would have.

Invalidation. The cache records _CACHE_FORMAT, the font directories searched, and the modification time of every directory the walk visited. Installing or removing a font changes its directory’s mtime, so the next call rebuilds rather than silently missing the new font – the failure mode a version-keyed-only cache (matplotlib’s fontlist-vXXX.json) is famous for.

CANVAS_MOJO_FONT_CACHE overrides where the file lives, and the value off disables the cache so every call scans – what the tests and anything debugging discovery want. Otherwise it is $XDG_CACHE_HOME/canvas_mojo/fonts.txt, or ~/.cache/canvas_mojo/ when that is unset.

Matching against an already-built FontDatabase is arithmetic over a list, so build one FontDatabase (or FontCache) and reuse it rather than resolving per call.

This module imports nothing from canvas.text, which is why FontSlant/FontWeight and the small binary readers below live here: Mojo resolves a struct’s method surface, and whatever it imports, eagerly. It is also why _face_covers_codepoint walks a cmap here – it answers “is this codepoint mapped” from a byte range read off disk, where TTFFace needs the whole parsed file.

This module resolves a font file and nothing more: no outline parsing, measuring, hinting or rasterizing.

Structs

  • FontSlant: A font’s upright/italic/oblique style. Defined here to keep this module independent of canvas.text; render.mojo re-exports it.
  • FontWeight: A font’s normal/bold weight, defined here for the same reason FontSlant is.
  • FontFace: One installed face: where its file is, what it calls itself, and the handful of style axes matching compares. Everything here comes out of the font’s own tables; nothing is inferred from the filename.
  • FontDatabase: Every installed face on this machine, scanned once.

Functions