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