• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "include/private/SkTPin.h"
9 #include "modules/svg/include/SkSVGGradient.h"
10 #include "modules/svg/include/SkSVGRenderContext.h"
11 #include "modules/svg/include/SkSVGStop.h"
12 #include "modules/svg/include/SkSVGValue.h"
13 
parseAndSetAttribute(const char * name,const char * value)14 bool SkSVGGradient::parseAndSetAttribute(const char* name, const char* value) {
15     return INHERITED::parseAndSetAttribute(name, value) ||
16            this->setGradientTransform(SkSVGAttributeParser::parse<SkSVGTransformType>(
17                    "gradientTransform", name, value)) ||
18            this->setHref(SkSVGAttributeParser::parse<SkSVGIRI>("xlink:href", name, value)) ||
19            this->setSpreadMethod(
20                    SkSVGAttributeParser::parse<SkSVGSpreadMethod>("spreadMethod", name, value)) ||
21            this->setGradientUnits(SkSVGAttributeParser::parse<SkSVGObjectBoundingBoxUnits>(
22                    "gradientUnits", name, value));
23 }
24 
25 // https://www.w3.org/TR/SVG11/pservers.html#LinearGradientElementHrefAttribute
collectColorStops(const SkSVGRenderContext & ctx,StopPositionArray * pos,StopColorArray * colors) const26 void SkSVGGradient::collectColorStops(const SkSVGRenderContext& ctx,
27                                       StopPositionArray* pos,
28                                       StopColorArray* colors) const {
29     // Used to resolve percentage offsets.
30     const SkSVGLengthContext ltx(SkSize::Make(1, 1));
31 
32     for (const auto& child : fChildren) {
33         if (child->tag() != SkSVGTag::kStop) {
34             continue;
35         }
36 
37         const auto& stop = static_cast<const SkSVGStop&>(*child);
38         colors->push_back(this->resolveStopColor(ctx, stop));
39         pos->push_back(SkTPin(ltx.resolve(stop.getOffset(), SkSVGLengthContext::LengthType::kOther),
40                               0.f, 1.f));
41     }
42 
43     SkASSERT(colors->count() == pos->count());
44 
45     if (pos->empty() && !fHref.iri().isEmpty()) {
46         const auto ref = ctx.findNodeById(fHref);
47         if (ref && (ref->tag() == SkSVGTag::kLinearGradient ||
48                     ref->tag() == SkSVGTag::kRadialGradient)) {
49             static_cast<const SkSVGGradient*>(ref.get())->collectColorStops(ctx, pos, colors);
50         }
51     }
52 }
53 
resolveStopColor(const SkSVGRenderContext & ctx,const SkSVGStop & stop) const54 SkColor4f SkSVGGradient::resolveStopColor(const SkSVGRenderContext& ctx,
55                                           const SkSVGStop& stop) const {
56     const auto& stopColor = stop.getStopColor();
57     const auto& stopOpacity = stop.getStopOpacity();
58     // Uninherited presentation attrs should have a concrete value at this point.
59     if (!stopColor.isValue() || !stopOpacity.isValue()) {
60         SkDebugf("unhandled: stop-color or stop-opacity has no value\n");
61         return SkColors::kBlack;
62     }
63 
64     const auto color = SkColor4f::FromColor(ctx.resolveSvgColor(*stopColor));
65 
66     return { color.fR, color.fG, color.fB, *stopOpacity };
67 }
68 
onAsPaint(const SkSVGRenderContext & ctx,SkPaint * paint) const69 bool SkSVGGradient::onAsPaint(const SkSVGRenderContext& ctx, SkPaint* paint) const {
70     StopColorArray colors;
71     StopPositionArray pos;
72 
73     this->collectColorStops(ctx, &pos, &colors);
74 
75     // TODO:
76     //       * stop (lazy?) sorting
77     //       * href loop detection
78     //       * href attribute inheritance (not just color stops)
79     //       * objectBoundingBox units support
80 
81     static_assert(static_cast<SkTileMode>(SkSVGSpreadMethod::Type::kPad) ==
82                   SkTileMode::kClamp, "SkSVGSpreadMethod::Type is out of sync");
83     static_assert(static_cast<SkTileMode>(SkSVGSpreadMethod::Type::kRepeat) ==
84                   SkTileMode::kRepeat, "SkSVGSpreadMethod::Type is out of sync");
85     static_assert(static_cast<SkTileMode>(SkSVGSpreadMethod::Type::kReflect) ==
86                   SkTileMode::kMirror, "SkSVGSpreadMethod::Type is out of sync");
87     const auto tileMode = static_cast<SkTileMode>(fSpreadMethod.type());
88 
89     const auto obbt = ctx.transformForCurrentOBB(fGradientUnits);
90     const auto localMatrix = SkMatrix::Translate(obbt.offset.x, obbt.offset.y)
91                            * SkMatrix::Scale(obbt.scale.x, obbt.scale.y)
92                            * fGradientTransform;
93 
94     paint->setShader(this->onMakeShader(ctx, colors.begin(), pos.begin(), colors.count(), tileMode,
95                                         localMatrix));
96     return true;
97 }
98 
99 // https://www.w3.org/TR/SVG11/pservers.html#LinearGradientElementSpreadMethodAttribute
100 template <>
parse(SkSVGSpreadMethod * spread)101 bool SkSVGAttributeParser::parse(SkSVGSpreadMethod* spread) {
102     static const struct {
103         SkSVGSpreadMethod::Type fType;
104         const char*             fName;
105     } gSpreadInfo[] = {
106         { SkSVGSpreadMethod::Type::kPad    , "pad"     },
107         { SkSVGSpreadMethod::Type::kReflect, "reflect" },
108         { SkSVGSpreadMethod::Type::kRepeat , "repeat"  },
109     };
110 
111     bool parsedValue = false;
112     for (size_t i = 0; i < SK_ARRAY_COUNT(gSpreadInfo); ++i) {
113         if (this->parseExpectedStringToken(gSpreadInfo[i].fName)) {
114             *spread = SkSVGSpreadMethod(gSpreadInfo[i].fType);
115             parsedValue = true;
116             break;
117         }
118     }
119 
120     return parsedValue && this->parseEOSToken();
121 }
122