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