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