feat: resolve rem/em font units to px before canvas measurement - #109
Open
okaris wants to merge 1 commit into
Open
feat: resolve rem/em font units to px before canvas measurement#109okaris wants to merge 1 commit into
okaris wants to merge 1 commit into
Conversation
Canvas ctx.font silently ignores relative CSS units like rem and em (spec limitation — WHATWG HTML #1682). Callers passing CSS-derived font strings such as "0.9rem Inter" get no measurement at all; canvas keeps the previous font and returns wrong widths. This adds resolveFont() which rewrites relative units to absolute px using getComputedStyle(document.documentElement).fontSize before the string reaches canvas. Results are cached at module level so the DOM read happens once. Environments without a document (OffscreenCanvas, Node) fall back to 16px. - getFontMeasurementState() now resolves the font before setting ctx.font - parseFontSize() resolves before extracting the px value - clearMeasurementCaches() clears the resolution cache - resolveFont() is exported for direct use by consumers
okaris
force-pushed
the
feat/resolve-relative-font-units
branch
from
July 26, 2026 13:21
66f926d to
961808b
Compare
Author
|
Rebased onto current The previous version accidentally included built What is left is the original change only:
Verified on the rebased branch: Happy to split it further or adjust the caching approach if you would prefer something different. |
okaris
added a commit
to inference-sh/shadcn-registry
that referenced
this pull request
Jul 26, 2026
The fork branch was 4 releases behind upstream (0.0.4 vs 0.0.8). Rebasing it onto upstream main brings in substantial text-layout work — analysis +29%, layout +20%, line-break +12%, plus a new line-text module — while keeping our rem/em canvas fix. Switches the ref from feat/ to built/resolve-relative-font-units. The feat/ branch is now the clean upstream PR (chenglou/pretext#109, 2 files) and no longer carries dist/. package.json points main/exports at ./dist and there is no prepare script, so a git dep needs the built output committed — the built/ branch tracks feat/ plus that output.
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.
Problem
Canvas
ctx.fontsilently ignores relative CSS units (rem,em). When callers pass CSS-derived font strings like"0.9rem Inter", canvas keeps its previous font and returns wrong widths. This is a spec limitation — canvas can exist without a document root, so relative units have no reference point (WHATWG HTML #1682).In practice, anyone using Tailwind or a CSS framework with custom
text-smsizes (e.g.--text-sm: 0.9reminstead of the default0.875rem) gets silently wrong measurements. The text measures at whatever font was previously set on the canvas context, not at the intended size.Related: #107 (canvas doesn't account for certain CSS properties).
Solution
Add
resolveFont()inmeasurement.tsthat rewrites relative units to absolute px before the string reaches canvas:Resolution uses
getComputedStyle(document.documentElement).fontSize— the same level of DOM access thatgetEmojiCorrection()already does. Results are cached at module level so the DOM read happens once. Environments without a document (OffscreenCanvas, Node.js) fall back to 16px.Changes
resolveFont(font)— exported, cached, handles rem/em → pxgetFontMeasurementState()— resolves font beforectx.font =and cache keyingparseFontSize()— resolves before extracting the px valueclearMeasurementCaches()— clears the resolution cache + cached root font sizeTest plan
bun test src/layout.test.ts— 85 tests pass (5 new)