1 /*
2 * Copyright 2016 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8 #include "modules/svg/include/SkSVGNode.h"
9
10 #include "include/core/SkColor.h"
11 #include "include/core/SkM44.h"
12 #include "include/core/SkMatrix.h"
13 #include "include/core/SkPath.h"
14 #include "include/pathops/SkPathOps.h"
15 #include "include/private/base/SkAssert.h"
16 #include "modules/svg/include/SkSVGRenderContext.h"
17 #include "src/base/SkTLazy.h" // IWYU pragma: keep
18
19 #include <algorithm>
20 #include <array>
21 #include <cstddef>
22
SkSVGNode(SkSVGTag t)23 SkSVGNode::SkSVGNode(SkSVGTag t) : fTag(t) {
24 // Uninherited presentation attributes need a non-null default value.
25 fPresentationAttributes.fStopColor.set(SkSVGColor(SK_ColorBLACK));
26 fPresentationAttributes.fStopOpacity.set(SkSVGNumberType(1.0f));
27 fPresentationAttributes.fFloodColor.set(SkSVGColor(SK_ColorBLACK));
28 fPresentationAttributes.fFloodOpacity.set(SkSVGNumberType(1.0f));
29 fPresentationAttributes.fLightingColor.set(SkSVGColor(SK_ColorWHITE));
30 }
31
~SkSVGNode()32 SkSVGNode::~SkSVGNode() { }
33
render(const SkSVGRenderContext & ctx) const34 void SkSVGNode::render(const SkSVGRenderContext& ctx) const {
35 SkSVGRenderContext localContext(ctx, this);
36
37 if (this->onPrepareToRender(&localContext)) {
38 this->onRender(localContext);
39 }
40 }
41
asPaint(const SkSVGRenderContext & ctx,SkPaint * paint) const42 bool SkSVGNode::asPaint(const SkSVGRenderContext& ctx, SkPaint* paint) const {
43 SkSVGRenderContext localContext(ctx);
44
45 return this->onPrepareToRender(&localContext) && this->onAsPaint(localContext, paint);
46 }
47
asPath(const SkSVGRenderContext & ctx) const48 SkPath SkSVGNode::asPath(const SkSVGRenderContext& ctx) const {
49 SkSVGRenderContext localContext(ctx);
50 if (!this->onPrepareToRender(&localContext)) {
51 return SkPath();
52 }
53
54 SkPath path = this->onAsPath(localContext);
55
56 if (const auto* clipPath = localContext.clipPath()) {
57 // There is a clip-path present on the current node.
58 Op(path, *clipPath, kIntersect_SkPathOp, &path);
59 }
60
61 return path;
62 }
63
objectBoundingBox(const SkSVGRenderContext & ctx) const64 SkRect SkSVGNode::objectBoundingBox(const SkSVGRenderContext& ctx) const {
65 return this->onObjectBoundingBox(ctx);
66 }
67
onPrepareToRender(SkSVGRenderContext * ctx) const68 bool SkSVGNode::onPrepareToRender(SkSVGRenderContext* ctx) const {
69 ctx->applyPresentationAttributes(fPresentationAttributes,
70 this->hasChildren() ? 0 : SkSVGRenderContext::kLeaf);
71
72 // visibility:hidden and display:none disable rendering.
73 // TODO: if display is not a value (true when display="inherit"), we currently
74 // ignore it. Eventually we should be able to add SkASSERT(display.isValue()).
75 const auto visibility = ctx->presentationContext().fInherited.fVisibility->type();
76 const auto display = fPresentationAttributes.fDisplay; // display is uninherited
77 return visibility != SkSVGVisibility::Type::kHidden &&
78 (!display.isValue() || *display != SkSVGDisplay::kNone);
79 }
80
setAttribute(SkSVGAttribute attr,const SkSVGValue & v)81 void SkSVGNode::setAttribute(SkSVGAttribute attr, const SkSVGValue& v) {
82 this->onSetAttribute(attr, v);
83 }
84
85 template <typename T>
SetInheritedByDefault(SkTLazy<T> & presentation_attribute,const T & value)86 void SetInheritedByDefault(SkTLazy<T>& presentation_attribute, const T& value) {
87 if (value.type() != T::Type::kInherit) {
88 presentation_attribute.set(value);
89 } else {
90 // kInherited values are semantically equivalent to
91 // the absence of a local presentation attribute.
92 presentation_attribute.reset();
93 }
94 }
95
parseAndSetAttribute(const char * n,const char * v)96 bool SkSVGNode::parseAndSetAttribute(const char* n, const char* v) {
97 #define PARSE_AND_SET(svgName, attrName) \
98 this->set##attrName( \
99 SkSVGAttributeParser::parseProperty<decltype(fPresentationAttributes.f##attrName)>( \
100 svgName, n, v))
101
102 return PARSE_AND_SET( "clip-path" , ClipPath)
103 || PARSE_AND_SET("clip-rule" , ClipRule)
104 || PARSE_AND_SET("color" , Color)
105 || PARSE_AND_SET("color-interpolation" , ColorInterpolation)
106 || PARSE_AND_SET("color-interpolation-filters", ColorInterpolationFilters)
107 || PARSE_AND_SET("display" , Display)
108 || PARSE_AND_SET("fill" , Fill)
109 || PARSE_AND_SET("fill-opacity" , FillOpacity)
110 || PARSE_AND_SET("fill-rule" , FillRule)
111 || PARSE_AND_SET("filter" , Filter)
112 || PARSE_AND_SET("flood-color" , FloodColor)
113 || PARSE_AND_SET("flood-opacity" , FloodOpacity)
114 || PARSE_AND_SET("font-family" , FontFamily)
115 || PARSE_AND_SET("font-size" , FontSize)
116 || PARSE_AND_SET("font-style" , FontStyle)
117 || PARSE_AND_SET("font-weight" , FontWeight)
118 || PARSE_AND_SET("lighting-color" , LightingColor)
119 || PARSE_AND_SET("mask" , Mask)
120 || PARSE_AND_SET("opacity" , Opacity)
121 || PARSE_AND_SET("stop-color" , StopColor)
122 || PARSE_AND_SET("stop-opacity" , StopOpacity)
123 || PARSE_AND_SET("stroke" , Stroke)
124 || PARSE_AND_SET("stroke-dasharray" , StrokeDashArray)
125 || PARSE_AND_SET("stroke-dashoffset" , StrokeDashOffset)
126 || PARSE_AND_SET("stroke-linecap" , StrokeLineCap)
127 || PARSE_AND_SET("stroke-linejoin" , StrokeLineJoin)
128 || PARSE_AND_SET("stroke-miterlimit" , StrokeMiterLimit)
129 || PARSE_AND_SET("stroke-opacity" , StrokeOpacity)
130 || PARSE_AND_SET("stroke-width" , StrokeWidth)
131 || PARSE_AND_SET("text-anchor" , TextAnchor)
132 || PARSE_AND_SET("visibility" , Visibility);
133
134 #undef PARSE_AND_SET
135 }
136
137 // https://www.w3.org/TR/SVG11/coords.html#PreserveAspectRatioAttribute
ComputeViewboxMatrix(const SkRect & viewBox,const SkRect & viewPort,SkSVGPreserveAspectRatio par)138 SkMatrix SkSVGNode::ComputeViewboxMatrix(const SkRect& viewBox,
139 const SkRect& viewPort,
140 SkSVGPreserveAspectRatio par) {
141 if (viewBox.isEmpty() || viewPort.isEmpty()) {
142 return SkMatrix::Scale(0, 0);
143 }
144
145 auto compute_scale = [&]() -> SkV2 {
146 const auto sx = viewPort.width() / viewBox.width(),
147 sy = viewPort.height() / viewBox.height();
148
149 if (par.fAlign == SkSVGPreserveAspectRatio::kNone) {
150 // none -> anisotropic scaling, regardless of fScale
151 return {sx, sy};
152 }
153
154 // isotropic scaling
155 const auto s = par.fScale == SkSVGPreserveAspectRatio::kMeet
156 ? std::min(sx, sy)
157 : std::max(sx, sy);
158 return {s, s};
159 };
160
161 auto compute_trans = [&](const SkV2& scale) -> SkV2 {
162 static constexpr float gAlignCoeffs[] = {
163 0.0f, // Min
164 0.5f, // Mid
165 1.0f // Max
166 };
167
168 const size_t x_coeff = par.fAlign >> 0 & 0x03,
169 y_coeff = par.fAlign >> 2 & 0x03;
170
171 SkASSERT(x_coeff < std::size(gAlignCoeffs) &&
172 y_coeff < std::size(gAlignCoeffs));
173
174 const auto tx = -viewBox.x() * scale.x,
175 ty = -viewBox.y() * scale.y,
176 dx = viewPort.width() - viewBox.width() * scale.x,
177 dy = viewPort.height() - viewBox.height() * scale.y;
178
179 return {
180 tx + dx * gAlignCoeffs[x_coeff],
181 ty + dy * gAlignCoeffs[y_coeff]
182 };
183 };
184
185 const auto s = compute_scale(),
186 t = compute_trans(s);
187
188 return SkMatrix::Translate(t.x, t.y) *
189 SkMatrix::Scale(s.x, s.y);
190 }
191