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