Title: [117709] trunk
Revision
117709
Author
[email protected]
Date
2012-05-20 12:49:45 -0700 (Sun, 20 May 2012)

Log Message

Fix hit testing on non-scaling strokes
https://bugs.webkit.org/show_bug.cgi?id=82628

Reviewed by Nikolas Zimmermann.

Source/WebCore:

This change fixes hit testing on non-scaling strokes. It contains fixes for 3 bugs:
1) RenderSVGRect::shapeDependentStrokeContains was not falling back to shape-based hit testing.
2) m_strokeAndMarkerBoundingBox did not account for non-scaling strokes.
3) RenderSVGShape::shapeDependentStrokeContains did not have any support for non-scaling strokes.

This change also contains some refactoring/cleanup of the non-scale-stroke codepaths.

Test: svg/hittest/svg-shapes-non-scale-stroke.html

* rendering/svg/RenderSVGEllipse.cpp:
(WebCore::RenderSVGEllipse::createShape):
* rendering/svg/RenderSVGRect.cpp:
(WebCore::RenderSVGRect::createShape):
(WebCore::RenderSVGRect::shapeDependentStrokeContains):
* rendering/svg/RenderSVGShape.cpp:
(WebCore::RenderSVGShape::shapeDependentStrokeContains):
(WebCore::RenderSVGShape::shapeDependentFillContains):
(WebCore::RenderSVGShape::nonScalingStrokePath):
(WebCore):
(WebCore::RenderSVGShape::setupNonScalingStrokeContext):
(WebCore::RenderSVGShape::nonScalingStrokeTransform):
(WebCore::RenderSVGShape::strokePath):
(WebCore::RenderSVGShape::fillAndStrokePath):
(WebCore::RenderSVGShape::updateCachedBoundaries):
(WebCore::RenderSVGShape::inflateWithStrokeAndMarkerBounds):
* rendering/svg/RenderSVGShape.h:
(WebCore::RenderSVGShape::hasNonScalingStroke):
(RenderSVGShape):

LayoutTests:

* platform/chromium/test_expectations.txt:
* platform/mac/Skipped:
* svg/custom/non-scaling-stroke-expected.txt:
* svg/hittest/svg-shapes-non-scale-stroke-expected.txt: Added.
* svg/hittest/svg-shapes-non-scale-stroke.html: Added.

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (117708 => 117709)


--- trunk/LayoutTests/ChangeLog	2012-05-20 19:10:28 UTC (rev 117708)
+++ trunk/LayoutTests/ChangeLog	2012-05-20 19:49:45 UTC (rev 117709)
@@ -1,3 +1,16 @@
+2012-05-20  Philip Rogers  <[email protected]>
+
+        Fix hit testing on non-scaling strokes
+        https://bugs.webkit.org/show_bug.cgi?id=82628
+
+        Reviewed by Nikolas Zimmermann.
+
+        * platform/chromium/test_expectations.txt:
+        * platform/mac/Skipped:
+        * svg/custom/non-scaling-stroke-expected.txt:
+        * svg/hittest/svg-shapes-non-scale-stroke-expected.txt: Added.
+        * svg/hittest/svg-shapes-non-scale-stroke.html: Added.
+
 2012-05-19  Joshua Bell  <[email protected]>
 
         IndexedDB layout tests should make better use of test library functions

Modified: trunk/LayoutTests/platform/chromium/test_expectations.txt (117708 => 117709)


--- trunk/LayoutTests/platform/chromium/test_expectations.txt	2012-05-20 19:10:28 UTC (rev 117708)
+++ trunk/LayoutTests/platform/chromium/test_expectations.txt	2012-05-20 19:49:45 UTC (rev 117709)
@@ -1280,8 +1280,6 @@
 
 BUGWK83875 LINUX : svg/text/font-size-below-point-five.svg = PASS TEXT
 
-BUGWK82628 : svg/hittest/svg-ellipse-non-scale-stroke.xhtml = FAIL
-
 // It is very hard to see why some of these are failing. The diff must be tiny in some cases.
 // Other fail intermittently in a way that is odd - clipping and masking should be stable.
 // It is probably a test design flaw.

Modified: trunk/LayoutTests/platform/mac/Skipped (117708 => 117709)


--- trunk/LayoutTests/platform/mac/Skipped	2012-05-20 19:10:28 UTC (rev 117708)
+++ trunk/LayoutTests/platform/mac/Skipped	2012-05-20 19:49:45 UTC (rev 117709)
@@ -642,9 +642,6 @@
 fast/repaint/scroll-relative-table-inside-table-cell.html
 fast/table/cell-pref-width-invalidation.html
 
-# https://bugs.webkit.org/show_bug.cgi?id=82628
-svg/hittest/svg-ellipse-non-scale-stroke.xhtml
-
 # The inner <input> should now grow to take the full cell's height.
 fast/table/colspanMinWidth-vertical.html
 

Modified: trunk/LayoutTests/svg/custom/non-scaling-stroke-expected.txt (117708 => 117709)


--- trunk/LayoutTests/svg/custom/non-scaling-stroke-expected.txt	2012-05-20 19:10:28 UTC (rev 117708)
+++ trunk/LayoutTests/svg/custom/non-scaling-stroke-expected.txt	2012-05-20 19:49:45 UTC (rev 117709)
@@ -1,7 +1,7 @@
 layer at (0,0) size 800x600
   RenderView at (0,0) size 800x600
 layer at (0,0) size 800x600
-  RenderSVGRoot {svg} at (18,12) size 259x306
+  RenderSVGRoot {svg} at (12,0) size 287x488
     RenderSVGHiddenContainer {defs} at (0,0) size 0x0
       RenderSVGResourceLinearGradient {linearGradient} [id="grad1"] [gradientUnits=objectBoundingBox] [start=(0,0)] [end=(1,1)]
         RenderSVGGradientStop {stop} [offset=0.00] [color=#0000FF]
@@ -15,14 +15,14 @@
         RenderSVGRect {rect} at (0,10) size 10x10 [fill={[type=SOLID] [color=#0000FF]}] [x=0.00] [y=10.00] [width=10.00] [height=10.00]
         RenderSVGRect {rect} at (10,10) size 10x10 [fill={[type=SOLID] [color=#FFFF00]}] [x=10.00] [y=10.00] [width=10.00] [height=10.00]
       RenderSVGRect {rect} at (0,0) size 400x50 [x=0.00] [y=0.00] [width=400.00] [height=50.00]
-    RenderSVGContainer {g} at (18,12) size 104x66 [transform={m=((1.00,0.00)(0.00,1.00)) t=(20.00,20.00)}]
-      RenderSVGRect {rect} at (18,12) size 104x66 [transform={m=((0.25,0.00)(0.00,1.00)) t=(0.00,0.00)}] [stroke={[type=LINEAR-GRADIENT] [id="grad1"] [stroke width=15.00]}] [x=0.00] [y=0.00] [width=400.00] [height=50.00]
-    RenderSVGRect {rect} at (18,92) size 104x66 [transform={m=((0.25,0.00)(0.00,1.00)) t=(20.00,100.00)}] [stroke={[type=LINEAR-GRADIENT] [id="grad2"] [stroke width=15.00]}] [x=0.00] [y=0.00] [width=400.00] [height=50.00]
-    RenderSVGContainer {use} at (18,172) size 104x66 [transform={m=((0.25,0.00)(0.00,1.00)) t=(20.00,180.00)}]
-      RenderSVGRect {rect} at (18,172) size 104x66 [stroke={[type=PATTERN] [id="pattern"] [stroke width=15.00]}] [x=0.00] [y=0.00] [width=400.00] [height=50.00]
-    RenderSVGContainer {use} at (18,252) size 104x66 [transform={m=((0.25,0.00)(0.00,1.00)) t=(20.00,260.00)}]
-      RenderSVGRect {rect} at (18,252) size 104x66 [stroke={[type=SOLID] [color=#008000] [stroke width=15.00]}] [x=0.00] [y=0.00] [width=400.00] [height=50.00]
-    RenderSVGContainer {use} at (156,12) size 121x66 [transform={m=((0.25,0.00)(0.25,1.00)) t=(160.00,20.00)}]
-      RenderSVGRect {rect} at (156,12) size 121x66 [stroke={[type=SOLID] [color=#008000] [stroke width=15.00]}] [x=0.00] [y=0.00] [width=400.00] [height=50.00]
-    RenderSVGContainer {use} at (158,89) size 104x217 [transform={m=((0.25,0.36)(0.00,1.00)) t=(160.00,100.00)}]
-      RenderSVGRect {rect} at (158,89) size 104x217 [stroke={[type=SOLID] [color=#008000] [stroke width=15.00]}] [x=0.00] [y=0.00] [width=400.00] [height=50.00]
+    RenderSVGContainer {g} at (12,12) size 116x66 [transform={m=((1.00,0.00)(0.00,1.00)) t=(20.00,20.00)}]
+      RenderSVGRect {rect} at (12,12) size 116x66 [transform={m=((0.25,0.00)(0.00,1.00)) t=(0.00,0.00)}] [stroke={[type=LINEAR-GRADIENT] [id="grad1"] [stroke width=15.00]}] [x=0.00] [y=0.00] [width=400.00] [height=50.00]
+    RenderSVGRect {rect} at (12,92) size 116x66 [transform={m=((0.25,0.00)(0.00,1.00)) t=(20.00,100.00)}] [stroke={[type=LINEAR-GRADIENT] [id="grad2"] [stroke width=15.00]}] [x=0.00] [y=0.00] [width=400.00] [height=50.00]
+    RenderSVGContainer {use} at (12,172) size 116x66 [transform={m=((0.25,0.00)(0.00,1.00)) t=(20.00,180.00)}]
+      RenderSVGRect {rect} at (12,172) size 116x66 [stroke={[type=PATTERN] [id="pattern"] [stroke width=15.00]}] [x=0.00] [y=0.00] [width=400.00] [height=50.00]
+    RenderSVGContainer {use} at (12,252) size 116x66 [transform={m=((0.25,0.00)(0.00,1.00)) t=(20.00,260.00)}]
+      RenderSVGRect {rect} at (12,252) size 116x66 [stroke={[type=SOLID] [color=#008000] [stroke width=15.00]}] [x=0.00] [y=0.00] [width=400.00] [height=50.00]
+    RenderSVGContainer {use} at (134,12) size 165x66 [transform={m=((0.25,0.00)(0.25,1.00)) t=(160.00,20.00)}]
+      RenderSVGRect {rect} at (134,12) size 165x66 [stroke={[type=SOLID] [color=#008000] [stroke width=15.00]}] [x=0.00] [y=0.00] [width=400.00] [height=50.00]
+    RenderSVGContainer {use} at (152,0) size 116x488 [transform={m=((0.25,0.36)(0.00,1.00)) t=(160.00,100.00)}]
+      RenderSVGRect {rect} at (152,0) size 116x488 [stroke={[type=SOLID] [color=#008000] [stroke width=15.00]}] [x=0.00] [y=0.00] [width=400.00] [height=50.00]

Added: trunk/LayoutTests/svg/hittest/svg-shapes-non-scale-stroke-expected.txt (0 => 117709)


--- trunk/LayoutTests/svg/hittest/svg-shapes-non-scale-stroke-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/svg/hittest/svg-shapes-non-scale-stroke-expected.txt	2012-05-20 19:49:45 UTC (rev 117709)
@@ -0,0 +1,51 @@
+Tests for WK82628 - Make sure hit testing works properly on ellipses, rects, lines and curves with non-scaling stroke.
+
+On success, you will see a series of "PASS" messages and no "FAIL" messages.
+
+PASS ellipse1 stroke contains point at (29, 37)
+PASS ellipse2 stroke contains point at (279, 37)
+PASS ellipse1 stroke contains point at (34, 48)
+PASS ellipse2 stroke contains point at (284, 48)
+PASS ellipse1 stroke contains point at (94, 70)
+PASS ellipse2 stroke contains point at (344, 70)
+PASS ellipse1 stroke contains point at (100, 78)
+PASS ellipse2 stroke contains point at (350, 78)
+PASS ellipse1 stroke does not contain point at (33, 18)
+PASS ellipse2 stroke does not contain point at (283, 18)
+PASS ellipse1 stroke does not contain point at (81, 49)
+PASS ellipse2 stroke does not contain point at (331, 49)
+PASS ellipse1 stroke does not contain point at (129, 78)
+PASS ellipse2 stroke does not contain point at (379, 78)
+PASS rect1 stroke contains point at (172, 45)
+PASS rect2 stroke contains point at (422, 45)
+PASS rect1 stroke contains point at (183, 63)
+PASS rect2 stroke contains point at (433, 63)
+PASS rect1 stroke contains point at (220, 82)
+PASS rect2 stroke contains point at (470, 82)
+PASS rect1 stroke contains point at (233, 90)
+PASS rect2 stroke contains point at (483, 90)
+PASS rect1 stroke does not contain point at (205, 68)
+PASS rect2 stroke does not contain point at (455, 68)
+PASS line1 stroke contains point at (127, 97)
+PASS line2 stroke contains point at (377, 97)
+PASS line1 stroke contains point at (174, 146)
+PASS line2 stroke contains point at (424, 146)
+PASS line1 stroke does not contain point at (138, 131)
+PASS line2 stroke does not contain point at (388, 131)
+PASS line1 stroke does not contain point at (177, 126)
+PASS line2 stroke does not contain point at (427, 126)
+PASS curve1 stroke contains point at (24, 159)
+PASS curve2 stroke contains point at (274, 159)
+PASS curve1 stroke contains point at (107, 126)
+PASS curve2 stroke contains point at (357, 126)
+PASS curve1 stroke contains point at (169, 193)
+PASS curve2 stroke contains point at (419, 193)
+PASS curve1 stroke contains point at (195, 159)
+PASS curve2 stroke contains point at (445, 159)
+PASS curve1 stroke does not contain point at (5, 164)
+PASS curve2 stroke does not contain point at (255, 164)
+PASS curve1 stroke does not contain point at (81, 140)
+PASS curve2 stroke does not contain point at (331, 140)
+PASS curve1 stroke does not contain point at (161, 177)
+PASS curve2 stroke does not contain point at (411, 177)
+

Added: trunk/LayoutTests/svg/hittest/svg-shapes-non-scale-stroke.html (0 => 117709)


--- trunk/LayoutTests/svg/hittest/svg-shapes-non-scale-stroke.html	                        (rev 0)
+++ trunk/LayoutTests/svg/hittest/svg-shapes-non-scale-stroke.html	2012-05-20 19:49:45 UTC (rev 117709)
@@ -0,0 +1,175 @@
+<html xmlns='http://www.w3.org/1999/xhtml'>
+  <head>
+    <style type="text/css">
+      #svgRoot {
+        margin: 0px;
+        padding: 0px;
+        position: absolute; 
+        top: 0px; 
+        left: 0px;
+      }
+      .nonscaletest {
+        stroke-width: 20px;
+        fill: #ccc;
+        fill-opacity: 0.4;
+      }
+      #ellipse1, #ellipse2 {
+          stroke: #f31900;
+      }
+      #line1, #line2 {
+          stroke: #3364c2;
+      }
+      #rect1, #rect2 {
+          stroke: #f7d72b;
+      }
+      #curve1, #curve2 {
+          stroke: #44c400;
+      }
+    </style>
+    <script type="text/_javascript_">
+      function enter(event) {
+        event.target.setAttribute("stroke-opacity", "0.4");
+      };
+      function exit(event) {
+        event.target.setAttribute("stroke-opacity", "0.7");
+      };
+    </script>
+  </head>
+  <body>
+    <p>Tests for WK82628 - Make sure hit testing works properly on ellipses, rects, lines and curves with non-scaling stroke.</p>
+    <p>On success, you will see a series of "PASS" messages and no "FAIL" messages.</p>
+    <pre id="console"></pre>
+
+    <svg id="svgRoot" width="500px" height="300px" viewBox="0 0 500 300" xmlns="http://www.w3.org/2000/svg">
+      <!-- The following are all small and scaled up -->
+      <g transform="translate(30, 0) scale(5, 5)">
+        <ellipse id="ellipse1" class="nonscaletest" cx="10" cy="10" rx="10" ry="5" vector-effect="non-scaling-stroke" pointer-events="visibleStroke" _onmouseover_="enter(evt)" _onmouseout_="exit(evt)" stroke-opacity="0.7" stroke-dasharray="15,25" stroke-linecap="round"/>
+        <rect id="rect1" class="nonscaletest" x="30" y="10" width="10" height="7" vector-effect="non-scaling-stroke" pointer-events="visibleStroke" _onmouseover_="enter(evt)" _onmouseout_="exit(evt)" stroke-opacity="0.7"  stroke-dasharray="15,25" stroke-linecap="round"/>
+        <path id="line1" class="nonscaletest" d="M20 20 l 10 10z" vector-effect="non-scaling-stroke" pointer-events="visibleStroke" _onmouseover_="enter(evt)" _onmouseout_="exit(evt)" stroke-opacity="0.7" stroke-dasharray="15,25" stroke-linecap="round"/>
+        <g transform="translate(-6, 12)">
+          <path id="curve1" class="nonscaletest" d="M5,20 C10,10 25,10 25,20 S40,30 40,20" vector-effect="non-scaling-stroke" pointer-events="visibleStroke" _onmouseover_="enter(evt)" _onmouseout_="exit(evt)" stroke-opacity="0.7" stroke-dasharray="15,25" stroke-linecap="round"/>
+        </g>
+      </g>
+      <!-- The following are all large and scaled down -->
+      <g transform="translate(275, 0) scale(0.2, 0.2)">
+        <ellipse id="ellipse2" class="nonscaletest" cx="250" cy="250" rx="250" ry="125" vector-effect="non-scaling-stroke" pointer-events="visibleStroke" _onmouseover_="enter(evt)" class="nonscaletest" _onmouseout_="exit(evt)" stroke-opacity="0.7" stroke-dasharray="15,25" stroke-linecap="round"/>
+        <rect id="rect2" class="nonscaletest" x="750" y="250" width="250" height="175" vector-effect="non-scaling-stroke" pointer-events="visibleStroke" _onmouseover_="enter(evt)" _onmouseout_="exit(evt)" stroke-opacity="0.7" stroke-dasharray="15,25" stroke-linecap="round"/>
+        <path id="line2" class="nonscaletest" d="M500 500 l 250 250z" vector-effect="non-scaling-stroke" pointer-events="visibleStroke" _onmouseover_="enter(evt)" _onmouseout_="exit(evt)" stroke-opacity="0.7" stroke-dasharray="15,25" stroke-linecap="round"/>
+        <g transform="translate(-150, 300)">
+          <path id="curve2" class="nonscaletest" d="M125,500 C250,250 625,250 600,500 S1000,750 1000,500" vector-effect="non-scaling-stroke" pointer-events="visibleStroke" _onmouseover_="enter(evt)" _onmouseout_="exit(evt)" stroke-opacity="0.7" stroke-dasharray="15,25" stroke-linecap="round"/>
+        </g>
+      </g>
+    </svg>
+    <script type="text/_javascript_">
+      // document.body._onmousedown_ = function(evt) { console.log("mouse down at " + evt.x + "," + evt.y); }
+      if (window.layoutTestController)
+        layoutTestController.dumpAsText();
+
+      var resultString = "";
+      var ellipse1 = document.getElementById("ellipse1");
+      var ellipse2 = document.getElementById("ellipse2"); // 250px to the right of ellipse1
+      var pointsOnEllipse1Stroke = [
+         {x: 29, y: 37},
+         {x: 34, y: 48},
+         {x: 94, y: 70},
+         {x: 100, y: 78}
+      ];
+      var pointsNotOnEllipse1Stroke = [
+         {x: 33, y: 18},
+         {x: 81, y: 49},
+         {x: 129, y: 78}
+      ];
+
+      var rect1 = document.getElementById("rect1");
+      var rect2 = document.getElementById("rect2"); // 250px to the right of rect1
+      var pointsOnRect1Stroke = [
+         {x: 172, y: 45},
+         {x: 183, y: 63},
+         {x: 220, y: 82},
+         {x: 233, y: 90}
+      ];
+      var pointsNotOnRect1Stroke = [
+         {x: 205, y: 68}
+      ];
+
+      var line1 = document.getElementById("line1");
+      var line2 = document.getElementById("line2"); // 250px to the right of line1
+      var pointsOnLine1Stroke = [
+         {x: 127, y: 97},
+         {x: 174, y: 146}
+      ];
+      var pointsNotOnLine1Stroke = [
+         {x: 138, y: 131},
+         {x: 177, y: 126}
+      ];
+
+      var curve1 = document.getElementById("curve1");
+      var curve2 = document.getElementById("curve2"); // 250px to the right of curve1
+      var pointsOnCurve1Stroke = [
+         {x: 24, y: 159},
+         {x: 107, y: 126},
+         {x: 169, y: 193},
+         {x: 195, y: 159}
+      ];
+      var pointsNotOnCurve1Stroke = [
+         {x: 5, y: 164},
+         {x: 81, y: 140},
+         {x: 161, y: 177}
+      ];
+
+      pointsOnEllipse1Stroke.forEach( function(point) {
+        var pass = (ellipse1 == document.elementFromPoint(point.x, point.y));
+        resultString += ((pass) ? "PASS" : "FAIL") + " ellipse1 stroke contains point at (" + point.x + ", " + point.y + ")\n";
+        var pass = (ellipse2 == document.elementFromPoint(point.x + 250, point.y));
+        resultString += ((pass) ? "PASS" : "FAIL") + " ellipse2 stroke contains point at (" + (point.x+250) + ", " + point.y + ")\n";
+      });
+      pointsNotOnEllipse1Stroke.forEach( function(point) {
+        var pass = (ellipse1 != document.elementFromPoint(point.x, point.y));
+        resultString += ((pass) ? "PASS" : "FAIL") + " ellipse1 stroke does not contain point at (" + point.x + ", " + point.y + ")\n";
+        var pass = (ellipse2 != document.elementFromPoint(point.x + 250, point.y));
+        resultString += ((pass) ? "PASS" : "FAIL") + " ellipse2 stroke does not contain point at (" + (point.x+250) + ", " + point.y + ")\n";
+      });
+
+      pointsOnRect1Stroke.forEach( function(point) {
+        var pass = (rect1 == document.elementFromPoint(point.x, point.y));
+        resultString += ((pass) ? "PASS" : "FAIL") + " rect1 stroke contains point at (" + point.x + ", " + point.y + ")\n";
+        var pass = (rect2 == document.elementFromPoint(point.x + 250, point.y));
+        resultString += ((pass) ? "PASS" : "FAIL") + " rect2 stroke contains point at (" + (point.x+250) + ", " + point.y + ")\n";
+      });
+      pointsNotOnRect1Stroke.forEach( function(point) {
+        var pass = (rect1 != document.elementFromPoint(point.x, point.y));
+        resultString += ((pass) ? "PASS" : "FAIL") + " rect1 stroke does not contain point at (" + point.x + ", " + point.y + ")\n";
+        var pass = (rect2 != document.elementFromPoint(point.x + 250, point.y));
+        resultString += ((pass) ? "PASS" : "FAIL") + " rect2 stroke does not contain point at (" + (point.x+250) + ", " + point.y + ")\n";
+      });
+
+      pointsOnLine1Stroke.forEach( function(point) {
+        var pass = (line1 == document.elementFromPoint(point.x, point.y));
+        resultString += ((pass) ? "PASS" : "FAIL") + " line1 stroke contains point at (" + point.x + ", " + point.y + ")\n";
+        var pass = (line2 == document.elementFromPoint(point.x + 250, point.y));
+        resultString += ((pass) ? "PASS" : "FAIL") + " line2 stroke contains point at (" + (point.x+250) + ", " + point.y + ")\n";
+      });
+      pointsNotOnLine1Stroke.forEach( function(point) {
+        var pass = (line1 != document.elementFromPoint(point.x, point.y));
+        resultString += ((pass) ? "PASS" : "FAIL") + " line1 stroke does not contain point at (" + point.x + ", " + point.y + ")\n";
+        var pass = (line2 != document.elementFromPoint(point.x + 250, point.y));
+        resultString += ((pass) ? "PASS" : "FAIL") + " line2 stroke does not contain point at (" + (point.x+250) + ", " + point.y + ")\n";
+      });
+
+      pointsOnCurve1Stroke.forEach( function(point) {
+        var pass = (curve1 == document.elementFromPoint(point.x, point.y));
+        resultString += ((pass) ? "PASS" : "FAIL") + " curve1 stroke contains point at (" + point.x + ", " + point.y + ")\n";
+        var pass = (curve2 == document.elementFromPoint(point.x + 250, point.y));
+        resultString += ((pass) ? "PASS" : "FAIL") + " curve2 stroke contains point at (" + (point.x+250) + ", " + point.y + ")\n";
+      });
+      pointsNotOnCurve1Stroke.forEach( function(point) {
+        var pass = (curve1 != document.elementFromPoint(point.x, point.y));
+        resultString += ((pass) ? "PASS" : "FAIL") + " curve1 stroke does not contain point at (" + point.x + ", " + point.y + ")\n";
+        var pass = (curve2 != document.elementFromPoint(point.x + 250, point.y));
+        resultString += ((pass) ? "PASS" : "FAIL") + " curve2 stroke does not contain point at (" + (point.x+250) + ", " + point.y + ")\n";
+      });
+
+      document.getElementById("console").innerHTML = resultString;
+    </script>
+  </body>
+</html>

Modified: trunk/Source/WebCore/ChangeLog (117708 => 117709)


--- trunk/Source/WebCore/ChangeLog	2012-05-20 19:10:28 UTC (rev 117708)
+++ trunk/Source/WebCore/ChangeLog	2012-05-20 19:49:45 UTC (rev 117709)
@@ -1,3 +1,39 @@
+2012-05-20  Philip Rogers  <[email protected]>
+
+        Fix hit testing on non-scaling strokes
+        https://bugs.webkit.org/show_bug.cgi?id=82628
+
+        Reviewed by Nikolas Zimmermann.
+
+        This change fixes hit testing on non-scaling strokes. It contains fixes for 3 bugs:
+        1) RenderSVGRect::shapeDependentStrokeContains was not falling back to shape-based hit testing.
+        2) m_strokeAndMarkerBoundingBox did not account for non-scaling strokes.
+        3) RenderSVGShape::shapeDependentStrokeContains did not have any support for non-scaling strokes.
+
+        This change also contains some refactoring/cleanup of the non-scale-stroke codepaths.
+
+        Test: svg/hittest/svg-shapes-non-scale-stroke.html
+
+        * rendering/svg/RenderSVGEllipse.cpp:
+        (WebCore::RenderSVGEllipse::createShape):
+        * rendering/svg/RenderSVGRect.cpp:
+        (WebCore::RenderSVGRect::createShape):
+        (WebCore::RenderSVGRect::shapeDependentStrokeContains):
+        * rendering/svg/RenderSVGShape.cpp:
+        (WebCore::RenderSVGShape::shapeDependentStrokeContains):
+        (WebCore::RenderSVGShape::shapeDependentFillContains):
+        (WebCore::RenderSVGShape::nonScalingStrokePath):
+        (WebCore):
+        (WebCore::RenderSVGShape::setupNonScalingStrokeContext):
+        (WebCore::RenderSVGShape::nonScalingStrokeTransform):
+        (WebCore::RenderSVGShape::strokePath):
+        (WebCore::RenderSVGShape::fillAndStrokePath):
+        (WebCore::RenderSVGShape::updateCachedBoundaries):
+        (WebCore::RenderSVGShape::inflateWithStrokeAndMarkerBounds):
+        * rendering/svg/RenderSVGShape.h:
+        (WebCore::RenderSVGShape::hasNonScalingStroke):
+        (RenderSVGShape):
+
 2012-05-19  Emil A Eklund  <[email protected]>
 
         Simplify RenderOverflow by using Rects

Modified: trunk/Source/WebCore/rendering/svg/RenderSVGEllipse.cpp (117708 => 117709)


--- trunk/Source/WebCore/rendering/svg/RenderSVGEllipse.cpp	2012-05-20 19:10:28 UTC (rev 117708)
+++ trunk/Source/WebCore/rendering/svg/RenderSVGEllipse.cpp	2012-05-20 19:49:45 UTC (rev 117709)
@@ -55,7 +55,7 @@
     m_radii = FloatSize();
 
     // Fallback to RenderSVGShape if shape has a non scaling stroke.
-    if (style()->svgStyle()->vectorEffect() == VE_NON_SCALING_STROKE) {
+    if (hasNonScalingStroke()) {
         RenderSVGShape::createShape();
         setIsPaintingFallback(true);
         return;

Modified: trunk/Source/WebCore/rendering/svg/RenderSVGRect.cpp (117708 => 117709)


--- trunk/Source/WebCore/rendering/svg/RenderSVGRect.cpp	2012-05-20 19:10:28 UTC (rev 117708)
+++ trunk/Source/WebCore/rendering/svg/RenderSVGRect.cpp	2012-05-20 19:49:45 UTC (rev 117709)
@@ -55,9 +55,8 @@
     SVGRectElement* rect = static_cast<SVGRectElement*>(node());
     ASSERT(rect);
 
-    bool nonScalingStroke = style()->svgStyle()->vectorEffect() == VE_NON_SCALING_STROKE;
     // Fallback to RenderSVGShape if rect has rounded corners.
-    if (rect->hasAttribute(SVGNames::rxAttr) || rect->hasAttribute(SVGNames::ryAttr) || nonScalingStroke) {
+    if (rect->hasAttribute(SVGNames::rxAttr) || rect->hasAttribute(SVGNames::ryAttr) || hasNonScalingStroke()) {
        RenderSVGShape::createShape();
        setIsPaintingFallback(true);
        return;
@@ -138,6 +137,9 @@
 
 bool RenderSVGRect::shapeDependentStrokeContains(const FloatPoint& point) const
 {
+    if (isPaintingFallback())
+        return RenderSVGShape::shapeDependentStrokeContains(point);
+
     return m_outerStrokeRect.contains(point, FloatRect::InsideOrOnStroke) && !m_innerStrokeRect.contains(point, FloatRect::InsideButNotOnStroke);
 }
 

Modified: trunk/Source/WebCore/rendering/svg/RenderSVGShape.cpp (117708 => 117709)


--- trunk/Source/WebCore/rendering/svg/RenderSVGShape.cpp	2012-05-20 19:10:28 UTC (rev 117708)
+++ trunk/Source/WebCore/rendering/svg/RenderSVGShape.cpp	2012-05-20 19:49:45 UTC (rev 117709)
@@ -100,12 +100,22 @@
 
 bool RenderSVGShape::shapeDependentStrokeContains(const FloatPoint& point) const
 {
+    ASSERT(m_path);
     BoundingRectStrokeStyleApplier applier(this, style());
+
+    if (hasNonScalingStroke()) {
+        AffineTransform nonScalingTransform = nonScalingStrokeTransform();
+        Path* usePath = nonScalingStrokePath(m_path.get(), nonScalingTransform);
+
+        return usePath->strokeContains(&applier, nonScalingTransform.mapPoint(point));
+    }
+
     return m_path->strokeContains(&applier, point);
 }
 
 bool RenderSVGShape::shapeDependentFillContains(const FloatPoint& point, const WindRule fillRule) const
 {
+    ASSERT(m_path);
     return m_path->contains(point, fillRule);
 }
 
@@ -197,6 +207,32 @@
     setNeedsLayout(false);
 }
 
+Path* RenderSVGShape::nonScalingStrokePath(const Path* path, const AffineTransform& strokeTransform) const
+{
+    DEFINE_STATIC_LOCAL(Path, tempPath, ());
+
+    tempPath = *path;
+    tempPath.transform(strokeTransform);
+
+    return &tempPath;
+}
+
+bool RenderSVGShape::setupNonScalingStrokeContext(AffineTransform& strokeTransform, GraphicsContextStateSaver& stateSaver)
+{
+    if (!strokeTransform.isInvertible())
+        return false;
+
+    stateSaver.save();
+    stateSaver.context()->concatCTM(strokeTransform.inverse());
+    return true;
+}
+
+AffineTransform RenderSVGShape::nonScalingStrokeTransform() const
+{
+    SVGStyledTransformableElement* element = static_cast<SVGStyledTransformableElement*>(node());
+    return element->getScreenCTM(SVGLocatable::DisallowStyleUpdate);
+}
+
 bool RenderSVGShape::shouldStrokeZeroLengthSubpath() const
 {
     // Spec(11.4): Any zero length subpath shall not be stroked if the "stroke-linecap" property has a value of butt
@@ -225,28 +261,6 @@
     return &tempPath;
 }
 
-Path* RenderSVGShape::nonScalingStrokePath(const Path* path, const AffineTransform& strokeTransform)
-{
-    DEFINE_STATIC_LOCAL(Path, tempPath, ());
-
-    tempPath = *path;
-    tempPath.transform(strokeTransform);
-
-    return &tempPath;
-}
-
-bool RenderSVGShape::setupNonScalingStrokeTransform(AffineTransform& strokeTransform, GraphicsContextStateSaver& stateSaver)
-{
-    SVGStyledTransformableElement* element = static_cast<SVGStyledTransformableElement*>(node());
-    strokeTransform = element->getScreenCTM(SVGLocatable::DisallowStyleUpdate);
-    if (!strokeTransform.isInvertible())
-        return false;
-
-    stateSaver.save();
-    stateSaver.context()->concatCTM(strokeTransform.inverse());
-    return true;
-}
-
 void RenderSVGShape::fillShape(RenderStyle* style, GraphicsContext* context, Path* path, RenderSVGShape* shape)
 {
     Color fallbackColor;
@@ -263,25 +277,23 @@
 }
 
 void RenderSVGShape::strokePath(RenderStyle* style, GraphicsContext* context, Path* path, RenderSVGResource* strokePaintingResource,
-                                const Color& fallbackColor, bool nonScalingStroke, const AffineTransform& nonScalingStrokeTransform,
-                                int applyMode)
+                                const Color& fallbackColor, int applyMode)
 {
     if (!style->svgStyle()->hasVisibleStroke())
         return;
-    Path* usePath = path;
-    if (nonScalingStroke) {
-        usePath = nonScalingStrokePath(path, nonScalingStrokeTransform);
-    }
+
     if (strokePaintingResource->applyResource(this, style, context, applyMode)) {
-        strokePaintingResource->postApplyResource(this, context, applyMode, usePath, this);
+        strokePaintingResource->postApplyResource(this, context, applyMode, path, this);
         return;
     }
+
     if (!fallbackColor.isValid())
         return;
+
     RenderSVGResourceSolidColor* fallbackResource = RenderSVGResource::sharedSolidPaintingResource();
     fallbackResource->setColor(fallbackColor);
     if (fallbackResource->applyResource(this, style, context, applyMode))
-        fallbackResource->postApplyResource(this, context, applyMode, usePath, this);
+        fallbackResource->postApplyResource(this, context, applyMode, path, this);
 }
 
 void RenderSVGShape::fillAndStrokePath(GraphicsContext* context)
@@ -295,23 +307,27 @@
     if (!strokePaintingResource)
         return;
 
+    Path* usePath = m_path.get();
     GraphicsContextStateSaver stateSaver(*context, false);
-    AffineTransform nonScalingStrokeTransform;
-    bool nonScalingStroke = style->svgStyle()->vectorEffect() == VE_NON_SCALING_STROKE;
-    if (nonScalingStroke) {
-        if (!setupNonScalingStrokeTransform(nonScalingStrokeTransform, stateSaver))
+    AffineTransform nonScalingTransform;
+
+    if (hasNonScalingStroke()) {
+        nonScalingTransform = nonScalingStrokeTransform();
+        if (!setupNonScalingStrokeContext(nonScalingTransform, stateSaver))
             return;
+        usePath = nonScalingStrokePath(usePath, nonScalingTransform);
     }
 
-    strokePath(style, context, m_path.get(), strokePaintingResource, fallbackColor, nonScalingStroke, nonScalingStrokeTransform, ApplyToStrokeMode);
+    strokePath(style, context, usePath, strokePaintingResource, fallbackColor, ApplyToStrokeMode);
 
     // Spec(11.4): Any zero length subpath shall not be stroked if the "stroke-linecap" property has a value of butt
     // but shall be stroked if the "stroke-linecap" property has a value of round or square
     for (size_t i = 0; i < m_zeroLengthLinecapLocations.size(); ++i) {
         Path* usePath = zeroLengthLinecapPath(m_zeroLengthLinecapLocations[i]);
-        strokePath(style, context, usePath, strokePaintingResource, fallbackColor, nonScalingStroke, nonScalingStrokeTransform, ApplyToFillMode);
+        if (hasNonScalingStroke())
+            usePath = nonScalingStrokePath(usePath, nonScalingTransform);
+        strokePath(style, context, usePath, strokePaintingResource, fallbackColor, ApplyToFillMode);
     }
-
 }
 
 void RenderSVGShape::paint(PaintInfo& paintInfo, const LayoutPoint&)
@@ -423,6 +439,7 @@
     m_fillBoundingBox = objectBoundingBox();
 
     // Add zero-length sub-path linecaps to the fill box
+    // FIXME: zero-length subpaths do not respect vector-effect = non-scaling-stroke.
     float strokeWidth = this->strokeWidth();
     for (size_t i = 0; i < m_zeroLengthLinecapLocations.size(); ++i)
         m_fillBoundingBox.unite(zeroLengthSubpathRect(m_zeroLengthLinecapLocations[i], strokeWidth));
@@ -449,7 +466,18 @@
     FloatRect strokeRect;
     if (svgStyle->hasStroke()) {
         BoundingRectStrokeStyleApplier strokeStyle(this, style());
-        m_strokeAndMarkerBoundingBox.unite(path().strokeBoundingRect(&strokeStyle));
+
+        // SVG1.2 Tiny only defines non scaling stroke for the stroke but not markers.
+        if (hasNonScalingStroke()) {
+            AffineTransform nonScalingTransform = nonScalingStrokeTransform();
+            if (nonScalingTransform.isInvertible()) {
+                Path* usePath = nonScalingStrokePath(m_path.get(), nonScalingTransform);
+                FloatRect strokeBoundingRect = usePath->strokeBoundingRect(&strokeStyle);
+                strokeBoundingRect = nonScalingTransform.inverse().mapRect(strokeBoundingRect);
+                m_strokeAndMarkerBoundingBox.unite(strokeBoundingRect);
+            }
+        } else
+            m_strokeAndMarkerBoundingBox.unite(path().strokeBoundingRect(&strokeStyle));
     }
     if (svgStyle->hasMarkers()) {
         FloatRect markerBounds = calculateMarkerBoundsIfNeeded();

Modified: trunk/Source/WebCore/rendering/svg/RenderSVGShape.h (117708 => 117709)


--- trunk/Source/WebCore/rendering/svg/RenderSVGShape.h	2012-05-20 19:10:28 UTC (rev 117708)
+++ trunk/Source/WebCore/rendering/svg/RenderSVGShape.h	2012-05-20 19:49:45 UTC (rev 117709)
@@ -97,6 +97,7 @@
     void processZeroLengthSubpaths();
 
     bool hasPath() const { return m_path.get(); }
+    bool hasNonScalingStroke() const { return style()->svgStyle()->vectorEffect() == VE_NON_SCALING_STROKE; }
 
 private:
     // Hit-detection separated for the fill and the stroke
@@ -118,15 +119,17 @@
 
     void updateCachedBoundaries();
 
+    AffineTransform nonScalingStrokeTransform() const;
+    bool setupNonScalingStrokeContext(AffineTransform&, GraphicsContextStateSaver&);
+    Path* nonScalingStrokePath(const Path*, const AffineTransform&) const;
+
     Path* zeroLengthLinecapPath(const FloatPoint&);
-    bool setupNonScalingStrokeTransform(AffineTransform&, GraphicsContextStateSaver&);
-    Path* nonScalingStrokePath(const Path*, const AffineTransform&);
     bool shouldStrokeZeroLengthSubpath() const;
     FloatRect zeroLengthSubpathRect(const FloatPoint&, float) const;
 
     void fillShape(RenderStyle*, GraphicsContext*, Path*, RenderSVGShape*);
     void strokePath(RenderStyle*, GraphicsContext*, Path*, RenderSVGResource*,
-                    const Color&, bool, const AffineTransform&, int);
+                    const Color&, int);
     void fillAndStrokePath(GraphicsContext*);
     void inflateWithStrokeAndMarkerBounds();
 
_______________________________________________
webkit-changes mailing list
[email protected]
http://lists.webkit.org/mailman/listinfo.cgi/webkit-changes

Reply via email to