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