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