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