[SVGDom] Add more presentation attributes.
authorfmalita <fmalita@chromium.org>
Thu, 11 Aug 2016 16:16:29 +0000 (09:16 -0700)
committerCommit bot <commit-bot@chromium.org>
Thu, 11 Aug 2016 16:16:29 +0000 (09:16 -0700)
Implement proper presentation attribute inheritance, and add
support for

* fill-opacity
* stroke-linecap
* stroke-linejoin
* stroke-opacity
* stroke-width

R=robertphillips@google.com,stephana@google.com
GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=2234153002

Review-Url: https://codereview.chromium.org/2234153002

experimental/svg/model/SkSVGAttribute.cpp
experimental/svg/model/SkSVGAttribute.h
experimental/svg/model/SkSVGAttributeParser.cpp
experimental/svg/model/SkSVGAttributeParser.h
experimental/svg/model/SkSVGDOM.cpp
experimental/svg/model/SkSVGNode.cpp
experimental/svg/model/SkSVGRenderContext.cpp
experimental/svg/model/SkSVGRenderContext.h
experimental/svg/model/SkSVGShape.cpp
experimental/svg/model/SkSVGTypes.h
experimental/svg/model/SkSVGValue.h

index 2f1cace..9d6b193 100644 (file)
@@ -6,29 +6,18 @@
  */
 
 #include "SkSVGAttribute.h"
-#include "SkSVGRenderContext.h"
 
-SkSVGPresentationAttributes::SkSVGPresentationAttributes()
-    : fFillIsSet(false)
-    , fStrokeIsSet(false) { }
-
-void SkSVGPresentationAttributes::setFill(const SkSVGColorType& c) {
-    fFill = c;
-    fFillIsSet = true;
-}
-
-void SkSVGPresentationAttributes::setStroke(const SkSVGColorType& c) {
-    fStroke = c;
-    fStrokeIsSet = true;
-}
+SkSVGPresentationAttributes SkSVGPresentationAttributes::MakeInitial() {
+    SkSVGPresentationAttributes result;
 
+    result.fFill.set(SkSVGPaint(SkSVGColorType(SK_ColorBLACK)));
+    result.fFillOpacity.set(SkSVGNumberType(1));
 
-void SkSVGPresentationAttributes::applyTo(SkSVGRenderContext* ctx) const {
-    if (fFillIsSet) {
-        ctx->writablePresentationContext()->setFillColor(fFill);
-    }
+    result.fStroke.set(SkSVGPaint(SkSVGPaint::Type::kNone));
+    result.fStrokeLineCap.set(SkSVGLineCap(SkSVGLineCap::Type::kButt));
+    result.fStrokeLineJoin.set(SkSVGLineJoin(SkSVGLineJoin::Type::kMiter));
+    result.fStrokeOpacity.set(SkSVGNumberType(1));
+    result.fStrokeWidth.set(SkSVGLength(1));
 
-    if (fStrokeIsSet) {
-        ctx->writablePresentationContext()->setStrokeColor(fStroke);
-    }
+    return result;
 }
index 1d7c96e..4ac595d 100644 (file)
@@ -16,10 +16,15 @@ class SkSVGRenderContext;
 enum class SkSVGAttribute {
     kD,
     kFill,
+    kFillOpacity,
     kHeight,
     kRx,
     kRy,
     kStroke,
+    kStrokeOpacity,
+    kStrokeLineCap,
+    kStrokeLineJoin,
+    kStrokeWidth,
     kTransform,
     kViewBox,
     kWidth,
@@ -29,22 +34,19 @@ enum class SkSVGAttribute {
     kUnknown,
 };
 
-class SkSVGPresentationAttributes {
-public:
-    SkSVGPresentationAttributes();
+struct SkSVGPresentationAttributes {
+    static SkSVGPresentationAttributes MakeInitial();
 
-    void setFill(const SkSVGColorType&);
-    void setStroke(const SkSVGColorType&);
+    // TODO: SkTLazy adds an extra ptr per attribute; refactor to reduce overhead.
 
-    void applyTo(SkSVGRenderContext*) const;
+    SkTLazy<SkSVGPaint>      fFill;
+    SkTLazy<SkSVGNumberType> fFillOpacity;
 
-private:
-    // Color only for now.
-    SkSVGColorType fFill;
-    SkSVGColorType fStroke;
-
-    unsigned fFillIsSet   : 1;
-    unsigned fStrokeIsSet : 1;
+    SkTLazy<SkSVGPaint>      fStroke;
+    SkTLazy<SkSVGLineCap>    fStrokeLineCap;
+    SkTLazy<SkSVGLineJoin>   fStrokeLineJoin;
+    SkTLazy<SkSVGNumberType> fStrokeOpacity;
+    SkTLazy<SkSVGLength>     fStrokeWidth;
 };
 
 #endif // SkSVGAttribute_DEFINED
index 7a2561c..308eb62 100644 (file)
@@ -359,3 +359,71 @@ bool SkSVGAttributeParser::parseTransform(SkSVGTransformType* t) {
     *t = SkSVGTransformType(matrix);
     return true;
 }
+
+// https://www.w3.org/TR/SVG/painting.html#SpecifyingPaint
+bool SkSVGAttributeParser::parsePaint(SkSVGPaint* paint) {
+    SkSVGColorType c;
+    bool parsedValue = false;
+    if (this->parseColor(&c)) {
+        *paint = SkSVGPaint(c);
+        parsedValue = true;
+    } else if (this->parseExpectedStringToken("none")) {
+        *paint = SkSVGPaint(SkSVGPaint::Type::kNone);
+        parsedValue = true;
+    } else if (this->parseExpectedStringToken("currentColor")) {
+        *paint = SkSVGPaint(SkSVGPaint::Type::kCurrentColor);
+        parsedValue = true;
+    } else if (this->parseExpectedStringToken("inherit")) {
+        *paint = SkSVGPaint(SkSVGPaint::Type::kInherit);
+        parsedValue = true;
+    }
+    return parsedValue && this->parseEOSToken();
+}
+
+// https://www.w3.org/TR/SVG/painting.html#StrokeLinecapProperty
+bool SkSVGAttributeParser::parseLineCap(SkSVGLineCap* cap) {
+    static const struct {
+        SkSVGLineCap::Type fType;
+        const char*        fName;
+    } gCapInfo[] = {
+        { SkSVGLineCap::Type::kButt   , "butt"    },
+        { SkSVGLineCap::Type::kRound  , "round"   },
+        { SkSVGLineCap::Type::kSquare , "square"  },
+        { SkSVGLineCap::Type::kInherit, "inherit" },
+    };
+
+    bool parsedValue = false;
+    for (size_t i = 0; i < SK_ARRAY_COUNT(gCapInfo); ++i) {
+        if (this->parseExpectedStringToken(gCapInfo[i].fName)) {
+            *cap = SkSVGLineCap(gCapInfo[i].fType);
+            parsedValue = true;
+            break;
+        }
+    }
+
+    return parsedValue && this->parseEOSToken();
+}
+
+// https://www.w3.org/TR/SVG/painting.html#StrokeLinejoinProperty
+bool SkSVGAttributeParser::parseLineJoin(SkSVGLineJoin* join) {
+    static const struct {
+        SkSVGLineJoin::Type fType;
+        const char*         fName;
+    } gJoinInfo[] = {
+        { SkSVGLineJoin::Type::kMiter  , "miter"   },
+        { SkSVGLineJoin::Type::kRound  , "round"   },
+        { SkSVGLineJoin::Type::kBevel  , "bevel"   },
+        { SkSVGLineJoin::Type::kInherit, "inherit" },
+    };
+
+    bool parsedValue = false;
+    for (size_t i = 0; i < SK_ARRAY_COUNT(gJoinInfo); ++i) {
+        if (this->parseExpectedStringToken(gJoinInfo[i].fName)) {
+            *join = SkSVGLineJoin(gJoinInfo[i].fType);
+            parsedValue = true;
+            break;
+        }
+    }
+
+    return parsedValue && this->parseEOSToken();
+}
index c616113..637bf4a 100644 (file)
@@ -19,6 +19,9 @@ public:
     bool parseLength(SkSVGLength*);
     bool parseViewBox(SkSVGViewBoxType*);
     bool parseTransform(SkSVGTransformType*);
+    bool parsePaint(SkSVGPaint*);
+    bool parseLineCap(SkSVGLineCap*);
+    bool parseLineJoin(SkSVGLineJoin*);
 
 private:
     // Stack-only
index 2d1972b..6e4bc49 100644 (file)
@@ -25,13 +25,17 @@ namespace {
 
 bool SetPaintAttribute(const sk_sp<SkSVGNode>& node, SkSVGAttribute attr,
                        const char* stringValue) {
-    SkSVGColorType color;
+    SkSVGPaint paint;
     SkSVGAttributeParser parser(stringValue);
-    if (!parser.parseColor(&color)) {
-        return false;
+    if (!parser.parsePaint(&paint)) {
+        // Until we have paint server support, failing here will cause default/all-black rendering.
+        // It's better to just not draw for now.
+        paint = SkSVGPaint(SkSVGPaint::Type::kNone);
+
+        // return false;
     }
 
-    node->setAttribute(attr, SkSVGColorValue(color));
+    node->setAttribute(attr, SkSVGPaintValue(paint));
     return true;
 }
 
@@ -70,6 +74,18 @@ bool SetLengthAttribute(const sk_sp<SkSVGNode>& node, SkSVGAttribute attr,
     return true;
 }
 
+bool SetNumberAttribute(const sk_sp<SkSVGNode>& node, SkSVGAttribute attr,
+                        const char* stringValue) {
+    SkSVGNumberType number;
+    SkSVGAttributeParser parser(stringValue);
+    if (!parser.parseNumber(&number)) {
+        return false;
+    }
+
+    node->setAttribute(attr, SkSVGNumberValue(number));
+    return true;
+}
+
 bool SetViewBoxAttribute(const sk_sp<SkSVGNode>& node, SkSVGAttribute attr,
                          const char* stringValue) {
     SkSVGViewBoxType viewBox;
@@ -82,6 +98,30 @@ bool SetViewBoxAttribute(const sk_sp<SkSVGNode>& node, SkSVGAttribute attr,
     return true;
 }
 
+bool SetLineCapAttribute(const sk_sp<SkSVGNode>& node, SkSVGAttribute attr,
+                         const char* stringValue) {
+    SkSVGLineCap lineCap;
+    SkSVGAttributeParser parser(stringValue);
+    if (!parser.parseLineCap(&lineCap)) {
+        return false;
+    }
+
+    node->setAttribute(attr, SkSVGLineCapValue(lineCap));
+    return true;
+}
+
+bool SetLineJoinAttribute(const sk_sp<SkSVGNode>& node, SkSVGAttribute attr,
+                          const char* stringValue) {
+    SkSVGLineJoin lineJoin;
+    SkSVGAttributeParser parser(stringValue);
+    if (!parser.parseLineJoin(&lineJoin)) {
+        return false;
+    }
+
+    node->setAttribute(attr, SkSVGLineJoinValue(lineJoin));
+    return true;
+}
+
 SkString TrimmedString(const char* first, const char* last) {
     SkASSERT(first);
     SkASSERT(last);
@@ -160,18 +200,23 @@ struct AttrParseInfo {
 };
 
 SortedDictionaryEntry<AttrParseInfo> gAttributeParseInfo[] = {
-    { "d"        , { SkSVGAttribute::kD        , SetPathDataAttribute  }},
-    { "fill"     , { SkSVGAttribute::kFill     , SetPaintAttribute     }},
-    { "height"   , { SkSVGAttribute::kHeight   , SetLengthAttribute    }},
-    { "rx"       , { SkSVGAttribute::kRx       , SetLengthAttribute    }},
-    { "ry"       , { SkSVGAttribute::kRy       , SetLengthAttribute    }},
-    { "stroke"   , { SkSVGAttribute::kStroke   , SetPaintAttribute     }},
-    { "style"    , { SkSVGAttribute::kUnknown  , SetStyleAttributes    }},
-    { "transform", { SkSVGAttribute::kTransform, SetTransformAttribute }},
-    { "viewBox"  , { SkSVGAttribute::kViewBox  , SetViewBoxAttribute   }},
-    { "width"    , { SkSVGAttribute::kWidth    , SetLengthAttribute    }},
-    { "x"        , { SkSVGAttribute::kX        , SetLengthAttribute    }},
-    { "y"        , { SkSVGAttribute::kY        , SetLengthAttribute    }},
+    { "d"              , { SkSVGAttribute::kD             , SetPathDataAttribute  }},
+    { "fill"           , { SkSVGAttribute::kFill          , SetPaintAttribute     }},
+    { "fill-opacity"   , { SkSVGAttribute::kFillOpacity   , SetNumberAttribute    }},
+    { "height"         , { SkSVGAttribute::kHeight        , SetLengthAttribute    }},
+    { "rx"             , { SkSVGAttribute::kRx            , SetLengthAttribute    }},
+    { "ry"             , { SkSVGAttribute::kRy            , SetLengthAttribute    }},
+    { "stroke"         , { SkSVGAttribute::kStroke        , SetPaintAttribute     }},
+    { "stroke-linecap" , { SkSVGAttribute::kStrokeLineCap , SetLineCapAttribute   }},
+    { "stroke-linejoin", { SkSVGAttribute::kStrokeLineJoin, SetLineJoinAttribute  }},
+    { "stroke-opacity" , { SkSVGAttribute::kStrokeOpacity , SetNumberAttribute    }},
+    { "stroke-width"   , { SkSVGAttribute::kStrokeWidth   , SetLengthAttribute    }},
+    { "style"          , { SkSVGAttribute::kUnknown       , SetStyleAttributes    }},
+    { "transform"      , { SkSVGAttribute::kTransform     , SetTransformAttribute }},
+    { "viewBox"        , { SkSVGAttribute::kViewBox       , SetViewBoxAttribute   }},
+    { "width"          , { SkSVGAttribute::kWidth         , SetLengthAttribute    }},
+    { "x"              , { SkSVGAttribute::kX             , SetLengthAttribute    }},
+    { "y"              , { SkSVGAttribute::kY             , SetLengthAttribute    }},
 };
 
 SortedDictionaryEntry<sk_sp<SkSVGNode>(*)()> gTagFactories[] = {
index 34c6e17..d60c984 100644 (file)
@@ -25,7 +25,7 @@ void SkSVGNode::render(const SkSVGRenderContext& ctx) const {
 }
 
 bool SkSVGNode::onPrepareToRender(SkSVGRenderContext* ctx) const {
-    fPresentationAttributes.applyTo(ctx);
+    ctx->applyPresentationAttributes(fPresentationAttributes);
     return true;
 }
 
@@ -36,13 +36,40 @@ void SkSVGNode::setAttribute(SkSVGAttribute attr, const SkSVGValue& v) {
 void SkSVGNode::onSetAttribute(SkSVGAttribute attr, const SkSVGValue& v) {
     switch (attr) {
     case SkSVGAttribute::kFill:
-        if (const SkSVGColorValue* color = v.as<SkSVGColorValue>()) {
-            fPresentationAttributes.setFill(*color);
+        if (const SkSVGPaintValue* paint = v.as<SkSVGPaintValue>()) {
+            fPresentationAttributes.fFill.set(*paint);
+        }
+        break;
+    case SkSVGAttribute::kFillOpacity:
+        if (const SkSVGNumberValue* opacity = v.as<SkSVGNumberValue>()) {
+            fPresentationAttributes.fFillOpacity.set(
+                SkSVGNumberType(SkTPin<SkScalar>((*opacity)->value(), 0, 1)));
         }
         break;
     case SkSVGAttribute::kStroke:
-        if (const SkSVGColorValue* color = v.as<SkSVGColorValue>()) {
-            fPresentationAttributes.setStroke(*color);
+        if (const SkSVGPaintValue* paint = v.as<SkSVGPaintValue>()) {
+            fPresentationAttributes.fStroke.set(*paint);
+        }
+        break;
+    case SkSVGAttribute::kStrokeOpacity:
+        if (const SkSVGNumberValue* opacity = v.as<SkSVGNumberValue>()) {
+            fPresentationAttributes.fStrokeOpacity.set(
+                SkSVGNumberType(SkTPin<SkScalar>((*opacity)->value(), 0, 1)));
+        }
+        break;
+    case SkSVGAttribute::kStrokeLineCap:
+        if (const SkSVGLineCapValue* lineCap = v.as<SkSVGLineCapValue>()) {
+            fPresentationAttributes.fStrokeLineCap.set(*lineCap);
+        }
+        break;
+    case SkSVGAttribute::kStrokeLineJoin:
+        if (const SkSVGLineJoinValue* lineJoin = v.as<SkSVGLineJoinValue>()) {
+            fPresentationAttributes.fStrokeLineJoin.set(*lineJoin);
+        }
+        break;
+    case SkSVGAttribute::kStrokeWidth:
+        if (const SkSVGLengthValue* strokeWidth = v.as<SkSVGLengthValue>()) {
+            fPresentationAttributes.fStrokeWidth.set(*strokeWidth);
         }
         break;
     default:
index e902d4e..ee92c47 100644 (file)
@@ -6,6 +6,7 @@
  */
 
 #include "SkCanvas.h"
+#include "SkSVGAttribute.h"
 #include "SkSVGRenderContext.h"
 #include "SkSVGTypes.h"
 
@@ -30,17 +31,15 @@ SkScalar length_size_for_type(const SkSize& viewport, SkSVGLengthContext::Length
 SkScalar SkSVGLengthContext::resolve(const SkSVGLength& l, LengthType t) const {
     switch (l.unit()) {
     case SkSVGLength::Unit::kNumber:
+        // Fall through.
+    case SkSVGLength::Unit::kPX:
         return l.value();
-        break;
     case SkSVGLength::Unit::kPercentage:
         return l.value() * length_size_for_type(fViewport, t) / 100;
-        break;
     default:
         SkDebugf("unsupported unit type: <%d>\n", l.unit());
-        break;
+        return 0;
     }
-
-    return 0;
 }
 
 SkRect SkSVGLengthContext::resolveRect(const SkSVGLength& x, const SkSVGLength& y,
@@ -52,55 +51,135 @@ SkRect SkSVGLengthContext::resolveRect(const SkSVGLength& x, const SkSVGLength&
         this->resolve(h, SkSVGLengthContext::LengthType::kVertical));
 }
 
-SkSVGPresentationContext::SkSVGPresentationContext() {}
+namespace {
 
-SkSVGPresentationContext::SkSVGPresentationContext(const SkSVGPresentationContext& o) {
-    this->initFrom(o);
+SkPaint::Cap toSkCap(const SkSVGLineCap& cap) {
+    switch (cap.type()) {
+    case SkSVGLineCap::Type::kButt:
+        return SkPaint::kButt_Cap;
+    case SkSVGLineCap::Type::kRound:
+        return SkPaint::kRound_Cap;
+    case SkSVGLineCap::Type::kSquare:
+        return SkPaint::kSquare_Cap;
+    default:
+        SkASSERT(false);
+        return SkPaint::kButt_Cap;
+    }
 }
 
-SkSVGPresentationContext& SkSVGPresentationContext::operator=(const SkSVGPresentationContext& o) {
-    this->initFrom(o);
-    return *this;
+SkPaint::Join toSkJoin(const SkSVGLineJoin& join) {
+    switch (join.type()) {
+    case SkSVGLineJoin::Type::kMiter:
+        return SkPaint::kMiter_Join;
+    case SkSVGLineJoin::Type::kRound:
+        return SkPaint::kRound_Join;
+    case SkSVGLineJoin::Type::kBevel:
+        return SkPaint::kBevel_Join;
+    default:
+        SkASSERT(false);
+        return SkPaint::kMiter_Join;
+    }
 }
 
-void SkSVGPresentationContext::initFrom(const SkSVGPresentationContext& other) {
-    if (other.fFill.isValid()) {
-        fFill.set(*other.fFill.get());
-    } else {
-        fFill.reset();
+void applySvgPaint(const SkSVGPaint& svgPaint, SkPaint* p) {
+    switch (svgPaint.type()) {
+    case SkSVGPaint::Type::kColor:
+        p->setColor(SkColorSetA(svgPaint.color(), p->getAlpha()));
+        break;
+    case SkSVGPaint::Type::kCurrentColor:
+        SkDebugf("unimplemented 'currentColor' paint type");
+        // Fall through.
+    case SkSVGPaint::Type::kNone:
+        // Fall through.
+    case SkSVGPaint::Type::kInherit:
+        break;
     }
+}
 
-    if (other.fStroke.isValid()) {
-        fStroke.set(*other.fStroke.get());
-    } else {
-        fStroke.reset();
-    }
+// Commit the selected attribute to the paint cache.
+template <SkSVGAttribute>
+void commitToPaint(const SkSVGPresentationAttributes&,
+                   const SkSVGLengthContext&,
+                   SkSVGPresentationContext*);
+
+template <>
+void commitToPaint<SkSVGAttribute::kFill>(const SkSVGPresentationAttributes& attrs,
+                                          const SkSVGLengthContext&,
+                                          SkSVGPresentationContext* pctx) {
+    applySvgPaint(*attrs.fFill.get(), &pctx->fFillPaint);
 }
 
-SkPaint& SkSVGPresentationContext::ensureFill() {
-    if (!fFill.isValid()) {
-        fFill.init();
-        fFill.get()->setStyle(SkPaint::kFill_Style);
-        fFill.get()->setAntiAlias(true);
+template <>
+void commitToPaint<SkSVGAttribute::kStroke>(const SkSVGPresentationAttributes& attrs,
+                                            const SkSVGLengthContext&,
+                                            SkSVGPresentationContext* pctx) {
+    applySvgPaint(*attrs.fStroke.get(), &pctx->fStrokePaint);
+}
+
+template <>
+void commitToPaint<SkSVGAttribute::kFillOpacity>(const SkSVGPresentationAttributes& attrs,
+                                                 const SkSVGLengthContext&,
+                                                 SkSVGPresentationContext* pctx) {
+    pctx->fFillPaint.setAlpha(static_cast<uint8_t>(*attrs.fFillOpacity.get() * 255));
+}
+
+template <>
+void commitToPaint<SkSVGAttribute::kStrokeLineCap>(const SkSVGPresentationAttributes& attrs,
+                                                   const SkSVGLengthContext&,
+                                                   SkSVGPresentationContext* pctx) {
+    const auto& cap = *attrs.fStrokeLineCap.get();
+    if (cap.type() != SkSVGLineCap::Type::kInherit) {
+        pctx->fStrokePaint.setStrokeCap(toSkCap(cap));
     }
-    return *fFill.get();
 }
 
-SkPaint& SkSVGPresentationContext::ensureStroke() {
-    if (!fStroke.isValid()) {
-        fStroke.init();
-        fStroke.get()->setStyle(SkPaint::kStroke_Style);
-        fStroke.get()->setAntiAlias(true);
+template <>
+void commitToPaint<SkSVGAttribute::kStrokeLineJoin>(const SkSVGPresentationAttributes& attrs,
+                                                    const SkSVGLengthContext&,
+                                                    SkSVGPresentationContext* pctx) {
+    const auto& join = *attrs.fStrokeLineJoin.get();
+    if (join.type() != SkSVGLineJoin::Type::kInherit) {
+        pctx->fStrokePaint.setStrokeJoin(toSkJoin(join));
     }
-    return *fStroke.get();
 }
 
-void SkSVGPresentationContext::setFillColor(SkColor color) {
-    this->ensureFill().setColor(color);
+template <>
+void commitToPaint<SkSVGAttribute::kStrokeOpacity>(const SkSVGPresentationAttributes& attrs,
+                                                   const SkSVGLengthContext&,
+                                                   SkSVGPresentationContext* pctx) {
+    pctx->fStrokePaint.setAlpha(static_cast<uint8_t>(*attrs.fStrokeOpacity.get() * 255));
 }
 
-void SkSVGPresentationContext::setStrokeColor(SkColor color) {
-    this->ensureStroke().setColor(color);
+template <>
+void commitToPaint<SkSVGAttribute::kStrokeWidth>(const SkSVGPresentationAttributes& attrs,
+                                                 const SkSVGLengthContext& lctx,
+                                                 SkSVGPresentationContext* pctx) {
+    auto strokeWidth = lctx.resolve(*attrs.fStrokeWidth.get(),
+                                    SkSVGLengthContext::LengthType::kOther);
+    pctx->fStrokePaint.setStrokeWidth(strokeWidth);
+}
+
+} // anonymous ns
+
+SkSVGPresentationContext::SkSVGPresentationContext()
+    : fInherited(SkSVGPresentationAttributes::MakeInitial()) {
+
+    fFillPaint.setStyle(SkPaint::kFill_Style);
+    fStrokePaint.setStyle(SkPaint::kStroke_Style);
+
+    // TODO: drive AA off presentation attrs also (shape-rendering?)
+    fFillPaint.setAntiAlias(true);
+    fStrokePaint.setAntiAlias(true);
+
+    // Commit initial values to the paint cache.
+    SkSVGLengthContext dummy(SkSize::Make(0, 0));
+    commitToPaint<SkSVGAttribute::kFill>(fInherited, dummy, this);
+    commitToPaint<SkSVGAttribute::kFillOpacity>(fInherited, dummy, this);
+    commitToPaint<SkSVGAttribute::kStroke>(fInherited, dummy, this);
+    commitToPaint<SkSVGAttribute::kStrokeLineCap>(fInherited, dummy, this);
+    commitToPaint<SkSVGAttribute::kStrokeLineJoin>(fInherited, dummy, this);
+    commitToPaint<SkSVGAttribute::kStrokeOpacity>(fInherited, dummy, this);
+    commitToPaint<SkSVGAttribute::kStrokeWidth>(fInherited, dummy, this);
 }
 
 SkSVGRenderContext::SkSVGRenderContext(SkCanvas* canvas,
@@ -112,10 +191,47 @@ SkSVGRenderContext::SkSVGRenderContext(SkCanvas* canvas,
     , fCanvasSaveCount(canvas->getSaveCount()) {}
 
 SkSVGRenderContext::SkSVGRenderContext(const SkSVGRenderContext& other)
-    : SkSVGRenderContext(other.canvas(),
-                         other.lengthContext(),
-                         other.presentationContext()) {}
+    : SkSVGRenderContext(other.fCanvas,
+                         *other.fLengthContext,
+                         *other.fPresentationContext) {}
 
 SkSVGRenderContext::~SkSVGRenderContext() {
     fCanvas->restoreToCount(fCanvasSaveCount);
 }
+
+void SkSVGRenderContext::applyPresentationAttributes(const SkSVGPresentationAttributes& attrs) {
+
+#define ApplyLazyInheritedAttribute(ATTR)                                               \
+    do {                                                                                \
+        /* All attributes should be defined on the inherited context. */                \
+        SkASSERT(fPresentationContext->fInherited.f ## ATTR.isValid());                 \
+        const auto* value = attrs.f ## ATTR.getMaybeNull();                             \
+        if (value && *value != *fPresentationContext->fInherited.f ## ATTR.get()) {     \
+            /* Update the local attribute value */                                      \
+            fPresentationContext.writable()->fInherited.f ## ATTR.set(*value);          \
+            /* Update the cached paints */                                              \
+            commitToPaint<SkSVGAttribute::k ## ATTR>(attrs, *fLengthContext,            \
+                                                     fPresentationContext.writable());  \
+        }                                                                               \
+    } while (false)
+
+    ApplyLazyInheritedAttribute(Fill);
+    ApplyLazyInheritedAttribute(FillOpacity);
+    ApplyLazyInheritedAttribute(Stroke);
+    ApplyLazyInheritedAttribute(StrokeLineCap);
+    ApplyLazyInheritedAttribute(StrokeLineJoin);
+    ApplyLazyInheritedAttribute(StrokeOpacity);
+    ApplyLazyInheritedAttribute(StrokeWidth);
+
+#undef ApplyLazyInheritedAttribute
+}
+
+const SkPaint* SkSVGRenderContext::fillPaint() const {
+    const SkSVGPaint::Type paintType = fPresentationContext->fInherited.fFill.get()->type();
+    return paintType != SkSVGPaint::Type::kNone ? &fPresentationContext->fFillPaint : nullptr;
+}
+
+const SkPaint* SkSVGRenderContext::strokePaint() const {
+    const SkSVGPaint::Type paintType = fPresentationContext->fInherited.fStroke.get()->type();
+    return paintType != SkSVGPaint::Type::kNone ? &fPresentationContext->fStrokePaint : nullptr;
+}
index 47886d7..e6df725 100644 (file)
@@ -11,6 +11,7 @@
 #include "SkPaint.h"
 #include "SkRect.h"
 #include "SkSize.h"
+#include "SkSVGAttribute.h"
 #include "SkTLazy.h"
 #include "SkTypes.h"
 
@@ -38,27 +39,17 @@ private:
     SkSize fViewport;
 };
 
-class SkSVGPresentationContext {
-public:
+struct SkSVGPresentationContext {
     SkSVGPresentationContext();
-    SkSVGPresentationContext(const SkSVGPresentationContext&);
-    SkSVGPresentationContext& operator=(const SkSVGPresentationContext&);
-
-    const SkPaint* fillPaint() const { return fFill.getMaybeNull(); }
-    const SkPaint* strokePaint() const { return fStroke.getMaybeNull(); }
-
-    void setFillColor(SkColor);
-    void setStrokeColor(SkColor);
-
-private:
-    void initFrom(const SkSVGPresentationContext&);
+    SkSVGPresentationContext(const SkSVGPresentationContext&)            = default;
+    SkSVGPresentationContext& operator=(const SkSVGPresentationContext&) = default;
 
-    SkPaint& ensureFill();
-    SkPaint& ensureStroke();
+    // Inherited presentation attributes, computed for the current node.
+    SkSVGPresentationAttributes fInherited;
 
-    // TODO: convert to regular SkPaints and track explicit attribute values instead.
-    SkTLazy<SkPaint> fFill;
-    SkTLazy<SkPaint> fStroke;
+    // Cached paints, reflecting the current presentation attributes.
+    SkPaint fFillPaint;
+    SkPaint fStrokePaint;
 };
 
 class SkSVGRenderContext {
@@ -70,13 +61,13 @@ public:
     const SkSVGLengthContext& lengthContext() const { return *fLengthContext; }
     SkSVGLengthContext* writableLengthContext() { return fLengthContext.writable(); }
 
-    const SkSVGPresentationContext& presentationContext() const { return *fPresentationContext; }
-    SkSVGPresentationContext* writablePresentationContext() {
-        return fPresentationContext.writable();
-    }
-
     SkCanvas* canvas() const { return fCanvas; }
 
+    void applyPresentationAttributes(const SkSVGPresentationAttributes&);
+
+    const SkPaint* fillPaint() const;
+    const SkPaint* strokePaint() const;
+
 private:
     // Stack-only
     void* operator new(size_t)                               = delete;
index 516d4a2..38af4c9 100644 (file)
@@ -12,11 +12,11 @@ SkSVGShape::SkSVGShape(SkSVGTag t) : INHERITED(t) {}
 
 void SkSVGShape::onRender(const SkSVGRenderContext& ctx) const {
     // TODO: this approach forces duplicate geometry resolution in onDraw(); refactor to avoid.
-    if (const SkPaint* fillPaint = ctx.presentationContext().fillPaint()) {
+    if (const SkPaint* fillPaint = ctx.fillPaint()) {
         this->onDraw(ctx.canvas(), ctx.lengthContext(), *fillPaint);
     }
 
-    if (const SkPaint* strokePaint = ctx.presentationContext().strokePaint()) {
+    if (const SkPaint* strokePaint = ctx.strokePaint()) {
         this->onDraw(ctx.canvas(), ctx.lengthContext(), *strokePaint);
     }
 }
index b2e2db1..c8330e1 100644 (file)
@@ -23,6 +23,13 @@ public:
     SkSVGPrimitiveTypeWrapper(const SkSVGPrimitiveTypeWrapper&)            = default;
     SkSVGPrimitiveTypeWrapper& operator=(const SkSVGPrimitiveTypeWrapper&) = default;
 
+    bool operator==(const SkSVGPrimitiveTypeWrapper<T>& other) const {
+        return fValue == other.fValue;
+    }
+    bool operator!=(const SkSVGPrimitiveTypeWrapper<T>& other) const {
+        return !(*this == other);
+    }
+
     const T& value() const { return fValue; }
     operator const T&() const { return fValue; }
 
@@ -57,6 +64,11 @@ public:
     SkSVGLength(const SkSVGLength&)            = default;
     SkSVGLength& operator=(const SkSVGLength&) = default;
 
+    bool operator==(const SkSVGLength& other) const {
+        return fUnit == other.fUnit && fValue == other.fValue;
+    }
+    bool operator!=(const SkSVGLength& other) const { return !(*this == other); }
+
     const SkScalar& value() const { return fValue; }
     const Unit&     unit()  const { return fUnit;  }
 
@@ -65,4 +77,82 @@ private:
     Unit     fUnit;
 };
 
+class SkSVGPaint {
+public:
+    enum class Type {
+        kNone,
+        kCurrentColor,
+        kColor,
+        kInherit,
+    };
+
+    constexpr SkSVGPaint() : fType(Type::kInherit), fColor(SK_ColorBLACK) {}
+    explicit constexpr SkSVGPaint(Type t) : fType(t), fColor(SK_ColorBLACK) {}
+    explicit constexpr SkSVGPaint(const SkSVGColorType& c) : fType(Type::kColor), fColor(c) {}
+
+    SkSVGPaint(const SkSVGPaint&)            = default;
+    SkSVGPaint& operator=(const SkSVGPaint&) = default;
+
+    bool operator==(const SkSVGPaint& other) const {
+        return fType == other.fType && fColor == other.fColor;
+    }
+    bool operator!=(const SkSVGPaint& other) const { return !(*this == other); }
+
+    Type type() const { return fType; }
+    const SkSVGColorType& color() const { SkASSERT(fType == Type::kColor); return fColor; }
+
+private:
+    Type fType;
+
+    SkSVGColorType fColor;
+};
+
+class SkSVGLineCap {
+public:
+    enum class Type {
+        kButt,
+        kRound,
+        kSquare,
+        kInherit,
+    };
+
+    constexpr SkSVGLineCap() : fType(Type::kInherit) {}
+    constexpr explicit SkSVGLineCap(Type t) : fType(t) {}
+
+    SkSVGLineCap(const SkSVGLineCap&)            = default;
+    SkSVGLineCap& operator=(const SkSVGLineCap&) = default;
+
+    bool operator==(const SkSVGLineCap& other) const { return fType == other.fType; }
+    bool operator!=(const SkSVGLineCap& other) const { return !(*this == other); }
+
+    Type type() const { return fType; }
+
+private:
+    Type fType;
+};
+
+class SkSVGLineJoin {
+public:
+    enum class Type {
+        kMiter,
+        kRound,
+        kBevel,
+        kInherit,
+    };
+
+    constexpr SkSVGLineJoin() : fType(Type::kInherit) {}
+    constexpr explicit SkSVGLineJoin(Type t) : fType(t) {}
+
+    SkSVGLineJoin(const SkSVGLineJoin&)            = default;
+    SkSVGLineJoin& operator=(const SkSVGLineJoin&) = default;
+
+    bool operator==(const SkSVGLineJoin& other) const { return fType == other.fType; }
+    bool operator!=(const SkSVGLineJoin& other) const { return !(*this == other); }
+
+    Type type() const { return fType; }
+
+private:
+    Type fType;
+};
+
 #endif // SkSVGTypes_DEFINED
index 00715e2..583e602 100644 (file)
@@ -19,6 +19,10 @@ public:
     enum class Type {
         kColor,
         kLength,
+        kLineCap,
+        kLineJoin,
+        kNumber,
+        kPaint,
         kPath,
         kTransform,
         kViewBox,
@@ -50,6 +54,7 @@ public:
         , fWrappedValue(v) { }
 
     operator const T&() const { return fWrappedValue; }
+    const T* operator->() const { return &fWrappedValue; }
 
 private:
     // Stack-only
@@ -66,5 +71,9 @@ using SkSVGLengthValue    = SkSVGWrapperValue<SkSVGLength       , SkSVGValue::Ty
 using SkSVGPathValue      = SkSVGWrapperValue<SkPath            , SkSVGValue::Type::kPath     >;
 using SkSVGTransformValue = SkSVGWrapperValue<SkSVGTransformType, SkSVGValue::Type::kTransform>;
 using SkSVGViewBoxValue   = SkSVGWrapperValue<SkSVGViewBoxType  , SkSVGValue::Type::kViewBox  >;
+using SkSVGPaintValue     = SkSVGWrapperValue<SkSVGPaint        , SkSVGValue::Type::kPaint    >;
+using SkSVGLineCapValue   = SkSVGWrapperValue<SkSVGLineCap      , SkSVGValue::Type::kLineCap  >;
+using SkSVGLineJoinValue  = SkSVGWrapperValue<SkSVGLineJoin     , SkSVGValue::Type::kLineJoin >;
+using SkSVGNumberValue    = SkSVGWrapperValue<SkSVGNumberType   , SkSVGValue::Type::kNumber   >;
 
 #endif // SkSVGValue_DEFINED