1 /*
2 * Copyright 2017 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/SkSVGPattern.h"
9
10 #include "include/core/SkPicture.h"
11 #include "include/core/SkPictureRecorder.h"
12 #include "include/core/SkShader.h"
13 #include "include/core/SkTileMode.h"
14 #include "modules/svg/include/SkSVGRenderContext.h"
15 #include "modules/svg/include/SkSVGValue.h"
16
SkSVGPattern()17 SkSVGPattern::SkSVGPattern() : INHERITED(SkSVGTag::kPattern) {}
18
parseAndSetAttribute(const char * name,const char * value)19 bool SkSVGPattern::parseAndSetAttribute(const char* name, const char* value) {
20 return INHERITED::parseAndSetAttribute(name, value) ||
21 this->setX(SkSVGAttributeParser::parse<SkSVGLength>("x", name, value)) ||
22 this->setY(SkSVGAttributeParser::parse<SkSVGLength>("y", name, value)) ||
23 this->setWidth(SkSVGAttributeParser::parse<SkSVGLength>("width", name, value)) ||
24 this->setHeight(SkSVGAttributeParser::parse<SkSVGLength>("height", name, value)) ||
25 this->setPatternTransform(SkSVGAttributeParser::parse<SkSVGTransformType>(
26 "patternTransform", name, value)) ||
27 this->setHref(SkSVGAttributeParser::parse<SkSVGIRI>("xlink:href", name, value));
28 }
29
hrefTarget(const SkSVGRenderContext & ctx) const30 const SkSVGPattern* SkSVGPattern::hrefTarget(const SkSVGRenderContext& ctx) const {
31 if (fHref.iri().isEmpty()) {
32 return nullptr;
33 }
34
35 const auto href = ctx.findNodeById(fHref);
36 if (!href || href->tag() != SkSVGTag::kPattern) {
37 return nullptr;
38 }
39
40 return static_cast<const SkSVGPattern*>(href.get());
41 }
42
43 template <typename T>
inherit_if_needed(const SkTLazy<T> & src,SkTLazy<T> & dst)44 int inherit_if_needed(const SkTLazy<T>& src, SkTLazy<T>& dst) {
45 if (!dst.isValid()) {
46 dst = src;
47 return 1;
48 }
49
50 return 0;
51 }
52
53 /* https://www.w3.org/TR/SVG11/pservers.html#PatternElementHrefAttribute
54 *
55 * Any attributes which are defined on the referenced element which are not defined on this element
56 * are inherited by this element. If this element has no children, and the referenced element does
57 * (possibly due to its own ‘xlink:href’ attribute), then this element inherits the children from
58 * the referenced element. Inheritance can be indirect to an arbitrary level; thus, if the
59 * referenced element inherits attributes or children due to its own ‘xlink:href’ attribute, then
60 * the current element can inherit those attributes or children.
61 */
resolveHref(const SkSVGRenderContext & ctx,PatternAttributes * attrs) const62 const SkSVGPattern* SkSVGPattern::resolveHref(const SkSVGRenderContext& ctx,
63 PatternAttributes* attrs) const {
64 const SkSVGPattern *currentNode = this,
65 *contentNode = this;
66 do {
67 // Bitwise OR to avoid short-circuiting.
68 const bool didInherit =
69 inherit_if_needed(currentNode->fX , attrs->fX) |
70 inherit_if_needed(currentNode->fY , attrs->fY) |
71 inherit_if_needed(currentNode->fWidth , attrs->fWidth) |
72 inherit_if_needed(currentNode->fHeight , attrs->fHeight) |
73 inherit_if_needed(currentNode->fPatternTransform, attrs->fPatternTransform);
74
75 if (!contentNode->hasChildren()) {
76 contentNode = currentNode;
77 }
78
79 if (contentNode->hasChildren() && !didInherit) {
80 // All attributes have been resolved, and a valid content node has been found.
81 // We can terminate the href chain early.
82 break;
83 }
84
85 // TODO: reference loop mitigation.
86 currentNode = currentNode->hrefTarget(ctx);
87 } while (currentNode);
88
89 return contentNode;
90 }
91
onAsPaint(const SkSVGRenderContext & ctx,SkPaint * paint) const92 bool SkSVGPattern::onAsPaint(const SkSVGRenderContext& ctx, SkPaint* paint) const {
93 PatternAttributes attrs;
94 const auto* contentNode = this->resolveHref(ctx, &attrs);
95
96 const auto tile = ctx.lengthContext().resolveRect(
97 attrs.fX.isValid() ? *attrs.fX : SkSVGLength(0),
98 attrs.fY.isValid() ? *attrs.fY : SkSVGLength(0),
99 attrs.fWidth.isValid() ? *attrs.fWidth : SkSVGLength(0),
100 attrs.fHeight.isValid() ? *attrs.fHeight : SkSVGLength(0));
101
102 if (tile.isEmpty()) {
103 return false;
104 }
105
106 const SkMatrix* patternTransform = attrs.fPatternTransform.isValid()
107 ? attrs.fPatternTransform.get()
108 : nullptr;
109
110 SkPictureRecorder recorder;
111 SkSVGRenderContext recordingContext(ctx, recorder.beginRecording(tile));
112
113 // Cannot call into INHERITED:: because SkSVGHiddenContainer skips rendering.
114 contentNode->SkSVGContainer::onRender(recordingContext);
115
116 paint->setShader(recorder.finishRecordingAsPicture()->makeShader(
117 SkTileMode::kRepeat,
118 SkTileMode::kRepeat,
119 SkFilterMode::kLinear,
120 patternTransform,
121 &tile));
122 return true;
123 }
124