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 "experimental/svg/model/SkSVGGradient.h"
9 #include "experimental/svg/model/SkSVGRenderContext.h"
10 #include "experimental/svg/model/SkSVGStop.h"
11 #include "experimental/svg/model/SkSVGValue.h"
12
setHref(const SkSVGStringType & href)13 void SkSVGGradient::setHref(const SkSVGStringType& href) {
14 fHref = std::move(href);
15 }
16
setGradientTransform(const SkSVGTransformType & t)17 void SkSVGGradient::setGradientTransform(const SkSVGTransformType& t) {
18 fGradientTransform = t;
19 }
20
setSpreadMethod(const SkSVGSpreadMethod & spread)21 void SkSVGGradient::setSpreadMethod(const SkSVGSpreadMethod& spread) {
22 fSpreadMethod = spread;
23 }
24
onSetAttribute(SkSVGAttribute attr,const SkSVGValue & v)25 void SkSVGGradient::onSetAttribute(SkSVGAttribute attr, const SkSVGValue& v) {
26 switch (attr) {
27 case SkSVGAttribute::kGradientTransform:
28 if (const auto* t = v.as<SkSVGTransformValue>()) {
29 this->setGradientTransform(*t);
30 }
31 break;
32 case SkSVGAttribute::kHref:
33 if (const auto* href = v.as<SkSVGStringValue>()) {
34 this->setHref(*href);
35 }
36 break;
37 case SkSVGAttribute::kSpreadMethod:
38 if (const auto* spread = v.as<SkSVGSpreadMethodValue>()) {
39 this->setSpreadMethod(*spread);
40 }
41 break;
42 default:
43 this->INHERITED::onSetAttribute(attr, v);
44 }
45 }
46
47 // https://www.w3.org/TR/SVG/pservers.html#LinearGradientElementHrefAttribute
collectColorStops(const SkSVGRenderContext & ctx,StopPositionArray * pos,StopColorArray * colors) const48 void SkSVGGradient::collectColorStops(const SkSVGRenderContext& ctx,
49 StopPositionArray* pos,
50 StopColorArray* colors) const {
51 // Used to resolve percentage offsets.
52 const SkSVGLengthContext ltx(SkSize::Make(1, 1));
53
54 for (const auto& child : fChildren) {
55 if (child->tag() != SkSVGTag::kStop) {
56 continue;
57 }
58
59 const auto& stop = static_cast<const SkSVGStop&>(*child);
60 colors->push_back(SkColorSetA(stop.stopColor(),
61 SkScalarRoundToInt(stop.stopOpacity() * 255)));
62 pos->push_back(SkTPin(ltx.resolve(stop.offset(), SkSVGLengthContext::LengthType::kOther),
63 0.f, 1.f));
64 }
65
66 SkASSERT(colors->count() == pos->count());
67
68 if (pos->empty() && !fHref.value().isEmpty()) {
69 const auto* ref = ctx.findNodeById(fHref);
70 if (ref && (ref->tag() == SkSVGTag::kLinearGradient ||
71 ref->tag() == SkSVGTag::kRadialGradient)) {
72 static_cast<const SkSVGGradient*>(ref)->collectColorStops(ctx, pos, colors);
73 }
74 }
75 }
76
onAsPaint(const SkSVGRenderContext & ctx,SkPaint * paint) const77 bool SkSVGGradient::onAsPaint(const SkSVGRenderContext& ctx, SkPaint* paint) const {
78 StopColorArray colors;
79 StopPositionArray pos;
80
81 this->collectColorStops(ctx, &pos, &colors);
82
83 // TODO:
84 // * stop (lazy?) sorting
85 // * href loop detection
86 // * href attribute inheritance (not just color stops)
87 // * objectBoundingBox units support
88
89 static_assert(static_cast<SkTileMode>(SkSVGSpreadMethod::Type::kPad) ==
90 SkTileMode::kClamp, "SkSVGSpreadMethod::Type is out of sync");
91 static_assert(static_cast<SkTileMode>(SkSVGSpreadMethod::Type::kRepeat) ==
92 SkTileMode::kRepeat, "SkSVGSpreadMethod::Type is out of sync");
93 static_assert(static_cast<SkTileMode>(SkSVGSpreadMethod::Type::kReflect) ==
94 SkTileMode::kMirror, "SkSVGSpreadMethod::Type is out of sync");
95 const auto tileMode = static_cast<SkTileMode>(fSpreadMethod.type());
96
97 paint->setShader(this->onMakeShader(ctx, colors.begin(), pos.begin(), colors.count(), tileMode,
98 fGradientTransform.value()));
99 return true;
100 }
101