[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>
This commit is contained in:
Florin Malita 2020-10-21 16:55:46 -04:00 committed by Skia Commit-Bot
parent e51b6a3da6
commit 385e74470f
10 changed files with 210 additions and 77 deletions

View File

@ -1326,6 +1326,7 @@ Result SVGSrc::draw(GrDirectContext*, SkCanvas* canvas) const {
SkAutoCanvasRestore acr(canvas, true);
canvas->scale(fScale, fScale);
canvas->drawColor(SK_ColorWHITE);
fDom->render(canvas);
return Result::Ok();

View File

@ -37,6 +37,7 @@ enum class SkSVGAttribute {
kOpacity,
kPatternTransform,
kPoints,
kPreserveAspectRatio,
kR, // <circle>, <radialGradient>: radius
kRx, // <ellipse>,<rect>: horizontal (corner) radius
kRy, // <ellipse>,<rect>: vertical (corner) radius

View File

@ -32,6 +32,7 @@ public:
bool parseGradientUnits(SkSVGGradientUnits*);
bool parseVisibility(SkSVGVisibility*);
bool parseDashArray(SkSVGDashArray*);
bool parsePreserveAspectRatio(SkSVGPreserveAspectRatio*);
bool parseFontFamily(SkSVGFontFamily*);
bool parseFontSize(SkSVGFontSize*);

View File

@ -128,4 +128,14 @@ private:
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

View File

@ -20,10 +20,13 @@ public:
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 @@ protected:
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;

View File

@ -437,4 +437,30 @@ private:
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

View File

@ -34,6 +34,7 @@ public:
kPaint,
kPath,
kPoints,
kPreserveAspectRatio,
kSpreadMethod,
kStopColor,
kString,
@ -106,4 +107,7 @@ using SkSVGFontSizeValue = SkSVGWrapperValue<SkSVGFontSize , SkSVGValue:
using SkSVGFontStyleValue = SkSVGWrapperValue<SkSVGFontStyle , SkSVGValue::Type::kFontStyle >;
using SkSVGFontWeightValue = SkSVGWrapperValue<SkSVGFontWeight , SkSVGValue::Type::kFontWeight>;
using SkSVGPreserveAspectRatioValue = SkSVGWrapperValue<SkSVGPreserveAspectRatio,
SkSVGValue::Type::kPreserveAspectRatio>;
#endif // SkSVGValue_DEFINED

View File

@ -821,3 +821,41 @@ bool SkSVGAttributeParser::parseFontWeight(SkSVGFontWeight* weight) {
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();
}

View File

@ -308,6 +308,18 @@ bool SetFontWeightAttribute(const sk_sp<SkSVGNode>& node, SkSVGAttribute attr,
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 @@ struct AttrParseInfo {
};
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[] = {

View File

@ -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 @@ bool SkSVGSVG::onPrepareToRender(SkSVGRenderContext* ctx) const {
// 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 @@ bool SkSVGSVG::onPrepareToRender(SkSVGRenderContext* ctx) const {
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 @@ void SkSVGSVG::onSetAttribute(SkSVGAttribute attr, const SkSVGValue& v) {
this->setViewBox(*vb);
}
break;
case SkSVGAttribute::kPreserveAspectRatio:
if (const auto* par = v.as<SkSVGPreserveAspectRatioValue>()) {
this->setPreserveAspectRatio(*par);
}
break;
default:
this->INHERITED::onSetAttribute(attr, v);
}