Implement SVGGeometryElement's isPointInFill and isPointInStroke
authorkrit@webkit.org <krit@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sun, 13 May 2018 09:25:44 +0000 (09:25 +0000)
committerkrit@webkit.org <krit@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sun, 13 May 2018 09:25:44 +0000 (09:25 +0000)
https://bugs.webkit.org/show_bug.cgi?id=185580

Reviewed by Antti Koivisto.

Implement isPointInFill and isPointInStroke methods for
SVGGeometryElement interface from SVG2.

https://svgwg.org/svg2-draft/types.html#InterfaceSVGGeometryElement

Source/WebCore:

Tests: svg/dom/SVGGeometry-isPointInFill.xhtml
       svg/dom/SVGGeometry-isPointInStroke.xhtml

* rendering/svg/RenderSVGEllipse.cpp:
(WebCore::RenderSVGEllipse::shapeDependentStrokeContains): Flag
        to switch between local and "global" coordinate space for hit testing.
* rendering/svg/RenderSVGEllipse.h:
* rendering/svg/RenderSVGPath.cpp:
(WebCore::RenderSVGPath::shapeDependentStrokeContains): Flag
        to switch between local and "global" coordinate space for hit testing.
* rendering/svg/RenderSVGPath.h:
* rendering/svg/RenderSVGRect.cpp:
(WebCore::RenderSVGRect::shapeDependentStrokeContains): Flag
        to switch between local and "global" coordinate space for hit testing.
* rendering/svg/RenderSVGRect.h:
* rendering/svg/RenderSVGShape.cpp:
(WebCore::RenderSVGShape::shapeDependentStrokeContains): Flag
        to switch between local and "global" coordinate space for hit testing.
(WebCore::RenderSVGShape::isPointInFill): Take the winding rule given by
        `fill-rule` to test if a given point is in the fill area of a path.
(WebCore::RenderSVGShape::isPointInStroke): Take stroke properties into
        account to check if a point is on top of the stroke area.
* rendering/svg/RenderSVGShape.h:
* svg/SVGGeometryElement.cpp:
(WebCore::SVGGeometryElement::isPointInFill):
(WebCore::SVGGeometryElement::isPointInStroke):
(WebCore::SVGGeometryElement::createElementRenderer): Deleted. This is getting implemented
        by inheriting classes. No need to create RenderSVGPath here.
* svg/SVGGeometryElement.h:
* svg/SVGGeometryElement.idl:

LayoutTests:

* svg/dom/SVGGeometry-isPointInFill-expected.txt: Added.
* svg/dom/SVGGeometry-isPointInFill.xhtml: Added.
* svg/dom/SVGGeometry-isPointInStroke-expected.txt: Added.
* svg/dom/SVGGeometry-isPointInStroke.xhtml: Added.

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

17 files changed:
LayoutTests/ChangeLog
LayoutTests/svg/dom/SVGGeometry-isPointInFill-expected.txt [new file with mode: 0644]
LayoutTests/svg/dom/SVGGeometry-isPointInFill.xhtml [new file with mode: 0644]
LayoutTests/svg/dom/SVGGeometry-isPointInStroke-expected.txt [new file with mode: 0644]
LayoutTests/svg/dom/SVGGeometry-isPointInStroke.xhtml [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/rendering/svg/RenderSVGEllipse.cpp
Source/WebCore/rendering/svg/RenderSVGEllipse.h
Source/WebCore/rendering/svg/RenderSVGPath.cpp
Source/WebCore/rendering/svg/RenderSVGPath.h
Source/WebCore/rendering/svg/RenderSVGRect.cpp
Source/WebCore/rendering/svg/RenderSVGRect.h
Source/WebCore/rendering/svg/RenderSVGShape.cpp
Source/WebCore/rendering/svg/RenderSVGShape.h
Source/WebCore/svg/SVGGeometryElement.cpp
Source/WebCore/svg/SVGGeometryElement.h
Source/WebCore/svg/SVGGeometryElement.idl

index 6ab4ded..98916bf 100644 (file)
@@ -1,3 +1,20 @@
+2018-05-13  Dirk Schulze  <krit@webkit.org>
+
+        Implement SVGGeometryElement's isPointInFill and isPointInStroke
+        https://bugs.webkit.org/show_bug.cgi?id=185580
+
+        Reviewed by Antti Koivisto.
+
+        Implement isPointInFill and isPointInStroke methods for
+        SVGGeometryElement interface from SVG2.
+
+        https://svgwg.org/svg2-draft/types.html#InterfaceSVGGeometryElement
+
+        * svg/dom/SVGGeometry-isPointInFill-expected.txt: Added.
+        * svg/dom/SVGGeometry-isPointInFill.xhtml: Added.
+        * svg/dom/SVGGeometry-isPointInStroke-expected.txt: Added.
+        * svg/dom/SVGGeometry-isPointInStroke.xhtml: Added.
+
 2018-05-12  Wenson Hsieh  <wenson_hsieh@apple.com>
 
         Unreviewed, rebaseline a layout test after r231717
diff --git a/LayoutTests/svg/dom/SVGGeometry-isPointInFill-expected.txt b/LayoutTests/svg/dom/SVGGeometry-isPointInFill-expected.txt
new file mode 100644 (file)
index 0000000..cc00773
--- /dev/null
@@ -0,0 +1,58 @@
+Test isPointInFill() on path.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+
+Test simple different coordinates
+PASS p1.isPointInFill() is true
+PASS p1.isPointInFill({}) is true
+PASS p1.isPointInFill({x: 0, y: 0}) is true
+PASS p1.isPointInFill({x: 200, y: 200}) is false
+PASS p1.isPointInFill({x: -200, y: -200}) is false
+PASS p1.isPointInFill({x: -100, y: -100}) is true
+PASS p1.isPointInFill(new DOMPoint()) is true
+PASS p1.isPointInFill(new DOMPoint(100, 100)) is true
+PASS p1.isPointInFill(new DOMPoint(-200, -200)) is false
+PASS p1.isPointInFill(new DOMPointReadOnly()) is true
+PASS p1.isPointInFill(new DOMPointReadOnly(-200, -200)) is false
+PASS p1.isPointInFill(svgPoint) is true
+PASS p1.isPointInFill(svgPoint) is false
+PASS p1.isPointInFill(new DOMPoint(NaN)) is false
+PASS p1.isPointInFill(new DOMPoint(Infinity)) is false
+PASS p1.isPointInFill('string') threw exception TypeError: Type error.
+
+Test that transform doesn't affect result
+PASS p2.isPointInFill() is true
+
+Verify that no argument or empty dictionary is the same as 0,0 and may return false
+PASS p3.isPointInFill() is false
+PASS p3.isPointInFill({}) is false
+
+display: none; should not affect isPointInFill
+FAIL p4.isPointInFill() should be true. Was false.
+
+visibility: hidden; should not affect isPointInFill
+PASS p5.isPointInFill() is true
+
+opacity: 0; should not affect isPointInFill
+PASS p6.isPointInFill() is true
+
+opacity: 0; on group should not affect isPointInFill
+PASS p7.isPointInFill() is true
+
+isPointInFill should respect fill-rule: nonzero
+PASS p8.isPointInFill() is true
+
+isPointInFill should respect fill-rule: evenodd
+PASS p9.isPointInFill() is false
+
+isPointInFill should not respect clip-rule 1
+PASS p10.isPointInFill() is true
+
+isPointInFill should not respect clip-rule 2
+PASS p11.isPointInFill() is false
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/svg/dom/SVGGeometry-isPointInFill.xhtml b/LayoutTests/svg/dom/SVGGeometry-isPointInFill.xhtml
new file mode 100644 (file)
index 0000000..43f9336
--- /dev/null
@@ -0,0 +1,116 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script src="../../resources/js-test-pre.js"></script>
+</head>
+<body onload="run()">
+<svg id="svg" xmlns="http://www.w3.org/2000/svg" width="200" height="200">
+    <path id="p1" d="M-100 -100L100 -100 L100 100 L-100 100 z"/>
+    <path id="p2" d="M-100 -100L100 -100 L100 100 L-100 100 z" transform="translate(-1000, -1000)"/>
+    <path id="p3" d="M100 100L300 100 L300 300 L100 300 z"/>
+    <path id="p4" d="M-100 -100L100 -100 L100 100 L-100 100 z" style="display: none"/>
+    <path id="p5" d="M-100 -100L100 -100 L100 100 L-100 100 z" style="visibility: hidden"/>
+    <path id="p6" d="M-100 -100L100 -100 L100 100 L-100 100 z" style="opacity: 0"/>
+    <g style="opacity: 0">
+        <path id="p7" d="M-100 -100L100 -100 L100 100 L-100 100 z"/>
+    </g>
+    <path id="p8" d="M-100 -100L100 -100 L100 100 L-100 100 z M-50 -50L50 -50 L50 50 L-50 50 z" fill-rule="nonzero"/>
+    <path id="p9" d="M-100 -100L100 -100 L100 100 L-100 100 z M-50 -50L50 -50 L50 50 L-50 50 z" fill-rule="evenodd"/>
+    <clipPath>
+        <path id="p10" d="M-100 -100L100 -100 L100 100 L-100 100 z M-50 -50L50 -50 L50 50 L-50 50 z" fill-rule="nonzero" clip-rule="evenodd"/>
+        <path id="p11" d="M-100 -100L100 -100 L100 100 L-100 100 z M-50 -50L50 -50 L50 50 L-50 50 z" fill-rule="evenodd" clip-rule="nonzero"/>
+    </clipPath>
+</svg>
+<p id="description"></p>
+<div id="console"></div>
+<script type="text/javascript">
+<![CDATA[
+window.enablePixelTesting = false;
+window.jsTestIsAsync = true;
+var svg = document.getElementById("svg");
+var p1 = document.getElementById("p1"),
+    p2 = document.getElementById("p2"),
+    p3 = document.getElementById("p3"),
+    p4 = document.getElementById("p4"),
+    p5 = document.getElementById("p5"),
+    p6 = document.getElementById("p6"),
+    p7 = document.getElementById("p7"),
+    p8 = document.getElementById("p8"),
+    p9 = document.getElementById("p9"),
+    p10 = document.getElementById("p10"),
+    p11 = document.getElementById("p11");
+var svgPoint = svg.createSVGPoint();
+
+function run() {
+    description("Test isPointInFill() on path.");
+
+    debug("");
+    debug("Test simple different coordinates");
+
+    shouldBe("p1.isPointInFill()", "true");
+    var point = {};
+    shouldBe("p1.isPointInFill({})", "true");
+    shouldBe("p1.isPointInFill({x: 0, y: 0})", "true");
+    shouldBe("p1.isPointInFill({x: 200, y: 200})", "false");
+    shouldBe("p1.isPointInFill({x: -200, y: -200})", "false");
+    shouldBe("p1.isPointInFill({x: -100, y: -100})", "true");
+    shouldBe("p1.isPointInFill(new DOMPoint())", "true");
+    shouldBe("p1.isPointInFill(new DOMPoint(100, 100))", "true");
+    shouldBe("p1.isPointInFill(new DOMPoint(-200, -200))", "false");
+    shouldBe("p1.isPointInFill(new DOMPointReadOnly())", "true");
+    shouldBe("p1.isPointInFill(new DOMPointReadOnly(-200, -200))", "false");
+    shouldBe("p1.isPointInFill(svgPoint)", "true");
+    svgPoint.x = -200;
+    svgPoint.y = -200;
+    shouldBe("p1.isPointInFill(svgPoint)", "false");
+    shouldBe("p1.isPointInFill(new DOMPoint(NaN))", "false");
+    shouldBe("p1.isPointInFill(new DOMPoint(Infinity))", "false");
+    shouldThrow("p1.isPointInFill('string')");
+
+    debug("");
+    debug("Test that transform doesn't affect result");
+    shouldBe("p2.isPointInFill()", "true");
+
+    debug("");
+    debug("Verify that no argument or empty dictionary is the same as 0,0 and may return false");
+    shouldBe("p3.isPointInFill()", "false");
+    shouldBe("p3.isPointInFill({})", "false");
+
+    debug("");
+    debug("display: none; should not affect isPointInFill");
+    shouldBe("p4.isPointInFill()", "true");
+
+    debug("");
+    debug("visibility: hidden; should not affect isPointInFill");
+    shouldBe("p5.isPointInFill()", "true");
+
+    debug("");
+    debug("opacity: 0; should not affect isPointInFill");
+    shouldBe("p6.isPointInFill()", "true");
+
+    debug("");
+    debug("opacity: 0; on group should not affect isPointInFill");
+    shouldBe("p7.isPointInFill()", "true");
+
+    debug("");
+    debug("isPointInFill should respect fill-rule: nonzero");
+    shouldBe("p8.isPointInFill()", "true");
+
+    debug("");
+    debug("isPointInFill should respect fill-rule: evenodd");
+    shouldBe("p9.isPointInFill()", "false");
+
+    debug("");
+    debug("isPointInFill should not respect clip-rule 1");
+    shouldBe("p10.isPointInFill()", "true");
+
+    debug("");
+    debug("isPointInFill should not respect clip-rule 2");
+    shouldBe("p11.isPointInFill()", "false");
+
+    finishJSTest();
+}
+]]>
+</script>
+<script src="../../resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/svg/dom/SVGGeometry-isPointInStroke-expected.txt b/LayoutTests/svg/dom/SVGGeometry-isPointInStroke-expected.txt
new file mode 100644 (file)
index 0000000..9f1e378
--- /dev/null
@@ -0,0 +1,72 @@
+Test isPointInStroke() on path.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+
+Test that fill area does not contribute to stroke area
+PASS p1.isPointInStroke() is false
+PASS p1.isPointInStroke({}) is false
+
+Test disabled stroke
+PASS p2.isPointInStroke({x: 1, y: 0}) is false
+
+Test general functionality
+PASS p3.isPointInStroke() is true
+PASS p3.isPointInStroke({}) is true
+PASS p3.isPointInStroke(new DOMPoint()) is true
+PASS p3.isPointInStroke(new DOMPoint(1, 0)) is true
+PASS p3.isPointInStroke(new DOMPoint(-200, -200)) is false
+PASS p3.isPointInStroke(new DOMPointReadOnly()) is true
+PASS p3.isPointInStroke(new DOMPointReadOnly(-200, -200)) is false
+PASS p3.isPointInStroke(svgPoint) is true
+PASS p3.isPointInStroke(svgPoint) is false
+PASS p3.isPointInStroke(new DOMPoint(NaN)) is false
+PASS p3.isPointInStroke(new DOMPoint(Infinity)) is false
+PASS p3.isPointInStroke('string') threw exception TypeError: Type error.
+
+Test CSS properties that shall not affect isPointInStroke
+PASS p11.isPointInStroke() is true
+FAIL p12.isPointInStroke() should be true. Was false.
+PASS p13.isPointInStroke() is true
+PASS p14.isPointInStroke() is true
+
+Test different stroke properties
+PASS p4.isPointInStroke() is false
+PASS p4.isPointInStroke({x: 19}) is false
+PASS p4.isPointInStroke({x: 20}) is true
+PASS p4.isPointInStroke({x: 30}) is true
+PASS p4.isPointInStroke({x: 40}) is true
+PASS p4.isPointInStroke({x: 41}) is false
+PASS p4.isPointInStroke({x: 50}) is false
+PASS p4.isPointInStroke({x: 59}) is false
+PASS p4.isPointInStroke({x: 60}) is true
+PASS p4.isPointInStroke({x: 20, y: 10}) is true
+PASS p4.isPointInStroke({x: 20, y: 11}) is false
+PASS p4.isPointInStroke({x: 20, y: -10}) is true
+PASS p4.isPointInStroke({x: 20, y: -11}) is false
+
+Test different linecaps
+PASS p5.isPointInStroke({x: 18}) is true
+PASS p6.isPointInStroke({x: 18}) is true
+PASS p7.isPointInStroke({x: 18}) is false
+
+Test different linejoins
+PASS p8.isPointInStroke({x: 20, y: 60}) is true
+PASS p8.isPointInStroke({x: 20, y: 75}) is true
+PASS p8.isPointInStroke({x: 20, y: 90}) is true
+PASS p9.isPointInStroke({x: 20, y: 60}) is true
+PASS p9.isPointInStroke({x: 20, y: 70}) is true
+PASS p9.isPointInStroke({x: 20, y: 90}) is false
+PASS p10.isPointInStroke({x: 20, y: 60}) is true
+PASS p10.isPointInStroke({x: 20, y: 70}) is false
+PASS p10.isPointInStroke({x: 20, y: 90}) is false
+
+Test non-scaling-stroke
+PASS p15.isPointInStroke({}) is true
+PASS p15.isPointInStroke({y: 1}) is true
+PASS p15.isPointInStroke({y: 11}) is false
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/svg/dom/SVGGeometry-isPointInStroke.xhtml b/LayoutTests/svg/dom/SVGGeometry-isPointInStroke.xhtml
new file mode 100644 (file)
index 0000000..9859470
--- /dev/null
@@ -0,0 +1,130 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script src="../../resources/js-test-pre.js"></script>
+</head>
+<body onload="run()">
+<svg id="svg" xmlns="http://www.w3.org/2000/svg" width="200" height="200">
+    <path id="p1" d="M-100 -100L100 -100 L100 100 L-100 100 z"/>
+    <path id="p2" d="M-1 0 L 200 0"/>
+    <path id="p3" d="M-1 0 L 200 0" stroke="black"/>
+    <path id="p4" d="M0 0 L 200 0" stroke="black" stroke-width="20" stroke-dashoffset="20" stroke-dasharray="20 20"/>
+    <path id="p5" d="M20 0 L 200 0" stroke="black" stroke-width="20" stroke-linecap="round"/>
+    <path id="p6" d="M20 0 L 200 0" stroke="black" stroke-width="20" stroke-linecap="square"/>
+    <path id="p7" d="M20 0 L 200 0" stroke="black" stroke-width="20" stroke-linecap="butt"/>
+    <path id="p8" d="M0 0 L20 60 L40 0" stroke="black" stroke-width="20" stroke-linejoin="miter" stroke-miterlimit="30"/>
+    <path id="p9" d="M0 0 L20 60 L40 0" stroke="black" stroke-width="20" stroke-linejoin="round" stroke-miterlimit="30"/>
+    <path id="p10" d="M0 0 L20 60 L40 0" stroke="black" stroke-width="20" stroke-linejoin="bevel" stroke-miterlimit="30"/>
+    <path id="p11" d="M-1 0 L 200 0" stroke="black" stroke-width="20" transform="translate(-200, -200)"/>
+    <path id="p12" d="M-1 0 L 200 0" stroke="black" stroke-width="20" display="none"/>
+    <path id="p13" d="M-1 0 L 200 0" stroke="black" stroke-width="20" visibility="hidden"/>
+    <path id="p14" d="M-1 0 L 200 0" stroke="black" stroke-width="20" opacity="0"/>
+    <g transform="scale(20)">
+        <path id="p15" d="M0 0 L 200 0" stroke="black" stroke-width="20" vector-effect="non-scaling-stroke"/>
+    </g>
+</svg>
+<p id="description"></p>
+<div id="console"></div>
+<script type="text/javascript">
+<![CDATA[
+window.enablePixelTesting = false;
+window.jsTestIsAsync = true;
+var svg = document.getElementById("svg");
+var p1 = document.getElementById("p1"),
+    p2 = document.getElementById("p2"),
+    p3 = document.getElementById("p3"),
+    p4 = document.getElementById("p4"),
+    p5 = document.getElementById("p5"),
+    p6 = document.getElementById("p6"),
+    p7 = document.getElementById("p7"),
+    p8 = document.getElementById("p8"),
+    p9 = document.getElementById("p9"),
+    p10 = document.getElementById("p10"),
+    p11 = document.getElementById("p11"),
+    p12 = document.getElementById("p12"),
+    p13 = document.getElementById("p13"),
+    p14 = document.getElementById("p14"),
+    p15 = document.getElementById("p15");
+var svgPoint = svg.createSVGPoint();
+
+function run() {
+    description("Test isPointInStroke() on path.");
+
+    debug("");
+    debug("Test that fill area does not contribute to stroke area");
+    shouldBe("p1.isPointInStroke()", "false");
+    shouldBe("p1.isPointInStroke({})", "false");
+
+    debug("");
+    debug("Test disabled stroke");
+    shouldBe("p2.isPointInStroke({x: 1, y: 0})", "false");
+
+    debug("");
+    debug("Test general functionality");
+    shouldBe("p3.isPointInStroke()", "true");
+    shouldBe("p3.isPointInStroke({})", "true");
+    shouldBe("p3.isPointInStroke(new DOMPoint())", "true");
+    shouldBe("p3.isPointInStroke(new DOMPoint(1, 0))", "true");
+    shouldBe("p3.isPointInStroke(new DOMPoint(-200, -200))", "false");
+    shouldBe("p3.isPointInStroke(new DOMPointReadOnly())", "true");
+    shouldBe("p3.isPointInStroke(new DOMPointReadOnly(-200, -200))", "false");
+    shouldBe("p3.isPointInStroke(svgPoint)", "true");
+    svgPoint.x = -200;
+    svgPoint.y = -200;
+    shouldBe("p3.isPointInStroke(svgPoint)", "false");
+    shouldBe("p3.isPointInStroke(new DOMPoint(NaN))", "false");
+    shouldBe("p3.isPointInStroke(new DOMPoint(Infinity))", "false");
+    shouldThrow("p3.isPointInStroke('string')");
+
+    debug("");
+    debug("Test CSS properties that shall not affect isPointInStroke");
+    shouldBe("p11.isPointInStroke()", "true");
+    shouldBe("p12.isPointInStroke()", "true");
+    shouldBe("p13.isPointInStroke()", "true");
+    shouldBe("p14.isPointInStroke()", "true");
+
+    debug("");
+    debug("Test different stroke properties");
+    shouldBe("p4.isPointInStroke()", "false");
+    shouldBe("p4.isPointInStroke({x: 19})", "false");
+    shouldBe("p4.isPointInStroke({x: 20})", "true");
+    shouldBe("p4.isPointInStroke({x: 30})", "true");
+    shouldBe("p4.isPointInStroke({x: 40})", "true");
+    shouldBe("p4.isPointInStroke({x: 41})", "false");
+    shouldBe("p4.isPointInStroke({x: 50})", "false");
+    shouldBe("p4.isPointInStroke({x: 59})", "false");
+    shouldBe("p4.isPointInStroke({x: 60})", "true");
+    shouldBe("p4.isPointInStroke({x: 20, y: 10})", "true");
+    shouldBe("p4.isPointInStroke({x: 20, y: 11})", "false");
+    shouldBe("p4.isPointInStroke({x: 20, y: -10})", "true");
+    shouldBe("p4.isPointInStroke({x: 20, y: -11})", "false");
+
+    debug("");
+    debug("Test different linecaps");
+    shouldBe("p5.isPointInStroke({x: 18})", "true");
+    shouldBe("p6.isPointInStroke({x: 18})", "true");
+    shouldBe("p7.isPointInStroke({x: 18})", "false");
+
+    debug("");
+    debug("Test different linejoins");
+    shouldBe("p8.isPointInStroke({x: 20, y: 60})", "true");
+    shouldBe("p8.isPointInStroke({x: 20, y: 75})", "true");
+    shouldBe("p8.isPointInStroke({x: 20, y: 90})", "true");
+    shouldBe("p9.isPointInStroke({x: 20, y: 60})", "true");
+    shouldBe("p9.isPointInStroke({x: 20, y: 70})", "true");
+    shouldBe("p9.isPointInStroke({x: 20, y: 90})", "false");
+    shouldBe("p10.isPointInStroke({x: 20, y: 60})", "true");
+    shouldBe("p10.isPointInStroke({x: 20, y: 70})", "false");
+    shouldBe("p10.isPointInStroke({x: 20, y: 90})", "false");
+
+    debug("");
+    debug("Test non-scaling-stroke");
+    shouldBe("p15.isPointInStroke({})", "true");
+    shouldBe("p15.isPointInStroke({y: 1})", "true");
+    shouldBe("p15.isPointInStroke({y: 11})", "false");
+    finishJSTest();
+}
+]]>
+</script>
+<script src="../../resources/js-test-post.js"></script>
+</body>
+</html>
index 80c27c8..cf540ae 100644 (file)
@@ -1,3 +1,46 @@
+2018-05-13  Dirk Schulze  <krit@webkit.org>
+
+        Implement SVGGeometryElement's isPointInFill and isPointInStroke
+        https://bugs.webkit.org/show_bug.cgi?id=185580
+
+        Reviewed by Antti Koivisto.
+
+        Implement isPointInFill and isPointInStroke methods for
+        SVGGeometryElement interface from SVG2.
+
+        https://svgwg.org/svg2-draft/types.html#InterfaceSVGGeometryElement
+
+        Tests: svg/dom/SVGGeometry-isPointInFill.xhtml
+               svg/dom/SVGGeometry-isPointInStroke.xhtml
+
+        * rendering/svg/RenderSVGEllipse.cpp:
+        (WebCore::RenderSVGEllipse::shapeDependentStrokeContains): Flag
+                to switch between local and "global" coordinate space for hit testing.
+        * rendering/svg/RenderSVGEllipse.h:
+        * rendering/svg/RenderSVGPath.cpp:
+        (WebCore::RenderSVGPath::shapeDependentStrokeContains): Flag
+                to switch between local and "global" coordinate space for hit testing.
+        * rendering/svg/RenderSVGPath.h:
+        * rendering/svg/RenderSVGRect.cpp:
+        (WebCore::RenderSVGRect::shapeDependentStrokeContains): Flag
+                to switch between local and "global" coordinate space for hit testing.
+        * rendering/svg/RenderSVGRect.h:
+        * rendering/svg/RenderSVGShape.cpp:
+        (WebCore::RenderSVGShape::shapeDependentStrokeContains): Flag
+                to switch between local and "global" coordinate space for hit testing.
+        (WebCore::RenderSVGShape::isPointInFill): Take the winding rule given by
+                `fill-rule` to test if a given point is in the fill area of a path.
+        (WebCore::RenderSVGShape::isPointInStroke): Take stroke properties into
+                account to check if a point is on top of the stroke area.
+        * rendering/svg/RenderSVGShape.h:
+        * svg/SVGGeometryElement.cpp:
+        (WebCore::SVGGeometryElement::isPointInFill):
+        (WebCore::SVGGeometryElement::isPointInStroke):
+        (WebCore::SVGGeometryElement::createElementRenderer): Deleted. This is getting implemented
+                by inheriting classes. No need to create RenderSVGPath here.
+        * svg/SVGGeometryElement.h:
+        * svg/SVGGeometryElement.idl:
+
 2018-05-12  Zalan Bujtas  <zalan@apple.com>
 
         Use WeakPtr for m_enclosingPaginationLayer in RenderLayer
index c80e4fc..7da9199 100644 (file)
@@ -113,14 +113,14 @@ void RenderSVGEllipse::strokeShape(GraphicsContext& context) const
     context.strokeEllipse(m_fillBoundingBox);
 }
 
-bool RenderSVGEllipse::shapeDependentStrokeContains(const FloatPoint& point)
+bool RenderSVGEllipse::shapeDependentStrokeContains(const FloatPoint& point, PointCoordinateSpace pointCoordinateSpace)
 {
     // The optimized contains code below does not support non-smooth strokes so we need
     // to fall back to RenderSVGShape::shapeDependentStrokeContains in these cases.
     if (m_usePathFallback || !hasSmoothStroke()) {
         if (!hasPath())
             RenderSVGShape::updateShapeFromElement();
-        return RenderSVGShape::shapeDependentStrokeContains(point);
+        return RenderSVGShape::shapeDependentStrokeContains(point, pointCoordinateSpace);
     }
 
     float halfStrokeWidth = strokeWidth() / 2;
index 9f74938..556a354 100644 (file)
@@ -44,7 +44,7 @@ private:
     bool isRenderingDisabled() const override;
     void fillShape(GraphicsContext&) const override;
     void strokeShape(GraphicsContext&) const override;
-    bool shapeDependentStrokeContains(const FloatPoint&) override;
+    bool shapeDependentStrokeContains(const FloatPoint&, PointCoordinateSpace = GlobalCoordinateSpace) override;
     bool shapeDependentFillContains(const FloatPoint&, const WindRule) const override;
     void calculateRadiiAndCenter();
 
index 494ad55..029ac6d 100644 (file)
@@ -101,9 +101,9 @@ void RenderSVGPath::strokeShape(GraphicsContext& context) const
     }
 }
 
-bool RenderSVGPath::shapeDependentStrokeContains(const FloatPoint& point)
+bool RenderSVGPath::shapeDependentStrokeContains(const FloatPoint& point, PointCoordinateSpace pointCoordinateSpace)
 {
-    if (RenderSVGShape::shapeDependentStrokeContains(point))
+    if (RenderSVGShape::shapeDependentStrokeContains(point, pointCoordinateSpace))
         return true;
 
     for (size_t i = 0; i < m_zeroLengthLinecapLocations.size(); ++i) {
index 5316790..cfe3e19 100644 (file)
@@ -43,7 +43,7 @@ private:
     FloatRect calculateUpdatedStrokeBoundingBox() const;
 
     void strokeShape(GraphicsContext&) const override;
-    bool shapeDependentStrokeContains(const FloatPoint&) override;
+    bool shapeDependentStrokeContains(const FloatPoint&, PointCoordinateSpace = GlobalCoordinateSpace) override;
 
     bool shouldStrokeZeroLengthSubpath() const;
     Path* zeroLengthLinecapPath(const FloatPoint&) const;
index e1a594f..cf9c040 100644 (file)
@@ -134,14 +134,14 @@ void RenderSVGRect::strokeShape(GraphicsContext& context) const
     context.strokeRect(m_fillBoundingBox, strokeWidth());
 }
 
-bool RenderSVGRect::shapeDependentStrokeContains(const FloatPoint& point)
+bool RenderSVGRect::shapeDependentStrokeContains(const FloatPoint& point, PointCoordinateSpace pointCoordinateSpace)
 {
     // The optimized contains code below does not support non-smooth strokes so we need
     // to fall back to RenderSVGShape::shapeDependentStrokeContains in these cases.
     if (m_usePathFallback || !hasSmoothStroke()) {
         if (!hasPath())
             RenderSVGShape::updateShapeFromElement();
-        return RenderSVGShape::shapeDependentStrokeContains(point);
+        return RenderSVGShape::shapeDependentStrokeContains(point, pointCoordinateSpace);
     }
 
     return m_outerStrokeRect.contains(point, FloatRect::InsideOrOnStroke) && !m_innerStrokeRect.contains(point, FloatRect::InsideButNotOnStroke);
index fbe6718..4ff5218 100644 (file)
@@ -50,7 +50,7 @@ private:
     bool isRenderingDisabled() const override;
     void fillShape(GraphicsContext&) const override;
     void strokeShape(GraphicsContext&) const override;
-    bool shapeDependentStrokeContains(const FloatPoint&) override;
+    bool shapeDependentStrokeContains(const FloatPoint&, PointCoordinateSpace = GlobalCoordinateSpace) override;
     bool shapeDependentFillContains(const FloatPoint&, const WindRule) const override;
 
 private:
index 30a40e1..bf0653d 100644 (file)
@@ -8,6 +8,7 @@
  * Copyright (C) 2009 Jeff Schiller <codedread@gmail.com>
  * Copyright (C) 2011 Renata Hodovan <reni@webkit.org>
  * Copyright (C) 2011 University of Szeged
+ * Copyright (C) 2018 Adobe Systems Incorporated. All rights reserved.
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Library General Public
@@ -110,12 +111,12 @@ void RenderSVGShape::strokeShape(GraphicsContext& context) const
     context.strokePath(*usePath);
 }
 
-bool RenderSVGShape::shapeDependentStrokeContains(const FloatPoint& point)
+bool RenderSVGShape::shapeDependentStrokeContains(const FloatPoint& point, PointCoordinateSpace pointCoordinateSpace)
 {
     ASSERT(m_path);
     BoundingRectStrokeStyleApplier applier(*this);
 
-    if (hasNonScalingStroke()) {
+    if (hasNonScalingStroke() && pointCoordinateSpace != LocalCoordinateSpace) {
         AffineTransform nonScalingTransform = nonScalingStrokeTransform();
         Path* usePath = nonScalingStrokePath(m_path.get(), nonScalingTransform);
 
@@ -331,6 +332,19 @@ void RenderSVGShape::addFocusRingRects(Vector<LayoutRect>& rects, const LayoutPo
         rects.append(rect);
 }
 
+bool RenderSVGShape::isPointInFill(const FloatPoint& point)
+{
+    return shapeDependentFillContains(point, style().svgStyle().fillRule());
+}
+
+bool RenderSVGShape::isPointInStroke(const FloatPoint& point)
+{
+    if (!style().svgStyle().hasStroke())
+        return false;
+
+    return shapeDependentStrokeContains(point, LocalCoordinateSpace);
+}
+
 bool RenderSVGShape::nodeAtFloatPoint(const HitTestRequest& request, HitTestResult& result, const FloatPoint& pointInParent, HitTestAction hitTestAction)
 {
     // We only draw in the forground phase, so we only hit-test then.
index effa248..d8c2e56 100644 (file)
@@ -45,6 +45,10 @@ class SVGGraphicsElement;
 class RenderSVGShape : public RenderSVGModelObject {
     WTF_MAKE_ISO_ALLOCATED(RenderSVGShape);
 public:
+    enum PointCoordinateSpace {
+        GlobalCoordinateSpace,
+        LocalCoordinateSpace
+    };
     RenderSVGShape(SVGGraphicsElement&, RenderStyle&&);
     virtual ~RenderSVGShape();
 
@@ -58,6 +62,9 @@ public:
     virtual void strokeShape(GraphicsContext&) const;
     virtual bool isRenderingDisabled() const = 0;
 
+    bool isPointInFill(const FloatPoint&);
+    bool isPointInStroke(const FloatPoint&);
+
     bool hasPath() const { return m_path.get(); }
     Path& path() const
     {
@@ -71,7 +78,7 @@ protected:
 
     virtual void updateShapeFromElement();
     virtual bool isEmpty() const;
-    virtual bool shapeDependentStrokeContains(const FloatPoint&);
+    virtual bool shapeDependentStrokeContains(const FloatPoint&, PointCoordinateSpace = GlobalCoordinateSpace);
     virtual bool shapeDependentFillContains(const FloatPoint&, const WindRule) const;
     float strokeWidth() const;
     bool hasSmoothStroke() const;
index e2b62af..1afb57e 100644 (file)
@@ -23,6 +23,7 @@
 #include "config.h"
 #include "SVGGeometryElement.h"
 
+#include "DOMPoint.h"
 #include "RenderSVGPath.h"
 #include "RenderSVGResource.h"
 #include "SVGDocumentExtensions.h"
@@ -51,6 +52,30 @@ SVGGeometryElement::SVGGeometryElement(const QualifiedName& tagName, Document& d
     registerAnimatedPropertiesForSVGGeometryElement();
 }
 
+bool SVGGeometryElement::isPointInFill(DOMPointInit&& pointInit)
+{
+    document().updateLayoutIgnorePendingStylesheets();
+
+    auto* renderer = downcast<RenderSVGShape>(this->renderer());
+    if (!renderer)
+        return false;
+
+    FloatPoint point {static_cast<float>(pointInit.x), static_cast<float>(pointInit.y)};
+    return renderer->isPointInFill(point);
+}
+
+bool SVGGeometryElement::isPointInStroke(DOMPointInit&& pointInit)
+{
+    document().updateLayoutIgnorePendingStylesheets();
+
+    auto* renderer = downcast<RenderSVGShape>(this->renderer());
+    if (!renderer)
+        return false;
+
+    FloatPoint point {static_cast<float>(pointInit.x), static_cast<float>(pointInit.y)};
+    return renderer->isPointInStroke(point);
+}
+
 bool SVGGeometryElement::isSupportedAttribute(const QualifiedName& attrName)
 {
     static const auto supportedAttributes = makeNeverDestroyed([] {
@@ -87,9 +112,4 @@ void SVGGeometryElement::svgAttributeChanged(const QualifiedName& attrName)
     ASSERT_NOT_REACHED();
 }
 
-RenderPtr<RenderElement> SVGGeometryElement::createElementRenderer(RenderStyle&& style, const RenderTreePosition&)
-{
-    return createRenderer<RenderSVGPath>(*this, WTFMove(style));
-}
-
 }
index 1939199..fff0262 100644 (file)
@@ -30,6 +30,7 @@
 
 namespace WebCore {
 
+struct DOMPointInit;
 class SVGPoint;
 
 class SVGGeometryElement : public SVGGraphicsElement {
@@ -39,6 +40,9 @@ public:
     virtual float getTotalLength() const = 0;
     virtual Ref<SVGPoint> getPointAtLength(float distance) const = 0;
 
+    bool isPointInFill(DOMPointInit&&);
+    bool isPointInStroke(DOMPointInit&&);
+
 protected:
     SVGGeometryElement(const QualifiedName&, Document&);
 
@@ -49,8 +53,6 @@ protected:
     BEGIN_DECLARE_ANIMATED_PROPERTIES(SVGGeometryElement)
         DECLARE_ANIMATED_NUMBER(PathLength, pathLength)
     END_DECLARE_ANIMATED_PROPERTIES
-
-    RenderPtr<RenderElement> createElementRenderer(RenderStyle&&, const RenderTreePosition&) override;
 };
 
 } // namespace WebCore
index 1d98e23..56df6f1 100644 (file)
@@ -27,8 +27,8 @@
 interface SVGGeometryElement : SVGGraphicsElement {
     readonly attribute SVGAnimatedNumber pathLength; // FIXME: Should be [SameObject].
 
-    // boolean isPointInFill(DOMPointInit point);
-    // boolean isPointInStroke(DOMPointInit point);
+    boolean isPointInFill(optional DOMPointInit point);
+    boolean isPointInStroke(optional DOMPointInit point);
     unrestricted float getTotalLength();
     [NewObject] SVGPoint getPointAtLength(float distance);
 };