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
25namespace {
26
27static SkString svg_color(SkColor color) {
28    return SkStringPrintf("rgb(%u,%u,%u)",
29                          SkColorGetR(color),
30                          SkColorGetG(color),
31                          SkColorGetB(color));
32}
33
34static SkScalar svg_opacity(SkColor color) {
35    return SkIntToScalar(SkColorGetA(color)) / SK_AlphaOPAQUE;
36}
37
38// Keep in sync with SkPaint::Cap
39static const char* cap_map[]  = {
40    NULL,    // kButt_Cap (default)
41    "round", // kRound_Cap
42    "square" // kSquare_Cap
43};
44SK_COMPILE_ASSERT(SK_ARRAY_COUNT(cap_map) == SkPaint::kCapCount, missing_cap_map_entry);
45
46static 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
52static const char* join_map[] = {
53    NULL,    // kMiter_Join (default)
54    "round", // kRound_Join
55    "bevel"  // kBevel_Join
56};
57SK_COMPILE_ASSERT(SK_ARRAY_COUNT(join_map) == SkPaint::kJoinCount, missing_join_map_entry);
58
59static 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
65static const char* text_align_map[] = {
66    NULL,     // kLeft_Align (default)
67    "middle", // kCenter_Align
68    "end"     // kRight_Align
69};
70SK_COMPILE_ASSERT(SK_ARRAY_COUNT(text_align_map) == SkPaint::kAlignCount,
71                  missing_text_align_map_entry);
72static 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
77static 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
106struct Resources {
107    Resources(const SkPaint& paint)
108        : fPaintServer(svg_color(paint.getColor())) {}
109
110    SkString fPaintServer;
111    SkString fClip;
112};
113
114class SVGTextBuilder : SkNoncopyable {
115public:
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
174    const SkString& text() const { return fText; }
175    const SkString& posX() const { return fPosX; }
176    const SkString& posY() const { return fPosY; }
177
178private:
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
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.
248class SkSVGDevice::ResourceBucket : ::SkNoncopyable {
249public:
250    ResourceBucket() : fGradientCount(0), fClipCount(0), fPathCount(0), fImageCount(0) {}
251
252    SkString addLinearGradient() {
253        return SkStringPrintf("gradient_%d", fGradientCount++);
254    }
255
256    SkString addClip() {
257        return SkStringPrintf("clip_%d", fClipCount++);
258    }
259
260    SkString addPath() {
261        return SkStringPrintf("path_%d", fPathCount++);
262    }
263
264    SkString addImage() {
265        return SkStringPrintf("img_%d", fImageCount++);
266    }
267
268private:
269    uint32_t fGradientCount;
270    uint32_t fClipCount;
271    uint32_t fPathCount;
272    uint32_t fImageCount;
273};
274
275class SkSVGDevice::AutoElement : ::SkNoncopyable {
276public:
277    AutoElement(const char name[], SkXMLWriter* writer)
278        : fWriter(writer)
279        , fResourceBucket(NULL) {
280        fWriter->startElement(name);
281    }
282
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
305    ~AutoElement() {
306        fWriter->endElement();
307    }
308
309    void addAttribute(const char name[], const char val[]) {
310        fWriter->addAttribute(name, val);
311    }
312
313    void addAttribute(const char name[], const SkString& val) {
314        fWriter->addAttribute(name, val.c_str());
315    }
316
317    void addAttribute(const char name[], int32_t val) {
318        fWriter->addS32Attribute(name, val);
319    }
320
321    void addAttribute(const char name[], SkScalar val) {
322        fWriter->addScalarAttribute(name, val);
323    }
324
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
333private:
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
347void 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
392Resources 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
414void 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
439void 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
469SkString 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
508void 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
521void SkSVGDevice::AutoElement::addPathAttributes(const SkPath& path) {
522    SkString pathData;
523    SkParsePath::ToSVGString(path, &pathData);
524    this->addAttribute("d", pathData);
525}
526
527void 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
563SkBaseDevice* SkSVGDevice::Create(const SkISize& size, SkXMLWriter* writer) {
564    if (!writer) {
565        return NULL;
566    }
567
568    return SkNEW_ARGS(SkSVGDevice, (size, writer));
569}
570
571SkSVGDevice::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
589SkSVGDevice::~SkSVGDevice() {
590}
591
592SkImageInfo SkSVGDevice::imageInfo() const {
593    return fLegacyBitmap.info();
594}
595
596const SkBitmap& SkSVGDevice::onAccessBitmap() {
597    return fLegacyBitmap;
598}
599
600void 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
606void 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
636void 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
641void 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
649void 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
657void 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
663void 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
696void 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
706void 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
716void 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
741void 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
752void 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
766void 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
803void 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
812void SkSVGDevice::drawDevice(const SkDraw&, SkBaseDevice*, int x, int y,
813                             const SkPaint&) {
814    // todo
815    SkDebugf("unsupported operation: drawDevice()\n");
816}
817