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 "SkSVGDevice.h"
9 
10 #include "SkAnnotationKeys.h"
11 #include "SkBase64.h"
12 #include "SkBitmap.h"
13 #include "SkBlendMode.h"
14 #include "SkChecksum.h"
15 #include "SkClipOpPriv.h"
16 #include "SkClipStack.h"
17 #include "SkColorFilter.h"
18 #include "SkData.h"
19 #include "SkDraw.h"
20 #include "SkImage.h"
21 #include "SkImageEncoder.h"
22 #include "SkJpegCodec.h"
23 #include "SkPaint.h"
24 #include "SkParsePath.h"
25 #include "SkPngCodec.h"
26 #include "SkShader.h"
27 #include "SkStream.h"
28 #include "SkTHash.h"
29 #include "SkTo.h"
30 #include "SkTypeface.h"
31 #include "SkUtils.h"
32 #include "SkXMLWriter.h"
33 
34 namespace {
35 
svg_color(SkColor color)36 static SkString svg_color(SkColor color) {
37     return SkStringPrintf("rgb(%u,%u,%u)",
38                           SkColorGetR(color),
39                           SkColorGetG(color),
40                           SkColorGetB(color));
41 }
42 
svg_opacity(SkColor color)43 static SkScalar svg_opacity(SkColor color) {
44     return SkIntToScalar(SkColorGetA(color)) / SK_AlphaOPAQUE;
45 }
46 
47 // Keep in sync with SkPaint::Cap
48 static const char* cap_map[]  = {
49     nullptr,    // kButt_Cap (default)
50     "round", // kRound_Cap
51     "square" // kSquare_Cap
52 };
53 static_assert(SK_ARRAY_COUNT(cap_map) == SkPaint::kCapCount, "missing_cap_map_entry");
54 
svg_cap(SkPaint::Cap cap)55 static const char* svg_cap(SkPaint::Cap cap) {
56     SkASSERT(cap < SK_ARRAY_COUNT(cap_map));
57     return cap_map[cap];
58 }
59 
60 // Keep in sync with SkPaint::Join
61 static const char* join_map[] = {
62     nullptr,    // kMiter_Join (default)
63     "round", // kRound_Join
64     "bevel"  // kBevel_Join
65 };
66 static_assert(SK_ARRAY_COUNT(join_map) == SkPaint::kJoinCount, "missing_join_map_entry");
67 
svg_join(SkPaint::Join join)68 static const char* svg_join(SkPaint::Join join) {
69     SkASSERT(join < SK_ARRAY_COUNT(join_map));
70     return join_map[join];
71 }
72 
svg_transform(const SkMatrix & t)73 static SkString svg_transform(const SkMatrix& t) {
74     SkASSERT(!t.isIdentity());
75 
76     SkString tstr;
77     switch (t.getType()) {
78     case SkMatrix::kPerspective_Mask:
79         // TODO: handle perspective matrices?
80         break;
81     case SkMatrix::kTranslate_Mask:
82         tstr.printf("translate(%g %g)", t.getTranslateX(), t.getTranslateY());
83         break;
84     case SkMatrix::kScale_Mask:
85         tstr.printf("scale(%g %g)", t.getScaleX(), t.getScaleY());
86         break;
87     default:
88         // http://www.w3.org/TR/SVG/coords.html#TransformMatrixDefined
89         //    | a c e |
90         //    | b d f |
91         //    | 0 0 1 |
92         tstr.printf("matrix(%g %g %g %g %g %g)",
93                     t.getScaleX(),     t.getSkewY(),
94                     t.getSkewX(),      t.getScaleY(),
95                     t.getTranslateX(), t.getTranslateY());
96         break;
97     }
98 
99     return tstr;
100 }
101 
102 struct Resources {
Resources__anonc7730f840111::Resources103     Resources(const SkPaint& paint)
104         : fPaintServer(svg_color(paint.getColor())) {}
105 
106     SkString fPaintServer;
107     SkString fClip;
108     SkString fColorFilter;
109 };
110 
111 // Determine if the paint requires us to reset the viewport.
112 // Currently, we do this whenever the paint shader calls
113 // for a repeating image.
RequiresViewportReset(const SkPaint & paint)114 bool RequiresViewportReset(const SkPaint& paint) {
115   SkShader* shader = paint.getShader();
116   if (!shader)
117     return false;
118 
119   SkShader::TileMode xy[2];
120   SkImage* image = shader->isAImage(nullptr, xy);
121 
122   if (!image)
123     return false;
124 
125   for (int i = 0; i < 2; i++) {
126     if (xy[i] == SkShader::kRepeat_TileMode)
127       return true;
128   }
129   return false;
130 }
131 
132 }  // namespace
133 
134 // For now all this does is serve unique serial IDs, but it will eventually evolve to track
135 // and deduplicate resources.
136 class SkSVGDevice::ResourceBucket : ::SkNoncopyable {
137 public:
ResourceBucket()138     ResourceBucket()
139             : fGradientCount(0)
140             , fClipCount(0)
141             , fPathCount(0)
142             , fImageCount(0)
143             , fPatternCount(0)
144             , fColorFilterCount(0) {}
145 
addLinearGradient()146     SkString addLinearGradient() {
147         return SkStringPrintf("gradient_%d", fGradientCount++);
148     }
149 
addClip()150     SkString addClip() {
151         return SkStringPrintf("clip_%d", fClipCount++);
152     }
153 
addPath()154     SkString addPath() {
155         return SkStringPrintf("path_%d", fPathCount++);
156     }
157 
addImage()158     SkString addImage() {
159         return SkStringPrintf("img_%d", fImageCount++);
160     }
161 
addColorFilter()162     SkString addColorFilter() { return SkStringPrintf("cfilter_%d", fColorFilterCount++); }
163 
addPattern()164     SkString addPattern() {
165       return SkStringPrintf("pattern_%d", fPatternCount++);
166     }
167 
168 private:
169     uint32_t fGradientCount;
170     uint32_t fClipCount;
171     uint32_t fPathCount;
172     uint32_t fImageCount;
173     uint32_t fPatternCount;
174     uint32_t fColorFilterCount;
175 };
176 
177 struct SkSVGDevice::MxCp {
178     const SkMatrix* fMatrix;
179     const SkClipStack*  fClipStack;
180 
MxCpSkSVGDevice::MxCp181     MxCp(const SkMatrix* mx, const SkClipStack* cs) : fMatrix(mx), fClipStack(cs) {}
MxCpSkSVGDevice::MxCp182     MxCp(SkSVGDevice* device) : fMatrix(&device->ctm()), fClipStack(&device->cs()) {}
183 };
184 
185 class SkSVGDevice::AutoElement : ::SkNoncopyable {
186 public:
AutoElement(const char name[],SkXMLWriter * writer)187     AutoElement(const char name[], SkXMLWriter* writer)
188         : fWriter(writer)
189         , fResourceBucket(nullptr) {
190         fWriter->startElement(name);
191     }
192 
AutoElement(const char name[],SkXMLWriter * writer,ResourceBucket * bucket,const MxCp & mc,const SkPaint & paint)193     AutoElement(const char name[], SkXMLWriter* writer, ResourceBucket* bucket,
194                 const MxCp& mc, const SkPaint& paint)
195         : fWriter(writer)
196         , fResourceBucket(bucket) {
197 
198         Resources res = this->addResources(mc, paint);
199 
200         if (!res.fClip.isEmpty()) {
201             // The clip is in device space. Apply it via a <g> wrapper to avoid local transform
202             // interference.
203             fClipGroup.reset(new AutoElement("g", fWriter));
204             fClipGroup->addAttribute("clip-path",res.fClip);
205         }
206 
207         fWriter->startElement(name);
208 
209         this->addPaint(paint, res);
210 
211         if (!mc.fMatrix->isIdentity()) {
212             this->addAttribute("transform", svg_transform(*mc.fMatrix));
213         }
214     }
215 
~AutoElement()216     ~AutoElement() {
217         fWriter->endElement();
218     }
219 
addAttribute(const char name[],const char val[])220     void addAttribute(const char name[], const char val[]) {
221         fWriter->addAttribute(name, val);
222     }
223 
addAttribute(const char name[],const SkString & val)224     void addAttribute(const char name[], const SkString& val) {
225         fWriter->addAttribute(name, val.c_str());
226     }
227 
addAttribute(const char name[],int32_t val)228     void addAttribute(const char name[], int32_t val) {
229         fWriter->addS32Attribute(name, val);
230     }
231 
addAttribute(const char name[],SkScalar val)232     void addAttribute(const char name[], SkScalar val) {
233         fWriter->addScalarAttribute(name, val);
234     }
235 
addText(const SkString & text)236     void addText(const SkString& text) {
237         fWriter->addText(text.c_str(), text.size());
238     }
239 
240     void addRectAttributes(const SkRect&);
241     void addPathAttributes(const SkPath&);
242     void addTextAttributes(const SkFont&);
243 
244 private:
245     Resources addResources(const MxCp&, const SkPaint& paint);
246     void addClipResources(const MxCp&, Resources* resources);
247     void addShaderResources(const SkPaint& paint, Resources* resources);
248     void addGradientShaderResources(const SkShader* shader, const SkPaint& paint,
249                                     Resources* resources);
250     void addColorFilterResources(const SkColorFilter& cf, Resources* resources);
251     void addImageShaderResources(const SkShader* shader, const SkPaint& paint,
252                                  Resources* resources);
253 
254     void addPatternDef(const SkBitmap& bm);
255 
256     void addPaint(const SkPaint& paint, const Resources& resources);
257 
258 
259     SkString addLinearGradientDef(const SkShader::GradientInfo& info, const SkShader* shader);
260 
261     SkXMLWriter*               fWriter;
262     ResourceBucket*            fResourceBucket;
263     std::unique_ptr<AutoElement> fClipGroup;
264 };
265 
addPaint(const SkPaint & paint,const Resources & resources)266 void SkSVGDevice::AutoElement::addPaint(const SkPaint& paint, const Resources& resources) {
267     SkPaint::Style style = paint.getStyle();
268     if (style == SkPaint::kFill_Style || style == SkPaint::kStrokeAndFill_Style) {
269         this->addAttribute("fill", resources.fPaintServer);
270 
271         if (SK_AlphaOPAQUE != SkColorGetA(paint.getColor())) {
272             this->addAttribute("fill-opacity", svg_opacity(paint.getColor()));
273         }
274     } else {
275         SkASSERT(style == SkPaint::kStroke_Style);
276         this->addAttribute("fill", "none");
277     }
278 
279     if (!resources.fColorFilter.isEmpty()) {
280         this->addAttribute("filter", resources.fColorFilter.c_str());
281     }
282 
283     if (style == SkPaint::kStroke_Style || style == SkPaint::kStrokeAndFill_Style) {
284         this->addAttribute("stroke", resources.fPaintServer);
285 
286         SkScalar strokeWidth = paint.getStrokeWidth();
287         if (strokeWidth == 0) {
288             // Hairline stroke
289             strokeWidth = 1;
290             this->addAttribute("vector-effect", "non-scaling-stroke");
291         }
292         this->addAttribute("stroke-width", strokeWidth);
293 
294         if (const char* cap = svg_cap(paint.getStrokeCap())) {
295             this->addAttribute("stroke-linecap", cap);
296         }
297 
298         if (const char* join = svg_join(paint.getStrokeJoin())) {
299             this->addAttribute("stroke-linejoin", join);
300         }
301 
302         if (paint.getStrokeJoin() == SkPaint::kMiter_Join) {
303             this->addAttribute("stroke-miterlimit", paint.getStrokeMiter());
304         }
305 
306         if (SK_AlphaOPAQUE != SkColorGetA(paint.getColor())) {
307             this->addAttribute("stroke-opacity", svg_opacity(paint.getColor()));
308         }
309     } else {
310         SkASSERT(style == SkPaint::kFill_Style);
311         this->addAttribute("stroke", "none");
312     }
313 }
314 
addResources(const MxCp & mc,const SkPaint & paint)315 Resources SkSVGDevice::AutoElement::addResources(const MxCp& mc, const SkPaint& paint) {
316     Resources resources(paint);
317 
318     // FIXME: this is a weak heuristic and we end up with LOTS of redundant clips.
319     bool hasClip   = !mc.fClipStack->isWideOpen();
320     bool hasShader = SkToBool(paint.getShader());
321 
322     if (hasClip || hasShader) {
323         AutoElement defs("defs", fWriter);
324 
325         if (hasClip) {
326             this->addClipResources(mc, &resources);
327         }
328 
329         if (hasShader) {
330             this->addShaderResources(paint, &resources);
331         }
332     }
333 
334     if (const SkColorFilter* cf = paint.getColorFilter()) {
335         // TODO: Implement skia color filters for blend modes other than SrcIn
336         SkBlendMode mode;
337         if (cf->asColorMode(nullptr, &mode) && mode == SkBlendMode::kSrcIn) {
338             this->addColorFilterResources(*cf, &resources);
339         }
340     }
341     return resources;
342 }
343 
addGradientShaderResources(const SkShader * shader,const SkPaint & paint,Resources * resources)344 void SkSVGDevice::AutoElement::addGradientShaderResources(const SkShader* shader,
345                                                           const SkPaint& paint,
346                                                           Resources* resources) {
347     SkShader::GradientInfo grInfo;
348     grInfo.fColorCount = 0;
349     if (SkShader::kLinear_GradientType != shader->asAGradient(&grInfo)) {
350         // TODO: non-linear gradient support
351         return;
352     }
353 
354     SkAutoSTArray<16, SkColor>  grColors(grInfo.fColorCount);
355     SkAutoSTArray<16, SkScalar> grOffsets(grInfo.fColorCount);
356     grInfo.fColors = grColors.get();
357     grInfo.fColorOffsets = grOffsets.get();
358 
359     // One more call to get the actual colors/offsets.
360     shader->asAGradient(&grInfo);
361     SkASSERT(grInfo.fColorCount <= grColors.count());
362     SkASSERT(grInfo.fColorCount <= grOffsets.count());
363 
364     resources->fPaintServer.printf("url(#%s)", addLinearGradientDef(grInfo, shader).c_str());
365 }
366 
addColorFilterResources(const SkColorFilter & cf,Resources * resources)367 void SkSVGDevice::AutoElement::addColorFilterResources(const SkColorFilter& cf,
368                                                        Resources* resources) {
369     SkString colorfilterID = fResourceBucket->addColorFilter();
370     {
371         AutoElement filterElement("filter", fWriter);
372         filterElement.addAttribute("id", colorfilterID);
373         filterElement.addAttribute("x", "0%");
374         filterElement.addAttribute("y", "0%");
375         filterElement.addAttribute("width", "100%");
376         filterElement.addAttribute("height", "100%");
377 
378         SkColor filterColor;
379         SkBlendMode mode;
380         bool asColorMode = cf.asColorMode(&filterColor, &mode);
381         SkAssertResult(asColorMode);
382         SkASSERT(mode == SkBlendMode::kSrcIn);
383 
384         {
385             // first flood with filter color
386             AutoElement floodElement("feFlood", fWriter);
387             floodElement.addAttribute("flood-color", svg_color(filterColor));
388             floodElement.addAttribute("flood-opacity", svg_opacity(filterColor));
389             floodElement.addAttribute("result", "flood");
390         }
391 
392         {
393             // apply the transform to filter color
394             AutoElement compositeElement("feComposite", fWriter);
395             compositeElement.addAttribute("in", "flood");
396             compositeElement.addAttribute("operator", "in");
397         }
398     }
399     resources->fColorFilter.printf("url(#%s)", colorfilterID.c_str());
400 }
401 
402 // Returns data uri from bytes.
403 // it will use any cached data if available, otherwise will
404 // encode as png.
AsDataUri(SkImage * image)405 sk_sp<SkData> AsDataUri(SkImage* image) {
406     sk_sp<SkData> imageData = image->encodeToData();
407     if (!imageData) {
408         return nullptr;
409     }
410 
411     const char* src = (char*)imageData->data();
412     const char* selectedPrefix = nullptr;
413     size_t selectedPrefixLength = 0;
414 
415     const static char pngDataPrefix[] = "data:image/png;base64,";
416     const static char jpgDataPrefix[] = "data:image/jpeg;base64,";
417 
418     if (SkJpegCodec::IsJpeg(src, imageData->size())) {
419         selectedPrefix = jpgDataPrefix;
420         selectedPrefixLength = sizeof(jpgDataPrefix);
421     } else {
422       if (!SkPngCodec::IsPng(src, imageData->size())) {
423         imageData = image->encodeToData(SkEncodedImageFormat::kPNG, 100);
424       }
425       selectedPrefix = pngDataPrefix;
426       selectedPrefixLength = sizeof(pngDataPrefix);
427     }
428 
429     size_t b64Size = SkBase64::Encode(imageData->data(), imageData->size(), nullptr);
430     sk_sp<SkData> dataUri = SkData::MakeUninitialized(selectedPrefixLength + b64Size);
431     char* dest = (char*)dataUri->writable_data();
432     memcpy(dest, selectedPrefix, selectedPrefixLength);
433     SkBase64::Encode(imageData->data(), imageData->size(), dest + selectedPrefixLength - 1);
434     dest[dataUri->size() - 1] = 0;
435     return dataUri;
436 }
437 
addImageShaderResources(const SkShader * shader,const SkPaint & paint,Resources * resources)438 void SkSVGDevice::AutoElement::addImageShaderResources(const SkShader* shader, const SkPaint& paint,
439                                                        Resources* resources) {
440     SkMatrix outMatrix;
441 
442     SkShader::TileMode xy[2];
443     SkImage* image = shader->isAImage(&outMatrix, xy);
444     SkASSERT(image);
445 
446     SkString patternDims[2];  // width, height
447 
448     sk_sp<SkData> dataUri = AsDataUri(image);
449     if (!dataUri) {
450         return;
451     }
452     SkIRect imageSize = image->bounds();
453     for (int i = 0; i < 2; i++) {
454         int imageDimension = i == 0 ? imageSize.width() : imageSize.height();
455         switch (xy[i]) {
456             case SkShader::kRepeat_TileMode:
457                 patternDims[i].appendScalar(imageDimension);
458             break;
459             default:
460                 // TODO: other tile modes?
461                 patternDims[i] = "100%";
462         }
463     }
464 
465     SkString patternID = fResourceBucket->addPattern();
466     {
467         AutoElement pattern("pattern", fWriter);
468         pattern.addAttribute("id", patternID);
469         pattern.addAttribute("patternUnits", "userSpaceOnUse");
470         pattern.addAttribute("patternContentUnits", "userSpaceOnUse");
471         pattern.addAttribute("width", patternDims[0]);
472         pattern.addAttribute("height", patternDims[1]);
473         pattern.addAttribute("x", 0);
474         pattern.addAttribute("y", 0);
475 
476         {
477             SkString imageID = fResourceBucket->addImage();
478             AutoElement imageTag("image", fWriter);
479             imageTag.addAttribute("id", imageID);
480             imageTag.addAttribute("x", 0);
481             imageTag.addAttribute("y", 0);
482             imageTag.addAttribute("width", image->width());
483             imageTag.addAttribute("height", image->height());
484             imageTag.addAttribute("xlink:href", static_cast<const char*>(dataUri->data()));
485         }
486     }
487     resources->fPaintServer.printf("url(#%s)", patternID.c_str());
488 }
489 
addShaderResources(const SkPaint & paint,Resources * resources)490 void SkSVGDevice::AutoElement::addShaderResources(const SkPaint& paint, Resources* resources) {
491     const SkShader* shader = paint.getShader();
492     SkASSERT(shader);
493 
494     if (shader->asAGradient(nullptr) != SkShader::kNone_GradientType) {
495         this->addGradientShaderResources(shader, paint, resources);
496     } else if (shader->isAImage()) {
497         this->addImageShaderResources(shader, paint, resources);
498     }
499     // TODO: other shader types?
500 }
501 
addClipResources(const MxCp & mc,Resources * resources)502 void SkSVGDevice::AutoElement::addClipResources(const MxCp& mc, Resources* resources) {
503     SkASSERT(!mc.fClipStack->isWideOpen());
504 
505     SkPath clipPath;
506     (void) mc.fClipStack->asPath(&clipPath);
507 
508     SkString clipID = fResourceBucket->addClip();
509     const char* clipRule = clipPath.getFillType() == SkPath::kEvenOdd_FillType ?
510                            "evenodd" : "nonzero";
511     {
512         // clipPath is in device space, but since we're only pushing transform attributes
513         // to the leaf nodes, so are all our elements => SVG userSpaceOnUse == device space.
514         AutoElement clipPathElement("clipPath", fWriter);
515         clipPathElement.addAttribute("id", clipID);
516 
517         SkRect clipRect = SkRect::MakeEmpty();
518         if (clipPath.isEmpty() || clipPath.isRect(&clipRect)) {
519             AutoElement rectElement("rect", fWriter);
520             rectElement.addRectAttributes(clipRect);
521             rectElement.addAttribute("clip-rule", clipRule);
522         } else {
523             AutoElement pathElement("path", fWriter);
524             pathElement.addPathAttributes(clipPath);
525             pathElement.addAttribute("clip-rule", clipRule);
526         }
527     }
528 
529     resources->fClip.printf("url(#%s)", clipID.c_str());
530 }
531 
addLinearGradientDef(const SkShader::GradientInfo & info,const SkShader * shader)532 SkString SkSVGDevice::AutoElement::addLinearGradientDef(const SkShader::GradientInfo& info,
533                                                         const SkShader* shader) {
534     SkASSERT(fResourceBucket);
535     SkString id = fResourceBucket->addLinearGradient();
536 
537     {
538         AutoElement gradient("linearGradient", fWriter);
539 
540         gradient.addAttribute("id", id);
541         gradient.addAttribute("gradientUnits", "userSpaceOnUse");
542         gradient.addAttribute("x1", info.fPoint[0].x());
543         gradient.addAttribute("y1", info.fPoint[0].y());
544         gradient.addAttribute("x2", info.fPoint[1].x());
545         gradient.addAttribute("y2", info.fPoint[1].y());
546 
547         if (!shader->getLocalMatrix().isIdentity()) {
548             this->addAttribute("gradientTransform", svg_transform(shader->getLocalMatrix()));
549         }
550 
551         SkASSERT(info.fColorCount >= 2);
552         for (int i = 0; i < info.fColorCount; ++i) {
553             SkColor color = info.fColors[i];
554             SkString colorStr(svg_color(color));
555 
556             {
557                 AutoElement stop("stop", fWriter);
558                 stop.addAttribute("offset", info.fColorOffsets[i]);
559                 stop.addAttribute("stop-color", colorStr.c_str());
560 
561                 if (SK_AlphaOPAQUE != SkColorGetA(color)) {
562                     stop.addAttribute("stop-opacity", svg_opacity(color));
563                 }
564             }
565         }
566     }
567 
568     return id;
569 }
570 
addRectAttributes(const SkRect & rect)571 void SkSVGDevice::AutoElement::addRectAttributes(const SkRect& rect) {
572     // x, y default to 0
573     if (rect.x() != 0) {
574         this->addAttribute("x", rect.x());
575     }
576     if (rect.y() != 0) {
577         this->addAttribute("y", rect.y());
578     }
579 
580     this->addAttribute("width", rect.width());
581     this->addAttribute("height", rect.height());
582 }
583 
addPathAttributes(const SkPath & path)584 void SkSVGDevice::AutoElement::addPathAttributes(const SkPath& path) {
585     SkString pathData;
586     SkParsePath::ToSVGString(path, &pathData);
587     this->addAttribute("d", pathData);
588 }
589 
addTextAttributes(const SkFont & font)590 void SkSVGDevice::AutoElement::addTextAttributes(const SkFont& font) {
591     this->addAttribute("font-size", font.getSize());
592 
593     SkString familyName;
594     SkTHashSet<SkString> familySet;
595     sk_sp<SkTypeface> tface = font.refTypefaceOrDefault();
596 
597     SkASSERT(tface);
598     SkFontStyle style = tface->fontStyle();
599     if (style.slant() == SkFontStyle::kItalic_Slant) {
600         this->addAttribute("font-style", "italic");
601     } else if (style.slant() == SkFontStyle::kOblique_Slant) {
602         this->addAttribute("font-style", "oblique");
603     }
604     int weightIndex = (SkTPin(style.weight(), 100, 900) - 50) / 100;
605     if (weightIndex != 3) {
606         static constexpr const char* weights[] = {
607             "100", "200", "300", "normal", "400", "500", "600", "bold", "800", "900"
608         };
609         this->addAttribute("font-weight", weights[weightIndex]);
610     }
611     int stretchIndex = style.width() - 1;
612     if (stretchIndex != 4) {
613         static constexpr const char* stretches[] = {
614             "ultra-condensed", "extra-condensed", "condensed", "semi-condensed",
615             "normal",
616             "semi-expanded", "expanded", "extra-expanded", "ultra-expanded"
617         };
618         this->addAttribute("font-stretch", stretches[stretchIndex]);
619     }
620 
621     sk_sp<SkTypeface::LocalizedStrings> familyNameIter(tface->createFamilyNameIterator());
622     SkTypeface::LocalizedString familyString;
623     if (familyNameIter) {
624         while (familyNameIter->next(&familyString)) {
625             if (familySet.contains(familyString.fString)) {
626                 continue;
627             }
628             familySet.add(familyString.fString);
629             familyName.appendf((familyName.isEmpty() ? "%s" : ", %s"), familyString.fString.c_str());
630         }
631     }
632     if (!familyName.isEmpty()) {
633         this->addAttribute("font-family", familyName);
634     }
635 }
636 
Create(const SkISize & size,SkXMLWriter * writer)637 SkBaseDevice* SkSVGDevice::Create(const SkISize& size, SkXMLWriter* writer) {
638     if (!writer) {
639         return nullptr;
640     }
641 
642     return new SkSVGDevice(size, writer);
643 }
644 
SkSVGDevice(const SkISize & size,SkXMLWriter * writer)645 SkSVGDevice::SkSVGDevice(const SkISize& size, SkXMLWriter* writer)
646     : INHERITED(SkImageInfo::MakeUnknown(size.fWidth, size.fHeight),
647                 SkSurfaceProps(0, kUnknown_SkPixelGeometry))
648     , fWriter(writer)
649     , fResourceBucket(new ResourceBucket)
650 {
651     SkASSERT(writer);
652 
653     fWriter->writeHeader();
654 
655     // The root <svg> tag gets closed by the destructor.
656     fRootElement.reset(new AutoElement("svg", fWriter));
657 
658     fRootElement->addAttribute("xmlns", "http://www.w3.org/2000/svg");
659     fRootElement->addAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
660     fRootElement->addAttribute("width", size.width());
661     fRootElement->addAttribute("height", size.height());
662 }
663 
~SkSVGDevice()664 SkSVGDevice::~SkSVGDevice() {
665 }
666 
drawPaint(const SkPaint & paint)667 void SkSVGDevice::drawPaint(const SkPaint& paint) {
668     AutoElement rect("rect", fWriter, fResourceBucket.get(), MxCp(this), paint);
669     rect.addRectAttributes(SkRect::MakeWH(SkIntToScalar(this->width()),
670                                           SkIntToScalar(this->height())));
671 }
672 
drawAnnotation(const SkRect & rect,const char key[],SkData * value)673 void SkSVGDevice::drawAnnotation(const SkRect& rect, const char key[], SkData* value) {
674     if (!value) {
675         return;
676     }
677 
678     if (!strcmp(SkAnnotationKeys::URL_Key(), key) ||
679         !strcmp(SkAnnotationKeys::Link_Named_Dest_Key(), key)) {
680         this->cs().save();
681         this->cs().clipRect(rect, this->ctm(), kIntersect_SkClipOp, true);
682         SkRect transformedRect = this->cs().bounds(this->getGlobalBounds());
683         this->cs().restore();
684         if (transformedRect.isEmpty()) {
685             return;
686         }
687 
688         SkString url(static_cast<const char*>(value->data()), value->size() - 1);
689         AutoElement a("a", fWriter);
690         a.addAttribute("xlink:href", url.c_str());
691         {
692             AutoElement r("rect", fWriter);
693             r.addAttribute("fill-opacity", "0.0");
694             r.addRectAttributes(transformedRect);
695         }
696     }
697 }
698 
drawPoints(SkCanvas::PointMode mode,size_t count,const SkPoint pts[],const SkPaint & paint)699 void SkSVGDevice::drawPoints(SkCanvas::PointMode mode, size_t count,
700                              const SkPoint pts[], const SkPaint& paint) {
701     SkPath path;
702 
703     switch (mode) {
704             // todo
705         case SkCanvas::kPoints_PointMode:
706             // TODO?
707             break;
708         case SkCanvas::kLines_PointMode:
709             count -= 1;
710             for (size_t i = 0; i < count; i += 2) {
711                 path.rewind();
712                 path.moveTo(pts[i]);
713                 path.lineTo(pts[i+1]);
714                 AutoElement elem("path", fWriter, fResourceBucket.get(), MxCp(this), paint);
715                 elem.addPathAttributes(path);
716             }
717             break;
718         case SkCanvas::kPolygon_PointMode:
719             if (count > 1) {
720                 path.addPoly(pts, SkToInt(count), false);
721                 path.moveTo(pts[0]);
722                 AutoElement elem("path", fWriter, fResourceBucket.get(), MxCp(this), paint);
723                 elem.addPathAttributes(path);
724             }
725             break;
726     }
727 }
728 
drawRect(const SkRect & r,const SkPaint & paint)729 void SkSVGDevice::drawRect(const SkRect& r, const SkPaint& paint) {
730     std::unique_ptr<AutoElement> svg;
731     if (RequiresViewportReset(paint)) {
732       svg.reset(new AutoElement("svg", fWriter, fResourceBucket.get(), MxCp(this), paint));
733       svg->addRectAttributes(r);
734     }
735 
736     AutoElement rect("rect", fWriter, fResourceBucket.get(), MxCp(this), paint);
737 
738     if (svg) {
739       rect.addAttribute("x", 0);
740       rect.addAttribute("y", 0);
741       rect.addAttribute("width", "100%");
742       rect.addAttribute("height", "100%");
743     } else {
744       rect.addRectAttributes(r);
745     }
746 }
747 
drawOval(const SkRect & oval,const SkPaint & paint)748 void SkSVGDevice::drawOval(const SkRect& oval, const SkPaint& paint) {
749     AutoElement ellipse("ellipse", fWriter, fResourceBucket.get(), MxCp(this), paint);
750     ellipse.addAttribute("cx", oval.centerX());
751     ellipse.addAttribute("cy", oval.centerY());
752     ellipse.addAttribute("rx", oval.width() / 2);
753     ellipse.addAttribute("ry", oval.height() / 2);
754 }
755 
drawRRect(const SkRRect & rr,const SkPaint & paint)756 void SkSVGDevice::drawRRect(const SkRRect& rr, const SkPaint& paint) {
757     SkPath path;
758     path.addRRect(rr);
759 
760     AutoElement elem("path", fWriter, fResourceBucket.get(), MxCp(this), paint);
761     elem.addPathAttributes(path);
762 }
763 
drawPath(const SkPath & path,const SkPaint & paint,bool pathIsMutable)764 void SkSVGDevice::drawPath(const SkPath& path, const SkPaint& paint, bool pathIsMutable) {
765     AutoElement elem("path", fWriter, fResourceBucket.get(), MxCp(this), paint);
766     elem.addPathAttributes(path);
767 
768     // TODO: inverse fill types?
769     if (path.getFillType() == SkPath::kEvenOdd_FillType) {
770         elem.addAttribute("fill-rule", "evenodd");
771     }
772 }
773 
encode(const SkBitmap & src)774 static sk_sp<SkData> encode(const SkBitmap& src) {
775     SkDynamicMemoryWStream buf;
776     return SkEncodeImage(&buf, src, SkEncodedImageFormat::kPNG, 80) ? buf.detachAsData() : nullptr;
777 }
778 
drawBitmapCommon(const MxCp & mc,const SkBitmap & bm,const SkPaint & paint)779 void SkSVGDevice::drawBitmapCommon(const MxCp& mc, const SkBitmap& bm, const SkPaint& paint) {
780     sk_sp<SkData> pngData = encode(bm);
781     if (!pngData) {
782         return;
783     }
784 
785     size_t b64Size = SkBase64::Encode(pngData->data(), pngData->size(), nullptr);
786     SkAutoTMalloc<char> b64Data(b64Size);
787     SkBase64::Encode(pngData->data(), pngData->size(), b64Data.get());
788 
789     SkString svgImageData("data:image/png;base64,");
790     svgImageData.append(b64Data.get(), b64Size);
791 
792     SkString imageID = fResourceBucket->addImage();
793     {
794         AutoElement defs("defs", fWriter);
795         {
796             AutoElement image("image", fWriter);
797             image.addAttribute("id", imageID);
798             image.addAttribute("width", bm.width());
799             image.addAttribute("height", bm.height());
800             image.addAttribute("xlink:href", svgImageData);
801         }
802     }
803 
804     {
805         AutoElement imageUse("use", fWriter, fResourceBucket.get(), mc, paint);
806         imageUse.addAttribute("xlink:href", SkStringPrintf("#%s", imageID.c_str()));
807     }
808 }
809 
drawBitmap(const SkBitmap & bitmap,SkScalar x,SkScalar y,const SkPaint & paint)810 void SkSVGDevice::drawBitmap(const SkBitmap& bitmap, SkScalar x, SkScalar y,
811                              const SkPaint& paint) {
812     MxCp mc(this);
813     SkMatrix adjustedMatrix = *mc.fMatrix;
814     adjustedMatrix.preTranslate(x, y);
815     mc.fMatrix = &adjustedMatrix;
816 
817     drawBitmapCommon(mc, bitmap, paint);
818 }
819 
drawSprite(const SkBitmap & bitmap,int x,int y,const SkPaint & paint)820 void SkSVGDevice::drawSprite(const SkBitmap& bitmap,
821                              int x, int y, const SkPaint& paint) {
822     MxCp mc(this);
823     SkMatrix adjustedMatrix = *mc.fMatrix;
824     adjustedMatrix.preTranslate(SkIntToScalar(x), SkIntToScalar(y));
825     mc.fMatrix = &adjustedMatrix;
826 
827     drawBitmapCommon(mc, bitmap, paint);
828 }
829 
drawBitmapRect(const SkBitmap & bm,const SkRect * srcOrNull,const SkRect & dst,const SkPaint & paint,SkCanvas::SrcRectConstraint)830 void SkSVGDevice::drawBitmapRect(const SkBitmap& bm, const SkRect* srcOrNull,
831                                  const SkRect& dst, const SkPaint& paint,
832                                  SkCanvas::SrcRectConstraint) {
833     SkClipStack* cs = &this->cs();
834     SkClipStack::AutoRestore ar(cs, false);
835     if (srcOrNull && *srcOrNull != SkRect::Make(bm.bounds())) {
836         cs->save();
837         cs->clipRect(dst, this->ctm(), kIntersect_SkClipOp, paint.isAntiAlias());
838     }
839 
840     SkMatrix adjustedMatrix;
841     adjustedMatrix.setRectToRect(srcOrNull ? *srcOrNull : SkRect::Make(bm.bounds()),
842                                  dst,
843                                  SkMatrix::kFill_ScaleToFit);
844     adjustedMatrix.postConcat(this->ctm());
845 
846     drawBitmapCommon(MxCp(&adjustedMatrix, cs), bm, paint);
847 }
848 
849 class SVGTextBuilder : SkNoncopyable {
850 public:
SVGTextBuilder(SkPoint origin,const SkGlyphRun & glyphRun)851     SVGTextBuilder(SkPoint origin, const SkGlyphRun& glyphRun)
852             : fOrigin(origin)
853             , fLastCharWasWhitespace(true) { // start off in whitespace mode to strip all leadingspace
854         auto runSize = glyphRun.runSize();
855         SkAutoSTArray<64, SkUnichar> unichars(runSize);
856         glyphRun.font().glyphsToUnichars(glyphRun.glyphsIDs().data(), runSize, unichars.get());
857         auto positions = glyphRun.positions();
858         for (size_t i = 0; i < runSize; ++i) {
859             this->appendUnichar(unichars[i], positions[i]);
860         }
861     }
862 
text() const863     const SkString& text() const { return fText; }
posX() const864     const SkString& posX() const { return fPosX; }
posY() const865     const SkString& posY() const { return fPosY; }
866 
867 private:
appendUnichar(SkUnichar c,SkPoint position)868     void appendUnichar(SkUnichar c, SkPoint position) {
869         bool discardPos = false;
870         bool isWhitespace = false;
871 
872         switch(c) {
873             case ' ':
874             case '\t':
875                 // consolidate whitespace to match SVG's xml:space=default munging
876                 // (http://www.w3.org/TR/SVG/text.html#WhiteSpace)
877                 if (fLastCharWasWhitespace) {
878                     discardPos = true;
879                 } else {
880                     fText.appendUnichar(c);
881                 }
882                 isWhitespace = true;
883                 break;
884             case '\0':
885                 // SkPaint::glyphsToUnichars() returns \0 for inconvertible glyphs, but these
886                 // are not legal XML characters (http://www.w3.org/TR/REC-xml/#charsets)
887                 discardPos = true;
888                 isWhitespace = fLastCharWasWhitespace; // preserve whitespace consolidation
889                 break;
890             case '&':
891                 fText.append("&amp;");
892                 break;
893             case '"':
894                 fText.append("&quot;");
895                 break;
896             case '\'':
897                 fText.append("&apos;");
898                 break;
899             case '<':
900                 fText.append("&lt;");
901                 break;
902             case '>':
903                 fText.append("&gt;");
904                 break;
905             default:
906                 fText.appendUnichar(c);
907                 break;
908         }
909 
910         this->advancePos(discardPos, position);
911         fLastCharWasWhitespace = isWhitespace;
912     }
913 
advancePos(bool discard,SkPoint position)914     void advancePos(bool discard, SkPoint position) {
915         if (!discard) {
916             SkPoint finalPosition = fOrigin + position;
917             fPosX.appendf("%.8g, ", finalPosition.x());
918             fPosY.appendf("%.8g, ", finalPosition.y());
919         }
920     }
921 
922     const SkPoint   fOrigin;
923 
924     SkString fText, fPosX, fPosY;
925     bool     fLastCharWasWhitespace;
926 };
927 
drawGlyphRunList(const SkGlyphRunList & glyphRunList)928 void SkSVGDevice::drawGlyphRunList(const SkGlyphRunList& glyphRunList)  {
929 
930     auto processGlyphRun = [this]
931                            (SkPoint origin, const SkGlyphRun& glyphRun, const SkPaint& runPaint) {
932         AutoElement elem("text", fWriter, fResourceBucket.get(), MxCp(this), runPaint);
933         elem.addTextAttributes(glyphRun.font());
934 
935         SVGTextBuilder builder(origin, glyphRun);
936         elem.addAttribute("x", builder.posX());
937         elem.addAttribute("y", builder.posY());
938         elem.addText(builder.text());
939     };
940 
941     for (auto& glyphRun : glyphRunList) {
942         processGlyphRun(glyphRunList.origin(), glyphRun, glyphRunList.paint());
943     }
944 }
945 
drawVertices(const SkVertices *,const SkVertices::Bone[],int,SkBlendMode,const SkPaint &)946 void SkSVGDevice::drawVertices(const SkVertices*, const SkVertices::Bone[], int, SkBlendMode,
947                                const SkPaint&) {
948     // todo
949 }
950 
drawDevice(SkBaseDevice *,int x,int y,const SkPaint &)951 void SkSVGDevice::drawDevice(SkBaseDevice*, int x, int y,
952                              const SkPaint&) {
953     // todo
954 }
955