[svg] Convert stop-color and stop-opacity to presentation attrs

These are somewhat the first presentation attributes of their kind,
in that they are non-inherited but also not applied via canvas layers.

Implementation-wise the main difference is that these attributes are
not propagated through the fInherited field of the render context's
presentation attribute list.

Change-Id: I0909507b0ecbd21732b3f80c46a343f5a0a9bf7a
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/340661
Reviewed-by: Florin Malita <fmalita@chromium.org>
Commit-Queue: Tyler Denniston <tdenniston@google.com>
diff --git a/modules/svg/include/SkSVGAttribute.h b/modules/svg/include/SkSVGAttribute.h
index 7bf1a63..f75b383 100644
--- a/modules/svg/include/SkSVGAttribute.h
+++ b/modules/svg/include/SkSVGAttribute.h
@@ -43,8 +43,6 @@
     kRx, // <ellipse>,<rect>: horizontal (corner) radius
     kRy, // <ellipse>,<rect>: vertical (corner) radius
     kSpreadMethod,
-    kStopColor,
-    kStopOpacity,
     kStroke,
     kStrokeDashArray,
     kStrokeDashOffset,
@@ -98,12 +96,12 @@
     SkSVGProperty<SkSVGFontWeight, true> fFontWeight;
     SkSVGProperty<SkSVGTextAnchor, true> fTextAnchor;
 
-    // TODO(tdenniston): add SkSVGStopColor
-
     // uninherited
     SkSVGProperty<SkSVGNumberType, false> fOpacity;
     SkSVGProperty<SkSVGClip      , false> fClipPath;
     SkSVGProperty<SkSVGFilterType, false> fFilter;
+    SkSVGProperty<SkSVGColor     , false> fStopColor;
+    SkSVGProperty<SkSVGNumberType, false> fStopOpacity;
 };
 
 #endif // SkSVGAttribute_DEFINED
diff --git a/modules/svg/include/SkSVGAttributeParser.h b/modules/svg/include/SkSVGAttributeParser.h
index 72114e6..41f772f 100644
--- a/modules/svg/include/SkSVGAttributeParser.h
+++ b/modules/svg/include/SkSVGAttributeParser.h
@@ -19,7 +19,6 @@
     bool parseInteger(SkSVGIntegerType*);
     bool parseViewBox(SkSVGViewBoxType*);
     bool parsePoints(SkSVGPointsType*);
-    bool parseStopColor(SkSVGStopColor*);
     bool parsePreserveAspectRatio(SkSVGPreserveAspectRatio*);
 
     // TODO: Migrate all parse*() functions to this style (and delete the old version)
diff --git a/modules/svg/include/SkSVGNode.h b/modules/svg/include/SkSVGNode.h
index 04d8bd4..9c7ce16 100644
--- a/modules/svg/include/SkSVGNode.h
+++ b/modules/svg/include/SkSVGNode.h
@@ -121,6 +121,8 @@
     SVG_PRES_ATTR(ClipPath        , SkSVGClip      , false)
     SVG_PRES_ATTR(Filter          , SkSVGFilterType, false)
     SVG_PRES_ATTR(Opacity         , SkSVGNumberType, false)
+    SVG_PRES_ATTR(StopColor       , SkSVGColor     , false)
+    SVG_PRES_ATTR(StopOpacity     , SkSVGNumberType, false)
 
 protected:
     SkSVGNode(SkSVGTag);
diff --git a/modules/svg/include/SkSVGStop.h b/modules/svg/include/SkSVGStop.h
index a558f57..8b5f950 100644
--- a/modules/svg/include/SkSVGStop.h
+++ b/modules/svg/include/SkSVGStop.h
@@ -21,12 +21,8 @@
     }
 
     const SkSVGLength& offset() const { return fOffset; }
-    const SkSVGStopColor& stopColor() const { return fStopColor; }
-    const SkSVGNumberType& stopOpacity() const { return fStopOpacity; }
 
     void setOffset(const SkSVGLength&);
-    void setStopColor(const SkSVGStopColor&);
-    void setStopOpacity(const SkSVGNumberType&);
 
 protected:
     void onSetAttribute(SkSVGAttribute, const SkSVGValue&) override;
@@ -34,9 +30,7 @@
 private:
     SkSVGStop();
 
-    SkSVGLength          fOffset = SkSVGLength(0  , SkSVGLength::Unit::kPercentage);
-    SkSVGStopColor    fStopColor = SkSVGStopColor(SK_ColorBLACK);
-    SkSVGNumberType fStopOpacity = SkSVGNumberType(1);
+    SkSVGLength fOffset = SkSVGLength(0, SkSVGLength::Unit::kPercentage);
 
     using INHERITED = SkSVGHiddenContainer;
 };
diff --git a/modules/svg/src/SkSVGAttribute.cpp b/modules/svg/src/SkSVGAttribute.cpp
index 6a9129d..d07aa91 100644
--- a/modules/svg/src/SkSVGAttribute.cpp
+++ b/modules/svg/src/SkSVGAttribute.cpp
@@ -34,5 +34,8 @@
     result.fFontWeight.init(SkSVGFontWeight::Type::kNormal);
     result.fTextAnchor.init(SkSVGTextAnchor::Type::kStart);
 
+    result.fStopColor.set(SkSVGColor(SK_ColorBLACK));
+    result.fStopOpacity.set(SkSVGNumberType(1));
+
     return result;
 }
diff --git a/modules/svg/src/SkSVGAttributeParser.cpp b/modules/svg/src/SkSVGAttributeParser.cpp
index d34f153..5bfea3b 100644
--- a/modules/svg/src/SkSVGAttributeParser.cpp
+++ b/modules/svg/src/SkSVGAttributeParser.cpp
@@ -604,23 +604,6 @@
     return parsedValue && this->parseEOSToken();
 }
 
-// https://www.w3.org/TR/SVG11/pservers.html#StopElement
-bool SkSVGAttributeParser::parseStopColor(SkSVGStopColor* stopColor) {
-    SkSVGColorType c;
-    bool parsedValue = false;
-    if (this->parse(&c)) {
-        *stopColor = SkSVGStopColor(c);
-        parsedValue = true;
-    } else if (this->parseExpectedStringToken("currentColor")) {
-        *stopColor = SkSVGStopColor(SkSVGStopColor::Type::kCurrentColor);
-        parsedValue = true;
-    } else if (this->parseExpectedStringToken("inherit")) {
-        *stopColor = SkSVGStopColor(SkSVGStopColor::Type::kInherit);
-        parsedValue = true;
-    }
-    return parsedValue && this->parseEOSToken();
-}
-
 // https://www.w3.org/TR/SVG11/coords.html#ObjectBoundingBoxUnits
 template <>
 bool SkSVGAttributeParser::parse(SkSVGObjectBoundingBoxUnits* objectBoundingBoxUnits) {
diff --git a/modules/svg/src/SkSVGDOM.cpp b/modules/svg/src/SkSVGDOM.cpp
index 356de27..643230e 100644
--- a/modules/svg/src/SkSVGDOM.cpp
+++ b/modules/svg/src/SkSVGDOM.cpp
@@ -93,17 +93,6 @@
     return true;
 }
 
-bool SetNumberAttribute(const sk_sp<SkSVGNode>& node, SkSVGAttribute attr,
-                        const char* stringValue) {
-    auto parseResult = SkSVGAttributeParser::parse<SkSVGNumberType>(stringValue);
-    if (!parseResult.isValid()) {
-        return false;
-    }
-
-    node->setAttribute(attr, SkSVGNumberValue(*parseResult));
-    return true;
-}
-
 bool SetViewBoxAttribute(const sk_sp<SkSVGNode>& node, SkSVGAttribute attr,
                          const char* stringValue) {
     SkSVGViewBoxType viewBox;
@@ -116,18 +105,6 @@
     return true;
 }
 
-bool SetStopColorAttribute(const sk_sp<SkSVGNode>& node, SkSVGAttribute attr,
-                           const char* stringValue) {
-    SkSVGStopColor stopColor;
-    SkSVGAttributeParser parser(stringValue);
-    if (!parser.parseStopColor(&stopColor)) {
-        return false;
-    }
-
-    node->setAttribute(attr, SkSVGStopColorValue(stopColor));
-    return true;
-}
-
 bool SetObjectBoundingBoxUnitsAttribute(const sk_sp<SkSVGNode>& node,
                                         SkSVGAttribute attr,
                                         const char* stringValue) {
@@ -259,8 +236,6 @@
     { "r"                  , { SkSVGAttribute::kR                , SetLengthAttribute       }},
     { "rx"                 , { SkSVGAttribute::kRx               , SetLengthAttribute       }},
     { "ry"                 , { SkSVGAttribute::kRy               , SetLengthAttribute       }},
-    { "stop-color"         , { SkSVGAttribute::kStopColor        , SetStopColorAttribute    }},
-    { "stop-opacity"       , { SkSVGAttribute::kStopOpacity      , SetNumberAttribute       }},
     { "style"              , { SkSVGAttribute::kUnknown          , SetStyleAttributes       }},
     { "text"               , { SkSVGAttribute::kText             , SetStringAttribute       }},
     { "transform"          , { SkSVGAttribute::kTransform        , SetTransformAttribute    }},
diff --git a/modules/svg/src/SkSVGGradient.cpp b/modules/svg/src/SkSVGGradient.cpp
index 2448d5b..cc5b297 100644
--- a/modules/svg/src/SkSVGGradient.cpp
+++ b/modules/svg/src/SkSVGGradient.cpp
@@ -53,25 +53,28 @@
 
 SkColor SkSVGGradient::resolveStopColor(const SkSVGRenderContext& ctx,
                                         const SkSVGStop& stop) const {
-    const SkSVGStopColor& stopColor = stop.stopColor();
+    const auto& stopColor = stop.getStopColor();
+    const auto& stopOpacity = stop.getStopOpacity();
+    // Uninherited presentation attrs should have a concrete value at this point.
+    if (!stopColor.isValue() || !stopOpacity.isValue()) {
+        SkDebugf("unhandled: stop-color or stop-opacity has no value\n");
+        return SK_ColorBLACK;
+    }
+
     SkColor color;
-    switch (stopColor.type()) {
-        case SkSVGStopColor::Type::kColor:
-            color = stopColor.color();
+    switch (stopColor->type()) {
+        case SkSVGColor::Type::kColor:
+            color = stopColor->color();
             break;
-        case SkSVGStopColor::Type::kCurrentColor:
+        case SkSVGColor::Type::kCurrentColor:
             color = *ctx.presentationContext().fInherited.fColor;
             break;
-        case SkSVGStopColor::Type::kICCColor:
+        case SkSVGColor::Type::kICCColor:
             SkDebugf("unimplemented 'icccolor' stop-color type\n");
             color = SK_ColorBLACK;
             break;
-        case SkSVGStopColor::Type::kInherit:
-            SkDebugf("unimplemented 'inherit' stop-color type\n");
-            color = SK_ColorBLACK;
-            break;
     }
-    return SkColorSetA(color, SkScalarRoundToInt(stop.stopOpacity() * 255));
+    return SkColorSetA(color, SkScalarRoundToInt(*stopOpacity * 255));
 }
 
 bool SkSVGGradient::onAsPaint(const SkSVGRenderContext& ctx, SkPaint* paint) const {
diff --git a/modules/svg/src/SkSVGNode.cpp b/modules/svg/src/SkSVGNode.cpp
index a8f39ad..acf8f8f 100644
--- a/modules/svg/src/SkSVGNode.cpp
+++ b/modules/svg/src/SkSVGNode.cpp
@@ -14,7 +14,11 @@
 #include "modules/svg/include/SkSVGValue.h"
 #include "src/core/SkTLazy.h"
 
-SkSVGNode::SkSVGNode(SkSVGTag t) : fTag(t) { }
+SkSVGNode::SkSVGNode(SkSVGTag t) : fTag(t) {
+    // Uninherited presentation attributes need a non-null default value.
+    fPresentationAttributes.fStopColor.set(SkSVGColor(SK_ColorBLACK));
+    fPresentationAttributes.fStopOpacity.set(SkSVGNumberType(1.0f));
+}
 
 SkSVGNode::~SkSVGNode() { }
 
@@ -94,6 +98,8 @@
            || PARSE_AND_SET("font-style"       , FontStyle)
            || PARSE_AND_SET("font-weight"      , FontWeight)
            || PARSE_AND_SET("opacity"          , Opacity)
+           || PARSE_AND_SET("stop-color"       , StopColor)
+           || PARSE_AND_SET("stop-opacity"     , StopOpacity)
            || PARSE_AND_SET("stroke"           , Stroke)
            || PARSE_AND_SET("stroke-dasharray" , StrokeDashArray)
            || PARSE_AND_SET("stroke-dashoffset", StrokeDashOffset)
diff --git a/modules/svg/src/SkSVGRenderContext.cpp b/modules/svg/src/SkSVGRenderContext.cpp
index 7f7be49..6925a4d 100644
--- a/modules/svg/src/SkSVGRenderContext.cpp
+++ b/modules/svg/src/SkSVGRenderContext.cpp
@@ -455,6 +455,12 @@
     if (attrs.fFilter.isValue()) {
         this->applyFilter(*attrs.fFilter);
     }
+
+    // Remaining uninherited presentation attributes are accessed as SkSVGNode fields, not via
+    // the render context.
+    // TODO: resolve these in a pre-render styling pass and assert here that they are values.
+    // - stop-color
+    // - stop-opacity
 }
 
 void SkSVGRenderContext::applyOpacity(SkScalar opacity, uint32_t flags) {
diff --git a/modules/svg/src/SkSVGStop.cpp b/modules/svg/src/SkSVGStop.cpp
index edf39bc..03b80cc 100644
--- a/modules/svg/src/SkSVGStop.cpp
+++ b/modules/svg/src/SkSVGStop.cpp
@@ -16,14 +16,6 @@
     fOffset = offset;
 }
 
-void SkSVGStop::setStopColor(const SkSVGStopColor& color) {
-    fStopColor = color;
-}
-
-void SkSVGStop::setStopOpacity(const SkSVGNumberType& opacity) {
-    fStopOpacity = SkTPin<SkScalar>(opacity, 0, 1);
-}
-
 void SkSVGStop::onSetAttribute(SkSVGAttribute attr, const SkSVGValue& v) {
     switch (attr) {
     case SkSVGAttribute::kOffset:
@@ -31,16 +23,6 @@
             this->setOffset(*offset);
         }
         break;
-    case SkSVGAttribute::kStopColor:
-        if (const auto* color = v.as<SkSVGStopColorValue>()) {
-            this->setStopColor(*color);
-        }
-        break;
-    case SkSVGAttribute::kStopOpacity:
-        if (const auto* opacity = v.as<SkSVGNumberValue>()) {
-            this->setStopOpacity(*opacity);
-        }
-        break;
     default:
         this->INHERITED::onSetAttribute(attr, v);
     }