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 "SkCanvas.h"
9 #include "SkDashPathEffect.h"
10 #include "SkPath.h"
11 #include "SkSVGAttribute.h"
12 #include "SkSVGNode.h"
13 #include "SkSVGRenderContext.h"
14 #include "SkSVGTypes.h"
15
16 namespace {
17
length_size_for_type(const SkSize & viewport,SkSVGLengthContext::LengthType t)18 SkScalar length_size_for_type(const SkSize& viewport, SkSVGLengthContext::LengthType t) {
19 switch (t) {
20 case SkSVGLengthContext::LengthType::kHorizontal:
21 return viewport.width();
22 case SkSVGLengthContext::LengthType::kVertical:
23 return viewport.height();
24 case SkSVGLengthContext::LengthType::kOther:
25 return SkScalarSqrt(viewport.width() * viewport.height());
26 }
27
28 SkASSERT(false); // Not reached.
29 return 0;
30 }
31
32 // Multipliers for DPI-relative units.
33 constexpr SkScalar kINMultiplier = 1.00f;
34 constexpr SkScalar kPTMultiplier = kINMultiplier / 72.272f;
35 constexpr SkScalar kPCMultiplier = kPTMultiplier * 12;
36 constexpr SkScalar kMMMultiplier = kINMultiplier / 25.4f;
37 constexpr SkScalar kCMMultiplier = kMMMultiplier * 10;
38
39 } // anonymous ns
40
resolve(const SkSVGLength & l,LengthType t) const41 SkScalar SkSVGLengthContext::resolve(const SkSVGLength& l, LengthType t) const {
42 switch (l.unit()) {
43 case SkSVGLength::Unit::kNumber:
44 // Fall through.
45 case SkSVGLength::Unit::kPX:
46 return l.value();
47 case SkSVGLength::Unit::kPercentage:
48 return l.value() * length_size_for_type(fViewport, t) / 100;
49 case SkSVGLength::Unit::kCM:
50 return l.value() * fDPI * kCMMultiplier;
51 case SkSVGLength::Unit::kMM:
52 return l.value() * fDPI * kMMMultiplier;
53 case SkSVGLength::Unit::kIN:
54 return l.value() * fDPI * kINMultiplier;
55 case SkSVGLength::Unit::kPT:
56 return l.value() * fDPI * kPTMultiplier;
57 case SkSVGLength::Unit::kPC:
58 return l.value() * fDPI * kPCMultiplier;
59 default:
60 SkDebugf("unsupported unit type: <%d>\n", l.unit());
61 return 0;
62 }
63 }
64
resolveRect(const SkSVGLength & x,const SkSVGLength & y,const SkSVGLength & w,const SkSVGLength & h) const65 SkRect SkSVGLengthContext::resolveRect(const SkSVGLength& x, const SkSVGLength& y,
66 const SkSVGLength& w, const SkSVGLength& h) const {
67 return SkRect::MakeXYWH(
68 this->resolve(x, SkSVGLengthContext::LengthType::kHorizontal),
69 this->resolve(y, SkSVGLengthContext::LengthType::kVertical),
70 this->resolve(w, SkSVGLengthContext::LengthType::kHorizontal),
71 this->resolve(h, SkSVGLengthContext::LengthType::kVertical));
72 }
73
74 namespace {
75
toSkCap(const SkSVGLineCap & cap)76 SkPaint::Cap toSkCap(const SkSVGLineCap& cap) {
77 switch (cap.type()) {
78 case SkSVGLineCap::Type::kButt:
79 return SkPaint::kButt_Cap;
80 case SkSVGLineCap::Type::kRound:
81 return SkPaint::kRound_Cap;
82 case SkSVGLineCap::Type::kSquare:
83 return SkPaint::kSquare_Cap;
84 default:
85 SkASSERT(false);
86 return SkPaint::kButt_Cap;
87 }
88 }
89
toSkJoin(const SkSVGLineJoin & join)90 SkPaint::Join toSkJoin(const SkSVGLineJoin& join) {
91 switch (join.type()) {
92 case SkSVGLineJoin::Type::kMiter:
93 return SkPaint::kMiter_Join;
94 case SkSVGLineJoin::Type::kRound:
95 return SkPaint::kRound_Join;
96 case SkSVGLineJoin::Type::kBevel:
97 return SkPaint::kBevel_Join;
98 default:
99 SkASSERT(false);
100 return SkPaint::kMiter_Join;
101 }
102 }
103
applySvgPaint(const SkSVGRenderContext & ctx,const SkSVGPaint & svgPaint,SkPaint * p)104 void applySvgPaint(const SkSVGRenderContext& ctx, const SkSVGPaint& svgPaint, SkPaint* p) {
105 switch (svgPaint.type()) {
106 case SkSVGPaint::Type::kColor:
107 p->setColor(SkColorSetA(svgPaint.color(), p->getAlpha()));
108 break;
109 case SkSVGPaint::Type::kIRI: {
110 const auto* node = ctx.findNodeById(svgPaint.iri());
111 if (!node || !node->asPaint(ctx, p)) {
112 p->setColor(SK_ColorTRANSPARENT);
113 }
114 break;
115 }
116 case SkSVGPaint::Type::kCurrentColor:
117 SkDebugf("unimplemented 'currentColor' paint type");
118 // Fall through.
119 case SkSVGPaint::Type::kNone:
120 // Fall through.
121 case SkSVGPaint::Type::kInherit:
122 break;
123 }
124 }
125
opacity_to_alpha(SkScalar o)126 inline uint8_t opacity_to_alpha(SkScalar o) {
127 return SkTo<uint8_t>(SkScalarRoundToInt(o * 255));
128 }
129
130 // Commit the selected attribute to the paint cache.
131 template <SkSVGAttribute>
132 void commitToPaint(const SkSVGPresentationAttributes&,
133 const SkSVGRenderContext&,
134 SkSVGPresentationContext*);
135
136 template <>
commitToPaint(const SkSVGPresentationAttributes & attrs,const SkSVGRenderContext & ctx,SkSVGPresentationContext * pctx)137 void commitToPaint<SkSVGAttribute::kFill>(const SkSVGPresentationAttributes& attrs,
138 const SkSVGRenderContext& ctx,
139 SkSVGPresentationContext* pctx) {
140 applySvgPaint(ctx, *attrs.fFill.get(), &pctx->fFillPaint);
141 }
142
143 template <>
commitToPaint(const SkSVGPresentationAttributes & attrs,const SkSVGRenderContext & ctx,SkSVGPresentationContext * pctx)144 void commitToPaint<SkSVGAttribute::kStroke>(const SkSVGPresentationAttributes& attrs,
145 const SkSVGRenderContext& ctx,
146 SkSVGPresentationContext* pctx) {
147 applySvgPaint(ctx, *attrs.fStroke.get(), &pctx->fStrokePaint);
148 }
149
150 template <>
commitToPaint(const SkSVGPresentationAttributes & attrs,const SkSVGRenderContext &,SkSVGPresentationContext * pctx)151 void commitToPaint<SkSVGAttribute::kFillOpacity>(const SkSVGPresentationAttributes& attrs,
152 const SkSVGRenderContext&,
153 SkSVGPresentationContext* pctx) {
154 pctx->fFillPaint.setAlpha(opacity_to_alpha(*attrs.fFillOpacity.get()));
155 }
156
157 template <>
commitToPaint(const SkSVGPresentationAttributes & attrs,const SkSVGRenderContext & ctx,SkSVGPresentationContext * pctx)158 void commitToPaint<SkSVGAttribute::kStrokeDashArray>(const SkSVGPresentationAttributes& attrs,
159 const SkSVGRenderContext& ctx,
160 SkSVGPresentationContext* pctx) {
161 const auto& dashArray = attrs.fStrokeDashArray.get();
162 if (dashArray->type() != SkSVGDashArray::Type::kDashArray) {
163 return;
164 }
165
166 const auto count = dashArray->dashArray().count();
167 SkSTArray<128, SkScalar, true> intervals(count);
168 for (const auto& dash : dashArray->dashArray()) {
169 intervals.push_back(ctx.lengthContext().resolve(dash,
170 SkSVGLengthContext::LengthType::kOther));
171 }
172
173 if (count & 1) {
174 // If an odd number of values is provided, then the list of values
175 // is repeated to yield an even number of values.
176 intervals.push_back_n(count);
177 memcpy(intervals.begin() + count, intervals.begin(), count);
178 }
179
180 SkASSERT((intervals.count() & 1) == 0);
181
182 const SkScalar phase = ctx.lengthContext().resolve(*pctx->fInherited.fStrokeDashOffset.get(),
183 SkSVGLengthContext::LengthType::kOther);
184 pctx->fStrokePaint.setPathEffect(SkDashPathEffect::Make(intervals.begin(),
185 intervals.count(),
186 phase));
187 }
188
189 template <>
commitToPaint(const SkSVGPresentationAttributes &,const SkSVGRenderContext &,SkSVGPresentationContext *)190 void commitToPaint<SkSVGAttribute::kStrokeDashOffset>(const SkSVGPresentationAttributes&,
191 const SkSVGRenderContext&,
192 SkSVGPresentationContext*) {
193 // Applied via kStrokeDashArray.
194 }
195
196 template <>
commitToPaint(const SkSVGPresentationAttributes & attrs,const SkSVGRenderContext &,SkSVGPresentationContext * pctx)197 void commitToPaint<SkSVGAttribute::kStrokeLineCap>(const SkSVGPresentationAttributes& attrs,
198 const SkSVGRenderContext&,
199 SkSVGPresentationContext* pctx) {
200 const auto& cap = *attrs.fStrokeLineCap.get();
201 if (cap.type() != SkSVGLineCap::Type::kInherit) {
202 pctx->fStrokePaint.setStrokeCap(toSkCap(cap));
203 }
204 }
205
206 template <>
commitToPaint(const SkSVGPresentationAttributes & attrs,const SkSVGRenderContext &,SkSVGPresentationContext * pctx)207 void commitToPaint<SkSVGAttribute::kStrokeLineJoin>(const SkSVGPresentationAttributes& attrs,
208 const SkSVGRenderContext&,
209 SkSVGPresentationContext* pctx) {
210 const auto& join = *attrs.fStrokeLineJoin.get();
211 if (join.type() != SkSVGLineJoin::Type::kInherit) {
212 pctx->fStrokePaint.setStrokeJoin(toSkJoin(join));
213 }
214 }
215
216 template <>
commitToPaint(const SkSVGPresentationAttributes & attrs,const SkSVGRenderContext &,SkSVGPresentationContext * pctx)217 void commitToPaint<SkSVGAttribute::kStrokeMiterLimit>(const SkSVGPresentationAttributes& attrs,
218 const SkSVGRenderContext&,
219 SkSVGPresentationContext* pctx) {
220 pctx->fStrokePaint.setStrokeMiter(*attrs.fStrokeMiterLimit.get());
221 }
222
223 template <>
commitToPaint(const SkSVGPresentationAttributes & attrs,const SkSVGRenderContext &,SkSVGPresentationContext * pctx)224 void commitToPaint<SkSVGAttribute::kStrokeOpacity>(const SkSVGPresentationAttributes& attrs,
225 const SkSVGRenderContext&,
226 SkSVGPresentationContext* pctx) {
227 pctx->fStrokePaint.setAlpha(opacity_to_alpha(*attrs.fStrokeOpacity.get()));
228 }
229
230 template <>
commitToPaint(const SkSVGPresentationAttributes & attrs,const SkSVGRenderContext & ctx,SkSVGPresentationContext * pctx)231 void commitToPaint<SkSVGAttribute::kStrokeWidth>(const SkSVGPresentationAttributes& attrs,
232 const SkSVGRenderContext& ctx,
233 SkSVGPresentationContext* pctx) {
234 auto strokeWidth = ctx.lengthContext().resolve(*attrs.fStrokeWidth.get(),
235 SkSVGLengthContext::LengthType::kOther);
236 pctx->fStrokePaint.setStrokeWidth(strokeWidth);
237 }
238
239 template <>
commitToPaint(const SkSVGPresentationAttributes &,const SkSVGRenderContext &,SkSVGPresentationContext *)240 void commitToPaint<SkSVGAttribute::kFillRule>(const SkSVGPresentationAttributes&,
241 const SkSVGRenderContext&,
242 SkSVGPresentationContext*) {
243 // Not part of the SkPaint state; applied to the path at render time.
244 }
245
246 template <>
commitToPaint(const SkSVGPresentationAttributes &,const SkSVGRenderContext &,SkSVGPresentationContext *)247 void commitToPaint<SkSVGAttribute::kClipRule>(const SkSVGPresentationAttributes&,
248 const SkSVGRenderContext&,
249 SkSVGPresentationContext*) {
250 // Not part of the SkPaint state; applied to the path at clip time.
251 }
252
253 template <>
commitToPaint(const SkSVGPresentationAttributes &,const SkSVGRenderContext &,SkSVGPresentationContext *)254 void commitToPaint<SkSVGAttribute::kVisibility>(const SkSVGPresentationAttributes&,
255 const SkSVGRenderContext&,
256 SkSVGPresentationContext*) {
257 // Not part of the SkPaint state; queried to veto rendering.
258 }
259
260 } // anonymous ns
261
SkSVGPresentationContext()262 SkSVGPresentationContext::SkSVGPresentationContext()
263 : fInherited(SkSVGPresentationAttributes::MakeInitial()) {
264
265 fFillPaint.setStyle(SkPaint::kFill_Style);
266 fStrokePaint.setStyle(SkPaint::kStroke_Style);
267
268 // TODO: drive AA off presentation attrs also (shape-rendering?)
269 fFillPaint.setAntiAlias(true);
270 fStrokePaint.setAntiAlias(true);
271
272 // Commit initial values to the paint cache.
273 SkCanvas dummyCanvas(0, 0);
274 SkSVGRenderContext dummy(&dummyCanvas, SkSVGIDMapper(), SkSVGLengthContext(SkSize::Make(0, 0)),
275 *this);
276
277 commitToPaint<SkSVGAttribute::kFill>(fInherited, dummy, this);
278 commitToPaint<SkSVGAttribute::kFillOpacity>(fInherited, dummy, this);
279 commitToPaint<SkSVGAttribute::kStroke>(fInherited, dummy, this);
280 commitToPaint<SkSVGAttribute::kStrokeLineCap>(fInherited, dummy, this);
281 commitToPaint<SkSVGAttribute::kStrokeLineJoin>(fInherited, dummy, this);
282 commitToPaint<SkSVGAttribute::kStrokeMiterLimit>(fInherited, dummy, this);
283 commitToPaint<SkSVGAttribute::kStrokeOpacity>(fInherited, dummy, this);
284 commitToPaint<SkSVGAttribute::kStrokeWidth>(fInherited, dummy, this);
285 }
286
SkSVGRenderContext(SkCanvas * canvas,const SkSVGIDMapper & mapper,const SkSVGLengthContext & lctx,const SkSVGPresentationContext & pctx)287 SkSVGRenderContext::SkSVGRenderContext(SkCanvas* canvas,
288 const SkSVGIDMapper& mapper,
289 const SkSVGLengthContext& lctx,
290 const SkSVGPresentationContext& pctx)
291 : fIDMapper(mapper)
292 , fLengthContext(lctx)
293 , fPresentationContext(pctx)
294 , fCanvas(canvas)
295 , fCanvasSaveCount(canvas->getSaveCount()) {}
296
SkSVGRenderContext(const SkSVGRenderContext & other)297 SkSVGRenderContext::SkSVGRenderContext(const SkSVGRenderContext& other)
298 : SkSVGRenderContext(other.fCanvas,
299 other.fIDMapper,
300 *other.fLengthContext,
301 *other.fPresentationContext) {}
302
SkSVGRenderContext(const SkSVGRenderContext & other,SkCanvas * canvas)303 SkSVGRenderContext::SkSVGRenderContext(const SkSVGRenderContext& other, SkCanvas* canvas)
304 : SkSVGRenderContext(canvas,
305 other.fIDMapper,
306 *other.fLengthContext,
307 *other.fPresentationContext) {}
308
~SkSVGRenderContext()309 SkSVGRenderContext::~SkSVGRenderContext() {
310 fCanvas->restoreToCount(fCanvasSaveCount);
311 }
312
findNodeById(const SkString & id) const313 const SkSVGNode* SkSVGRenderContext::findNodeById(const SkString& id) const {
314 const auto* v = fIDMapper.find(id);
315 return v ? v->get() : nullptr;
316 }
317
applyPresentationAttributes(const SkSVGPresentationAttributes & attrs,uint32_t flags)318 void SkSVGRenderContext::applyPresentationAttributes(const SkSVGPresentationAttributes& attrs,
319 uint32_t flags) {
320
321 #define ApplyLazyInheritedAttribute(ATTR) \
322 do { \
323 /* All attributes should be defined on the inherited context. */ \
324 SkASSERT(fPresentationContext->fInherited.f ## ATTR.isValid()); \
325 const auto* value = attrs.f ## ATTR.getMaybeNull(); \
326 if (value && *value != *fPresentationContext->fInherited.f ## ATTR.get()) { \
327 /* Update the local attribute value */ \
328 fPresentationContext.writable()->fInherited.f ## ATTR.set(*value); \
329 /* Update the cached paints */ \
330 commitToPaint<SkSVGAttribute::k ## ATTR>(attrs, *this, \
331 fPresentationContext.writable()); \
332 } \
333 } while (false)
334
335 ApplyLazyInheritedAttribute(Fill);
336 ApplyLazyInheritedAttribute(FillOpacity);
337 ApplyLazyInheritedAttribute(FillRule);
338 ApplyLazyInheritedAttribute(ClipRule);
339 ApplyLazyInheritedAttribute(Stroke);
340 ApplyLazyInheritedAttribute(StrokeDashOffset);
341 ApplyLazyInheritedAttribute(StrokeDashArray);
342 ApplyLazyInheritedAttribute(StrokeLineCap);
343 ApplyLazyInheritedAttribute(StrokeLineJoin);
344 ApplyLazyInheritedAttribute(StrokeMiterLimit);
345 ApplyLazyInheritedAttribute(StrokeOpacity);
346 ApplyLazyInheritedAttribute(StrokeWidth);
347 ApplyLazyInheritedAttribute(Visibility);
348
349 #undef ApplyLazyInheritedAttribute
350
351 // Uninherited attributes. Only apply to the current context.
352
353 if (auto* opacity = attrs.fOpacity.getMaybeNull()) {
354 this->applyOpacity(opacity->value(), flags);
355 }
356
357 if (auto* clip = attrs.fClipPath.getMaybeNull()) {
358 this->applyClip(*clip);
359 }
360 }
361
applyOpacity(SkScalar opacity,uint32_t flags)362 void SkSVGRenderContext::applyOpacity(SkScalar opacity, uint32_t flags) {
363 if (opacity >= 1) {
364 return;
365 }
366
367 const bool hasFill = SkToBool(this->fillPaint());
368 const bool hasStroke = SkToBool(this->strokePaint());
369
370 // We can apply the opacity as paint alpha iif it only affects one atomic draw.
371 // For now, this means a) the target node doesn't have any descendants, and
372 // b) it only has a stroke or a fill (but not both). Going forward, we may need
373 // to refine this heuristic (e.g. to accommodate markers).
374 if ((flags & kLeaf) && (hasFill ^ hasStroke)) {
375 auto* pctx = fPresentationContext.writable();
376 if (hasFill) {
377 pctx->fFillPaint.setAlpha(
378 SkScalarRoundToInt(opacity * pctx->fFillPaint.getAlpha()));
379 } else {
380 pctx->fStrokePaint.setAlpha(
381 SkScalarRoundToInt(opacity * pctx->fStrokePaint.getAlpha()));
382 }
383 } else {
384 // Expensive, layer-based fall back.
385 SkPaint opacityPaint;
386 opacityPaint.setAlpha(opacity_to_alpha(opacity));
387 // Balanced in the destructor, via restoreToCount().
388 fCanvas->saveLayer(nullptr, &opacityPaint);
389 }
390 }
391
saveOnce()392 void SkSVGRenderContext::saveOnce() {
393 // The canvas only needs to be saved once, per local SkSVGRenderContext.
394 if (fCanvas->getSaveCount() == fCanvasSaveCount) {
395 fCanvas->save();
396 }
397
398 SkASSERT(fCanvas->getSaveCount() > fCanvasSaveCount);
399 }
400
applyClip(const SkSVGClip & clip)401 void SkSVGRenderContext::applyClip(const SkSVGClip& clip) {
402 if (clip.type() != SkSVGClip::Type::kIRI) {
403 return;
404 }
405
406 const SkSVGNode* clipNode = this->findNodeById(clip.iri());
407 if (!clipNode || clipNode->tag() != SkSVGTag::kClipPath) {
408 return;
409 }
410
411 const SkPath clipPath = clipNode->asPath(*this);
412
413 // We use the computed clip path in two ways:
414 //
415 // - apply to the current canvas, for drawing
416 // - track in the presentation context, for asPath() composition
417 //
418 // TODO: the two uses are exclusive, avoid canvas churn when non needed.
419
420 this->saveOnce();
421
422 fCanvas->clipPath(clipPath, true);
423 fClipPath.set(clipPath);
424 }
425
fillPaint() const426 const SkPaint* SkSVGRenderContext::fillPaint() const {
427 const SkSVGPaint::Type paintType = fPresentationContext->fInherited.fFill.get()->type();
428 return paintType != SkSVGPaint::Type::kNone ? &fPresentationContext->fFillPaint : nullptr;
429 }
430
strokePaint() const431 const SkPaint* SkSVGRenderContext::strokePaint() const {
432 const SkSVGPaint::Type paintType = fPresentationContext->fInherited.fStroke.get()->type();
433 return paintType != SkSVGPaint::Type::kNone ? &fPresentationContext->fStrokePaint : nullptr;
434 }
435