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