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