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