[svg] Add gradientUnits attribute, value, and parsing

Specifying gradientUnits will allow gradient coordinates to be specified
relative to object bounding boxes, as seen in test 'coords-units-01-b'.

Not yet used with this CL.

Bug: skia:10842
Change-Id: I6038cf3995a94c7e3a7ac73ad8305872353a403c
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/328977
Reviewed-by: Florin Malita <fmalita@chromium.org>
Auto-Submit: Tyler Denniston <tdenniston@google.com>
Commit-Queue: Tyler Denniston <tdenniston@google.com>
diff --git a/modules/svg/include/SkSVGAttribute.h b/modules/svg/include/SkSVGAttribute.h
index cd64e91..bd8204a 100644
--- a/modules/svg/include/SkSVGAttribute.h
+++ b/modules/svg/include/SkSVGAttribute.h
@@ -29,6 +29,7 @@
     kFontWeight,
     kFx, // <radialGradient>: focal point x position
     kFy, // <radialGradient>: focal point y position
+    kGradientUnits,
     kGradientTransform,
     kHeight,
     kHref,
diff --git a/modules/svg/include/SkSVGAttributeParser.h b/modules/svg/include/SkSVGAttributeParser.h
index 1ac9aac..1876dea 100644
--- a/modules/svg/include/SkSVGAttributeParser.h
+++ b/modules/svg/include/SkSVGAttributeParser.h
@@ -29,6 +29,7 @@
     bool parseIRI(SkSVGStringType*);
     bool parseSpreadMethod(SkSVGSpreadMethod*);
     bool parseStopColor(SkSVGStopColor*);
+    bool parseGradientUnits(SkSVGGradientUnits*);
     bool parseVisibility(SkSVGVisibility*);
     bool parseDashArray(SkSVGDashArray*);
 
diff --git a/modules/svg/include/SkSVGGradient.h b/modules/svg/include/SkSVGGradient.h
index 469bfe6..301a698 100644
--- a/modules/svg/include/SkSVGGradient.h
+++ b/modules/svg/include/SkSVGGradient.h
@@ -23,6 +23,7 @@
     void setHref(const SkSVGStringType&);
     void setGradientTransform(const SkSVGTransformType&);
     void setSpreadMethod(const SkSVGSpreadMethod&);
+    void setGradientUnits(const SkSVGGradientUnits&);
 
 protected:
     explicit SkSVGGradient(SkSVGTag t) : INHERITED(t) {}
@@ -44,6 +45,7 @@
     SkSVGStringType    fHref;
     SkSVGTransformType fGradientTransform = SkSVGTransformType(SkMatrix::I());
     SkSVGSpreadMethod  fSpreadMethod = SkSVGSpreadMethod(SkSVGSpreadMethod::Type::kPad);
+    SkSVGGradientUnits fGradientUnits = SkSVGGradientUnits(SkSVGGradientUnits::Type::kUserSpaceOnUse);
 
     using INHERITED = SkSVGHiddenContainer;
 };
diff --git a/modules/svg/include/SkSVGTypes.h b/modules/svg/include/SkSVGTypes.h
index e0ea73e..5f535de 100644
--- a/modules/svg/include/SkSVGTypes.h
+++ b/modules/svg/include/SkSVGTypes.h
@@ -308,6 +308,27 @@
     SkSVGColorType fColor;
 };
 
+class SkSVGGradientUnits {
+public:
+    enum class Type {
+        kUserSpaceOnUse,
+        kObjectBoundingBox,
+    };
+
+    SkSVGGradientUnits() : fType(Type::kUserSpaceOnUse) {}
+    explicit SkSVGGradientUnits(Type t) : fType(t) {}
+
+    bool operator==(const SkSVGGradientUnits& other) const {
+        return fType == other.fType;
+    }
+    bool operator!=(const SkSVGGradientUnits& other) const { return !(*this == other); }
+
+    Type type() const { return fType; }
+
+private:
+    Type fType;
+};
+
 class SkSVGFontFamily {
 public:
     enum class Type {
diff --git a/modules/svg/include/SkSVGValue.h b/modules/svg/include/SkSVGValue.h
index 0144a70..9c9f251 100644
--- a/modules/svg/include/SkSVGValue.h
+++ b/modules/svg/include/SkSVGValue.h
@@ -26,6 +26,7 @@
         kFontSize,
         kFontStyle,
         kFontWeight,
+        kGradientUnits,
         kLength,
         kLineCap,
         kLineJoin,
@@ -95,6 +96,8 @@
 using SkSVGSpreadMethodValue = SkSVGWrapperValue<SkSVGSpreadMethod ,
                                                  SkSVGValue::Type::kSpreadMethod>;
 using SkSVGStopColorValue    = SkSVGWrapperValue<SkSVGStopColor    , SkSVGValue::Type::kStopColor >;
+using SkSVGGradientUnitsValue= SkSVGWrapperValue<SkSVGGradientUnits,
+                                                 SkSVGValue::Type::kGradientUnits>;
 using SkSVGVisibilityValue   = SkSVGWrapperValue<SkSVGVisibility   , SkSVGValue::Type::kVisibility>;
 using SkSVGDashArrayValue    = SkSVGWrapperValue<SkSVGDashArray    , SkSVGValue::Type::kDashArray >;
 
diff --git a/modules/svg/src/SkSVGAttributeParser.cpp b/modules/svg/src/SkSVGAttributeParser.cpp
index 18acdf4..8cfc6c3 100644
--- a/modules/svg/src/SkSVGAttributeParser.cpp
+++ b/modules/svg/src/SkSVGAttributeParser.cpp
@@ -597,6 +597,19 @@
     return parsedValue && this->parseEOSToken();
 }
 
+// https://www.w3.org/TR/SVG11/pservers.html#LinearGradientElementGradientUnitsAttribute
+bool SkSVGAttributeParser::parseGradientUnits(SkSVGGradientUnits* gradientUnits) {
+    bool parsedValue = false;
+    if (this->parseExpectedStringToken("userSpaceOnUse")) {
+        *gradientUnits = SkSVGGradientUnits(SkSVGGradientUnits::Type::kUserSpaceOnUse);
+        parsedValue = true;
+    } else if (this->parseExpectedStringToken("objectBoundingBox")) {
+        *gradientUnits = SkSVGGradientUnits(SkSVGGradientUnits::Type::kObjectBoundingBox);
+        parsedValue = true;
+    }
+    return parsedValue && this->parseEOSToken();
+}
+
 // https://www.w3.org/TR/SVG11/shapes.html#PolygonElementPointsAttribute
 bool SkSVGAttributeParser::parsePoints(SkSVGPointsType* points) {
     SkTDArray<SkPoint> pts;
diff --git a/modules/svg/src/SkSVGDOM.cpp b/modules/svg/src/SkSVGDOM.cpp
index 8ab5ade..58f1ef0 100644
--- a/modules/svg/src/SkSVGDOM.cpp
+++ b/modules/svg/src/SkSVGDOM.cpp
@@ -200,6 +200,18 @@
     return true;
 }
 
+bool SetGradientUnitsAttribute(const sk_sp<SkSVGNode>& node, SkSVGAttribute attr,
+                               const char* stringValue) {
+    SkSVGGradientUnits gradientUnits;
+    SkSVGAttributeParser parser(stringValue);
+    if (!parser.parseGradientUnits(&gradientUnits)) {
+        return false;
+    }
+
+    node->setAttribute(attr, SkSVGGradientUnitsValue(gradientUnits));
+    return true;
+}
+
 bool SetPointsAttribute(const sk_sp<SkSVGNode>& node, SkSVGAttribute attr,
                         const char* stringValue) {
     SkSVGPointsType points;
@@ -391,6 +403,7 @@
     { "fx"               , { SkSVGAttribute::kFx               , SetLengthAttribute       }},
     { "fy"               , { SkSVGAttribute::kFy               , SetLengthAttribute       }},
     { "gradientTransform", { SkSVGAttribute::kGradientTransform, SetTransformAttribute    }},
+    { "gradientUnits"    , { SkSVGAttribute::kGradientUnits    , SetGradientUnitsAttribute}},
     { "height"           , { SkSVGAttribute::kHeight           , SetLengthAttribute       }},
     { "offset"           , { SkSVGAttribute::kOffset           , SetLengthAttribute       }},
     { "opacity"          , { SkSVGAttribute::kOpacity          , SetNumberAttribute       }},
diff --git a/modules/svg/src/SkSVGGradient.cpp b/modules/svg/src/SkSVGGradient.cpp
index 656920c..66d0d38 100644
--- a/modules/svg/src/SkSVGGradient.cpp
+++ b/modules/svg/src/SkSVGGradient.cpp
@@ -23,6 +23,10 @@
     fSpreadMethod = spread;
 }
 
+void SkSVGGradient::setGradientUnits(const SkSVGGradientUnits& gradientUnits) {
+    fGradientUnits = gradientUnits;
+}
+
 void SkSVGGradient::onSetAttribute(SkSVGAttribute attr, const SkSVGValue& v) {
     switch (attr) {
     case SkSVGAttribute::kGradientTransform:
@@ -40,6 +44,11 @@
             this->setSpreadMethod(*spread);
         }
         break;
+    case SkSVGAttribute::kGradientUnits:
+        if (const auto* gradientUnits = v.as<SkSVGGradientUnitsValue>()) {
+            this->setGradientUnits(*gradientUnits);
+        }
+        break;
     default:
         this->INHERITED::onSetAttribute(attr, v);
     }