RESOLVED FIXED266832
Curved SVG textpath doesn't render unicode text/font properly on Apple devices
https://bugs.webkit.org/show_bug.cgi?id=266832
Summary Curved SVG textpath doesn't render unicode text/font properly on Apple devices
Sudhakar Anivella
Reported 2023-12-22 19:46:31 PST
Created attachment 469183 [details] SVG textpath unicode rendering issue screenshots and sample SVG I am trying to render Unicode text (Telugu) on a curved SVG textPath. Text on textPath is not rendered properly on apple devices (Safari as well as Chrome), characters do not cluster as expected, where as text using rendered properly. Where as same SVG code is workign as expected with Firefox and Chrome on Linux. Refer attached screenshots and SVG sample.
Attachments
SVG textpath unicode rendering issue screenshots and sample SVG (176.29 KB, application/zip)
2023-12-22 19:46 PST, Sudhakar Anivella
no flags
Testcase (317.37 KB, image/svg+xml)
2023-12-23 08:26 PST, Ahmad Saleem
no flags
rendering in safari, firefox, chrome (117.80 KB, image/png)
2023-12-24 20:16 PST, Karl Dubost
no flags
Test Case - as attachment (317.36 KB, image/svg+xml)
2024-09-14 07:04 PDT, Ahmad Saleem
no flags
rendering in safari, firefox, chrome (558.81 KB, image/png)
2026-06-26 08:57 PDT, Karl Dubost
no flags
testcase-probe (1.94 KB, text/html)
2026-06-26 09:41 PDT, Karl Dubost
no flags
Ahmad Saleem
Comment 1 2023-12-23 08:26:48 PST
Created attachment 469190 [details] Testcase
Sudhakar Anivella
Comment 2 2023-12-23 19:16:23 PST
Comment on attachment 469190 [details] Testcase @ahmad.saleem792 I think testcase_266832.svg contains first line as blank line. You may want to remove it to prevent "XML Parsing Error: XML or text declaration not at start of entity"
Ahmad Saleem
Comment 3 2023-12-23 19:17:06 PST
(In reply to Sudhakar Anivella from comment #2) > Comment on attachment 469190 [details] > Testcase > > @ahmad.saleem792 I think testcase_266832.svg contains first line as blank > line. You may want to remove it to prevent "XML Parsing Error: XML or text > declaration not at start of entity" Yes - I marked it as 'obsolete' and added 'JSFiddle' in URL field. :-)
Sudhakar Anivella
Comment 4 2023-12-23 19:39:56 PST
Thank you!
Karl Dubost
Comment 5 2023-12-24 20:16:27 PST
Created attachment 469195 [details] rendering in safari, firefox, chrome Safari Technology Preview 184 19618.1.7.1.1 Firefox Nightly 123.0a1 12323.12.24 Google Chrome Canary 122.0.6205.0 6205.0
Radar WebKit Bug Importer
Comment 6 2023-12-29 19:47:12 PST
Ahmad Saleem
Comment 7 2024-09-14 07:04:58 PDT
Created attachment 472555 [details] Test Case - as attachment
kyle.hensel
Comment 8 2026-06-26 06:28:21 PDT
another excellent test case is https://mapmeld.com/text-path-problems
Karl Dubost
Comment 9 2026-06-26 08:57:31 PDT
Created attachment 480219 [details] rendering in safari, firefox, chrome Safari Technology Preview 26.0 (Release 246) 22625.1.20.19.1 Firefox Nightly 154.0a1 15426.6.25 Google Chrome Canary 151.0.7912.0 7912.0
Karl Dubost
Comment 10 2026-06-26 08:59:57 PDT
Vitor, Elika this last testcase has potentially interesting things.
Karl Dubost
Comment 11 2026-06-26 09:22:54 PDT
Ah this is about measurements relative to code point. in WebKit, on a path every character starts a new fragment WebKit needs to treat the text as a series of clusters and just not individual code point, with all their own width.
Karl Dubost
Comment 12 2026-06-26 09:41:00 PDT
Created attachment 480220 [details] testcase-probe And we can use diff --git a/Source/WebCore/rendering/svg/SVGTextBoxPainter.cpp b/Source/WebCore/rendering/svg/SVGTextBoxPainter.cpp index 94a33af7038a..b6645ceae273 100644 --- a/Source/WebCore/rendering/svg/SVGTextBoxPainter.cpp +++ b/Source/WebCore/rendering/svg/SVGTextBoxPainter.cpp @@ -672,6 +672,13 @@ void SVGTextBoxPainter<TextBoxPath>::paintText(const Style::ComputedStyle& style TextRun constructTextRun(StringView text, TextDirection direction, const Style::ComputedStyle& style, const SVGTextFragment& fragment) { + // BUG266832-DEBUG: confirm the painter re-shapes one code point at a time on a textPath. + // A lone combining mark here (length==1, U+09xx vowel sign etc.) is what makes the font emit a dotted circle. + auto debugSub = text.substring(fragment.characterOffset, fragment.length); + WTFLogAlways("[BUG266832-PAINT] charOffset=%u length=%u first=U+%04X last=U+%04X", + fragment.characterOffset, fragment.length, + debugSub.length() ? debugSub[0] : 0, debugSub.length() ? debugSub[debugSub.length() - 1] : 0); + TextRun run(text.substring(fragment.characterOffset, fragment.length), 0, /* xPos, only relevant with allowTabs=true */ 0, /* padding, only relevant for justified text, not relevant for SVG */ diff --git a/Source/WebCore/rendering/svg/SVGTextLayoutEngine.cpp b/Source/WebCore/rendering/svg/SVGTextLayoutEngine.cpp index 74c2799243af..a8af418d909e 100644 --- a/Source/WebCore/rendering/svg/SVGTextLayoutEngine.cpp +++ b/Source/WebCore/rendering/svg/SVGTextLayoutEngine.cpp @@ -132,6 +132,11 @@ void SVGTextLayoutEngine::recordTextFragment(InlineIterator::SVGTextBoxIterator return Vector<SVGTextFragment> { }; }).iterator->value; + // BUG266832-DEBUG: confirm path layout emits one single-character fragment per code point. + WTFLogAlways("[BUG266832-LAYOUT] inPath=%d charOffset=%u length=%u metricsOffset=%u x=%.1f y=%.1f width=%.2f", + m_inPathLayout, m_currentTextFragment.characterOffset, m_currentTextFragment.length, + m_currentTextFragment.metricsListOffset, m_currentTextFragment.x, m_currentTextFragment.y, m_currentTextFragment.width); + fragments.append(m_currentTextFragment); m_currentTextFragment = SVGTextFragment(); }
Karl Dubost
Comment 13 2026-06-26 09:42:01 PDT
It confirms the issue. [BUG266832-LAYOUT] inPath=0 charOffset=0 length=6 metricsOffset=0 x=0.0 y=0.0 width=57.88 [BUG266832-LAYOUT] inPath=1 charOffset=0 length=1 metricsOffset=0 x=30.2 y=112.8 width=14.36 [BUG266832-LAYOUT] inPath=1 charOffset=1 length=1 metricsOffset=1 x=31.5 y=97.6 width=16.21 [BUG266832-LAYOUT] inPath=1 charOffset=2 length=1 metricsOffset=2 x=33.6 y=85.1 width=9.10 [BUG266832-LAYOUT] inPath=1 charOffset=3 length=1 metricsOffset=3 x=35.7 y=76.3 width=9.10 [BUG266832-LAYOUT] inPath=1 charOffset=4 length=1 metricsOffset=4 x=38.3 y=67.5 width=9.10 [BUG266832-LAYOUT] inPath=1 charOffset=5 length=1 metricsOffset=5 x=39.7 y=63.2 width=0.00 [BUG266832-LAYOUT] inPath=0 charOffset=0 length=6 metricsOffset=0 x=20.0 y=45.0 width=57.88 [BUG266832-LAYOUT] inPath=0 charOffset=0 length=6 metricsOffset=0 x=0.0 y=0.0 width=46.72 [BUG266832-LAYOUT] inPath=1 charOffset=0 length=1 metricsOffset=0 x=30.0 y=116.1 width=7.79 [BUG266832-LAYOUT] inPath=1 charOffset=1 length=1 metricsOffset=1 x=30.4 y=108.3 width=7.79 [BUG266832-LAYOUT] inPath=1 charOffset=2 length=1 metricsOffset=2 x=31.1 y=100.6 width=7.79 [BUG266832-LAYOUT] inPath=1 charOffset=3 length=1 metricsOffset=3 x=32.2 y=92.9 width=7.79 [BUG266832-LAYOUT] inPath=1 charOffset=4 length=1 metricsOffset=4 x=33.6 y=85.2 width=7.79 [BUG266832-LAYOUT] inPath=1 charOffset=5 length=1 metricsOffset=5 x=35.3 y=77.6 width=7.79 [BUG266832-PAINT] charOffset=0 length=1 first=U+0065 last=U+0065 [BUG266832-PAINT] charOffset=1 length=1 first=U+0301 last=U+0301 [BUG266832-PAINT] charOffset=2 length=1 first=U+0065 last=U+0065 [BUG266832-PAINT] charOffset=3 length=1 first=U+0301 last=U+0301 [BUG266832-PAINT] charOffset=4 length=1 first=U+0065 last=U+0065 [BUG266832-PAINT] charOffset=5 length=1 first=U+0301 last=U+0301 [BUG266832-PAINT] charOffset=0 length=1 first=U+0928 last=U+0928 [BUG266832-PAINT] charOffset=1 length=1 first=U+092E last=U+092E [BUG266832-PAINT] charOffset=2 length=1 first=U+0938 last=U+0938 [BUG266832-PAINT] charOffset=3 length=1 first=U+094D last=U+094D [BUG266832-PAINT] charOffset=4 length=1 first=U+0924 last=U+0924 [BUG266832-PAINT] charOffset=5 length=1 first=U+0947 last=U+0947 [BUG266832-PAINT] charOffset=0 length=6 first=U+0928 last=U+0947 [BUG266832-LAYOUT] inPath=0 charOffset=0 length=6 metricsOffset=0 x=0.0 y=0.0 width=57.88 [BUG266832-LAYOUT] inPath=1 charOffset=0 length=1 metricsOffset=0 x=30.2 y=112.8 width=14.36 [BUG266832-LAYOUT] inPath=1 charOffset=1 length=1 metricsOffset=1 x=31.5 y=97.6 width=16.21 [BUG266832-LAYOUT] inPath=1 charOffset=2 length=1 metricsOffset=2 x=33.6 y=85.1 width=9.10 [BUG266832-LAYOUT] inPath=1 charOffset=3 length=1 metricsOffset=3 x=35.7 y=76.3 width=9.10 [BUG266832-LAYOUT] inPath=1 charOffset=4 length=1 metricsOffset=4 x=38.3 y=67.5 width=9.10 [BUG266832-LAYOUT] inPath=1 charOffset=5 length=1 metricsOffset=5 x=39.7 y=63.2 width=0.00 [BUG266832-LAYOUT] inPath=0 charOffset=0 length=6 metricsOffset=0 x=20.0 y=45.0 width=57.88 [BUG266832-LAYOUT] inPath=0 charOffset=0 length=6 metricsOffset=0 x=0.0 y=0.0 width=46.72 [BUG266832-LAYOUT] inPath=1 charOffset=0 length=1 metricsOffset=0 x=30.0 y=116.1 width=7.79 [BUG266832-LAYOUT] inPath=1 charOffset=1 length=1 metricsOffset=1 x=30.4 y=108.3 width=7.79 [BUG266832-LAYOUT] inPath=1 charOffset=2 length=1 metricsOffset=2 x=31.1 y=100.6 width=7.79 [BUG266832-LAYOUT] inPath=1 charOffset=3 length=1 metricsOffset=3 x=32.2 y=92.9 width=7.79 [BUG266832-LAYOUT] inPath=1 charOffset=4 length=1 metricsOffset=4 x=33.6 y=85.2 width=7.79 [BUG266832-LAYOUT] inPath=1 charOffset=5 length=1 metricsOffset=5 x=35.3 y=77.6 width=7.79 [BUG266832-PAINT] charOffset=0 length=1 first=U+0065 last=U+0065 [BUG266832-PAINT] charOffset=1 length=1 first=U+0301 last=U+0301 [BUG266832-PAINT] charOffset=2 length=1 first=U+0065 last=U+0065 [BUG266832-PAINT] charOffset=3 length=1 first=U+0301 last=U+0301 [BUG266832-PAINT] charOffset=4 length=1 first=U+0065 last=U+0065 [BUG266832-PAINT] charOffset=5 length=1 first=U+0301 last=U+0301 [BUG266832-PAINT] charOffset=0 length=1 first=U+0928 last=U+0928 [BUG266832-PAINT] charOffset=1 length=1 first=U+092E last=U+092E [BUG266832-PAINT] charOffset=2 length=1 first=U+0938 last=U+0938 [BUG266832-PAINT] charOffset=3 length=1 first=U+094D last=U+094D [BUG266832-PAINT] charOffset=4 length=1 first=U+0924 last=U+0924 [BUG266832-PAINT] charOffset=5 length=1 first=U+0947 last=U+0947 [BUG266832-PAINT] charOffset=0 length=6 first=U+0928 last=U+0947 [BUG266832-PAINT] charOffset=0 length=1 first=U+0928 last=U+0928 [BUG266832-PAINT] charOffset=1 length=1 first=U+092E last=U+092E [BUG266832-PAINT] charOffset=2 length=1 first=U+0938 last=U+0938 [BUG266832-PAINT] charOffset=3 length=1 first=U+094D last=U+094D [BUG266832-PAINT] charOffset=4 length=1 first=U+0924 last=U+0924 [BUG266832-PAINT] charOffset=5 length=1 first=U+0947 last=U+0947 [BUG266832-PAINT] charOffset=0 length=6 first=U+0928 last=U+0947 [BUG266832-PAINT] charOffset=0 length=1 first=U+0065 last=U+0065 [BUG266832-PAINT] charOffset=1 length=1 first=U+0301 last=U+0301 [BUG266832-PAINT] charOffset=2 length=1 first=U+0065 last=U+0065 [BUG266832-PAINT] charOffset=3 length=1 first=U+0301 last=U+0301 [BUG266832-PAINT] charOffset=4 length=1 first=U+0065 last=U+0065 [BUG266832-PAINT] charOffset=5 length=1 first=U+0301 last=U+0301 [BUG266832-PAINT] charOffset=0 length=1 first=U+0065 last=U+0065 [BUG266832-PAINT] charOffset=1 length=1 first=U+0301 last=U+0301 [BUG266832-PAINT] charOffset=2 length=1 first=U+0065 last=U+0065 [BUG266832-PAINT] charOffset=3 length=1 first=U+0301 last=U+0301 [BUG266832-PAINT] charOffset=4 length=1 first=U+0065 last=U+0065 [BUG266832-PAINT] charOffset=5 length=1 first=U+0301 last=U+0301 [BUG266832-PAINT] charOffset=0 length=1 first=U+0928 last=U+0928 [BUG266832-PAINT] charOffset=1 length=1 first=U+092E last=U+092E [BUG266832-PAINT] charOffset=2 length=1 first=U+0938 last=U+0938 [BUG266832-PAINT] charOffset=3 length=1 first=U+094D last=U+094D [BUG266832-PAINT] charOffset=4 length=1 first=U+0924 last=U+0924 [BUG266832-PAINT] charOffset=5 length=1 first=U+0947 last=U+0947 [BUG266832-PAINT] charOffset=0 length=6 first=U+0928 last=U+0947
Karl Dubost
Comment 14 2026-06-26 09:57:01 PDT
1. One fragment per code point on a path six length=1 fragments at six distinct arc positions, vs a single length=6 fragment for plain text. (Predicted from SVGTextLayoutEngine.cpp:633.) 2. "Correct width" explained 14.36+16.21+9.10+9.10+9.10+0.00 = 57.87 ≈ 57.88 plain. Cluster advance preserved in total, smeared across code points, each planted at its own offset. 3. Dotted circles caught at the source U+094D (virama) and U+0947 (vowel sign E) handed to the shaper alone; plain text shapes all six together in one call. (Confirms SVGTextBoxPainter.cpp:675.) 4. Latin é shows the identical split same defect, subtler symptom.
Karl Dubost
Comment 15 2026-06-26 10:02:42 PDT
WebKit places code points today instead it must place typographic characters. https://w3c.github.io/svgwg/svg2-draft/single-page.html#text-TSpanNotes > The ‘x’, ‘y’, ‘dx’, ‘dy’, and ‘rotate’ on the ‘text’ and ‘tspan’ elements are useful in high-end typography scenarios where individual glyphs require exact placement. Then the spec defines a set of rules for each cases.
Karl Dubost
Comment 16 2026-06-29 01:35:02 PDT
EWS
Comment 17 2026-06-30 07:21:11 PDT
Committed 316144@main (ef2bac73ff93): <https://commits.webkit.org/316144@main> Reviewed commits have been landed. Closing PR #68085 and removing active labels.
Karl Dubost
Comment 18 2026-06-30 07:28:16 PDT
Submitted web-platform-tests pull request: https://github.com/web-platform-tests/wpt/pull/60996
Karl Dubost
Comment 19 2026-08-17 02:06:58 PDT
Similar issue on Bug 73354
Note You need to log in before you can comment on or make changes to this bug.