• 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/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