2011-03-29 Nikolas Zimmermann <nzimmermann@rim.com>
authorzimmermann@webkit.org <zimmermann@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 30 Mar 2011 08:36:42 +0000 (08:36 +0000)
committerzimmermann@webkit.org <zimmermann@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 30 Mar 2011 08:36:42 +0000 (08:36 +0000)
        Reviewed by Eric Seidel.

        REGRESSION (r68976): Incorrect bidi rendering in SVG text
        https://bugs.webkit.org/show_bug.cgi?id=53980

        Deconvolute SVGTextLayoutEngine code, which was confusing due to the simultaneous processing of the rendered text
        in visual and logical order. Added several helper methods to make the code more readable.

        Fix Unicode directional formatting characters support, now works as expected.

        Test: svg/text/bidi-embedded-direction.svg

        * editing/visible_units.cpp: Refactor getLeafBoxesInLogicalOrder(), move to InlineFlowBox.
        (WebCore::getLogicalStartBoxAndNode): Use new collectLeafBoxesInLogicalOrder() method in InlineFlowBox.
        (WebCore::getLogicalEndBoxAndNode): Ditto.
        * rendering/InlineFlowBox.cpp: Add new helper function, that returns a list of all leaf boxes in logical order.
        (WebCore::InlineFlowBox::collectLeafBoxesInLogicalOrder):
        * rendering/InlineFlowBox.h:
        * rendering/svg/RenderSVGText.cpp: Actually trigger reordering the x/y/dx/dy/rotate value lists, if needed.
        (WebCore::RenderSVGText::RenderSVGText):
        (WebCore::RenderSVGText::layout):
        * rendering/svg/RenderSVGText.h: Ditto.
        (WebCore::RenderSVGText::layoutAttributes):
        (WebCore::RenderSVGText::needsReordering):
        * rendering/svg/SVGRootInlineBox.cpp: Use new InlineFlowBox::collectLeafBoxesINLogicalOrder(), with a custom "inline box reverse" implementation,
                                              which not only reverses the order of InlineBoxes, but also the order of the x/y/dx/dy/rotate value lists, if needed.
        (WebCore::SVGRootInlineBox::computePerCharacterLayoutInformation):
        (WebCore::SVGRootInlineBox::layoutCharactersInTextBoxes):
        (WebCore::swapItems):
        (WebCore::reverseInlineBoxRangeAndValueListsIfNeeded):
        (WebCore::SVGRootInlineBox::reorderValueLists):
        * rendering/svg/SVGRootInlineBox.h:
        * rendering/svg/SVGTextLayoutAttributes.cpp: Store RenderSVGInlineText* pointer, where we belong to.
        (WebCore::SVGTextLayoutAttributes::SVGTextLayoutAttributes):
        (WebCore::SVGTextLayoutAttributes::dump):
        * rendering/svg/SVGTextLayoutAttributes.h:
        (WebCore::SVGTextLayoutAttributes::context):
        * rendering/svg/SVGTextLayoutAttributesBuilder.cpp: Pass RenderSVGInlineText* object when creating SVGTextLayoutAttributes.
        (WebCore::SVGTextLayoutAttributesBuilder::buildLayoutAttributesForTextSubtree):
        (WebCore::SVGTextLayoutAttributesBuilder::propagateLayoutAttributes):
        * rendering/svg/SVGTextLayoutAttributesBuilder.h:
        * rendering/svg/SVGTextLayoutEngine.cpp: Rewrite & cleanup the main layout algorithm, to be less confusing.
        (WebCore::SVGTextLayoutEngine::SVGTextLayoutEngine):
        (WebCore::SVGTextLayoutEngine::updateRelativePositionAdjustmentsIfNeeded):
        (WebCore::SVGTextLayoutEngine::recordTextFragment):
        (WebCore::SVGTextLayoutEngine::currentLogicalCharacterAttributes):
        (WebCore::SVGTextLayoutEngine::currentLogicalCharacterMetrics):
        (WebCore::SVGTextLayoutEngine::currentVisualCharacterMetrics):
        (WebCore::SVGTextLayoutEngine::advanceToNextLogicalCharacter):
        (WebCore::SVGTextLayoutEngine::advanceToNextVisualCharacter):
        (WebCore::SVGTextLayoutEngine::layoutTextOnLineOrPath):
        * rendering/svg/SVGTextLayoutEngine.h:
2011-03-29  Nikolas Zimmermann  <nzimmermann@rim.com>

        Reviewed by Eric Seidel.

        REGRESSION (r68976): Incorrect bidi rendering in SVG text
        https://bugs.webkit.org/show_bug.cgi?id=53980

        Add testcase from bug 53980, assuring that BiDi works as well, when using the Unicode directional formatting characters.

        * platform/mac/svg/text/bidi-embedded-direction-expected.checksum: Added.
        * platform/mac/svg/text/bidi-embedded-direction-expected.png: Added.
        * platform/mac/svg/text/bidi-embedded-direction-expected.txt: Added.
        * platform/mac/svg/text/bidi-reorder-value-lists-expected.checksum:
        * platform/mac/svg/text/bidi-reorder-value-lists-expected.png:
        * platform/mac/svg/text/bidi-reorder-value-lists-expected.txt:
        * platform/mac/svg/text/font-size-below-point-five-expected.txt: Update result, as text runs aren't created anymore for empty text.
        * svg/text/bidi-embedded-direction.svg: Added.
        * svg/text/bidi-reorder-value-lists.svg: Extend testcase, to cover more reordering cases.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@82411 268f45cc-cd09-0410-ab3c-d52691b4dbfc

24 files changed:
LayoutTests/ChangeLog
LayoutTests/platform/mac/svg/text/bidi-embedded-direction-expected.checksum [new file with mode: 0644]
LayoutTests/platform/mac/svg/text/bidi-embedded-direction-expected.png [new file with mode: 0644]
LayoutTests/platform/mac/svg/text/bidi-embedded-direction-expected.txt [new file with mode: 0644]
LayoutTests/platform/mac/svg/text/bidi-reorder-value-lists-expected.checksum
LayoutTests/platform/mac/svg/text/bidi-reorder-value-lists-expected.png
LayoutTests/platform/mac/svg/text/bidi-reorder-value-lists-expected.txt
LayoutTests/platform/mac/svg/text/font-size-below-point-five-expected.txt
LayoutTests/svg/text/bidi-embedded-direction.svg [new file with mode: 0644]
LayoutTests/svg/text/bidi-reorder-value-lists.svg
Source/WebCore/ChangeLog
Source/WebCore/editing/visible_units.cpp
Source/WebCore/rendering/InlineFlowBox.cpp
Source/WebCore/rendering/InlineFlowBox.h
Source/WebCore/rendering/svg/RenderSVGText.cpp
Source/WebCore/rendering/svg/RenderSVGText.h
Source/WebCore/rendering/svg/SVGRootInlineBox.cpp
Source/WebCore/rendering/svg/SVGRootInlineBox.h
Source/WebCore/rendering/svg/SVGTextLayoutAttributes.cpp
Source/WebCore/rendering/svg/SVGTextLayoutAttributes.h
Source/WebCore/rendering/svg/SVGTextLayoutAttributesBuilder.cpp
Source/WebCore/rendering/svg/SVGTextLayoutAttributesBuilder.h
Source/WebCore/rendering/svg/SVGTextLayoutEngine.cpp
Source/WebCore/rendering/svg/SVGTextLayoutEngine.h

index 0b2afca..e01f2ca 100644 (file)
@@ -1,3 +1,22 @@
+2011-03-29  Nikolas Zimmermann  <nzimmermann@rim.com>
+
+        Reviewed by Eric Seidel.
+
+        REGRESSION (r68976): Incorrect bidi rendering in SVG text
+        https://bugs.webkit.org/show_bug.cgi?id=53980
+
+        Add testcase from bug 53980, assuring that BiDi works as well, when using the Unicode directional formatting characters.
+
+        * platform/mac/svg/text/bidi-embedded-direction-expected.checksum: Added.
+        * platform/mac/svg/text/bidi-embedded-direction-expected.png: Added.
+        * platform/mac/svg/text/bidi-embedded-direction-expected.txt: Added.
+        * platform/mac/svg/text/bidi-reorder-value-lists-expected.checksum:
+        * platform/mac/svg/text/bidi-reorder-value-lists-expected.png:
+        * platform/mac/svg/text/bidi-reorder-value-lists-expected.txt:
+        * platform/mac/svg/text/font-size-below-point-five-expected.txt: Update result, as text runs aren't created anymore for empty text.
+        * svg/text/bidi-embedded-direction.svg: Added.
+        * svg/text/bidi-reorder-value-lists.svg: Extend testcase, to cover more reordering cases.
+
 2011-03-30  Yuta Kitamura  <yutak@chromium.org>
 
         Unreviewed, remove duplicate Chromium test expectations.
diff --git a/LayoutTests/platform/mac/svg/text/bidi-embedded-direction-expected.checksum b/LayoutTests/platform/mac/svg/text/bidi-embedded-direction-expected.checksum
new file mode 100644 (file)
index 0000000..65ee3c7
--- /dev/null
@@ -0,0 +1 @@
+3e4c13dd888e9dab2a250e82caca31cf
\ No newline at end of file
diff --git a/LayoutTests/platform/mac/svg/text/bidi-embedded-direction-expected.png b/LayoutTests/platform/mac/svg/text/bidi-embedded-direction-expected.png
new file mode 100644 (file)
index 0000000..a7b7e53
Binary files /dev/null and b/LayoutTests/platform/mac/svg/text/bidi-embedded-direction-expected.png differ
diff --git a/LayoutTests/platform/mac/svg/text/bidi-embedded-direction-expected.txt b/LayoutTests/platform/mac/svg/text/bidi-embedded-direction-expected.txt
new file mode 100644 (file)
index 0000000..584ab58
--- /dev/null
@@ -0,0 +1,31 @@
+layer at (0,0) size 800x600
+  RenderView at (0,0) size 800x600
+layer at (0,0) size 150x275
+  RenderSVGRoot {svg} at (9,50) size 113x200
+    RenderSVGPath {line} at (9,50) size 2x200 [stroke={[type=SOLID] [color=#000000]}] [fill={[type=SOLID] [color=#000000]}] [x1=10.00] [y1=25.00] [x2=10.00] [y2=225.00]
+    RenderSVGText {text} at (10,28) size 111x28 contains 1 chunk(s)
+      RenderSVGInlineText {#text} at (56,0) size 55x28
+        chunk 1 text run 1 at (66.00,50.00) startOffset 0 endOffset 5 width 55.00 RTL: "\x{5E9}\x{5DC}\x{5D5}\x{5DD} "
+      RenderSVGTSpan {tspan} at (0,0) size 56x28
+        RenderSVGInlineText {#text} at (0,0) size 56x28
+          chunk 1 text run 1 at (10.00,50.00) startOffset 0 endOffset 5 width 56.00: "world"
+      RenderSVGInlineText {#text} at (0,0) size 0x0
+    RenderSVGText {text} at (10,78) size 111x28 contains 1 chunk(s)
+      RenderSVGInlineText {#text} at (-10,-78) size 111x28
+        chunk 1 text run 1 at (10.00,100.00) startOffset 0 endOffset 1 width 0.00 RTL: "\x{202C}"
+        chunk 1 text run 1 at (10.00,100.00) startOffset 0 endOffset 6 width 56.00: "\x{202A}world"
+        chunk 1 text run 1 at (66.00,100.00) startOffset 0 endOffset 6 width 55.00 RTL: "\x{202B}\x{5E9}\x{5DC}\x{5D5}\x{5DD} "
+        chunk 1 text run 1 at (121.00,100.00) startOffset 0 endOffset 1 width 0.00: "\x{202C}"
+    RenderSVGText {text} at (10,128) size 112x28 contains 1 chunk(s)
+      RenderSVGInlineText {#text} at (56,0) size 56x28
+        chunk 1 text run 1 at (66.00,150.00) startOffset 0 endOffset 6 width 56.00 RTL override: "hello "
+      RenderSVGTSpan {tspan} at (0,0) size 56x28
+        RenderSVGInlineText {#text} at (0,0) size 56x28
+          chunk 1 text run 1 at (10.00,150.00) startOffset 0 endOffset 5 width 56.00: "world"
+      RenderSVGInlineText {#text} at (0,0) size 0x0
+    RenderSVGText {text} at (10,178) size 112x28 contains 1 chunk(s)
+      RenderSVGInlineText {#text} at (-10,-178) size 112x28
+        chunk 1 text run 1 at (10.00,200.00) startOffset 0 endOffset 1 width 0.00 RTL override: "\x{202C}"
+        chunk 1 text run 1 at (10.00,200.00) startOffset 0 endOffset 6 width 56.00: "\x{202A}world"
+        chunk 1 text run 1 at (66.00,200.00) startOffset 0 endOffset 7 width 56.00 RTL override: "\x{202E}hello "
+        chunk 1 text run 1 at (122.00,200.00) startOffset 0 endOffset 1 width 0.00: "\x{202C}"
index f84a731..368f117 100644 (file)
Binary files a/LayoutTests/platform/mac/svg/text/bidi-reorder-value-lists-expected.png and b/LayoutTests/platform/mac/svg/text/bidi-reorder-value-lists-expected.png differ
index 6ad05a9..f8ef07b 100644 (file)
@@ -1,25 +1,52 @@
 layer at (0,0) size 800x600
   RenderView at (0,0) size 800x600
 layer at (0,0) size 400x400
-  RenderSVGRoot {svg} at (40,60) size 360x196
-    RenderSVGContainer {g} at (40,60) size 360x196 [transform={m=((4.00,0.00)(0.00,4.00)) t=(0.00,0.00)}]
-      RenderSVGText {text} at (10,15) size 90x19 contains 1 chunk(s)
+  RenderSVGRoot {svg} at (30,15) size 270x237
+    RenderSVGContainer {g} at (30,15) size 270x237 [transform={m=((3.00,0.00)(0.00,3.00)) t=(0.00,0.00)}]
+      RenderSVGText {text} at (10,5) size 90x19 contains 1 chunk(s)
         RenderSVGInlineText {#text} at (0,0) size 90x19
-          chunk 1 text run 1 at (10.00,30.00) startOffset 0 endOffset 1 width 9.75: "T"
-          chunk 1 text run 1 at (20.00,30.00) startOffset 0 endOffset 1 width 7.00: "e"
-          chunk 1 text run 1 at (30.00,30.00) startOffset 0 endOffset 1 width 6.25: "s"
-          chunk 1 text run 1 at (40.00,30.00) startOffset 0 endOffset 1 width 4.50: "t"
-          chunk 1 text run 1 at (50.00,30.00) startOffset 0 endOffset 1 width 4.00: " "
-          chunk 1 text run 1 at (54.00,30.00) startOffset 0 endOffset 5 width 45.57 RTL: "\x{5D0}\x{5D1}\x{5D2}\x{5D3}\x{5D4}"
+          chunk 1 text run 1 at (10.00,20.00) startOffset 0 endOffset 1 width 9.67: "T"
+          chunk 1 text run 1 at (20.00,20.00) startOffset 0 endOffset 1 width 7.00: "e"
+          chunk 1 text run 1 at (30.00,20.00) startOffset 0 endOffset 1 width 6.33: "s"
+          chunk 1 text run 1 at (40.00,20.00) startOffset 0 endOffset 1 width 4.33: "t"
+          chunk 1 text run 1 at (50.00,20.00) startOffset 0 endOffset 1 width 4.00: " "
+          chunk 1 text run 1 at (54.00,20.00) startOffset 0 endOffset 5 width 45.57 RTL: "\x{5D0}\x{5D1}\x{5D2}\x{5D3}\x{5D4}"
+      RenderSVGText {text} at (10,25) size 89x19 contains 1 chunk(s)
+        RenderSVGTSpan {tspan} at (0,0) size 89x19
+          RenderSVGInlineText {#text} at (0,0) size 89x19
+            chunk 1 text run 1 at (10.00,40.00) startOffset 0 endOffset 1 width 9.67: "T"
+            chunk 1 text run 1 at (20.00,40.00) startOffset 0 endOffset 1 width 7.00: "e"
+            chunk 1 text run 1 at (30.00,40.00) startOffset 0 endOffset 1 width 6.33: "s"
+            chunk 1 text run 1 at (40.00,40.00) startOffset 0 endOffset 1 width 4.33: "t"
+            chunk 1 text run 1 at (50.00,40.00) startOffset 0 endOffset 1 width 4.00: " "
+            chunk 1 text run 1 at (55.00,40.00) startOffset 0 endOffset 1 width 11.39 RTL: "\x{5D4}"
+            chunk 1 text run 1 at (64.00,40.00) startOffset 0 endOffset 1 width 8.02 RTL: "\x{5D3}"
+            chunk 1 text run 1 at (72.00,40.00) startOffset 0 endOffset 1 width 6.84 RTL: "\x{5D2}"
+            chunk 1 text run 1 at (79.00,40.00) startOffset 0 endOffset 1 width 9.01 RTL: "\x{5D1}"
+            chunk 1 text run 1 at (88.00,40.00) startOffset 0 endOffset 1 width 10.32 RTL: "\x{5D0}"
       RenderSVGText {text} at (10,45) size 89x19 contains 1 chunk(s)
-        RenderSVGInlineText {#text} at (0,0) size 89x19
-          chunk 1 text run 1 at (10.00,60.00) startOffset 0 endOffset 1 width 9.75: "T"
+        RenderSVGInlineText {#text} at (0,0) size 44x19
+          chunk 1 text run 1 at (10.00,60.00) startOffset 0 endOffset 1 width 9.67: "T"
           chunk 1 text run 1 at (20.00,60.00) startOffset 0 endOffset 1 width 7.00: "e"
-          chunk 1 text run 1 at (30.00,60.00) startOffset 0 endOffset 1 width 6.25: "s"
-          chunk 1 text run 1 at (40.00,60.00) startOffset 0 endOffset 1 width 4.50: "t"
+          chunk 1 text run 1 at (30.00,60.00) startOffset 0 endOffset 1 width 6.33: "s"
+          chunk 1 text run 1 at (40.00,60.00) startOffset 0 endOffset 1 width 4.33: "t"
           chunk 1 text run 1 at (50.00,60.00) startOffset 0 endOffset 1 width 4.00: " "
-          chunk 1 text run 1 at (55.00,60.00) startOffset 0 endOffset 1 width 11.39 RTL: "\x{5D4}"
-          chunk 1 text run 1 at (64.00,60.00) startOffset 0 endOffset 1 width 8.02 RTL: "\x{5D3}"
-          chunk 1 text run 1 at (72.00,60.00) startOffset 0 endOffset 1 width 6.84 RTL: "\x{5D2}"
-          chunk 1 text run 1 at (79.00,60.00) startOffset 0 endOffset 1 width 9.01 RTL: "\x{5D1}"
-          chunk 1 text run 1 at (88.00,60.00) startOffset 0 endOffset 1 width 10.32 RTL: "\x{5D0}"
+        RenderSVGTSpan {tspan} at (0,0) size 44x19
+          RenderSVGInlineText {#text} at (45,0) size 44x19
+            chunk 1 text run 1 at (55.00,60.00) startOffset 0 endOffset 1 width 11.39 RTL: "\x{5D4}"
+            chunk 1 text run 1 at (64.00,60.00) startOffset 0 endOffset 1 width 8.02 RTL: "\x{5D3}"
+            chunk 1 text run 1 at (72.00,60.00) startOffset 0 endOffset 1 width 6.84 RTL: "\x{5D2}"
+            chunk 1 text run 1 at (79.00,60.00) startOffset 0 endOffset 1 width 9.01 RTL: "\x{5D1}"
+            chunk 1 text run 1 at (88.00,60.00) startOffset 0 endOffset 1 width 10.32 RTL: "\x{5D0}"
+      RenderSVGText {text} at (10,65) size 89x19 contains 1 chunk(s)
+        RenderSVGInlineText {#text} at (0,0) size 89x19
+          chunk 1 text run 1 at (10.00,80.00) startOffset 0 endOffset 1 width 9.67: "T"
+          chunk 1 text run 1 at (20.00,80.00) startOffset 0 endOffset 1 width 7.00: "e"
+          chunk 1 text run 1 at (30.00,80.00) startOffset 0 endOffset 1 width 6.33: "s"
+          chunk 1 text run 1 at (40.00,80.00) startOffset 0 endOffset 1 width 4.33: "t"
+          chunk 1 text run 1 at (50.00,80.00) startOffset 0 endOffset 1 width 4.00: " "
+          chunk 1 text run 1 at (55.00,80.00) startOffset 0 endOffset 1 width 11.39 RTL: "\x{5D4}"
+          chunk 1 text run 1 at (64.00,80.00) startOffset 0 endOffset 1 width 8.02 RTL: "\x{5D3}"
+          chunk 1 text run 1 at (72.00,80.00) startOffset 0 endOffset 1 width 6.84 RTL: "\x{5D2}"
+          chunk 1 text run 1 at (79.00,80.00) startOffset 0 endOffset 1 width 9.01 RTL: "\x{5D1}"
+          chunk 1 text run 1 at (88.00,80.00) startOffset 0 endOffset 1 width 10.32 RTL: "\x{5D0}"
index cffa308..71f1374 100644 (file)
@@ -30,7 +30,6 @@ layer at (0,0) size 800x600
         chunk 1 text run 1 at (35.47,10.00) startOffset 0 endOffset 1 width 4.06: " "
       RenderSVGTSpan {tspan} at (0,0) size 0x0
         RenderSVGInlineText {#text} at (-10,5) size 0x0
-          chunk 1 text run 1 at (39.53,10.00) startOffset 0 endOffset 1 width 0.00: "6"
       RenderSVGInlineText {#text} at (0,0) size 0x0
     RenderSVGText {text} at (63,42) size 124x11 contains 1 chunk(s)
       RenderSVGInlineText {#text} at (0,0) size 124x11
diff --git a/LayoutTests/svg/text/bidi-embedded-direction.svg b/LayoutTests/svg/text/bidi-embedded-direction.svg
new file mode 100644 (file)
index 0000000..46f7f7a
--- /dev/null
@@ -0,0 +1,17 @@
+<svg xmlns="http://www.w3.org/2000/svg" 
+    width="150px" height="275px" viewBox="0 -25 150 275">
+  <line x1="10" y1="25" x2="10" y2="225" stroke="black" stroke-width="1"/>
+  <text x="10" y="50" font-size="24" font-family="Arial" direction="rtl" unicode-bidi="embed">
+    שלום <tspan direction="ltr" unicode-bidi="embed">world</tspan>
+  </text>
+  <text x="10" y="100" font-size="24" font-family="Arial">
+    &#x202B;שלום &#x202A;world&#x202C;&#x202C;
+  </text>
+
+  <text x="10" y="150" font-size="24" font-family="Arial" direction="rtl" unicode-bidi="bidi-override">
+    hello <tspan direction="ltr" unicode-bidi="embed">world</tspan>
+  </text>
+  <text x="10" y="200" font-size="24" font-family="Arial">
+    &#x202E;hello &#x202A;world&#x202C;&#x202C;
+  </text>
+</svg>
index 7dfa78c..7b0ea75 100644 (file)
@@ -1,7 +1,7 @@
 <svg width="400" height="400" xmlns="http://www.w3.org/2000/svg">
-    <g font-size="16" transform="scale(4,4)">
+    <g font-size="16" transform="scale(3,3)">
         <!-- The order of all characters in both lines should be the same, the spacing is different due the absolute positioning in the second line -->
-        <text x="10 20 30 40 50" y="30">Test &#x5d0;&#x5d1;&#x5d2;&#x5d3;&#x5d4;</text>
+        <text y="20" x="10 20 30 40 50">Test &#x5d0;&#x5d1;&#x5d2;&#x5d3;&#x5d4;</text>
 
         <!--
           Quoting SVG 1.1: Whenever the character data within a ‘tspan’ element is re-ordered, the corresponding
           It's rendered according to the BiDi algorithm as: Test &#x5d4;&#x5d3;&#x5d2;&#x5d1;&#x5d0;
           The Hebrew string is reordered, and the x/y/dx/dy/rotate lists as well to maintain correspondence.
          -->
-        <text x="10 20 30 40 50 88 79 72 64 55" y="60">Test &#x5d0;&#x5d1;&#x5d2;&#x5d3;&#x5d4;</text>
+        <text y="40"><tspan x="10 20 30 40 50 88 79 72 64 55">Test &#x5d0;&#x5d1;&#x5d2;&#x5d3;&#x5d4;</tspan></text>
+
+        <text y="60" x="10 20 30 40 50">Test <tspan x="88 79 72 64 55">&#x5d0;&#x5d1;&#x5d2;&#x5d3;&#x5d4;</tspan></text>
+
+        <text y="80" x="10 20 30 40 50 88 79 72 64 55">Test &#x5d0;&#x5d1;&#x5d2;&#x5d3;&#x5d4;</text>
     </g>
 </svg>
index f5806b2..b5dfc02 100644 (file)
@@ -1,3 +1,58 @@
+2011-03-29  Nikolas Zimmermann  <nzimmermann@rim.com>
+
+        Reviewed by Eric Seidel.
+
+        REGRESSION (r68976): Incorrect bidi rendering in SVG text
+        https://bugs.webkit.org/show_bug.cgi?id=53980
+
+        Deconvolute SVGTextLayoutEngine code, which was confusing due to the simultaneous processing of the rendered text
+        in visual and logical order. Added several helper methods to make the code more readable.
+
+        Fix Unicode directional formatting characters support, now works as expected.
+
+        Test: svg/text/bidi-embedded-direction.svg
+
+        * editing/visible_units.cpp: Refactor getLeafBoxesInLogicalOrder(), move to InlineFlowBox.
+        (WebCore::getLogicalStartBoxAndNode): Use new collectLeafBoxesInLogicalOrder() method in InlineFlowBox.
+        (WebCore::getLogicalEndBoxAndNode): Ditto.
+        * rendering/InlineFlowBox.cpp: Add new helper function, that returns a list of all leaf boxes in logical order.
+        (WebCore::InlineFlowBox::collectLeafBoxesInLogicalOrder):
+        * rendering/InlineFlowBox.h:
+        * rendering/svg/RenderSVGText.cpp: Actually trigger reordering the x/y/dx/dy/rotate value lists, if needed.
+        (WebCore::RenderSVGText::RenderSVGText):
+        (WebCore::RenderSVGText::layout):
+        * rendering/svg/RenderSVGText.h: Ditto.
+        (WebCore::RenderSVGText::layoutAttributes):
+        (WebCore::RenderSVGText::needsReordering):
+        * rendering/svg/SVGRootInlineBox.cpp: Use new InlineFlowBox::collectLeafBoxesINLogicalOrder(), with a custom "inline box reverse" implementation,
+                                              which not only reverses the order of InlineBoxes, but also the order of the x/y/dx/dy/rotate value lists, if needed.
+        (WebCore::SVGRootInlineBox::computePerCharacterLayoutInformation):
+        (WebCore::SVGRootInlineBox::layoutCharactersInTextBoxes):
+        (WebCore::swapItems):
+        (WebCore::reverseInlineBoxRangeAndValueListsIfNeeded):
+        (WebCore::SVGRootInlineBox::reorderValueLists):
+        * rendering/svg/SVGRootInlineBox.h:
+        * rendering/svg/SVGTextLayoutAttributes.cpp: Store RenderSVGInlineText* pointer, where we belong to.
+        (WebCore::SVGTextLayoutAttributes::SVGTextLayoutAttributes):
+        (WebCore::SVGTextLayoutAttributes::dump):
+        * rendering/svg/SVGTextLayoutAttributes.h:
+        (WebCore::SVGTextLayoutAttributes::context):
+        * rendering/svg/SVGTextLayoutAttributesBuilder.cpp: Pass RenderSVGInlineText* object when creating SVGTextLayoutAttributes.
+        (WebCore::SVGTextLayoutAttributesBuilder::buildLayoutAttributesForTextSubtree):
+        (WebCore::SVGTextLayoutAttributesBuilder::propagateLayoutAttributes):
+        * rendering/svg/SVGTextLayoutAttributesBuilder.h:
+        * rendering/svg/SVGTextLayoutEngine.cpp: Rewrite & cleanup the main layout algorithm, to be less confusing.
+        (WebCore::SVGTextLayoutEngine::SVGTextLayoutEngine):
+        (WebCore::SVGTextLayoutEngine::updateRelativePositionAdjustmentsIfNeeded):
+        (WebCore::SVGTextLayoutEngine::recordTextFragment):
+        (WebCore::SVGTextLayoutEngine::currentLogicalCharacterAttributes):
+        (WebCore::SVGTextLayoutEngine::currentLogicalCharacterMetrics):
+        (WebCore::SVGTextLayoutEngine::currentVisualCharacterMetrics):
+        (WebCore::SVGTextLayoutEngine::advanceToNextLogicalCharacter):
+        (WebCore::SVGTextLayoutEngine::advanceToNextVisualCharacter):
+        (WebCore::SVGTextLayoutEngine::layoutTextOnLineOrPath):
+        * rendering/svg/SVGTextLayoutEngine.h:
+
 2011-03-30  Ilya Tikhonovsky  <loislo@chromium.org>
 
         Not reviewed trivial change.
index e8b2403..212d2cc 100644 (file)
@@ -1041,60 +1041,10 @@ VisiblePosition endOfEditableContent(const VisiblePosition& visiblePosition)
     return lastPositionInNode(highestRoot);
 }
 
-static void getLeafBoxesInLogicalOrder(RootInlineBox* rootBox, Vector<InlineBox*>& leafBoxesInLogicalOrder)
-{
-    unsigned char minLevel = 128;
-    unsigned char maxLevel = 0;
-    unsigned count = 0;
-    InlineBox* r = rootBox->firstLeafChild();
-    // First find highest and lowest levels,
-    // and initialize leafBoxesInLogicalOrder with the leaf boxes in visual order.
-    while (r) {
-        if (r->bidiLevel() > maxLevel)
-            maxLevel = r->bidiLevel();
-        if (r->bidiLevel() < minLevel)
-            minLevel = r->bidiLevel();
-        leafBoxesInLogicalOrder.append(r);
-        r = r->nextLeafChild();
-        ++count;
-    }
-
-    if (rootBox->renderer()->style()->visuallyOrdered())
-        return;
-    // Reverse of reordering of the line (L2 according to Bidi spec):
-    // L2. From the highest level found in the text to the lowest odd level on each line,
-    // reverse any contiguous sequence of characters that are at that level or higher.
-
-    // Reversing the reordering of the line is only done up to the lowest odd level.
-    if (!(minLevel % 2))
-        minLevel++;
-    
-    InlineBox** end = leafBoxesInLogicalOrder.end();
-    while (minLevel <= maxLevel) {
-        InlineBox** iter = leafBoxesInLogicalOrder.begin();
-        while (iter != end) {
-            while (iter != end) {
-                if ((*iter)->bidiLevel() >= minLevel)
-                    break;
-                ++iter;
-            }
-            InlineBox** first = iter;
-            while (iter != end) {
-                if ((*iter)->bidiLevel() < minLevel)
-                    break;
-                ++iter;
-            }
-            InlineBox** last = iter;
-            std::reverse(first, last);
-        }                
-        ++minLevel;
-    }
-}
-
 static void getLogicalStartBoxAndNode(RootInlineBox* rootBox, InlineBox*& startBox, Node*& startNode)
 {
     Vector<InlineBox*> leafBoxesInLogicalOrder;
-    getLeafBoxesInLogicalOrder(rootBox, leafBoxesInLogicalOrder);
+    rootBox->collectLeafBoxesInLogicalOrder(leafBoxesInLogicalOrder);
     startBox = 0;
     startNode = 0;
     for (size_t i = 0; i < leafBoxesInLogicalOrder.size(); ++i) {
@@ -1108,7 +1058,7 @@ static void getLogicalStartBoxAndNode(RootInlineBox* rootBox, InlineBox*& startB
 static void getLogicalEndBoxAndNode(RootInlineBox* rootBox, InlineBox*& endBox, Node*& endNode)
 {
     Vector<InlineBox*> leafBoxesInLogicalOrder;
-    getLeafBoxesInLogicalOrder(rootBox, leafBoxesInLogicalOrder);
+    rootBox->collectLeafBoxesInLogicalOrder(leafBoxesInLogicalOrder);
     endBox = 0;
     endNode = 0;
     // Generated content (e.g. list markers and CSS :before and :after
index 52666cf..cd468b6 100644 (file)
@@ -1334,6 +1334,59 @@ int InlineFlowBox::computeUnderAnnotationAdjustment(int allowedPosition) const
     return result;
 }
 
+void InlineFlowBox::collectLeafBoxesInLogicalOrder(Vector<InlineBox*>& leafBoxesInLogicalOrder, CustomInlineBoxRangeReverse customReverseImplementation, void* userData) const
+{
+    InlineBox* leaf = firstLeafChild();
+
+    // FIXME: The reordering code is a copy of parts from BidiResolver::createBidiRunsForLine, operating directly on InlineBoxes, instead of BidiRuns.
+    // Investigate on how this code could possibly be shared.
+    unsigned char minLevel = 128;
+    unsigned char maxLevel = 0;
+
+    // First find highest and lowest levels, and initialize leafBoxesInLogicalOrder with the leaf boxes in visual order.
+    for (; leaf; leaf = leaf->nextLeafChild()) {
+        minLevel = min(minLevel, leaf->bidiLevel());
+        maxLevel = max(maxLevel, leaf->bidiLevel());
+        leafBoxesInLogicalOrder.append(leaf);
+    }
+
+    if (renderer()->style()->visuallyOrdered())
+        return;
+
+    // Reverse of reordering of the line (L2 according to Bidi spec):
+    // L2. From the highest level found in the text to the lowest odd level on each line,
+    // reverse any contiguous sequence of characters that are at that level or higher.
+
+    // Reversing the reordering of the line is only done up to the lowest odd level.
+    if (!(minLevel % 2))
+        ++minLevel;
+
+    Vector<InlineBox*>::iterator end = leafBoxesInLogicalOrder.end();
+    while (minLevel <= maxLevel) {
+        Vector<InlineBox*>::iterator it = leafBoxesInLogicalOrder.begin();
+        while (it != end) {
+            while (it != end) {
+                if ((*it)->bidiLevel() >= minLevel)
+                    break;
+                ++it;
+            }
+            Vector<InlineBox*>::iterator first = it;
+            while (it != end) {
+                if ((*it)->bidiLevel() < minLevel)
+                    break;
+                ++it;
+            }
+            Vector<InlineBox*>::iterator last = it;
+            if (customReverseImplementation) {
+                ASSERT(userData);
+                (*customReverseImplementation)(userData, first, last);
+            } else
+                std::reverse(first, last);
+        }                
+        ++minLevel;
+    }
+}
+
 #ifndef NDEBUG
 
 void InlineFlowBox::checkConsistency() const
index e792de3..c1bd0e8 100644 (file)
@@ -76,6 +76,9 @@ public:
     InlineBox* firstLeafChild() const;
     InlineBox* lastLeafChild() const;
 
+    typedef void (*CustomInlineBoxRangeReverse)(void* userData, Vector<InlineBox*>::iterator first, Vector<InlineBox*>::iterator last);
+    void collectLeafBoxesInLogicalOrder(Vector<InlineBox*>&, CustomInlineBoxRangeReverse customReverseImplementation = 0, void* userData = 0) const;
+
     virtual void setConstructed()
     {
         InlineBox::setConstructed();
index 34c02ef..8ca3d58 100644 (file)
@@ -51,6 +51,7 @@ namespace WebCore {
 
 RenderSVGText::RenderSVGText(SVGTextElement* node) 
     : RenderSVGBlock(node)
+    , m_needsReordering(false)
     , m_needsPositioningValuesUpdate(true)
     , m_needsTransformUpdate(true)
 {
@@ -127,6 +128,7 @@ void RenderSVGText::layout()
         // Perform SVG text layout phase one (see SVGTextLayoutAttributesBuilder for details).
         SVGTextLayoutAttributesBuilder layoutAttributesBuilder;
         layoutAttributesBuilder.buildLayoutAttributesForTextSubtree(this);
+        m_needsReordering = true;
         m_needsPositioningValuesUpdate = false;
         updateCachedBoundariesInParents = true;
     }
@@ -150,6 +152,9 @@ void RenderSVGText::layout()
     ASSERT(childrenInline());
     forceLayoutInlineChildren();
 
+    if (m_needsReordering)
+        m_needsReordering = false;
+
     if (!updateCachedBoundariesInParents)
         updateCachedBoundariesInParents = oldBoundaries != objectBoundingBox();
 
index 188d5fc..93fc5f8 100644 (file)
@@ -26,6 +26,7 @@
 
 #include "AffineTransform.h"
 #include "RenderSVGBlock.h"
+#include "SVGTextLayoutAttributes.h"
 
 namespace WebCore {
 
@@ -33,7 +34,7 @@ class SVGTextElement;
 
 class RenderSVGText : public RenderSVGBlock {
 public:
-    RenderSVGText(SVGTextElement* node);
+    RenderSVGText(SVGTextElement*);
 
     void setNeedsPositioningValuesUpdate() { m_needsPositioningValuesUpdate = true; }
     virtual void setNeedsTransformUpdate() { m_needsTransformUpdate = true; }
@@ -42,6 +43,9 @@ public:
     static RenderSVGText* locateRenderSVGTextAncestor(RenderObject*);
     static const RenderSVGText* locateRenderSVGTextAncestor(const RenderObject*);
 
+    Vector<SVGTextLayoutAttributes>& layoutAttributes() { return m_layoutAttributes; }
+    bool needsReordering() const { return m_needsReordering; }
+
 private:
     virtual const char* renderName() const { return "RenderSVGText"; }
     virtual bool isSVGText() const { return true; }
@@ -71,9 +75,11 @@ private:
     virtual RenderBlock* firstLineBlock() const;
     virtual void updateFirstLetter();
 
+    bool m_needsReordering : 1;
     bool m_needsPositioningValuesUpdate : 1;
     bool m_needsTransformUpdate : 1;
     AffineTransform m_localTransform;
+    Vector<SVGTextLayoutAttributes> m_layoutAttributes;
 };
 
 inline RenderSVGText* toRenderSVGText(RenderObject* object)
index 6d6b9fe..ddbd3ea 100644 (file)
@@ -25,8 +25,8 @@
 
 #if ENABLE(SVG)
 #include "GraphicsContext.h"
-#include "RenderBlock.h"
 #include "RenderSVGInlineText.h"
+#include "RenderSVGText.h"
 #include "SVGInlineFlowBox.h"
 #include "SVGInlineTextBox.h"
 #include "SVGNames.h"
@@ -73,18 +73,15 @@ void SVGRootInlineBox::paint(PaintInfo& paintInfo, int, int)
 
 void SVGRootInlineBox::computePerCharacterLayoutInformation()
 {
-    RenderBlock* parentBlock = block();
+    RenderSVGText* parentBlock = toRenderSVGText(block());
     ASSERT(parentBlock);
 
-    // Build list of all text boxes which belong to our root renderer, in logical order,
-    // aka. the order of the characters as they appear in the original document.
-    // This is needed to maintain correspondence between the x/y/dx/dy/rotate value
-    // lists and the potentially reordered characters in the inline box tree.
-    Vector<SVGInlineTextBox*> boxesInLogicalOrder;
-    buildTextBoxListInLogicalOrder(parentBlock, boxesInLogicalOrder);
+    Vector<SVGTextLayoutAttributes>& attributes = parentBlock->layoutAttributes();
+    if (parentBlock->needsReordering())
+        reorderValueLists(attributes);
 
     // Perform SVG text layout phase two (see SVGTextLayoutEngine for details).
-    SVGTextLayoutEngine characterLayout(boxesInLogicalOrder);
+    SVGTextLayoutEngine characterLayout(attributes);
     layoutCharactersInTextBoxes(this, characterLayout);
 
     // Perform SVG text layout phase three (see SVGTextChunkBuilder for details).
@@ -96,27 +93,6 @@ void SVGRootInlineBox::computePerCharacterLayoutInformation()
     layoutRootBox();
 }
 
-void SVGRootInlineBox::buildTextBoxListInLogicalOrder(RenderObject* start, Vector<SVGInlineTextBox*>& boxes)
-{
-    bool ltr = start->style()->isLeftToRightDirection();
-    for (RenderObject* child = start->firstChild(); child; child = child->nextSibling()) {
-        if (child->isSVGInline()) {
-            buildTextBoxListInLogicalOrder(child, boxes);
-            continue;
-        }
-
-        if (!child->isSVGInlineText())
-            continue;
-
-        RenderSVGInlineText* text = toRenderSVGInlineText(child);
-        for (InlineTextBox* textBox = ltr ? text->firstTextBox() : text->lastTextBox(); textBox; textBox = ltr ? textBox->nextTextBox() : textBox->prevTextBox()) {
-            if (!textBox->isSVGInlineTextBox())
-                continue;
-            boxes.append(static_cast<SVGInlineTextBox*>(textBox));
-        }
-    }
-}
-
 void SVGRootInlineBox::layoutCharactersInTextBoxes(InlineFlowBox* start, SVGTextLayoutEngine& characterLayout)
 {
     for (InlineBox* child = start->firstChild(); child; child = child->nextOnLine()) {
@@ -139,10 +115,10 @@ void SVGRootInlineBox::layoutCharactersInTextBoxes(InlineFlowBox* start, SVGText
             if (isTextPath) {
                 // Build text chunks for all <textPath> children, using the line layout algorithm.
                 // This is needeed as text-anchor is just an additional startOffset for text paths.
-                Vector<SVGInlineTextBox*> boxesInLogicalOrder;
-                buildTextBoxListInLogicalOrder(flowBox->renderer(), boxesInLogicalOrder);
+                RenderSVGText* parentBlock = toRenderSVGText(block());
+                ASSERT(parentBlock);
 
-                SVGTextLayoutEngine lineLayout(boxesInLogicalOrder);
+                SVGTextLayoutEngine lineLayout(parentBlock->layoutAttributes());
                 layoutCharactersInTextBoxes(flowBox, lineLayout);
 
                 characterLayout.beginTextPathLayout(child->renderer(), lineLayout);
@@ -250,6 +226,91 @@ InlineBox* SVGRootInlineBox::closestLeafChildForPosition(const IntPoint& point)
 
     return closestLeaf ? closestLeaf : lastLeaf;
 }
+static inline void swapItemsInVector(Vector<float>& firstVector, Vector<float>& lastVector, unsigned first, unsigned last)
+{
+    float temp = firstVector.at(first);
+    firstVector.at(first) = lastVector.at(last);
+    lastVector.at(last) = temp;
+}
+
+static inline void swapItemsInLayoutAttributes(SVGTextLayoutAttributes& firstAttributes, SVGTextLayoutAttributes& lastAttributes, unsigned firstPosition, unsigned lastPosition)
+{
+    swapItemsInVector(firstAttributes.xValues(), lastAttributes.xValues(), firstPosition, lastPosition);
+    swapItemsInVector(firstAttributes.yValues(), lastAttributes.yValues(), firstPosition, lastPosition);
+    swapItemsInVector(firstAttributes.dxValues(), lastAttributes.dxValues(), firstPosition, lastPosition);
+    swapItemsInVector(firstAttributes.dyValues(), lastAttributes.dyValues(), firstPosition, lastPosition);
+    swapItemsInVector(firstAttributes.rotateValues(), lastAttributes.rotateValues(), firstPosition, lastPosition);
+}
+
+static inline void findFirstAndLastAttributesInVector(Vector<SVGTextLayoutAttributes>& attributes, RenderSVGInlineText* firstContext, RenderSVGInlineText* lastContext,
+                                                      SVGTextLayoutAttributes*& first, SVGTextLayoutAttributes*& last)
+{
+    first = 0;
+    last = 0;
+
+    unsigned attributesSize = attributes.size();
+    for (unsigned i = 0; i < attributesSize; ++i) {
+        SVGTextLayoutAttributes& current = attributes.at(i);
+        if (!first && firstContext == current.context())
+            first = &current;
+        if (!last && lastContext == current.context())
+            last = &current;
+        if (first && last)
+            break;
+    }
+
+    ASSERT(first);
+    ASSERT(last);
+}
+
+static inline void reverseInlineBoxRangeAndValueListsIfNeeded(void* userData, Vector<InlineBox*>::iterator first, Vector<InlineBox*>::iterator last)
+{
+    ASSERT(userData);
+    Vector<SVGTextLayoutAttributes>& attributes = *reinterpret_cast<Vector<SVGTextLayoutAttributes>*>(userData);
+
+    // This is a copy of std::reverse(first, last). It additionally assure that the value lists within the InlineBoxes are reordered as well.
+    while (true)  {
+        if (first == last || first == --last)
+            return;
+
+        ASSERT((*first)->isSVGInlineTextBox());
+        ASSERT((*last)->isSVGInlineTextBox());
+
+        SVGInlineTextBox* firstTextBox = static_cast<SVGInlineTextBox*>(*first);
+        SVGInlineTextBox* lastTextBox = static_cast<SVGInlineTextBox*>(*last);
+
+        // Reordering is only necessary for BiDi text that is _absolutely_ positioned.
+        if (firstTextBox->len() == 1 && firstTextBox->len() == lastTextBox->len()) {
+            RenderSVGInlineText* firstContext = toRenderSVGInlineText(firstTextBox->textRenderer());
+            RenderSVGInlineText* lastContext = toRenderSVGInlineText(lastTextBox->textRenderer());
+
+            SVGTextLayoutAttributes* firstAttributes = 0;
+            SVGTextLayoutAttributes* lastAttributes = 0;
+            findFirstAndLastAttributesInVector(attributes, firstContext, lastContext, firstAttributes, lastAttributes);
+
+            unsigned firstBoxPosition = firstTextBox->start();
+            unsigned firstBoxEnd = firstTextBox->end();
+
+            unsigned lastBoxPosition = lastTextBox->start();
+            unsigned lastBoxEnd = lastTextBox->end();
+            for (; firstBoxPosition <= firstBoxEnd && lastBoxPosition <= lastBoxEnd; ++lastBoxPosition, ++firstBoxPosition)
+                swapItemsInLayoutAttributes(*firstAttributes, *lastAttributes, firstBoxPosition, lastBoxPosition);
+        }
+
+        InlineBox* temp = *first;
+        *first = *last;
+        *last = temp;
+
+        ++first;
+    }
+}
+
+void SVGRootInlineBox::reorderValueLists(Vector<SVGTextLayoutAttributes>& attributes)
+{
+    Vector<InlineBox*> leafBoxesInLogicalOrder;
+    collectLeafBoxesInLogicalOrder(leafBoxesInLogicalOrder, reverseInlineBoxRangeAndValueListsIfNeeded, &attributes);
+}
 
 } // namespace WebCore
 
index 4a29039..39612e7 100644 (file)
@@ -55,7 +55,7 @@ public:
     InlineBox* closestLeafChildForPosition(const IntPoint&);
 
 private:
-    void buildTextBoxListInLogicalOrder(RenderObject*, Vector<SVGInlineTextBox*>&);
+    void reorderValueLists(Vector<SVGTextLayoutAttributes>&);
     void layoutCharactersInTextBoxes(InlineFlowBox*, SVGTextLayoutEngine&);
     void layoutChildBoxes(InlineFlowBox*);
     void layoutRootBox();
index 3037b77..4c227b4 100644 (file)
@@ -27,7 +27,8 @@
 
 namespace WebCore {
 
-SVGTextLayoutAttributes::SVGTextLayoutAttributes()
+SVGTextLayoutAttributes::SVGTextLayoutAttributes(RenderSVGInlineText* context)
+    : m_context(context)
 {
 }
 
@@ -65,6 +66,8 @@ static inline void dumpLayoutVector(const Vector<float>& values)
 
 void SVGTextLayoutAttributes::dump() const
 {
+    fprintf(stderr, "context: %p\n", m_context);
+
     fprintf(stderr, "x values: ");
     dumpLayoutVector(m_xValues);
     fprintf(stderr, "\n");
index d08d5b7..fc6bd10 100644 (file)
 
 namespace WebCore {
 
+class RenderSVGInlineText;
+
 class SVGTextLayoutAttributes {
 public:
-    SVGTextLayoutAttributes();
+    SVGTextLayoutAttributes(RenderSVGInlineText* context = 0);
 
     void reserveCapacity(unsigned length);
     void dump() const;
 
     static float emptyValue();
 
+    RenderSVGInlineText* context() const { return m_context; }
+
     Vector<float>& xValues() { return m_xValues; }
     const Vector<float>& xValues() const { return m_xValues; }
 
@@ -55,6 +59,7 @@ public:
     const Vector<SVGTextMetrics>& textMetricsValues() const { return m_textMetricsValues; }
 
 private:
+    RenderSVGInlineText* m_context;
     Vector<float> m_xValues;
     Vector<float> m_yValues;
     Vector<float> m_dxValues;
index fdd91f1..86be424 100644 (file)
@@ -52,9 +52,11 @@ void SVGTextLayoutAttributesBuilder::buildLayoutAttributesForTextSubtree(RenderS
     buildOutermostLayoutScope(textRoot, atCharacter);
 
     // Propagate layout attributes to each RenderSVGInlineText object.
+    Vector<SVGTextLayoutAttributes>& allAttributes = textRoot->layoutAttributes();
+    allAttributes.clear();
     atCharacter = 0;
     lastCharacter = '\0';
-    propagateLayoutAttributes(textRoot, atCharacter, lastCharacter);
+    propagateLayoutAttributes(textRoot, allAttributes, atCharacter, lastCharacter);
 }
 
 static inline void extractFloatValuesFromSVGLengthList(SVGElement* lengthContext, const SVGLengthList& list, Vector<float>& floatValues, unsigned textContentLength)
@@ -186,7 +188,7 @@ void SVGTextLayoutAttributesBuilder::buildOutermostLayoutScope(RenderSVGText* te
     m_scopes.prepend(scope);
 }
 
-void SVGTextLayoutAttributesBuilder::propagateLayoutAttributes(RenderObject* start, unsigned& atCharacter, UChar& lastCharacter) const
+void SVGTextLayoutAttributesBuilder::propagateLayoutAttributes(RenderObject* start, Vector<SVGTextLayoutAttributes>& allAttributes, unsigned& atCharacter, UChar& lastCharacter) const
 {
     for (RenderObject* child = start->firstChild(); child; child = child->nextSibling()) { 
         if (child->isSVGInlineText()) {
@@ -195,7 +197,7 @@ void SVGTextLayoutAttributesBuilder::propagateLayoutAttributes(RenderObject* sta
             unsigned textLength = text->textLength();
             bool preserveWhiteSpace = shouldPreserveAllWhiteSpace(text->style());
 
-            SVGTextLayoutAttributes attributes;
+            SVGTextLayoutAttributes attributes(text);
             attributes.reserveCapacity(textLength);
     
             unsigned valueListPosition = atCharacter;
@@ -243,6 +245,7 @@ void SVGTextLayoutAttributesBuilder::propagateLayoutAttributes(RenderObject* sta
 #endif
 
             text->storeLayoutAttributes(attributes);
+            allAttributes.append(attributes);
             atCharacter = valueListPosition;
             continue;
         }
@@ -250,7 +253,7 @@ void SVGTextLayoutAttributesBuilder::propagateLayoutAttributes(RenderObject* sta
         if (!child->isSVGInline())
             continue;
 
-        propagateLayoutAttributes(child, atCharacter, lastCharacter);
+        propagateLayoutAttributes(child, allAttributes, atCharacter, lastCharacter);
     }
 }
 
index c68185b..b368c51 100644 (file)
@@ -60,7 +60,7 @@ private:
     void buildLayoutScope(LayoutScope&, RenderObject*, unsigned textContentStart, unsigned textContentLength) const;
     void buildLayoutScopes(RenderObject*, unsigned& atCharacter, UChar& lastCharacter);
     void buildOutermostLayoutScope(RenderSVGText*, unsigned textLength);
-    void propagateLayoutAttributes(RenderObject*, unsigned& atCharacter, UChar& lastCharacter) const;
+    void propagateLayoutAttributes(RenderObject*, Vector<SVGTextLayoutAttributes>& allAttributes, unsigned& atCharacter, UChar& lastCharacter) const;
 
     enum LayoutValueType {
         XValueAttribute,
index d0e8f5e..71db2ea 100644 (file)
 
 namespace WebCore {
 
-SVGTextLayoutEngine::SVGTextLayoutEngine(Vector<SVGInlineTextBox*>& boxesInLogicalOrder)
-    : m_x(0)
+SVGTextLayoutEngine::SVGTextLayoutEngine(Vector<SVGTextLayoutAttributes>& layoutAttributes)
+    : m_layoutAttributes(layoutAttributes)
+    , m_logicalCharacterOffset(0)
+    , m_logicalMetricsListOffset(0)
+    , m_visualCharacterOffset(0)
+    , m_visualMetricsListOffset(0)
+    , m_x(0)
     , m_y(0)
     , m_dx(0)
     , m_dy(0)
@@ -46,13 +51,7 @@ SVGTextLayoutEngine::SVGTextLayoutEngine(Vector<SVGInlineTextBox*>& boxesInLogic
     , m_textPathSpacing(0)
     , m_textPathScaling(1)
 {
-    unsigned size = boxesInLogicalOrder.size();
-    for (unsigned i = 0; i < size; ++i) {
-        SVGInlineTextBox* textBox = boxesInLogicalOrder.at(i);
-        m_ranges.append(CharacterRange(textBox->start(), textBox->end(), textBox));
-    }
-
-    ASSERT(!m_ranges.isEmpty());
+    ASSERT(!m_layoutAttributes.isEmpty());
 }
 
 void SVGTextLayoutEngine::updateCharacerPositionIfNeeded(float& x, float& y)
@@ -84,7 +83,7 @@ void SVGTextLayoutEngine::updateCurrentTextPosition(float x, float y, float glyp
     }
 }
 
-void SVGTextLayoutEngine::updateRelativePositionAdjustmentsIfNeeded(Vector<float>& dxValues, Vector<float>& dyValues, unsigned positionListOffset)
+void SVGTextLayoutEngine::updateRelativePositionAdjustmentsIfNeeded(Vector<float>& dxValues, Vector<float>& dyValues)
 {
     // Update relative positioning information.
     if (dxValues.isEmpty() && dyValues.isEmpty())
@@ -92,14 +91,14 @@ void SVGTextLayoutEngine::updateRelativePositionAdjustmentsIfNeeded(Vector<float
 
     float dx = 0;
     if (!dxValues.isEmpty()) {
-        float& dxCurrent = dxValues.at(positionListOffset);
+        float& dxCurrent = dxValues.at(m_logicalCharacterOffset);
         if (dxCurrent != SVGTextLayoutAttributes::emptyValue())
             dx = dxCurrent;
     }
 
     float dy = 0;
     if (!dyValues.isEmpty()) {
-        float& dyCurrent = dyValues.at(positionListOffset);
+        float& dyCurrent = dyValues.at(m_logicalCharacterOffset);
         if (dyCurrent != SVGTextLayoutAttributes::emptyValue())
             dy = dyCurrent;
     }
@@ -120,16 +119,16 @@ void SVGTextLayoutEngine::updateRelativePositionAdjustmentsIfNeeded(Vector<float
     m_dy = dy;
 }
 
-void SVGTextLayoutEngine::recordTextFragment(SVGInlineTextBox* textBox, Vector<SVGTextMetrics>& textMetricsValues, unsigned characterOffset, unsigned metricsListOffset)
+void SVGTextLayoutEngine::recordTextFragment(SVGInlineTextBox* textBox, Vector<SVGTextMetrics>& textMetricsValues)
 {
     ASSERT(!m_currentTextFragment.length);
-    ASSERT(metricsListOffset > 0);
+    ASSERT(m_visualMetricsListOffset > 0);
 
     // Figure out length of fragment.
-    m_currentTextFragment.length = characterOffset - m_currentTextFragment.characterOffset;
+    m_currentTextFragment.length = m_visualCharacterOffset - m_currentTextFragment.characterOffset;
 
     // Figure out fragment metrics.
-    SVGTextMetrics& lastCharacterMetrics = textMetricsValues.at(metricsListOffset - 1);
+    SVGTextMetrics& lastCharacterMetrics = textMetricsValues.at(m_visualMetricsListOffset - 1);
     m_currentTextFragment.width = lastCharacterMetrics.width();
     m_currentTextFragment.height = lastCharacterMetrics.height();
 
@@ -137,11 +136,11 @@ void SVGTextLayoutEngine::recordTextFragment(SVGInlineTextBox* textBox, Vector<S
         // SVGTextLayoutAttributesBuilder assures that the length of the range is equal to the sum of the individual lengths of the glyphs.
         float length = 0;
         if (m_isVerticalText) {
-            for (unsigned i = m_currentTextFragment.metricsListOffset; i < metricsListOffset; ++i)
+            for (unsigned i = m_currentTextFragment.metricsListOffset; i < m_visualMetricsListOffset; ++i)
                 length += textMetricsValues.at(i).height();
             m_currentTextFragment.height = length;
         } else {
-            for (unsigned i = m_currentTextFragment.metricsListOffset; i < metricsListOffset; ++i)
+            for (unsigned i = m_currentTextFragment.metricsListOffset; i < m_visualMetricsListOffset; ++i)
                 length += textMetricsValues.at(i).width();
             m_currentTextFragment.width = length;
         }
@@ -343,27 +342,95 @@ void SVGTextLayoutEngine::finalizeTransformMatrices(Vector<SVGInlineTextBox*>& b
     boxes.clear();
 }
 
-void SVGTextLayoutEngine::nextLogicalBoxAndOffset(unsigned consumeCharacters, unsigned& positionListOffset, SVGInlineTextBox*& box)
+bool SVGTextLayoutEngine::currentLogicalCharacterAttributes(SVGTextLayoutAttributes& logicalAttributes)
+{
+    logicalAttributes = m_layoutAttributes.first();
+    if (m_logicalCharacterOffset != logicalAttributes.xValues().size())
+        return true;
+
+    m_layoutAttributes.remove(0);
+    if (m_layoutAttributes.isEmpty())
+        return false;
+
+    logicalAttributes = m_layoutAttributes.first();
+    m_logicalMetricsListOffset = 0;
+    m_logicalCharacterOffset = 0;
+    return true;
+}
+
+bool SVGTextLayoutEngine::currentLogicalCharacterMetrics(SVGTextLayoutAttributes& logicalAttributes, SVGTextMetrics& logicalMetrics)
 {
-    ASSERT(!m_ranges.isEmpty());
-
-    unsigned consumed = 0;
-    do {
-        CharacterRange& firstRange = m_ranges.first();
-        if (!consumed) {
-            positionListOffset = firstRange.start;
-            box = firstRange.box;
-        } else
-            ASSERT(firstRange.box == box);
-
-        ++consumed;
-        if (firstRange.start == firstRange.end) {
-            m_ranges.remove(0);
+    logicalMetrics = SVGTextMetrics::emptyMetrics();
+
+    Vector<SVGTextMetrics>& textMetricsValues = logicalAttributes.textMetricsValues();
+    unsigned textMetricsSize = textMetricsValues.size();
+    while (true) {
+        if (m_logicalMetricsListOffset == textMetricsSize) {
+            if (!currentLogicalCharacterAttributes(logicalAttributes))
+                return false;
+
+            textMetricsValues = logicalAttributes.textMetricsValues();
+            textMetricsSize = textMetricsValues.size();
+            continue;
+        }
+
+        logicalMetrics = textMetricsValues.at(m_logicalMetricsListOffset);
+        if (logicalMetrics == SVGTextMetrics::emptyMetrics() || (!logicalMetrics.width() && !logicalMetrics.height())) {
+            advanceToNextLogicalCharacter(logicalMetrics);
+            continue;
+        }
+
+        // Stop if we found the next valid logical text metrics object.
+        return true;
+    }
+
+    ASSERT_NOT_REACHED();
+    return true;
+}
+
+bool SVGTextLayoutEngine::currentVisualCharacterMetrics(SVGInlineTextBox* textBox, RenderSVGInlineText* text, SVGTextMetrics& metrics)
+{
+    SVGTextLayoutAttributes& attributes = text->layoutAttributes();
+    Vector<SVGTextMetrics>& textMetricsValues = attributes.textMetricsValues();
+    ASSERT(!textMetricsValues.isEmpty());
+
+    unsigned textMetricsSize = textMetricsValues.size();
+    unsigned boxStart = textBox->start();
+    unsigned boxLength = textBox->len();
+
+    if (m_visualMetricsListOffset == textMetricsSize)
+        return false;
+
+    while (m_visualMetricsListOffset < textMetricsSize) {
+        SVGTextMetrics& visualMetrics = textMetricsValues.at(m_visualMetricsListOffset);
+
+        // Advance to text box start location.
+        if (m_visualCharacterOffset < boxStart) {
+            advanceToNextVisualCharacter(visualMetrics);
             continue;
         }
 
-        ++firstRange.start;
-    } while (consumed < consumeCharacters);
+        // Stop if we've finished processing this text box.
+        if (m_visualCharacterOffset >= boxStart + boxLength)
+            return false;
+
+        metrics = visualMetrics;
+        return true;
+    }
+
+    return false;
+}
+
+void SVGTextLayoutEngine::advanceToNextLogicalCharacter(const SVGTextMetrics& logicalMetrics)
+{
+    ++m_logicalMetricsListOffset;
+    m_logicalCharacterOffset += logicalMetrics.length();
+}
+
+void SVGTextLayoutEngine::advanceToNextVisualCharacter(const SVGTextMetrics& visualMetrics)
+{
+    ++m_visualMetricsListOffset;
+    m_visualCharacterOffset += visualMetrics.length();
 }
 
 void SVGTextLayoutEngine::layoutTextOnLineOrPath(SVGInlineTextBox* textBox, RenderSVGInlineText* text, const RenderStyle* style)
@@ -376,16 +443,10 @@ void SVGTextLayoutEngine::layoutTextOnLineOrPath(SVGInlineTextBox* textBox, Rend
     const SVGRenderStyle* svgStyle = style->svgStyle();
     ASSERT(svgStyle);
 
-    SVGTextLayoutAttributes& attributes = text->layoutAttributes();
-    Vector<SVGTextMetrics>& textMetricsValues = attributes.textMetricsValues();
+    m_visualMetricsListOffset = 0;
+    m_visualCharacterOffset = 0;
 
-    unsigned boxStart = textBox->start();
-    unsigned boxLength = textBox->len();
-    unsigned textMetricsSize = textMetricsValues.size();
-
-    unsigned positionListOffset = 0;
-    unsigned metricsListOffset = 0;
-    unsigned characterOffset = 0;
+    Vector<SVGTextMetrics>& textMetricsValues = text->layoutAttributes().textMetricsValues();
     const UChar* characters = text->characters();
 
     const Font& font = style->font();
@@ -400,65 +461,63 @@ void SVGTextLayoutEngine::layoutTextOnLineOrPath(SVGInlineTextBox* textBox, Rend
     baselineShift -= baselineLayout.calculateAlignmentBaselineShift(m_isVerticalText, text);
 
     // Main layout algorithm.
-    // Find the start of the current text box in this list, respecting ligatures.
-    for (; metricsListOffset < textMetricsSize; ++metricsListOffset) {
-        SVGTextMetrics& metrics = textMetricsValues.at(metricsListOffset);
+    while (true) {
+        // Find the start of the current text box in this list, respecting ligatures.
+        SVGTextMetrics visualMetrics = SVGTextMetrics::emptyMetrics();
+        if (!currentVisualCharacterMetrics(textBox, text, visualMetrics))
+            break;
 
-        // Advance to text box start location.
-        if (characterOffset < boxStart) {
-            characterOffset += metrics.length();
+        if (visualMetrics == SVGTextMetrics::emptyMetrics()) {
+            advanceToNextVisualCharacter(visualMetrics);
             continue;
         }
-   
-        // Stop if we've finished processing this text box.
-        if (characterOffset >= boxStart + boxLength)
+
+        SVGTextLayoutAttributes logicalAttributes;
+        if (!currentLogicalCharacterAttributes(logicalAttributes))
+            break;
+
+        SVGTextMetrics logicalMetrics = SVGTextMetrics::emptyMetrics();
+        if (!currentLogicalCharacterMetrics(logicalAttributes, logicalMetrics))
             break;
-        SVGInlineTextBox* ownerBox = 0;
-        nextLogicalBoxAndOffset(metrics.length(), positionListOffset, ownerBox);
 
-        SVGTextLayoutAttributes& currentBoxAttributes = toRenderSVGInlineText(ownerBox->textRenderer())->layoutAttributes();
-        Vector<float>& xValues = currentBoxAttributes.xValues();
-        Vector<float>& yValues = currentBoxAttributes.yValues();
+        Vector<float>& xValues = logicalAttributes.xValues();
+        Vector<float>& yValues = logicalAttributes.yValues();
+        Vector<float>& dxValues = logicalAttributes.dxValues();
+        Vector<float>& dyValues = logicalAttributes.dyValues();
+        Vector<float>& rotateValues = logicalAttributes.rotateValues();
 
-        float x = xValues.at(positionListOffset);
-        float y = yValues.at(positionListOffset);
+        float x = xValues.at(m_logicalCharacterOffset);
+        float y = yValues.at(m_logicalCharacterOffset);
 
         // When we've advanced to the box start offset, determine using the original x/y values,
         // whether this character starts a new text chunk, before doing any further processing.
-        if (characterOffset == boxStart)
-            textBox->setStartsNewTextChunk(toRenderSVGInlineText(ownerBox->textRenderer())->characterStartsNewTextChunk(positionListOffset));
-
-        if (metrics == SVGTextMetrics::emptyMetrics()) {
-            characterOffset += metrics.length();
-            continue;
-        }
+        if (m_visualCharacterOffset == textBox->start())
+            textBox->setStartsNewTextChunk(logicalAttributes.context()->characterStartsNewTextChunk(m_logicalCharacterOffset));
 
-        const UChar* currentCharacter = characters + characterOffset;
         float angle = 0;
-        Vector<float>& rotateValues = currentBoxAttributes.rotateValues();
         if (!rotateValues.isEmpty()) {
-            float newAngle = rotateValues.at(positionListOffset);
+            float newAngle = rotateValues.at(m_logicalCharacterOffset);
             if (newAngle != SVGTextLayoutAttributes::emptyValue())
                 angle = newAngle;
         }
 
         // Calculate glyph orientation angle.
+        const UChar* currentCharacter = characters + m_visualCharacterOffset;
         float orientationAngle = baselineLayout.calculateGlyphOrientationAngle(m_isVerticalText, svgStyle, *currentCharacter);
 
         // Calculate glyph advance & x/y orientation shifts.
         float xOrientationShift = 0;
         float yOrientationShift = 0;
-        float glyphAdvance = baselineLayout.calculateGlyphAdvanceAndOrientation(m_isVerticalText, metrics, orientationAngle, xOrientationShift, yOrientationShift);
+        float glyphAdvance = baselineLayout.calculateGlyphAdvanceAndOrientation(m_isVerticalText, visualMetrics, orientationAngle, xOrientationShift, yOrientationShift);
 
         // Assign current text position to x/y values, if needed.
         updateCharacerPositionIfNeeded(x, y);
 
         // Apply dx/dy value adjustments to current text position, if needed.
-        updateRelativePositionAdjustmentsIfNeeded(currentBoxAttributes.dxValues(), currentBoxAttributes.dyValues(), positionListOffset);
+        updateRelativePositionAdjustmentsIfNeeded(dxValues, dyValues);
 
         // Calculate SVG Fonts kerning, if needed.
-        float kerning = spacingLayout.calculateSVGKerning(m_isVerticalText, metrics.glyph());
+        float kerning = spacingLayout.calculateSVGKerning(m_isVerticalText, visualMetrics.glyph());
 
         // Calculate CSS 'kerning', 'letter-spacing' and 'word-spacing' for next character, if needed.
         float spacing = spacingLayout.calculateCSSKerningAndSpacing(svgStyle, lengthContext, currentCharacter);
@@ -498,7 +557,8 @@ void SVGTextLayoutEngine::layoutTextOnLineOrPath(SVGInlineTextBox* textBox, Rend
 
             // Skip character, if we're before the path.
             if (textPathOffset < 0) {
-                characterOffset += metrics.length();
+                advanceToNextLogicalCharacter(logicalMetrics);
+                advanceToNextVisualCharacter(visualMetrics);
                 continue;
             }
 
@@ -550,7 +610,7 @@ void SVGTextLayoutEngine::layoutTextOnLineOrPath(SVGInlineTextBox* textBox, Rend
         // If we already started a fragment, close it now.
         if (didStartTextFragment && shouldStartNewFragment) {
             applySpacingToNextCharacter = false;
-            recordTextFragment(textBox, textMetricsValues, characterOffset, metricsListOffset);
+            recordTextFragment(textBox, textMetricsValues);
         }
 
         // Eventually start a new fragment, if not yet done.
@@ -559,8 +619,8 @@ void SVGTextLayoutEngine::layoutTextOnLineOrPath(SVGInlineTextBox* textBox, Rend
             ASSERT(!m_currentTextFragment.length);
 
             didStartTextFragment = true;
-            m_currentTextFragment.characterOffset = characterOffset;
-            m_currentTextFragment.metricsListOffset = metricsListOffset;
+            m_currentTextFragment.characterOffset = m_visualCharacterOffset;
+            m_currentTextFragment.metricsListOffset = m_visualMetricsListOffset;
             m_currentTextFragment.x = x;
             m_currentTextFragment.y = y;
 
@@ -601,7 +661,8 @@ void SVGTextLayoutEngine::layoutTextOnLineOrPath(SVGInlineTextBox* textBox, Rend
             updateCurrentTextPosition(xNew, yNew, glyphAdvance + spacing);
         }
 
-        characterOffset += metrics.length();
+        advanceToNextLogicalCharacter(logicalMetrics);
+        advanceToNextVisualCharacter(visualMetrics);
         lastAngle = angle;
     }
 
@@ -609,7 +670,7 @@ void SVGTextLayoutEngine::layoutTextOnLineOrPath(SVGInlineTextBox* textBox, Rend
         return;
 
     // Close last open fragment, if needed.
-    recordTextFragment(textBox, textMetricsValues, characterOffset, metricsListOffset);
+    recordTextFragment(textBox, textMetricsValues);
 }
 
 }
index 6341c75..22dd59b 100644 (file)
@@ -24,6 +24,7 @@
 #include "Path.h"
 #include "SVGTextChunkBuilder.h"
 #include "SVGTextFragment.h"
+#include "SVGTextLayoutAttributes.h"
 #include "SVGTextMetrics.h"
 #include <wtf/Vector.h>
 
@@ -47,7 +48,7 @@ class SVGRenderStyle;
 class SVGTextLayoutEngine {
     WTF_MAKE_NONCOPYABLE(SVGTextLayoutEngine);
 public:
-    SVGTextLayoutEngine(Vector<SVGInlineTextBox*>& boxesInLogicalOrder);
+    SVGTextLayoutEngine(Vector<SVGTextLayoutAttributes>&);
     SVGTextChunkBuilder& chunkLayoutBuilder() { return m_chunkLayoutBuilder; }
 
     void beginTextPathLayout(RenderObject*, SVGTextLayoutEngine& lineLayout);
@@ -57,40 +58,34 @@ public:
     void finishLayout();
 
 private:
-    struct CharacterRange {
-        CharacterRange(unsigned newStart = 0, unsigned newEnd = 0, SVGInlineTextBox* newBox = 0)
-            : start(newStart)
-            , end(newEnd)
-            , box(newBox)
-        {
-        }
-
-        unsigned start;
-        unsigned end;
-        SVGInlineTextBox* box;
-    };
-
-    typedef Vector<CharacterRange> CharacterRanges;
-
     void updateCharacerPositionIfNeeded(float& x, float& y);
     void updateCurrentTextPosition(float x, float y, float glyphAdvance);
-    void updateRelativePositionAdjustmentsIfNeeded(Vector<float>& dxValues, Vector<float>& dyValues, unsigned valueListPosition);
+    void updateRelativePositionAdjustmentsIfNeeded(Vector<float>& dxValues, Vector<float>& dyValues);
 
-    void recordTextFragment(SVGInlineTextBox*, Vector<SVGTextMetrics>& textMetricValues, unsigned characterOffset, unsigned metricsListOffset);
+    void recordTextFragment(SVGInlineTextBox*, Vector<SVGTextMetrics>& textMetricValues);
     bool parentDefinesTextLength(RenderObject*) const;
 
     void layoutTextOnLineOrPath(SVGInlineTextBox*, RenderSVGInlineText*, const RenderStyle*);
     void finalizeTransformMatrices(Vector<SVGInlineTextBox*>&);
 
-    void nextLogicalBoxAndOffset(unsigned consumeCharacters, unsigned& positionListOffset, SVGInlineTextBox*&);
+    bool currentLogicalCharacterAttributes(SVGTextLayoutAttributes&);
+    bool currentLogicalCharacterMetrics(SVGTextLayoutAttributes&, SVGTextMetrics&);
+    bool currentVisualCharacterMetrics(SVGInlineTextBox*, RenderSVGInlineText*, SVGTextMetrics&);
+
+    void advanceToNextLogicalCharacter(const SVGTextMetrics&);
+    void advanceToNextVisualCharacter(const SVGTextMetrics&);
 
 private:
-    CharacterRanges m_ranges;
+    Vector<SVGTextLayoutAttributes> m_layoutAttributes;
     Vector<SVGInlineTextBox*> m_lineLayoutBoxes;
     Vector<SVGInlineTextBox*> m_pathLayoutBoxes;
     SVGTextChunkBuilder m_chunkLayoutBuilder;
 
     SVGTextFragment m_currentTextFragment;
+    unsigned m_logicalCharacterOffset;
+    unsigned m_logicalMetricsListOffset;
+    unsigned m_visualCharacterOffset;
+    unsigned m_visualMetricsListOffset;
     float m_x;
     float m_y;
     float m_dx;