fix(text): share decoration geometry between Canvas and SDF renderers - #910
Draft
wouterlucas wants to merge 2 commits into
Draft
wouterlucas wants to merge 2 commits into
wouterlucas wants to merge 2 commits into
Conversation
wouterlucas
force-pushed
the
feat/style-aware-text-layout
branch
from
September 18, 2026 21:17
307035d to
44d9803
Compare
wouterlucas
force-pushed
the
fix/rich-text-decoration-consistency
branch
2 times, most recently
from
September 19, 2026 07:29
14d7688 to
77de409
Compare
Underline and strikethrough offsets were defined independently per backend and disagreed, so identical markup rendered at different heights depending on which renderer was active: the underline gap was 8% of font size on Canvas and 10% on SDF, and the strikethrough sat at 60% of the top-to-baseline distance on Canvas but 75% on SDF. The offsets now come from RichTextMetrics. Each backend keeps its own baseline source, since those differ for good reason -- Canvas uses normalized font metrics, SDF uses the BMFont `base` value, which is more accurate for its atlases -- and only the offsets from the baseline are unified. The Canvas values were adopted, so SDF underline and strikethrough move slightly. Visual regression snapshots covering SDF underline or strikethrough will need regenerating.
Renders the same styled markup on the Canvas and SDF renderers so advance-width behaviour can be compared directly, with a guide box tracking each node's reported width and a rule marking maxWidth. Covers the reported defect (inline bold swallowing the following space), italic shear overhang, adjacent styled runs, trailing styled runs, wrapping driven by styled width, mid-word splits and centred alignment.
wouterlucas
force-pushed
the
feat/style-aware-text-layout
branch
from
September 19, 2026 13:11
d87b9ac to
c665533
Compare
wouterlucas
force-pushed
the
fix/rich-text-decoration-consistency
branch
from
September 19, 2026 13:11
77de409 to
8f91526
Compare
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.
Part 4 of 4. Base is #909 — review that first; this diff is isolated against it.
Problem
Underline and strikethrough offsets were defined independently per backend and disagreed, so identical markup rendered at different heights depending on which renderer was active:
fontSize * 0.08(CanvasTextRenderer.ts:238)fontSize * 0.1(SdfTextRenderer.ts:407)ascender - ascender * 0.4→ 60%base * 0.75→ 75%Approach
Offsets now come from
RichTextMetrics.Each backend keeps its own baseline source, since those differ for good reason — Canvas uses normalized font metrics, SDF uses the BMFont
basevalue, which is more accurate for its atlases and is already documented as such in a comment there. Only the offsets from the baseline are unified.The Canvas values were adopted, so SDF underline and strikethrough move slightly.
Visual regression snapshots covering SDF underline or strikethrough will need regenerating. I was unable to run
pnpm test:visuallocally (needs Docker/browser), so this has not been verified visually — please confirm before undrafting.On the strikethrough ratio specifically: a true strikethrough is centred on the x-height, but neither backend has x-height available, so it is approximated as a fraction of the top-to-baseline distance. 60% (Canvas) sits slightly high, 75% (SDF) slightly low relative to a computed ideal of roughly 69%. I adopted the Canvas value rather than inventing a third number, but if you would rather split the difference,
STRIKE_BASELINE_RATIOis a single constant.Also includes
examples/tests/text-rich-spacing.ts— renders the same styled markup on both renderers with a guide box tracking each node reported width and a rule markingmaxWidth. Covers the reported defect, italic shear overhang, adjacent styled runs, trailing styled runs, wrapping driven by styled width, mid-word splits, and centred alignment. Step through with →.Testing
tsc --noEmitclean, 336 tests pass, eslint 0 errors. Adds 5 tests covering sub-pixel stroke clamping, underline gap floor and scaling, and strikethrough placement.