Skip to content

LibWeb: Advance the SVG current text position across text runs - #11030

Merged
AtkinsSJ merged 1 commit into
LadybirdBrowser:masterfrom
sideshowbarker:svg-tspan-text-advance
Aug 7, 2026
Merged

LibWeb: Advance the SVG current text position across text runs#11030
AtkinsSJ merged 1 commit into
LadybirdBrowser:masterfrom
sideshowbarker:svg-tspan-text-advance

Conversation

@sideshowbarker

@sideshowbarker sideshowbarker commented Aug 7, 2026

Copy link
Copy Markdown
Member

Problem: All <tspan> elements without explicit positions rendered on top of each other, at the position where their parent <text> element starts.

Cause: SVG text layout never advanced the current text position by the advance of each rendered text run. The position handed to a text box’s children was the position before any of the box’s own glyphs got placed. And between sibling boxes, the position was clobbered with the bottom-right corner of the previous box’s ink bounding box. That differs from the glyph advance, and is wrong in y — since the pen must stay on the baseline. In addition, text-anchor was applied separately to every box’s own text run. So, anchoring a <text> whose content is split across <tspan>s shifted each run individually, instead of the whole text chunk.

Fix: Track the current text position as the spec defines it: each text run advances the position by its own advance; a <tspan> without x/y continues at the current position; and a <text> element always starts at its own absolute position — since x and y default to 0 for <text>. Apply text-anchor per text chunk: When a box starts a chunk (every <text>, and any <tspan> with an absolute position), measure the total advance of all runs in the chunk, and shift the chunk’s starting position once — instead of shifting every run separately. Fixes #10625.

Problem: All <tspan> elements without explicit positions rendered on top
of each other, at the position where their parent <text> element starts.

Cause: SVG text layout never advanced the current text position by the
advance of each rendered text run. The position handed to a text box’s
children was the position before any of the box’s own glyphs got placed.
And between sibling boxes, the position was clobbered with the bottom-
right corner of the previous box’s ink bounding box. That differs from
the glyph advance, and is wrong in y — since the pen must stay on the
baseline. In addition, “text-anchor” was applied separately to every
box’s own text run. So, anchoring a <text> whose content is split across
<tspan>s shifted each run individually, instead of the whole text chunk.

Fix: Track the current text position as the spec defines it: each text
run advances the position by its own advance; a <tspan> without 'x'/'y'
continues at the current position; and a <text> element always starts at
its own absolute position — since 'x' and 'y' default to 0 for <text>.
Apply “text-anchor” per text chunk: When a box starts a chunk (every
<text>, and any <tspan> with an absolute position), measure the total
advance of all runs in the chunk, and shift the chunk’s starting
position once — instead of shifting every run separately.

Fixes LadybirdBrowser#10625
@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

SVG text layout now measures text chunks across descendants, applies chunk-level anchoring and positioning, and propagates the resulting position through Rust layout. New and updated tests validate tspan placement, rendering, and bounding rectangles.

Changes

SVG text positioning

Layer / File(s) Summary
Chunk measurement and anchoring
Libraries/LibWeb/Layout/LayoutRustBridge.cpp
Measures descendant text advances, detects chunks, applies positioning attributes and text-anchor, and generates paths from adjusted positions.
Text position propagation
Libraries/LibWeb/Layout/LayoutRustBridge.cpp, Libraries/LibWeb/Rust/src/layout/svg_formatting_context.rs
Renames the returned position field to text_position_after and uses it for descendant and following text layout.
SVG text positioning validation
Tests/LibWeb/Layout/expected/svg/svg-text-positioning.txt, Tests/LibWeb/Ref/..., Tests/LibWeb/Text/expected/...
Updates layout expectations and adds rendering coverage for tspan positioning, anchoring, and bounding rectangles.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant SVGTextElement
  participant LayoutRustBridge
  participant SVGFormattingContext
  SVGTextElement->>LayoutRustBridge: provide text positioning attributes
  LayoutRustBridge->>LayoutRustBridge: measure chunks and apply text-anchor
  LayoutRustBridge-->>SVGFormattingContext: return text_position_after
  SVGFormattingContext->>SVGFormattingContext: position descendant and following text
Loading

Possibly related PRs

Suggested reviewers: kalenikaliaksandr

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes address issue #10625 by advancing tspan text positions and preserving correct spacing across consecutive runs.
Out of Scope Changes check ✅ Passed All code and test changes support the linked SVG tspan positioning and spacing objectives.
Description check ✅ Passed The description clearly explains the SVG text layout changes, their cause, the fix, and the linked issue.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@AtkinsSJ
AtkinsSJ merged commit 4fd3989 into LadybirdBrowser:master Aug 7, 2026
15 checks passed
@sideshowbarker
sideshowbarker deleted the svg-tspan-text-advance branch August 7, 2026 19:50
@BenWiederhake

Copy link
Copy Markdown
Contributor

Thank you! :)

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.

SVG tspan causes letter spacing to fail

3 participants