Coverage Report

Created: 2021-08-22 09:07

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