[svg] Implement gradientUnits="objectBoundingBox"

Currently only works properly when filling rects, since that is the only
node that implements bounds computation with this CL.

Summary of changes:

- Scale gradient coords by object bounds when units are
  kObjectBoundingBox.
- Make fGradientUnits protected instead of private.
- Change default value of fGradientUnits to kObjectBoundingBox, which
  is the default according to the spec.
- Change SkSVGNode::onObjectBoundingBox to take a full render context
  instead of length context.
- Implement bounds computation for SkSVGRect, SkSVGContainer and
  SkSVGUse.

Bug: skia:10842
Change-Id: I2e999985e67644e50da7f681fde190bcf4823eec
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/329223
Reviewed-by: Florin Malita <fmalita@chromium.org>
Commit-Queue: Tyler Denniston <tdenniston@google.com>
diff --git a/modules/svg/include/SkSVGContainer.h b/modules/svg/include/SkSVGContainer.h
index 4b386ec..92ab80f 100644
--- a/modules/svg/include/SkSVGContainer.h
+++ b/modules/svg/include/SkSVGContainer.h
@@ -24,6 +24,8 @@
 
     SkPath onAsPath(const SkSVGRenderContext&) const override;
 
+    SkRect onObjectBoundingBox(const SkSVGRenderContext&) const override;
+
     bool hasChildren() const final;
 
     // TODO: add some sort of child iterator, and hide the container.
diff --git a/modules/svg/include/SkSVGGradient.h b/modules/svg/include/SkSVGGradient.h
index 301a698..175d2fc 100644
--- a/modules/svg/include/SkSVGGradient.h
+++ b/modules/svg/include/SkSVGGradient.h
@@ -36,6 +36,8 @@
                                          const SkColor*, const SkScalar*, int count,
                                          SkTileMode, const SkMatrix& localMatrix) const = 0;
 
+    SkSVGGradientUnits fGradientUnits = SkSVGGradientUnits(SkSVGGradientUnits::Type::kObjectBoundingBox);
+
 private:
     using StopPositionArray = SkSTArray<2, SkScalar, true>;
     using    StopColorArray = SkSTArray<2,  SkColor, true>;
@@ -45,7 +47,6 @@
     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/SkSVGNode.h b/modules/svg/include/SkSVGNode.h
index 001b5ca..9017f88 100644
--- a/modules/svg/include/SkSVGNode.h
+++ b/modules/svg/include/SkSVGNode.h
@@ -73,7 +73,7 @@
     void render(const SkSVGRenderContext&) const;
     bool asPaint(const SkSVGRenderContext&, SkPaint*) const;
     SkPath asPath(const SkSVGRenderContext&) const;
-    SkRect objectBoundingBox(const SkSVGLengthContext&) const;
+    SkRect objectBoundingBox(const SkSVGRenderContext&) const;
 
     void setAttribute(SkSVGAttribute, const SkSVGValue&);
     bool setAttribute(const char* attributeName, const char* attributeValue);
@@ -121,7 +121,7 @@
 
     virtual bool hasChildren() const { return false; }
 
-    virtual SkRect onObjectBoundingBox(const SkSVGLengthContext&) const {
+    virtual SkRect onObjectBoundingBox(const SkSVGRenderContext&) const {
         return SkRect::MakeEmpty();
     }
 
diff --git a/modules/svg/include/SkSVGRect.h b/modules/svg/include/SkSVGRect.h
index 507d9d9..cd91187 100644
--- a/modules/svg/include/SkSVGRect.h
+++ b/modules/svg/include/SkSVGRect.h
@@ -33,6 +33,8 @@
 
     SkPath onAsPath(const SkSVGRenderContext&) const override;
 
+    SkRect onObjectBoundingBox(const SkSVGRenderContext&) const override;
+
 private:
     SkSVGRect();
 
diff --git a/modules/svg/include/SkSVGTransformableNode.h b/modules/svg/include/SkSVGTransformableNode.h
index 55e0c0a..a60c347 100644
--- a/modules/svg/include/SkSVGTransformableNode.h
+++ b/modules/svg/include/SkSVGTransformableNode.h
@@ -26,6 +26,8 @@
 
     void mapToParent(SkPath*) const;
 
+    void mapToParent(SkRect*) const;
+
 private:
     // FIXME: should be sparse
     SkSVGTransformType fTransform;
diff --git a/modules/svg/include/SkSVGUse.h b/modules/svg/include/SkSVGUse.h
index 4a87f99..3b430bc 100644
--- a/modules/svg/include/SkSVGUse.h
+++ b/modules/svg/include/SkSVGUse.h
@@ -33,6 +33,7 @@
     bool onPrepareToRender(SkSVGRenderContext*) const override;
     void onRender(const SkSVGRenderContext&) const override;
     SkPath onAsPath(const SkSVGRenderContext&) const override;
+    SkRect onObjectBoundingBox(const SkSVGRenderContext&) const override;
 
 private:
     SkSVGUse();
diff --git a/modules/svg/src/SkSVGContainer.cpp b/modules/svg/src/SkSVGContainer.cpp
index 961194c..72d2b7b 100644
--- a/modules/svg/src/SkSVGContainer.cpp
+++ b/modules/svg/src/SkSVGContainer.cpp
@@ -39,3 +39,15 @@
     this->mapToParent(&path);
     return path;
 }
+
+SkRect SkSVGContainer::onObjectBoundingBox(const SkSVGRenderContext& ctx) const {
+    SkRect bounds = SkRect::MakeEmpty();
+
+    for (int i = 0; i < fChildren.count(); ++i) {
+        const SkRect childBounds = fChildren[i]->objectBoundingBox(ctx);
+        bounds.join(childBounds);
+    }
+
+    this->mapToParent(&bounds);
+    return bounds;
+}
diff --git a/modules/svg/src/SkSVGGradient.cpp b/modules/svg/src/SkSVGGradient.cpp
index 66d0d38..9500681 100644
--- a/modules/svg/src/SkSVGGradient.cpp
+++ b/modules/svg/src/SkSVGGradient.cpp
@@ -126,7 +126,16 @@
                   SkTileMode::kMirror, "SkSVGSpreadMethod::Type is out of sync");
     const auto tileMode = static_cast<SkTileMode>(fSpreadMethod.type());
 
+    SkMatrix localMatrix = SkMatrix::I();
+    if (fGradientUnits.type() == SkSVGGradientUnits::Type::kObjectBoundingBox) {
+        SkASSERT(ctx.node());
+        const SkRect objBounds = ctx.node()->objectBoundingBox(ctx);
+        localMatrix.preTranslate(objBounds.fLeft, objBounds.fTop);
+        localMatrix.preScale(objBounds.width(), objBounds.height());
+    }
+    localMatrix.preConcat(fGradientTransform);
+
     paint->setShader(this->onMakeShader(ctx, colors.begin(), pos.begin(), colors.count(), tileMode,
-                                        fGradientTransform));
+                                        localMatrix));
     return true;
 }
diff --git a/modules/svg/src/SkSVGLinearGradient.cpp b/modules/svg/src/SkSVGLinearGradient.cpp
index 436428d..1579942 100644
--- a/modules/svg/src/SkSVGLinearGradient.cpp
+++ b/modules/svg/src/SkSVGLinearGradient.cpp
@@ -59,7 +59,11 @@
                                                   const SkColor* colors, const SkScalar* pos,
                                                   int count, SkTileMode tm,
                                                   const SkMatrix& localMatrix) const {
-    const auto& lctx = ctx.lengthContext();
+    const SkSVGLengthContext lctx =
+            fGradientUnits.type() == SkSVGGradientUnits::Type::kObjectBoundingBox
+                    ? SkSVGLengthContext({1, 1})
+                    : ctx.lengthContext();
+
     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);
diff --git a/modules/svg/src/SkSVGNode.cpp b/modules/svg/src/SkSVGNode.cpp
index cf12945..07f3a47 100644
--- a/modules/svg/src/SkSVGNode.cpp
+++ b/modules/svg/src/SkSVGNode.cpp
@@ -48,8 +48,8 @@
     return path;
 }
 
-SkRect SkSVGNode::objectBoundingBox(const SkSVGLengthContext& lctx) const {
-    return this->onObjectBoundingBox(lctx);
+SkRect SkSVGNode::objectBoundingBox(const SkSVGRenderContext& ctx) const {
+    return this->onObjectBoundingBox(ctx);
 }
 
 bool SkSVGNode::onPrepareToRender(SkSVGRenderContext* ctx) const {
diff --git a/modules/svg/src/SkSVGRadialGradient.cpp b/modules/svg/src/SkSVGRadialGradient.cpp
index 4d311a6..0300248 100644
--- a/modules/svg/src/SkSVGRadialGradient.cpp
+++ b/modules/svg/src/SkSVGRadialGradient.cpp
@@ -68,7 +68,11 @@
                                                   const SkColor* colors, const SkScalar* pos,
                                                   int count, SkTileMode tm,
                                                   const SkMatrix& m) const {
-    const auto&  lctx = ctx.lengthContext();
+    const SkSVGLengthContext lctx =
+            fGradientUnits.type() == SkSVGGradientUnits::Type::kObjectBoundingBox
+                    ? SkSVGLengthContext({1, 1})
+                    : ctx.lengthContext();
+
     const auto      r = lctx.resolve(fR , SkSVGLengthContext::LengthType::kOther);
     const auto center = SkPoint::Make(
             lctx.resolve(fCx, SkSVGLengthContext::LengthType::kHorizontal),
@@ -79,6 +83,9 @@
         fFy.isValid() ? lctx.resolve(*fFy, SkSVGLengthContext::LengthType::kVertical)
                       : center.y());
 
+    // TODO: Handle r == 0 which has a specific meaning according to the spec
+    SkASSERT(r != 0);
+
     return center == focal
         ? SkGradientShader::MakeRadial(center, r, colors, pos, count, tm, 0, &m)
         : SkGradientShader::MakeTwoPointConical(focal, 0, center, r, colors, pos, count, tm, 0, &m);
diff --git a/modules/svg/src/SkSVGRect.cpp b/modules/svg/src/SkSVGRect.cpp
index 9f5e3a9..74bce16 100644
--- a/modules/svg/src/SkSVGRect.cpp
+++ b/modules/svg/src/SkSVGRect.cpp
@@ -94,3 +94,7 @@
 
     return path;
 }
+
+SkRect SkSVGRect::onObjectBoundingBox(const SkSVGRenderContext& ctx) const {
+    return ctx.lengthContext().resolveRect(fX, fY, fWidth, fHeight);
+}
diff --git a/modules/svg/src/SkSVGTransformableNode.cpp b/modules/svg/src/SkSVGTransformableNode.cpp
index f76f2c4..55f7550 100644
--- a/modules/svg/src/SkSVGTransformableNode.cpp
+++ b/modules/svg/src/SkSVGTransformableNode.cpp
@@ -41,3 +41,7 @@
     // transforms the path to parent node coordinates.
     path->transform(fTransform);
 }
+
+void SkSVGTransformableNode::mapToParent(SkRect* rect) const {
+    *rect = fTransform.mapRect(*rect);
+}
diff --git a/modules/svg/src/SkSVGUse.cpp b/modules/svg/src/SkSVGUse.cpp
index ea510ea..bf4457c 100644
--- a/modules/svg/src/SkSVGUse.cpp
+++ b/modules/svg/src/SkSVGUse.cpp
@@ -84,3 +84,15 @@
 
     return ref->asPath(ctx);
 }
+
+SkRect SkSVGUse::onObjectBoundingBox(const SkSVGRenderContext& ctx) const {
+    const auto ref = ctx.findNodeById(fHref);
+    if (!ref) {
+        return SkRect::MakeEmpty();
+    }
+
+    const SkRect bounds = ref->objectBoundingBox(ctx);
+    const SkScalar x = ctx.lengthContext().resolve(fX, SkSVGLengthContext::LengthType::kHorizontal);
+    const SkScalar y = ctx.lengthContext().resolve(fY, SkSVGLengthContext::LengthType::kVertical);
+    return SkRect::MakeXYWH(x, y, bounds.width(), bounds.height());
+}