• 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 "SkSVGDevice.h"
9 
10 #include "SkBase64.h"
11 #include "SkBitmap.h"
12 #include "SkChecksum.h"
13 #include "SkClipStack.h"
14 #include "SkData.h"
15 #include "SkDraw.h"
16 #include "SkImageEncoder.h"
17 #include "SkPaint.h"
18 #include "SkParsePath.h"
19 #include "SkShader.h"
20 #include "SkStream.h"
21 #include "SkTHash.h"
22 #include "SkTypeface.h"
23 #include "SkUtils.h"
24 #include "SkXMLWriter.h"
25 #include "SkClipOpPriv.h"
26 
27 namespace {
28 
svg_color(SkColor color)29 static SkString svg_color(SkColor color) {
30     return SkStringPrintf("rgb(%u,%u,%u)",
31                           SkColorGetR(color),
32                           SkColorGetG(color),
33                           SkColorGetB(color));
34 }
35 
svg_opacity(SkColor color)36 static SkScalar svg_opacity(SkColor color) {
37     return SkIntToScalar(SkColorGetA(color)) / SK_AlphaOPAQUE;
38 }
39 
40 // Keep in sync with SkPaint::Cap
41 static const char* cap_map[]  = {
42     nullptr,    // kButt_Cap (default)
43     "round", // kRound_Cap
44     "square" // kSquare_Cap
45 };
46 static_assert(SK_ARRAY_COUNT(cap_map) == SkPaint::kCapCount, "missing_cap_map_entry");
47 
svg_cap(SkPaint::Cap cap)48 static const char* svg_cap(SkPaint::Cap cap) {
49     SkASSERT(cap < SK_ARRAY_COUNT(cap_map));
50     return cap_map[cap];
51 }
52 
53 // Keep in sync with SkPaint::Join
54 static const char* join_map[] = {
55     nullptr,    // kMiter_Join (default)
56     "round", // kRound_Join
57     "bevel"  // kBevel_Join
58 };
59 static_assert(SK_ARRAY_COUNT(join_map) == SkPaint::kJoinCount, "missing_join_map_entry");
60 
svg_join(SkPaint::Join join)61 static const char* svg_join(SkPaint::Join join) {
62     SkASSERT(join < SK_ARRAY_COUNT(join_map));
63     return join_map[join];
64 }
65 
66 // Keep in sync with SkPaint::Align
67 static const char* text_align_map[] = {
68     nullptr,     // kLeft_Align (default)
69     "middle", // kCenter_Align
70     "end"     // kRight_Align
71 };
72 static_assert(SK_ARRAY_COUNT(text_align_map) == SkPaint::kAlignCount,
73               "missing_text_align_map_entry");
svg_text_align(SkPaint::Align align)74 static const char* svg_text_align(SkPaint::Align align) {
75     SkASSERT(align < SK_ARRAY_COUNT(text_align_map));
76     return text_align_map[align];
77 }
78 
svg_transform(const SkMatrix & t)79 static SkString svg_transform(const SkMatrix& t) {
80     SkASSERT(!t.isIdentity());
81 
82     SkString tstr;
83     switch (t.getType()) {
84     case SkMatrix::kPerspective_Mask:
85         SkDebugf("Can't handle perspective matrices.");
86         break;
87     case SkMatrix::kTranslate_Mask:
88         tstr.printf("translate(%g %g)", t.getTranslateX(), t.getTranslateY());
89         break;
90     case SkMatrix::kScale_Mask:
91         tstr.printf("scale(%g %g)", t.getScaleX(), t.getScaleY());
92         break;
93     default:
94         // http://www.w3.org/TR/SVG/coords.html#TransformMatrixDefined
95         //    | a c e |
96         //    | b d f |
97         //    | 0 0 1 |
98         tstr.printf("matrix(%g %g %g %g %g %g)",
99                     t.getScaleX(),     t.getSkewY(),
100                     t.getSkewX(),      t.getScaleY(),
101                     t.getTranslateX(), t.getTranslateY());
102         break;
103     }
104 
105     return tstr;
106 }
107 
108 struct Resources {
Resources__anond794dccc0111::Resources109     Resources(const SkPaint& paint)
110         : fPaintServer(svg_color(paint.getColor())) {}
111 
112     SkString fPaintServer;
113     SkString fClip;
114 };
115 
116 class SVGTextBuilder : SkNoncopyable {
117 public:
SVGTextBuilder(const void * text,size_t byteLen,const SkPaint & paint,const SkPoint & offset,unsigned scalarsPerPos,const SkScalar pos[]=nullptr)118     SVGTextBuilder(const void* text, size_t byteLen, const SkPaint& paint, const SkPoint& offset,
119                    unsigned scalarsPerPos, const SkScalar pos[] = nullptr)
120         : fOffset(offset)
121         , fScalarsPerPos(scalarsPerPos)
122         , fPos(pos)
123         , fLastCharWasWhitespace(true) // start off in whitespace mode to strip all leading space
124     {
125         SkASSERT(scalarsPerPos <= 2);
126         SkASSERT(scalarsPerPos == 0 || SkToBool(pos));
127 
128         int count = paint.countText(text, byteLen);
129 
130         switch(paint.getTextEncoding()) {
131         case SkPaint::kGlyphID_TextEncoding: {
132             SkASSERT(count * sizeof(uint16_t) == byteLen);
133             SkAutoSTArray<64, SkUnichar> unichars(count);
134             paint.glyphsToUnichars((const uint16_t*)text, count, unichars.get());
135             for (int i = 0; i < count; ++i) {
136                 this->appendUnichar(unichars[i]);
137             }
138         } break;
139         case SkPaint::kUTF8_TextEncoding: {
140             const char* c8 = reinterpret_cast<const char*>(text);
141             for (int i = 0; i < count; ++i) {
142                 this->appendUnichar(SkUTF8_NextUnichar(&c8));
143             }
144             SkASSERT(reinterpret_cast<const char*>(text) + byteLen == c8);
145         } break;
146         case SkPaint::kUTF16_TextEncoding: {
147             const uint16_t* c16 = reinterpret_cast<const uint16_t*>(text);
148             for (int i = 0; i < count; ++i) {
149                 this->appendUnichar(SkUTF16_NextUnichar(&c16));
150             }
151             SkASSERT(SkIsAlign2(byteLen));
152             SkASSERT(reinterpret_cast<const uint16_t*>(text) + (byteLen / 2) == c16);
153         } break;
154         case SkPaint::kUTF32_TextEncoding: {
155             SkASSERT(count * sizeof(uint32_t) == byteLen);
156             const uint32_t* c32 = reinterpret_cast<const uint32_t*>(text);
157             for (int i = 0; i < count; ++i) {
158                 this->appendUnichar(c32[i]);
159             }
160         } break;
161         default:
162             SkFAIL("unknown text encoding");
163         }
164 
165         if (scalarsPerPos < 2) {
166             SkASSERT(fPosY.isEmpty());
167             fPosY.appendScalar(offset.y()); // DrawText or DrawPosTextH (fixed Y).
168         }
169 
170         if (scalarsPerPos < 1) {
171             SkASSERT(fPosX.isEmpty());
172             fPosX.appendScalar(offset.x()); // DrawText (X also fixed).
173         }
174     }
175 
text() const176     const SkString& text() const { return fText; }
posX() const177     const SkString& posX() const { return fPosX; }
posY() const178     const SkString& posY() const { return fPosY; }
179 
180 private:
appendUnichar(SkUnichar c)181     void appendUnichar(SkUnichar c) {
182         bool discardPos = false;
183         bool isWhitespace = false;
184 
185         switch(c) {
186         case ' ':
187         case '\t':
188             // consolidate whitespace to match SVG's xml:space=default munging
189             // (http://www.w3.org/TR/SVG/text.html#WhiteSpace)
190             if (fLastCharWasWhitespace) {
191                 discardPos = true;
192             } else {
193                 fText.appendUnichar(c);
194             }
195             isWhitespace = true;
196             break;
197         case '\0':
198             // SkPaint::glyphsToUnichars() returns \0 for inconvertible glyphs, but these
199             // are not legal XML characters (http://www.w3.org/TR/REC-xml/#charsets)
200             discardPos = true;
201             isWhitespace = fLastCharWasWhitespace; // preserve whitespace consolidation
202             break;
203         case '&':
204             fText.append("&amp;");
205             break;
206         case '"':
207             fText.append("&quot;");
208             break;
209         case '\'':
210             fText.append("&apos;");
211             break;
212         case '<':
213             fText.append("&lt;");
214             break;
215         case '>':
216             fText.append("&gt;");
217             break;
218         default:
219             fText.appendUnichar(c);
220             break;
221         }
222 
223         this->advancePos(discardPos);
224         fLastCharWasWhitespace = isWhitespace;
225     }
226 
advancePos(bool discard)227     void advancePos(bool discard) {
228         if (!discard && fScalarsPerPos > 0) {
229             fPosX.appendf("%.8g, ", fOffset.x() + fPos[0]);
230             if (fScalarsPerPos > 1) {
231                 SkASSERT(fScalarsPerPos == 2);
232                 fPosY.appendf("%.8g, ", fOffset.y() + fPos[1]);
233             }
234         }
235         fPos += fScalarsPerPos;
236     }
237 
238     const SkPoint&  fOffset;
239     const unsigned  fScalarsPerPos;
240     const SkScalar* fPos;
241 
242     SkString fText, fPosX, fPosY;
243     bool     fLastCharWasWhitespace;
244 };
245 
246 }
247 
248 // For now all this does is serve unique serial IDs, but it will eventually evolve to track
249 // and deduplicate resources.
250 class SkSVGDevice::ResourceBucket : ::SkNoncopyable {
251 public:
ResourceBucket()252     ResourceBucket() : fGradientCount(0), fClipCount(0), fPathCount(0), fImageCount(0) {}
253 
addLinearGradient()254     SkString addLinearGradient() {
255         return SkStringPrintf("gradient_%d", fGradientCount++);
256     }
257 
addClip()258     SkString addClip() {
259         return SkStringPrintf("clip_%d", fClipCount++);
260     }
261 
addPath()262     SkString addPath() {
263         return SkStringPrintf("path_%d", fPathCount++);
264     }
265 
addImage()266     SkString addImage() {
267         return SkStringPrintf("img_%d", fImageCount++);
268     }
269 
270 private:
271     uint32_t fGradientCount;
272     uint32_t fClipCount;
273     uint32_t fPathCount;
274     uint32_t fImageCount;
275 };
276 
277 struct SkSVGDevice::MxCp {
278     const SkMatrix* fMatrix;
279     const SkClipStack*  fClipStack;
280 
MxCpSkSVGDevice::MxCp281     MxCp(const SkMatrix* mx, const SkClipStack* cs) : fMatrix(mx), fClipStack(cs) {}
MxCpSkSVGDevice::MxCp282     MxCp(SkSVGDevice* device) : fMatrix(&device->ctm()), fClipStack(&device->cs()) {}
283 };
284 
285 class SkSVGDevice::AutoElement : ::SkNoncopyable {
286 public:
AutoElement(const char name[],SkXMLWriter * writer)287     AutoElement(const char name[], SkXMLWriter* writer)
288         : fWriter(writer)
289         , fResourceBucket(nullptr) {
290         fWriter->startElement(name);
291     }
292 
AutoElement(const char name[],SkXMLWriter * writer,ResourceBucket * bucket,const MxCp & mc,const SkPaint & paint)293     AutoElement(const char name[], SkXMLWriter* writer, ResourceBucket* bucket,
294                 const MxCp& mc, const SkPaint& paint)
295         : fWriter(writer)
296         , fResourceBucket(bucket) {
297 
298         Resources res = this->addResources(mc, paint);
299         if (!res.fClip.isEmpty()) {
300             // The clip is in device space. Apply it via a <g> wrapper to avoid local transform
301             // interference.
302             fClipGroup.reset(new AutoElement("g", fWriter));
303             fClipGroup->addAttribute("clip-path",res.fClip);
304         }
305 
306         fWriter->startElement(name);
307 
308         this->addPaint(paint, res);
309 
310         if (!mc.fMatrix->isIdentity()) {
311             this->addAttribute("transform", svg_transform(*mc.fMatrix));
312         }
313     }
314 
~AutoElement()315     ~AutoElement() {
316         fWriter->endElement();
317     }
318 
addAttribute(const char name[],const char val[])319     void addAttribute(const char name[], const char val[]) {
320         fWriter->addAttribute(name, val);
321     }
322 
addAttribute(const char name[],const SkString & val)323     void addAttribute(const char name[], const SkString& val) {
324         fWriter->addAttribute(name, val.c_str());
325     }
326 
addAttribute(const char name[],int32_t val)327     void addAttribute(const char name[], int32_t val) {
328         fWriter->addS32Attribute(name, val);
329     }
330 
addAttribute(const char name[],SkScalar val)331     void addAttribute(const char name[], SkScalar val) {
332         fWriter->addScalarAttribute(name, val);
333     }
334 
addText(const SkString & text)335     void addText(const SkString& text) {
336         fWriter->addText(text.c_str(), text.size());
337     }
338 
339     void addRectAttributes(const SkRect&);
340     void addPathAttributes(const SkPath&);
341     void addTextAttributes(const SkPaint&);
342 
343 private:
344     Resources addResources(const MxCp&, const SkPaint& paint);
345     void addClipResources(const MxCp&, Resources* resources);
346     void addShaderResources(const SkPaint& paint, Resources* resources);
347 
348     void addPaint(const SkPaint& paint, const Resources& resources);
349 
350     SkString addLinearGradientDef(const SkShader::GradientInfo& info, const SkShader* shader);
351 
352     SkXMLWriter*               fWriter;
353     ResourceBucket*            fResourceBucket;
354     std::unique_ptr<AutoElement> fClipGroup;
355 };
356 
addPaint(const SkPaint & paint,const Resources & resources)357 void SkSVGDevice::AutoElement::addPaint(const SkPaint& paint, const Resources& resources) {
358     SkPaint::Style style = paint.getStyle();
359     if (style == SkPaint::kFill_Style || style == SkPaint::kStrokeAndFill_Style) {
360         this->addAttribute("fill", resources.fPaintServer);
361 
362         if (SK_AlphaOPAQUE != SkColorGetA(paint.getColor())) {
363             this->addAttribute("fill-opacity", svg_opacity(paint.getColor()));
364         }
365     } else {
366         SkASSERT(style == SkPaint::kStroke_Style);
367         this->addAttribute("fill", "none");
368     }
369 
370     if (style == SkPaint::kStroke_Style || style == SkPaint::kStrokeAndFill_Style) {
371         this->addAttribute("stroke", resources.fPaintServer);
372 
373         SkScalar strokeWidth = paint.getStrokeWidth();
374         if (strokeWidth == 0) {
375             // Hairline stroke
376             strokeWidth = 1;
377             this->addAttribute("vector-effect", "non-scaling-stroke");
378         }
379         this->addAttribute("stroke-width", strokeWidth);
380 
381         if (const char* cap = svg_cap(paint.getStrokeCap())) {
382             this->addAttribute("stroke-linecap", cap);
383         }
384 
385         if (const char* join = svg_join(paint.getStrokeJoin())) {
386             this->addAttribute("stroke-linejoin", join);
387         }
388 
389         if (paint.getStrokeJoin() == SkPaint::kMiter_Join) {
390             this->addAttribute("stroke-miterlimit", paint.getStrokeMiter());
391         }
392 
393         if (SK_AlphaOPAQUE != SkColorGetA(paint.getColor())) {
394             this->addAttribute("stroke-opacity", svg_opacity(paint.getColor()));
395         }
396     } else {
397         SkASSERT(style == SkPaint::kFill_Style);
398         this->addAttribute("stroke", "none");
399     }
400 }
401 
addResources(const MxCp & mc,const SkPaint & paint)402 Resources SkSVGDevice::AutoElement::addResources(const MxCp& mc, const SkPaint& paint) {
403     Resources resources(paint);
404 
405     // FIXME: this is a weak heuristic and we end up with LOTS of redundant clips.
406     bool hasClip   = !mc.fClipStack->isWideOpen();
407     bool hasShader = SkToBool(paint.getShader());
408 
409     if (hasClip || hasShader) {
410         AutoElement defs("defs", fWriter);
411 
412         if (hasClip) {
413             this->addClipResources(mc, &resources);
414         }
415 
416         if (hasShader) {
417             this->addShaderResources(paint, &resources);
418         }
419     }
420 
421     return resources;
422 }
423 
addShaderResources(const SkPaint & paint,Resources * resources)424 void SkSVGDevice::AutoElement::addShaderResources(const SkPaint& paint, Resources* resources) {
425     const SkShader* shader = paint.getShader();
426     SkASSERT(SkToBool(shader));
427 
428     SkShader::GradientInfo grInfo;
429     grInfo.fColorCount = 0;
430     if (SkShader::kLinear_GradientType != shader->asAGradient(&grInfo)) {
431         // TODO: non-linear gradient support
432         SkDebugf("unsupported shader type\n");
433         return;
434     }
435 
436     SkAutoSTArray<16, SkColor>  grColors(grInfo.fColorCount);
437     SkAutoSTArray<16, SkScalar> grOffsets(grInfo.fColorCount);
438     grInfo.fColors = grColors.get();
439     grInfo.fColorOffsets = grOffsets.get();
440 
441     // One more call to get the actual colors/offsets.
442     shader->asAGradient(&grInfo);
443     SkASSERT(grInfo.fColorCount <= grColors.count());
444     SkASSERT(grInfo.fColorCount <= grOffsets.count());
445 
446     resources->fPaintServer.printf("url(#%s)", addLinearGradientDef(grInfo, shader).c_str());
447 }
448 
addClipResources(const MxCp & mc,Resources * resources)449 void SkSVGDevice::AutoElement::addClipResources(const MxCp& mc, Resources* resources) {
450     SkASSERT(!mc.fClipStack->isWideOpen());
451 
452     SkPath clipPath;
453     (void) mc.fClipStack->asPath(&clipPath);
454 
455     SkString clipID = fResourceBucket->addClip();
456     const char* clipRule = clipPath.getFillType() == SkPath::kEvenOdd_FillType ?
457                            "evenodd" : "nonzero";
458     {
459         // clipPath is in device space, but since we're only pushing transform attributes
460         // to the leaf nodes, so are all our elements => SVG userSpaceOnUse == device space.
461         AutoElement clipPathElement("clipPath", fWriter);
462         clipPathElement.addAttribute("id", clipID);
463 
464         SkRect clipRect = SkRect::MakeEmpty();
465         if (clipPath.isEmpty() || clipPath.isRect(&clipRect)) {
466             AutoElement rectElement("rect", fWriter);
467             rectElement.addRectAttributes(clipRect);
468             rectElement.addAttribute("clip-rule", clipRule);
469         } else {
470             AutoElement pathElement("path", fWriter);
471             pathElement.addPathAttributes(clipPath);
472             pathElement.addAttribute("clip-rule", clipRule);
473         }
474     }
475 
476     resources->fClip.printf("url(#%s)", clipID.c_str());
477 }
478 
addLinearGradientDef(const SkShader::GradientInfo & info,const SkShader * shader)479 SkString SkSVGDevice::AutoElement::addLinearGradientDef(const SkShader::GradientInfo& info,
480                                                         const SkShader* shader) {
481     SkASSERT(fResourceBucket);
482     SkString id = fResourceBucket->addLinearGradient();
483 
484     {
485         AutoElement gradient("linearGradient", fWriter);
486 
487         gradient.addAttribute("id", id);
488         gradient.addAttribute("gradientUnits", "userSpaceOnUse");
489         gradient.addAttribute("x1", info.fPoint[0].x());
490         gradient.addAttribute("y1", info.fPoint[0].y());
491         gradient.addAttribute("x2", info.fPoint[1].x());
492         gradient.addAttribute("y2", info.fPoint[1].y());
493 
494         if (!shader->getLocalMatrix().isIdentity()) {
495             this->addAttribute("gradientTransform", svg_transform(shader->getLocalMatrix()));
496         }
497 
498         SkASSERT(info.fColorCount >= 2);
499         for (int i = 0; i < info.fColorCount; ++i) {
500             SkColor color = info.fColors[i];
501             SkString colorStr(svg_color(color));
502 
503             {
504                 AutoElement stop("stop", fWriter);
505                 stop.addAttribute("offset", info.fColorOffsets[i]);
506                 stop.addAttribute("stop-color", colorStr.c_str());
507 
508                 if (SK_AlphaOPAQUE != SkColorGetA(color)) {
509                     stop.addAttribute("stop-opacity", svg_opacity(color));
510                 }
511             }
512         }
513     }
514 
515     return id;
516 }
517 
addRectAttributes(const SkRect & rect)518 void SkSVGDevice::AutoElement::addRectAttributes(const SkRect& rect) {
519     // x, y default to 0
520     if (rect.x() != 0) {
521         this->addAttribute("x", rect.x());
522     }
523     if (rect.y() != 0) {
524         this->addAttribute("y", rect.y());
525     }
526 
527     this->addAttribute("width", rect.width());
528     this->addAttribute("height", rect.height());
529 }
530 
addPathAttributes(const SkPath & path)531 void SkSVGDevice::AutoElement::addPathAttributes(const SkPath& path) {
532     SkString pathData;
533     SkParsePath::ToSVGString(path, &pathData);
534     this->addAttribute("d", pathData);
535 }
536 
addTextAttributes(const SkPaint & paint)537 void SkSVGDevice::AutoElement::addTextAttributes(const SkPaint& paint) {
538     this->addAttribute("font-size", paint.getTextSize());
539 
540     if (const char* textAlign = svg_text_align(paint.getTextAlign())) {
541         this->addAttribute("text-anchor", textAlign);
542     }
543 
544     SkString familyName;
545     SkTHashSet<SkString> familySet;
546     sk_sp<SkTypeface> tface(paint.getTypeface() ? paint.refTypeface() : SkTypeface::MakeDefault());
547 
548     SkASSERT(tface);
549     SkTypeface::Style style = tface->style();
550     if (style & SkTypeface::kItalic) {
551         this->addAttribute("font-style", "italic");
552     }
553     if (style & SkTypeface::kBold) {
554         this->addAttribute("font-weight", "bold");
555     }
556 
557     sk_sp<SkTypeface::LocalizedStrings> familyNameIter(tface->createFamilyNameIterator());
558     SkTypeface::LocalizedString familyString;
559     if (familyNameIter) {
560         while (familyNameIter->next(&familyString)) {
561             if (familySet.contains(familyString.fString)) {
562                 continue;
563             }
564             familySet.add(familyString.fString);
565             familyName.appendf((familyName.isEmpty() ? "%s" : ", %s"), familyString.fString.c_str());
566         }
567     }
568     if (!familyName.isEmpty()) {
569         this->addAttribute("font-family", familyName);
570     }
571 }
572 
Create(const SkISize & size,SkXMLWriter * writer)573 SkBaseDevice* SkSVGDevice::Create(const SkISize& size, SkXMLWriter* writer) {
574     if (!writer) {
575         return nullptr;
576     }
577 
578     return new SkSVGDevice(size, writer);
579 }
580 
SkSVGDevice(const SkISize & size,SkXMLWriter * writer)581 SkSVGDevice::SkSVGDevice(const SkISize& size, SkXMLWriter* writer)
582     : INHERITED(SkImageInfo::MakeUnknown(size.fWidth, size.fHeight),
583                 SkSurfaceProps(0, kUnknown_SkPixelGeometry))
584     , fWriter(writer)
585     , fResourceBucket(new ResourceBucket)
586 {
587     SkASSERT(writer);
588 
589     fWriter->writeHeader();
590 
591     // The root <svg> tag gets closed by the destructor.
592     fRootElement.reset(new AutoElement("svg", fWriter));
593 
594     fRootElement->addAttribute("xmlns", "http://www.w3.org/2000/svg");
595     fRootElement->addAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
596     fRootElement->addAttribute("width", size.width());
597     fRootElement->addAttribute("height", size.height());
598 }
599 
~SkSVGDevice()600 SkSVGDevice::~SkSVGDevice() {
601 }
602 
drawPaint(const SkPaint & paint)603 void SkSVGDevice::drawPaint(const SkPaint& paint) {
604     AutoElement rect("rect", fWriter, fResourceBucket.get(), MxCp(this), paint);
605     rect.addRectAttributes(SkRect::MakeWH(SkIntToScalar(this->width()),
606                                           SkIntToScalar(this->height())));
607 }
608 
drawPoints(SkCanvas::PointMode mode,size_t count,const SkPoint pts[],const SkPaint & paint)609 void SkSVGDevice::drawPoints(SkCanvas::PointMode mode, size_t count,
610                              const SkPoint pts[], const SkPaint& paint) {
611     SkPath path;
612 
613     switch (mode) {
614             // todo
615         case SkCanvas::kPoints_PointMode:
616             SkDebugf("unsupported operation: drawPoints(kPoints_PointMode)\n");
617             break;
618         case SkCanvas::kLines_PointMode:
619             count -= 1;
620             for (size_t i = 0; i < count; i += 2) {
621                 path.rewind();
622                 path.moveTo(pts[i]);
623                 path.lineTo(pts[i+1]);
624                 AutoElement elem("path", fWriter, fResourceBucket.get(), MxCp(this), paint);
625                 elem.addPathAttributes(path);
626             }
627             break;
628         case SkCanvas::kPolygon_PointMode:
629             if (count > 1) {
630                 path.addPoly(pts, SkToInt(count), false);
631                 path.moveTo(pts[0]);
632                 AutoElement elem("path", fWriter, fResourceBucket.get(), MxCp(this), paint);
633                 elem.addPathAttributes(path);
634             }
635             break;
636     }
637 }
638 
drawRect(const SkRect & r,const SkPaint & paint)639 void SkSVGDevice::drawRect(const SkRect& r, const SkPaint& paint) {
640     AutoElement rect("rect", fWriter, fResourceBucket.get(), MxCp(this), paint);
641     rect.addRectAttributes(r);
642 }
643 
drawOval(const SkRect & oval,const SkPaint & paint)644 void SkSVGDevice::drawOval(const SkRect& oval, const SkPaint& paint) {
645     AutoElement ellipse("ellipse", fWriter, fResourceBucket.get(), MxCp(this), paint);
646     ellipse.addAttribute("cx", oval.centerX());
647     ellipse.addAttribute("cy", oval.centerY());
648     ellipse.addAttribute("rx", oval.width() / 2);
649     ellipse.addAttribute("ry", oval.height() / 2);
650 }
651 
drawRRect(const SkRRect & rr,const SkPaint & paint)652 void SkSVGDevice::drawRRect(const SkRRect& rr, const SkPaint& paint) {
653     SkPath path;
654     path.addRRect(rr);
655 
656     AutoElement elem("path", fWriter, fResourceBucket.get(), MxCp(this), paint);
657     elem.addPathAttributes(path);
658 }
659 
drawPath(const SkPath & path,const SkPaint & paint,const SkMatrix * prePathMatrix,bool pathIsMutable)660 void SkSVGDevice::drawPath(const SkPath& path, const SkPaint& paint,
661                            const SkMatrix* prePathMatrix, bool pathIsMutable) {
662     AutoElement elem("path", fWriter, fResourceBucket.get(), MxCp(this), paint);
663     elem.addPathAttributes(path);
664 
665     // TODO: inverse fill types?
666     if (path.getFillType() == SkPath::kEvenOdd_FillType) {
667         elem.addAttribute("fill-rule", "evenodd");
668     }
669 }
670 
encode(const SkBitmap & src)671 static sk_sp<SkData> encode(const SkBitmap& src) {
672     SkDynamicMemoryWStream buf;
673     return SkEncodeImage(&buf, src, SkEncodedImageFormat::kPNG, 80) ? buf.detachAsData() : nullptr;
674 }
675 
drawBitmapCommon(const MxCp & mc,const SkBitmap & bm,const SkPaint & paint)676 void SkSVGDevice::drawBitmapCommon(const MxCp& mc, const SkBitmap& bm, const SkPaint& paint) {
677     sk_sp<SkData> pngData = encode(bm);
678     if (!pngData) {
679         return;
680     }
681 
682     size_t b64Size = SkBase64::Encode(pngData->data(), pngData->size(), nullptr);
683     SkAutoTMalloc<char> b64Data(b64Size);
684     SkBase64::Encode(pngData->data(), pngData->size(), b64Data.get());
685 
686     SkString svgImageData("data:image/png;base64,");
687     svgImageData.append(b64Data.get(), b64Size);
688 
689     SkString imageID = fResourceBucket->addImage();
690     {
691         AutoElement defs("defs", fWriter);
692         {
693             AutoElement image("image", fWriter);
694             image.addAttribute("id", imageID);
695             image.addAttribute("width", bm.width());
696             image.addAttribute("height", bm.height());
697             image.addAttribute("xlink:href", svgImageData);
698         }
699     }
700 
701     {
702         AutoElement imageUse("use", fWriter, fResourceBucket.get(), mc, paint);
703         imageUse.addAttribute("xlink:href", SkStringPrintf("#%s", imageID.c_str()));
704     }
705 }
706 
drawBitmap(const SkBitmap & bitmap,SkScalar x,SkScalar y,const SkPaint & paint)707 void SkSVGDevice::drawBitmap(const SkBitmap& bitmap, SkScalar x, SkScalar y,
708                              const SkPaint& paint) {
709     MxCp mc(this);
710     SkMatrix adjustedMatrix = *mc.fMatrix;
711     adjustedMatrix.preTranslate(x, y);
712     mc.fMatrix = &adjustedMatrix;
713 
714     drawBitmapCommon(mc, bitmap, paint);
715 }
716 
drawSprite(const SkBitmap & bitmap,int x,int y,const SkPaint & paint)717 void SkSVGDevice::drawSprite(const SkBitmap& bitmap,
718                              int x, int y, const SkPaint& paint) {
719     MxCp mc(this);
720     SkMatrix adjustedMatrix = *mc.fMatrix;
721     adjustedMatrix.preTranslate(SkIntToScalar(x), SkIntToScalar(y));
722     mc.fMatrix = &adjustedMatrix;
723 
724     drawBitmapCommon(mc, bitmap, paint);
725 }
726 
drawBitmapRect(const SkBitmap & bm,const SkRect * srcOrNull,const SkRect & dst,const SkPaint & paint,SkCanvas::SrcRectConstraint)727 void SkSVGDevice::drawBitmapRect(const SkBitmap& bm, const SkRect* srcOrNull,
728                                  const SkRect& dst, const SkPaint& paint,
729                                  SkCanvas::SrcRectConstraint) {
730     SkClipStack* cs = &this->cs();
731     SkClipStack::AutoRestore ar(cs, false);
732     if (srcOrNull && *srcOrNull != SkRect::Make(bm.bounds())) {
733         cs->save();
734         cs->clipRect(dst, this->ctm(), kIntersect_SkClipOp, paint.isAntiAlias());
735     }
736 
737     SkMatrix adjustedMatrix;
738     adjustedMatrix.setRectToRect(srcOrNull ? *srcOrNull : SkRect::Make(bm.bounds()),
739                                  dst,
740                                  SkMatrix::kFill_ScaleToFit);
741     adjustedMatrix.postConcat(this->ctm());
742 
743     drawBitmapCommon(MxCp(&adjustedMatrix, cs), bm, paint);
744 }
745 
drawText(const void * text,size_t len,SkScalar x,SkScalar y,const SkPaint & paint)746 void SkSVGDevice::drawText(const void* text, size_t len,
747                            SkScalar x, SkScalar y, const SkPaint& paint) {
748     AutoElement elem("text", fWriter, fResourceBucket.get(), MxCp(this), paint);
749     elem.addTextAttributes(paint);
750 
751     SVGTextBuilder builder(text, len, paint, SkPoint::Make(x, y), 0);
752     elem.addAttribute("x", builder.posX());
753     elem.addAttribute("y", builder.posY());
754     elem.addText(builder.text());
755 }
756 
drawPosText(const void * text,size_t len,const SkScalar pos[],int scalarsPerPos,const SkPoint & offset,const SkPaint & paint)757 void SkSVGDevice::drawPosText(const void* text, size_t len,
758                               const SkScalar pos[], int scalarsPerPos, const SkPoint& offset,
759                               const SkPaint& paint) {
760     SkASSERT(scalarsPerPos == 1 || scalarsPerPos == 2);
761 
762     AutoElement elem("text", fWriter, fResourceBucket.get(), MxCp(this), paint);
763     elem.addTextAttributes(paint);
764 
765     SVGTextBuilder builder(text, len, paint, offset, scalarsPerPos, pos);
766     elem.addAttribute("x", builder.posX());
767     elem.addAttribute("y", builder.posY());
768     elem.addText(builder.text());
769 }
770 
drawTextOnPath(const void * text,size_t len,const SkPath & path,const SkMatrix * matrix,const SkPaint & paint)771 void SkSVGDevice::drawTextOnPath(const void* text, size_t len, const SkPath& path,
772                                  const SkMatrix* matrix, const SkPaint& paint) {
773     SkString pathID = fResourceBucket->addPath();
774 
775     {
776         AutoElement defs("defs", fWriter);
777         AutoElement pathElement("path", fWriter);
778         pathElement.addAttribute("id", pathID);
779         pathElement.addPathAttributes(path);
780 
781     }
782 
783     {
784         AutoElement textElement("text", fWriter);
785         textElement.addTextAttributes(paint);
786 
787         if (matrix && !matrix->isIdentity()) {
788             textElement.addAttribute("transform", svg_transform(*matrix));
789         }
790 
791         {
792             AutoElement textPathElement("textPath", fWriter);
793             textPathElement.addAttribute("xlink:href", SkStringPrintf("#%s", pathID.c_str()));
794 
795             if (paint.getTextAlign() != SkPaint::kLeft_Align) {
796                 SkASSERT(paint.getTextAlign() == SkPaint::kCenter_Align ||
797                          paint.getTextAlign() == SkPaint::kRight_Align);
798                 textPathElement.addAttribute("startOffset",
799                     paint.getTextAlign() == SkPaint::kCenter_Align ? "50%" : "100%");
800             }
801 
802             SVGTextBuilder builder(text, len, paint, SkPoint::Make(0, 0), 0);
803             textPathElement.addText(builder.text());
804         }
805     }
806 }
807 
drawVertices(const SkVertices *,SkBlendMode,const SkPaint &)808 void SkSVGDevice::drawVertices(const SkVertices*, SkBlendMode, const SkPaint&) {
809     // todo
810     SkDebugf("unsupported operation: drawVertices()\n");
811 }
812 
drawDevice(SkBaseDevice *,int x,int y,const SkPaint &)813 void SkSVGDevice::drawDevice(SkBaseDevice*, int x, int y,
814                              const SkPaint&) {
815     // todo
816     SkDebugf("unsupported operation: drawDevice()\n");
817 }
818