Back to hub
Status: Shipped for Text v1 Author: Claude Opus 4.6
Overview
The <textPath> element renders text along an arbitrary path referenced by href. Donner supports the core textPath feature set: basic glyph-on-path positioning, startOffset, text-anchor, baseline-shift perpendicular to the path, letter-spacing, rotate, and transforms on the referenced path element.
Supported Attributes
| Attribute | Type | Default | Status |
| href / xlink:href | <IRI> | required | Shipped |
| startOffset | <length> or <percentage> | 0 | Shipped |
| method | align | stretch | align | align only; stretch deferred |
| side | left | right | left | left only; right deferred (SVG2) |
| spacing | auto | exact | exact | exact only; auto deferred |
Architecture
Path Resolution (TextSystem::resolveTextPath)
When a <textPath> span is encountered during instantiateComputedComponent:
- Resolve href — Look up the target element via Reference::resolve(). If the href is empty, the reference is invalid, or the target has no path data, the span is marked textPathFailed = true and its glyphs are hidden (SVG spec §10.12.1).
- Extract path geometry — Get the Path from the target's ComputedPathComponent, or parse it from the PathComponent's d attribute as a fallback.
- Apply referenced element transform — If the referenced <path> element has a ComputedLocalTransformComponent, the path points are transformed into the parent coordinate space. This ensures text follows the path as it visually appears.
- Resolve startOffset — Convert the startOffset attribute to pixels. Percentages are resolved against the (potentially transformed) path length.
- Store results — The resolved Path and pathStartOffset (in pixels) are stored on the ComputedTextComponent::TextSpan.
Glyph Positioning (TextEngine::layout)
During the per-span layout loop, spans with pathData set enter the text-on-path branch:
- text-anchor — Compute total text advance (including kerning and inter-glyph letter-spacing) and shift the effective startOffset: middle shifts by -advance/2, end by -advance. Path-based runs are marked onPath = true so the post-loop applyTextAnchor() skips them (anchor is already applied along the path, not linearly).
- baseline-shift — The combined dominant-baseline and per-span baseline-shift offset is applied perpendicular to the path tangent using the direction (sin(θ), -cos(θ)).
- Per-glyph positioning — For each glyph:
- Apply within-run kerning (xKern) to the advance accumulator before sampling.
- Sample the path at startOffset + advanceAccum + xAdvance/2 to get position and angle.
- Place the glyph origin shifted back along the tangent by half the advance width.
- Apply the perpendicular baseline offset.
- Set rotation to the tangent angle plus any per-character rotation from rotateList.
- Glyphs past the path end are hidden (glyphIndex = 0).
- Letter-spacing — Added to the inter-glyph advance accumulation (not after the last glyph), so characters spread along the path with correct spacing.
- Nested textPath rejection — findApplicableTextPathEntity() stops searching when it encounters any <textPath> element, even if it's not a valid direct child of <text>. Content inside a nested (invalid) textPath is marked textPathFailed and hidden.
Character endpoints
Character endpoint queries accumulate a path cluster's signed advance vectors from its first alignment point. The direction includes the writing mode, authored rotation, and path rotation; zero-advance marks retain the cluster endpoint. Both aligned and stretched text use the placed advance direction, including when the outline follows a bend.
//donner/svg/tests:svg_tests and its _text_full and _geode variants cover vertical and sloped paths, authored rotation, upright CJK with combining marks, and a contour-corner control for getEndPositionOfChar. The controls also retain unrotated flat-text behavior and authored textLength checks.
Error Handling
- Missing/invalid href: Span marked textPathFailed, glyphs hidden entirely.
- Empty path: Same behavior — resolveTextPath returns early, span marked failed.
- Path overflow: Glyphs past the path end are hidden via glyphIndex = 0.
Test Coverage
The e-textPath-* resvg test suite remains enabled, but custom goldens are now restricted to the human-approved pre-existing set only.
Custom goldens and thresholds
Any golden or tolerance change requires explicit human approval after visual retriage.
| Tests | Reason |
| 001-005 | Minor char advance diffs |
| 009-010 | Minor char advance diffs |
| 011 | AA diffs |
| 013-014 | Minor char advance diffs |
| 015 | AA diffs |
| 019-020 | Minor char advance diffs / AA diffs |
| 022-023 | Minor char advance diffs |
| 026-029 | Minor char advance diffs |
| 032, 035-037 | Minor char advance diffs |
| 034 | AA artifacts (WithThreshold 0.05) |
Skipped Tests
| Test | Reason |
| 007 | method=stretch not implemented |
| 008 | spacing=auto not implemented |
| 012 | Reference disagreement: SVG 2 geometric endpoint continuation |
| 016 | Link to <rect> (SVG 2 feature) |
| 021 | Vertical path frames supported; upright CJK origin parity remains |
| 030 | Vertical text + circular path (deferred) |
| 033 | UB: baseline-shift + rotate interaction |
| 040 | Filter on textPath not implemented |
| 041 | side=right (SVG 2 feature) |
| 042 | path attribute (SVG 2 feature) |
| 043-044 | path + href interaction (SVG 2) |
Key Files
Future Work
Open Issues
- 012: Mixed textPath/tspan/textPath continuation follows the SVG 2 text layout algorithm: unanchored text after each path starts at that path's geometric endpoint. The historical reference instead continues from the occupied text extent, so it remains skipped. Independent renderer controls compare both continuations against explicit endpoint positions in //donner/svg/renderer/tests:text_path_convergence_tests and its _geode and _text_full variants.
Deferred Features
- Implement method=stretch for glyph stretching along path curvature.
- Implement spacing=auto for automatic inter-character spacing on path.
- Support side=right (SVG 2) for text on the opposite side of the path.
- Support path attribute (SVG 2) for inline path data.
- Handle text across discontinuous subpaths correctly.
- Map vertical inline advances, glyph offsets, rotation, and textLength into the path frame. //donner/svg/renderer/tests:text_engine_scripted_tests covers upright glyph offsets, cluster rotation, and textLength. //donner/svg/renderer/tests:text_path_convergence_tests and its _geode and _text_full variants compare the straight-path vertical layout and textLength against ordinary vertical text, with visible-ink negative controls.
- Resolve upright CJK glyph-origin differences between text backends and the reference.