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