* font-catalog: hand fontique only bounded font-store files fontique 0.11 imports a collection by registering the whole file once for each face in it, scanning every face again each time and rebuilding the family's font list, so a downloaded collection whose header declares many faces costs far more than its size. The store is now walked here (bounded depth and file count, no symbolic links) and collections that declare more than 64 faces are left to the catalog's own scan, which reads each face once. * font-catalog: load font-store files one at a time and keep fontique's walk fontique 0.11 re-adds every font gathered in one load_fonts_from_paths call to its family for each face it registers, so handing it the whole store at once repeats the store's fonts quadratically in the file count (126,230 family entries for 2,084 faces on a 400-file test store, 4 s; one call per file gives the same families, fonts, order and default fonts with 2,084 entries in 90 ms). The store walk now matches what fontique did with the folder before: links are followed (to regular files and folders, each folder once), depth 16, read_dir order, with a 200,000-entry budget like the catalog's scan. Pipes, devices and dangling links are skipped.
craft-font-catalog
The installed fonts for the Craft apps: discovery on every platform, lookup by family and style or by any other name, and loading shared between threads.
What it finds
A Catalog reads, in this order:
| Where | Source | Platforms |
|---|---|---|
The platform's font folders (font_dirs) |
System |
all |
The suite's font store (font_store_dir) |
Store |
all |
The app's own folders (Catalog::set_app_dirs, for example office_font_dirs) |
App |
all |
Files the platform's font service knows (system feature, through fontique) |
System |
macOS, Windows, Linux, FreeBSD |
| The registry's font list | System |
Windows |
Files from injected listers (Catalog::with_lister) |
System |
all |
The font folders:
- macOS:
/System/Library/Fonts,/Library/Fonts,/Network/Library/Fonts,~/Library/Fonts,/System/Library/Assetsand the font assets macOS downloads on demand (AssetsV2/com_apple_MobileAsset_Font*). - Windows:
%WINDIR%\Fontsand%LOCALAPPDATA%\Microsoft\Windows\Fonts. - Linux and the BSDs:
/usr/share/fonts,/usr/local/share/fonts, the Flatpak host folders (/run/host/fonts,/run/host/local-fonts,/run/host/user-fonts),~/.local/share/fonts,~/.fonts,$XDG_DATA_HOME/fonts,$XDG_DATA_DIRS/*/fonts, and the<dir>and<remap-dir>entries of the fontconfig configuration with its includes and the user's own. Inside a Flatpak sandbox, also the host's folders from/run/host/font-dirs.xmland from the host's configuration under/run/host/etc/fonts. - Haiku: the system's and the user's
data/fontsfolders.
The font service covers fonts outside those folders:
- macOS: CoreText's available fonts, which include fonts that Adobe Fonts, RightFont and other font managers activate from their own folders.
- Windows: DirectWrite's system font collection, which includes per-user fonts and the fonts that font services such as Adobe Fonts load at run time. The registry adds fonts that programs register by full path.
- Linux and FreeBSD: fontconfig, loaded at run time (
fontconfig-dlopen), so a system without it still runs.
FontEnv holds the environment all of this depends on (HOME, the XDG variables, FONTCONFIG_FILE, WINDIR, LOCALAPPDATA, CRAFT_FONT_STORE, a file system root), so tests can describe any system without touching the process environment.
The font store
font_store_dir is the one per-user folder that the suite's font store installs fonts into and every app's catalog reads. No other program reads it.
| Platform | Folder |
|---|---|
| macOS | ~/Library/Application Support/Craft Fonts |
| Windows | %LOCALAPPDATA%\Craft Fonts |
| Linux, BSDs, Haiku | $XDG_DATA_HOME/craft-fonts (~/.local/share/craft-fonts) |
CRAFT_FONT_STORE replaces it, for portable installs and tests.
Scanning
The scan reads each file's table directory and its name, OS/2 and fvar tables, at most 1 MiB per table, never the whole file. Faces without outline tables (glyf, CFF , CFF2, VARC) are left out. Files in folders count when they have a font extension (.ttf, .otf, .ttc, .otc); files a font service or lister names count whatever their name. Mac suitcase fonts are read from their resource fork. Damaged files give no faces, never a panic.
Each face (FaceInfo) has its family and style, legacy family and style, family names in other languages, full name, PostScript name, weight, width, italic, a named instance's axis settings, fsType, the OS/2 code page bits, its file and index, and its source. Each named instance of a variable font is a face of its own.
Lookup
families()andstyles(family).family(name)andresolve(name, style)accept any name of a family: its own name ignoring ASCII case, its names in other languages and its legacy names ignoring case and anything but letters and digits.resolvealso accepts a legacy family with its legacy style (Roboto Light/Regular), a full name, and a PostScript name.- Within a family,
resolvematches the style by name (any of its names). Otherwise it picks the nearest weight, with italic differing counting as 1000, then the nearest width. It returnsMatch::ExactorMatch::NearestStyle, so the app decides about substitutes. by_postscript_name(name).
Loading and threads
- The first scan runs on first use, or on a background thread after
scan_in_background. Lookups wait for it. - Lookups read an immutable snapshot, so a rescan never blocks them.
load_familyreads a family's files once. Other threads asking for the same family wait and share the result; other families load at the same time. Faces of one file share its bytes (Arc<[u8]>).add_bytesandadd_fileadd fonts from documents, downloads or the app's bundle, withinLimits. Added faces win over installed faces of the same family and style.remove_addedremoves a source's faces, for example when a document closes.
Changes
rescanscans again, reading only new and changed files.installed_fonts_changedcompares the modification times of every folder the scan read (and the folders of files a font service named) and, on Windows, the registry's font list. It costs a few hundredstatcalls, not a scan.rescan_if_changeddoes both; apps call it when their window is activated.watch(interval, on_change)polls on a background thread.generationcounts the catalog's changes.
One enumeration for the UI and documents
With the system feature, system::collection() returns the process's fontique Collection: the system's fonts and the font store's. The catalog lists its files, and craft-ui's font fallback can query the same collection instead of building its own, so the system's fonts are enumerated once per process. A rescan builds a new collection; system::generation() tells holders of an older clone to take a new one.
Try it
cargo run -p craft-font-catalog --example fonts # counts, folders, faces outside them
cargo run -p craft-font-catalog --example fonts -- Arial # a family's faces
cargo run -p craft-font-catalog --example fonts -- --watch # report fonts installed or removed