Skip to content

Commit 225aa01

Browse files
committed
[SVG text] Implement the SVG 2 text layout algorithm
https://bugs.webkit.org/show_bug.cgi?id=327817 rdar://problem/190204825 Reviewed by NOBODY (OOPS!). The old SVGTextLayoutEngine is built around assumptions of the legacy line layout. We can do better. This patch adds Layout::SVGTextLayout which implements the text layout algorithm as described by the SVG 2 specification (https://w3c.github.io/svgwg/svg2-draft/text.html#TextLayoutAlgorithm). The code maps directly to spec language making it much more readable than the old code. It also performs better. The old implementation measured text repeatedly, checked grapheme cluster boundaries for every character and used hash maps for temporary storage. The new one caches the measurements, skips the cluster checks for 8-bit text and uses index-based storage. On a microbenchmark with 10k text labels text changes are about 25% faster. Speedometer Charts-observable-plot improves by about 1% and React-Stockcharts-SVG by about 0.5%. The new path is behind the internal SVG2TextLayoutAlgorithmEnabled flag, off by default for now. * LayoutTests/svg/text/textLength-spacing-absolute-positions-expected.txt: Added. * LayoutTests/svg/text/textLength-spacing-absolute-positions.html: Added. * Source/WTF/Scripts/Preferences/UnifiedWebPreferences.yaml: * Source/WebCore/Sources.txt: * Source/WebCore/WebCore.xcodeproj/project.pbxproj: * Source/WebCore/layout/formattingContexts/inline/SVGTextLayout.cpp: Added. * Source/WebCore/layout/formattingContexts/inline/SVGTextLayout.h: Added. * Source/WebCore/rendering/svg/RenderSVGInlineText.cpp: (WebCore::RenderSVGInlineText::setTextInternal): (WebCore::RenderSVGInlineText::styleDidChange): (WebCore::RenderSVGInlineText::updateScaledFont): Invalidate the cached text metrics. * Source/WebCore/rendering/svg/RenderSVGInlineText.h: * Source/WebCore/rendering/svg/RenderSVGText.cpp: (WebCore::RenderSVGText::shouldHandleSubtreeMutations const): (WebCore::RenderSVGText::subtreeTextDidChange): (WebCore::RenderSVGText::layout): Skip the layout attribute building with the new path. (WebCore::RenderSVGText::computePerCharacterLayoutInformation): (WebCore::RenderSVGText::usesSVGTextLayout const): * Source/WebCore/rendering/svg/RenderSVGText.h: * Source/WebCore/rendering/svg/SVGTextLayoutEngine.cpp: (WebCore::SVGTextLayoutEngine::layoutTextOnLineOrPath): * Source/WebCore/rendering/svg/SVGTextLayoutEngineBaseline.cpp: (WebCore::SVGTextLayoutEngineBaseline::dominantBaselineToAlignmentBaseline const): (WebCore::SVGTextLayoutEngineBaseline::calculateAlignmentBaselineShift const): Take the style instead of the renderer so layout boxes can use it. * Source/WebCore/rendering/svg/SVGTextLayoutEngineBaseline.h:
1 parent 71c793a commit 225aa01

14 files changed

Lines changed: 1325 additions & 24 deletions
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
abc
2+
abc
3+
😀😀😀
4+
😀
5+
XXXX
6+
7+
PASS Absolute x positions are not moved by textLength spacing
8+
PASS textLength spacing is distributed between the characters of a single chunk
9+
PASS textLength spacing on a path counts characters, not code units
10+
PASS textLength with lengthAdjust=spacingAndGlyphs scales the whole element when x is a list
11+
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<!DOCTYPE html><!-- webkit-test-runner [ SVG2TextLayoutAlgorithmEnabled=true ] -->
2+
<meta charset="utf-8">
3+
<title>textLength with absolutely positioned characters</title>
4+
<script src="../../resources/testharness.js"></script>
5+
<script src="../../resources/testharnessreport.js"></script>
6+
<svg width="600" height="200" font-family="Ahem" font-size="20">
7+
<text id="absolute" x="10 100 200" y="30" textLength="300" lengthAdjust="spacing">abc</text>
8+
<text id="single" x="10" y="60" textLength="300" lengthAdjust="spacing">abc</text>
9+
<path id="path" d="M 10 150 L 590 150"/>
10+
<text id="onPath"><textPath href="#path" textLength="300" lengthAdjust="spacing">&#x1F600;&#x1F600;&#x1F600;</textPath></text>
11+
<text id="reference" x="10" y="190">&#x1F600;</text>
12+
<text id="glyphs" x="0 50 100 150" y="120" font-size="50" textLength="100" lengthAdjust="spacingAndGlyphs">XXXX</text>
13+
</svg>
14+
<script>
15+
function startX(id, index)
16+
{
17+
return document.getElementById(id).getStartPositionOfChar(index).x;
18+
}
19+
20+
test(() => {
21+
assert_approx_equals(startX("absolute", 0), 10, 0.01);
22+
assert_approx_equals(startX("absolute", 1), 100, 0.01);
23+
assert_approx_equals(startX("absolute", 2), 200, 0.01);
24+
}, "Absolute x positions are not moved by textLength spacing");
25+
26+
test(() => {
27+
assert_approx_equals(startX("single", 0), 10, 0.01);
28+
assert_approx_equals(startX("single", 1), 150, 0.01);
29+
assert_approx_equals(startX("single", 2), 290, 0.01);
30+
}, "textLength spacing is distributed between the characters of a single chunk");
31+
32+
test(() => {
33+
const advance = document.getElementById("reference").getComputedTextLength();
34+
// Each emoji is a surrogate pair. Spacing goes between the three characters, not the six code units.
35+
const spacing = (300 - 3 * advance) / 2;
36+
assert_approx_equals(startX("onPath", 2) - startX("onPath", 0), advance + spacing, 0.5);
37+
assert_approx_equals(startX("onPath", 4) - startX("onPath", 0), 2 * (advance + spacing), 0.5);
38+
}, "textLength spacing on a path counts characters, not code units");
39+
40+
test(() => {
41+
// The x values are the natural advances. The whole element scales to half, like without the x list.
42+
const text = document.getElementById("glyphs");
43+
for (let i = 0; i < 4; ++i) {
44+
const extent = text.getExtentOfChar(i);
45+
assert_approx_equals(extent.x, 25 * i, 0.01, `character ${i} x`);
46+
assert_approx_equals(extent.width, 25, 0.01, `character ${i} width`);
47+
}
48+
}, "textLength with lengthAdjust=spacingAndGlyphs scales the whole element when x is a list");
49+
</script>

‎Source/WTF/Scripts/Preferences/UnifiedWebPreferences.yaml‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5245,6 +5245,13 @@ SKAttributionEnabled:
52455245
humanReadableDescription: "SKAttribution"
52465246
defaultValue: true
52475247

5248+
SVG2TextLayoutAlgorithmEnabled:
5249+
type: bool
5250+
status: internal
5251+
humanReadableName: "SVG 2 text layout algorithm"
5252+
humanReadableDescription: "Use the SVG 2 text layout algorithm for SVG text"
5253+
defaultValue: false
5254+
52485255
SVGExternalResourcesEnabled:
52495256
type: bool
52505257
status: stable

‎Source/WebCore/Sources.txt‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2122,6 +2122,7 @@ layout/formattingContexts/inline/InlineTextItem.cpp @header:RenderStyleGetters
21222122
layout/formattingContexts/inline/IntrinsicWidthHandler.cpp @header:RenderStyleGetters
21232123
layout/formattingContexts/inline/RangeBasedLineBuilder.cpp @header:RenderStyleGetters
21242124
layout/formattingContexts/inline/SVGTextDisplayContentBuilder.cpp @header:RenderStyleGetters
2125+
layout/formattingContexts/inline/SVGTextLayout.cpp @header:RenderStyleGetters
21252126
layout/formattingContexts/inline/TextOnlySimpleLineBuilder.cpp @header:RenderStyleGetters
21262127
layout/formattingContexts/inline/display/InlineDisplayContent.cpp
21272128
layout/formattingContexts/inline/display/InlineDisplayContentBuilder.cpp @header:RenderStyleGetters

‎Source/WebCore/WebCore.xcodeproj/project.pbxproj‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6971,6 +6971,7 @@
69716971
E403B7A2251B11930019E800 /* LayoutIntegrationCoverage.h in Headers */ = {isa = PBXBuildFile; fileRef = E403B7A1251B11930019E800 /* LayoutIntegrationCoverage.h */; };
69726972
E403B7A3251B11C10019E800 /* LayoutIntegrationPagination.h in Headers */ = {isa = PBXBuildFile; fileRef = E4312AB724B3265600678349 /* LayoutIntegrationPagination.h */; };
69736973
C814C569BBA6F36D17A949B9 /* SVGTextDisplayContentBuilder.h in Headers */ = {isa = PBXBuildFile; fileRef = 8C34B552C743CB4BA7256D9A /* SVGTextDisplayContentBuilder.h */; };
6974+
89ACE0522BC1BB06CFFF1775 /* SVGTextLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = 00580A55849484D03A89EE07 /* SVGTextLayout.h */; };
69746975
E406204C2E5F2EC0009144F4 /* StyleRuleFunction.h in Headers */ = {isa = PBXBuildFile; fileRef = E406204B2E5F2EC0009144F4 /* StyleRuleFunction.h */; };
69756976
E415EB13270C351300E8DA1B /* InlineIteratorInlineBox.h in Headers */ = {isa = PBXBuildFile; fileRef = E415EB11270C351200E8DA1B /* InlineIteratorInlineBox.h */; };
69766977
E418025523D4549B00FFB071 /* LayoutIntegrationBoxTreeUpdater.h in Headers */ = {isa = PBXBuildFile; fileRef = E418025323D4549A00FFB071 /* LayoutIntegrationBoxTreeUpdater.h */; };
@@ -23041,6 +23042,8 @@
2304123042
E4312AB724B3265600678349 /* LayoutIntegrationPagination.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LayoutIntegrationPagination.h; sourceTree = "<group>"; };
2304223043
5D3C5B1036F71428B78A1EE2 /* SVGTextDisplayContentBuilder.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = SVGTextDisplayContentBuilder.cpp; sourceTree = "<group>"; };
2304323044
8C34B552C743CB4BA7256D9A /* SVGTextDisplayContentBuilder.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SVGTextDisplayContentBuilder.h; sourceTree = "<group>"; };
23045+
B6422CEB4AEBB2DE83C8EDC7 /* SVGTextLayout.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = SVGTextLayout.cpp; sourceTree = "<group>"; };
23046+
00580A55849484D03A89EE07 /* SVGTextLayout.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SVGTextLayout.h; sourceTree = "<group>"; };
2304423047
E43A023A17EB370A004CDD25 /* RenderElement.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RenderElement.h; sourceTree = "<group>"; };
2304523048
E43A023C17EB3713004CDD25 /* RenderElement.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RenderElement.cpp; sourceTree = "<group>"; };
2304623049
E43AF8E41AC5B7DD00CA717E /* CacheValidation.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CacheValidation.cpp; sourceTree = "<group>"; };
@@ -25426,6 +25429,8 @@
2542625429
DDECCC1E2BABA8480041CD3C /* RangeBasedLineBuilder.h */,
2542725430
5D3C5B1036F71428B78A1EE2 /* SVGTextDisplayContentBuilder.cpp */,
2542825431
8C34B552C743CB4BA7256D9A /* SVGTextDisplayContentBuilder.h */,
25432+
B6422CEB4AEBB2DE83C8EDC7 /* SVGTextLayout.cpp */,
25433+
00580A55849484D03A89EE07 /* SVGTextLayout.h */,
2542925434
DDA3D5D02A7D847E000ECEE1 /* TextOnlySimpleLineBuilder.cpp */,
2543025435
DDA3D5CE2A7D844D000ECEE1 /* TextOnlySimpleLineBuilder.h */,
2543125436
);
@@ -50075,6 +50080,7 @@
5007550080
C814C569BBA6F36D17A949B9 /* SVGTextDisplayContentBuilder.h in Headers */,
5007650081
B2227ACB0D00BF220071B782 /* SVGTextElement.h in Headers */,
5007750082
08F0BFC41255C53C00075185 /* SVGTextFragment.h in Headers */,
50083+
89ACE0522BC1BB06CFFF1775 /* SVGTextLayout.h in Headers */,
5007850084
088C2F7A12390081003D65CE /* SVGTextLayoutAttributes.h in Headers */,
5007950085
081093DC1255F0E700ED9D29 /* SVGTextLayoutAttributesBuilder.h in Headers */,
5008050086
081668DA125603D5006F25DE /* SVGTextLayoutEngine.h in Headers */,

0 commit comments

Comments
 (0)