fix(text): bold and italic rich text runs no longer overlap following text - #908
Open
wouterlucas wants to merge 2 commits into
Open
wouterlucas wants to merge 2 commits into
wouterlucas wants to merge 2 commits into
Conversation
wouterlucas
force-pushed
the
fix/rich-text-styled-advance
branch
from
September 18, 2026 21:14
023abc0 to
e6bf739
Compare
wouterlucas
marked this pull request as ready for review
September 19, 2026 12:12
Styled spans were drawn with a styled face but advanced by unstyled metrics, so the run overlapped whatever came after it. With `richText: true`, 'The [b]brown[/b] fox' rendered as 'brownfox'. Canvas: the draw context was switched to the span's font, but the advance came from CanvasFontHandler.measureText, which ignores its fontFamily argument and measures against a shared context still set to the base font. A bold segment therefore advanced by its regular-face width. Added setMeasureFont/getMeasureFont so the measure context tracks the draw context, and restore the base font afterwards since the context is shared with font metric calculation and the plain text path. SDF: bold is faked by shifting the shader's alpha threshold and italic by shearing, neither of which touches xadvance. Bold now adds the width the threshold shift actually produces (2 * shift * distanceRange, centred on the advance), and an italic run pays for its shear overhang when it ends. Kerning is suppressed across a style boundary, where the pair adjustment from the regular face does not apply. Both renderers also correct line widths after layout. mapTextLayout is style-agnostic, so a styled line reported a width narrower than its drawn extent; that width sizes the Canvas texture and is published as the node's width, so fixing the advance without it would have traded overlap for clipping. Alignment offsets are recomputed alongside, since the non-left alignments reference the widest line. The shared formulas live in RichTextMetrics so measurement and drawing cannot drift apart; the SDF bold constant is defined next to a note pointing at the matching shader threshold. Line breaking is still base-font based, so a styled line can still wrap slightly early or late. That needs style-aware measurement inside the layout engine and is left to a follow-up.
wouterlucas
force-pushed
the
fix/rich-text-styled-advance
branch
from
September 19, 2026 13:11
e6bf739 to
97d7aa3
Compare
The certified snapshots encoded the bug. text-rich-canvas-9 showed "bold underline" and "and" drawn on top of each other, reading "underlineand", which is the defect this branch fixes. Recaptured in the CI container. Full suite: 209/209 pass. Only text-rich-* snapshots changed; no other test was affected.
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 2 of 4. Base is #907 — review that first; this diff is isolated against it.
Fixes the reported defect: with
richText: true,The [b]brown[/b] foxrenders asbrownfox.Root cause
Styled spans were drawn with a styled face but advanced by unstyled metrics.
Canvas. The draw context was switched to the span font (
CanvasTextRenderer.ts:286-289), but the advance came fromCanvasFontHandler.measureText, which ignores itsfontFamilyargument and measures against a shared context still set to the base font (CanvasFontHandler.ts:226-243, primed atCanvasTextRenderer.ts:146). A bold segment therefore advanced by its regular-face width. The deficit — typically 3-6% of segment width — consumes the following space.SDF. Bold is faked by shifting the shader alpha threshold (
SdfShader.ts:178,threshold = 0.5 - v_style * 0.05) and italic by shearing (ITALIC_SHEAR), neither of which touchesxadvance(SdfTextRenderer.ts:618). Milder than Canvas — crowding rather than overlap — but wrong.Changes
setMeasureFont/getMeasureFontonCanvasFontHandlerso the measure context tracks the draw context. Restored to the base font afterwards, since the context is shared withcalculateFontMetricsand the plain text path.2 * shift * distanceRange, centred on the advance by shiftingx1by half.RichTextMetricsmodule so measurement and drawing cannot drift apart. The SDF bold constant sits next to a note pointing at the matching shader threshold — changing one without the other reintroduces the bug.The width correction is not optional
effectiveMaxWidth(TextLayoutEngine.ts:110-121) is the longest base-font-measured line, and inCanvasTextRenderer.tsit both sizes the texture (:176,:192) and is published as the node width (:408).Fixing the advance alone would push drawn glyphs past
canvasW, trading overlap for right-edge clipping, and still under-report width to siblings. So both renderers re-measure each final line with span fonts and recomputeeffectiveWidthbefore the canvas is sized. Alignment offsets are recomputed alongside, since non-left alignments reference the widest line.Known limitation
Line breaking is still base-font based, so a styled line can wrap slightly early or late. That needs style-aware measurement inside the layout engine — PR 3 of this stack.
Testing
tsc --noEmitclean, 325 tests pass, eslint 0 errors. Adds 20 tests:RichTextMetricsunit coverage, plus aCanvasFontHandlersuite using a context stub whosemeasureTextdepends on the active font, pinning that a styled measurement ofThe [b]brown[/b] foxexceeds the base measurement.