Skip to content

fix(text): share decoration geometry between Canvas and SDF renderers - #910

Draft
wouterlucas wants to merge 2 commits into
feat/style-aware-text-layoutfrom
fix/rich-text-decoration-consistency
Draft

wouterlucas wants to merge 2 commits into
feat/style-aware-text-layoutfrom
fix/rich-text-decoration-consistency

Conversation

@wouterlucas

Copy link
Copy Markdown
Contributor

Part 4 of 4. Base is #909review 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:

Canvas SDF
Underline gap fontSize * 0.08 (CanvasTextRenderer.ts:238) fontSize * 0.1 (SdfTextRenderer.ts:407)
Strikethrough 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 base value, 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.

⚠️ Requires snapshot regeneration

Visual regression snapshots covering SDF underline or strikethrough will need regenerating. I was unable to run pnpm test:visual locally (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_RATIO is 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 marking maxWidth. 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 --noEmit clean, 336 tests pass, eslint 0 errors. Adds 5 tests covering sub-pixel stroke clamping, underline gap floor and scaling, and strikethrough placement.

@wouterlucas
wouterlucas force-pushed the feat/style-aware-text-layout branch from 307035d to 44d9803 Compare September 18, 2026 21:17
@wouterlucas
wouterlucas force-pushed the fix/rich-text-decoration-consistency branch 2 times, most recently from 14d7688 to 77de409 Compare September 19, 2026 07:29
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
wouterlucas force-pushed the feat/style-aware-text-layout branch from d87b9ac to c665533 Compare September 19, 2026 13:11
@wouterlucas
wouterlucas force-pushed the fix/rich-text-decoration-consistency branch from 77de409 to 8f91526 Compare September 19, 2026 13:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant