• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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