[svg] Support preserveAspectRatio for images

https://www.w3.org/TR/SVG11/struct.html#ImageElement
https://www.w3.org/TR/SVG11/coords.html#PreserveAspectRatioAttribute

We already had a function to compute the appropriate matrix, and since
we can share the functionality with other elements that establish a new
viewport (including svg, symbol, and a few others), this CL moves the
function to the SVG node base class.

Relevant test for images is struct-image-06.

Bug: skia:10842
Change-Id: I5d6261210d03959e28d0bd7189da7f4ea53abc03
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/366398
Commit-Queue: Tyler Denniston <tdenniston@google.com>
Reviewed-by: Florin Malita <fmalita@chromium.org>
diff --git a/modules/svg/src/SkSVGNode.cpp b/modules/svg/src/SkSVGNode.cpp
index ae3aef2..9f3a3e6 100644
--- a/modules/svg/src/SkSVGNode.cpp
+++ b/modules/svg/src/SkSVGNode.cpp
@@ -122,3 +122,57 @@
 
 #undef PARSE_AND_SET
 }
+
+// https://www.w3.org/TR/SVG11/coords.html#PreserveAspectRatioAttribute
+SkMatrix SkSVGNode::ComputeViewboxMatrix(const SkRect& viewBox,
+                                         const SkRect& viewPort,
+                                         SkSVGPreserveAspectRatio par) {
+    SkASSERT(!viewBox.isEmpty());
+    SkASSERT(!viewPort.isEmpty());
+
+    auto compute_scale = [&]() -> SkV2 {
+        const auto sx = viewPort.width()  / viewBox.width(),
+                   sy = viewPort.height() / viewBox.height();
+
+        if (par.fAlign == SkSVGPreserveAspectRatio::kNone) {
+            // none -> anisotropic scaling, regardless of fScale
+            return {sx, sy};
+        }
+
+        // isotropic scaling
+        const auto s = par.fScale == SkSVGPreserveAspectRatio::kMeet
+                            ? std::min(sx, sy)
+                            : std::max(sx, sy);
+        return {s, s};
+    };
+
+    auto compute_trans = [&](const SkV2& scale) -> SkV2 {
+        static constexpr float gAlignCoeffs[] = {
+                0.0f, // Min
+                0.5f, // Mid
+                1.0f  // Max
+        };
+
+        const size_t x_coeff = par.fAlign >> 0 & 0x03,
+                     y_coeff = par.fAlign >> 2 & 0x03;
+
+        SkASSERT(x_coeff < SK_ARRAY_COUNT(gAlignCoeffs) &&
+                 y_coeff < SK_ARRAY_COUNT(gAlignCoeffs));
+
+        const auto tx = -viewBox.x() * scale.x,
+                   ty = -viewBox.y() * scale.y,
+                   dx = viewPort.width()  - viewBox.width() * scale.x,
+                   dy = viewPort.height() - viewBox.height() * scale.y;
+
+        return {
+            tx + dx * gAlignCoeffs[x_coeff],
+            ty + dy * gAlignCoeffs[y_coeff]
+        };
+    };
+
+    const auto s = compute_scale(),
+               t = compute_trans(s);
+
+    return SkMatrix::Translate(t.x, t.y) *
+           SkMatrix::Scale(s.x, s.y);
+}