Support bezier paths in clip-path property
authorsimon.fraser@apple.com <simon.fraser@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 26 Oct 2015 04:57:46 +0000 (04:57 +0000)
committersimon.fraser@apple.com <simon.fraser@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 26 Oct 2015 04:57:46 +0000 (04:57 +0000)
https://bugs.webkit.org/show_bug.cgi?id=149996

Reviewed by Darin Adler.

Source/WebCore:

Support path() in the -webkit-clip-path property, as specified in
https://drafts.csswg.org/css-shapes-2/#supported-basic-shapes

Added BasicShapePath and CSSBasicShapePath, which both represent the path
as a SVGPathByteStream and wind rule.

Make BasicShape::canBlend() a virtual function, and implement it on each subclass.
Make various BasicShape subclass function overrides private, other than windRule()
wich is called on derived classes in a few places.

Add SVGPathBlender::canBlendPaths() which returns true if the given paths can be
interpolated. Uses the same logic as blendAnimatedPath(), without doing any interpolation.

RenderElement::createsGroup() is fixed to have clip-path trigger a group,
which fixes rendering of clip-path with a descendant compositing layer.

Tests: compositing/masks/clip-path-composited-descendent.html
       css3/masking/clip-path-with-path.html
       transitions/clip-path-path-transitions.html

* css/BasicShapeFunctions.cpp:
(WebCore::valueForBasicShape):
(WebCore::basicShapeForValue):
* css/CSSBasicShapes.cpp:
(WebCore::CSSBasicShapePath::CSSBasicShapePath):
(WebCore::CSSBasicShapePath::pathData):
(WebCore::buildPathString):
(WebCore::CSSBasicShapePath::cssText):
(WebCore::CSSBasicShapePath::equals):
* css/CSSBasicShapes.h:
* css/CSSParser.cpp:
(WebCore::CSSParser::parseBasicShapePath):
(WebCore::CSSParser::parseBasicShape):
* css/CSSParser.h:
* rendering/RenderElement.h:
(WebCore::RenderElement::createsGroup):
* rendering/style/BasicShapes.cpp:
(WebCore::BasicShapeCircle::canBlend):
(WebCore::BasicShapeEllipse::canBlend):
(WebCore::BasicShapePolygon::canBlend):
(WebCore::BasicShapePath::BasicShapePath):
(WebCore::BasicShapePath::path):
(WebCore::BasicShapePath::operator==):
(WebCore::BasicShapePath::canBlend):
(WebCore::BasicShapePath::blend):
(WebCore::BasicShapeInset::canBlend):
(WebCore::BasicShape::canBlend): Deleted.
* rendering/style/BasicShapes.h:
* svg/SVGPathBlender.cpp:
(WebCore::SVGPathBlender::addAnimatedPath):
(WebCore::SVGPathBlender::blendAnimatedPath):
(WebCore::SVGPathBlender::canBlendPaths):
(WebCore::SVGPathBlender::SVGPathBlender):
(WebCore::SVGPathBlender::blendMoveToSegment):
(WebCore::SVGPathBlender::blendLineToSegment):
(WebCore::SVGPathBlender::blendLineToHorizontalSegment):
(WebCore::SVGPathBlender::blendLineToVerticalSegment):
(WebCore::SVGPathBlender::blendCurveToCubicSegment):
(WebCore::SVGPathBlender::blendCurveToCubicSmoothSegment):
(WebCore::SVGPathBlender::blendCurveToQuadraticSegment):
(WebCore::SVGPathBlender::blendCurveToQuadraticSmoothSegment):
(WebCore::SVGPathBlender::blendArcToSegment):
* svg/SVGPathBlender.h:
* svg/SVGPathByteStream.h:
(WebCore::SVGPathByteStream::operator==):
* svg/SVGPathUtilities.cpp:
(WebCore::canBlendSVGPathByteStreams):
* svg/SVGPathUtilities.h:

LayoutTests:

Tests for compositing with clip-path and a composited descendant,
a ref test which tests clip-paths with evenodd, comparing to SVG rendering,
and a transition test to test path blendability.

* compositing/masks/clip-path-composited-descendent-expected.txt: Added.
* compositing/masks/clip-path-composited-descendent.html: Added.
* css3/masking/clip-path-with-path-expected.html: Added.
* css3/masking/clip-path-with-path.html: Added.
* transitions/clip-path-path-transitions-expected.txt: Added.
* transitions/clip-path-path-transitions.html: Added.
* transitions/resources/transition-test-helpers.js: Add some basic parsing
support for paths.
(extractPathValues):
(parseClipPath):
* transitions/svg-transitions-expected.txt:

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

23 files changed:
LayoutTests/ChangeLog
LayoutTests/compositing/masks/clip-path-composited-descendent-expected.txt [new file with mode: 0644]
LayoutTests/compositing/masks/clip-path-composited-descendent.html [new file with mode: 0644]
LayoutTests/css3/masking/clip-path-with-path-expected.html [new file with mode: 0644]
LayoutTests/css3/masking/clip-path-with-path.html [new file with mode: 0644]
LayoutTests/transitions/clip-path-path-transitions-expected.txt [new file with mode: 0644]
LayoutTests/transitions/clip-path-path-transitions.html [new file with mode: 0644]
LayoutTests/transitions/resources/transition-test-helpers.js
LayoutTests/transitions/svg-transitions-expected.txt
Source/WebCore/ChangeLog
Source/WebCore/css/BasicShapeFunctions.cpp
Source/WebCore/css/CSSBasicShapes.cpp
Source/WebCore/css/CSSBasicShapes.h
Source/WebCore/css/CSSParser.cpp
Source/WebCore/css/CSSParser.h
Source/WebCore/rendering/RenderElement.h
Source/WebCore/rendering/style/BasicShapes.cpp
Source/WebCore/rendering/style/BasicShapes.h
Source/WebCore/svg/SVGPathBlender.cpp
Source/WebCore/svg/SVGPathBlender.h
Source/WebCore/svg/SVGPathByteStream.h
Source/WebCore/svg/SVGPathUtilities.cpp
Source/WebCore/svg/SVGPathUtilities.h

index f9f5f2e..c0fba25 100644 (file)
@@ -1,3 +1,26 @@
+2015-10-25  Simon Fraser  <simon.fraser@apple.com>
+
+        Support bezier paths in clip-path property
+        https://bugs.webkit.org/show_bug.cgi?id=149996
+
+        Reviewed by Darin Adler.
+        
+        Tests for compositing with clip-path and a composited descendant,
+        a ref test which tests clip-paths with evenodd, comparing to SVG rendering,
+        and a transition test to test path blendability.
+
+        * compositing/masks/clip-path-composited-descendent-expected.txt: Added.
+        * compositing/masks/clip-path-composited-descendent.html: Added.
+        * css3/masking/clip-path-with-path-expected.html: Added.
+        * css3/masking/clip-path-with-path.html: Added.
+        * transitions/clip-path-path-transitions-expected.txt: Added.
+        * transitions/clip-path-path-transitions.html: Added.
+        * transitions/resources/transition-test-helpers.js: Add some basic parsing
+        support for paths.
+        (extractPathValues):
+        (parseClipPath):
+        * transitions/svg-transitions-expected.txt:
+
 2015-10-25  Youenn Fablet  <youenn.fablet@crf.canon.fr>
 
         Import W3C XMLHttpRequest tests
diff --git a/LayoutTests/compositing/masks/clip-path-composited-descendent-expected.txt b/LayoutTests/compositing/masks/clip-path-composited-descendent-expected.txt
new file mode 100644 (file)
index 0000000..26ddd53
--- /dev/null
@@ -0,0 +1,27 @@
+(GraphicsLayer
+  (anchor 0.00 0.00)
+  (bounds 800.00 600.00)
+  (children 1
+    (GraphicsLayer
+      (bounds 800.00 600.00)
+      (contentsOpaque 1)
+      (children 1
+        (GraphicsLayer
+          (position 8.00 8.00)
+          (bounds 200.00 200.00)
+          (mask layer)
+            (GraphicsLayer
+              (bounds 200.00 200.00)
+            )
+          (children 1
+            (GraphicsLayer
+              (bounds 300.00 300.00)
+              (contentsOpaque 1)
+            )
+          )
+        )
+      )
+    )
+  )
+)
+
diff --git a/LayoutTests/compositing/masks/clip-path-composited-descendent.html b/LayoutTests/compositing/masks/clip-path-composited-descendent.html
new file mode 100644 (file)
index 0000000..51ad04e
--- /dev/null
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+
+<html>
+<head>
+    <title>Tests that clip-path with a composited descendent triggers composited clipping</title>
+    <style>
+        .box {
+            width: 200px;
+            height: 200px;
+            background-color: red;
+            -webkit-clip-path: inset(100px 100px 0px 0px);
+        }
+        
+        .child {
+            width: 300px;
+            height: 300px;
+            background-color: green;
+            -webkit-transform: translateZ(0);
+        }
+    </style>
+    <script>
+        if (window.testRunner)
+            testRunner.dumpAsText();
+
+        function doTest()
+        {
+            if (window.internals)
+                document.getElementById('layers').textContent = window.internals.layerTreeAsText(document);
+
+        }
+        window.addEventListener('load', doTest, false);
+    </script>
+</head>
+<body>
+    <div class="box">
+        <div class="child"></div>
+    </div>
+<pre id="layers"></pre>
+</body>
+</html>
diff --git a/LayoutTests/css3/masking/clip-path-with-path-expected.html b/LayoutTests/css3/masking/clip-path-with-path-expected.html
new file mode 100644 (file)
index 0000000..44a71c5
--- /dev/null
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+
+<html>
+<head>
+    <style>
+        .box {
+            margin: 20px;
+            height: 150px;
+            width: 200px;
+        }
+    </style>
+</head>
+<body>
+
+<div class="box">
+    <svg width="100%" height="100%" viewBox="0 0 200 150">
+               <clipPath id="clip1">
+                       <path d="M100,40l20,0 0,60 20,0 0,-20 -60,0 0,-20 80,0 0,60 -60,0 0,-80z"/>
+               </clipPath>
+               <rect x="50" y="30" width="350" height="100" fill="blue" clip-path="url(#clip1)"/>
+    </svg>
+</div>
+
+<div class="evenodd box">
+    <svg width="100%" height="100%" viewBox="0 0 200 150">
+               <clipPath id="clip2">
+                       <path clip-rule="evenodd" d="M100,40l20,0 0,60 20,0 0,-20 -60,0 0,-20 80,0 0,60 -60,0 0,-80z"/>
+               </clipPath>
+               <rect x="50" y="30" width="350" height="100" fill="green" clip-path="url(#clip2)"/>
+    </svg>
+</div>
+
+</body>
+</html>
diff --git a/LayoutTests/css3/masking/clip-path-with-path.html b/LayoutTests/css3/masking/clip-path-with-path.html
new file mode 100644 (file)
index 0000000..b68891e
--- /dev/null
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+
+<html>
+<head>
+    <style>
+        .box {
+            margin: 20px;
+            height: 150px;
+            width: 200px;
+            -webkit-clip-path: path("M100,40l20,0 0,60 20,0 0,-20 -60,0 0,-20 80,0 0,60 -60,0 0,-80z");
+            background-color: blue;
+        }
+        
+        .box.evenodd {
+            -webkit-clip-path: path(evenodd, "M100,40l20,0 0,60 20,0 0,-20 -60,0 0,-20 80,0 0,60 -60,0 0,-80z");
+            background-color: green;
+        }
+    </style>
+</head>
+<body>
+
+<div class="box"></div>
+<div class="evenodd box"></div>
+
+</body>
+</html>
diff --git a/LayoutTests/transitions/clip-path-path-transitions-expected.txt b/LayoutTests/transitions/clip-path-path-transitions-expected.txt
new file mode 100644 (file)
index 0000000..950ff50
--- /dev/null
@@ -0,0 +1,5 @@
+  
+PASS - "-webkit-clip-path" property for "path1" element at 1s saw something close to: path('M 80 40 l 20 0 l 0 60 l 20 0 l 0 -20 l -50 0 l 0 -20 l 80 0 l 0 60 l -60 0 l 0 -80 Z')
+PASS - "-webkit-clip-path" property for "path2" element at 1s saw something close to: path('M 100 40 l 20 0 l 0 60 l 0 -20 l -60 0 l 0 -20 l 80 0 l 0 60 l -60 0 l 0 -80 Z')
+PASS - "-webkit-clip-path" property for "path3" element at 1s saw something close to: path(evenodd, 'M 100 40 l 20 0 l 0 60 l 20 0 l 0 -20 l -60 0 l 0 -20 l 80 0 l 0 60 l -60 0 l 0 -80 Z')
+
diff --git a/LayoutTests/transitions/clip-path-path-transitions.html b/LayoutTests/transitions/clip-path-path-transitions.html
new file mode 100644 (file)
index 0000000..804ad31
--- /dev/null
@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+
+<html>
+<head>
+  <style>
+    .box {
+        display: inline-block;
+        height: 200px;
+        width: 200px;
+        margin: 10px;
+        background-color: gray;
+        transition: -webkit-clip-path 2s linear;
+    }
+    
+    #path1 {
+        -webkit-clip-path: path("M 60 40 l 20 0 l 0 60 l 20 0 l 0 -20 l -40 0 l 0 -20 l 80 0 l 0 60 l -60 0 l 0 -80 Z")
+    }
+    
+    body.final #path1 {
+        -webkit-clip-path: path("M 100 40 l 20 0 l 0 60 l 20 0 l 0 -20 l -60 0 l 0 -20 l 80 0 l 0 60 l -60 0 l 0 -80 Z");
+    }
+
+    /* Nonmatched segments, should not animate */
+    #path2 {
+        -webkit-clip-path: path("M 60 40 l 20 0 l 0 60 l 20 0 l 0 -20 l -40 0 l 0 -20 l 80 0 l 0 60 l -60 0 l 0 -80 Z")
+    }
+    
+    body.final #path2 {
+        -webkit-clip-path: path("M 100 40 l 20 0 l 0 60 l 0 -20 l -60 0 l 0 -20 l 80 0 l 0 60 l -60 0 l 0 -80 Z");
+    }
+
+    /* Nonmatched winding rule, should not animate */
+    #path3 {
+        -webkit-clip-path: path("M 60 40 l 20 0 l 0 60 l 20 0 l 0 -20 l -40 0 l 0 -20 l 80 0 l 0 60 l -60 0 l 0 -80 Z")
+    }
+    
+    body.final #path3 {
+        -webkit-clip-path: path(evenodd, "M 100 40 l 20 0 l 0 60 l 20 0 l 0 -20 l -60 0 l 0 -20 l 80 0 l 0 60 l -60 0 l 0 -80 Z");
+    }
+  </style>
+  <script src="resources/transition-test-helpers.js"></script>
+  <script type="text/javascript">
+
+    const expectedValues = [
+      // [time, element-id, property, expected-value, tolerance]
+      [1, 'path1', '-webkit-clip-path', 'path(\'M 80 40 l 20 0 l 0 60 l 20 0 l 0 -20 l -50 0 l 0 -20 l 80 0 l 0 60 l -60 0 l 0 -80 Z\')', 2],
+      [1, 'path2', '-webkit-clip-path', 'path(\'M 100 40 l 20 0 l 0 60 l 0 -20 l -60 0 l 0 -20 l 80 0 l 0 60 l -60 0 l 0 -80 Z\')', 2],
+      [1, 'path3', '-webkit-clip-path', 'path(evenodd, \'M 100 40 l 20 0 l 0 60 l 20 0 l 0 -20 l -60 0 l 0 -20 l 80 0 l 0 60 l -60 0 l 0 -80 Z\')', 2],
+    ];
+  
+    function setupTest()
+    {
+        document.body.classList.add('final');
+    }
+    
+    runTransitionTest(expectedValues, setupTest, usePauseAPI);
+  </script>
+</head>
+<body>
+  <div id="path1" class="box"></div>
+  <div id="path2" class="box"></div>
+  <div id="path3" class="box"></div>
+
+  <div id="result"></div>
+</body>
+</html>
index e0cef21..610333b 100644 (file)
@@ -69,17 +69,35 @@ function parseCrossFade(s)
     return {"from": matches[1], "to": matches[2], "percent": parseFloat(matches[3])}
 }
 
+function extractPathValues(path)
+{
+    var components = path.split(' ');
+    var result = [];
+    for (component of components) {
+        var compMatch;
+        if (compMatch = component.match(/[0-9.-]+/)) {
+            result.push(parseFloat(component))
+        }
+    }
+    return result;
+}
+
 function parseClipPath(s)
 {
+    var pathMatch;
+    if (pathMatch = s.match(/path\(((evenodd|nonzero), ?)?\'(.+)\'\)/))
+        return extractPathValues(pathMatch[pathMatch.length - 1]);
+    
     // FIXME: This only matches a subset of the shape syntax, and the polygon expects 4 points.
     var patterns = [
         /inset\(([\d.]+)\w+ ([\d.]+)\w+\)/,
         /circle\(([\d.]+)\w+ at ([\d.]+)\w+ ([\d.]+)\w+\)/,
         /ellipse\(([\d.]+)\w+ ([\d.]+)\w+ at ([\d.]+)\w+ ([\d.]+)\w+\)/,
-        /polygon\(([\d.]+)\w* ([\d.]+)\w*\, ([\d.]+)\w* ([\d.]+)\w*\, ([\d.]+)\w* ([\d.]+)\w*\, ([\d.]+)\w* ([\d.]+)\w*\)/
+        /polygon\(([\d.]+)\w* ([\d.]+)\w*\, ([\d.]+)\w* ([\d.]+)\w*\, ([\d.]+)\w* ([\d.]+)\w*\, ([\d.]+)\w* ([\d.]+)\w*\)/,
     ];
     
     for (pattern of patterns) {
+        var matchResult;
         if (matchResult = s.match(pattern)) {
             var result = [];
             for (var i = 1; i < matchResult.length; ++i)
index 91f6e05..58505ea 100644 (file)
@@ -1,4 +1,4 @@
-CONSOLE MESSAGE: line 275: Failed to pause 'fill' transition on element 'rect7'
+CONSOLE MESSAGE: line 293: Failed to pause 'fill' transition on element 'rect7'
 Example
 PASS - "fill-opacity" property for "rect1" element at 1s saw something close to: 0.6
 PASS - "stroke-width" property for "rect1" element at 1s saw something close to: 3
index d554b14..ee710bb 100644 (file)
@@ -1,3 +1,79 @@
+2015-10-25  Simon Fraser  <simon.fraser@apple.com>
+
+        Support bezier paths in clip-path property
+        https://bugs.webkit.org/show_bug.cgi?id=149996
+
+        Reviewed by Darin Adler.
+        
+        Support path() in the -webkit-clip-path property, as specified in
+        https://drafts.csswg.org/css-shapes-2/#supported-basic-shapes
+        
+        Added BasicShapePath and CSSBasicShapePath, which both represent the path
+        as a SVGPathByteStream and wind rule.
+        
+        Make BasicShape::canBlend() a virtual function, and implement it on each subclass.
+        Make various BasicShape subclass function overrides private, other than windRule()
+        wich is called on derived classes in a few places.
+        
+        Add SVGPathBlender::canBlendPaths() which returns true if the given paths can be
+        interpolated. Uses the same logic as blendAnimatedPath(), without doing any interpolation.
+        
+        RenderElement::createsGroup() is fixed to have clip-path trigger a group,
+        which fixes rendering of clip-path with a descendant compositing layer.
+
+        Tests: compositing/masks/clip-path-composited-descendent.html
+               css3/masking/clip-path-with-path.html
+               transitions/clip-path-path-transitions.html
+
+        * css/BasicShapeFunctions.cpp:
+        (WebCore::valueForBasicShape):
+        (WebCore::basicShapeForValue):
+        * css/CSSBasicShapes.cpp:
+        (WebCore::CSSBasicShapePath::CSSBasicShapePath):
+        (WebCore::CSSBasicShapePath::pathData):
+        (WebCore::buildPathString):
+        (WebCore::CSSBasicShapePath::cssText):
+        (WebCore::CSSBasicShapePath::equals):
+        * css/CSSBasicShapes.h:
+        * css/CSSParser.cpp:
+        (WebCore::CSSParser::parseBasicShapePath):
+        (WebCore::CSSParser::parseBasicShape):
+        * css/CSSParser.h:
+        * rendering/RenderElement.h:
+        (WebCore::RenderElement::createsGroup):
+        * rendering/style/BasicShapes.cpp:
+        (WebCore::BasicShapeCircle::canBlend):
+        (WebCore::BasicShapeEllipse::canBlend):
+        (WebCore::BasicShapePolygon::canBlend):
+        (WebCore::BasicShapePath::BasicShapePath):
+        (WebCore::BasicShapePath::path):
+        (WebCore::BasicShapePath::operator==):
+        (WebCore::BasicShapePath::canBlend):
+        (WebCore::BasicShapePath::blend):
+        (WebCore::BasicShapeInset::canBlend):
+        (WebCore::BasicShape::canBlend): Deleted.
+        * rendering/style/BasicShapes.h:
+        * svg/SVGPathBlender.cpp:
+        (WebCore::SVGPathBlender::addAnimatedPath):
+        (WebCore::SVGPathBlender::blendAnimatedPath):
+        (WebCore::SVGPathBlender::canBlendPaths):
+        (WebCore::SVGPathBlender::SVGPathBlender):
+        (WebCore::SVGPathBlender::blendMoveToSegment):
+        (WebCore::SVGPathBlender::blendLineToSegment):
+        (WebCore::SVGPathBlender::blendLineToHorizontalSegment):
+        (WebCore::SVGPathBlender::blendLineToVerticalSegment):
+        (WebCore::SVGPathBlender::blendCurveToCubicSegment):
+        (WebCore::SVGPathBlender::blendCurveToCubicSmoothSegment):
+        (WebCore::SVGPathBlender::blendCurveToQuadraticSegment):
+        (WebCore::SVGPathBlender::blendCurveToQuadraticSmoothSegment):
+        (WebCore::SVGPathBlender::blendArcToSegment):
+        * svg/SVGPathBlender.h:
+        * svg/SVGPathByteStream.h:
+        (WebCore::SVGPathByteStream::operator==):
+        * svg/SVGPathUtilities.cpp:
+        (WebCore::canBlendSVGPathByteStreams):
+        * svg/SVGPathUtilities.h:
+
 2015-10-25  Gwang Yoon Hwang  <yoon@igalia.com>
 
         [TexMap] Fix a misused flag for GstGL
index 064fcd2..ca96f99 100644 (file)
@@ -36,6 +36,7 @@
 #include "CSSValuePool.h"
 #include "Pair.h"
 #include "RenderStyle.h"
+#include "SVGPathByteStream.h"
 
 namespace WebCore {
 
@@ -71,28 +72,30 @@ Ref<CSSValue> valueForBasicShape(const RenderStyle& style, const BasicShape& bas
     RefPtr<CSSBasicShape> basicShapeValue;
     switch (basicShape.type()) {
     case BasicShape::BasicShapeCircleType: {
-        const auto& circle = downcast<BasicShapeCircle>(basicShape);
+        auto& circle = downcast<BasicShapeCircle>(basicShape);
         auto circleValue = CSSBasicShapeCircle::create();
 
         circleValue->setCenterX(valueForCenterCoordinate(cssValuePool, style, circle.centerX(), HORIZONTAL));
         circleValue->setCenterY(valueForCenterCoordinate(cssValuePool, style, circle.centerY(), VERTICAL));
         circleValue->setRadius(basicShapeRadiusToCSSValue(style, cssValuePool, circle.radius()));
-        basicShapeValue = circleValue.copyRef();
+
+        basicShapeValue = WTF::move(circleValue);
         break;
     }
     case BasicShape::BasicShapeEllipseType: {
-        const auto& ellipse = downcast<BasicShapeEllipse>(basicShape);
+        auto& ellipse = downcast<BasicShapeEllipse>(basicShape);
         auto ellipseValue = CSSBasicShapeEllipse::create();
 
         ellipseValue->setCenterX(valueForCenterCoordinate(cssValuePool, style, ellipse.centerX(), HORIZONTAL));
         ellipseValue->setCenterY(valueForCenterCoordinate(cssValuePool, style, ellipse.centerY(), VERTICAL));
         ellipseValue->setRadiusX(basicShapeRadiusToCSSValue(style, cssValuePool, ellipse.radiusX()));
         ellipseValue->setRadiusY(basicShapeRadiusToCSSValue(style, cssValuePool, ellipse.radiusY()));
-        basicShapeValue = ellipseValue.copyRef();
+
+        basicShapeValue = WTF::move(ellipseValue);
         break;
     }
     case BasicShape::BasicShapePolygonType: {
-        const auto& polygon = downcast<BasicShapePolygon>(basicShape);
+        auto& polygon = downcast<BasicShapePolygon>(basicShape);
         auto polygonValue = CSSBasicShapePolygon::create();
 
         polygonValue->setWindRule(polygon.windRule());
@@ -100,11 +103,19 @@ Ref<CSSValue> valueForBasicShape(const RenderStyle& style, const BasicShape& bas
         for (unsigned i = 0; i < values.size(); i += 2)
             polygonValue->appendPoint(cssValuePool.createValue(values.at(i), style), cssValuePool.createValue(values.at(i + 1), style));
 
-        basicShapeValue = polygonValue.copyRef();
+        basicShapeValue = WTF::move(polygonValue);
+        break;
+    }
+    case BasicShape::BasicShapePathType: {
+        auto& pathShape = downcast<BasicShapePath>(basicShape);
+        auto pathShapeValue = CSSBasicShapePath::create(pathShape.pathData()->copy());
+        pathShapeValue->setWindRule(pathShape.windRule());
+
+        basicShapeValue = WTF::move(pathShapeValue);
         break;
     }
     case BasicShape::BasicShapeInsetType: {
-        const auto& inset = downcast<BasicShapeInset>(basicShape);
+        auto& inset = downcast<BasicShapeInset>(basicShape);
         auto insetValue = CSSBasicShapeInset::create();
 
         insetValue->setTop(cssValuePool.createValue(inset.top(), style));
@@ -117,11 +128,9 @@ Ref<CSSValue> valueForBasicShape(const RenderStyle& style, const BasicShape& bas
         insetValue->setBottomRightRadius(cssValuePool.createValue(inset.bottomRightRadius(), style));
         insetValue->setBottomLeftRadius(cssValuePool.createValue(inset.bottomLeftRadius(), style));
 
-        basicShapeValue = insetValue.copyRef();
+        basicShapeValue = WTF::move(insetValue);
         break;
     }
-    default:
-        break;
     }
 
     return cssValuePool.createValue(basicShapeValue.releaseNonNull());
@@ -205,19 +214,19 @@ Ref<BasicShape> basicShapeForValue(const CSSToLengthConversionData& conversionDa
 
     switch (basicShapeValue->type()) {
     case CSSBasicShape::CSSBasicShapeCircleType: {
-        const CSSBasicShapeCircle& circleValue = downcast<CSSBasicShapeCircle>(*basicShapeValue);
-        RefPtr<BasicShapeCircle> circle = BasicShapeCircle::create();
+        auto& circleValue = downcast<CSSBasicShapeCircle>(*basicShapeValue);
+        auto circle = BasicShapeCircle::create();
 
         circle->setCenterX(convertToCenterCoordinate(conversionData, circleValue.centerX()));
         circle->setCenterY(convertToCenterCoordinate(conversionData, circleValue.centerY()));
         circle->setRadius(cssValueToBasicShapeRadius(conversionData, circleValue.radius()));
 
-        basicShape = circle.release();
+        basicShape = WTF::move(circle);
         break;
     }
     case CSSBasicShape::CSSBasicShapeEllipseType: {
-        const CSSBasicShapeEllipse& ellipseValue = downcast<CSSBasicShapeEllipse>(*basicShapeValue);
-        RefPtr<BasicShapeEllipse> ellipse = BasicShapeEllipse::create();
+        auto& ellipseValue = downcast<CSSBasicShapeEllipse>(*basicShapeValue);
+        auto ellipse = BasicShapeEllipse::create();
 
         ellipse->setCenterX(convertToCenterCoordinate(conversionData, ellipseValue.centerX()));
         ellipse->setCenterY(convertToCenterCoordinate(conversionData, ellipseValue.centerY()));
@@ -225,24 +234,24 @@ Ref<BasicShape> basicShapeForValue(const CSSToLengthConversionData& conversionDa
         ellipse->setRadiusX(cssValueToBasicShapeRadius(conversionData, ellipseValue.radiusX()));
         ellipse->setRadiusY(cssValueToBasicShapeRadius(conversionData, ellipseValue.radiusY()));
 
-        basicShape = ellipse.release();
+        basicShape = WTF::move(ellipse);
         break;
     }
     case CSSBasicShape::CSSBasicShapePolygonType: {
-        const CSSBasicShapePolygon& polygonValue = downcast<CSSBasicShapePolygon>(*basicShapeValue);
-        RefPtr<BasicShapePolygon> polygon = BasicShapePolygon::create();
+        auto& polygonValue = downcast<CSSBasicShapePolygon>(*basicShapeValue);
+        auto polygon = BasicShapePolygon::create();
 
         polygon->setWindRule(polygonValue.windRule());
         const Vector<RefPtr<CSSPrimitiveValue>>& values = polygonValue.values();
         for (unsigned i = 0; i < values.size(); i += 2)
             polygon->appendPoint(convertToLength(conversionData, values.at(i).get()), convertToLength(conversionData, values.at(i + 1).get()));
 
-        basicShape = polygon.release();
+        basicShape = WTF::move(polygon);
         break;
     }
     case CSSBasicShape::CSSBasicShapeInsetType: {
-        const CSSBasicShapeInset& rectValue = downcast<CSSBasicShapeInset>(*basicShapeValue);
-        RefPtr<BasicShapeInset> rect = BasicShapeInset::create();
+        auto& rectValue = downcast<CSSBasicShapeInset>(*basicShapeValue);
+        auto rect = BasicShapeInset::create();
 
         rect->setTop(convertToLength(conversionData, rectValue.top()));
         rect->setRight(convertToLength(conversionData, rectValue.right()));
@@ -254,12 +263,18 @@ Ref<BasicShape> basicShapeForValue(const CSSToLengthConversionData& conversionDa
         rect->setBottomRightRadius(convertToLengthSize(conversionData, rectValue.bottomRightRadius()));
         rect->setBottomLeftRadius(convertToLengthSize(conversionData, rectValue.bottomLeftRadius()));
 
-        basicShape = rect.release();
+        basicShape = WTF::move(rect);
         break;
     }
-    default:
+    case CSSBasicShape::CSSBasicShapePathType: {
+        auto& pathValue = downcast<CSSBasicShapePath>(*basicShapeValue);
+        auto path = BasicShapePath::create(pathValue.pathData().copy());
+        path->setWindRule(pathValue.windRule());
+
+        basicShape = WTF::move(path);
         break;
     }
+    }
 
     return basicShape.releaseNonNull();
 }
index 1de7716..4559b96 100644 (file)
 
 #include "CSSBasicShapes.h"
 
+#include "CSSParser.h"
 #include "CSSPrimitiveValueMappings.h"
 #include "CSSValuePool.h"
 #include "Pair.h"
+#include "SVGPathByteStream.h"
+#include "SVGPathUtilities.h"
 #include <wtf/text/StringBuilder.h>
 
 using namespace WTF;
@@ -198,6 +201,47 @@ bool CSSBasicShapeEllipse::equals(const CSSBasicShape& shape) const
         && compareCSSValuePtr(m_radiusY, other.m_radiusY);
 }
 
+CSSBasicShapePath::CSSBasicShapePath(std::unique_ptr<SVGPathByteStream>&& pathData)
+    : m_byteStream(WTF::move(pathData))
+{
+}
+
+static String buildPathString(const WindRule& windRule, const String& path, const String& box)
+{
+    StringBuilder result;
+    if (windRule == RULE_EVENODD)
+        result.appendLiteral("path(evenodd, ");
+    else
+        result.appendLiteral("path(");
+
+    result.append(quoteCSSString(path));
+    result.append(')');
+
+    if (box.length()) {
+        result.append(' ');
+        result.append(box);
+    }
+
+    return result.toString();
+}
+
+String CSSBasicShapePath::cssText() const
+{
+    String pathString;
+    buildStringFromByteStream(*m_byteStream, pathString, UnalteredParsing);
+
+    return buildPathString(m_windRule, pathString, m_referenceBox ? m_referenceBox->cssText() : String());
+}
+
+bool CSSBasicShapePath::equals(const CSSBasicShape& otherShape) const
+{
+    if (!is<CSSBasicShapePath>(otherShape))
+        return false;
+
+    auto& otherShapePath = downcast<CSSBasicShapePath>(otherShape);
+    return windRule() == otherShapePath.windRule() && pathData() == otherShapePath.pathData();
+}
+
 static String buildPolygonString(const WindRule& windRule, const Vector<String>& points)
 {
     ASSERT(!(points.size() % 2));
index 4cc1e24..c8cd327 100644 (file)
 
 namespace WebCore {
 
+class SVGPathByteStream;
+
 class CSSBasicShape : public RefCounted<CSSBasicShape> {
 public:
     enum Type {
         CSSBasicShapePolygonType,
         CSSBasicShapeCircleType,
         CSSBasicShapeEllipseType,
-        CSSBasicShapeInsetType
+        CSSBasicShapeInsetType,
+        CSSBasicShapePathType
     };
 
     virtual Type type() const = 0;
@@ -107,13 +110,12 @@ public:
     void setBottomRightRadius(PassRefPtr<CSSPrimitiveValue> radius) { m_bottomRightRadius = radius; }
     void setBottomLeftRadius(PassRefPtr<CSSPrimitiveValue> radius) { m_bottomLeftRadius = radius; }
 
-    virtual String cssText() const override;
-    virtual bool equals(const CSSBasicShape&) const override;
-
 private:
     CSSBasicShapeInset() { }
 
     virtual Type type() const override { return CSSBasicShapeInsetType; }
+    virtual String cssText() const override;
+    virtual bool equals(const CSSBasicShape&) const override;
 
     RefPtr<CSSPrimitiveValue> m_top;
     RefPtr<CSSPrimitiveValue> m_right;
@@ -130,9 +132,6 @@ class CSSBasicShapeCircle final : public CSSBasicShape {
 public:
     static Ref<CSSBasicShapeCircle> create() { return adoptRef(*new CSSBasicShapeCircle); }
 
-    virtual String cssText() const override;
-    virtual bool equals(const CSSBasicShape&) const override;
-
     CSSPrimitiveValue* centerX() const { return m_centerX.get(); }
     CSSPrimitiveValue* centerY() const { return m_centerY.get(); }
     CSSPrimitiveValue* radius() const { return m_radius.get(); }
@@ -145,6 +144,8 @@ private:
     CSSBasicShapeCircle() { }
 
     virtual Type type() const override { return CSSBasicShapeCircleType; }
+    virtual String cssText() const override;
+    virtual bool equals(const CSSBasicShape&) const override;
 
     RefPtr<CSSPrimitiveValue> m_centerX;
     RefPtr<CSSPrimitiveValue> m_centerY;
@@ -165,13 +166,12 @@ public:
     void setRadiusX(PassRefPtr<CSSPrimitiveValue> radiusX) { m_radiusX = radiusX; }
     void setRadiusY(PassRefPtr<CSSPrimitiveValue> radiusY) { m_radiusY = radiusY; }
 
-    virtual String cssText() const override;
-    virtual bool equals(const CSSBasicShape&) const override;
-
 private:
     CSSBasicShapeEllipse() { }
 
     virtual Type type() const override { return CSSBasicShapeEllipseType; }
+    virtual String cssText() const override;
+    virtual bool equals(const CSSBasicShape&) const override;
 
     RefPtr<CSSPrimitiveValue> m_centerX;
     RefPtr<CSSPrimitiveValue> m_centerY;
@@ -193,12 +193,9 @@ public:
     RefPtr<CSSPrimitiveValue> getYAt(unsigned i) const { return m_values.at(i * 2 + 1); }
     const Vector<RefPtr<CSSPrimitiveValue>>& values() const { return m_values; }
 
-    void setWindRule(WindRule w) { m_windRule = w; }
+    void setWindRule(WindRule rule) { m_windRule = rule; }
     WindRule windRule() const { return m_windRule; }
 
-    virtual String cssText() const override;
-    virtual bool equals(const CSSBasicShape&) const override;
-
 private:
     CSSBasicShapePolygon()
         : m_windRule(RULE_NONZERO)
@@ -206,11 +203,39 @@ private:
     }
 
     virtual Type type() const override { return CSSBasicShapePolygonType; }
+    virtual String cssText() const override;
+    virtual bool equals(const CSSBasicShape&) const override;
 
     Vector<RefPtr<CSSPrimitiveValue>> m_values;
     WindRule m_windRule;
 };
 
+class CSSBasicShapePath final : public CSSBasicShape {
+public:
+    static Ref<CSSBasicShapePath> create(std::unique_ptr<SVGPathByteStream>&& pathData)
+    {
+        return adoptRef(*new CSSBasicShapePath(WTF::move(pathData)));
+    }
+
+    const SVGPathByteStream& pathData() const
+    {
+        return *m_byteStream;
+    }
+
+    void setWindRule(WindRule rule) { m_windRule = rule; }
+    WindRule windRule() const { return m_windRule; }
+
+private:
+    CSSBasicShapePath(std::unique_ptr<SVGPathByteStream>&&);
+
+    virtual Type type() const override { return CSSBasicShapePathType; }
+    virtual String cssText() const override;
+    virtual bool equals(const CSSBasicShape&) const override;
+
+    std::unique_ptr<SVGPathByteStream> m_byteStream;
+    WindRule m_windRule { RULE_NONZERO };
+};
+
 } // namespace WebCore
 
 #define SPECIALIZE_TYPE_TRAITS_CSS_BASIC_SHAPES(ToValueTypeName) \
@@ -222,5 +247,6 @@ SPECIALIZE_TYPE_TRAITS_CSS_BASIC_SHAPES(CSSBasicShapeInset)
 SPECIALIZE_TYPE_TRAITS_CSS_BASIC_SHAPES(CSSBasicShapeCircle)
 SPECIALIZE_TYPE_TRAITS_CSS_BASIC_SHAPES(CSSBasicShapeEllipse)
 SPECIALIZE_TYPE_TRAITS_CSS_BASIC_SHAPES(CSSBasicShapePolygon)
+SPECIALIZE_TYPE_TRAITS_CSS_BASIC_SHAPES(CSSBasicShapePath)
 
 #endif // CSSBasicShapes_h
index 7c11092..1ad1b2f 100644 (file)
@@ -83,6 +83,8 @@
 #include "RenderTheme.h"
 #include "RuntimeEnabledFeatures.h"
 #include "SVGParserUtilities.h"
+#include "SVGPathByteStream.h"
+#include "SVGPathUtilities.h"
 #include "SelectorChecker.h"
 #include "SelectorCheckerTestFunctions.h"
 #include "Settings.h"
@@ -6638,6 +6640,37 @@ RefPtr<CSSBasicShape> CSSParser::parseBasicShapePolygon(CSSParserValueList& args
     return shape;
 }
 
+RefPtr<CSSBasicShape> CSSParser::parseBasicShapePath(CSSParserValueList& args)
+{
+    unsigned size = args.size();
+    if (size != 1 && size != 3)
+        return nullptr;
+
+    WindRule windRule = RULE_NONZERO;
+
+    CSSParserValue* argument = args.current();
+    if (argument->id == CSSValueEvenodd || argument->id == CSSValueNonzero) {
+        windRule = argument->id == CSSValueEvenodd ? RULE_EVENODD : RULE_NONZERO;
+
+        if (!isComma(args.next()))
+            return nullptr;
+        argument = args.next();
+    }
+
+    if (argument->unit != CSSPrimitiveValue::CSS_STRING)
+        return nullptr;
+
+    auto byteStream = std::make_unique<SVGPathByteStream>();
+    if (!buildSVGPathByteStreamFromString(argument->string, *byteStream, UnalteredParsing))
+        return nullptr;
+
+    RefPtr<CSSBasicShapePath> shape = CSSBasicShapePath::create(WTF::move(byteStream));
+    shape->setWindRule(windRule);
+
+    args.next();
+    return shape;
+}
+
 static bool isBoxValue(CSSValueID valueId, CSSPropertyID propId)
 {
     switch (valueId) {
@@ -6748,6 +6781,8 @@ RefPtr<CSSPrimitiveValue> CSSParser::parseBasicShape()
         shape = parseBasicShapeEllipse(*args);
     else if (equalIgnoringCase(value.function->name, "polygon("))
         shape = parseBasicShapePolygon(*args);
+    else if (equalIgnoringCase(value.function->name, "path("))
+        shape = parseBasicShapePath(*args);
     else if (equalIgnoringCase(value.function->name, "inset("))
         shape = parseBasicShapeInset(*args);
 
index b800ab0..04952db 100644 (file)
@@ -244,6 +244,7 @@ public:
     RefPtr<CSSBasicShape> parseBasicShapeCircle(CSSParserValueList&);
     RefPtr<CSSBasicShape> parseBasicShapeEllipse(CSSParserValueList&);
     RefPtr<CSSBasicShape> parseBasicShapePolygon(CSSParserValueList&);
+    RefPtr<CSSBasicShape> parseBasicShapePath(CSSParserValueList&);
     RefPtr<CSSBasicShape> parseBasicShapeInset(CSSParserValueList&);
 
     bool parseFont(bool important);
index e268beb..6b1d6c4 100644 (file)
@@ -152,7 +152,7 @@ public:
     bool mayCauseRepaintInsideViewport(const IntRect* visibleRect = nullptr) const;
 
     // Returns true if this renderer requires a new stacking context.
-    bool createsGroup() const { return isTransparent() || hasMask() || hasFilter() || hasBackdropFilter() || hasBlendMode(); }
+    bool createsGroup() const { return isTransparent() || hasMask() || hasClipPath() || hasFilter() || hasBackdropFilter() || hasBlendMode(); }
 
     bool isTransparent() const { return style().opacity() < 1.0f; }
     float opacity() const { return style().opacity(); }
index 21ec299..23b0202 100644 (file)
@@ -38,6 +38,8 @@
 #include "LengthFunctions.h"
 #include "Path.h"
 #include "RenderBox.h"
+#include "SVGPathByteStream.h"
+#include "SVGPathUtilities.h"
 
 namespace WebCore {
 
@@ -58,43 +60,12 @@ void BasicShapeCenterCoordinate::updateComputedLength()
     m_computedLength = Length(CalculationValue::create(WTF::move(op), CalculationRangeAll));
 }
 
-bool BasicShape::canBlend(const BasicShape& other) const
-{
-    // FIXME: Support animations between different shapes in the future.
-    if (type() != other.type())
-        return false;
-
-    // Just polygons with same number of vertices can be animated.
-    if (is<BasicShapePolygon>(*this)
-        && (downcast<BasicShapePolygon>(*this).values().size() != downcast<BasicShapePolygon>(other).values().size()
-        || downcast<BasicShapePolygon>(*this).windRule() != downcast<BasicShapePolygon>(other).windRule()))
-        return false;
-
-    // Circles with keywords for radii coordinates cannot be animated.
-    if (is<BasicShapeCircle>(*this)) {
-        const auto& thisCircle = downcast<BasicShapeCircle>(*this);
-        const auto& otherCircle = downcast<BasicShapeCircle>(other);
-        if (!thisCircle.radius().canBlend(otherCircle.radius()))
-            return false;
-    }
-
-    // Ellipses with keywords for radii coordinates cannot be animated.
-    if (!is<BasicShapeEllipse>(*this))
-        return true;
-
-    const auto& thisEllipse = downcast<BasicShapeEllipse>(*this);
-    const auto& otherEllipse = downcast<BasicShapeEllipse>(other);
-    return (thisEllipse.radiusX().canBlend(otherEllipse.radiusX())
-        && thisEllipse.radiusY().canBlend(otherEllipse.radiusY()));
-}
-
-
 bool BasicShapeCircle::operator==(const BasicShape& other) const
 {
     if (type() != other.type())
         return false;
 
-    const auto& otherCircle = downcast<BasicShapeCircle>(other);
+    auto& otherCircle = downcast<BasicShapeCircle>(other);
     return m_centerX == otherCircle.m_centerX
         && m_centerY == otherCircle.m_centerY
         && m_radius == otherCircle.m_radius;
@@ -132,16 +103,24 @@ void BasicShapeCircle::path(Path& path, const FloatRect& boundingBox)
     ));
 }
 
+bool BasicShapeCircle::canBlend(const BasicShape& other) const
+{
+    if (type() != other.type())
+        return false;
+
+    return radius().canBlend(downcast<BasicShapeCircle>(other).radius());
+}
+
 Ref<BasicShape> BasicShapeCircle::blend(const BasicShape& other, double progress) const
 {
     ASSERT(type() == other.type());
-    const auto& otherCircle = downcast<BasicShapeCircle>(other);
-    RefPtr<BasicShapeCircle> result =  BasicShapeCircle::create();
+    auto& otherCircle = downcast<BasicShapeCircle>(other);
+    auto result =  BasicShapeCircle::create();
 
     result->setCenterX(m_centerX.blend(otherCircle.centerX(), progress));
     result->setCenterY(m_centerY.blend(otherCircle.centerY(), progress));
     result->setRadius(m_radius.blend(otherCircle.radius(), progress));
-    return result.releaseNonNull();
+    return WTF::move(result);
 }
 
 bool BasicShapeEllipse::operator==(const BasicShape& other) const
@@ -149,7 +128,7 @@ bool BasicShapeEllipse::operator==(const BasicShape& other) const
     if (type() != other.type())
         return false;
 
-    const auto& otherEllipse = downcast<BasicShapeEllipse>(other);
+    auto& otherEllipse = downcast<BasicShapeEllipse>(other);
     return m_centerX == otherEllipse.m_centerX
         && m_centerY == otherEllipse.m_centerY
         && m_radiusX == otherEllipse.m_radiusX
@@ -184,11 +163,20 @@ void BasicShapeEllipse::path(Path& path, const FloatRect& boundingBox)
         radiusY * 2));
 }
 
+bool BasicShapeEllipse::canBlend(const BasicShape& other) const
+{
+    if (type() != other.type())
+        return false;
+
+    auto& otherEllipse = downcast<BasicShapeEllipse>(other);
+    return radiusX().canBlend(otherEllipse.radiusX()) && radiusY().canBlend(otherEllipse.radiusY());
+}
+
 Ref<BasicShape> BasicShapeEllipse::blend(const BasicShape& other, double progress) const
 {
     ASSERT(type() == other.type());
-    const auto& otherEllipse = downcast<BasicShapeEllipse>(other);
-    RefPtr<BasicShapeEllipse> result = BasicShapeEllipse::create();
+    auto& otherEllipse = downcast<BasicShapeEllipse>(other);
+    auto result = BasicShapeEllipse::create();
 
     if (m_radiusX.type() != BasicShapeRadius::Value || otherEllipse.radiusX().type() != BasicShapeRadius::Value
         || m_radiusY.type() != BasicShapeRadius::Value || otherEllipse.radiusY().type() != BasicShapeRadius::Value) {
@@ -196,14 +184,14 @@ Ref<BasicShape> BasicShapeEllipse::blend(const BasicShape& other, double progres
         result->setCenterY(otherEllipse.centerY());
         result->setRadiusX(otherEllipse.radiusX());
         result->setRadiusY(otherEllipse.radiusY());
-        return result.releaseNonNull();
+        return WTF::move(result);
     }
 
     result->setCenterX(m_centerX.blend(otherEllipse.centerX(), progress));
     result->setCenterY(m_centerY.blend(otherEllipse.centerY(), progress));
     result->setRadiusX(m_radiusX.blend(otherEllipse.radiusX(), progress));
     result->setRadiusY(m_radiusY.blend(otherEllipse.radiusY(), progress));
-    return result.releaseNonNull();
+    return WTF::move(result);
 }
 
 bool BasicShapePolygon::operator==(const BasicShape& other) const
@@ -211,7 +199,7 @@ bool BasicShapePolygon::operator==(const BasicShape& other) const
     if (type() != other.type())
         return false;
 
-    const auto& otherPolygon = downcast<BasicShapePolygon>(other);
+    auto& otherPolygon = downcast<BasicShapePolygon>(other);
     return m_windRule == otherPolygon.m_windRule
         && m_values == otherPolygon.m_values;
 }
@@ -234,18 +222,27 @@ void BasicShapePolygon::path(Path& path, const FloatRect& boundingBox)
     path.closeSubpath();
 }
 
+bool BasicShapePolygon::canBlend(const BasicShape& other) const
+{
+    if (type() != other.type())
+        return false;
+
+    auto& otherPolygon = downcast<BasicShapePolygon>(other);
+    return values().size() == otherPolygon.values().size() && windRule() == otherPolygon.windRule();
+}
+
 Ref<BasicShape> BasicShapePolygon::blend(const BasicShape& other, double progress) const
 {
     ASSERT(type() == other.type());
 
-    const auto& otherPolygon = downcast<BasicShapePolygon>(other);
+    auto& otherPolygon = downcast<BasicShapePolygon>(other);
     ASSERT(m_values.size() == otherPolygon.values().size());
     ASSERT(!(m_values.size() % 2));
 
     size_t length = m_values.size();
-    RefPtr<BasicShapePolygon> result = BasicShapePolygon::create();
+    auto result = BasicShapePolygon::create();
     if (!length)
-        return result.releaseNonNull();
+        return WTF::move(result);
 
     result->setWindRule(otherPolygon.windRule());
 
@@ -254,7 +251,51 @@ Ref<BasicShape> BasicShapePolygon::blend(const BasicShape& other, double progres
             m_values.at(i + 1).blend(otherPolygon.values().at(i + 1), progress));
     }
 
-    return result.releaseNonNull();
+    return WTF::move(result);
+}
+
+BasicShapePath::BasicShapePath(std::unique_ptr<SVGPathByteStream>&& byteStream)
+    : m_byteStream(WTF::move(byteStream))
+{
+}
+
+void BasicShapePath::path(Path& path, const FloatRect& boundingBox)
+{
+    ASSERT(path.isEmpty());
+    buildPathFromByteStream(*m_byteStream, path);
+    path.translate(toFloatSize(boundingBox.location()));
+}
+
+bool BasicShapePath::operator==(const BasicShape& other) const
+{
+    if (type() != other.type())
+        return false;
+
+    auto& otherPath = downcast<BasicShapePath>(other);
+    return m_windRule == otherPath.m_windRule && *m_byteStream == *otherPath.m_byteStream;
+}
+
+bool BasicShapePath::canBlend(const BasicShape& other) const
+{
+    if (type() != other.type())
+        return false;
+
+    auto& otherPath = downcast<BasicShapePath>(other);
+    return windRule() == otherPath.windRule() && canBlendSVGPathByteStreams(*m_byteStream, *otherPath.pathData());
+}
+
+Ref<BasicShape> BasicShapePath::blend(const BasicShape& from, double progress) const
+{
+    ASSERT(type() == from.type());
+
+    auto& fromPath = downcast<BasicShapePath>(from);
+
+    auto resultingPathBytes = std::make_unique<SVGPathByteStream>();
+    buildAnimatedSVGPathByteStream(*fromPath.m_byteStream, *m_byteStream, *resultingPathBytes, progress);
+
+    auto result = BasicShapePath::create(WTF::move(resultingPathBytes));
+    result->setWindRule(windRule());
+    return WTF::move(result);
 }
 
 bool BasicShapeInset::operator==(const BasicShape& other) const
@@ -262,7 +303,7 @@ bool BasicShapeInset::operator==(const BasicShape& other) const
     if (type() != other.type())
         return false;
 
-    const auto& otherInset = downcast<BasicShapeInset>(other);
+    auto& otherInset = downcast<BasicShapeInset>(other);
     return m_right == otherInset.m_right
         && m_top == otherInset.m_top
         && m_bottom == otherInset.m_bottom
@@ -295,12 +336,17 @@ void BasicShapeInset::path(Path& path, const FloatRect& boundingBox)
     path.addRoundedRect(FloatRoundedRect(rect, radii));
 }
 
+bool BasicShapeInset::canBlend(const BasicShape& other) const
+{
+    return type() == other.type();
+}
+
 Ref<BasicShape> BasicShapeInset::blend(const BasicShape& other, double progress) const
 {
     ASSERT(type() == other.type());
 
-    const auto& otherInset = downcast<BasicShapeInset>(other);
-    RefPtr<BasicShapeInset> result =  BasicShapeInset::create();
+    auto& otherInset = downcast<BasicShapeInset>(other);
+    auto result =  BasicShapeInset::create();
     result->setTop(m_top.blend(otherInset.top(), progress));
     result->setRight(m_right.blend(otherInset.right(), progress));
     result->setBottom(m_bottom.blend(otherInset.bottom(), progress));
@@ -311,6 +357,6 @@ Ref<BasicShape> BasicShapeInset::blend(const BasicShape& other, double progress)
     result->setBottomRightRadius(m_bottomRightRadius.blend(otherInset.bottomRightRadius(), progress));
     result->setBottomLeftRadius(m_bottomLeftRadius.blend(otherInset.bottomLeftRadius(), progress));
 
-    return result.releaseNonNull();
+    return WTF::move(result);
 }
 }
index d88c7ab..e57ca16 100644 (file)
@@ -44,6 +44,7 @@ namespace WebCore {
 class FloatRect;
 class Path;
 class RenderBox;
+class SVGPathByteStream;
 
 class BasicShape : public RefCounted<BasicShape> {
 public:
@@ -51,18 +52,20 @@ public:
 
     enum Type {
         BasicShapePolygonType,
+        BasicShapePathType,
         BasicShapeCircleType,
         BasicShapeEllipseType,
         BasicShapeInsetType
     };
 
-    bool canBlend(const BasicShape&) const;
+    virtual Type type() const = 0;
 
     virtual void path(Path&, const FloatRect&) = 0;
     virtual WindRule windRule() const { return RULE_NONZERO; }
+
+    virtual bool canBlend(const BasicShape&) const = 0;
     virtual Ref<BasicShape> blend(const BasicShape&, double) const = 0;
 
-    virtual Type type() const = 0;
     virtual bool operator==(const BasicShape&) const = 0;
 };
 
@@ -111,11 +114,11 @@ public:
     }
 
 private:
+    void updateComputedLength();
+
     Direction m_direction;
     Length m_length;
     Length m_computedLength;
-
-    void updateComputedLength();
 };
 
 class BasicShapeRadius {
@@ -125,10 +128,23 @@ public:
         ClosestSide,
         FarthestSide
     };
-    BasicShapeRadius() : m_value(Undefined), m_type(ClosestSide) { }
-    explicit BasicShapeRadius(Length v) : m_value(v), m_type(Value) { }
-    explicit BasicShapeRadius(Type t) : m_value(Undefined), m_type(t) { }
-    BasicShapeRadius(const BasicShapeRadius& other) : m_value(other.value()), m_type(other.type()) { }
+    BasicShapeRadius()
+        : m_value(Undefined),
+        m_type(ClosestSide)
+    { }
+
+    explicit BasicShapeRadius(Length v)
+        : m_value(v)
+        , m_type(Value)
+    { }
+    explicit BasicShapeRadius(Type t)
+        : m_value(Undefined)
+        , m_type(t)
+    { }
+    BasicShapeRadius(const BasicShapeRadius& other)
+        : m_value(other.value())
+        , m_type(other.type())
+    { }
 
     const Length& value() const { return m_value; }
     Type type() const { return m_type; }
@@ -171,15 +187,18 @@ public:
     void setCenterY(BasicShapeCenterCoordinate centerY) { m_centerY = WTF::move(centerY); }
     void setRadius(BasicShapeRadius radius) { m_radius = WTF::move(radius); }
 
+private:
+    BasicShapeCircle() = default;
+
+    virtual Type type() const override { return BasicShapeCircleType; }
+
     virtual void path(Path&, const FloatRect&) override;
+
+    virtual bool canBlend(const BasicShape&) const override;
     virtual Ref<BasicShape> blend(const BasicShape&, double) const override;
 
-    virtual Type type() const override { return BasicShapeCircleType; }
     virtual bool operator==(const BasicShape&) const override;
 
-private:
-    BasicShapeCircle() { }
-
     BasicShapeCenterCoordinate m_centerX;
     BasicShapeCenterCoordinate m_centerY;
     BasicShapeRadius m_radius;
@@ -200,15 +219,18 @@ public:
     void setRadiusX(BasicShapeRadius radiusX) { m_radiusX = WTF::move(radiusX); }
     void setRadiusY(BasicShapeRadius radiusY) { m_radiusY = WTF::move(radiusY); }
 
+private:
+    BasicShapeEllipse() = default;
+
+    virtual Type type() const override { return BasicShapeEllipseType; }
+
     virtual void path(Path&, const FloatRect&) override;
+
+    virtual bool canBlend(const BasicShape&) const override;
     virtual Ref<BasicShape> blend(const BasicShape&, double) const override;
 
-    virtual Type type() const override { return BasicShapeEllipseType; }
     virtual bool operator==(const BasicShape&) const override;
 
-private:
-    BasicShapeEllipse() { }
-
     BasicShapeCenterCoordinate m_centerX;
     BasicShapeCenterCoordinate m_centerY;
     BasicShapeRadius m_radiusX;
@@ -226,21 +248,50 @@ public:
     void setWindRule(WindRule windRule) { m_windRule = windRule; }
     void appendPoint(Length x, Length y) { m_values.append(WTF::move(x)); m_values.append(WTF::move(y)); }
 
+    virtual WindRule windRule() const override { return m_windRule; }
+
+private:
+    BasicShapePolygon() = default;
+
+    virtual Type type() const override { return BasicShapePolygonType; }
+
     virtual void path(Path&, const FloatRect&) override;
+
+    virtual bool canBlend(const BasicShape&) const override;
     virtual Ref<BasicShape> blend(const BasicShape&, double) const override;
 
+    virtual bool operator==(const BasicShape&) const override;
+
+    WindRule m_windRule { RULE_NONZERO };
+    Vector<Length> m_values;
+};
+
+class BasicShapePath final : public BasicShape {
+public:
+    static Ref<BasicShapePath> create(std::unique_ptr<SVGPathByteStream>&& byteStream)
+    {
+        return adoptRef(*new BasicShapePath(WTF::move(byteStream)));
+    }
+
+    void setWindRule(WindRule windRule) { m_windRule = windRule; }
     virtual WindRule windRule() const override { return m_windRule; }
 
-    virtual Type type() const override { return BasicShapePolygonType; }
-    virtual bool operator==(const BasicShape&) const override;
+    const SVGPathByteStream* pathData() const { return m_byteStream.get(); }
 
 private:
-    BasicShapePolygon()
-        : m_windRule(RULE_NONZERO)
-    { }
+    BasicShapePath(std::unique_ptr<SVGPathByteStream>&&);
 
-    WindRule m_windRule;
-    Vector<Length> m_values;
+    virtual Type type() const override { return BasicShapePathType; }
+
+    virtual void path(Path&, const FloatRect&) override;
+
+    virtual bool canBlend(const BasicShape&) const override;
+    virtual Ref<BasicShape> blend(const BasicShape&, double) const override;
+
+    virtual bool operator==(const BasicShape&) const override;
+
+    std::unique_ptr<SVGPathByteStream> m_byteStream;
+    WindRule m_windRule { RULE_NONZERO };
 };
 
 class BasicShapeInset final : public BasicShape {
@@ -267,15 +318,18 @@ public:
     void setBottomRightRadius(LengthSize radius) { m_bottomRightRadius = WTF::move(radius); }
     void setBottomLeftRadius(LengthSize radius) { m_bottomLeftRadius = WTF::move(radius); }
 
+private:
+    BasicShapeInset() = default;
+
+    virtual Type type() const override { return BasicShapeInsetType; }
+
     virtual void path(Path&, const FloatRect&) override;
+
+    virtual bool canBlend(const BasicShape&) const override;
     virtual Ref<BasicShape> blend(const BasicShape&, double) const override;
 
-    virtual Type type() const override { return BasicShapeInsetType; }
     virtual bool operator==(const BasicShape&) const override;
 
-private:
-    BasicShapeInset() { }
-
     Length m_right;
     Length m_top;
     Length m_bottom;
@@ -297,6 +351,7 @@ SPECIALIZE_TYPE_TRAITS_END()
 SPECIALIZE_TYPE_TRAITS_BASIC_SHAPE(BasicShapeCircle, BasicShape::BasicShapeCircleType)
 SPECIALIZE_TYPE_TRAITS_BASIC_SHAPE(BasicShapeEllipse, BasicShape::BasicShapeEllipseType)
 SPECIALIZE_TYPE_TRAITS_BASIC_SHAPE(BasicShapePolygon, BasicShape::BasicShapePolygonType)
+SPECIALIZE_TYPE_TRAITS_BASIC_SHAPE(BasicShapePath, BasicShape::BasicShapePathType)
 SPECIALIZE_TYPE_TRAITS_BASIC_SHAPE(BasicShapeInset, BasicShape::BasicShapeInsetType)
 
 #endif // BasicShapes_h
index 7aa84e8..f59fcc8 100644 (file)
@@ -30,17 +30,23 @@ namespace WebCore {
 
 bool SVGPathBlender::addAnimatedPath(SVGPathSource& fromSource, SVGPathSource& toSource, SVGPathConsumer& consumer, unsigned repeatCount)
 {
-    SVGPathBlender blender(fromSource, toSource, consumer);
+    SVGPathBlender blender(fromSource, toSource, &consumer);
     return blender.addAnimatedPath(repeatCount);
 }
 
 bool SVGPathBlender::blendAnimatedPath(SVGPathSource& fromSource, SVGPathSource& toSource, SVGPathConsumer& consumer, float progress)
 {
-    SVGPathBlender blender(fromSource, toSource, consumer);
+    SVGPathBlender blender(fromSource, toSource, &consumer);
     return blender.blendAnimatedPath(progress);
 }
 
-SVGPathBlender::SVGPathBlender(SVGPathSource& fromSource, SVGPathSource& toSource, SVGPathConsumer& consumer)
+bool SVGPathBlender::canBlendPaths(SVGPathSource& fromSource, SVGPathSource& toSource)
+{
+    SVGPathBlender blender(fromSource, toSource);
+    return blender.canBlendPaths();
+}
+
+SVGPathBlender::SVGPathBlender(SVGPathSource& fromSource, SVGPathSource& toSource, SVGPathConsumer* consumer)
     : m_fromSource(fromSource)
     , m_toSource(toSource)
     , m_consumer(consumer)
@@ -118,7 +124,10 @@ bool SVGPathBlender::blendMoveToSegment(float progress)
         || !m_toSource.parseMoveToSegment(toTargetPoint))
         return false;
 
-    m_consumer.moveTo(blendAnimatedFloatPoint(fromTargetPoint, toTargetPoint, progress), false, m_isInFirstHalfOfAnimation ? m_fromMode : m_toMode);
+    if (!m_consumer)
+        return true;
+
+    m_consumer->moveTo(blendAnimatedFloatPoint(fromTargetPoint, toTargetPoint, progress), false, m_isInFirstHalfOfAnimation ? m_fromMode : m_toMode);
     m_fromCurrentPoint = m_fromMode == AbsoluteCoordinates ? fromTargetPoint : m_fromCurrentPoint + fromTargetPoint;
     m_toCurrentPoint = m_toMode == AbsoluteCoordinates ? toTargetPoint : m_toCurrentPoint + toTargetPoint;
     return true;
@@ -132,7 +141,10 @@ bool SVGPathBlender::blendLineToSegment(float progress)
         || !m_toSource.parseLineToSegment(toTargetPoint))
         return false;
 
-    m_consumer.lineTo(blendAnimatedFloatPoint(fromTargetPoint, toTargetPoint, progress), m_isInFirstHalfOfAnimation ? m_fromMode : m_toMode);
+    if (!m_consumer)
+        return true;
+
+    m_consumer->lineTo(blendAnimatedFloatPoint(fromTargetPoint, toTargetPoint, progress), m_isInFirstHalfOfAnimation ? m_fromMode : m_toMode);
     m_fromCurrentPoint = m_fromMode == AbsoluteCoordinates ? fromTargetPoint : m_fromCurrentPoint + fromTargetPoint;
     m_toCurrentPoint = m_toMode == AbsoluteCoordinates ? toTargetPoint : m_toCurrentPoint + toTargetPoint;
     return true;
@@ -146,7 +158,10 @@ bool SVGPathBlender::blendLineToHorizontalSegment(float progress)
         || !m_toSource.parseLineToHorizontalSegment(toX))
         return false;
 
-    m_consumer.lineToHorizontal(blendAnimatedDimensonalFloat(fromX, toX, BlendHorizontal, progress), m_isInFirstHalfOfAnimation ? m_fromMode : m_toMode);
+    if (!m_consumer)
+        return true;
+
+    m_consumer->lineToHorizontal(blendAnimatedDimensonalFloat(fromX, toX, BlendHorizontal, progress), m_isInFirstHalfOfAnimation ? m_fromMode : m_toMode);
     m_fromCurrentPoint.setX(m_fromMode == AbsoluteCoordinates ? fromX : m_fromCurrentPoint.x() + fromX);
     m_toCurrentPoint.setX(m_toMode == AbsoluteCoordinates ? toX : m_toCurrentPoint.x() + toX);
     return true;
@@ -160,7 +175,10 @@ bool SVGPathBlender::blendLineToVerticalSegment(float progress)
         || !m_toSource.parseLineToVerticalSegment(toY))
         return false;
 
-    m_consumer.lineToVertical(blendAnimatedDimensonalFloat(fromY, toY, BlendVertical, progress), m_isInFirstHalfOfAnimation ? m_fromMode : m_toMode);
+    if (!m_consumer)
+        return true;
+
+    m_consumer->lineToVertical(blendAnimatedDimensonalFloat(fromY, toY, BlendVertical, progress), m_isInFirstHalfOfAnimation ? m_fromMode : m_toMode);
     m_fromCurrentPoint.setY(m_fromMode == AbsoluteCoordinates ? fromY : m_fromCurrentPoint.y() + fromY);
     m_toCurrentPoint.setY(m_toMode == AbsoluteCoordinates ? toY : m_toCurrentPoint.y() + toY);
     return true;
@@ -178,7 +196,10 @@ bool SVGPathBlender::blendCurveToCubicSegment(float progress)
         || !m_toSource.parseCurveToCubicSegment(toPoint1, toPoint2, toTargetPoint))
         return false;
 
-    m_consumer.curveToCubic(blendAnimatedFloatPoint(fromPoint1, toPoint1, progress),
+    if (!m_consumer)
+        return true;
+
+    m_consumer->curveToCubic(blendAnimatedFloatPoint(fromPoint1, toPoint1, progress),
         blendAnimatedFloatPoint(fromPoint2, toPoint2, progress),
         blendAnimatedFloatPoint(fromTargetPoint, toTargetPoint, progress),
         m_isInFirstHalfOfAnimation ? m_fromMode : m_toMode);
@@ -197,7 +218,10 @@ bool SVGPathBlender::blendCurveToCubicSmoothSegment(float progress)
         || !m_toSource.parseCurveToCubicSmoothSegment(toPoint2, toTargetPoint))
         return false;
 
-    m_consumer.curveToCubicSmooth(blendAnimatedFloatPoint(fromPoint2, toPoint2, progress),
+    if (!m_consumer)
+        return true;
+
+    m_consumer->curveToCubicSmooth(blendAnimatedFloatPoint(fromPoint2, toPoint2, progress),
         blendAnimatedFloatPoint(fromTargetPoint, toTargetPoint, progress),
         m_isInFirstHalfOfAnimation ? m_fromMode : m_toMode);
     m_fromCurrentPoint = m_fromMode == AbsoluteCoordinates ? fromTargetPoint : m_fromCurrentPoint + fromTargetPoint;
@@ -215,7 +239,10 @@ bool SVGPathBlender::blendCurveToQuadraticSegment(float progress)
         || !m_toSource.parseCurveToQuadraticSegment(toPoint1, toTargetPoint))
         return false;
 
-    m_consumer.curveToQuadratic(blendAnimatedFloatPoint(fromPoint1, toPoint1, progress),
+    if (!m_consumer)
+        return true;
+
+    m_consumer->curveToQuadratic(blendAnimatedFloatPoint(fromPoint1, toPoint1, progress),
         blendAnimatedFloatPoint(fromTargetPoint, toTargetPoint, progress),
         m_isInFirstHalfOfAnimation ? m_fromMode : m_toMode);
     m_fromCurrentPoint = m_fromMode == AbsoluteCoordinates ? fromTargetPoint : m_fromCurrentPoint + fromTargetPoint;
@@ -231,7 +258,10 @@ bool SVGPathBlender::blendCurveToQuadraticSmoothSegment(float progress)
         || !m_toSource.parseCurveToQuadraticSmoothSegment(toTargetPoint))
         return false;
 
-    m_consumer.curveToQuadraticSmooth(blendAnimatedFloatPoint(fromTargetPoint, toTargetPoint, progress), m_isInFirstHalfOfAnimation ? m_fromMode : m_toMode);
+    if (!m_consumer)
+        return true;
+
+    m_consumer->curveToQuadraticSmooth(blendAnimatedFloatPoint(fromTargetPoint, toTargetPoint, progress), m_isInFirstHalfOfAnimation ? m_fromMode : m_toMode);
     m_fromCurrentPoint = m_fromMode == AbsoluteCoordinates ? fromTargetPoint : m_fromCurrentPoint + fromTargetPoint;
     m_toCurrentPoint = m_toMode == AbsoluteCoordinates ? toTargetPoint : m_toCurrentPoint + toTargetPoint;
     return true;
@@ -255,11 +285,14 @@ bool SVGPathBlender::blendArcToSegment(float progress)
         || !m_toSource.parseArcToSegment(toRx, toRy, toAngle, toLargeArc, toSweep, toTargetPoint))
         return false;
 
+    if (!m_consumer)
+        return true;
+
     if (m_addTypesCount) {
         ASSERT(m_fromMode == m_toMode);
         FloatPoint scaledToTargetPoint = toTargetPoint;
         scaledToTargetPoint.scale(m_addTypesCount, m_addTypesCount);
-        m_consumer.arcTo(fromRx + toRx * m_addTypesCount,
+        m_consumer->arcTo(fromRx + toRx * m_addTypesCount,
             fromRy + toRy * m_addTypesCount,
             fromAngle + toAngle * m_addTypesCount,
             fromLargeArc || toLargeArc,
@@ -267,7 +300,7 @@ bool SVGPathBlender::blendArcToSegment(float progress)
             fromTargetPoint + scaledToTargetPoint,
             m_fromMode);
     } else {
-        m_consumer.arcTo(blend(fromRx, toRx, progress),
+        m_consumer->arcTo(blend(fromRx, toRx, progress),
             blend(fromRy, toRy, progress),
             blend(fromAngle, toAngle, progress),
             m_isInFirstHalfOfAnimation ? fromLargeArc : toLargeArc,
@@ -312,6 +345,87 @@ bool SVGPathBlender::addAnimatedPath(unsigned repeatCount)
     return blendAnimatedPath(0);
 }
 
+bool SVGPathBlender::canBlendPaths()
+{
+    float progress = 0.5;
+    bool fromSourceHadData = m_fromSource.hasMoreData();
+    while (m_toSource.hasMoreData()) {
+        SVGPathSegType fromCommand;
+        SVGPathSegType toCommand;
+        if ((fromSourceHadData && !m_fromSource.parseSVGSegmentType(fromCommand)) || !m_toSource.parseSVGSegmentType(toCommand))
+            return false;
+
+        m_toMode = coordinateModeOfCommand(toCommand);
+        m_fromMode = fromSourceHadData ? coordinateModeOfCommand(fromCommand) : m_toMode;
+        if (m_fromMode != m_toMode && m_addTypesCount)
+            return false;
+
+        if (fromSourceHadData && !isSegmentEqual(fromCommand, toCommand, m_fromMode, m_toMode))
+            return false;
+
+        switch (toCommand) {
+        case PathSegMoveToRel:
+        case PathSegMoveToAbs:
+            if (!blendMoveToSegment(progress))
+                return false;
+            break;
+        case PathSegLineToRel:
+        case PathSegLineToAbs:
+            if (!blendLineToSegment(progress))
+                return false;
+            break;
+        case PathSegLineToHorizontalRel:
+        case PathSegLineToHorizontalAbs:
+            if (!blendLineToHorizontalSegment(progress))
+                return false;
+            break;
+        case PathSegLineToVerticalRel:
+        case PathSegLineToVerticalAbs:
+            if (!blendLineToVerticalSegment(progress))
+                return false;
+            break;
+        case PathSegClosePath:
+            break;
+        case PathSegCurveToCubicRel:
+        case PathSegCurveToCubicAbs:
+            if (!blendCurveToCubicSegment(progress))
+                return false;
+            break;
+        case PathSegCurveToCubicSmoothRel:
+        case PathSegCurveToCubicSmoothAbs:
+            if (!blendCurveToCubicSmoothSegment(progress))
+                return false;
+            break;
+        case PathSegCurveToQuadraticRel:
+        case PathSegCurveToQuadraticAbs:
+            if (!blendCurveToQuadraticSegment(progress))
+                return false;
+            break;
+        case PathSegCurveToQuadraticSmoothRel:
+        case PathSegCurveToQuadraticSmoothAbs:
+            if (!blendCurveToQuadraticSmoothSegment(progress))
+                return false;
+            break;
+        case PathSegArcRel:
+        case PathSegArcAbs:
+            if (!blendArcToSegment(progress))
+                return false;
+            break;
+        case PathSegUnknown:
+            return false;
+        }
+
+        if (!fromSourceHadData)
+            continue;
+        if (m_fromSource.hasMoreData() != m_toSource.hasMoreData())
+            return false;
+        if (!m_fromSource.hasMoreData() || !m_toSource.hasMoreData())
+            return true;
+    }
+
+    return true;
+}
+
 bool SVGPathBlender::blendAnimatedPath(float progress)
 {
     m_isInFirstHalfOfAnimation = progress < 0.5f;
@@ -353,7 +467,7 @@ bool SVGPathBlender::blendAnimatedPath(float progress)
                 return false;
             break;
         case PathSegClosePath:
-            m_consumer.closePath();
+            m_consumer->closePath();
             break;
         case PathSegCurveToCubicRel:
         case PathSegCurveToCubicAbs:
index 505d108..dae7e10 100644 (file)
@@ -39,8 +39,12 @@ public:
     static bool addAnimatedPath(SVGPathSource& from, SVGPathSource& to, SVGPathConsumer&, unsigned repeatCount);
     static bool blendAnimatedPath(SVGPathSource& from, SVGPathSource& to, SVGPathConsumer&, float);
 
+    static bool canBlendPaths(SVGPathSource& from, SVGPathSource& to);
+
 private:
-    SVGPathBlender(SVGPathSource&, SVGPathSource&, SVGPathConsumer&);
+    SVGPathBlender(SVGPathSource&, SVGPathSource&, SVGPathConsumer* = nullptr);
+
+    bool canBlendPaths();
 
     bool addAnimatedPath(unsigned repeatCount);
     bool blendAnimatedPath(float progress);
@@ -60,7 +64,7 @@ private:
 
     SVGPathSource& m_fromSource;
     SVGPathSource& m_toSource;
-    SVGPathConsumer& m_consumer;
+    SVGPathConsumer* m_consumer; // A null consumer indicates that we're just checking blendability.
 
     FloatPoint m_fromCurrentPoint;
     FloatPoint m_toCurrentPoint;
index a83afef..0bd7b75 100644 (file)
@@ -49,6 +49,11 @@ public:
 
     SVGPathByteStream() { }
     SVGPathByteStream(const Data& data) : m_data(data) { }
+    
+    bool operator==(const SVGPathByteStream& other) const
+    {
+        return m_data == other.m_data;
+    }
 
     std::unique_ptr<SVGPathByteStream> copy() const
     {
index bab498f..142a22c 100644 (file)
@@ -125,6 +125,13 @@ bool buildSVGPathByteStreamFromString(const String& d, SVGPathByteStream& result
     return SVGPathParser::parseToByteStream(source, result, parsingMode);
 }
 
+bool canBlendSVGPathByteStreams(const SVGPathByteStream& fromStream, const SVGPathByteStream& toStream)
+{
+    SVGPathByteStreamSource fromSource(fromStream);
+    SVGPathByteStreamSource toSource(toStream);
+    return SVGPathBlender::canBlendPaths(fromSource, toSource);
+}
+
 bool buildAnimatedSVGPathByteStream(const SVGPathByteStream& fromStream, const SVGPathByteStream& toStream, SVGPathByteStream& result, float progress)
 {
     ASSERT(&toStream != &result);
index f64a124..45d29fb 100644 (file)
@@ -49,6 +49,8 @@ bool buildStringFromSVGPathSegList(const SVGPathSegList&, String&, PathParsingMo
 // SVGPathByteStream -> SVGPathSegList
 bool buildSVGPathSegListFromByteStream(const SVGPathByteStream&, SVGPathElement&, SVGPathSegList&, PathParsingMode);
 
+bool canBlendSVGPathByteStreams(const SVGPathByteStream& from, const SVGPathByteStream& to);
+
 bool buildAnimatedSVGPathByteStream(const SVGPathByteStream& from, const SVGPathByteStream& to, SVGPathByteStream& result, float progress);
 bool addToSVGPathByteStream(SVGPathByteStream& streamToAppendTo, const SVGPathByteStream& from, unsigned repeatCount = 1);