feat(text): configureFonts() runtime font API (+ Hanken Grotesk sans-serif default) - #37
Merged
Merged
Conversation
Repoint FONT_FAMILY_MAP['sans-serif'] from Atkinson Hyperlegible Next to Hanken Grotesk as the primary face (generic system fallbacks unchanged), and load Hanken Grotesk instead of Atkinson in the playground's Google Fonts link so canvas measurement matches the loaded face. The default family for unstyled nodes is unchanged (still handwriting); this only changes what the sans-serif token renders. The font-epoch cache invalidation already repaints once the new face finishes loading.
Make the font stacks / sizes / line-heights customizable at runtime
instead of being fixed module constants.
- Add a live font registry in text/defaults.ts behind
getFontStack/getFontSizePx/getLineHeightPx getters, plus
configureFonts({ family?, size?, lineHeight? }) (partial deep-merge
over defaults) and resetFonts(). App-global by design — web fonts are
loaded document-globally, so a per-store stack override wouldn't map to
a real face. The consumer still loads the faces; the library names them.
- Route every resolution site (measure, layout, paint-canvas, renderer,
textarea editor, edge labels, PNG/SVG export, height estimate) through
the getters so measurement, paint, editing and export stay consistent.
- configureFonts() bumps the font epoch → measure cache clears + bitmap
cache invalidates (keyed on epoch) + mounted canvases repaint.
- Make font-epoch.ts a dependency-free leaf emitter (export bumpFontEpoch,
drop its measure import); measure now subscribes and clears itself.
Removes the import cycle the registry would otherwise create.
- Keep FONT_FAMILY_MAP / FONT_SIZE_MAP / LINE_HEIGHT_MAP as deprecated
aliases of the new DEFAULT_* consts for back-compat.
7 unit tests (override merge, canvas-font resolution, epoch bump, reset).
Full suites (core 358 unit + 64 browser, react 24), lint, typecheck pass.
- Ignore explicit-undefined override values in configureFonts (merge only
defined keys). A caller passing { M: cfg.customM } with an undefined
value would otherwise poison the registry → undefined size → NaN
geometry across measure/paint/editor/export.
- Don't latch fontTrackingInitialized when there's no document, so a later
call once the DOM exists can still attach the document.fonts listeners.
Matters now that measure.ts subscribes at import time rather than the
renderer at browser runtime.
- Correct the configureFonts / FontConfig.size docs: overrides apply to
text laid out after the call; already-persisted node w/h are not
reflowed (configure at startup, before nodes are created/loaded).
Adds a test for the undefined-override case. Full suites (core 359 unit +
64 browser, react 24), lint, typecheck pass.
Document the five font-family tokens, how to override a stack (or the sizes/line-heights) via configureFonts, the load-the-face-yourself caveat, and list the API in the core surface section.
winlp4ever
added a commit
that referenced
this pull request
Aug 7, 2026
#38) * revert(text): restore Atkinson Hyperlegible Next as the sans-serif default Hanken Grotesk (#37) read as flatter in practice — Atkinson Hyperlegible Next has more character, so restore it as the built-in sans-serif stack. The configureFonts() API from #37 stays; only the default face changes, so anyone who wants Hanken Grotesk can still opt in via configureFonts. Playground font link + the default-stack test updated to match. * docs(text): use Inter (not Hanken Grotesk) in the configureFonts examples Revert #38 removed Hanken Grotesk from the codebase; the override examples in the README + configureFonts/FontConfig doc-comments still referenced it, which read as a stray leftover. Use Inter as a neutral example font.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Exposes the font mapping as a runtime-customizable API so consumers can swap the font stacks / sizes / line-heights, with the built-in defaults preserved. Also sets the
sans-serifdefault to Hanken Grotesk (folds in the original small change so the two don't conflict).Public API
Also exported:
getFontStack/getFontSizePx/getLineHeightPx(resolvers),resetFonts(), andDEFAULT_FONT_STACKS/DEFAULT_FONT_SIZES/DEFAULT_LINE_HEIGHTS+ theFontConfigtype.How it works
text/defaults.tsbehind getters. Every resolution site — measure, layout, paint-canvas, renderer, textarea editor, edge labels, PNG/SVG export, height estimate — now reads through the getters, so measurement, paint, editing, and export can't drift apart.configureFonts()bumps the font epoch → measure cache clears, bitmap cache invalidates (keyed on the epoch), mounted canvases repaint.@font-face/ Google Fonts); the library only names them.font-epoch.tsis now a dependency-free leaf emitter (exportsbumpFontEpoch, no longer importsmeasure);measuresubscribes and clears itself. This removes the import cycle the registry would otherwise introduce.FONT_FAMILY_MAP/FONT_SIZE_MAP/LINE_HEIGHT_MAPkept as deprecated aliases of the newDEFAULT_*consts (back-compat; they reflect defaults, not overrides).Default change
sans-serifnow defaults to Hanken Grotesk; the playground loads it via Google Fonts.Tests
packages/core/tests/font-config.test.ts— 7 unit tests: default = Hanken Grotesk, partial family/size/line-height merges leave others untouched, override flows into the resolved canvas-font string, epoch bumps on configure,resetFonts()restores.Full suites: core 358 unit + 64 browser, react 24 browser+unit. Lint + typecheck clean.
No perf impact
Getter calls replace direct map indexing (both O(1)); render hot path unchanged.