xref: /aosp_15_r20/external/skia/src/svg/SkSVGDevice.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1*c8dee2aaSAndroid Build Coastguard Worker /*
2*c8dee2aaSAndroid Build Coastguard Worker  * Copyright 2015 Google Inc.
3*c8dee2aaSAndroid Build Coastguard Worker  *
4*c8dee2aaSAndroid Build Coastguard Worker  * Use of this source code is governed by a BSD-style license that can be
5*c8dee2aaSAndroid Build Coastguard Worker  * found in the LICENSE file.
6*c8dee2aaSAndroid Build Coastguard Worker  */
7*c8dee2aaSAndroid Build Coastguard Worker 
8*c8dee2aaSAndroid Build Coastguard Worker #include "src/svg/SkSVGDevice.h"
9*c8dee2aaSAndroid Build Coastguard Worker 
10*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkBitmap.h"
11*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkBlendMode.h"
12*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkClipOp.h"
13*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkColor.h"
14*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkColorFilter.h"
15*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkData.h"
16*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkDataTable.h"
17*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkFont.h"
18*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkFontStyle.h"
19*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkImage.h"
20*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkImageInfo.h"
21*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkMatrix.h"
22*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkPaint.h"
23*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkPath.h"
24*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkPathBuilder.h"
25*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkPathEffect.h"
26*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkPathTypes.h"
27*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkPathUtils.h"
28*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkPoint.h"
29*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkRRect.h"
30*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkRect.h"
31*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkScalar.h"
32*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkShader.h"
33*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkSize.h"
34*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkSpan.h"
35*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkStream.h"
36*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkString.h"
37*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkSurfaceProps.h"
38*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkTileMode.h"
39*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkTypeface.h"
40*c8dee2aaSAndroid Build Coastguard Worker #include "include/encode/SkPngEncoder.h"
41*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/base/SkDebug.h"
42*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/base/SkNoncopyable.h"
43*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/base/SkTPin.h"
44*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/base/SkTemplates.h"
45*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/base/SkTo.h"
46*c8dee2aaSAndroid Build Coastguard Worker #include "include/svg/SkSVGCanvas.h"
47*c8dee2aaSAndroid Build Coastguard Worker #include "src/base/SkBase64.h"
48*c8dee2aaSAndroid Build Coastguard Worker #include "src/base/SkTLazy.h"
49*c8dee2aaSAndroid Build Coastguard Worker #include "src/core/SkAnnotationKeys.h"
50*c8dee2aaSAndroid Build Coastguard Worker #include "src/core/SkClipStack.h"
51*c8dee2aaSAndroid Build Coastguard Worker #include "src/core/SkDevice.h"
52*c8dee2aaSAndroid Build Coastguard Worker #include "src/core/SkFontPriv.h"
53*c8dee2aaSAndroid Build Coastguard Worker #include "src/core/SkTHash.h"
54*c8dee2aaSAndroid Build Coastguard Worker #include "src/image/SkImage_Base.h"
55*c8dee2aaSAndroid Build Coastguard Worker #include "src/shaders/SkColorShader.h"
56*c8dee2aaSAndroid Build Coastguard Worker #include "src/shaders/SkShaderBase.h"
57*c8dee2aaSAndroid Build Coastguard Worker #include "src/text/GlyphRun.h"
58*c8dee2aaSAndroid Build Coastguard Worker #include "src/xml/SkXMLWriter.h"
59*c8dee2aaSAndroid Build Coastguard Worker 
60*c8dee2aaSAndroid Build Coastguard Worker #include <cstring>
61*c8dee2aaSAndroid Build Coastguard Worker #include <memory>
62*c8dee2aaSAndroid Build Coastguard Worker #include <string>
63*c8dee2aaSAndroid Build Coastguard Worker #include <utility>
64*c8dee2aaSAndroid Build Coastguard Worker 
65*c8dee2aaSAndroid Build Coastguard Worker using namespace skia_private;
66*c8dee2aaSAndroid Build Coastguard Worker 
67*c8dee2aaSAndroid Build Coastguard Worker class SkBlender;
68*c8dee2aaSAndroid Build Coastguard Worker class SkMesh;
69*c8dee2aaSAndroid Build Coastguard Worker class SkVertices;
70*c8dee2aaSAndroid Build Coastguard Worker struct SkSamplingOptions;
71*c8dee2aaSAndroid Build Coastguard Worker 
72*c8dee2aaSAndroid Build Coastguard Worker namespace {
73*c8dee2aaSAndroid Build Coastguard Worker 
svg_color(SkColor color)74*c8dee2aaSAndroid Build Coastguard Worker static SkString svg_color(SkColor color) {
75*c8dee2aaSAndroid Build Coastguard Worker     // https://www.w3.org/TR/css-color-3/#html4
76*c8dee2aaSAndroid Build Coastguard Worker     auto named_color = [](SkColor c) -> const char* {
77*c8dee2aaSAndroid Build Coastguard Worker         switch (c & 0xffffff) {
78*c8dee2aaSAndroid Build Coastguard Worker         case 0x000000: return "black";
79*c8dee2aaSAndroid Build Coastguard Worker         case 0x000080: return "navy";
80*c8dee2aaSAndroid Build Coastguard Worker         case 0x0000ff: return "blue";
81*c8dee2aaSAndroid Build Coastguard Worker         case 0x008000: return "green";
82*c8dee2aaSAndroid Build Coastguard Worker         case 0x008080: return "teal";
83*c8dee2aaSAndroid Build Coastguard Worker         case 0x00ff00: return "lime";
84*c8dee2aaSAndroid Build Coastguard Worker         case 0x00ffff: return "aqua";
85*c8dee2aaSAndroid Build Coastguard Worker         case 0x800000: return "maroon";
86*c8dee2aaSAndroid Build Coastguard Worker         case 0x800080: return "purple";
87*c8dee2aaSAndroid Build Coastguard Worker         case 0x808000: return "olive";
88*c8dee2aaSAndroid Build Coastguard Worker         case 0x808080: return "gray";
89*c8dee2aaSAndroid Build Coastguard Worker         case 0xc0c0c0: return "silver";
90*c8dee2aaSAndroid Build Coastguard Worker         case 0xff0000: return "red";
91*c8dee2aaSAndroid Build Coastguard Worker         case 0xff00ff: return "fuchsia";
92*c8dee2aaSAndroid Build Coastguard Worker         case 0xffff00: return "yellow";
93*c8dee2aaSAndroid Build Coastguard Worker         case 0xffffff: return "white";
94*c8dee2aaSAndroid Build Coastguard Worker         default: break;
95*c8dee2aaSAndroid Build Coastguard Worker         }
96*c8dee2aaSAndroid Build Coastguard Worker 
97*c8dee2aaSAndroid Build Coastguard Worker         return nullptr;
98*c8dee2aaSAndroid Build Coastguard Worker     };
99*c8dee2aaSAndroid Build Coastguard Worker 
100*c8dee2aaSAndroid Build Coastguard Worker     if (const auto* nc = named_color(color)) {
101*c8dee2aaSAndroid Build Coastguard Worker         return SkString(nc);
102*c8dee2aaSAndroid Build Coastguard Worker     }
103*c8dee2aaSAndroid Build Coastguard Worker 
104*c8dee2aaSAndroid Build Coastguard Worker     uint8_t r = SkColorGetR(color);
105*c8dee2aaSAndroid Build Coastguard Worker     uint8_t g = SkColorGetG(color);
106*c8dee2aaSAndroid Build Coastguard Worker     uint8_t b = SkColorGetB(color);
107*c8dee2aaSAndroid Build Coastguard Worker 
108*c8dee2aaSAndroid Build Coastguard Worker     // Some users care about every byte here, so we'll use hex colors with single-digit channels
109*c8dee2aaSAndroid Build Coastguard Worker     // when possible.
110*c8dee2aaSAndroid Build Coastguard Worker     uint8_t rh = r >> 4;
111*c8dee2aaSAndroid Build Coastguard Worker     uint8_t rl = r & 0xf;
112*c8dee2aaSAndroid Build Coastguard Worker     uint8_t gh = g >> 4;
113*c8dee2aaSAndroid Build Coastguard Worker     uint8_t gl = g & 0xf;
114*c8dee2aaSAndroid Build Coastguard Worker     uint8_t bh = b >> 4;
115*c8dee2aaSAndroid Build Coastguard Worker     uint8_t bl = b & 0xf;
116*c8dee2aaSAndroid Build Coastguard Worker     if ((rh == rl) && (gh == gl) && (bh == bl)) {
117*c8dee2aaSAndroid Build Coastguard Worker         return SkStringPrintf("#%1X%1X%1X", rh, gh, bh);
118*c8dee2aaSAndroid Build Coastguard Worker     }
119*c8dee2aaSAndroid Build Coastguard Worker 
120*c8dee2aaSAndroid Build Coastguard Worker     return SkStringPrintf("#%02X%02X%02X", r, g, b);
121*c8dee2aaSAndroid Build Coastguard Worker }
122*c8dee2aaSAndroid Build Coastguard Worker 
svg_opacity(SkColor color)123*c8dee2aaSAndroid Build Coastguard Worker static SkScalar svg_opacity(SkColor color) {
124*c8dee2aaSAndroid Build Coastguard Worker     return SkIntToScalar(SkColorGetA(color)) / SK_AlphaOPAQUE;
125*c8dee2aaSAndroid Build Coastguard Worker }
126*c8dee2aaSAndroid Build Coastguard Worker 
127*c8dee2aaSAndroid Build Coastguard Worker // Keep in sync with SkPaint::Cap
128*c8dee2aaSAndroid Build Coastguard Worker static const char* cap_map[]  = {
129*c8dee2aaSAndroid Build Coastguard Worker     nullptr,    // kButt_Cap (default)
130*c8dee2aaSAndroid Build Coastguard Worker     "round", // kRound_Cap
131*c8dee2aaSAndroid Build Coastguard Worker     "square" // kSquare_Cap
132*c8dee2aaSAndroid Build Coastguard Worker };
133*c8dee2aaSAndroid Build Coastguard Worker static_assert(std::size(cap_map) == SkPaint::kCapCount, "missing_cap_map_entry");
134*c8dee2aaSAndroid Build Coastguard Worker 
svg_cap(SkPaint::Cap cap)135*c8dee2aaSAndroid Build Coastguard Worker static const char* svg_cap(SkPaint::Cap cap) {
136*c8dee2aaSAndroid Build Coastguard Worker     SkASSERT(static_cast<size_t>(cap) < std::size(cap_map));
137*c8dee2aaSAndroid Build Coastguard Worker     return cap_map[cap];
138*c8dee2aaSAndroid Build Coastguard Worker }
139*c8dee2aaSAndroid Build Coastguard Worker 
140*c8dee2aaSAndroid Build Coastguard Worker // Keep in sync with SkPaint::Join
141*c8dee2aaSAndroid Build Coastguard Worker static const char* join_map[] = {
142*c8dee2aaSAndroid Build Coastguard Worker     nullptr,    // kMiter_Join (default)
143*c8dee2aaSAndroid Build Coastguard Worker     "round", // kRound_Join
144*c8dee2aaSAndroid Build Coastguard Worker     "bevel"  // kBevel_Join
145*c8dee2aaSAndroid Build Coastguard Worker };
146*c8dee2aaSAndroid Build Coastguard Worker static_assert(std::size(join_map) == SkPaint::kJoinCount, "missing_join_map_entry");
147*c8dee2aaSAndroid Build Coastguard Worker 
svg_join(SkPaint::Join join)148*c8dee2aaSAndroid Build Coastguard Worker static const char* svg_join(SkPaint::Join join) {
149*c8dee2aaSAndroid Build Coastguard Worker     SkASSERT(join < std::size(join_map));
150*c8dee2aaSAndroid Build Coastguard Worker     return join_map[join];
151*c8dee2aaSAndroid Build Coastguard Worker }
152*c8dee2aaSAndroid Build Coastguard Worker 
svg_transform(const SkMatrix & t)153*c8dee2aaSAndroid Build Coastguard Worker static SkString svg_transform(const SkMatrix& t) {
154*c8dee2aaSAndroid Build Coastguard Worker     SkASSERT(!t.isIdentity());
155*c8dee2aaSAndroid Build Coastguard Worker 
156*c8dee2aaSAndroid Build Coastguard Worker     SkString tstr;
157*c8dee2aaSAndroid Build Coastguard Worker     switch (t.getType()) {
158*c8dee2aaSAndroid Build Coastguard Worker     case SkMatrix::kPerspective_Mask:
159*c8dee2aaSAndroid Build Coastguard Worker         // TODO: handle perspective matrices?
160*c8dee2aaSAndroid Build Coastguard Worker         break;
161*c8dee2aaSAndroid Build Coastguard Worker     case SkMatrix::kTranslate_Mask:
162*c8dee2aaSAndroid Build Coastguard Worker         tstr.printf("translate(%g %g)", t.getTranslateX(), t.getTranslateY());
163*c8dee2aaSAndroid Build Coastguard Worker         break;
164*c8dee2aaSAndroid Build Coastguard Worker     case SkMatrix::kScale_Mask:
165*c8dee2aaSAndroid Build Coastguard Worker         tstr.printf("scale(%g %g)", t.getScaleX(), t.getScaleY());
166*c8dee2aaSAndroid Build Coastguard Worker         break;
167*c8dee2aaSAndroid Build Coastguard Worker     default:
168*c8dee2aaSAndroid Build Coastguard Worker         // http://www.w3.org/TR/SVG/coords.html#TransformMatrixDefined
169*c8dee2aaSAndroid Build Coastguard Worker         //    | a c e |
170*c8dee2aaSAndroid Build Coastguard Worker         //    | b d f |
171*c8dee2aaSAndroid Build Coastguard Worker         //    | 0 0 1 |
172*c8dee2aaSAndroid Build Coastguard Worker         tstr.printf("matrix(%g %g %g %g %g %g)",
173*c8dee2aaSAndroid Build Coastguard Worker                     t.getScaleX(),     t.getSkewY(),
174*c8dee2aaSAndroid Build Coastguard Worker                     t.getSkewX(),      t.getScaleY(),
175*c8dee2aaSAndroid Build Coastguard Worker                     t.getTranslateX(), t.getTranslateY());
176*c8dee2aaSAndroid Build Coastguard Worker         break;
177*c8dee2aaSAndroid Build Coastguard Worker     }
178*c8dee2aaSAndroid Build Coastguard Worker 
179*c8dee2aaSAndroid Build Coastguard Worker     return tstr;
180*c8dee2aaSAndroid Build Coastguard Worker }
181*c8dee2aaSAndroid Build Coastguard Worker 
182*c8dee2aaSAndroid Build Coastguard Worker struct Resources {
Resources__anona5033e630111::Resources183*c8dee2aaSAndroid Build Coastguard Worker     Resources(const SkPaint& paint)
184*c8dee2aaSAndroid Build Coastguard Worker         : fPaintServer(svg_color(paint.getColor())) {}
185*c8dee2aaSAndroid Build Coastguard Worker 
186*c8dee2aaSAndroid Build Coastguard Worker     SkString fPaintServer;
187*c8dee2aaSAndroid Build Coastguard Worker     SkString fColorFilter;
188*c8dee2aaSAndroid Build Coastguard Worker };
189*c8dee2aaSAndroid Build Coastguard Worker 
190*c8dee2aaSAndroid Build Coastguard Worker // Determine if the paint requires us to reset the viewport.
191*c8dee2aaSAndroid Build Coastguard Worker // Currently, we do this whenever the paint shader calls
192*c8dee2aaSAndroid Build Coastguard Worker // for a repeating image.
RequiresViewportReset(const SkPaint & paint)193*c8dee2aaSAndroid Build Coastguard Worker bool RequiresViewportReset(const SkPaint& paint) {
194*c8dee2aaSAndroid Build Coastguard Worker   SkShader* shader = paint.getShader();
195*c8dee2aaSAndroid Build Coastguard Worker   if (!shader)
196*c8dee2aaSAndroid Build Coastguard Worker     return false;
197*c8dee2aaSAndroid Build Coastguard Worker 
198*c8dee2aaSAndroid Build Coastguard Worker   SkTileMode xy[2];
199*c8dee2aaSAndroid Build Coastguard Worker   SkImage* image = shader->isAImage(nullptr, xy);
200*c8dee2aaSAndroid Build Coastguard Worker 
201*c8dee2aaSAndroid Build Coastguard Worker   if (!image)
202*c8dee2aaSAndroid Build Coastguard Worker     return false;
203*c8dee2aaSAndroid Build Coastguard Worker 
204*c8dee2aaSAndroid Build Coastguard Worker   for (int i = 0; i < 2; i++) {
205*c8dee2aaSAndroid Build Coastguard Worker     if (xy[i] == SkTileMode::kRepeat)
206*c8dee2aaSAndroid Build Coastguard Worker       return true;
207*c8dee2aaSAndroid Build Coastguard Worker   }
208*c8dee2aaSAndroid Build Coastguard Worker   return false;
209*c8dee2aaSAndroid Build Coastguard Worker }
210*c8dee2aaSAndroid Build Coastguard Worker 
AddPath(const sktext::GlyphRun & glyphRun,const SkPoint & offset,SkPath * path)211*c8dee2aaSAndroid Build Coastguard Worker void AddPath(const sktext::GlyphRun& glyphRun, const SkPoint& offset, SkPath* path) {
212*c8dee2aaSAndroid Build Coastguard Worker     struct Rec {
213*c8dee2aaSAndroid Build Coastguard Worker         SkPath*        fPath;
214*c8dee2aaSAndroid Build Coastguard Worker         const SkPoint  fOffset;
215*c8dee2aaSAndroid Build Coastguard Worker         const SkPoint* fPos;
216*c8dee2aaSAndroid Build Coastguard Worker     } rec = { path, offset, glyphRun.positions().data() };
217*c8dee2aaSAndroid Build Coastguard Worker 
218*c8dee2aaSAndroid Build Coastguard Worker     glyphRun.font().getPaths(glyphRun.glyphsIDs().data(), SkToInt(glyphRun.glyphsIDs().size()),
219*c8dee2aaSAndroid Build Coastguard Worker             [](const SkPath* path, const SkMatrix& mx, void* ctx) {
220*c8dee2aaSAndroid Build Coastguard Worker                 Rec* rec = reinterpret_cast<Rec*>(ctx);
221*c8dee2aaSAndroid Build Coastguard Worker                 if (path) {
222*c8dee2aaSAndroid Build Coastguard Worker                     SkMatrix total = mx;
223*c8dee2aaSAndroid Build Coastguard Worker                     total.postTranslate(rec->fPos->fX + rec->fOffset.fX,
224*c8dee2aaSAndroid Build Coastguard Worker                                         rec->fPos->fY + rec->fOffset.fY);
225*c8dee2aaSAndroid Build Coastguard Worker                     rec->fPath->addPath(*path, total);
226*c8dee2aaSAndroid Build Coastguard Worker                 } else {
227*c8dee2aaSAndroid Build Coastguard Worker                     // TODO: this is going to drop color emojis.
228*c8dee2aaSAndroid Build Coastguard Worker                 }
229*c8dee2aaSAndroid Build Coastguard Worker                 rec->fPos += 1; // move to the next glyph's position
230*c8dee2aaSAndroid Build Coastguard Worker             }, &rec);
231*c8dee2aaSAndroid Build Coastguard Worker }
232*c8dee2aaSAndroid Build Coastguard Worker 
233*c8dee2aaSAndroid Build Coastguard Worker }  // namespace
234*c8dee2aaSAndroid Build Coastguard Worker 
235*c8dee2aaSAndroid Build Coastguard Worker // For now all this does is serve unique serial IDs, but it will eventually evolve to track
236*c8dee2aaSAndroid Build Coastguard Worker // and deduplicate resources.
237*c8dee2aaSAndroid Build Coastguard Worker class SkSVGDevice::ResourceBucket : ::SkNoncopyable {
238*c8dee2aaSAndroid Build Coastguard Worker public:
ResourceBucket()239*c8dee2aaSAndroid Build Coastguard Worker     ResourceBucket()
240*c8dee2aaSAndroid Build Coastguard Worker             : fGradientCount(0)
241*c8dee2aaSAndroid Build Coastguard Worker             , fPathCount(0)
242*c8dee2aaSAndroid Build Coastguard Worker             , fImageCount(0)
243*c8dee2aaSAndroid Build Coastguard Worker             , fPatternCount(0)
244*c8dee2aaSAndroid Build Coastguard Worker             , fColorFilterCount(0) {}
245*c8dee2aaSAndroid Build Coastguard Worker 
addLinearGradient()246*c8dee2aaSAndroid Build Coastguard Worker     SkString addLinearGradient() {
247*c8dee2aaSAndroid Build Coastguard Worker         return SkStringPrintf("gradient_%u", fGradientCount++);
248*c8dee2aaSAndroid Build Coastguard Worker     }
249*c8dee2aaSAndroid Build Coastguard Worker 
addPath()250*c8dee2aaSAndroid Build Coastguard Worker     SkString addPath() {
251*c8dee2aaSAndroid Build Coastguard Worker         return SkStringPrintf("path_%u", fPathCount++);
252*c8dee2aaSAndroid Build Coastguard Worker     }
253*c8dee2aaSAndroid Build Coastguard Worker 
addImage()254*c8dee2aaSAndroid Build Coastguard Worker     SkString addImage() {
255*c8dee2aaSAndroid Build Coastguard Worker         return SkStringPrintf("img_%u", fImageCount++);
256*c8dee2aaSAndroid Build Coastguard Worker     }
257*c8dee2aaSAndroid Build Coastguard Worker 
addColorFilter()258*c8dee2aaSAndroid Build Coastguard Worker     SkString addColorFilter() { return SkStringPrintf("cfilter_%u", fColorFilterCount++); }
259*c8dee2aaSAndroid Build Coastguard Worker 
addPattern()260*c8dee2aaSAndroid Build Coastguard Worker     SkString addPattern() {
261*c8dee2aaSAndroid Build Coastguard Worker       return SkStringPrintf("pattern_%u", fPatternCount++);
262*c8dee2aaSAndroid Build Coastguard Worker     }
263*c8dee2aaSAndroid Build Coastguard Worker 
264*c8dee2aaSAndroid Build Coastguard Worker private:
265*c8dee2aaSAndroid Build Coastguard Worker     uint32_t fGradientCount;
266*c8dee2aaSAndroid Build Coastguard Worker     uint32_t fPathCount;
267*c8dee2aaSAndroid Build Coastguard Worker     uint32_t fImageCount;
268*c8dee2aaSAndroid Build Coastguard Worker     uint32_t fPatternCount;
269*c8dee2aaSAndroid Build Coastguard Worker     uint32_t fColorFilterCount;
270*c8dee2aaSAndroid Build Coastguard Worker };
271*c8dee2aaSAndroid Build Coastguard Worker 
272*c8dee2aaSAndroid Build Coastguard Worker struct SkSVGDevice::MxCp {
273*c8dee2aaSAndroid Build Coastguard Worker     const SkMatrix* fMatrix;
274*c8dee2aaSAndroid Build Coastguard Worker     const SkClipStack*  fClipStack;
275*c8dee2aaSAndroid Build Coastguard Worker 
MxCpSkSVGDevice::MxCp276*c8dee2aaSAndroid Build Coastguard Worker     MxCp(const SkMatrix* mx, const SkClipStack* cs) : fMatrix(mx), fClipStack(cs) {}
MxCpSkSVGDevice::MxCp277*c8dee2aaSAndroid Build Coastguard Worker     MxCp(SkSVGDevice* device) : fMatrix(&device->localToDevice()), fClipStack(&device->cs()) {}
278*c8dee2aaSAndroid Build Coastguard Worker };
279*c8dee2aaSAndroid Build Coastguard Worker 
280*c8dee2aaSAndroid Build Coastguard Worker class SkSVGDevice::AutoElement : ::SkNoncopyable {
281*c8dee2aaSAndroid Build Coastguard Worker public:
AutoElement(const char name[],SkXMLWriter * writer)282*c8dee2aaSAndroid Build Coastguard Worker     AutoElement(const char name[], SkXMLWriter* writer)
283*c8dee2aaSAndroid Build Coastguard Worker         : fWriter(writer)
284*c8dee2aaSAndroid Build Coastguard Worker         , fResourceBucket(nullptr) {
285*c8dee2aaSAndroid Build Coastguard Worker         fWriter->startElement(name);
286*c8dee2aaSAndroid Build Coastguard Worker     }
287*c8dee2aaSAndroid Build Coastguard Worker 
AutoElement(const char name[],const std::unique_ptr<SkXMLWriter> & writer)288*c8dee2aaSAndroid Build Coastguard Worker     AutoElement(const char name[], const std::unique_ptr<SkXMLWriter>& writer)
289*c8dee2aaSAndroid Build Coastguard Worker         : AutoElement(name, writer.get()) {}
290*c8dee2aaSAndroid Build Coastguard Worker 
AutoElement(const char name[],SkSVGDevice * svgdev,ResourceBucket * bucket,const MxCp & mc,const SkPaint & paint)291*c8dee2aaSAndroid Build Coastguard Worker     AutoElement(const char name[], SkSVGDevice* svgdev,
292*c8dee2aaSAndroid Build Coastguard Worker                 ResourceBucket* bucket, const MxCp& mc, const SkPaint& paint)
293*c8dee2aaSAndroid Build Coastguard Worker         : fWriter(svgdev->fWriter.get())
294*c8dee2aaSAndroid Build Coastguard Worker         , fResourceBucket(bucket) {
295*c8dee2aaSAndroid Build Coastguard Worker 
296*c8dee2aaSAndroid Build Coastguard Worker         svgdev->syncClipStack(*mc.fClipStack);
297*c8dee2aaSAndroid Build Coastguard Worker         Resources res = this->addResources(mc, paint);
298*c8dee2aaSAndroid Build Coastguard Worker 
299*c8dee2aaSAndroid Build Coastguard Worker         fWriter->startElement(name);
300*c8dee2aaSAndroid Build Coastguard Worker 
301*c8dee2aaSAndroid Build Coastguard Worker         this->addPaint(paint, res);
302*c8dee2aaSAndroid Build Coastguard Worker 
303*c8dee2aaSAndroid Build Coastguard Worker         if (!mc.fMatrix->isIdentity()) {
304*c8dee2aaSAndroid Build Coastguard Worker             this->addAttribute("transform", svg_transform(*mc.fMatrix));
305*c8dee2aaSAndroid Build Coastguard Worker         }
306*c8dee2aaSAndroid Build Coastguard Worker     }
307*c8dee2aaSAndroid Build Coastguard Worker 
~AutoElement()308*c8dee2aaSAndroid Build Coastguard Worker     ~AutoElement() {
309*c8dee2aaSAndroid Build Coastguard Worker         fWriter->endElement();
310*c8dee2aaSAndroid Build Coastguard Worker     }
311*c8dee2aaSAndroid Build Coastguard Worker 
addAttribute(const char name[],const char val[])312*c8dee2aaSAndroid Build Coastguard Worker     void addAttribute(const char name[], const char val[]) {
313*c8dee2aaSAndroid Build Coastguard Worker         fWriter->addAttribute(name, val);
314*c8dee2aaSAndroid Build Coastguard Worker     }
315*c8dee2aaSAndroid Build Coastguard Worker 
addAttribute(const char name[],const SkString & val)316*c8dee2aaSAndroid Build Coastguard Worker     void addAttribute(const char name[], const SkString& val) {
317*c8dee2aaSAndroid Build Coastguard Worker         fWriter->addAttribute(name, val.c_str());
318*c8dee2aaSAndroid Build Coastguard Worker     }
319*c8dee2aaSAndroid Build Coastguard Worker 
addAttribute(const char name[],int32_t val)320*c8dee2aaSAndroid Build Coastguard Worker     void addAttribute(const char name[], int32_t val) {
321*c8dee2aaSAndroid Build Coastguard Worker         fWriter->addS32Attribute(name, val);
322*c8dee2aaSAndroid Build Coastguard Worker     }
323*c8dee2aaSAndroid Build Coastguard Worker 
addAttribute(const char name[],SkScalar val)324*c8dee2aaSAndroid Build Coastguard Worker     void addAttribute(const char name[], SkScalar val) {
325*c8dee2aaSAndroid Build Coastguard Worker         fWriter->addScalarAttribute(name, val);
326*c8dee2aaSAndroid Build Coastguard Worker     }
327*c8dee2aaSAndroid Build Coastguard Worker 
addText(const SkString & text)328*c8dee2aaSAndroid Build Coastguard Worker     void addText(const SkString& text) {
329*c8dee2aaSAndroid Build Coastguard Worker         fWriter->addText(text.c_str(), text.size());
330*c8dee2aaSAndroid Build Coastguard Worker     }
331*c8dee2aaSAndroid Build Coastguard Worker 
332*c8dee2aaSAndroid Build Coastguard Worker     void addRectAttributes(const SkRect&);
333*c8dee2aaSAndroid Build Coastguard Worker     void addPathAttributes(const SkPath&, SkParsePath::PathEncoding);
334*c8dee2aaSAndroid Build Coastguard Worker     void addTextAttributes(const SkFont&);
335*c8dee2aaSAndroid Build Coastguard Worker 
336*c8dee2aaSAndroid Build Coastguard Worker private:
337*c8dee2aaSAndroid Build Coastguard Worker     Resources addResources(const MxCp&, const SkPaint& paint);
338*c8dee2aaSAndroid Build Coastguard Worker     void addShaderResources(const SkPaint& paint, Resources* resources);
339*c8dee2aaSAndroid Build Coastguard Worker     void addGradientShaderResources(const SkShader* shader, const SkPaint& paint,
340*c8dee2aaSAndroid Build Coastguard Worker                                     Resources* resources);
341*c8dee2aaSAndroid Build Coastguard Worker     void addColorFilterResources(const SkColorFilter& cf, Resources* resources);
342*c8dee2aaSAndroid Build Coastguard Worker     void addImageShaderResources(const SkShader* shader, const SkPaint& paint,
343*c8dee2aaSAndroid Build Coastguard Worker                                  Resources* resources);
344*c8dee2aaSAndroid Build Coastguard Worker 
345*c8dee2aaSAndroid Build Coastguard Worker     void addPatternDef(const SkBitmap& bm);
346*c8dee2aaSAndroid Build Coastguard Worker 
347*c8dee2aaSAndroid Build Coastguard Worker     void addPaint(const SkPaint& paint, const Resources& resources);
348*c8dee2aaSAndroid Build Coastguard Worker 
349*c8dee2aaSAndroid Build Coastguard Worker     SkString addLinearGradientDef(const SkShaderBase::GradientInfo& info,
350*c8dee2aaSAndroid Build Coastguard Worker                                   const SkShader* shader,
351*c8dee2aaSAndroid Build Coastguard Worker                                   const SkMatrix& localMatrix);
352*c8dee2aaSAndroid Build Coastguard Worker 
353*c8dee2aaSAndroid Build Coastguard Worker     SkXMLWriter*               fWriter;
354*c8dee2aaSAndroid Build Coastguard Worker     ResourceBucket*            fResourceBucket;
355*c8dee2aaSAndroid Build Coastguard Worker };
356*c8dee2aaSAndroid Build Coastguard Worker 
addPaint(const SkPaint & paint,const Resources & resources)357*c8dee2aaSAndroid Build Coastguard Worker void SkSVGDevice::AutoElement::addPaint(const SkPaint& paint, const Resources& resources) {
358*c8dee2aaSAndroid Build Coastguard Worker     // Path effects are applied to all vector graphics (rects, rrects, ovals,
359*c8dee2aaSAndroid Build Coastguard Worker     // paths etc).  This should only happen when a path effect is attached to
360*c8dee2aaSAndroid Build Coastguard Worker     // non-vector graphics (text, image) or a new vector graphics primitive is
361*c8dee2aaSAndroid Build Coastguard Worker     //added that is not handled by base drawPath() routine.
362*c8dee2aaSAndroid Build Coastguard Worker     if (paint.getPathEffect() != nullptr) {
363*c8dee2aaSAndroid Build Coastguard Worker         SkDebugf("Unsupported path effect in addPaint.");
364*c8dee2aaSAndroid Build Coastguard Worker     }
365*c8dee2aaSAndroid Build Coastguard Worker     SkPaint::Style style = paint.getStyle();
366*c8dee2aaSAndroid Build Coastguard Worker     if (style == SkPaint::kFill_Style || style == SkPaint::kStrokeAndFill_Style) {
367*c8dee2aaSAndroid Build Coastguard Worker         static constexpr char kDefaultFill[] = "black";
368*c8dee2aaSAndroid Build Coastguard Worker         if (!resources.fPaintServer.equals(kDefaultFill)) {
369*c8dee2aaSAndroid Build Coastguard Worker             this->addAttribute("fill", resources.fPaintServer);
370*c8dee2aaSAndroid Build Coastguard Worker         }
371*c8dee2aaSAndroid Build Coastguard Worker         if (SK_AlphaOPAQUE != SkColorGetA(paint.getColor())) {
372*c8dee2aaSAndroid Build Coastguard Worker             this->addAttribute("fill-opacity", svg_opacity(paint.getColor()));
373*c8dee2aaSAndroid Build Coastguard Worker         }
374*c8dee2aaSAndroid Build Coastguard Worker     } else {
375*c8dee2aaSAndroid Build Coastguard Worker         SkASSERT(style == SkPaint::kStroke_Style);
376*c8dee2aaSAndroid Build Coastguard Worker         this->addAttribute("fill", "none");
377*c8dee2aaSAndroid Build Coastguard Worker     }
378*c8dee2aaSAndroid Build Coastguard Worker 
379*c8dee2aaSAndroid Build Coastguard Worker     if (!resources.fColorFilter.isEmpty()) {
380*c8dee2aaSAndroid Build Coastguard Worker         this->addAttribute("filter", resources.fColorFilter.c_str());
381*c8dee2aaSAndroid Build Coastguard Worker     }
382*c8dee2aaSAndroid Build Coastguard Worker 
383*c8dee2aaSAndroid Build Coastguard Worker     if (style == SkPaint::kStroke_Style || style == SkPaint::kStrokeAndFill_Style) {
384*c8dee2aaSAndroid Build Coastguard Worker         this->addAttribute("stroke", resources.fPaintServer);
385*c8dee2aaSAndroid Build Coastguard Worker 
386*c8dee2aaSAndroid Build Coastguard Worker         SkScalar strokeWidth = paint.getStrokeWidth();
387*c8dee2aaSAndroid Build Coastguard Worker         if (strokeWidth == 0) {
388*c8dee2aaSAndroid Build Coastguard Worker             // Hairline stroke
389*c8dee2aaSAndroid Build Coastguard Worker             strokeWidth = 1;
390*c8dee2aaSAndroid Build Coastguard Worker             this->addAttribute("vector-effect", "non-scaling-stroke");
391*c8dee2aaSAndroid Build Coastguard Worker         }
392*c8dee2aaSAndroid Build Coastguard Worker         this->addAttribute("stroke-width", strokeWidth);
393*c8dee2aaSAndroid Build Coastguard Worker 
394*c8dee2aaSAndroid Build Coastguard Worker         if (const char* cap = svg_cap(paint.getStrokeCap())) {
395*c8dee2aaSAndroid Build Coastguard Worker             this->addAttribute("stroke-linecap", cap);
396*c8dee2aaSAndroid Build Coastguard Worker         }
397*c8dee2aaSAndroid Build Coastguard Worker 
398*c8dee2aaSAndroid Build Coastguard Worker         if (const char* join = svg_join(paint.getStrokeJoin())) {
399*c8dee2aaSAndroid Build Coastguard Worker             this->addAttribute("stroke-linejoin", join);
400*c8dee2aaSAndroid Build Coastguard Worker         }
401*c8dee2aaSAndroid Build Coastguard Worker 
402*c8dee2aaSAndroid Build Coastguard Worker         if (paint.getStrokeJoin() == SkPaint::kMiter_Join) {
403*c8dee2aaSAndroid Build Coastguard Worker             this->addAttribute("stroke-miterlimit", paint.getStrokeMiter());
404*c8dee2aaSAndroid Build Coastguard Worker         }
405*c8dee2aaSAndroid Build Coastguard Worker 
406*c8dee2aaSAndroid Build Coastguard Worker         if (SK_AlphaOPAQUE != SkColorGetA(paint.getColor())) {
407*c8dee2aaSAndroid Build Coastguard Worker             this->addAttribute("stroke-opacity", svg_opacity(paint.getColor()));
408*c8dee2aaSAndroid Build Coastguard Worker         }
409*c8dee2aaSAndroid Build Coastguard Worker     } else {
410*c8dee2aaSAndroid Build Coastguard Worker         SkASSERT(style == SkPaint::kFill_Style);
411*c8dee2aaSAndroid Build Coastguard Worker         // SVG default stroke value is "none".
412*c8dee2aaSAndroid Build Coastguard Worker     }
413*c8dee2aaSAndroid Build Coastguard Worker }
414*c8dee2aaSAndroid Build Coastguard Worker 
addResources(const MxCp & mc,const SkPaint & paint)415*c8dee2aaSAndroid Build Coastguard Worker Resources SkSVGDevice::AutoElement::addResources(const MxCp& mc, const SkPaint& paint) {
416*c8dee2aaSAndroid Build Coastguard Worker     Resources resources(paint);
417*c8dee2aaSAndroid Build Coastguard Worker 
418*c8dee2aaSAndroid Build Coastguard Worker     if (paint.getShader()) {
419*c8dee2aaSAndroid Build Coastguard Worker         AutoElement defs("defs", fWriter);
420*c8dee2aaSAndroid Build Coastguard Worker 
421*c8dee2aaSAndroid Build Coastguard Worker         this->addShaderResources(paint, &resources);
422*c8dee2aaSAndroid Build Coastguard Worker     }
423*c8dee2aaSAndroid Build Coastguard Worker 
424*c8dee2aaSAndroid Build Coastguard Worker     if (const SkColorFilter* cf = paint.getColorFilter()) {
425*c8dee2aaSAndroid Build Coastguard Worker         // TODO: Implement skia color filters for blend modes other than SrcIn
426*c8dee2aaSAndroid Build Coastguard Worker         SkBlendMode mode;
427*c8dee2aaSAndroid Build Coastguard Worker         if (cf->asAColorMode(nullptr, &mode) && mode == SkBlendMode::kSrcIn) {
428*c8dee2aaSAndroid Build Coastguard Worker             this->addColorFilterResources(*cf, &resources);
429*c8dee2aaSAndroid Build Coastguard Worker         }
430*c8dee2aaSAndroid Build Coastguard Worker     }
431*c8dee2aaSAndroid Build Coastguard Worker 
432*c8dee2aaSAndroid Build Coastguard Worker     return resources;
433*c8dee2aaSAndroid Build Coastguard Worker }
434*c8dee2aaSAndroid Build Coastguard Worker 
addGradientShaderResources(const SkShader * shader,const SkPaint & paint,Resources * resources)435*c8dee2aaSAndroid Build Coastguard Worker void SkSVGDevice::AutoElement::addGradientShaderResources(const SkShader* shader,
436*c8dee2aaSAndroid Build Coastguard Worker                                                           const SkPaint& paint,
437*c8dee2aaSAndroid Build Coastguard Worker                                                           Resources* resources) {
438*c8dee2aaSAndroid Build Coastguard Worker     SkASSERT(shader);
439*c8dee2aaSAndroid Build Coastguard Worker     if (as_SB(shader)->type() == SkShaderBase::ShaderType::kColor) {
440*c8dee2aaSAndroid Build Coastguard Worker         auto colorShader = static_cast<const SkColorShader*>(shader);
441*c8dee2aaSAndroid Build Coastguard Worker         resources->fPaintServer = svg_color(colorShader->color());
442*c8dee2aaSAndroid Build Coastguard Worker         return;
443*c8dee2aaSAndroid Build Coastguard Worker     }
444*c8dee2aaSAndroid Build Coastguard Worker 
445*c8dee2aaSAndroid Build Coastguard Worker     SkShaderBase::GradientInfo grInfo;
446*c8dee2aaSAndroid Build Coastguard Worker     const auto gradient_type = as_SB(shader)->asGradient(&grInfo);
447*c8dee2aaSAndroid Build Coastguard Worker 
448*c8dee2aaSAndroid Build Coastguard Worker     if (gradient_type != SkShaderBase::GradientType::kLinear) {
449*c8dee2aaSAndroid Build Coastguard Worker         // TODO: other gradient support
450*c8dee2aaSAndroid Build Coastguard Worker         return;
451*c8dee2aaSAndroid Build Coastguard Worker     }
452*c8dee2aaSAndroid Build Coastguard Worker 
453*c8dee2aaSAndroid Build Coastguard Worker     AutoSTArray<16, SkColor>  grColors(grInfo.fColorCount);
454*c8dee2aaSAndroid Build Coastguard Worker     AutoSTArray<16, SkScalar> grOffsets(grInfo.fColorCount);
455*c8dee2aaSAndroid Build Coastguard Worker     grInfo.fColors = grColors.get();
456*c8dee2aaSAndroid Build Coastguard Worker     grInfo.fColorOffsets = grOffsets.get();
457*c8dee2aaSAndroid Build Coastguard Worker 
458*c8dee2aaSAndroid Build Coastguard Worker     // One more call to get the actual colors/offsets and local matrix.
459*c8dee2aaSAndroid Build Coastguard Worker     SkMatrix localMatrix;
460*c8dee2aaSAndroid Build Coastguard Worker     as_SB(shader)->asGradient(&grInfo, &localMatrix);
461*c8dee2aaSAndroid Build Coastguard Worker     SkASSERT(grInfo.fColorCount <= grColors.count());
462*c8dee2aaSAndroid Build Coastguard Worker     SkASSERT(grInfo.fColorCount <= grOffsets.count());
463*c8dee2aaSAndroid Build Coastguard Worker 
464*c8dee2aaSAndroid Build Coastguard Worker     SkASSERT(grColors.size() > 0);
465*c8dee2aaSAndroid Build Coastguard Worker     resources->fPaintServer =
466*c8dee2aaSAndroid Build Coastguard Worker             SkStringPrintf("url(#%s)", addLinearGradientDef(grInfo, shader, localMatrix).c_str());
467*c8dee2aaSAndroid Build Coastguard Worker }
468*c8dee2aaSAndroid Build Coastguard Worker 
addColorFilterResources(const SkColorFilter & cf,Resources * resources)469*c8dee2aaSAndroid Build Coastguard Worker void SkSVGDevice::AutoElement::addColorFilterResources(const SkColorFilter& cf,
470*c8dee2aaSAndroid Build Coastguard Worker                                                        Resources* resources) {
471*c8dee2aaSAndroid Build Coastguard Worker     SkString colorfilterID = fResourceBucket->addColorFilter();
472*c8dee2aaSAndroid Build Coastguard Worker     {
473*c8dee2aaSAndroid Build Coastguard Worker         AutoElement filterElement("filter", fWriter);
474*c8dee2aaSAndroid Build Coastguard Worker         filterElement.addAttribute("id", colorfilterID);
475*c8dee2aaSAndroid Build Coastguard Worker         filterElement.addAttribute("x", "0%");
476*c8dee2aaSAndroid Build Coastguard Worker         filterElement.addAttribute("y", "0%");
477*c8dee2aaSAndroid Build Coastguard Worker         filterElement.addAttribute("width", "100%");
478*c8dee2aaSAndroid Build Coastguard Worker         filterElement.addAttribute("height", "100%");
479*c8dee2aaSAndroid Build Coastguard Worker 
480*c8dee2aaSAndroid Build Coastguard Worker         SkColor filterColor;
481*c8dee2aaSAndroid Build Coastguard Worker         SkBlendMode mode;
482*c8dee2aaSAndroid Build Coastguard Worker         bool asAColorMode = cf.asAColorMode(&filterColor, &mode);
483*c8dee2aaSAndroid Build Coastguard Worker         SkAssertResult(asAColorMode);
484*c8dee2aaSAndroid Build Coastguard Worker         SkASSERT(mode == SkBlendMode::kSrcIn);
485*c8dee2aaSAndroid Build Coastguard Worker 
486*c8dee2aaSAndroid Build Coastguard Worker         {
487*c8dee2aaSAndroid Build Coastguard Worker             // first flood with filter color
488*c8dee2aaSAndroid Build Coastguard Worker             AutoElement floodElement("feFlood", fWriter);
489*c8dee2aaSAndroid Build Coastguard Worker             floodElement.addAttribute("flood-color", svg_color(filterColor));
490*c8dee2aaSAndroid Build Coastguard Worker             floodElement.addAttribute("flood-opacity", svg_opacity(filterColor));
491*c8dee2aaSAndroid Build Coastguard Worker             floodElement.addAttribute("result", "flood");
492*c8dee2aaSAndroid Build Coastguard Worker         }
493*c8dee2aaSAndroid Build Coastguard Worker 
494*c8dee2aaSAndroid Build Coastguard Worker         {
495*c8dee2aaSAndroid Build Coastguard Worker             // apply the transform to filter color
496*c8dee2aaSAndroid Build Coastguard Worker             AutoElement compositeElement("feComposite", fWriter);
497*c8dee2aaSAndroid Build Coastguard Worker             compositeElement.addAttribute("in", "flood");
498*c8dee2aaSAndroid Build Coastguard Worker             compositeElement.addAttribute("operator", "in");
499*c8dee2aaSAndroid Build Coastguard Worker         }
500*c8dee2aaSAndroid Build Coastguard Worker     }
501*c8dee2aaSAndroid Build Coastguard Worker     resources->fColorFilter.printf("url(#%s)", colorfilterID.c_str());
502*c8dee2aaSAndroid Build Coastguard Worker }
503*c8dee2aaSAndroid Build Coastguard Worker 
is_png(const void * bytes,size_t length)504*c8dee2aaSAndroid Build Coastguard Worker static bool is_png(const void* bytes, size_t length) {
505*c8dee2aaSAndroid Build Coastguard Worker     static constexpr uint8_t pngSig[] = {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A};
506*c8dee2aaSAndroid Build Coastguard Worker     return length >= sizeof(pngSig) && !memcmp(bytes, pngSig, sizeof(pngSig));
507*c8dee2aaSAndroid Build Coastguard Worker }
508*c8dee2aaSAndroid Build Coastguard Worker 
is_jpeg(const void * bytes,size_t length)509*c8dee2aaSAndroid Build Coastguard Worker static bool is_jpeg(const void* bytes, size_t length) {
510*c8dee2aaSAndroid Build Coastguard Worker     static constexpr uint8_t jpegSig[] = {0xFF, 0xD8, 0xFF};
511*c8dee2aaSAndroid Build Coastguard Worker     return length >= sizeof(jpegSig) && !memcmp(bytes, jpegSig, sizeof(jpegSig));
512*c8dee2aaSAndroid Build Coastguard Worker }
513*c8dee2aaSAndroid Build Coastguard Worker 
514*c8dee2aaSAndroid Build Coastguard Worker // Returns data uri from bytes.
515*c8dee2aaSAndroid Build Coastguard Worker // it will use any cached data if available, otherwise will
516*c8dee2aaSAndroid Build Coastguard Worker // encode as png.
AsDataUri(SkImage * image)517*c8dee2aaSAndroid Build Coastguard Worker sk_sp<SkData> AsDataUri(SkImage* image) {
518*c8dee2aaSAndroid Build Coastguard Worker     static constexpr char jpgDataPrefix[] = "data:image/jpeg;base64,";
519*c8dee2aaSAndroid Build Coastguard Worker     static constexpr char pngDataPrefix[] = "data:image/png;base64,";
520*c8dee2aaSAndroid Build Coastguard Worker 
521*c8dee2aaSAndroid Build Coastguard Worker     SkASSERT(!image->isTextureBacked());
522*c8dee2aaSAndroid Build Coastguard Worker 
523*c8dee2aaSAndroid Build Coastguard Worker     const char* selectedPrefix = pngDataPrefix;
524*c8dee2aaSAndroid Build Coastguard Worker     size_t selectedPrefixLength = sizeof(pngDataPrefix);
525*c8dee2aaSAndroid Build Coastguard Worker 
526*c8dee2aaSAndroid Build Coastguard Worker     sk_sp<SkData> imageData = image->refEncodedData();
527*c8dee2aaSAndroid Build Coastguard Worker     if (imageData) {  // Already encoded as something
528*c8dee2aaSAndroid Build Coastguard Worker         if (is_jpeg(imageData->data(), imageData->size())) {
529*c8dee2aaSAndroid Build Coastguard Worker             selectedPrefix = jpgDataPrefix;
530*c8dee2aaSAndroid Build Coastguard Worker             selectedPrefixLength = sizeof(jpgDataPrefix);
531*c8dee2aaSAndroid Build Coastguard Worker         } else if (!is_png(imageData->data(), imageData->size())) {
532*c8dee2aaSAndroid Build Coastguard Worker             // re-encode the image as a PNG.
533*c8dee2aaSAndroid Build Coastguard Worker             // GrDirectContext is nullptr because we shouldn't have any texture-based images
534*c8dee2aaSAndroid Build Coastguard Worker             // passed in.
535*c8dee2aaSAndroid Build Coastguard Worker             imageData = SkPngEncoder::Encode(nullptr, image, {});
536*c8dee2aaSAndroid Build Coastguard Worker             if (!imageData) {
537*c8dee2aaSAndroid Build Coastguard Worker                 return nullptr;
538*c8dee2aaSAndroid Build Coastguard Worker             }
539*c8dee2aaSAndroid Build Coastguard Worker         }
540*c8dee2aaSAndroid Build Coastguard Worker         // else, it's already encoded as a PNG - we don't need to do anything.
541*c8dee2aaSAndroid Build Coastguard Worker     } else {
542*c8dee2aaSAndroid Build Coastguard Worker         // It was not encoded as something, so we need to encode it as a PNG.
543*c8dee2aaSAndroid Build Coastguard Worker         imageData = SkPngEncoder::Encode(nullptr, image, {});
544*c8dee2aaSAndroid Build Coastguard Worker         if (!imageData) {
545*c8dee2aaSAndroid Build Coastguard Worker             return nullptr;
546*c8dee2aaSAndroid Build Coastguard Worker         }
547*c8dee2aaSAndroid Build Coastguard Worker     }
548*c8dee2aaSAndroid Build Coastguard Worker 
549*c8dee2aaSAndroid Build Coastguard Worker     size_t b64Size = SkBase64::EncodedSize(imageData->size());
550*c8dee2aaSAndroid Build Coastguard Worker     sk_sp<SkData> dataUri = SkData::MakeUninitialized(selectedPrefixLength + b64Size);
551*c8dee2aaSAndroid Build Coastguard Worker     char* dest = (char*)dataUri->writable_data();
552*c8dee2aaSAndroid Build Coastguard Worker     memcpy(dest, selectedPrefix, selectedPrefixLength);
553*c8dee2aaSAndroid Build Coastguard Worker     SkBase64::Encode(imageData->data(), imageData->size(), dest + selectedPrefixLength - 1);
554*c8dee2aaSAndroid Build Coastguard Worker     dest[dataUri->size() - 1] = 0;
555*c8dee2aaSAndroid Build Coastguard Worker     return dataUri;
556*c8dee2aaSAndroid Build Coastguard Worker }
557*c8dee2aaSAndroid Build Coastguard Worker 
addImageShaderResources(const SkShader * shader,const SkPaint & paint,Resources * resources)558*c8dee2aaSAndroid Build Coastguard Worker void SkSVGDevice::AutoElement::addImageShaderResources(const SkShader* shader, const SkPaint& paint,
559*c8dee2aaSAndroid Build Coastguard Worker                                                        Resources* resources) {
560*c8dee2aaSAndroid Build Coastguard Worker     SkMatrix outMatrix;
561*c8dee2aaSAndroid Build Coastguard Worker 
562*c8dee2aaSAndroid Build Coastguard Worker     SkTileMode xy[2];
563*c8dee2aaSAndroid Build Coastguard Worker     SkImage* image = shader->isAImage(&outMatrix, xy);
564*c8dee2aaSAndroid Build Coastguard Worker     SkASSERT(image);
565*c8dee2aaSAndroid Build Coastguard Worker 
566*c8dee2aaSAndroid Build Coastguard Worker     SkString patternDims[2];  // width, height
567*c8dee2aaSAndroid Build Coastguard Worker 
568*c8dee2aaSAndroid Build Coastguard Worker     sk_sp<SkData> dataUri = AsDataUri(image);
569*c8dee2aaSAndroid Build Coastguard Worker     if (!dataUri) {
570*c8dee2aaSAndroid Build Coastguard Worker         return;
571*c8dee2aaSAndroid Build Coastguard Worker     }
572*c8dee2aaSAndroid Build Coastguard Worker     SkIRect imageSize = image->bounds();
573*c8dee2aaSAndroid Build Coastguard Worker     for (int i = 0; i < 2; i++) {
574*c8dee2aaSAndroid Build Coastguard Worker         int imageDimension = i == 0 ? imageSize.width() : imageSize.height();
575*c8dee2aaSAndroid Build Coastguard Worker         switch (xy[i]) {
576*c8dee2aaSAndroid Build Coastguard Worker             case SkTileMode::kRepeat:
577*c8dee2aaSAndroid Build Coastguard Worker                 patternDims[i].appendScalar(imageDimension);
578*c8dee2aaSAndroid Build Coastguard Worker             break;
579*c8dee2aaSAndroid Build Coastguard Worker             default:
580*c8dee2aaSAndroid Build Coastguard Worker                 // TODO: other tile modes?
581*c8dee2aaSAndroid Build Coastguard Worker                 patternDims[i] = "100%";
582*c8dee2aaSAndroid Build Coastguard Worker         }
583*c8dee2aaSAndroid Build Coastguard Worker     }
584*c8dee2aaSAndroid Build Coastguard Worker 
585*c8dee2aaSAndroid Build Coastguard Worker     SkString patternID = fResourceBucket->addPattern();
586*c8dee2aaSAndroid Build Coastguard Worker     {
587*c8dee2aaSAndroid Build Coastguard Worker         AutoElement pattern("pattern", fWriter);
588*c8dee2aaSAndroid Build Coastguard Worker         pattern.addAttribute("id", patternID);
589*c8dee2aaSAndroid Build Coastguard Worker         pattern.addAttribute("patternUnits", "userSpaceOnUse");
590*c8dee2aaSAndroid Build Coastguard Worker         pattern.addAttribute("patternContentUnits", "userSpaceOnUse");
591*c8dee2aaSAndroid Build Coastguard Worker         pattern.addAttribute("width", patternDims[0]);
592*c8dee2aaSAndroid Build Coastguard Worker         pattern.addAttribute("height", patternDims[1]);
593*c8dee2aaSAndroid Build Coastguard Worker         pattern.addAttribute("x", 0);
594*c8dee2aaSAndroid Build Coastguard Worker         pattern.addAttribute("y", 0);
595*c8dee2aaSAndroid Build Coastguard Worker 
596*c8dee2aaSAndroid Build Coastguard Worker         {
597*c8dee2aaSAndroid Build Coastguard Worker             SkString imageID = fResourceBucket->addImage();
598*c8dee2aaSAndroid Build Coastguard Worker             AutoElement imageTag("image", fWriter);
599*c8dee2aaSAndroid Build Coastguard Worker             imageTag.addAttribute("id", imageID);
600*c8dee2aaSAndroid Build Coastguard Worker             imageTag.addAttribute("x", 0);
601*c8dee2aaSAndroid Build Coastguard Worker             imageTag.addAttribute("y", 0);
602*c8dee2aaSAndroid Build Coastguard Worker             imageTag.addAttribute("width", image->width());
603*c8dee2aaSAndroid Build Coastguard Worker             imageTag.addAttribute("height", image->height());
604*c8dee2aaSAndroid Build Coastguard Worker             imageTag.addAttribute("xlink:href", static_cast<const char*>(dataUri->data()));
605*c8dee2aaSAndroid Build Coastguard Worker         }
606*c8dee2aaSAndroid Build Coastguard Worker     }
607*c8dee2aaSAndroid Build Coastguard Worker     resources->fPaintServer.printf("url(#%s)", patternID.c_str());
608*c8dee2aaSAndroid Build Coastguard Worker }
609*c8dee2aaSAndroid Build Coastguard Worker 
addShaderResources(const SkPaint & paint,Resources * resources)610*c8dee2aaSAndroid Build Coastguard Worker void SkSVGDevice::AutoElement::addShaderResources(const SkPaint& paint, Resources* resources) {
611*c8dee2aaSAndroid Build Coastguard Worker     const SkShader* shader = paint.getShader();
612*c8dee2aaSAndroid Build Coastguard Worker     SkASSERT(shader);
613*c8dee2aaSAndroid Build Coastguard Worker 
614*c8dee2aaSAndroid Build Coastguard Worker     auto shaderType = as_SB(shader)->type();
615*c8dee2aaSAndroid Build Coastguard Worker     if (shaderType == SkShaderBase::ShaderType::kColor ||
616*c8dee2aaSAndroid Build Coastguard Worker         shaderType == SkShaderBase::ShaderType::kGradientBase) {
617*c8dee2aaSAndroid Build Coastguard Worker         this->addGradientShaderResources(shader, paint, resources);
618*c8dee2aaSAndroid Build Coastguard Worker     } else if (shader->isAImage()) {
619*c8dee2aaSAndroid Build Coastguard Worker         this->addImageShaderResources(shader, paint, resources);
620*c8dee2aaSAndroid Build Coastguard Worker     }
621*c8dee2aaSAndroid Build Coastguard Worker     // TODO: other shader types?
622*c8dee2aaSAndroid Build Coastguard Worker }
623*c8dee2aaSAndroid Build Coastguard Worker 
addLinearGradientDef(const SkShaderBase::GradientInfo & info,const SkShader * shader,const SkMatrix & localMatrix)624*c8dee2aaSAndroid Build Coastguard Worker SkString SkSVGDevice::AutoElement::addLinearGradientDef(const SkShaderBase::GradientInfo& info,
625*c8dee2aaSAndroid Build Coastguard Worker                                                         const SkShader* shader,
626*c8dee2aaSAndroid Build Coastguard Worker                                                         const SkMatrix& localMatrix) {
627*c8dee2aaSAndroid Build Coastguard Worker     SkASSERT(fResourceBucket);
628*c8dee2aaSAndroid Build Coastguard Worker     SkString id = fResourceBucket->addLinearGradient();
629*c8dee2aaSAndroid Build Coastguard Worker 
630*c8dee2aaSAndroid Build Coastguard Worker     {
631*c8dee2aaSAndroid Build Coastguard Worker         AutoElement gradient("linearGradient", fWriter);
632*c8dee2aaSAndroid Build Coastguard Worker 
633*c8dee2aaSAndroid Build Coastguard Worker         gradient.addAttribute("id", id);
634*c8dee2aaSAndroid Build Coastguard Worker         gradient.addAttribute("gradientUnits", "userSpaceOnUse");
635*c8dee2aaSAndroid Build Coastguard Worker         gradient.addAttribute("x1", info.fPoint[0].x());
636*c8dee2aaSAndroid Build Coastguard Worker         gradient.addAttribute("y1", info.fPoint[0].y());
637*c8dee2aaSAndroid Build Coastguard Worker         gradient.addAttribute("x2", info.fPoint[1].x());
638*c8dee2aaSAndroid Build Coastguard Worker         gradient.addAttribute("y2", info.fPoint[1].y());
639*c8dee2aaSAndroid Build Coastguard Worker 
640*c8dee2aaSAndroid Build Coastguard Worker         if (!localMatrix.isIdentity()) {
641*c8dee2aaSAndroid Build Coastguard Worker             this->addAttribute("gradientTransform", svg_transform(localMatrix));
642*c8dee2aaSAndroid Build Coastguard Worker         }
643*c8dee2aaSAndroid Build Coastguard Worker 
644*c8dee2aaSAndroid Build Coastguard Worker         SkASSERT(info.fColorCount >= 2);
645*c8dee2aaSAndroid Build Coastguard Worker         for (int i = 0; i < info.fColorCount; ++i) {
646*c8dee2aaSAndroid Build Coastguard Worker             SkColor color = info.fColors[i];
647*c8dee2aaSAndroid Build Coastguard Worker             SkString colorStr(svg_color(color));
648*c8dee2aaSAndroid Build Coastguard Worker 
649*c8dee2aaSAndroid Build Coastguard Worker             {
650*c8dee2aaSAndroid Build Coastguard Worker                 AutoElement stop("stop", fWriter);
651*c8dee2aaSAndroid Build Coastguard Worker                 stop.addAttribute("offset", info.fColorOffsets[i]);
652*c8dee2aaSAndroid Build Coastguard Worker                 stop.addAttribute("stop-color", colorStr.c_str());
653*c8dee2aaSAndroid Build Coastguard Worker 
654*c8dee2aaSAndroid Build Coastguard Worker                 if (SK_AlphaOPAQUE != SkColorGetA(color)) {
655*c8dee2aaSAndroid Build Coastguard Worker                     stop.addAttribute("stop-opacity", svg_opacity(color));
656*c8dee2aaSAndroid Build Coastguard Worker                 }
657*c8dee2aaSAndroid Build Coastguard Worker             }
658*c8dee2aaSAndroid Build Coastguard Worker         }
659*c8dee2aaSAndroid Build Coastguard Worker     }
660*c8dee2aaSAndroid Build Coastguard Worker 
661*c8dee2aaSAndroid Build Coastguard Worker     return id;
662*c8dee2aaSAndroid Build Coastguard Worker }
663*c8dee2aaSAndroid Build Coastguard Worker 
addRectAttributes(const SkRect & rect)664*c8dee2aaSAndroid Build Coastguard Worker void SkSVGDevice::AutoElement::addRectAttributes(const SkRect& rect) {
665*c8dee2aaSAndroid Build Coastguard Worker     // x, y default to 0
666*c8dee2aaSAndroid Build Coastguard Worker     if (rect.x() != 0) {
667*c8dee2aaSAndroid Build Coastguard Worker         this->addAttribute("x", rect.x());
668*c8dee2aaSAndroid Build Coastguard Worker     }
669*c8dee2aaSAndroid Build Coastguard Worker     if (rect.y() != 0) {
670*c8dee2aaSAndroid Build Coastguard Worker         this->addAttribute("y", rect.y());
671*c8dee2aaSAndroid Build Coastguard Worker     }
672*c8dee2aaSAndroid Build Coastguard Worker 
673*c8dee2aaSAndroid Build Coastguard Worker     this->addAttribute("width", rect.width());
674*c8dee2aaSAndroid Build Coastguard Worker     this->addAttribute("height", rect.height());
675*c8dee2aaSAndroid Build Coastguard Worker }
676*c8dee2aaSAndroid Build Coastguard Worker 
addPathAttributes(const SkPath & path,SkParsePath::PathEncoding encoding)677*c8dee2aaSAndroid Build Coastguard Worker void SkSVGDevice::AutoElement::addPathAttributes(const SkPath& path,
678*c8dee2aaSAndroid Build Coastguard Worker                                                  SkParsePath::PathEncoding encoding) {
679*c8dee2aaSAndroid Build Coastguard Worker     this->addAttribute("d", SkParsePath::ToSVGString(path, encoding));
680*c8dee2aaSAndroid Build Coastguard Worker }
681*c8dee2aaSAndroid Build Coastguard Worker 
addTextAttributes(const SkFont & font)682*c8dee2aaSAndroid Build Coastguard Worker void SkSVGDevice::AutoElement::addTextAttributes(const SkFont& font) {
683*c8dee2aaSAndroid Build Coastguard Worker     this->addAttribute("font-size", font.getSize());
684*c8dee2aaSAndroid Build Coastguard Worker 
685*c8dee2aaSAndroid Build Coastguard Worker     SkString familyName;
686*c8dee2aaSAndroid Build Coastguard Worker     THashSet<SkString> familySet;
687*c8dee2aaSAndroid Build Coastguard Worker     sk_sp<SkTypeface> tface = font.refTypeface();
688*c8dee2aaSAndroid Build Coastguard Worker 
689*c8dee2aaSAndroid Build Coastguard Worker     SkASSERT(tface);
690*c8dee2aaSAndroid Build Coastguard Worker     SkFontStyle style = tface->fontStyle();
691*c8dee2aaSAndroid Build Coastguard Worker     if (style.slant() == SkFontStyle::kItalic_Slant) {
692*c8dee2aaSAndroid Build Coastguard Worker         this->addAttribute("font-style", "italic");
693*c8dee2aaSAndroid Build Coastguard Worker     } else if (style.slant() == SkFontStyle::kOblique_Slant) {
694*c8dee2aaSAndroid Build Coastguard Worker         this->addAttribute("font-style", "oblique");
695*c8dee2aaSAndroid Build Coastguard Worker     }
696*c8dee2aaSAndroid Build Coastguard Worker     int weightIndex = (SkTPin(style.weight(), 100, 900) - 50) / 100;
697*c8dee2aaSAndroid Build Coastguard Worker     if (weightIndex != 3) {
698*c8dee2aaSAndroid Build Coastguard Worker         static constexpr const char* weights[] = {
699*c8dee2aaSAndroid Build Coastguard Worker             "100", "200", "300", "normal", "400", "500", "600", "bold", "800", "900"
700*c8dee2aaSAndroid Build Coastguard Worker         };
701*c8dee2aaSAndroid Build Coastguard Worker         this->addAttribute("font-weight", weights[weightIndex]);
702*c8dee2aaSAndroid Build Coastguard Worker     }
703*c8dee2aaSAndroid Build Coastguard Worker     int stretchIndex = style.width() - 1;
704*c8dee2aaSAndroid Build Coastguard Worker     if (stretchIndex != 4) {
705*c8dee2aaSAndroid Build Coastguard Worker         static constexpr const char* stretches[] = {
706*c8dee2aaSAndroid Build Coastguard Worker             "ultra-condensed", "extra-condensed", "condensed", "semi-condensed",
707*c8dee2aaSAndroid Build Coastguard Worker             "normal",
708*c8dee2aaSAndroid Build Coastguard Worker             "semi-expanded", "expanded", "extra-expanded", "ultra-expanded"
709*c8dee2aaSAndroid Build Coastguard Worker         };
710*c8dee2aaSAndroid Build Coastguard Worker         this->addAttribute("font-stretch", stretches[stretchIndex]);
711*c8dee2aaSAndroid Build Coastguard Worker     }
712*c8dee2aaSAndroid Build Coastguard Worker 
713*c8dee2aaSAndroid Build Coastguard Worker     sk_sp<SkTypeface::LocalizedStrings> familyNameIter(tface->createFamilyNameIterator());
714*c8dee2aaSAndroid Build Coastguard Worker     SkTypeface::LocalizedString familyString;
715*c8dee2aaSAndroid Build Coastguard Worker     if (familyNameIter) {
716*c8dee2aaSAndroid Build Coastguard Worker         while (familyNameIter->next(&familyString)) {
717*c8dee2aaSAndroid Build Coastguard Worker             if (familySet.contains(familyString.fString)) {
718*c8dee2aaSAndroid Build Coastguard Worker                 continue;
719*c8dee2aaSAndroid Build Coastguard Worker             }
720*c8dee2aaSAndroid Build Coastguard Worker             familySet.add(familyString.fString);
721*c8dee2aaSAndroid Build Coastguard Worker             familyName.appendf((familyName.isEmpty() ? "%s" : ", %s"), familyString.fString.c_str());
722*c8dee2aaSAndroid Build Coastguard Worker         }
723*c8dee2aaSAndroid Build Coastguard Worker     }
724*c8dee2aaSAndroid Build Coastguard Worker     if (!familyName.isEmpty()) {
725*c8dee2aaSAndroid Build Coastguard Worker         this->addAttribute("font-family", familyName);
726*c8dee2aaSAndroid Build Coastguard Worker     }
727*c8dee2aaSAndroid Build Coastguard Worker }
728*c8dee2aaSAndroid Build Coastguard Worker 
Make(const SkISize & size,std::unique_ptr<SkXMLWriter> writer,uint32_t flags)729*c8dee2aaSAndroid Build Coastguard Worker sk_sp<SkDevice> SkSVGDevice::Make(const SkISize& size,
730*c8dee2aaSAndroid Build Coastguard Worker                                   std::unique_ptr<SkXMLWriter> writer,
731*c8dee2aaSAndroid Build Coastguard Worker                                   uint32_t flags) {
732*c8dee2aaSAndroid Build Coastguard Worker     return writer ? sk_sp<SkDevice>(new SkSVGDevice(size, std::move(writer), flags))
733*c8dee2aaSAndroid Build Coastguard Worker                   : nullptr;
734*c8dee2aaSAndroid Build Coastguard Worker }
735*c8dee2aaSAndroid Build Coastguard Worker 
SkSVGDevice(const SkISize & size,std::unique_ptr<SkXMLWriter> writer,uint32_t flags)736*c8dee2aaSAndroid Build Coastguard Worker SkSVGDevice::SkSVGDevice(const SkISize& size, std::unique_ptr<SkXMLWriter> writer, uint32_t flags)
737*c8dee2aaSAndroid Build Coastguard Worker         : SkClipStackDevice(
738*c8dee2aaSAndroid Build Coastguard Worker             SkImageInfo::MakeUnknown(size.fWidth, size.fHeight),
739*c8dee2aaSAndroid Build Coastguard Worker             SkSurfaceProps())
740*c8dee2aaSAndroid Build Coastguard Worker         , fWriter(std::move(writer))
741*c8dee2aaSAndroid Build Coastguard Worker         , fResourceBucket(new ResourceBucket)
742*c8dee2aaSAndroid Build Coastguard Worker         , fFlags(flags)
743*c8dee2aaSAndroid Build Coastguard Worker {
744*c8dee2aaSAndroid Build Coastguard Worker     SkASSERT(fWriter);
745*c8dee2aaSAndroid Build Coastguard Worker 
746*c8dee2aaSAndroid Build Coastguard Worker     fWriter->writeHeader();
747*c8dee2aaSAndroid Build Coastguard Worker 
748*c8dee2aaSAndroid Build Coastguard Worker     // The root <svg> tag gets closed by the destructor.
749*c8dee2aaSAndroid Build Coastguard Worker     fRootElement = std::make_unique<AutoElement>("svg", fWriter);
750*c8dee2aaSAndroid Build Coastguard Worker 
751*c8dee2aaSAndroid Build Coastguard Worker     fRootElement->addAttribute("xmlns", "http://www.w3.org/2000/svg");
752*c8dee2aaSAndroid Build Coastguard Worker     fRootElement->addAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
753*c8dee2aaSAndroid Build Coastguard Worker     fRootElement->addAttribute("width", size.width());
754*c8dee2aaSAndroid Build Coastguard Worker     fRootElement->addAttribute("height", size.height());
755*c8dee2aaSAndroid Build Coastguard Worker }
756*c8dee2aaSAndroid Build Coastguard Worker 
~SkSVGDevice()757*c8dee2aaSAndroid Build Coastguard Worker SkSVGDevice::~SkSVGDevice() {
758*c8dee2aaSAndroid Build Coastguard Worker     // Pop order is important.
759*c8dee2aaSAndroid Build Coastguard Worker     while (!fClipStack.empty()) {
760*c8dee2aaSAndroid Build Coastguard Worker         fClipStack.pop_back();
761*c8dee2aaSAndroid Build Coastguard Worker     }
762*c8dee2aaSAndroid Build Coastguard Worker }
763*c8dee2aaSAndroid Build Coastguard Worker 
pathEncoding() const764*c8dee2aaSAndroid Build Coastguard Worker SkParsePath::PathEncoding SkSVGDevice::pathEncoding() const {
765*c8dee2aaSAndroid Build Coastguard Worker     return (fFlags & SkSVGCanvas::kRelativePathEncoding_Flag)
766*c8dee2aaSAndroid Build Coastguard Worker         ? SkParsePath::PathEncoding::Relative
767*c8dee2aaSAndroid Build Coastguard Worker         : SkParsePath::PathEncoding::Absolute;
768*c8dee2aaSAndroid Build Coastguard Worker }
769*c8dee2aaSAndroid Build Coastguard Worker 
syncClipStack(const SkClipStack & cs)770*c8dee2aaSAndroid Build Coastguard Worker void SkSVGDevice::syncClipStack(const SkClipStack& cs) {
771*c8dee2aaSAndroid Build Coastguard Worker     SkClipStack::B2TIter iter(cs);
772*c8dee2aaSAndroid Build Coastguard Worker 
773*c8dee2aaSAndroid Build Coastguard Worker     const SkClipStack::Element* elem;
774*c8dee2aaSAndroid Build Coastguard Worker     int rec_idx = 0;
775*c8dee2aaSAndroid Build Coastguard Worker 
776*c8dee2aaSAndroid Build Coastguard Worker     // First, find/preserve the common bottom.
777*c8dee2aaSAndroid Build Coastguard Worker     while ((elem = iter.next()) && (rec_idx < fClipStack.size())) {
778*c8dee2aaSAndroid Build Coastguard Worker         if (fClipStack[SkToInt(rec_idx)].fGenID != elem->getGenID()) {
779*c8dee2aaSAndroid Build Coastguard Worker             break;
780*c8dee2aaSAndroid Build Coastguard Worker         }
781*c8dee2aaSAndroid Build Coastguard Worker         rec_idx++;
782*c8dee2aaSAndroid Build Coastguard Worker     }
783*c8dee2aaSAndroid Build Coastguard Worker 
784*c8dee2aaSAndroid Build Coastguard Worker     // Discard out-of-date stack top.
785*c8dee2aaSAndroid Build Coastguard Worker     while (fClipStack.size() > rec_idx) {
786*c8dee2aaSAndroid Build Coastguard Worker         fClipStack.pop_back();
787*c8dee2aaSAndroid Build Coastguard Worker     }
788*c8dee2aaSAndroid Build Coastguard Worker 
789*c8dee2aaSAndroid Build Coastguard Worker     auto define_clip = [this](const SkClipStack::Element* e) {
790*c8dee2aaSAndroid Build Coastguard Worker         const auto cid = SkStringPrintf("cl_%x", e->getGenID());
791*c8dee2aaSAndroid Build Coastguard Worker 
792*c8dee2aaSAndroid Build Coastguard Worker         AutoElement clip_path("clipPath", fWriter);
793*c8dee2aaSAndroid Build Coastguard Worker         clip_path.addAttribute("id", cid);
794*c8dee2aaSAndroid Build Coastguard Worker 
795*c8dee2aaSAndroid Build Coastguard Worker         // TODO: handle non-intersect clips.
796*c8dee2aaSAndroid Build Coastguard Worker 
797*c8dee2aaSAndroid Build Coastguard Worker         switch (e->getDeviceSpaceType()) {
798*c8dee2aaSAndroid Build Coastguard Worker         case SkClipStack::Element::DeviceSpaceType::kEmpty: {
799*c8dee2aaSAndroid Build Coastguard Worker             // TODO: can we skip this?
800*c8dee2aaSAndroid Build Coastguard Worker             AutoElement rect("rect", fWriter);
801*c8dee2aaSAndroid Build Coastguard Worker         } break;
802*c8dee2aaSAndroid Build Coastguard Worker         case SkClipStack::Element::DeviceSpaceType::kRect: {
803*c8dee2aaSAndroid Build Coastguard Worker             AutoElement rect("rect", fWriter);
804*c8dee2aaSAndroid Build Coastguard Worker             rect.addRectAttributes(e->getDeviceSpaceRect());
805*c8dee2aaSAndroid Build Coastguard Worker         } break;
806*c8dee2aaSAndroid Build Coastguard Worker         case SkClipStack::Element::DeviceSpaceType::kRRect: {
807*c8dee2aaSAndroid Build Coastguard Worker             // TODO: complex rrect handling?
808*c8dee2aaSAndroid Build Coastguard Worker             const auto& rr   = e->getDeviceSpaceRRect();
809*c8dee2aaSAndroid Build Coastguard Worker             const auto radii = rr.getSimpleRadii();
810*c8dee2aaSAndroid Build Coastguard Worker 
811*c8dee2aaSAndroid Build Coastguard Worker             AutoElement rrect("rect", fWriter);
812*c8dee2aaSAndroid Build Coastguard Worker             rrect.addRectAttributes(rr.rect());
813*c8dee2aaSAndroid Build Coastguard Worker             rrect.addAttribute("rx", radii.x());
814*c8dee2aaSAndroid Build Coastguard Worker             rrect.addAttribute("ry", radii.y());
815*c8dee2aaSAndroid Build Coastguard Worker         } break;
816*c8dee2aaSAndroid Build Coastguard Worker         case SkClipStack::Element::DeviceSpaceType::kPath: {
817*c8dee2aaSAndroid Build Coastguard Worker             const auto& p = e->getDeviceSpacePath();
818*c8dee2aaSAndroid Build Coastguard Worker             AutoElement path("path", fWriter);
819*c8dee2aaSAndroid Build Coastguard Worker             path.addPathAttributes(p, this->pathEncoding());
820*c8dee2aaSAndroid Build Coastguard Worker             if (p.getFillType() == SkPathFillType::kEvenOdd) {
821*c8dee2aaSAndroid Build Coastguard Worker                 path.addAttribute("clip-rule", "evenodd");
822*c8dee2aaSAndroid Build Coastguard Worker             }
823*c8dee2aaSAndroid Build Coastguard Worker         } break;
824*c8dee2aaSAndroid Build Coastguard Worker         case SkClipStack::Element::DeviceSpaceType::kShader:
825*c8dee2aaSAndroid Build Coastguard Worker             // TODO: handle shader clipping, perhaps rasterize and apply as a mask image?
826*c8dee2aaSAndroid Build Coastguard Worker             break;
827*c8dee2aaSAndroid Build Coastguard Worker         }
828*c8dee2aaSAndroid Build Coastguard Worker 
829*c8dee2aaSAndroid Build Coastguard Worker         return cid;
830*c8dee2aaSAndroid Build Coastguard Worker     };
831*c8dee2aaSAndroid Build Coastguard Worker 
832*c8dee2aaSAndroid Build Coastguard Worker     // Rebuild the top.
833*c8dee2aaSAndroid Build Coastguard Worker     while (elem) {
834*c8dee2aaSAndroid Build Coastguard Worker         const auto cid = define_clip(elem);
835*c8dee2aaSAndroid Build Coastguard Worker 
836*c8dee2aaSAndroid Build Coastguard Worker         auto clip_grp = std::make_unique<AutoElement>("g", fWriter);
837*c8dee2aaSAndroid Build Coastguard Worker         clip_grp->addAttribute("clip-path", SkStringPrintf("url(#%s)", cid.c_str()));
838*c8dee2aaSAndroid Build Coastguard Worker 
839*c8dee2aaSAndroid Build Coastguard Worker         fClipStack.push_back({ std::move(clip_grp), elem->getGenID() });
840*c8dee2aaSAndroid Build Coastguard Worker 
841*c8dee2aaSAndroid Build Coastguard Worker         elem = iter.next();
842*c8dee2aaSAndroid Build Coastguard Worker     }
843*c8dee2aaSAndroid Build Coastguard Worker }
844*c8dee2aaSAndroid Build Coastguard Worker 
drawPaint(const SkPaint & paint)845*c8dee2aaSAndroid Build Coastguard Worker void SkSVGDevice::drawPaint(const SkPaint& paint) {
846*c8dee2aaSAndroid Build Coastguard Worker     AutoElement rect("rect", this, fResourceBucket.get(), MxCp(this), paint);
847*c8dee2aaSAndroid Build Coastguard Worker     rect.addRectAttributes(SkRect::MakeWH(SkIntToScalar(this->width()),
848*c8dee2aaSAndroid Build Coastguard Worker                                           SkIntToScalar(this->height())));
849*c8dee2aaSAndroid Build Coastguard Worker }
850*c8dee2aaSAndroid Build Coastguard Worker 
drawAnnotation(const SkRect & rect,const char key[],SkData * value)851*c8dee2aaSAndroid Build Coastguard Worker void SkSVGDevice::drawAnnotation(const SkRect& rect, const char key[], SkData* value) {
852*c8dee2aaSAndroid Build Coastguard Worker     if (!value) {
853*c8dee2aaSAndroid Build Coastguard Worker         return;
854*c8dee2aaSAndroid Build Coastguard Worker     }
855*c8dee2aaSAndroid Build Coastguard Worker 
856*c8dee2aaSAndroid Build Coastguard Worker     if (!strcmp(SkAnnotationKeys::URL_Key(), key) ||
857*c8dee2aaSAndroid Build Coastguard Worker         !strcmp(SkAnnotationKeys::Link_Named_Dest_Key(), key)) {
858*c8dee2aaSAndroid Build Coastguard Worker         this->cs().save();
859*c8dee2aaSAndroid Build Coastguard Worker         this->cs().clipRect(rect, this->localToDevice(), SkClipOp::kIntersect, true);
860*c8dee2aaSAndroid Build Coastguard Worker         SkRect transformedRect = this->cs().bounds(this->getGlobalBounds());
861*c8dee2aaSAndroid Build Coastguard Worker         this->cs().restore();
862*c8dee2aaSAndroid Build Coastguard Worker         if (transformedRect.isEmpty()) {
863*c8dee2aaSAndroid Build Coastguard Worker             return;
864*c8dee2aaSAndroid Build Coastguard Worker         }
865*c8dee2aaSAndroid Build Coastguard Worker 
866*c8dee2aaSAndroid Build Coastguard Worker         SkString url(static_cast<const char*>(value->data()), value->size() - 1);
867*c8dee2aaSAndroid Build Coastguard Worker         AutoElement a("a", fWriter);
868*c8dee2aaSAndroid Build Coastguard Worker         a.addAttribute("xlink:href", url.c_str());
869*c8dee2aaSAndroid Build Coastguard Worker         {
870*c8dee2aaSAndroid Build Coastguard Worker             AutoElement r("rect", fWriter);
871*c8dee2aaSAndroid Build Coastguard Worker             r.addAttribute("fill-opacity", "0.0");
872*c8dee2aaSAndroid Build Coastguard Worker             r.addRectAttributes(transformedRect);
873*c8dee2aaSAndroid Build Coastguard Worker         }
874*c8dee2aaSAndroid Build Coastguard Worker     }
875*c8dee2aaSAndroid Build Coastguard Worker }
876*c8dee2aaSAndroid Build Coastguard Worker 
drawPoints(SkCanvas::PointMode mode,size_t count,const SkPoint pts[],const SkPaint & paint)877*c8dee2aaSAndroid Build Coastguard Worker void SkSVGDevice::drawPoints(SkCanvas::PointMode mode, size_t count,
878*c8dee2aaSAndroid Build Coastguard Worker                              const SkPoint pts[], const SkPaint& paint) {
879*c8dee2aaSAndroid Build Coastguard Worker     SkPathBuilder path;
880*c8dee2aaSAndroid Build Coastguard Worker 
881*c8dee2aaSAndroid Build Coastguard Worker     switch (mode) {
882*c8dee2aaSAndroid Build Coastguard Worker             // todo
883*c8dee2aaSAndroid Build Coastguard Worker         case SkCanvas::kPoints_PointMode:
884*c8dee2aaSAndroid Build Coastguard Worker             for (size_t i = 0; i < count; ++i) {
885*c8dee2aaSAndroid Build Coastguard Worker                 path.moveTo(pts[i]);
886*c8dee2aaSAndroid Build Coastguard Worker                 path.lineTo(pts[i]);
887*c8dee2aaSAndroid Build Coastguard Worker             }
888*c8dee2aaSAndroid Build Coastguard Worker             break;
889*c8dee2aaSAndroid Build Coastguard Worker         case SkCanvas::kLines_PointMode:
890*c8dee2aaSAndroid Build Coastguard Worker             count -= 1;
891*c8dee2aaSAndroid Build Coastguard Worker             for (size_t i = 0; i < count; i += 2) {
892*c8dee2aaSAndroid Build Coastguard Worker                 path.moveTo(pts[i]);
893*c8dee2aaSAndroid Build Coastguard Worker                 path.lineTo(pts[i+1]);
894*c8dee2aaSAndroid Build Coastguard Worker             }
895*c8dee2aaSAndroid Build Coastguard Worker             break;
896*c8dee2aaSAndroid Build Coastguard Worker         case SkCanvas::kPolygon_PointMode:
897*c8dee2aaSAndroid Build Coastguard Worker             if (count > 1) {
898*c8dee2aaSAndroid Build Coastguard Worker                 path.addPolygon(pts, SkToInt(count), false);
899*c8dee2aaSAndroid Build Coastguard Worker             }
900*c8dee2aaSAndroid Build Coastguard Worker             break;
901*c8dee2aaSAndroid Build Coastguard Worker     }
902*c8dee2aaSAndroid Build Coastguard Worker 
903*c8dee2aaSAndroid Build Coastguard Worker     this->drawPath(path.detach(), paint, true);
904*c8dee2aaSAndroid Build Coastguard Worker }
905*c8dee2aaSAndroid Build Coastguard Worker 
drawRect(const SkRect & r,const SkPaint & paint)906*c8dee2aaSAndroid Build Coastguard Worker void SkSVGDevice::drawRect(const SkRect& r, const SkPaint& paint) {
907*c8dee2aaSAndroid Build Coastguard Worker     if (paint.getPathEffect()) {
908*c8dee2aaSAndroid Build Coastguard Worker         this->drawPath(SkPath::Rect(r), paint, true);
909*c8dee2aaSAndroid Build Coastguard Worker         return;
910*c8dee2aaSAndroid Build Coastguard Worker     }
911*c8dee2aaSAndroid Build Coastguard Worker 
912*c8dee2aaSAndroid Build Coastguard Worker     std::unique_ptr<AutoElement> svg;
913*c8dee2aaSAndroid Build Coastguard Worker     if (RequiresViewportReset(paint)) {
914*c8dee2aaSAndroid Build Coastguard Worker       svg = std::make_unique<AutoElement>("svg", this, fResourceBucket.get(), MxCp(this), paint);
915*c8dee2aaSAndroid Build Coastguard Worker       svg->addRectAttributes(r);
916*c8dee2aaSAndroid Build Coastguard Worker     }
917*c8dee2aaSAndroid Build Coastguard Worker 
918*c8dee2aaSAndroid Build Coastguard Worker     AutoElement rect("rect", this, fResourceBucket.get(), MxCp(this), paint);
919*c8dee2aaSAndroid Build Coastguard Worker 
920*c8dee2aaSAndroid Build Coastguard Worker     if (svg) {
921*c8dee2aaSAndroid Build Coastguard Worker       rect.addAttribute("x", 0);
922*c8dee2aaSAndroid Build Coastguard Worker       rect.addAttribute("y", 0);
923*c8dee2aaSAndroid Build Coastguard Worker       rect.addAttribute("width", "100%");
924*c8dee2aaSAndroid Build Coastguard Worker       rect.addAttribute("height", "100%");
925*c8dee2aaSAndroid Build Coastguard Worker     } else {
926*c8dee2aaSAndroid Build Coastguard Worker       rect.addRectAttributes(r);
927*c8dee2aaSAndroid Build Coastguard Worker     }
928*c8dee2aaSAndroid Build Coastguard Worker }
929*c8dee2aaSAndroid Build Coastguard Worker 
drawOval(const SkRect & oval,const SkPaint & paint)930*c8dee2aaSAndroid Build Coastguard Worker void SkSVGDevice::drawOval(const SkRect& oval, const SkPaint& paint) {
931*c8dee2aaSAndroid Build Coastguard Worker     if (paint.getPathEffect()) {
932*c8dee2aaSAndroid Build Coastguard Worker         this->drawPath(SkPath::Oval(oval), paint, true);
933*c8dee2aaSAndroid Build Coastguard Worker         return;
934*c8dee2aaSAndroid Build Coastguard Worker     }
935*c8dee2aaSAndroid Build Coastguard Worker 
936*c8dee2aaSAndroid Build Coastguard Worker     AutoElement ellipse("ellipse", this, fResourceBucket.get(), MxCp(this), paint);
937*c8dee2aaSAndroid Build Coastguard Worker     ellipse.addAttribute("cx", oval.centerX());
938*c8dee2aaSAndroid Build Coastguard Worker     ellipse.addAttribute("cy", oval.centerY());
939*c8dee2aaSAndroid Build Coastguard Worker     ellipse.addAttribute("rx", oval.width() / 2);
940*c8dee2aaSAndroid Build Coastguard Worker     ellipse.addAttribute("ry", oval.height() / 2);
941*c8dee2aaSAndroid Build Coastguard Worker }
942*c8dee2aaSAndroid Build Coastguard Worker 
drawRRect(const SkRRect & rr,const SkPaint & paint)943*c8dee2aaSAndroid Build Coastguard Worker void SkSVGDevice::drawRRect(const SkRRect& rr, const SkPaint& paint) {
944*c8dee2aaSAndroid Build Coastguard Worker     if (paint.getPathEffect()) {
945*c8dee2aaSAndroid Build Coastguard Worker         this->drawPath(SkPath::RRect(rr), paint, true);
946*c8dee2aaSAndroid Build Coastguard Worker         return;
947*c8dee2aaSAndroid Build Coastguard Worker     }
948*c8dee2aaSAndroid Build Coastguard Worker 
949*c8dee2aaSAndroid Build Coastguard Worker     AutoElement elem("path", this, fResourceBucket.get(), MxCp(this), paint);
950*c8dee2aaSAndroid Build Coastguard Worker     elem.addPathAttributes(SkPath::RRect(rr), this->pathEncoding());
951*c8dee2aaSAndroid Build Coastguard Worker }
952*c8dee2aaSAndroid Build Coastguard Worker 
drawPath(const SkPath & path,const SkPaint & paint,bool pathIsMutable)953*c8dee2aaSAndroid Build Coastguard Worker void SkSVGDevice::drawPath(const SkPath& path, const SkPaint& paint, bool pathIsMutable) {
954*c8dee2aaSAndroid Build Coastguard Worker     if (path.isInverseFillType()) {
955*c8dee2aaSAndroid Build Coastguard Worker       SkDebugf("Inverse path fill type not yet implemented.");
956*c8dee2aaSAndroid Build Coastguard Worker       return;
957*c8dee2aaSAndroid Build Coastguard Worker     }
958*c8dee2aaSAndroid Build Coastguard Worker 
959*c8dee2aaSAndroid Build Coastguard Worker     SkPath pathStorage;
960*c8dee2aaSAndroid Build Coastguard Worker     SkPath* pathPtr = const_cast<SkPath*>(&path);
961*c8dee2aaSAndroid Build Coastguard Worker     SkTCopyOnFirstWrite<SkPaint> path_paint(paint);
962*c8dee2aaSAndroid Build Coastguard Worker 
963*c8dee2aaSAndroid Build Coastguard Worker     // Apply path effect from paint to path.
964*c8dee2aaSAndroid Build Coastguard Worker     if (path_paint->getPathEffect()) {
965*c8dee2aaSAndroid Build Coastguard Worker       if (!pathIsMutable) {
966*c8dee2aaSAndroid Build Coastguard Worker         pathPtr = &pathStorage;
967*c8dee2aaSAndroid Build Coastguard Worker       }
968*c8dee2aaSAndroid Build Coastguard Worker       bool fill = skpathutils::FillPathWithPaint(path, *path_paint, pathPtr);
969*c8dee2aaSAndroid Build Coastguard Worker       if (fill) {
970*c8dee2aaSAndroid Build Coastguard Worker         // Path should be filled.
971*c8dee2aaSAndroid Build Coastguard Worker         path_paint.writable()->setStyle(SkPaint::kFill_Style);
972*c8dee2aaSAndroid Build Coastguard Worker       } else {
973*c8dee2aaSAndroid Build Coastguard Worker         // Path should be drawn with a hairline (width == 0).
974*c8dee2aaSAndroid Build Coastguard Worker         path_paint.writable()->setStyle(SkPaint::kStroke_Style);
975*c8dee2aaSAndroid Build Coastguard Worker         path_paint.writable()->setStrokeWidth(0);
976*c8dee2aaSAndroid Build Coastguard Worker       }
977*c8dee2aaSAndroid Build Coastguard Worker 
978*c8dee2aaSAndroid Build Coastguard Worker       path_paint.writable()->setPathEffect(nullptr); // path effect processed
979*c8dee2aaSAndroid Build Coastguard Worker     }
980*c8dee2aaSAndroid Build Coastguard Worker 
981*c8dee2aaSAndroid Build Coastguard Worker     // Create path element.
982*c8dee2aaSAndroid Build Coastguard Worker     AutoElement elem("path", this, fResourceBucket.get(), MxCp(this), *path_paint);
983*c8dee2aaSAndroid Build Coastguard Worker     elem.addPathAttributes(*pathPtr, this->pathEncoding());
984*c8dee2aaSAndroid Build Coastguard Worker 
985*c8dee2aaSAndroid Build Coastguard Worker     // TODO: inverse fill types?
986*c8dee2aaSAndroid Build Coastguard Worker     if (pathPtr->getFillType() == SkPathFillType::kEvenOdd) {
987*c8dee2aaSAndroid Build Coastguard Worker         elem.addAttribute("fill-rule", "evenodd");
988*c8dee2aaSAndroid Build Coastguard Worker     }
989*c8dee2aaSAndroid Build Coastguard Worker }
990*c8dee2aaSAndroid Build Coastguard Worker 
encode(const SkBitmap & src)991*c8dee2aaSAndroid Build Coastguard Worker static sk_sp<SkData> encode(const SkBitmap& src) {
992*c8dee2aaSAndroid Build Coastguard Worker     SkDynamicMemoryWStream buf;
993*c8dee2aaSAndroid Build Coastguard Worker     return SkPngEncoder::Encode(&buf, src.pixmap(), {}) ? buf.detachAsData() : nullptr;
994*c8dee2aaSAndroid Build Coastguard Worker }
995*c8dee2aaSAndroid Build Coastguard Worker 
drawBitmapCommon(const MxCp & mc,const SkBitmap & bm,const SkPaint & paint)996*c8dee2aaSAndroid Build Coastguard Worker void SkSVGDevice::drawBitmapCommon(const MxCp& mc, const SkBitmap& bm, const SkPaint& paint) {
997*c8dee2aaSAndroid Build Coastguard Worker     sk_sp<SkData> pngData = encode(bm);
998*c8dee2aaSAndroid Build Coastguard Worker     if (!pngData) {
999*c8dee2aaSAndroid Build Coastguard Worker         return;
1000*c8dee2aaSAndroid Build Coastguard Worker     }
1001*c8dee2aaSAndroid Build Coastguard Worker 
1002*c8dee2aaSAndroid Build Coastguard Worker     size_t b64Size = SkBase64::EncodedSize(pngData->size());
1003*c8dee2aaSAndroid Build Coastguard Worker     AutoTMalloc<char> b64Data(b64Size);
1004*c8dee2aaSAndroid Build Coastguard Worker     SkBase64::Encode(pngData->data(), pngData->size(), b64Data.get());
1005*c8dee2aaSAndroid Build Coastguard Worker 
1006*c8dee2aaSAndroid Build Coastguard Worker     SkString svgImageData("data:image/png;base64,");
1007*c8dee2aaSAndroid Build Coastguard Worker     svgImageData.append(b64Data.get(), b64Size);
1008*c8dee2aaSAndroid Build Coastguard Worker 
1009*c8dee2aaSAndroid Build Coastguard Worker     SkString imageID = fResourceBucket->addImage();
1010*c8dee2aaSAndroid Build Coastguard Worker     {
1011*c8dee2aaSAndroid Build Coastguard Worker         AutoElement defs("defs", fWriter);
1012*c8dee2aaSAndroid Build Coastguard Worker         {
1013*c8dee2aaSAndroid Build Coastguard Worker             AutoElement image("image", fWriter);
1014*c8dee2aaSAndroid Build Coastguard Worker             image.addAttribute("id", imageID);
1015*c8dee2aaSAndroid Build Coastguard Worker             image.addAttribute("width", bm.width());
1016*c8dee2aaSAndroid Build Coastguard Worker             image.addAttribute("height", bm.height());
1017*c8dee2aaSAndroid Build Coastguard Worker             image.addAttribute("xlink:href", svgImageData);
1018*c8dee2aaSAndroid Build Coastguard Worker         }
1019*c8dee2aaSAndroid Build Coastguard Worker     }
1020*c8dee2aaSAndroid Build Coastguard Worker 
1021*c8dee2aaSAndroid Build Coastguard Worker     {
1022*c8dee2aaSAndroid Build Coastguard Worker         AutoElement imageUse("use", this, fResourceBucket.get(), mc, paint);
1023*c8dee2aaSAndroid Build Coastguard Worker         imageUse.addAttribute("xlink:href", SkStringPrintf("#%s", imageID.c_str()));
1024*c8dee2aaSAndroid Build Coastguard Worker     }
1025*c8dee2aaSAndroid Build Coastguard Worker }
1026*c8dee2aaSAndroid Build Coastguard Worker 
drawImageRect(const SkImage * image,const SkRect * src,const SkRect & dst,const SkSamplingOptions & sampling,const SkPaint & paint,SkCanvas::SrcRectConstraint constraint)1027*c8dee2aaSAndroid Build Coastguard Worker void SkSVGDevice::drawImageRect(const SkImage* image, const SkRect* src, const SkRect& dst,
1028*c8dee2aaSAndroid Build Coastguard Worker                                 const SkSamplingOptions& sampling, const SkPaint& paint,
1029*c8dee2aaSAndroid Build Coastguard Worker                                 SkCanvas::SrcRectConstraint constraint) {
1030*c8dee2aaSAndroid Build Coastguard Worker     SkBitmap bm;
1031*c8dee2aaSAndroid Build Coastguard Worker     // TODO: support gpu images
1032*c8dee2aaSAndroid Build Coastguard Worker     if (!as_IB(image)->getROPixels(nullptr, &bm)) {
1033*c8dee2aaSAndroid Build Coastguard Worker         return;
1034*c8dee2aaSAndroid Build Coastguard Worker     }
1035*c8dee2aaSAndroid Build Coastguard Worker 
1036*c8dee2aaSAndroid Build Coastguard Worker     SkClipStack* cs = &this->cs();
1037*c8dee2aaSAndroid Build Coastguard Worker     SkClipStack::AutoRestore ar(cs, false);
1038*c8dee2aaSAndroid Build Coastguard Worker     if (src && *src != SkRect::Make(bm.bounds())) {
1039*c8dee2aaSAndroid Build Coastguard Worker         cs->save();
1040*c8dee2aaSAndroid Build Coastguard Worker         cs->clipRect(dst, this->localToDevice(), SkClipOp::kIntersect, paint.isAntiAlias());
1041*c8dee2aaSAndroid Build Coastguard Worker     }
1042*c8dee2aaSAndroid Build Coastguard Worker 
1043*c8dee2aaSAndroid Build Coastguard Worker     SkMatrix adjustedMatrix = this->localToDevice()
1044*c8dee2aaSAndroid Build Coastguard Worker                             * SkMatrix::RectToRect(src ? *src : SkRect::Make(bm.bounds()), dst);
1045*c8dee2aaSAndroid Build Coastguard Worker 
1046*c8dee2aaSAndroid Build Coastguard Worker     drawBitmapCommon(MxCp(&adjustedMatrix, cs), bm, paint);
1047*c8dee2aaSAndroid Build Coastguard Worker }
1048*c8dee2aaSAndroid Build Coastguard Worker 
1049*c8dee2aaSAndroid Build Coastguard Worker class SVGTextBuilder : SkNoncopyable {
1050*c8dee2aaSAndroid Build Coastguard Worker public:
SVGTextBuilder(SkPoint origin,const sktext::GlyphRun & glyphRun)1051*c8dee2aaSAndroid Build Coastguard Worker     SVGTextBuilder(SkPoint origin, const sktext::GlyphRun& glyphRun)
1052*c8dee2aaSAndroid Build Coastguard Worker             : fOrigin(origin) {
1053*c8dee2aaSAndroid Build Coastguard Worker         auto runSize = glyphRun.runSize();
1054*c8dee2aaSAndroid Build Coastguard Worker         AutoSTArray<64, SkUnichar> unichars(runSize);
1055*c8dee2aaSAndroid Build Coastguard Worker         SkFontPriv::GlyphsToUnichars(glyphRun.font(), glyphRun.glyphsIDs().data(),
1056*c8dee2aaSAndroid Build Coastguard Worker                                      runSize, unichars.get());
1057*c8dee2aaSAndroid Build Coastguard Worker         auto positions = glyphRun.positions();
1058*c8dee2aaSAndroid Build Coastguard Worker         for (size_t i = 0; i < runSize; ++i) {
1059*c8dee2aaSAndroid Build Coastguard Worker             this->appendUnichar(unichars[i], positions[i]);
1060*c8dee2aaSAndroid Build Coastguard Worker         }
1061*c8dee2aaSAndroid Build Coastguard Worker     }
1062*c8dee2aaSAndroid Build Coastguard Worker 
text() const1063*c8dee2aaSAndroid Build Coastguard Worker     const SkString& text() const { return fText; }
posX() const1064*c8dee2aaSAndroid Build Coastguard Worker     const SkString& posX() const { return fPosXStr; }
posY() const1065*c8dee2aaSAndroid Build Coastguard Worker     const SkString& posY() const { return fHasConstY ? fConstYStr : fPosYStr; }
1066*c8dee2aaSAndroid Build Coastguard Worker 
1067*c8dee2aaSAndroid Build Coastguard Worker private:
appendUnichar(SkUnichar c,SkPoint position)1068*c8dee2aaSAndroid Build Coastguard Worker     void appendUnichar(SkUnichar c, SkPoint position) {
1069*c8dee2aaSAndroid Build Coastguard Worker         bool discardPos = false;
1070*c8dee2aaSAndroid Build Coastguard Worker         bool isWhitespace = false;
1071*c8dee2aaSAndroid Build Coastguard Worker 
1072*c8dee2aaSAndroid Build Coastguard Worker         switch(c) {
1073*c8dee2aaSAndroid Build Coastguard Worker             case ' ':
1074*c8dee2aaSAndroid Build Coastguard Worker             case '\t':
1075*c8dee2aaSAndroid Build Coastguard Worker                 // consolidate whitespace to match SVG's xml:space=default munging
1076*c8dee2aaSAndroid Build Coastguard Worker                 // (http://www.w3.org/TR/SVG/text.html#WhiteSpace)
1077*c8dee2aaSAndroid Build Coastguard Worker                 if (fLastCharWasWhitespace) {
1078*c8dee2aaSAndroid Build Coastguard Worker                     discardPos = true;
1079*c8dee2aaSAndroid Build Coastguard Worker                 } else {
1080*c8dee2aaSAndroid Build Coastguard Worker                     fText.appendUnichar(c);
1081*c8dee2aaSAndroid Build Coastguard Worker                 }
1082*c8dee2aaSAndroid Build Coastguard Worker                 isWhitespace = true;
1083*c8dee2aaSAndroid Build Coastguard Worker                 break;
1084*c8dee2aaSAndroid Build Coastguard Worker             case '\0':
1085*c8dee2aaSAndroid Build Coastguard Worker                 // SkPaint::glyphsToUnichars() returns \0 for inconvertible glyphs, but these
1086*c8dee2aaSAndroid Build Coastguard Worker                 // are not legal XML characters (http://www.w3.org/TR/REC-xml/#charsets)
1087*c8dee2aaSAndroid Build Coastguard Worker                 discardPos = true;
1088*c8dee2aaSAndroid Build Coastguard Worker                 isWhitespace = fLastCharWasWhitespace; // preserve whitespace consolidation
1089*c8dee2aaSAndroid Build Coastguard Worker                 break;
1090*c8dee2aaSAndroid Build Coastguard Worker             case '&':
1091*c8dee2aaSAndroid Build Coastguard Worker                 fText.append("&amp;");
1092*c8dee2aaSAndroid Build Coastguard Worker                 break;
1093*c8dee2aaSAndroid Build Coastguard Worker             case '"':
1094*c8dee2aaSAndroid Build Coastguard Worker                 fText.append("&quot;");
1095*c8dee2aaSAndroid Build Coastguard Worker                 break;
1096*c8dee2aaSAndroid Build Coastguard Worker             case '\'':
1097*c8dee2aaSAndroid Build Coastguard Worker                 fText.append("&apos;");
1098*c8dee2aaSAndroid Build Coastguard Worker                 break;
1099*c8dee2aaSAndroid Build Coastguard Worker             case '<':
1100*c8dee2aaSAndroid Build Coastguard Worker                 fText.append("&lt;");
1101*c8dee2aaSAndroid Build Coastguard Worker                 break;
1102*c8dee2aaSAndroid Build Coastguard Worker             case '>':
1103*c8dee2aaSAndroid Build Coastguard Worker                 fText.append("&gt;");
1104*c8dee2aaSAndroid Build Coastguard Worker                 break;
1105*c8dee2aaSAndroid Build Coastguard Worker             default:
1106*c8dee2aaSAndroid Build Coastguard Worker                 fText.appendUnichar(c);
1107*c8dee2aaSAndroid Build Coastguard Worker                 break;
1108*c8dee2aaSAndroid Build Coastguard Worker         }
1109*c8dee2aaSAndroid Build Coastguard Worker 
1110*c8dee2aaSAndroid Build Coastguard Worker         fLastCharWasWhitespace = isWhitespace;
1111*c8dee2aaSAndroid Build Coastguard Worker 
1112*c8dee2aaSAndroid Build Coastguard Worker         if (discardPos) {
1113*c8dee2aaSAndroid Build Coastguard Worker             return;
1114*c8dee2aaSAndroid Build Coastguard Worker         }
1115*c8dee2aaSAndroid Build Coastguard Worker 
1116*c8dee2aaSAndroid Build Coastguard Worker         position += fOrigin;
1117*c8dee2aaSAndroid Build Coastguard Worker         fPosXStr.appendf("%.8g, ", position.fX);
1118*c8dee2aaSAndroid Build Coastguard Worker         fPosYStr.appendf("%.8g, ", position.fY);
1119*c8dee2aaSAndroid Build Coastguard Worker 
1120*c8dee2aaSAndroid Build Coastguard Worker         if (fConstYStr.isEmpty()) {
1121*c8dee2aaSAndroid Build Coastguard Worker             fConstYStr = fPosYStr;
1122*c8dee2aaSAndroid Build Coastguard Worker             fConstY    = position.fY;
1123*c8dee2aaSAndroid Build Coastguard Worker         } else {
1124*c8dee2aaSAndroid Build Coastguard Worker             fHasConstY &= SkScalarNearlyEqual(fConstY, position.fY);
1125*c8dee2aaSAndroid Build Coastguard Worker         }
1126*c8dee2aaSAndroid Build Coastguard Worker     }
1127*c8dee2aaSAndroid Build Coastguard Worker 
1128*c8dee2aaSAndroid Build Coastguard Worker     const SkPoint   fOrigin;
1129*c8dee2aaSAndroid Build Coastguard Worker 
1130*c8dee2aaSAndroid Build Coastguard Worker     SkString fText,
1131*c8dee2aaSAndroid Build Coastguard Worker              fPosXStr, fPosYStr,
1132*c8dee2aaSAndroid Build Coastguard Worker              fConstYStr;
1133*c8dee2aaSAndroid Build Coastguard Worker     SkScalar fConstY;
1134*c8dee2aaSAndroid Build Coastguard Worker     bool     fLastCharWasWhitespace = true, // start off in whitespace mode to strip leading space
1135*c8dee2aaSAndroid Build Coastguard Worker              fHasConstY             = true;
1136*c8dee2aaSAndroid Build Coastguard Worker };
1137*c8dee2aaSAndroid Build Coastguard Worker 
onDrawGlyphRunList(SkCanvas * canvas,const sktext::GlyphRunList & glyphRunList,const SkPaint & paint)1138*c8dee2aaSAndroid Build Coastguard Worker void SkSVGDevice::onDrawGlyphRunList(SkCanvas* canvas,
1139*c8dee2aaSAndroid Build Coastguard Worker                                      const sktext::GlyphRunList& glyphRunList,
1140*c8dee2aaSAndroid Build Coastguard Worker                                      const SkPaint& paint) {
1141*c8dee2aaSAndroid Build Coastguard Worker     SkASSERT(!glyphRunList.hasRSXForm());
1142*c8dee2aaSAndroid Build Coastguard Worker     const auto draw_as_path =
1143*c8dee2aaSAndroid Build Coastguard Worker             (fFlags & SkSVGCanvas::kConvertTextToPaths_Flag) || paint.getPathEffect();
1144*c8dee2aaSAndroid Build Coastguard Worker 
1145*c8dee2aaSAndroid Build Coastguard Worker     if (draw_as_path) {
1146*c8dee2aaSAndroid Build Coastguard Worker         // Emit a single <path> element.
1147*c8dee2aaSAndroid Build Coastguard Worker         SkPath path;
1148*c8dee2aaSAndroid Build Coastguard Worker         for (auto& glyphRun : glyphRunList) {
1149*c8dee2aaSAndroid Build Coastguard Worker             AddPath(glyphRun, glyphRunList.origin(), &path);
1150*c8dee2aaSAndroid Build Coastguard Worker         }
1151*c8dee2aaSAndroid Build Coastguard Worker 
1152*c8dee2aaSAndroid Build Coastguard Worker         this->drawPath(path, paint);
1153*c8dee2aaSAndroid Build Coastguard Worker 
1154*c8dee2aaSAndroid Build Coastguard Worker         return;
1155*c8dee2aaSAndroid Build Coastguard Worker     }
1156*c8dee2aaSAndroid Build Coastguard Worker 
1157*c8dee2aaSAndroid Build Coastguard Worker     // Emit one <text> element for each run.
1158*c8dee2aaSAndroid Build Coastguard Worker     for (auto& glyphRun : glyphRunList) {
1159*c8dee2aaSAndroid Build Coastguard Worker         AutoElement elem("text", this, fResourceBucket.get(), MxCp(this), paint);
1160*c8dee2aaSAndroid Build Coastguard Worker         elem.addTextAttributes(glyphRun.font());
1161*c8dee2aaSAndroid Build Coastguard Worker 
1162*c8dee2aaSAndroid Build Coastguard Worker         SVGTextBuilder builder(glyphRunList.origin(), glyphRun);
1163*c8dee2aaSAndroid Build Coastguard Worker         elem.addAttribute("x", builder.posX());
1164*c8dee2aaSAndroid Build Coastguard Worker         elem.addAttribute("y", builder.posY());
1165*c8dee2aaSAndroid Build Coastguard Worker         elem.addText(builder.text());
1166*c8dee2aaSAndroid Build Coastguard Worker     }
1167*c8dee2aaSAndroid Build Coastguard Worker }
1168*c8dee2aaSAndroid Build Coastguard Worker 
drawVertices(const SkVertices *,sk_sp<SkBlender>,const SkPaint &,bool)1169*c8dee2aaSAndroid Build Coastguard Worker void SkSVGDevice::drawVertices(const SkVertices*, sk_sp<SkBlender>, const SkPaint&, bool) {
1170*c8dee2aaSAndroid Build Coastguard Worker     // todo
1171*c8dee2aaSAndroid Build Coastguard Worker }
1172*c8dee2aaSAndroid Build Coastguard Worker 
drawMesh(const SkMesh &,sk_sp<SkBlender>,const SkPaint &)1173*c8dee2aaSAndroid Build Coastguard Worker void SkSVGDevice::drawMesh(const SkMesh&, sk_sp<SkBlender>, const SkPaint&) {
1174*c8dee2aaSAndroid Build Coastguard Worker     // todo
1175*c8dee2aaSAndroid Build Coastguard Worker }
1176