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
26namespace {
27
28static SkString svg_color(SkColor color) {
29    return SkStringPrintf("rgb(%u,%u,%u)",
30                          SkColorGetR(color),
31                          SkColorGetG(color),
32                          SkColorGetB(color));
33}
34
35static SkScalar svg_opacity(SkColor color) {
36    return SkIntToScalar(SkColorGetA(color)) / SK_AlphaOPAQUE;
37}
38
39// Keep in sync with SkPaint::Cap
40static const char* cap_map[]  = {
41    nullptr,    // kButt_Cap (default)
42    "round", // kRound_Cap
43    "square" // kSquare_Cap
44};
45static_assert(SK_ARRAY_COUNT(cap_map) == SkPaint::kCapCount, "missing_cap_map_entry");
46
47static 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
53static const char* join_map[] = {
54    nullptr,    // kMiter_Join (default)
55    "round", // kRound_Join
56    "bevel"  // kBevel_Join
57};
58static_assert(SK_ARRAY_COUNT(join_map) == SkPaint::kJoinCount, "missing_join_map_entry");
59
60static 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
66static const char* text_align_map[] = {
67    nullptr,     // kLeft_Align (default)
68    "middle", // kCenter_Align
69    "end"     // kRight_Align
70};
71static_assert(SK_ARRAY_COUNT(text_align_map) == SkPaint::kAlignCount,
72              "missing_text_align_map_entry");
73static 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
78static 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
107struct Resources {
108    Resources(const SkPaint& paint)
109        : fPaintServer(svg_color(paint.getColor())) {}
110
111    SkString fPaintServer;
112    SkString fClip;
113};
114
115class SVGTextBuilder : SkNoncopyable {
116public:
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
175    const SkString& text() const { return fText; }
176    const SkString& posX() const { return fPosX; }
177    const SkString& posY() const { return fPosY; }
178
179private:
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
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.
249class SkSVGDevice::ResourceBucket : ::SkNoncopyable {
250public:
251    ResourceBucket() : fGradientCount(0), fClipCount(0), fPathCount(0), fImageCount(0) {}
252
253    SkString addLinearGradient() {
254        return SkStringPrintf("gradient_%d", fGradientCount++);
255    }
256
257    SkString addClip() {
258        return SkStringPrintf("clip_%d", fClipCount++);
259    }
260
261    SkString addPath() {
262        return SkStringPrintf("path_%d", fPathCount++);
263    }
264
265    SkString addImage() {
266        return SkStringPrintf("img_%d", fImageCount++);
267    }
268
269private:
270    uint32_t fGradientCount;
271    uint32_t fClipCount;
272    uint32_t fPathCount;
273    uint32_t fImageCount;
274};
275
276class SkSVGDevice::AutoElement : ::SkNoncopyable {
277public:
278    AutoElement(const char name[], SkXMLWriter* writer)
279        : fWriter(writer)
280        , fResourceBucket(nullptr) {
281        fWriter->startElement(name);
282    }
283
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
306    ~AutoElement() {
307        fWriter->endElement();
308    }
309
310    void addAttribute(const char name[], const char val[]) {
311        fWriter->addAttribute(name, val);
312    }
313
314    void addAttribute(const char name[], const SkString& val) {
315        fWriter->addAttribute(name, val.c_str());
316    }
317
318    void addAttribute(const char name[], int32_t val) {
319        fWriter->addS32Attribute(name, val);
320    }
321
322    void addAttribute(const char name[], SkScalar val) {
323        fWriter->addScalarAttribute(name, val);
324    }
325
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
334private:
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
348void 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
393Resources 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
415void 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
440void 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
470SkString 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
509void 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
522void SkSVGDevice::AutoElement::addPathAttributes(const SkPath& path) {
523    SkString pathData;
524    SkParsePath::ToSVGString(path, &pathData);
525    this->addAttribute("d", pathData);
526}
527
528void 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
564SkBaseDevice* SkSVGDevice::Create(const SkISize& size, SkXMLWriter* writer) {
565    if (!writer) {
566        return nullptr;
567    }
568
569    return new SkSVGDevice(size, writer);
570}
571
572SkSVGDevice::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
591SkSVGDevice::~SkSVGDevice() {
592}
593
594SkImageInfo SkSVGDevice::imageInfo() const {
595    return fLegacyBitmap.info();
596}
597
598const SkBitmap& SkSVGDevice::onAccessBitmap() {
599    return fLegacyBitmap;
600}
601
602void 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
608void 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
638void 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
643void 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
651void 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
659void 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
665void 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
698void 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
708void 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
718void 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
743void 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
754void 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
768void 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
805void 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
814void SkSVGDevice::drawDevice(const SkDraw&, SkBaseDevice*, int x, int y,
815                             const SkPaint&) {
816    // todo
817    SkDebugf("unsupported operation: drawDevice()\n");
818}
819