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