• 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/SkBlendMode.h"
11 #include "include/core/SkCanvas.h"
12 #include "include/core/SkColor.h"
13 #include "include/core/SkImageFilter.h"
14 #include "include/core/SkPaint.h"
15 #include "include/core/SkPath.h"
16 #include "include/core/SkPathEffect.h"
17 #include "include/core/SkString.h"
18 #include "include/effects/SkDashPathEffect.h"
19 #include "include/private/base/SkDebug.h"
20 #include "include/private/base/SkSpan_impl.h"
21 #include "include/private/base/SkTArray.h"
22 #include "include/private/base/SkTPin.h"
23 #include "modules/svg/include/SkSVGAttribute.h"
24 #include "modules/svg/include/SkSVGClipPath.h"
25 #include "modules/svg/include/SkSVGFilter.h"
26 #include "modules/svg/include/SkSVGMask.h"
27 #include "modules/svg/include/SkSVGNode.h"
28 #include "modules/svg/include/SkSVGTypes.h"
29 
30 #include <cstring>
31 #include <vector>
32 
33 using namespace skia_private;
34 
35 namespace {
36 
length_size_for_type(const SkSize & viewport,SkSVGLengthContext::LengthType t)37 SkScalar length_size_for_type(const SkSize& viewport, SkSVGLengthContext::LengthType t) {
38     switch (t) {
39     case SkSVGLengthContext::LengthType::kHorizontal:
40         return viewport.width();
41     case SkSVGLengthContext::LengthType::kVertical:
42         return viewport.height();
43     case SkSVGLengthContext::LengthType::kOther: {
44         // https://www.w3.org/TR/SVG11/coords.html#Units_viewport_percentage
45         constexpr SkScalar rsqrt2 = 1.0f / SK_ScalarSqrt2;
46         const SkScalar w = viewport.width(), h = viewport.height();
47         return rsqrt2 * SkScalarSqrt(w * w + h * h);
48     }
49     }
50 
51     SkASSERT(false);  // Not reached.
52     return 0;
53 }
54 
55 // Multipliers for DPI-relative units.
56 constexpr SkScalar kINMultiplier = 1.00f;
57 constexpr SkScalar kPTMultiplier = kINMultiplier / 72.272f;
58 constexpr SkScalar kPCMultiplier = kPTMultiplier * 12;
59 constexpr SkScalar kMMMultiplier = kINMultiplier / 25.4f;
60 constexpr SkScalar kCMMultiplier = kMMMultiplier * 10;
61 
62 }  // namespace
63 
resolveForSVG(const SkSVGLength & l,LengthType t) const64 SkScalar SkSVGLengthContext::resolveForSVG(const SkSVGLength& l, LengthType t) const {
65     switch (l.unit()) {
66     case SkSVGLength::Unit::kNumber:
67         // Fall through.
68     case SkSVGLength::Unit::kPX:
69         return l.value() * fResizePercentage / DEFAULT_RESIZE_PERCENTAGE;
70     case SkSVGLength::Unit::kPercentage:
71         return l.value() * length_size_for_type(fViewport, t) / 100 * fResizePercentage / DEFAULT_RESIZE_PERCENTAGE;
72     case SkSVGLength::Unit::kCM:
73         return l.value() * fDPI * kCMMultiplier * fResizePercentage / DEFAULT_RESIZE_PERCENTAGE;
74     case SkSVGLength::Unit::kMM:
75         return l.value() * fDPI * kMMMultiplier * fResizePercentage / DEFAULT_RESIZE_PERCENTAGE;
76     case SkSVGLength::Unit::kIN:
77         return l.value() * fDPI * kINMultiplier * fResizePercentage / DEFAULT_RESIZE_PERCENTAGE;
78     case SkSVGLength::Unit::kPT:
79         return l.value() * fDPI * kPTMultiplier * fResizePercentage / DEFAULT_RESIZE_PERCENTAGE;
80     case SkSVGLength::Unit::kPC:
81         return l.value() * fDPI * kPCMultiplier * fResizePercentage / DEFAULT_RESIZE_PERCENTAGE;
82     default:
83         SkDEBUGF("unsupported unit type: <%d>\n", (int)l.unit());
84         return 0;
85     }
86 }
87 
resolveRectForSVG(const SkSVGLength & x,const SkSVGLength & y,const SkSVGLength & w,const SkSVGLength & h) const88 SkRect SkSVGLengthContext::resolveRectForSVG(const SkSVGLength& x, const SkSVGLength& y,
89                                        const SkSVGLength& w, const SkSVGLength& h) const {
90     return SkRect::MakeXYWH(
91         this->resolveForSVG(x, SkSVGLengthContext::LengthType::kHorizontal),
92         this->resolveForSVG(y, SkSVGLengthContext::LengthType::kVertical),
93         this->resolveForSVG(w, SkSVGLengthContext::LengthType::kHorizontal),
94         this->resolveForSVG(h, SkSVGLengthContext::LengthType::kVertical));
95 }
96 
resolve(const SkSVGLength & l,LengthType t) const97 SkScalar SkSVGLengthContext::resolve(const SkSVGLength& l, LengthType t) const {
98     switch (l.unit()) {
99     case SkSVGLength::Unit::kNumber:
100         // Fall through.
101     case SkSVGLength::Unit::kPX:
102         return l.value();
103     case SkSVGLength::Unit::kPercentage:
104         return l.value() * length_size_for_type(fViewport, t) / 100;
105     case SkSVGLength::Unit::kCM:
106         return l.value() * fDPI * kCMMultiplier;
107     case SkSVGLength::Unit::kMM:
108         return l.value() * fDPI * kMMMultiplier;
109     case SkSVGLength::Unit::kIN:
110         return l.value() * fDPI * kINMultiplier;
111     case SkSVGLength::Unit::kPT:
112         return l.value() * fDPI * kPTMultiplier;
113     case SkSVGLength::Unit::kPC:
114         return l.value() * fDPI * kPCMultiplier;
115     default:
116         SkDebugf("unsupported unit type: <%d>\n", (int)l.unit());
117         return 0;
118     }
119 }
120 
resolveRect(const SkSVGLength & x,const SkSVGLength & y,const SkSVGLength & w,const SkSVGLength & h) const121 SkRect SkSVGLengthContext::resolveRect(const SkSVGLength& x, const SkSVGLength& y,
122                                        const SkSVGLength& w, const SkSVGLength& h) const {
123     return SkRect::MakeXYWH(
124         this->resolve(x, SkSVGLengthContext::LengthType::kHorizontal),
125         this->resolve(y, SkSVGLengthContext::LengthType::kVertical),
126         this->resolve(w, SkSVGLengthContext::LengthType::kHorizontal),
127         this->resolve(h, SkSVGLengthContext::LengthType::kVertical));
128 }
129 
130 namespace {
131 
toSkCap(const SkSVGLineCap & cap)132 SkPaint::Cap toSkCap(const SkSVGLineCap& cap) {
133     switch (cap) {
134     case SkSVGLineCap::kButt:
135         return SkPaint::kButt_Cap;
136     case SkSVGLineCap::kRound:
137         return SkPaint::kRound_Cap;
138     case SkSVGLineCap::kSquare:
139         return SkPaint::kSquare_Cap;
140     }
141     SkUNREACHABLE;
142 }
143 
toSkJoin(const SkSVGLineJoin & join)144 SkPaint::Join toSkJoin(const SkSVGLineJoin& join) {
145     switch (join.type()) {
146     case SkSVGLineJoin::Type::kMiter:
147         return SkPaint::kMiter_Join;
148     case SkSVGLineJoin::Type::kRound:
149         return SkPaint::kRound_Join;
150     case SkSVGLineJoin::Type::kBevel:
151         return SkPaint::kBevel_Join;
152     default:
153         SkASSERT(false);
154         return SkPaint::kMiter_Join;
155     }
156 }
157 
dash_effect(const SkSVGPresentationAttributes & props,const SkSVGLengthContext & lctx)158 static sk_sp<SkPathEffect> dash_effect(const SkSVGPresentationAttributes& props,
159                                        const SkSVGLengthContext& lctx) {
160     if (props.fStrokeDashArray->type() != SkSVGDashArray::Type::kDashArray) {
161         return nullptr;
162     }
163 
164     const auto& da = *props.fStrokeDashArray;
165     const auto count = da.dashArray().size();
166     STArray<128, SkScalar, true> intervals(count);
167     for (const auto& dash : da.dashArray()) {
168         intervals.push_back(lctx.resolve(dash, SkSVGLengthContext::LengthType::kOther));
169     }
170 
171     if (count & 1) {
172         // If an odd number of values is provided, then the list of values
173         // is repeated to yield an even number of values.
174         intervals.push_back_n(count);
175         memcpy(intervals.begin() + count, intervals.begin(), count * sizeof(SkScalar));
176     }
177 
178     SkASSERT((intervals.size() & 1) == 0);
179 
180     const auto phase = lctx.resolve(*props.fStrokeDashOffset,
181                                     SkSVGLengthContext::LengthType::kOther);
182 
183     return SkDashPathEffect::Make(intervals.begin(), intervals.size(), phase);
184 }
185 
186 }  // namespace
187 
SkSVGPresentationContext()188 SkSVGPresentationContext::SkSVGPresentationContext()
189     : fInherited(SkSVGPresentationAttributes::MakeInitial())
190 {}
191 
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)192 SkSVGRenderContext::SkSVGRenderContext(SkCanvas* canvas,
193                                        const sk_sp<SkFontMgr>& fmgr,
194                                        const sk_sp<skresources::ResourceProvider>& rp,
195                                        const SkSVGIDMapper& mapper,
196                                        const SkSVGLengthContext& lctx,
197                                        const SkSVGPresentationContext& pctx,
198                                        const OBBScope& obbs,
199                                        const sk_sp<SkShapers::Factory>& fact)
200         : fFontMgr(fmgr)
201         , fTextShapingFactory(fact)
202         , fResourceProvider(rp)
203         , fIDMapper(mapper)
204         , fLengthContext(lctx)
205         , fPresentationContext(pctx)
206         , fCanvas(canvas)
207         , fCanvasSaveCount(canvas->getSaveCount())
208         , fOBBScope(obbs) {}
209 
SkSVGRenderContext(const SkSVGRenderContext & other)210 SkSVGRenderContext::SkSVGRenderContext(const SkSVGRenderContext& other)
211         : SkSVGRenderContext(other.fCanvas,
212                              other.fFontMgr,
213                              other.fResourceProvider,
214                              other.fIDMapper,
215                              *other.fLengthContext,
216                              *other.fPresentationContext,
217                              other.fOBBScope,
218                              other.fTextShapingFactory) {}
219 
SkSVGRenderContext(const SkSVGRenderContext & other,SkCanvas * canvas)220 SkSVGRenderContext::SkSVGRenderContext(const SkSVGRenderContext& other, SkCanvas* canvas)
221         : SkSVGRenderContext(canvas,
222                              other.fFontMgr,
223                              other.fResourceProvider,
224                              other.fIDMapper,
225                              *other.fLengthContext,
226                              *other.fPresentationContext,
227                              other.fOBBScope,
228                              other.fTextShapingFactory) {}
229 
SkSVGRenderContext(const SkSVGRenderContext & other,const SkSVGNode * node)230 SkSVGRenderContext::SkSVGRenderContext(const SkSVGRenderContext& other, const SkSVGNode* node)
231         : SkSVGRenderContext(other.fCanvas,
232                              other.fFontMgr,
233                              other.fResourceProvider,
234                              other.fIDMapper,
235                              *other.fLengthContext,
236                              *other.fPresentationContext,
237                              OBBScope{node, this},
238                              other.fTextShapingFactory) {}
239 
~SkSVGRenderContext()240 SkSVGRenderContext::~SkSVGRenderContext() {
241     fCanvas->restoreToCount(fCanvasSaveCount);
242 }
243 
findNodeById(const SkSVGIRI & iri) const244 SkSVGRenderContext::BorrowedNode SkSVGRenderContext::findNodeById(const SkSVGIRI& iri) const {
245     if (iri.type() != SkSVGIRI::Type::kLocal) {
246         SkDEBUGF("non-local iri references not currently supported");
247         return BorrowedNode(nullptr);
248     }
249     return BorrowedNode(fIDMapper.find(iri.iri()));
250 }
251 
applyPresentationAttributes(const SkSVGPresentationAttributes & attrs,uint32_t flags)252 void SkSVGRenderContext::applyPresentationAttributes(const SkSVGPresentationAttributes& attrs,
253                                                      uint32_t flags) {
254 
255 #define ApplyLazyInheritedAttribute(ATTR)                                               \
256     do {                                                                                \
257         /* All attributes should be defined on the inherited context. */                \
258         SkASSERT(fPresentationContext->fInherited.f ## ATTR.isValue());                 \
259         const auto& attr = attrs.f ## ATTR;                                             \
260         if (attr.isValue() && *attr != *fPresentationContext->fInherited.f ## ATTR) {   \
261             /* Update the local attribute value */                                      \
262             fPresentationContext.writable()->fInherited.f ## ATTR.set(*attr);           \
263         }                                                                               \
264     } while (false)
265 
266     ApplyLazyInheritedAttribute(Fill);
267     ApplyLazyInheritedAttribute(FillOpacity);
268     ApplyLazyInheritedAttribute(FillRule);
269     ApplyLazyInheritedAttribute(FontFamily);
270     ApplyLazyInheritedAttribute(FontSize);
271     ApplyLazyInheritedAttribute(FontStyle);
272     ApplyLazyInheritedAttribute(FontWeight);
273     ApplyLazyInheritedAttribute(ClipRule);
274     ApplyLazyInheritedAttribute(Stroke);
275     ApplyLazyInheritedAttribute(StrokeDashOffset);
276     ApplyLazyInheritedAttribute(StrokeDashArray);
277     ApplyLazyInheritedAttribute(StrokeLineCap);
278     ApplyLazyInheritedAttribute(StrokeLineJoin);
279     ApplyLazyInheritedAttribute(StrokeMiterLimit);
280     ApplyLazyInheritedAttribute(StrokeOpacity);
281     ApplyLazyInheritedAttribute(StrokeWidth);
282     ApplyLazyInheritedAttribute(TextAnchor);
283     ApplyLazyInheritedAttribute(Visibility);
284     ApplyLazyInheritedAttribute(Color);
285     ApplyLazyInheritedAttribute(ColorInterpolation);
286     ApplyLazyInheritedAttribute(ColorInterpolationFilters);
287 
288 #undef ApplyLazyInheritedAttribute
289 
290     // Uninherited attributes.  Only apply to the current context.
291 
292     const bool hasFilter = attrs.fFilter.isValue();
293     if (attrs.fOpacity.isValue()) {
294         this->applyOpacity(*attrs.fOpacity, flags, hasFilter);
295     }
296 
297     if (attrs.fClipPath.isValue()) {
298         this->applyClip(*attrs.fClipPath);
299     }
300 
301     if (attrs.fMask.isValue()) {
302         this->applyMask(*attrs.fMask);
303     }
304 
305     // TODO: when both a filter and opacity are present, we can apply both with a single layer
306     if (hasFilter) {
307         this->applyFilter(*attrs.fFilter);
308     }
309 
310     // Remaining uninherited presentation attributes are accessed as SkSVGNode fields, not via
311     // the render context.
312     // TODO: resolve these in a pre-render styling pass and assert here that they are values.
313     // - stop-color
314     // - stop-opacity
315     // - flood-color
316     // - flood-opacity
317     // - lighting-color
318 }
319 
applyOpacity(SkScalar opacity,uint32_t flags,bool hasFilter)320 void SkSVGRenderContext::applyOpacity(SkScalar opacity, uint32_t flags, bool hasFilter) {
321     if (opacity >= 1) {
322         return;
323     }
324 
325     const auto& props = fPresentationContext->fInherited;
326     const bool hasFill   = props.fFill  ->type() != SkSVGPaint::Type::kNone,
327                hasStroke = props.fStroke->type() != SkSVGPaint::Type::kNone;
328 
329     // We can apply the opacity as paint alpha if it only affects one atomic draw.
330     // For now, this means all of the following must be true:
331     //   - the target node doesn't have any descendants;
332     //   - it only has a stroke or a fill (but not both);
333     //   - it does not have a filter.
334     // Going forward, we may needto refine this heuristic (e.g. to accommodate markers).
335     if ((flags & kLeaf) && (hasFill ^ hasStroke) && !hasFilter) {
336         fDeferredPaintOpacity *= opacity;
337     } else {
338         // Expensive, layer-based fall back.
339         SkPaint opacityPaint;
340         opacityPaint.setAlphaf(SkTPin(opacity, 0.0f, 1.0f));
341         // Balanced in the destructor, via restoreToCount().
342         fCanvas->saveLayer(nullptr, &opacityPaint);
343     }
344 }
345 
applyFilter(const SkSVGFuncIRI & filter)346 void SkSVGRenderContext::applyFilter(const SkSVGFuncIRI& filter) {
347     if (filter.type() != SkSVGFuncIRI::Type::kIRI) {
348         return;
349     }
350 
351     const auto node = this->findNodeById(filter.iri());
352     if (!node || node->tag() != SkSVGTag::kFilter) {
353         return;
354     }
355 
356     const SkSVGFilter* filterNode = reinterpret_cast<const SkSVGFilter*>(node.get());
357     sk_sp<SkImageFilter> imageFilter = filterNode->buildFilterDAG(*this);
358     if (imageFilter) {
359         SkPaint filterPaint;
360         filterPaint.setImageFilter(imageFilter);
361         // Balanced in the destructor, via restoreToCount().
362         fCanvas->saveLayer(nullptr, &filterPaint);
363     }
364 }
365 
saveOnce()366 void SkSVGRenderContext::saveOnce() {
367     // The canvas only needs to be saved once, per local SkSVGRenderContext.
368     if (fCanvas->getSaveCount() == fCanvasSaveCount) {
369         fCanvas->save();
370     }
371 
372     SkASSERT(fCanvas->getSaveCount() > fCanvasSaveCount);
373 }
374 
applyClip(const SkSVGFuncIRI & clip)375 void SkSVGRenderContext::applyClip(const SkSVGFuncIRI& clip) {
376     if (clip.type() != SkSVGFuncIRI::Type::kIRI) {
377         return;
378     }
379 
380     const auto clipNode = this->findNodeById(clip.iri());
381     if (!clipNode || clipNode->tag() != SkSVGTag::kClipPath) {
382         return;
383     }
384 
385     const SkPath clipPath = static_cast<const SkSVGClipPath*>(clipNode.get())->resolveClip(*this);
386 
387     // We use the computed clip path in two ways:
388     //
389     //   - apply to the current canvas, for drawing
390     //   - track in the presentation context, for asPath() composition
391     //
392     // TODO: the two uses are exclusive, avoid canvas churn when non needed.
393 
394     this->saveOnce();
395 
396     fCanvas->clipPath(clipPath, true);
397     fClipPath.set(clipPath);
398 }
399 
applyMask(const SkSVGFuncIRI & mask)400 void SkSVGRenderContext::applyMask(const SkSVGFuncIRI& mask) {
401     if (mask.type() != SkSVGFuncIRI::Type::kIRI) {
402         return;
403     }
404 
405     const auto node = this->findNodeById(mask.iri());
406     if (!node || node->tag() != SkSVGTag::kMask) {
407         return;
408     }
409 
410     const auto* mask_node = static_cast<const SkSVGMask*>(node.get());
411     const auto mask_bounds = mask_node->bounds(*this);
412 
413     // Isolation/mask layer.
414     fCanvas->saveLayer(mask_bounds, nullptr);
415 
416     // Render and filter mask content.
417     mask_node->renderMask(*this);
418 
419     // Content layer
420     SkPaint masking_paint;
421     masking_paint.setBlendMode(SkBlendMode::kSrcIn);
422     fCanvas->saveLayer(mask_bounds, &masking_paint);
423 
424     // Content is also clipped to the specified mask bounds.
425     fCanvas->clipRect(mask_bounds, true);
426 
427     // At this point we're set up for content rendering.
428     // The pending layers are restored in the destructor (render context scope exit).
429     // Restoring triggers srcIn-compositing the content against the mask.
430 }
431 
commonPaint(const SkSVGPaint & paint_selector,float paint_opacity) const432 SkTLazy<SkPaint> SkSVGRenderContext::commonPaint(const SkSVGPaint& paint_selector,
433                                                  float paint_opacity) const {
434     if (paint_selector.type() == SkSVGPaint::Type::kNone) {
435         return SkTLazy<SkPaint>();
436     }
437 
438     SkTLazy<SkPaint> p;
439     p.init();
440 
441     switch (paint_selector.type()) {
442     case SkSVGPaint::Type::kColor:
443         p->setColor(this->resolveSvgColor(paint_selector.color()));
444         break;
445     case SkSVGPaint::Type::kIRI: {
446         // Our property inheritance is borked as it follows the render path and not the tree
447         // hierarchy.  To avoid gross transgressions like leaf node presentation attributes
448         // leaking into the paint server context, use a pristine presentation context when
449         // following hrefs.
450         //
451         // Preserve the OBB scope because some paints use object bounding box coords
452         // (e.g. gradient control points), which requires access to the render context
453         // and node being rendered.
454         SkSVGPresentationContext pctx;
455         pctx.fNamedColors = fPresentationContext->fNamedColors;
456         SkSVGRenderContext local_ctx(fCanvas,
457                                      fFontMgr,
458                                      fResourceProvider,
459                                      fIDMapper,
460                                      *fLengthContext,
461                                      pctx,
462                                      fOBBScope,
463                                      fTextShapingFactory);
464 
465         const auto node = this->findNodeById(paint_selector.iri());
466         if (!node || !node->asPaint(local_ctx, p.get())) {
467             // Use the fallback color.
468             p->setColor(this->resolveSvgColor(paint_selector.color()));
469         }
470     } break;
471     default:
472         SkUNREACHABLE;
473     }
474 
475     p->setAntiAlias(true); // TODO: shape-rendering support
476 
477     // We observe 3 opacity components:
478     //   - initial paint server opacity (e.g. color stop opacity)
479     //   - paint-specific opacity (e.g. 'fill-opacity', 'stroke-opacity')
480     //   - deferred opacity override (optimization for leaf nodes 'opacity')
481     p->setAlphaf(SkTPin(p->getAlphaf() * paint_opacity * fDeferredPaintOpacity, 0.0f, 1.0f));
482 
483     return p;
484 }
485 
fillPaint() const486 SkTLazy<SkPaint> SkSVGRenderContext::fillPaint() const {
487     const auto& props = fPresentationContext->fInherited;
488     auto p = this->commonPaint(*props.fFill, *props.fFillOpacity);
489 
490     if (p.isValid()) {
491         p->setStyle(SkPaint::kFill_Style);
492     }
493 
494     return p;
495 }
496 
strokePaint() const497 SkTLazy<SkPaint> SkSVGRenderContext::strokePaint() const {
498     const auto& props = fPresentationContext->fInherited;
499     auto p = this->commonPaint(*props.fStroke, *props.fStrokeOpacity);
500 
501     if (p.isValid()) {
502         p->setStyle(SkPaint::kStroke_Style);
503         p->setStrokeWidth(fLengthContext->resolve(*props.fStrokeWidth,
504                                                   SkSVGLengthContext::LengthType::kOther));
505         p->setStrokeCap(toSkCap(*props.fStrokeLineCap));
506         p->setStrokeJoin(toSkJoin(*props.fStrokeLineJoin));
507         p->setStrokeMiter(*props.fStrokeMiterLimit);
508         p->setPathEffect(dash_effect(props, *fLengthContext));
509     }
510 
511     return p;
512 }
513 
resolveSvgColor(const SkSVGColor & color) const514 SkSVGColorType SkSVGRenderContext::resolveSvgColor(const SkSVGColor& color) const {
515     if (fPresentationContext->fNamedColors) {
516         for (auto&& ident : color.vars()) {
517             SkSVGColorType* c = fPresentationContext->fNamedColors->find(ident);
518             if (c) {
519                 return *c;
520             }
521         }
522     }
523     switch (color.type()) {
524         case SkSVGColor::Type::kColor:
525             return color.color();
526         case SkSVGColor::Type::kCurrentColor:
527             return *fPresentationContext->fInherited.fColor;
528         case SkSVGColor::Type::kICCColor:
529             SkDEBUGF("ICC color unimplemented");
530             return SK_ColorBLACK;
531     }
532     SkUNREACHABLE;
533 }
534 
535 SkSVGRenderContext::OBBTransform
transformForCurrentOBB(SkSVGObjectBoundingBoxUnits u) const536 SkSVGRenderContext::transformForCurrentOBB(SkSVGObjectBoundingBoxUnits u) const {
537     if (!fOBBScope.fNode || u.type() == SkSVGObjectBoundingBoxUnits::Type::kUserSpaceOnUse) {
538         return {{0,0},{1,1}};
539     }
540     SkASSERT(fOBBScope.fCtx);
541 
542     const auto obb = fOBBScope.fNode->objectBoundingBox(*fOBBScope.fCtx);
543     return {{obb.x(), obb.y()}, {obb.width(), obb.height()}};
544 }
545 
resolveOBBRect(const SkSVGLength & x,const SkSVGLength & y,const SkSVGLength & w,const SkSVGLength & h,SkSVGObjectBoundingBoxUnits obbu) const546 SkRect SkSVGRenderContext::resolveOBBRect(const SkSVGLength& x, const SkSVGLength& y,
547                                           const SkSVGLength& w, const SkSVGLength& h,
548                                           SkSVGObjectBoundingBoxUnits obbu) const {
549     SkTCopyOnFirstWrite<SkSVGLengthContext> lctx(fLengthContext);
550 
551     if (obbu.type() == SkSVGObjectBoundingBoxUnits::Type::kObjectBoundingBox) {
552         *lctx.writable() = SkSVGLengthContext({1,1});
553     }
554 
555     auto r = lctx->resolveRect(x, y, w, h);
556     const auto obbt = this->transformForCurrentOBB(obbu);
557 
558     return SkRect::MakeXYWH(obbt.scale.x * r.x() + obbt.offset.x,
559                             obbt.scale.y * r.y() + obbt.offset.y,
560                             obbt.scale.x * r.width(),
561                             obbt.scale.y * r.height());
562 }
563