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