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