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