[svg] Add support for preserveAspectRatio
This fixes the aspect ratio for pretty much all tests.
Since we're going to rebaseline everything, also have dm use a white
background (to match other user agents).
Bug: skia:10842
Change-Id: Iab2afd61560af540539c216d1c3673f19fe0fe51
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/328982
Commit-Queue: Florin Malita <fmalita@chromium.org>
Commit-Queue: Florin Malita <fmalita@google.com>
Reviewed-by: Tyler Denniston <tdenniston@google.com>
diff --git a/dm/DMSrcSink.cpp b/dm/DMSrcSink.cpp
index eb2ff11..4d850a3 100644
--- a/dm/DMSrcSink.cpp
+++ b/dm/DMSrcSink.cpp
@@ -1326,6 +1326,7 @@
SkAutoCanvasRestore acr(canvas, true);
canvas->scale(fScale, fScale);
+ canvas->drawColor(SK_ColorWHITE);
fDom->render(canvas);
return Result::Ok();
diff --git a/modules/svg/include/SkSVGAttribute.h b/modules/svg/include/SkSVGAttribute.h
index bd8204a..22a2262 100644
--- a/modules/svg/include/SkSVGAttribute.h
+++ b/modules/svg/include/SkSVGAttribute.h
@@ -37,6 +37,7 @@
kOpacity,
kPatternTransform,
kPoints,
+ kPreserveAspectRatio,
kR, // <circle>, <radialGradient>: radius
kRx, // <ellipse>,<rect>: horizontal (corner) radius
kRy, // <ellipse>,<rect>: vertical (corner) radius
diff --git a/modules/svg/include/SkSVGAttributeParser.h b/modules/svg/include/SkSVGAttributeParser.h
index 1876dea..2e5f11c 100644
--- a/modules/svg/include/SkSVGAttributeParser.h
+++ b/modules/svg/include/SkSVGAttributeParser.h
@@ -32,6 +32,7 @@
bool parseGradientUnits(SkSVGGradientUnits*);
bool parseVisibility(SkSVGVisibility*);
bool parseDashArray(SkSVGDashArray*);
+ bool parsePreserveAspectRatio(SkSVGPreserveAspectRatio*);
bool parseFontFamily(SkSVGFontFamily*);
bool parseFontSize(SkSVGFontSize*);
diff --git a/modules/svg/include/SkSVGNode.h b/modules/svg/include/SkSVGNode.h
index 8f493d2..a2991f1 100644
--- a/modules/svg/include/SkSVGNode.h
+++ b/modules/svg/include/SkSVGNode.h
@@ -128,4 +128,14 @@
using INHERITED = SkRefCnt;
};
+#undef SVG_PRES_ATTR // presentation attributes are only defined for the base class
+
+#define SVG_ATTR(attr_name, attr_type, attr_default) \
+ private: \
+ attr_type f##attr_name = attr_default; \
+ public: \
+ const attr_type& get##attr_name() const { return f##attr_name; } \
+ void set##attr_name(const attr_type& a) { f##attr_name = a; } \
+ void set##attr_name(attr_type&& a) { f##attr_name = std::move(a); }
+
#endif // SkSVGNode_DEFINED
diff --git a/modules/svg/include/SkSVGSVG.h b/modules/svg/include/SkSVGSVG.h
index 7494d78..46ecfc9 100644
--- a/modules/svg/include/SkSVGSVG.h
+++ b/modules/svg/include/SkSVGSVG.h
@@ -20,10 +20,13 @@
static sk_sp<SkSVGSVG> Make() { return sk_sp<SkSVGSVG>(new SkSVGSVG()); }
- void setX(const SkSVGLength&);
- void setY(const SkSVGLength&);
- void setWidth(const SkSVGLength&);
- void setHeight(const SkSVGLength&);
+ SVG_ATTR(X , SkSVGLength, SkSVGLength(0))
+ SVG_ATTR(Y , SkSVGLength, SkSVGLength(0))
+ SVG_ATTR(Width , SkSVGLength, SkSVGLength(100, SkSVGLength::Unit::kPercentage))
+ SVG_ATTR(Height , SkSVGLength, SkSVGLength(100, SkSVGLength::Unit::kPercentage))
+ SVG_ATTR(PreserveAspectRatio, SkSVGPreserveAspectRatio, SkSVGPreserveAspectRatio())
+
+ // TODO: SVG_ATTR is not smart enough to handle SkTLazy<T>
void setViewBox(const SkSVGViewBoxType&);
SkSize intrinsicSize(const SkSVGLengthContext&) const;
@@ -36,11 +39,6 @@
private:
SkSVGSVG();
- SkSVGLength fX = SkSVGLength(0);
- SkSVGLength fY = SkSVGLength(0);
- SkSVGLength fWidth = SkSVGLength(100, SkSVGLength::Unit::kPercentage);
- SkSVGLength fHeight = SkSVGLength(100, SkSVGLength::Unit::kPercentage);
-
SkTLazy<SkSVGViewBoxType> fViewBox;
using INHERITED = SkSVGContainer;
diff --git a/modules/svg/include/SkSVGTypes.h b/modules/svg/include/SkSVGTypes.h
index 5f535de..6e84f85 100644
--- a/modules/svg/include/SkSVGTypes.h
+++ b/modules/svg/include/SkSVGTypes.h
@@ -437,4 +437,30 @@
Type fType;
};
+struct SkSVGPreserveAspectRatio {
+ enum Align : uint8_t {
+ // These values are chosen such that bits [0,1] encode X alignment, and
+ // bits [2,3] encode Y alignment.
+ kXMinYMin = 0x00,
+ kXMidYMin = 0x01,
+ kXMaxYMin = 0x02,
+ kXMinYMid = 0x04,
+ kXMidYMid = 0x05,
+ kXMaxYMid = 0x06,
+ kXMinYMax = 0x08,
+ kXMidYMax = 0x09,
+ kXMaxYMax = 0x0a,
+
+ kNone = 0x10,
+ };
+
+ enum Scale {
+ kMeet,
+ kSlice,
+ };
+
+ Align fAlign = kXMidYMid;
+ Scale fScale = kMeet;
+};
+
#endif // SkSVGTypes_DEFINED
diff --git a/modules/svg/include/SkSVGValue.h b/modules/svg/include/SkSVGValue.h
index 9c9f251..b9ea659 100644
--- a/modules/svg/include/SkSVGValue.h
+++ b/modules/svg/include/SkSVGValue.h
@@ -34,6 +34,7 @@
kPaint,
kPath,
kPoints,
+ kPreserveAspectRatio,
kSpreadMethod,
kStopColor,
kString,
@@ -106,4 +107,7 @@
using SkSVGFontStyleValue = SkSVGWrapperValue<SkSVGFontStyle , SkSVGValue::Type::kFontStyle >;
using SkSVGFontWeightValue = SkSVGWrapperValue<SkSVGFontWeight , SkSVGValue::Type::kFontWeight>;
+using SkSVGPreserveAspectRatioValue = SkSVGWrapperValue<SkSVGPreserveAspectRatio,
+ SkSVGValue::Type::kPreserveAspectRatio>;
+
#endif // SkSVGValue_DEFINED
diff --git a/modules/svg/src/SkSVGAttributeParser.cpp b/modules/svg/src/SkSVGAttributeParser.cpp
index 8cfc6c3..a56e8e7 100644
--- a/modules/svg/src/SkSVGAttributeParser.cpp
+++ b/modules/svg/src/SkSVGAttributeParser.cpp
@@ -821,3 +821,41 @@
return parsedValue && this->parseEOSToken();
}
+
+// https://www.w3.org/TR/SVG11/coords.html#PreserveAspectRatioAttribute
+bool SkSVGAttributeParser::parsePreserveAspectRatio(SkSVGPreserveAspectRatio* par) {
+ static constexpr std::tuple<const char*, SkSVGPreserveAspectRatio::Align> gAlignMap[] = {
+ { "none" , SkSVGPreserveAspectRatio::kNone },
+ { "xMinYMin", SkSVGPreserveAspectRatio::kXMinYMin },
+ { "xMidYMin", SkSVGPreserveAspectRatio::kXMidYMin },
+ { "xMaxYMin", SkSVGPreserveAspectRatio::kXMaxYMin },
+ { "xMinYMid", SkSVGPreserveAspectRatio::kXMinYMid },
+ { "xMidYMid", SkSVGPreserveAspectRatio::kXMidYMid },
+ { "xMaxYMid", SkSVGPreserveAspectRatio::kXMaxYMid },
+ { "xMinYMax", SkSVGPreserveAspectRatio::kXMinYMax },
+ { "xMidYMax", SkSVGPreserveAspectRatio::kXMidYMax },
+ { "xMaxYMax", SkSVGPreserveAspectRatio::kXMaxYMax },
+ };
+
+ static constexpr std::tuple<const char*, SkSVGPreserveAspectRatio::Scale> gScaleMap[] = {
+ { "meet" , SkSVGPreserveAspectRatio::kMeet },
+ { "slice", SkSVGPreserveAspectRatio::kSlice },
+ };
+
+ bool parsedValue = false;
+
+ // ignoring optional 'defer'
+ this->parseExpectedStringToken("defer");
+ this->parseWSToken();
+
+ if (this->parseEnumMap(gAlignMap, &par->fAlign)) {
+ parsedValue = true;
+
+ // optional scaling selector
+ this->parseWSToken();
+ this->parseEnumMap(gScaleMap, &par->fScale);
+ }
+
+ return parsedValue && this->parseEOSToken();
+}
+
diff --git a/modules/svg/src/SkSVGDOM.cpp b/modules/svg/src/SkSVGDOM.cpp
index 58f1ef0..27e2540 100644
--- a/modules/svg/src/SkSVGDOM.cpp
+++ b/modules/svg/src/SkSVGDOM.cpp
@@ -308,6 +308,18 @@
return true;
}
+bool SetPreserveAspectRatioAttribute(const sk_sp<SkSVGNode>& node, SkSVGAttribute attr,
+ const char* stringValue) {
+ SkSVGPreserveAspectRatio par;
+ SkSVGAttributeParser parser(stringValue);
+ if (!parser.parsePreserveAspectRatio(&par)) {
+ return false;
+ }
+
+ node->setAttribute(attr, SkSVGPreserveAspectRatioValue(par));
+ return true;
+}
+
SkString TrimmedString(const char* first, const char* last) {
SkASSERT(first);
SkASSERT(last);
@@ -386,57 +398,59 @@
};
SortedDictionaryEntry<AttrParseInfo> gAttributeParseInfo[] = {
- { "clip-path" , { SkSVGAttribute::kClipPath , SetClipPathAttribute }},
- { "clip-rule" , { SkSVGAttribute::kClipRule , SetFillRuleAttribute }},
- { "color" , { SkSVGAttribute::kColor , SetColorAttribute }},
- { "cx" , { SkSVGAttribute::kCx , SetLengthAttribute }},
- { "cy" , { SkSVGAttribute::kCy , SetLengthAttribute }},
- { "d" , { SkSVGAttribute::kD , SetPathDataAttribute }},
- { "fill" , { SkSVGAttribute::kFill , SetPaintAttribute }},
- { "fill-opacity" , { SkSVGAttribute::kFillOpacity , SetNumberAttribute }},
- { "fill-rule" , { SkSVGAttribute::kFillRule , SetFillRuleAttribute }},
- { "font-family" , { SkSVGAttribute::kFontFamily , SetFontFamilyAttribute }},
- { "font-size" , { SkSVGAttribute::kFontSize , SetFontSizeAttribute }},
- { "font-style" , { SkSVGAttribute::kFontStyle , SetFontStyleAttribute }},
- { "font-weight" , { SkSVGAttribute::kFontWeight , SetFontWeightAttribute }},
+ { "clip-path" , { SkSVGAttribute::kClipPath , SetClipPathAttribute }},
+ { "clip-rule" , { SkSVGAttribute::kClipRule , SetFillRuleAttribute }},
+ { "color" , { SkSVGAttribute::kColor , SetColorAttribute }},
+ { "cx" , { SkSVGAttribute::kCx , SetLengthAttribute }},
+ { "cy" , { SkSVGAttribute::kCy , SetLengthAttribute }},
+ { "d" , { SkSVGAttribute::kD , SetPathDataAttribute }},
+ { "fill" , { SkSVGAttribute::kFill , SetPaintAttribute }},
+ { "fill-opacity" , { SkSVGAttribute::kFillOpacity , SetNumberAttribute }},
+ { "fill-rule" , { SkSVGAttribute::kFillRule , SetFillRuleAttribute }},
+ { "font-family" , { SkSVGAttribute::kFontFamily , SetFontFamilyAttribute }},
+ { "font-size" , { SkSVGAttribute::kFontSize , SetFontSizeAttribute }},
+ { "font-style" , { SkSVGAttribute::kFontStyle , SetFontStyleAttribute }},
+ { "font-weight" , { SkSVGAttribute::kFontWeight , SetFontWeightAttribute }},
// focal point x & y
- { "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 }},
- { "patternTransform" , { SkSVGAttribute::kPatternTransform , SetTransformAttribute }},
- { "points" , { SkSVGAttribute::kPoints , SetPointsAttribute }},
- { "r" , { SkSVGAttribute::kR , SetLengthAttribute }},
- { "rx" , { SkSVGAttribute::kRx , SetLengthAttribute }},
- { "ry" , { SkSVGAttribute::kRy , SetLengthAttribute }},
- { "spreadMethod" , { SkSVGAttribute::kSpreadMethod , SetSpreadMethodAttribute }},
- { "stop-color" , { SkSVGAttribute::kStopColor , SetStopColorAttribute }},
- { "stop-opacity" , { SkSVGAttribute::kStopOpacity , SetNumberAttribute }},
- { "stroke" , { SkSVGAttribute::kStroke , SetPaintAttribute }},
- { "stroke-dasharray" , { SkSVGAttribute::kStrokeDashArray , SetDashArrayAttribute }},
- { "stroke-dashoffset", { SkSVGAttribute::kStrokeDashOffset , SetLengthAttribute }},
- { "stroke-linecap" , { SkSVGAttribute::kStrokeLineCap , SetLineCapAttribute }},
- { "stroke-linejoin" , { SkSVGAttribute::kStrokeLineJoin , SetLineJoinAttribute }},
- { "stroke-miterlimit", { SkSVGAttribute::kStrokeMiterLimit , SetNumberAttribute }},
- { "stroke-opacity" , { SkSVGAttribute::kStrokeOpacity , SetNumberAttribute }},
- { "stroke-width" , { SkSVGAttribute::kStrokeWidth , SetLengthAttribute }},
- { "style" , { SkSVGAttribute::kUnknown , SetStyleAttributes }},
- { "text" , { SkSVGAttribute::kText , SetStringAttribute }},
- { "text-anchor" , { SkSVGAttribute::kTextAnchor , SetStringAttribute }},
- { "transform" , { SkSVGAttribute::kTransform , SetTransformAttribute }},
- { "viewBox" , { SkSVGAttribute::kViewBox , SetViewBoxAttribute }},
- { "visibility" , { SkSVGAttribute::kVisibility , SetVisibilityAttribute }},
- { "width" , { SkSVGAttribute::kWidth , SetLengthAttribute }},
- { "x" , { SkSVGAttribute::kX , SetLengthAttribute }},
- { "x1" , { SkSVGAttribute::kX1 , SetLengthAttribute }},
- { "x2" , { SkSVGAttribute::kX2 , SetLengthAttribute }},
- { "xlink:href" , { SkSVGAttribute::kHref , SetIRIAttribute }},
- { "y" , { SkSVGAttribute::kY , SetLengthAttribute }},
- { "y1" , { SkSVGAttribute::kY1 , SetLengthAttribute }},
- { "y2" , { SkSVGAttribute::kY2 , SetLengthAttribute }},
+ { "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 }},
+ { "patternTransform" , { SkSVGAttribute::kPatternTransform , SetTransformAttribute }},
+ { "points" , { SkSVGAttribute::kPoints , SetPointsAttribute }},
+ { "preserveAspectRatio", { SkSVGAttribute::kPreserveAspectRatio,
+ SetPreserveAspectRatioAttribute }},
+ { "r" , { SkSVGAttribute::kR , SetLengthAttribute }},
+ { "rx" , { SkSVGAttribute::kRx , SetLengthAttribute }},
+ { "ry" , { SkSVGAttribute::kRy , SetLengthAttribute }},
+ { "spreadMethod" , { SkSVGAttribute::kSpreadMethod , SetSpreadMethodAttribute }},
+ { "stop-color" , { SkSVGAttribute::kStopColor , SetStopColorAttribute }},
+ { "stop-opacity" , { SkSVGAttribute::kStopOpacity , SetNumberAttribute }},
+ { "stroke" , { SkSVGAttribute::kStroke , SetPaintAttribute }},
+ { "stroke-dasharray" , { SkSVGAttribute::kStrokeDashArray , SetDashArrayAttribute }},
+ { "stroke-dashoffset" , { SkSVGAttribute::kStrokeDashOffset , SetLengthAttribute }},
+ { "stroke-linecap" , { SkSVGAttribute::kStrokeLineCap , SetLineCapAttribute }},
+ { "stroke-linejoin" , { SkSVGAttribute::kStrokeLineJoin , SetLineJoinAttribute }},
+ { "stroke-miterlimit" , { SkSVGAttribute::kStrokeMiterLimit , SetNumberAttribute }},
+ { "stroke-opacity" , { SkSVGAttribute::kStrokeOpacity , SetNumberAttribute }},
+ { "stroke-width" , { SkSVGAttribute::kStrokeWidth , SetLengthAttribute }},
+ { "style" , { SkSVGAttribute::kUnknown , SetStyleAttributes }},
+ { "text" , { SkSVGAttribute::kText , SetStringAttribute }},
+ { "text-anchor" , { SkSVGAttribute::kTextAnchor , SetStringAttribute }},
+ { "transform" , { SkSVGAttribute::kTransform , SetTransformAttribute }},
+ { "viewBox" , { SkSVGAttribute::kViewBox , SetViewBoxAttribute }},
+ { "visibility" , { SkSVGAttribute::kVisibility , SetVisibilityAttribute }},
+ { "width" , { SkSVGAttribute::kWidth , SetLengthAttribute }},
+ { "x" , { SkSVGAttribute::kX , SetLengthAttribute }},
+ { "x1" , { SkSVGAttribute::kX1 , SetLengthAttribute }},
+ { "x2" , { SkSVGAttribute::kX2 , SetLengthAttribute }},
+ { "xlink:href" , { SkSVGAttribute::kHref , SetIRIAttribute }},
+ { "y" , { SkSVGAttribute::kY , SetLengthAttribute }},
+ { "y1" , { SkSVGAttribute::kY1 , SetLengthAttribute }},
+ { "y2" , { SkSVGAttribute::kY2 , SetLengthAttribute }},
};
SortedDictionaryEntry<sk_sp<SkSVGNode>(*)()> gTagFactories[] = {
diff --git a/modules/svg/src/SkSVGSVG.cpp b/modules/svg/src/SkSVGSVG.cpp
index 2542d2b..4f2d573 100644
--- a/modules/svg/src/SkSVGSVG.cpp
+++ b/modules/svg/src/SkSVGSVG.cpp
@@ -12,6 +12,58 @@
SkSVGSVG::SkSVGSVG() : INHERITED(SkSVGTag::kSvg) { }
+static SkMatrix ViewboxMatrix(const SkRect& view_box, const SkRect& view_port,
+ const SkSVGPreserveAspectRatio& par) {
+ SkASSERT(!view_box.isEmpty());
+ SkASSERT(!view_port.isEmpty());
+
+ auto compute_scale = [&]() -> SkV2 {
+ const auto sx = view_port.width() / view_box.width(),
+ sy = view_port.height() / view_box.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 = -view_box.x() * scale.x,
+ ty = -view_box.y() * scale.y,
+ dx = view_port.width() - view_box.width() * scale.x,
+ dy = view_port.height() - view_box.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);
+}
+
bool SkSVGSVG::onPrepareToRender(SkSVGRenderContext* ctx) const {
auto viewPortRect = ctx->lengthContext().resolveRect(fX, fY, fWidth, fHeight);
auto contentMatrix = SkMatrix::Translate(viewPortRect.x(), viewPortRect.y());
@@ -28,8 +80,7 @@
// A viewBox overrides the intrinsic viewport.
viewPort = SkSize::Make(viewBox.width(), viewBox.height());
- contentMatrix.preConcat(
- SkMatrix::MakeRectToRect(viewBox, viewPortRect, SkMatrix::kFill_ScaleToFit));
+ contentMatrix.preConcat(ViewboxMatrix(viewBox, viewPortRect, fPreserveAspectRatio));
}
if (!contentMatrix.isIdentity()) {
@@ -44,22 +95,6 @@
return this->INHERITED::onPrepareToRender(ctx);
}
-void SkSVGSVG::setX(const SkSVGLength& x) {
- fX = x;
-}
-
-void SkSVGSVG::setY(const SkSVGLength& y) {
- fY = y;
-}
-
-void SkSVGSVG::setWidth(const SkSVGLength& w) {
- fWidth = w;
-}
-
-void SkSVGSVG::setHeight(const SkSVGLength& h) {
- fHeight = h;
-}
-
void SkSVGSVG::setViewBox(const SkSVGViewBoxType& vb) {
fViewBox.set(vb);
}
@@ -91,6 +126,11 @@
this->setViewBox(*vb);
}
break;
+ case SkSVGAttribute::kPreserveAspectRatio:
+ if (const auto* par = v.as<SkSVGPreserveAspectRatioValue>()) {
+ this->setPreserveAspectRatio(*par);
+ }
+ break;
default:
this->INHERITED::onSetAttribute(attr, v);
}