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/SkSVGRenderContext.h"
9
10 #include "include/core/SkCanvas.h"
11 #include "include/core/SkImageFilter.h"
12 #include "include/core/SkPath.h"
13 #include "include/core/SkPathEffect.h"
14 #include "include/effects/SkDashPathEffect.h"
15 #include "include/private/base/SkTo.h"
16 #include "modules/svg/include/SkSVGAttribute.h"
17 #include "modules/svg/include/SkSVGClipPath.h"
18 #include "modules/svg/include/SkSVGFilter.h"
19 #include "modules/svg/include/SkSVGMask.h"
20 #include "modules/svg/include/SkSVGNode.h"
21 #include "modules/svg/include/SkSVGTypes.h"
22
23 namespace {
24
length_size_for_type(const SkSize & viewport,SkSVGLengthContext::LengthType t)25 SkScalar length_size_for_type(const SkSize& viewport, SkSVGLengthContext::LengthType t) {
26 switch (t) {
27 case SkSVGLengthContext::LengthType::kHorizontal:
28 return viewport.width();
29 case SkSVGLengthContext::LengthType::kVertical:
30 return viewport.height();
31 case SkSVGLengthContext::LengthType::kOther: {
32 // https://www.w3.org/TR/SVG11/coords.html#Units_viewport_percentage
33 constexpr SkScalar rsqrt2 = 1.0f / SK_ScalarSqrt2;
34 const SkScalar w = viewport.width(), h = viewport.height();
35 return rsqrt2 * SkScalarSqrt(w * w + h * h);
36 }
37 }
38
39 SkASSERT(false); // Not reached.
40 return 0;
41 }
42
43 // Multipliers for DPI-relative units.
44 constexpr SkScalar kINMultiplier = 1.00f;
45 constexpr SkScalar kPTMultiplier = kINMultiplier / 72.272f;
46 constexpr SkScalar kPCMultiplier = kPTMultiplier * 12;
47 constexpr SkScalar kMMMultiplier = kINMultiplier / 25.4f;
48 constexpr SkScalar kCMMultiplier = kMMMultiplier * 10;
49
50 } // namespace
51
resolve(const SkSVGLength & l,LengthType t) const52 SkScalar SkSVGLengthContext::resolve(const SkSVGLength& l, LengthType t) const {
53 switch (l.unit()) {
54 case SkSVGLength::Unit::kNumber:
55 // Fall through.
56 case SkSVGLength::Unit::kPX:
57 return l.value();
58 case SkSVGLength::Unit::kPercentage:
59 return l.value() * length_size_for_type(fViewport, t) / 100;
60 case SkSVGLength::Unit::kCM:
61 return l.value() * fDPI * kCMMultiplier;
62 case SkSVGLength::Unit::kMM:
63 return l.value() * fDPI * kMMMultiplier;
64 case SkSVGLength::Unit::kIN:
65 return l.value() * fDPI * kINMultiplier;
66 case SkSVGLength::Unit::kPT:
67 return l.value() * fDPI * kPTMultiplier;
68 case SkSVGLength::Unit::kPC:
69 return l.value() * fDPI * kPCMultiplier;
70 default:
71 SkDebugf("unsupported unit type: <%d>\n", (int)l.unit());
72 return 0;
73 }
74 }
75
resolveRect(const SkSVGLength & x,const SkSVGLength & y,const SkSVGLength & w,const SkSVGLength & h) const76 SkRect SkSVGLengthContext::resolveRect(const SkSVGLength& x, const SkSVGLength& y,
77 const SkSVGLength& w, const SkSVGLength& h) const {
78 return SkRect::MakeXYWH(
79 this->resolve(x, SkSVGLengthContext::LengthType::kHorizontal),
80 this->resolve(y, SkSVGLengthContext::LengthType::kVertical),
81 this->resolve(w, SkSVGLengthContext::LengthType::kHorizontal),
82 this->resolve(h, SkSVGLengthContext::LengthType::kVertical));
83 }
84
85 namespace {
86
toSkCap(const SkSVGLineCap & cap)87 SkPaint::Cap toSkCap(const SkSVGLineCap& cap) {
88 switch (cap) {
89 case SkSVGLineCap::kButt:
90 return SkPaint::kButt_Cap;
91 case SkSVGLineCap::kRound:
92 return SkPaint::kRound_Cap;
93 case SkSVGLineCap::kSquare:
94 return SkPaint::kSquare_Cap;
95 }
96 SkUNREACHABLE;
97 }
98
toSkJoin(const SkSVGLineJoin & join)99 SkPaint::Join toSkJoin(const SkSVGLineJoin& join) {
100 switch (join.type()) {
101 case SkSVGLineJoin::Type::kMiter:
102 return SkPaint::kMiter_Join;
103 case SkSVGLineJoin::Type::kRound:
104 return SkPaint::kRound_Join;
105 case SkSVGLineJoin::Type::kBevel:
106 return SkPaint::kBevel_Join;
107 default:
108 SkASSERT(false);
109 return SkPaint::kMiter_Join;
110 }
111 }
112
dash_effect(const SkSVGPresentationAttributes & props,const SkSVGLengthContext & lctx)113 static sk_sp<SkPathEffect> dash_effect(const SkSVGPresentationAttributes& props,
114 const SkSVGLengthContext& lctx) {
115 if (props.fStrokeDashArray->type() != SkSVGDashArray::Type::kDashArray) {
116 return nullptr;
117 }
118
119 const auto& da = *props.fStrokeDashArray;
120 const auto count = da.dashArray().size();
121 SkSTArray<128, SkScalar, true> intervals(count);
122 for (const auto& dash : da.dashArray()) {
123 intervals.push_back(lctx.resolve(dash, SkSVGLengthContext::LengthType::kOther));
124 }
125
126 if (count & 1) {
127 // If an odd number of values is provided, then the list of values
128 // is repeated to yield an even number of values.
129 intervals.push_back_n(count);
130 memcpy(intervals.begin() + count, intervals.begin(), count * sizeof(SkScalar));
131 }
132
133 SkASSERT((intervals.size() & 1) == 0);
134
135 const auto phase = lctx.resolve(*props.fStrokeDashOffset,
136 SkSVGLengthContext::LengthType::kOther);
137
138 return SkDashPathEffect::Make(intervals.begin(), intervals.size(), phase);
139 }
140
141 } // namespace
142
SkSVGPresentationContext()143 SkSVGPresentationContext::SkSVGPresentationContext()
144 : fInherited(SkSVGPresentationAttributes::MakeInitial())
145 {}
146
SkSVGRenderContext(SkCanvas * canvas,const sk_sp<SkFontMgr> & fmgr,const sk_sp<skresources::ResourceProvider> & rp,const SkSVGIDMapper & mapper,const SkSVGLengthContext & lctx,const SkSVGPresentationContext & pctx,const OBBScope & obbs)147 SkSVGRenderContext::SkSVGRenderContext(SkCanvas* canvas,
148 const sk_sp<SkFontMgr>& fmgr,
149 const sk_sp<skresources::ResourceProvider>& rp,
150 const SkSVGIDMapper& mapper,
151 const SkSVGLengthContext& lctx,
152 const SkSVGPresentationContext& pctx,
153 const OBBScope& obbs)
154 : fFontMgr(fmgr)
155 , fResourceProvider(rp)
156 , fIDMapper(mapper)
157 , fLengthContext(lctx)
158 , fPresentationContext(pctx)
159 , fCanvas(canvas)
160 , fCanvasSaveCount(canvas->getSaveCount())
161 , fOBBScope(obbs) {}
162
SkSVGRenderContext(const SkSVGRenderContext & other)163 SkSVGRenderContext::SkSVGRenderContext(const SkSVGRenderContext& other)
164 : SkSVGRenderContext(other.fCanvas,
165 other.fFontMgr,
166 other.fResourceProvider,
167 other.fIDMapper,
168 *other.fLengthContext,
169 *other.fPresentationContext,
170 other.fOBBScope) {}
171
SkSVGRenderContext(const SkSVGRenderContext & other,SkCanvas * canvas)172 SkSVGRenderContext::SkSVGRenderContext(const SkSVGRenderContext& other, SkCanvas* canvas)
173 : SkSVGRenderContext(canvas,
174 other.fFontMgr,
175 other.fResourceProvider,
176 other.fIDMapper,
177 *other.fLengthContext,
178 *other.fPresentationContext,
179 other.fOBBScope) {}
180
SkSVGRenderContext(const SkSVGRenderContext & other,const SkSVGNode * node)181 SkSVGRenderContext::SkSVGRenderContext(const SkSVGRenderContext& other, const SkSVGNode* node)
182 : SkSVGRenderContext(other.fCanvas,
183 other.fFontMgr,
184 other.fResourceProvider,
185 other.fIDMapper,
186 *other.fLengthContext,
187 *other.fPresentationContext,
188 OBBScope{node, this}) {}
189
~SkSVGRenderContext()190 SkSVGRenderContext::~SkSVGRenderContext() {
191 fCanvas->restoreToCount(fCanvasSaveCount);
192 }
193
findNodeById(const SkSVGIRI & iri) const194 SkSVGRenderContext::BorrowedNode SkSVGRenderContext::findNodeById(const SkSVGIRI& iri) const {
195 if (iri.type() != SkSVGIRI::Type::kLocal) {
196 SkDebugf("non-local iri references not currently supported");
197 return BorrowedNode(nullptr);
198 }
199 return BorrowedNode(fIDMapper.find(iri.iri()));
200 }
201
applyPresentationAttributes(const SkSVGPresentationAttributes & attrs,uint32_t flags)202 void SkSVGRenderContext::applyPresentationAttributes(const SkSVGPresentationAttributes& attrs,
203 uint32_t flags) {
204
205 #define ApplyLazyInheritedAttribute(ATTR) \
206 do { \
207 /* All attributes should be defined on the inherited context. */ \
208 SkASSERT(fPresentationContext->fInherited.f ## ATTR.isValue()); \
209 const auto& attr = attrs.f ## ATTR; \
210 if (attr.isValue() && *attr != *fPresentationContext->fInherited.f ## ATTR) { \
211 /* Update the local attribute value */ \
212 fPresentationContext.writable()->fInherited.f ## ATTR.set(*attr); \
213 } \
214 } while (false)
215
216 ApplyLazyInheritedAttribute(Fill);
217 ApplyLazyInheritedAttribute(FillOpacity);
218 ApplyLazyInheritedAttribute(FillRule);
219 ApplyLazyInheritedAttribute(FontFamily);
220 ApplyLazyInheritedAttribute(FontSize);
221 ApplyLazyInheritedAttribute(FontStyle);
222 ApplyLazyInheritedAttribute(FontWeight);
223 ApplyLazyInheritedAttribute(ClipRule);
224 ApplyLazyInheritedAttribute(Stroke);
225 ApplyLazyInheritedAttribute(StrokeDashOffset);
226 ApplyLazyInheritedAttribute(StrokeDashArray);
227 ApplyLazyInheritedAttribute(StrokeLineCap);
228 ApplyLazyInheritedAttribute(StrokeLineJoin);
229 ApplyLazyInheritedAttribute(StrokeMiterLimit);
230 ApplyLazyInheritedAttribute(StrokeOpacity);
231 ApplyLazyInheritedAttribute(StrokeWidth);
232 ApplyLazyInheritedAttribute(TextAnchor);
233 ApplyLazyInheritedAttribute(Visibility);
234 ApplyLazyInheritedAttribute(Color);
235 ApplyLazyInheritedAttribute(ColorInterpolation);
236 ApplyLazyInheritedAttribute(ColorInterpolationFilters);
237
238 #undef ApplyLazyInheritedAttribute
239
240 // Uninherited attributes. Only apply to the current context.
241
242 const bool hasFilter = attrs.fFilter.isValue();
243 if (attrs.fOpacity.isValue()) {
244 this->applyOpacity(*attrs.fOpacity, flags, hasFilter);
245 }
246
247 if (attrs.fClipPath.isValue()) {
248 this->applyClip(*attrs.fClipPath);
249 }
250
251 if (attrs.fMask.isValue()) {
252 this->applyMask(*attrs.fMask);
253 }
254
255 // TODO: when both a filter and opacity are present, we can apply both with a single layer
256 if (hasFilter) {
257 this->applyFilter(*attrs.fFilter);
258 }
259
260 // Remaining uninherited presentation attributes are accessed as SkSVGNode fields, not via
261 // the render context.
262 // TODO: resolve these in a pre-render styling pass and assert here that they are values.
263 // - stop-color
264 // - stop-opacity
265 // - flood-color
266 // - flood-opacity
267 // - lighting-color
268 }
269
applyOpacity(SkScalar opacity,uint32_t flags,bool hasFilter)270 void SkSVGRenderContext::applyOpacity(SkScalar opacity, uint32_t flags, bool hasFilter) {
271 if (opacity >= 1) {
272 return;
273 }
274
275 const auto& props = fPresentationContext->fInherited;
276 const bool hasFill = props.fFill ->type() != SkSVGPaint::Type::kNone,
277 hasStroke = props.fStroke->type() != SkSVGPaint::Type::kNone;
278
279 // We can apply the opacity as paint alpha if it only affects one atomic draw.
280 // For now, this means all of the following must be true:
281 // - the target node doesn't have any descendants;
282 // - it only has a stroke or a fill (but not both);
283 // - it does not have a filter.
284 // Going forward, we may needto refine this heuristic (e.g. to accommodate markers).
285 if ((flags & kLeaf) && (hasFill ^ hasStroke) && !hasFilter) {
286 fDeferredPaintOpacity *= opacity;
287 } else {
288 // Expensive, layer-based fall back.
289 SkPaint opacityPaint;
290 opacityPaint.setAlphaf(SkTPin(opacity, 0.0f, 1.0f));
291 // Balanced in the destructor, via restoreToCount().
292 fCanvas->saveLayer(nullptr, &opacityPaint);
293 }
294 }
295
applyFilter(const SkSVGFuncIRI & filter)296 void SkSVGRenderContext::applyFilter(const SkSVGFuncIRI& filter) {
297 if (filter.type() != SkSVGFuncIRI::Type::kIRI) {
298 return;
299 }
300
301 const auto node = this->findNodeById(filter.iri());
302 if (!node || node->tag() != SkSVGTag::kFilter) {
303 return;
304 }
305
306 const SkSVGFilter* filterNode = reinterpret_cast<const SkSVGFilter*>(node.get());
307 sk_sp<SkImageFilter> imageFilter = filterNode->buildFilterDAG(*this);
308 if (imageFilter) {
309 SkPaint filterPaint;
310 filterPaint.setImageFilter(imageFilter);
311 // Balanced in the destructor, via restoreToCount().
312 fCanvas->saveLayer(nullptr, &filterPaint);
313 }
314 }
315
saveOnce()316 void SkSVGRenderContext::saveOnce() {
317 // The canvas only needs to be saved once, per local SkSVGRenderContext.
318 if (fCanvas->getSaveCount() == fCanvasSaveCount) {
319 fCanvas->save();
320 }
321
322 SkASSERT(fCanvas->getSaveCount() > fCanvasSaveCount);
323 }
324
applyClip(const SkSVGFuncIRI & clip)325 void SkSVGRenderContext::applyClip(const SkSVGFuncIRI& clip) {
326 if (clip.type() != SkSVGFuncIRI::Type::kIRI) {
327 return;
328 }
329
330 const auto clipNode = this->findNodeById(clip.iri());
331 if (!clipNode || clipNode->tag() != SkSVGTag::kClipPath) {
332 return;
333 }
334
335 const SkPath clipPath = static_cast<const SkSVGClipPath*>(clipNode.get())->resolveClip(*this);
336
337 // We use the computed clip path in two ways:
338 //
339 // - apply to the current canvas, for drawing
340 // - track in the presentation context, for asPath() composition
341 //
342 // TODO: the two uses are exclusive, avoid canvas churn when non needed.
343
344 this->saveOnce();
345
346 fCanvas->clipPath(clipPath, true);
347 fClipPath.set(clipPath);
348 }
349
applyMask(const SkSVGFuncIRI & mask)350 void SkSVGRenderContext::applyMask(const SkSVGFuncIRI& mask) {
351 if (mask.type() != SkSVGFuncIRI::Type::kIRI) {
352 return;
353 }
354
355 const auto node = this->findNodeById(mask.iri());
356 if (!node || node->tag() != SkSVGTag::kMask) {
357 return;
358 }
359
360 const auto* mask_node = static_cast<const SkSVGMask*>(node.get());
361 const auto mask_bounds = mask_node->bounds(*this);
362
363 // Isolation/mask layer.
364 fCanvas->saveLayer(mask_bounds, nullptr);
365
366 // Render and filter mask content.
367 mask_node->renderMask(*this);
368
369 // Content layer
370 SkPaint masking_paint;
371 masking_paint.setBlendMode(SkBlendMode::kSrcIn);
372 fCanvas->saveLayer(mask_bounds, &masking_paint);
373
374 // Content is also clipped to the specified mask bounds.
375 fCanvas->clipRect(mask_bounds, true);
376
377 // At this point we're set up for content rendering.
378 // The pending layers are restored in the destructor (render context scope exit).
379 // Restoring triggers srcIn-compositing the content against the mask.
380 }
381
commonPaint(const SkSVGPaint & paint_selector,float paint_opacity) const382 SkTLazy<SkPaint> SkSVGRenderContext::commonPaint(const SkSVGPaint& paint_selector,
383 float paint_opacity) const {
384 if (paint_selector.type() == SkSVGPaint::Type::kNone) {
385 return SkTLazy<SkPaint>();
386 }
387
388 SkTLazy<SkPaint> p;
389 p.init();
390
391 switch (paint_selector.type()) {
392 case SkSVGPaint::Type::kColor:
393 p->setColor(this->resolveSvgColor(paint_selector.color()));
394 break;
395 case SkSVGPaint::Type::kIRI: {
396 // Our property inheritance is borked as it follows the render path and not the tree
397 // hierarchy. To avoid gross transgressions like leaf node presentation attributes
398 // leaking into the paint server context, use a pristine presentation context when
399 // following hrefs.
400 //
401 // Preserve the OBB scope because some paints use object bounding box coords
402 // (e.g. gradient control points), which requires access to the render context
403 // and node being rendered.
404 SkSVGPresentationContext pctx;
405 pctx.fNamedColors = fPresentationContext->fNamedColors;
406 SkSVGRenderContext local_ctx(fCanvas,
407 fFontMgr,
408 fResourceProvider,
409 fIDMapper,
410 *fLengthContext,
411 pctx,
412 fOBBScope);
413
414 const auto node = this->findNodeById(paint_selector.iri());
415 if (!node || !node->asPaint(local_ctx, p.get())) {
416 // Use the fallback color.
417 p->setColor(this->resolveSvgColor(paint_selector.color()));
418 }
419 } break;
420 default:
421 SkUNREACHABLE;
422 }
423
424 p->setAntiAlias(true); // TODO: shape-rendering support
425
426 // We observe 3 opacity components:
427 // - initial paint server opacity (e.g. color stop opacity)
428 // - paint-specific opacity (e.g. 'fill-opacity', 'stroke-opacity')
429 // - deferred opacity override (optimization for leaf nodes 'opacity')
430 p->setAlphaf(SkTPin(p->getAlphaf() * paint_opacity * fDeferredPaintOpacity, 0.0f, 1.0f));
431
432 return p;
433 }
434
fillPaint() const435 SkTLazy<SkPaint> SkSVGRenderContext::fillPaint() const {
436 const auto& props = fPresentationContext->fInherited;
437 auto p = this->commonPaint(*props.fFill, *props.fFillOpacity);
438
439 if (p.isValid()) {
440 p->setStyle(SkPaint::kFill_Style);
441 }
442
443 return p;
444 }
445
strokePaint() const446 SkTLazy<SkPaint> SkSVGRenderContext::strokePaint() const {
447 const auto& props = fPresentationContext->fInherited;
448 auto p = this->commonPaint(*props.fStroke, *props.fStrokeOpacity);
449
450 if (p.isValid()) {
451 p->setStyle(SkPaint::kStroke_Style);
452 p->setStrokeWidth(fLengthContext->resolve(*props.fStrokeWidth,
453 SkSVGLengthContext::LengthType::kOther));
454 p->setStrokeCap(toSkCap(*props.fStrokeLineCap));
455 p->setStrokeJoin(toSkJoin(*props.fStrokeLineJoin));
456 p->setStrokeMiter(*props.fStrokeMiterLimit);
457 p->setPathEffect(dash_effect(props, *fLengthContext));
458 }
459
460 return p;
461 }
462
resolveSvgColor(const SkSVGColor & color) const463 SkSVGColorType SkSVGRenderContext::resolveSvgColor(const SkSVGColor& color) const {
464 if (fPresentationContext->fNamedColors) {
465 for (auto&& ident : color.vars()) {
466 SkSVGColorType* c = fPresentationContext->fNamedColors->find(ident);
467 if (c) {
468 return *c;
469 }
470 }
471 }
472 switch (color.type()) {
473 case SkSVGColor::Type::kColor:
474 return color.color();
475 case SkSVGColor::Type::kCurrentColor:
476 return *fPresentationContext->fInherited.fColor;
477 case SkSVGColor::Type::kICCColor:
478 SkDebugf("ICC color unimplemented");
479 return SK_ColorBLACK;
480 }
481 SkUNREACHABLE;
482 }
483
484 SkSVGRenderContext::OBBTransform
transformForCurrentOBB(SkSVGObjectBoundingBoxUnits u) const485 SkSVGRenderContext::transformForCurrentOBB(SkSVGObjectBoundingBoxUnits u) const {
486 if (!fOBBScope.fNode || u.type() == SkSVGObjectBoundingBoxUnits::Type::kUserSpaceOnUse) {
487 return {{0,0},{1,1}};
488 }
489 SkASSERT(fOBBScope.fCtx);
490
491 const auto obb = fOBBScope.fNode->objectBoundingBox(*fOBBScope.fCtx);
492 return {{obb.x(), obb.y()}, {obb.width(), obb.height()}};
493 }
494
resolveOBBRect(const SkSVGLength & x,const SkSVGLength & y,const SkSVGLength & w,const SkSVGLength & h,SkSVGObjectBoundingBoxUnits obbu) const495 SkRect SkSVGRenderContext::resolveOBBRect(const SkSVGLength& x, const SkSVGLength& y,
496 const SkSVGLength& w, const SkSVGLength& h,
497 SkSVGObjectBoundingBoxUnits obbu) const {
498 SkTCopyOnFirstWrite<SkSVGLengthContext> lctx(fLengthContext);
499
500 if (obbu.type() == SkSVGObjectBoundingBoxUnits::Type::kObjectBoundingBox) {
501 *lctx.writable() = SkSVGLengthContext({1,1});
502 }
503
504 auto r = lctx->resolveRect(x, y, w, h);
505 const auto obbt = this->transformForCurrentOBB(obbu);
506
507 return SkRect::MakeXYWH(obbt.scale.x * r.x() + obbt.offset.x,
508 obbt.scale.y * r.y() + obbt.offset.y,
509 obbt.scale.x * r.width(),
510 obbt.scale.y * r.height());
511 }
512