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