[SVGDom] Add fill-rule support
authorFlorin Malita <fmalita@chromium.org>
Thu, 1 Dec 2016 18:35:11 +0000 (13:35 -0500)
committerSkia Commit-Bot <skia-commit-bot@chromium.org>
Tue, 6 Dec 2016 16:05:41 +0000 (16:05 +0000)
There's a bit of friction with this attribute, because per spec it is
an inherited presentation attribute, but in Skia it is part of the
actual SkPath state.

So we must add some plumbing to SkSVGShape & friends to allow overriding
the fill type at render-time.

R=robertphillips@google.com,stephana@google.com

Change-Id: I9c926d653c6211beb3914bffac50d4349dbdd2c0
Reviewed-on: https://skia-review.googlesource.com/5415
Reviewed-by: Mike Reed <reed@google.com>
Commit-Queue: Florin Malita <fmalita@chromium.org>

25 files changed:
experimental/svg/model/SkSVGAttribute.cpp
experimental/svg/model/SkSVGAttribute.h
experimental/svg/model/SkSVGAttributeParser.cpp
experimental/svg/model/SkSVGAttributeParser.h
experimental/svg/model/SkSVGCircle.cpp
experimental/svg/model/SkSVGCircle.h
experimental/svg/model/SkSVGDOM.cpp
experimental/svg/model/SkSVGEllipse.cpp
experimental/svg/model/SkSVGEllipse.h
experimental/svg/model/SkSVGLine.cpp
experimental/svg/model/SkSVGLine.h
experimental/svg/model/SkSVGNode.cpp
experimental/svg/model/SkSVGNode.h
experimental/svg/model/SkSVGPath.cpp
experimental/svg/model/SkSVGPath.h
experimental/svg/model/SkSVGPoly.cpp
experimental/svg/model/SkSVGPoly.h
experimental/svg/model/SkSVGRect.cpp
experimental/svg/model/SkSVGRect.h
experimental/svg/model/SkSVGRenderContext.cpp
experimental/svg/model/SkSVGRenderContext.h
experimental/svg/model/SkSVGShape.cpp
experimental/svg/model/SkSVGShape.h
experimental/svg/model/SkSVGTypes.h
experimental/svg/model/SkSVGValue.h

index 9d6b193..4935d6a 100644 (file)
@@ -12,6 +12,7 @@ SkSVGPresentationAttributes SkSVGPresentationAttributes::MakeInitial() {
 
     result.fFill.set(SkSVGPaint(SkSVGColorType(SK_ColorBLACK)));
     result.fFillOpacity.set(SkSVGNumberType(1));
+    result.fFillRule.set(SkSVGFillRule(SkSVGFillRule::Type::kNonZero));
 
     result.fStroke.set(SkSVGPaint(SkSVGPaint::Type::kNone));
     result.fStrokeLineCap.set(SkSVGLineCap(SkSVGLineCap::Type::kButt));
index 478a26b..93c5f99 100644 (file)
@@ -19,6 +19,7 @@ enum class SkSVGAttribute {
     kD,
     kFill,
     kFillOpacity,
+    kFillRule,
     kGradientTransform,
     kHeight,
     kHref,
@@ -56,6 +57,7 @@ struct SkSVGPresentationAttributes {
 
     SkTLazy<SkSVGPaint>      fFill;
     SkTLazy<SkSVGNumberType> fFillOpacity;
+    SkTLazy<SkSVGFillRule>   fFillRule;
 
     SkTLazy<SkSVGPaint>      fStroke;
     SkTLazy<SkSVGLineCap>    fStrokeLineCap;
index 04b0508..5ca317c 100644 (file)
@@ -551,3 +551,26 @@ bool SkSVGAttributeParser::parsePoints(SkSVGPointsType* points) {
 
     return false;
 }
+
+// https://www.w3.org/TR/SVG/painting.html#FillRuleProperty
+bool SkSVGAttributeParser::parseFillRule(SkSVGFillRule* fillRule) {
+    static const struct {
+        SkSVGFillRule::Type fType;
+        const char*         fName;
+    } gFillRuleInfo[] = {
+        { SkSVGFillRule::Type::kNonZero, "nonzero" },
+        { SkSVGFillRule::Type::kEvenOdd, "evenodd" },
+        { SkSVGFillRule::Type::kInherit, "inherit" },
+    };
+
+    bool parsedValue = false;
+    for (size_t i = 0; i < SK_ARRAY_COUNT(gFillRuleInfo); ++i) {
+        if (this->parseExpectedStringToken(gFillRuleInfo[i].fName)) {
+            *fillRule = SkSVGFillRule(gFillRuleInfo[i].fType);
+            parsedValue = true;
+            break;
+        }
+    }
+
+    return parsedValue && this->parseEOSToken();
+}
index 9d6939b..2ffa79f 100644 (file)
@@ -15,6 +15,7 @@ public:
     SkSVGAttributeParser(const char[]);
 
     bool parseColor(SkSVGColorType*);
+    bool parseFillRule(SkSVGFillRule*);
     bool parseNumber(SkSVGNumberType*);
     bool parseLength(SkSVGLength*);
     bool parseViewBox(SkSVGViewBoxType*);
index 692cd9f..9d81173 100644 (file)
@@ -47,7 +47,7 @@ void SkSVGCircle::onSetAttribute(SkSVGAttribute attr, const SkSVGValue& v) {
 }
 
 void SkSVGCircle::onDraw(SkCanvas* canvas, const SkSVGLengthContext& lctx,
-                         const SkPaint& paint) const {
+                         const SkPaint& paint, SkPath::FillType) const {
     const auto cx = lctx.resolve(fCx, SkSVGLengthContext::LengthType::kHorizontal);
     const auto cy = lctx.resolve(fCy, SkSVGLengthContext::LengthType::kVertical);
     const auto  r = lctx.resolve(fR , SkSVGLengthContext::LengthType::kOther);
index ef1bd2a..02aaad4 100644 (file)
@@ -23,7 +23,8 @@ public:
 protected:
     void onSetAttribute(SkSVGAttribute, const SkSVGValue&) override;
 
-    void onDraw(SkCanvas*, const SkSVGLengthContext&, const SkPaint&) const override;
+    void onDraw(SkCanvas*, const SkSVGLengthContext&, const SkPaint&,
+                SkPath::FillType) const override;
 
 private:
     SkSVGCircle();
index c9745f1..2120f80 100644 (file)
@@ -173,6 +173,18 @@ bool SetPointsAttribute(const sk_sp<SkSVGNode>& node, SkSVGAttribute attr,
     return true;
 }
 
+bool SetFillRuleAttribute(const sk_sp<SkSVGNode>& node, SkSVGAttribute attr,
+                          const char* stringValue) {
+    SkSVGFillRule fillRule;
+    SkSVGAttributeParser parser(stringValue);
+    if (!parser.parseFillRule(&fillRule)) {
+        return false;
+    }
+
+    node->setAttribute(attr, SkSVGFillRuleValue(fillRule));
+    return true;
+}
+
 SkString TrimmedString(const char* first, const char* last) {
     SkASSERT(first);
     SkASSERT(last);
@@ -256,6 +268,7 @@ SortedDictionaryEntry<AttrParseInfo> gAttributeParseInfo[] = {
     { "d"                , { SkSVGAttribute::kD                , SetPathDataAttribute     }},
     { "fill"             , { SkSVGAttribute::kFill             , SetPaintAttribute        }},
     { "fill-opacity"     , { SkSVGAttribute::kFillOpacity      , SetNumberAttribute       }},
+    { "fill-rule"        , { SkSVGAttribute::kFillRule         , SetFillRuleAttribute     }},
     { "gradientTransform", { SkSVGAttribute::kGradientTransform, SetTransformAttribute    }},
     { "height"           , { SkSVGAttribute::kHeight           , SetLengthAttribute       }},
     { "offset"           , { SkSVGAttribute::kOffset           , SetLengthAttribute       }},
index f7461b8..481af5c 100644 (file)
@@ -56,7 +56,7 @@ void SkSVGEllipse::onSetAttribute(SkSVGAttribute attr, const SkSVGValue& v) {
 }
 
 void SkSVGEllipse::onDraw(SkCanvas* canvas, const SkSVGLengthContext& lctx,
-                          const SkPaint& paint) const {
+                          const SkPaint& paint, SkPath::FillType) const {
     const auto cx = lctx.resolve(fCx, SkSVGLengthContext::LengthType::kHorizontal);
     const auto cy = lctx.resolve(fCy, SkSVGLengthContext::LengthType::kVertical);
     const auto rx = lctx.resolve(fRx, SkSVGLengthContext::LengthType::kHorizontal);
index 640d7f6..0042b33 100644 (file)
@@ -24,7 +24,8 @@ public:
 protected:
     void onSetAttribute(SkSVGAttribute, const SkSVGValue&) override;
 
-    void onDraw(SkCanvas*, const SkSVGLengthContext&, const SkPaint&) const override;
+    void onDraw(SkCanvas*, const SkSVGLengthContext&, const SkPaint&,
+                SkPath::FillType) const override;
 
 private:
     SkSVGEllipse();
index 030aad8..27cdd46 100644 (file)
@@ -56,7 +56,7 @@ void SkSVGLine::onSetAttribute(SkSVGAttribute attr, const SkSVGValue& v) {
 }
 
 void SkSVGLine::onDraw(SkCanvas* canvas, const SkSVGLengthContext& lctx,
-                       const SkPaint& paint) const {
+                       const SkPaint& paint, SkPath::FillType) const {
     const auto x1 = lctx.resolve(fX1, SkSVGLengthContext::LengthType::kHorizontal);
     const auto y1 = lctx.resolve(fY1, SkSVGLengthContext::LengthType::kVertical);
     const auto x2 = lctx.resolve(fX2, SkSVGLengthContext::LengthType::kHorizontal);
index c2716e0..524fc2a 100644 (file)
@@ -24,7 +24,8 @@ public:
 protected:
     void onSetAttribute(SkSVGAttribute, const SkSVGValue&) override;
 
-    void onDraw(SkCanvas*, const SkSVGLengthContext&, const SkPaint&) const override;
+    void onDraw(SkCanvas*, const SkSVGLengthContext&, const SkPaint&,
+                SkPath::FillType) const override;
 
 private:
     SkSVGLine();
index f40ee5d..a5c8147 100644 (file)
@@ -49,6 +49,10 @@ void SkSVGNode::setFillOpacity(const SkSVGNumberType& opacity) {
         SkSVGNumberType(SkTPin<SkScalar>(opacity.value(), 0, 1)));
 }
 
+void SkSVGNode::setFillRule(const SkSVGFillRule& fillRule) {
+    fPresentationAttributes.fFillRule.set(fillRule);
+}
+
 void SkSVGNode::setOpacity(const SkSVGNumberType& opacity) {
     fPresentationAttributes.fOpacity.set(
         SkSVGNumberType(SkTPin<SkScalar>(opacity.value(), 0, 1)));
@@ -79,6 +83,11 @@ void SkSVGNode::onSetAttribute(SkSVGAttribute attr, const SkSVGValue& v) {
             this->setFillOpacity(*opacity);
         }
         break;
+    case SkSVGAttribute::kFillRule:
+        if (const SkSVGFillRuleValue* fillRule = v.as<SkSVGFillRuleValue>()) {
+            this->setFillRule(*fillRule);
+        }
+        break;
     case SkSVGAttribute::kOpacity:
         if (const SkSVGNumberValue* opacity = v.as<SkSVGNumberValue>()) {
             this->setOpacity(*opacity);
index 2a7c836..1e092a4 100644 (file)
@@ -47,6 +47,7 @@ public:
 
     void setFill(const SkSVGPaint&);
     void setFillOpacity(const SkSVGNumberType&);
+    void setFillRule(const SkSVGFillRule&);
     void setOpacity(const SkSVGNumberType&);
     void setStroke(const SkSVGPaint&);
     void setStrokeOpacity(const SkSVGNumberType&);
index 07e0a3e..dd24823 100644 (file)
@@ -25,6 +25,9 @@ void SkSVGPath::onSetAttribute(SkSVGAttribute attr, const SkSVGValue& v) {
     }
 }
 
-void SkSVGPath::onDraw(SkCanvas* canvas, const SkSVGLengthContext&, const SkPaint& paint) const {
+void SkSVGPath::onDraw(SkCanvas* canvas, const SkSVGLengthContext&, const SkPaint& paint,
+                       SkPath::FillType fillType) const {
+    // the passed fillType follows inheritance rules and needs to be applied at draw time.
+    fPath.setFillType(fillType);
     canvas->drawPath(fPath, paint);
 }
index e72f0d1..8297a8d 100644 (file)
@@ -21,12 +21,13 @@ public:
 protected:
     void onSetAttribute(SkSVGAttribute, const SkSVGValue&) override;
 
-    void onDraw(SkCanvas*, const SkSVGLengthContext&, const SkPaint&) const override;
+    void onDraw(SkCanvas*, const SkSVGLengthContext&, const SkPaint&,
+                SkPath::FillType) const override;
 
 private:
     SkSVGPath();
 
-    SkPath fPath;
+    mutable SkPath fPath; // mutated in onDraw(), to apply inherited fill types.
 
     typedef SkSVGShape INHERITED;
 };
index bcc716f..826ca24 100644 (file)
@@ -6,6 +6,7 @@
  */
 
 #include "SkCanvas.h"
+#include "SkTLazy.h"
 #include "SkSVGRenderContext.h"
 #include "SkSVGPoly.h"
 #include "SkSVGValue.h"
@@ -31,6 +32,9 @@ void SkSVGPoly::onSetAttribute(SkSVGAttribute attr, const SkSVGValue& v) {
     }
 }
 
-void SkSVGPoly::onDraw(SkCanvas* canvas, const SkSVGLengthContext&, const SkPaint& paint) const {
+void SkSVGPoly::onDraw(SkCanvas* canvas, const SkSVGLengthContext&, const SkPaint& paint,
+                       SkPath::FillType fillType) const {
+    // the passed fillType follows inheritance rules and needs to be applied at draw time.
+    fPath.setFillType(fillType);
     canvas->drawPath(fPath, paint);
 }
index 3ae8dc6..f75d362 100644 (file)
@@ -29,12 +29,13 @@ public:
 protected:
     void onSetAttribute(SkSVGAttribute, const SkSVGValue&) override;
 
-    void onDraw(SkCanvas*, const SkSVGLengthContext&, const SkPaint&) const override;
+    void onDraw(SkCanvas*, const SkSVGLengthContext&, const SkPaint&,
+                SkPath::FillType) const override;
 
 private:
     SkSVGPoly(SkSVGTag);
 
-    SkPath fPath;
+    mutable SkPath fPath;  // mutated in onDraw(), to apply inherited fill types.
 
     typedef SkSVGShape INHERITED;
 };
index cbb1830..1afd995 100644 (file)
@@ -75,7 +75,7 @@ void SkSVGRect::onSetAttribute(SkSVGAttribute attr, const SkSVGValue& v) {
 }
 
 void SkSVGRect::onDraw(SkCanvas* canvas, const SkSVGLengthContext& lctx,
-                       const SkPaint& paint) const {
+                       const SkPaint& paint, SkPath::FillType) const {
     const SkRect rect = lctx.resolveRect(fX, fY, fWidth, fHeight);
     const SkScalar rx = lctx.resolve(fRx, SkSVGLengthContext::LengthType::kHorizontal);
     const SkScalar ry = lctx.resolve(fRy, SkSVGLengthContext::LengthType::kVertical);
index 0da248c..affa65f 100644 (file)
@@ -26,7 +26,8 @@ public:
 protected:
     void onSetAttribute(SkSVGAttribute, const SkSVGValue&) override;
 
-    void onDraw(SkCanvas*, const SkSVGLengthContext&, const SkPaint&) const override;
+    void onDraw(SkCanvas*, const SkSVGLengthContext&, const SkPaint&,
+                SkPath::FillType) const override;
 
 private:
     SkSVGRect();
index 147d64e..fd02623 100644 (file)
@@ -6,6 +6,7 @@
  */
 
 #include "SkCanvas.h"
+#include "SkPath.h"
 #include "SkSVGAttribute.h"
 #include "SkSVGNode.h"
 #include "SkSVGRenderContext.h"
@@ -188,6 +189,13 @@ void commitToPaint<SkSVGAttribute::kStrokeWidth>(const SkSVGPresentationAttribut
     pctx->fStrokePaint.setStrokeWidth(strokeWidth);
 }
 
+template <>
+void commitToPaint<SkSVGAttribute::kFillRule>(const SkSVGPresentationAttributes&,
+                                              const SkSVGRenderContext&,
+                                              SkSVGPresentationContext*) {
+    // Not part of the SkPaint state; applied to the path at render time.
+}
+
 } // anonymous ns
 
 SkSVGPresentationContext::SkSVGPresentationContext()
@@ -258,6 +266,7 @@ void SkSVGRenderContext::applyPresentationAttributes(const SkSVGPresentationAttr
 
     ApplyLazyInheritedAttribute(Fill);
     ApplyLazyInheritedAttribute(FillOpacity);
+    ApplyLazyInheritedAttribute(FillRule);
     ApplyLazyInheritedAttribute(Stroke);
     ApplyLazyInheritedAttribute(StrokeLineCap);
     ApplyLazyInheritedAttribute(StrokeLineJoin);
index 393c284..9d2fb3a 100644 (file)
@@ -65,6 +65,8 @@ public:
     const SkSVGLengthContext& lengthContext() const { return *fLengthContext; }
     SkSVGLengthContext* writableLengthContext() { return fLengthContext.writable(); }
 
+    const SkSVGPresentationContext& presentationContext() const { return *fPresentationContext; }
+
     SkCanvas* canvas() const { return fCanvas; }
 
     enum ApplyFlags {
index 38af4c9..9351a2f 100644 (file)
 SkSVGShape::SkSVGShape(SkSVGTag t) : INHERITED(t) {}
 
 void SkSVGShape::onRender(const SkSVGRenderContext& ctx) const {
+    const SkPath::FillType fillType =
+        FillRuleToFillType(*ctx.presentationContext().fInherited.fFillRule.get());
+
     // TODO: this approach forces duplicate geometry resolution in onDraw(); refactor to avoid.
     if (const SkPaint* fillPaint = ctx.fillPaint()) {
-        this->onDraw(ctx.canvas(), ctx.lengthContext(), *fillPaint);
+        this->onDraw(ctx.canvas(), ctx.lengthContext(), *fillPaint, fillType);
     }
 
     if (const SkPaint* strokePaint = ctx.strokePaint()) {
-        this->onDraw(ctx.canvas(), ctx.lengthContext(), *strokePaint);
+        this->onDraw(ctx.canvas(), ctx.lengthContext(), *strokePaint, fillType);
     }
 }
 
 void SkSVGShape::appendChild(sk_sp<SkSVGNode>) {
     SkDebugf("cannot append child nodes to an SVG shape.\n");
 }
+
+SkPath::FillType SkSVGShape::FillRuleToFillType(const SkSVGFillRule& fillRule) {
+    switch (fillRule.type()) {
+    case SkSVGFillRule::Type::kNonZero:
+        return SkPath::kWinding_FillType;
+    case SkSVGFillRule::Type::kEvenOdd:
+        return SkPath::kEvenOdd_FillType;
+    default:
+        SkASSERT(false);
+        return SkPath::kWinding_FillType;
+    }
+}
index 1f3c455..48b2ead 100644 (file)
@@ -8,6 +8,7 @@
 #ifndef SkSVGShape_DEFINED
 #define SkSVGShape_DEFINED
 
+#include "SkPath.h"
 #include "SkSVGTransformableNode.h"
 
 class SkSVGLengthContext;
@@ -24,7 +25,10 @@ protected:
 
     void onRender(const SkSVGRenderContext&) const final;
 
-    virtual void onDraw(SkCanvas*, const SkSVGLengthContext&, const SkPaint&) const = 0;
+    virtual void onDraw(SkCanvas*, const SkSVGLengthContext&, const SkPaint&,
+                        SkPath::FillType) const = 0;
+
+    static SkPath::FillType FillRuleToFillType(const SkSVGFillRule&);
 
 private:
     typedef SkSVGTransformableNode INHERITED;
index b07f9a2..d5d2b71 100644 (file)
@@ -191,4 +191,27 @@ private:
     Type fType;
 };
 
+class SkSVGFillRule {
+public:
+    enum class Type {
+        kNonZero,
+        kEvenOdd,
+        kInherit,
+    };
+
+    constexpr SkSVGFillRule() : fType(Type::kInherit) {}
+    constexpr explicit SkSVGFillRule(Type t) : fType(t) {}
+
+    SkSVGFillRule(const SkSVGFillRule&)            = default;
+    SkSVGFillRule& operator=(const SkSVGFillRule&) = default;
+
+    bool operator==(const SkSVGFillRule& other) const { return fType == other.fType; }
+    bool operator!=(const SkSVGFillRule& other) const { return !(*this == other); }
+
+    Type type() const { return fType; }
+
+private:
+    Type fType;
+};
+
 #endif // SkSVGTypes_DEFINED
index 0160af0..9f3095c 100644 (file)
@@ -18,6 +18,7 @@ class SkSVGValue : public SkNoncopyable {
 public:
     enum class Type {
         kColor,
+        kFillRule,
         kLength,
         kLineCap,
         kLineJoin,
@@ -70,6 +71,7 @@ private:
 };
 
 using SkSVGColorValue        = SkSVGWrapperValue<SkSVGColorType    , SkSVGValue::Type::kColor    >;
+using SkSVGFillRuleValue     = SkSVGWrapperValue<SkSVGFillRule     , SkSVGValue::Type::kFillRule >;
 using SkSVGLengthValue       = SkSVGWrapperValue<SkSVGLength       , SkSVGValue::Type::kLength   >;
 using SkSVGPathValue         = SkSVGWrapperValue<SkPath            , SkSVGValue::Type::kPath     >;
 using SkSVGTransformValue    = SkSVGWrapperValue<SkSVGTransformType, SkSVGValue::Type::kTransform>;