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