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