• 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 "SkSVGRenderContext.h"
9 
10 #include "SkCanvas.h"
11 #include "SkDashPathEffect.h"
12 #include "SkPath.h"
13 #include "SkSVGAttribute.h"
14 #include "SkSVGNode.h"
15 #include "SkSVGTypes.h"
16 #include "SkTo.h"
17 
18 namespace {
19 
length_size_for_type(const SkSize & viewport,SkSVGLengthContext::LengthType t)20 SkScalar length_size_for_type(const SkSize& viewport, SkSVGLengthContext::LengthType t) {
21     switch (t) {
22     case SkSVGLengthContext::LengthType::kHorizontal:
23         return viewport.width();
24     case SkSVGLengthContext::LengthType::kVertical:
25         return viewport.height();
26     case SkSVGLengthContext::LengthType::kOther:
27         return SkScalarSqrt(viewport.width() * viewport.height());
28     }
29 
30     SkASSERT(false);  // Not reached.
31     return 0;
32 }
33 
34 // Multipliers for DPI-relative units.
35 constexpr SkScalar kINMultiplier = 1.00f;
36 constexpr SkScalar kPTMultiplier = kINMultiplier / 72.272f;
37 constexpr SkScalar kPCMultiplier = kPTMultiplier * 12;
38 constexpr SkScalar kMMMultiplier = kINMultiplier / 25.4f;
39 constexpr SkScalar kCMMultiplier = kMMMultiplier * 10;
40 
41 } // anonymous ns
42 
resolve(const SkSVGLength & l,LengthType t) const43 SkScalar SkSVGLengthContext::resolve(const SkSVGLength& l, LengthType t) const {
44     switch (l.unit()) {
45     case SkSVGLength::Unit::kNumber:
46         // Fall through.
47     case SkSVGLength::Unit::kPX:
48         return l.value();
49     case SkSVGLength::Unit::kPercentage:
50         return l.value() * length_size_for_type(fViewport, t) / 100;
51     case SkSVGLength::Unit::kCM:
52         return l.value() * fDPI * kCMMultiplier;
53     case SkSVGLength::Unit::kMM:
54         return l.value() * fDPI * kMMMultiplier;
55     case SkSVGLength::Unit::kIN:
56         return l.value() * fDPI * kINMultiplier;
57     case SkSVGLength::Unit::kPT:
58         return l.value() * fDPI * kPTMultiplier;
59     case SkSVGLength::Unit::kPC:
60         return l.value() * fDPI * kPCMultiplier;
61     default:
62         SkDebugf("unsupported unit type: <%d>\n", l.unit());
63         return 0;
64     }
65 }
66 
resolveRect(const SkSVGLength & x,const SkSVGLength & y,const SkSVGLength & w,const SkSVGLength & h) const67 SkRect SkSVGLengthContext::resolveRect(const SkSVGLength& x, const SkSVGLength& y,
68                                        const SkSVGLength& w, const SkSVGLength& h) const {
69     return SkRect::MakeXYWH(
70         this->resolve(x, SkSVGLengthContext::LengthType::kHorizontal),
71         this->resolve(y, SkSVGLengthContext::LengthType::kVertical),
72         this->resolve(w, SkSVGLengthContext::LengthType::kHorizontal),
73         this->resolve(h, SkSVGLengthContext::LengthType::kVertical));
74 }
75 
76 namespace {
77 
toSkCap(const SkSVGLineCap & cap)78 SkPaint::Cap toSkCap(const SkSVGLineCap& cap) {
79     switch (cap.type()) {
80     case SkSVGLineCap::Type::kButt:
81         return SkPaint::kButt_Cap;
82     case SkSVGLineCap::Type::kRound:
83         return SkPaint::kRound_Cap;
84     case SkSVGLineCap::Type::kSquare:
85         return SkPaint::kSquare_Cap;
86     default:
87         SkASSERT(false);
88         return SkPaint::kButt_Cap;
89     }
90 }
91 
toSkJoin(const SkSVGLineJoin & join)92 SkPaint::Join toSkJoin(const SkSVGLineJoin& join) {
93     switch (join.type()) {
94     case SkSVGLineJoin::Type::kMiter:
95         return SkPaint::kMiter_Join;
96     case SkSVGLineJoin::Type::kRound:
97         return SkPaint::kRound_Join;
98     case SkSVGLineJoin::Type::kBevel:
99         return SkPaint::kBevel_Join;
100     default:
101         SkASSERT(false);
102         return SkPaint::kMiter_Join;
103     }
104 }
105 
applySvgPaint(const SkSVGRenderContext & ctx,const SkSVGPaint & svgPaint,SkPaint * p)106 void applySvgPaint(const SkSVGRenderContext& ctx, const SkSVGPaint& svgPaint, SkPaint* p) {
107     switch (svgPaint.type()) {
108     case SkSVGPaint::Type::kColor:
109         p->setColor(SkColorSetA(svgPaint.color(), p->getAlpha()));
110         break;
111     case SkSVGPaint::Type::kIRI: {
112         const auto* node = ctx.findNodeById(svgPaint.iri());
113         if (!node || !node->asPaint(ctx, p)) {
114             p->setColor(SK_ColorTRANSPARENT);
115         }
116         break;
117     }
118     case SkSVGPaint::Type::kCurrentColor:
119         SkDebugf("unimplemented 'currentColor' paint type");
120         // Fall through.
121     case SkSVGPaint::Type::kNone:
122         // Fall through.
123     case SkSVGPaint::Type::kInherit:
124         break;
125     }
126 }
127 
opacity_to_alpha(SkScalar o)128 inline uint8_t opacity_to_alpha(SkScalar o) {
129     return SkTo<uint8_t>(SkScalarRoundToInt(o * 255));
130 }
131 
132 // Commit the selected attribute to the paint cache.
133 template <SkSVGAttribute>
134 void commitToPaint(const SkSVGPresentationAttributes&,
135                    const SkSVGRenderContext&,
136                    SkSVGPresentationContext*);
137 
138 template <>
commitToPaint(const SkSVGPresentationAttributes & attrs,const SkSVGRenderContext & ctx,SkSVGPresentationContext * pctx)139 void commitToPaint<SkSVGAttribute::kFill>(const SkSVGPresentationAttributes& attrs,
140                                           const SkSVGRenderContext& ctx,
141                                           SkSVGPresentationContext* pctx) {
142     applySvgPaint(ctx, *attrs.fFill.get(), &pctx->fFillPaint);
143 }
144 
145 template <>
commitToPaint(const SkSVGPresentationAttributes & attrs,const SkSVGRenderContext & ctx,SkSVGPresentationContext * pctx)146 void commitToPaint<SkSVGAttribute::kStroke>(const SkSVGPresentationAttributes& attrs,
147                                             const SkSVGRenderContext& ctx,
148                                             SkSVGPresentationContext* pctx) {
149     applySvgPaint(ctx, *attrs.fStroke.get(), &pctx->fStrokePaint);
150 }
151 
152 template <>
commitToPaint(const SkSVGPresentationAttributes & attrs,const SkSVGRenderContext &,SkSVGPresentationContext * pctx)153 void commitToPaint<SkSVGAttribute::kFillOpacity>(const SkSVGPresentationAttributes& attrs,
154                                                  const SkSVGRenderContext&,
155                                                  SkSVGPresentationContext* pctx) {
156     pctx->fFillPaint.setAlpha(opacity_to_alpha(*attrs.fFillOpacity.get()));
157 }
158 
159 template <>
commitToPaint(const SkSVGPresentationAttributes & attrs,const SkSVGRenderContext & ctx,SkSVGPresentationContext * pctx)160 void commitToPaint<SkSVGAttribute::kStrokeDashArray>(const SkSVGPresentationAttributes& attrs,
161                                                      const SkSVGRenderContext& ctx,
162                                                      SkSVGPresentationContext* pctx) {
163     const auto& dashArray = attrs.fStrokeDashArray.get();
164     if (dashArray->type() != SkSVGDashArray::Type::kDashArray) {
165         return;
166     }
167 
168     const auto count = dashArray->dashArray().count();
169     SkSTArray<128, SkScalar, true> intervals(count);
170     for (const auto& dash : dashArray->dashArray()) {
171         intervals.push_back(ctx.lengthContext().resolve(dash,
172                                                         SkSVGLengthContext::LengthType::kOther));
173     }
174 
175     if (count & 1) {
176         // If an odd number of values is provided, then the list of values
177         // is repeated to yield an even number of values.
178         intervals.push_back_n(count);
179         memcpy(intervals.begin() + count, intervals.begin(), count);
180     }
181 
182     SkASSERT((intervals.count() & 1) == 0);
183 
184     const SkScalar phase = ctx.lengthContext().resolve(*pctx->fInherited.fStrokeDashOffset.get(),
185                                                        SkSVGLengthContext::LengthType::kOther);
186     pctx->fStrokePaint.setPathEffect(SkDashPathEffect::Make(intervals.begin(),
187                                                             intervals.count(),
188                                                             phase));
189 }
190 
191 template <>
commitToPaint(const SkSVGPresentationAttributes &,const SkSVGRenderContext &,SkSVGPresentationContext *)192 void commitToPaint<SkSVGAttribute::kStrokeDashOffset>(const SkSVGPresentationAttributes&,
193                                                       const SkSVGRenderContext&,
194                                                       SkSVGPresentationContext*) {
195     // Applied via kStrokeDashArray.
196 }
197 
198 template <>
commitToPaint(const SkSVGPresentationAttributes & attrs,const SkSVGRenderContext &,SkSVGPresentationContext * pctx)199 void commitToPaint<SkSVGAttribute::kStrokeLineCap>(const SkSVGPresentationAttributes& attrs,
200                                                    const SkSVGRenderContext&,
201                                                    SkSVGPresentationContext* pctx) {
202     const auto& cap = *attrs.fStrokeLineCap.get();
203     if (cap.type() != SkSVGLineCap::Type::kInherit) {
204         pctx->fStrokePaint.setStrokeCap(toSkCap(cap));
205     }
206 }
207 
208 template <>
commitToPaint(const SkSVGPresentationAttributes & attrs,const SkSVGRenderContext &,SkSVGPresentationContext * pctx)209 void commitToPaint<SkSVGAttribute::kStrokeLineJoin>(const SkSVGPresentationAttributes& attrs,
210                                                     const SkSVGRenderContext&,
211                                                     SkSVGPresentationContext* pctx) {
212     const auto& join = *attrs.fStrokeLineJoin.get();
213     if (join.type() != SkSVGLineJoin::Type::kInherit) {
214         pctx->fStrokePaint.setStrokeJoin(toSkJoin(join));
215     }
216 }
217 
218 template <>
commitToPaint(const SkSVGPresentationAttributes & attrs,const SkSVGRenderContext &,SkSVGPresentationContext * pctx)219 void commitToPaint<SkSVGAttribute::kStrokeMiterLimit>(const SkSVGPresentationAttributes& attrs,
220                                                       const SkSVGRenderContext&,
221                                                       SkSVGPresentationContext* pctx) {
222     pctx->fStrokePaint.setStrokeMiter(*attrs.fStrokeMiterLimit.get());
223 }
224 
225 template <>
commitToPaint(const SkSVGPresentationAttributes & attrs,const SkSVGRenderContext &,SkSVGPresentationContext * pctx)226 void commitToPaint<SkSVGAttribute::kStrokeOpacity>(const SkSVGPresentationAttributes& attrs,
227                                                    const SkSVGRenderContext&,
228                                                    SkSVGPresentationContext* pctx) {
229     pctx->fStrokePaint.setAlpha(opacity_to_alpha(*attrs.fStrokeOpacity.get()));
230 }
231 
232 template <>
commitToPaint(const SkSVGPresentationAttributes & attrs,const SkSVGRenderContext & ctx,SkSVGPresentationContext * pctx)233 void commitToPaint<SkSVGAttribute::kStrokeWidth>(const SkSVGPresentationAttributes& attrs,
234                                                  const SkSVGRenderContext& ctx,
235                                                  SkSVGPresentationContext* pctx) {
236     auto strokeWidth = ctx.lengthContext().resolve(*attrs.fStrokeWidth.get(),
237                                                    SkSVGLengthContext::LengthType::kOther);
238     pctx->fStrokePaint.setStrokeWidth(strokeWidth);
239 }
240 
241 template <>
commitToPaint(const SkSVGPresentationAttributes &,const SkSVGRenderContext &,SkSVGPresentationContext *)242 void commitToPaint<SkSVGAttribute::kFillRule>(const SkSVGPresentationAttributes&,
243                                               const SkSVGRenderContext&,
244                                               SkSVGPresentationContext*) {
245     // Not part of the SkPaint state; applied to the path at render time.
246 }
247 
248 template <>
commitToPaint(const SkSVGPresentationAttributes &,const SkSVGRenderContext &,SkSVGPresentationContext *)249 void commitToPaint<SkSVGAttribute::kClipRule>(const SkSVGPresentationAttributes&,
250                                               const SkSVGRenderContext&,
251                                               SkSVGPresentationContext*) {
252     // Not part of the SkPaint state; applied to the path at clip time.
253 }
254 
255 template <>
commitToPaint(const SkSVGPresentationAttributes &,const SkSVGRenderContext &,SkSVGPresentationContext *)256 void commitToPaint<SkSVGAttribute::kVisibility>(const SkSVGPresentationAttributes&,
257                                                 const SkSVGRenderContext&,
258                                                 SkSVGPresentationContext*) {
259     // Not part of the SkPaint state; queried to veto rendering.
260 }
261 
262 } // anonymous ns
263 
SkSVGPresentationContext()264 SkSVGPresentationContext::SkSVGPresentationContext()
265     : fInherited(SkSVGPresentationAttributes::MakeInitial()) {
266 
267     fFillPaint.setStyle(SkPaint::kFill_Style);
268     fStrokePaint.setStyle(SkPaint::kStroke_Style);
269 
270     // TODO: drive AA off presentation attrs also (shape-rendering?)
271     fFillPaint.setAntiAlias(true);
272     fStrokePaint.setAntiAlias(true);
273 
274     // Commit initial values to the paint cache.
275     SkCanvas dummyCanvas(0, 0);
276     SkSVGRenderContext dummy(&dummyCanvas, SkSVGIDMapper(), SkSVGLengthContext(SkSize::Make(0, 0)),
277                              *this);
278 
279     commitToPaint<SkSVGAttribute::kFill>(fInherited, dummy, this);
280     commitToPaint<SkSVGAttribute::kFillOpacity>(fInherited, dummy, this);
281     commitToPaint<SkSVGAttribute::kStroke>(fInherited, dummy, this);
282     commitToPaint<SkSVGAttribute::kStrokeLineCap>(fInherited, dummy, this);
283     commitToPaint<SkSVGAttribute::kStrokeLineJoin>(fInherited, dummy, this);
284     commitToPaint<SkSVGAttribute::kStrokeMiterLimit>(fInherited, dummy, this);
285     commitToPaint<SkSVGAttribute::kStrokeOpacity>(fInherited, dummy, this);
286     commitToPaint<SkSVGAttribute::kStrokeWidth>(fInherited, dummy, this);
287 }
288 
SkSVGRenderContext(SkCanvas * canvas,const SkSVGIDMapper & mapper,const SkSVGLengthContext & lctx,const SkSVGPresentationContext & pctx)289 SkSVGRenderContext::SkSVGRenderContext(SkCanvas* canvas,
290                                        const SkSVGIDMapper& mapper,
291                                        const SkSVGLengthContext& lctx,
292                                        const SkSVGPresentationContext& pctx)
293     : fIDMapper(mapper)
294     , fLengthContext(lctx)
295     , fPresentationContext(pctx)
296     , fCanvas(canvas)
297     , fCanvasSaveCount(canvas->getSaveCount()) {}
298 
SkSVGRenderContext(const SkSVGRenderContext & other)299 SkSVGRenderContext::SkSVGRenderContext(const SkSVGRenderContext& other)
300     : SkSVGRenderContext(other.fCanvas,
301                          other.fIDMapper,
302                          *other.fLengthContext,
303                          *other.fPresentationContext) {}
304 
SkSVGRenderContext(const SkSVGRenderContext & other,SkCanvas * canvas)305 SkSVGRenderContext::SkSVGRenderContext(const SkSVGRenderContext& other, SkCanvas* canvas)
306     : SkSVGRenderContext(canvas,
307                          other.fIDMapper,
308                          *other.fLengthContext,
309                          *other.fPresentationContext) {}
310 
~SkSVGRenderContext()311 SkSVGRenderContext::~SkSVGRenderContext() {
312     fCanvas->restoreToCount(fCanvasSaveCount);
313 }
314 
findNodeById(const SkString & id) const315 const SkSVGNode* SkSVGRenderContext::findNodeById(const SkString& id) const {
316     const auto* v = fIDMapper.find(id);
317     return v ? v->get() : nullptr;
318 }
319 
applyPresentationAttributes(const SkSVGPresentationAttributes & attrs,uint32_t flags)320 void SkSVGRenderContext::applyPresentationAttributes(const SkSVGPresentationAttributes& attrs,
321                                                      uint32_t flags) {
322 
323 #define ApplyLazyInheritedAttribute(ATTR)                                               \
324     do {                                                                                \
325         /* All attributes should be defined on the inherited context. */                \
326         SkASSERT(fPresentationContext->fInherited.f ## ATTR.isValid());                 \
327         const auto* value = attrs.f ## ATTR.getMaybeNull();                             \
328         if (value && *value != *fPresentationContext->fInherited.f ## ATTR.get()) {     \
329             /* Update the local attribute value */                                      \
330             fPresentationContext.writable()->fInherited.f ## ATTR.set(*value);          \
331             /* Update the cached paints */                                              \
332             commitToPaint<SkSVGAttribute::k ## ATTR>(attrs, *this,    \
333                                                      fPresentationContext.writable());  \
334         }                                                                               \
335     } while (false)
336 
337     ApplyLazyInheritedAttribute(Fill);
338     ApplyLazyInheritedAttribute(FillOpacity);
339     ApplyLazyInheritedAttribute(FillRule);
340     ApplyLazyInheritedAttribute(ClipRule);
341     ApplyLazyInheritedAttribute(Stroke);
342     ApplyLazyInheritedAttribute(StrokeDashOffset);
343     ApplyLazyInheritedAttribute(StrokeDashArray);
344     ApplyLazyInheritedAttribute(StrokeLineCap);
345     ApplyLazyInheritedAttribute(StrokeLineJoin);
346     ApplyLazyInheritedAttribute(StrokeMiterLimit);
347     ApplyLazyInheritedAttribute(StrokeOpacity);
348     ApplyLazyInheritedAttribute(StrokeWidth);
349     ApplyLazyInheritedAttribute(Visibility);
350 
351 #undef ApplyLazyInheritedAttribute
352 
353     // Uninherited attributes.  Only apply to the current context.
354 
355     if (auto* opacity = attrs.fOpacity.getMaybeNull()) {
356         this->applyOpacity(opacity->value(), flags);
357     }
358 
359     if (auto* clip = attrs.fClipPath.getMaybeNull()) {
360         this->applyClip(*clip);
361     }
362 }
363 
applyOpacity(SkScalar opacity,uint32_t flags)364 void SkSVGRenderContext::applyOpacity(SkScalar opacity, uint32_t flags) {
365     if (opacity >= 1) {
366         return;
367     }
368 
369     const bool hasFill   = SkToBool(this->fillPaint());
370     const bool hasStroke = SkToBool(this->strokePaint());
371 
372     // We can apply the opacity as paint alpha iif it only affects one atomic draw.
373     // For now, this means a) the target node doesn't have any descendants, and
374     // b) it only has a stroke or a fill (but not both).  Going forward, we may need
375     // to refine this heuristic (e.g. to accommodate markers).
376     if ((flags & kLeaf) && (hasFill ^ hasStroke)) {
377         auto* pctx = fPresentationContext.writable();
378         if (hasFill) {
379             pctx->fFillPaint.setAlpha(
380                 SkScalarRoundToInt(opacity * pctx->fFillPaint.getAlpha()));
381         } else {
382             pctx->fStrokePaint.setAlpha(
383                 SkScalarRoundToInt(opacity * pctx->fStrokePaint.getAlpha()));
384         }
385     } else {
386         // Expensive, layer-based fall back.
387         SkPaint opacityPaint;
388         opacityPaint.setAlpha(opacity_to_alpha(opacity));
389         // Balanced in the destructor, via restoreToCount().
390         fCanvas->saveLayer(nullptr, &opacityPaint);
391     }
392 }
393 
saveOnce()394 void SkSVGRenderContext::saveOnce() {
395     // The canvas only needs to be saved once, per local SkSVGRenderContext.
396     if (fCanvas->getSaveCount() == fCanvasSaveCount) {
397         fCanvas->save();
398     }
399 
400     SkASSERT(fCanvas->getSaveCount() > fCanvasSaveCount);
401 }
402 
applyClip(const SkSVGClip & clip)403 void SkSVGRenderContext::applyClip(const SkSVGClip& clip) {
404     if (clip.type() != SkSVGClip::Type::kIRI) {
405         return;
406     }
407 
408     const SkSVGNode* clipNode = this->findNodeById(clip.iri());
409     if (!clipNode || clipNode->tag() != SkSVGTag::kClipPath) {
410         return;
411     }
412 
413     const SkPath clipPath = clipNode->asPath(*this);
414 
415     // We use the computed clip path in two ways:
416     //
417     //   - apply to the current canvas, for drawing
418     //   - track in the presentation context, for asPath() composition
419     //
420     // TODO: the two uses are exclusive, avoid canvas churn when non needed.
421 
422     this->saveOnce();
423 
424     fCanvas->clipPath(clipPath, true);
425     fClipPath.set(clipPath);
426 }
427 
fillPaint() const428 const SkPaint* SkSVGRenderContext::fillPaint() const {
429     const SkSVGPaint::Type paintType = fPresentationContext->fInherited.fFill.get()->type();
430     return paintType != SkSVGPaint::Type::kNone ? &fPresentationContext->fFillPaint : nullptr;
431 }
432 
strokePaint() const433 const SkPaint* SkSVGRenderContext::strokePaint() const {
434     const SkSVGPaint::Type paintType = fPresentationContext->fInherited.fStroke.get()->type();
435     return paintType != SkSVGPaint::Type::kNone ? &fPresentationContext->fStrokePaint : nullptr;
436 }
437