Font-family matching (match-font) + cmap parse-once cache - #1
Conversation
… faces
Ship the Liberation Sans/Serif/Mono family (Regular/Bold/Italic/BoldItalic)
in fonts/ — the metric-compatible substitutes browsers use on Linux for
Arial/Helvetica, Times, and Courier — so a consumer measures browser-accurate
text widths.
- match-font (family-list &key weight style) -> open-font. Case-insensitive
CSS family mapping (generic + common Windows/macOS aliases), lazy open +
cache, weight>=600 -> Bold, italic/oblique -> Italic. Never returns NIL:
falls back through the family list to LiberationSans-Regular.
- font-ascent / font-descent / font-line-gap: hhea metric accessors for
line-height computation.
- FONT-LICENSES.md + fonts/LICENSE-Liberation.txt (SIL OFL 1.1).
Exported match-font + the metric accessors. Test suite stays green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JA1saK7BkgavHNeurmK65k
font-glyph-index rebuilt the entire cmap hash table on EVERY codepoint lookup — reading the whole subtable (thousands of entries) to map a single character. On text-heavy consumers (a web engine measuring every word, repeatedly, during table auto-layout) this is quadratic and dominates wall time. Parse the best subtable once into a new lazy %cmap slot (mirroring %cff/%fvar/%gdef); lookups are now a single gethash. Behavior-identical (same subtable, same mappings); glyph indices unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JA1saK7BkgavHNeurmK65k
|
Merged via rebase (linear history preserved; two clean commits). Review notes for the record: cmap cache — verified behavior-preserving: swept 34,816 codepoints (ASCII, BMP, and non-BMP incl. format-12 ranges) across LiberationSans/Serif/Mono + DejaVuSans, comparing the cached match-font — never returns NIL across exact/unknown/empty-list/string-arg/generic/DejaVu inputs; distinct objects for regular vs bold; exports Suite — Non-blocking follow-up: |
Two additions from driving a web engine (weft) against scribe — a new consumer-facing API and a performance fix that turned out to be load-bearing for text-heavy pages.
1.
match-font— CSS font-family matching (5edbe15)Adds a
match-font (family-list &key weight style) → fontservice so a consumer can resolve a CSSfont-familystack + weight + style to a concrete face, with noNILreturns (graceful fallback chain ending at Liberation Sans Regular).fonts/.font-ascent/font-descent/font-line-gap(hhea) so a consumer can computeline-height: normalfrom real metrics.Verified against a browser:
match-font('("Verdana" "sans-serif"))measures a fixed string at 420.7px, matching Chromium's Liberation-Sans substitution to the pixel.2. Parse the cmap once, cache it on the font (
b6f0012)font-glyph-indexrebuilt the entire cmap hash table on every codepoint lookup — reading the whole subtable (thousands of entries) to map a single character. On a consumer that measures every word repeatedly (a web engine's table auto-layout), this is quadratic and dominates wall time.Fix: parse the best subtable once into a new lazy
%cmapslot (mirroring the existing%cff/%fvar/%gdefcaches); lookups are now a singlegethash. Behavior-identical — same subtable, same mappings, glyph indices unchanged.Impact measured downstream (weft rendering a 40 KB Hacker News page): 7.5 s → 0.18 s render, layout 6.9 s → 20 ms (345×), output byte-for-byte unchanged.
🤖 Generated with Claude Code
https://claude.ai/code/session_01JA1saK7BkgavHNeurmK65k