An immutable parsed-font registry held as a NIF resource.
Fonts are parsed once at construction and shared across sessions —
build one set at boot and hand it to every Mead.Session. The bundled
Liberation Sans family (regular/bold/italic/bold-italic, OFL) is always
registered as the "default" family, used when a node has no
font_family or names an unknown one.
set =
Mead.FontSet.new(%{
"Inter" => [
%{path: "fonts/Inter-Regular.ttf"},
%{path: "fonts/Inter-Bold.ttf", weight: 700},
%{path: "fonts/Inter-Italic.ttf", italic: true}
]
})
session = Mead.Session.new(set)
Mead.render(doc, session: session)Variant selection matches font_family exactly, then picks the nearest
font_weight with matching italic (italic mismatches are heavily
penalized, unknown families fall back to "default").
Fallback chains
When a face lacks a glyph, the missing characters are drawn from the
first family in the :fallback chain that covers them (per cluster —
the rest of the text keeps its primary face). The default family is
always the chain's implicit last entry, so declaring a chain only ever
adds coverage:
set =
Mead.FontSet.new(
%{
"Inter" => [%{path: "fonts/Inter-Regular.ttf"}],
"Noto Sans SC" => [%{path: "fonts/NotoSansSC-Regular.otf"}]
},
fallback: ["Noto Sans SC"]
)Without a chain, a glyph missing from every stacked face renders as
the primary face's .notdef (tofu).
Lazy fallback pool
:pool points at font files or directories on disk (e.g. an installed
Noto family) that back a last-resort, per-script fallback tail. Pool
files are scanned once at build time — coverage metadata only, no
resident font data — and a face is loaded lazily the first time a
document actually needs its script, then cached with a bounded LRU.
Resident memory stays proportional to what documents use, not the pool
size:
set =
Mead.FontSet.new(
%{"Inter" => [%{path: "fonts/Inter-Regular.ttf"}]},
pool: ["/usr/share/fonts/google-noto"]
)Declared families (and the :fallback chain) always win; the pool is
only consulted for scripts no declared face covers. Selection is fixed
at build time, so cache eviction can never change which face renders a
glyph. Scripts are detected against per-script sample characters, so a
pool of heavily subsetted fonts may go undetected — point the pool at
complete font files.
Summary
Functions
Builds a set where a single font file is the entire default family.
Used by the :font render option (mainly tests with the block font).
Builds a set from %{family => [variant]}, where each variant is a map
with :path or :data, plus optional :weight (default 400),
:italic (default false), and :index (default 0) — the face index
for TrueType/OpenType collections (.ttc), which is extracted into a
standalone face at build time. The bundled default family is always
included.
Types
Functions
Builds a set where a single font file is the entire default family.
Used by the :font render option (mainly tests with the block font).
Builds a set from %{family => [variant]}, where each variant is a map
with :path or :data, plus optional :weight (default 400),
:italic (default false), and :index (default 0) — the face index
for TrueType/OpenType collections (.ttc), which is extracted into a
standalone face at build time. The bundled default family is always
included.
Options
:fallback- ordered list of declared family names tried, in order, for glyphs the primary face lacks (see "Fallback chains" above). Every name must be a family declared infonts.:pool- font files or directories backing the lazy per-script fallback tail (see "Lazy fallback pool" above). Paths must exist.