Home | History | Annotate | Download | only in svg
      1 /*
      2  * Copyright 2015 Google Inc.
      3  *
      4  * Use of this source code is governed by a BSD-style license that can be
      5  * found in the LICENSE file.
      6  */
      7 
      8 #include "SkSVGDevice.h"
      9 
     10 #include "SkBase64.h"
     11 #include "SkBitmap.h"
     12 #include "SkChecksum.h"
     13 #include "SkClipStack.h"
     14 #include "SkData.h"
     15 #include "SkDraw.h"
     16 #include "SkImageEncoder.h"
     17 #include "SkPaint.h"
     18 #include "SkParsePath.h"
     19 #include "SkShader.h"
     20 #include "SkStream.h"
     21 #include "SkTHash.h"
     22 #include "SkTypeface.h"
     23 #include "SkUtils.h"
     24 #include "SkXMLWriter.h"
     25 
     26 namespace {
     27 
     28 static SkString svg_color(SkColor color) {
     29     return SkStringPrintf("rgb(%u,%u,%u)",
     30                           SkColorGetR(color),
     31                           SkColorGetG(color),
     32                           SkColorGetB(color));
     33 }
     34 
     35 static SkScalar svg_opacity(SkColor color) {
     36     return SkIntToScalar(SkColorGetA(color)) / SK_AlphaOPAQUE;
     37 }
     38 
     39 // Keep in sync with SkPaint::Cap
     40 static const char* cap_map[]  = {
     41     nullptr,    // kButt_Cap (default)
     42     "round", // kRound_Cap
     43     "square" // kSquare_Cap
     44 };
     45 static_assert(SK_ARRAY_COUNT(cap_map) == SkPaint::kCapCount, "missing_cap_map_entry");
     46 
     47 static const char* svg_cap(SkPaint::Cap cap) {
     48     SkASSERT(cap < SK_ARRAY_COUNT(cap_map));
     49     return cap_map[cap];
     50 }
     51 
     52 // Keep in sync with SkPaint::Join
     53 static const char* join_map[] = {
     54     nullptr,    // kMiter_Join (default)
     55     "round", // kRound_Join
     56     "bevel"  // kBevel_Join
     57 };
     58 static_assert(SK_ARRAY_COUNT(join_map) == SkPaint::kJoinCount, "missing_join_map_entry");
     59 
     60 static const char* svg_join(SkPaint::Join join) {
     61     SkASSERT(join < SK_ARRAY_COUNT(join_map));
     62     return join_map[join];
     63 }
     64 
     65 // Keep in sync with SkPaint::Align
     66 static const char* text_align_map[] = {
     67     nullptr,     // kLeft_Align (default)
     68     "middle", // kCenter_Align
     69     "end"     // kRight_Align
     70 };
     71 static_assert(SK_ARRAY_COUNT(text_align_map) == SkPaint::kAlignCount,
     72               "missing_text_align_map_entry");
     73 static const char* svg_text_align(SkPaint::Align align) {
     74     SkASSERT(align < SK_ARRAY_COUNT(text_align_map));
     75     return text_align_map[align];
     76 }
     77 
     78 static SkString svg_transform(const SkMatrix& t) {
     79     SkASSERT(!t.isIdentity());
     80 
     81     SkString tstr;
     82     switch (t.getType()) {
     83     case SkMatrix::kPerspective_Mask:
     84         SkDebugf("Can't handle perspective matrices.");
     85         break;
     86     case SkMatrix::kTranslate_Mask:
     87         tstr.printf("translate(%g %g)", t.getTranslateX(), t.getTranslateY());
     88         break;
     89     case SkMatrix::kScale_Mask:
     90         tstr.printf("scale(%g %g)", t.getScaleX(), t.getScaleY());
     91         break;
     92     default:
     93         // http://www.w3.org/TR/SVG/coords.html#TransformMatrixDefined
     94         //    | a c e |
     95         //    | b d f |
     96         //    | 0 0 1 |
     97         tstr.printf("matrix(%g %g %g %g %g %g)",
     98                     t.getScaleX(),     t.getSkewY(),
     99                     t.getSkewX(),      t.getScaleY(),
    100                     t.getTranslateX(), t.getTranslateY());
    101         break;
    102     }
    103 
    104     return tstr;
    105 }
    106 
    107 struct Resources {
    108     Resources(const SkPaint& paint)
    109         : fPaintServer(svg_color(paint.getColor())) {}
    110 
    111     SkString fPaintServer;
    112     SkString fClip;
    113 };
    114 
    115 class SVGTextBuilder : SkNoncopyable {
    116 public:
    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 
    179 private:
    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.
    249 class SkSVGDevice::ResourceBucket : ::SkNoncopyable {
    250 public:
    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 
    269 private:
    270     uint32_t fGradientCount;
    271     uint32_t fClipCount;
    272     uint32_t fPathCount;
    273     uint32_t fImageCount;
    274 };
    275 
    276 class SkSVGDevice::AutoElement : ::SkNoncopyable {
    277 public:
    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 
    334 private:
    335     Resources addResources(const SkDraw& draw, const SkPaint& paint);
    336     void addClipResources(const SkDraw& draw, Resources* resources);
    337     void addShaderResources(const SkPaint& paint, Resources* resources);
    338 
    339     void addPaint(const SkPaint& paint, const Resources& resources);
    340 
    341     SkString addLinearGradientDef(const SkShader::GradientInfo& info, const SkShader* shader);
    342 
    343     SkXMLWriter*               fWriter;
    344     ResourceBucket*            fResourceBucket;
    345     SkAutoTDelete<AutoElement> fClipGroup;
    346 };
    347 
    348 void SkSVGDevice::AutoElement::addPaint(const SkPaint& paint, const Resources& resources) {
    349     SkPaint::Style style = paint.getStyle();
    350     if (style == SkPaint::kFill_Style || style == SkPaint::kStrokeAndFill_Style) {
    351         this->addAttribute("fill", resources.fPaintServer);
    352 
    353         if (SK_AlphaOPAQUE != SkColorGetA(paint.getColor())) {
    354             this->addAttribute("fill-opacity", svg_opacity(paint.getColor()));
    355         }
    356     } else {
    357         SkASSERT(style == SkPaint::kStroke_Style);
    358         this->addAttribute("fill", "none");
    359     }
    360 
    361     if (style == SkPaint::kStroke_Style || style == SkPaint::kStrokeAndFill_Style) {
    362         this->addAttribute("stroke", resources.fPaintServer);
    363 
    364         SkScalar strokeWidth = paint.getStrokeWidth();
    365         if (strokeWidth == 0) {
    366             // Hairline stroke
    367             strokeWidth = 1;
    368             this->addAttribute("vector-effect", "non-scaling-stroke");
    369         }
    370         this->addAttribute("stroke-width", strokeWidth);
    371 
    372         if (const char* cap = svg_cap(paint.getStrokeCap())) {
    373             this->addAttribute("stroke-linecap", cap);
    374         }
    375 
    376         if (const char* join = svg_join(paint.getStrokeJoin())) {
    377             this->addAttribute("stroke-linejoin", join);
    378         }
    379 
    380         if (paint.getStrokeJoin() == SkPaint::kMiter_Join) {
    381             this->addAttribute("stroke-miterlimit", paint.getStrokeMiter());
    382         }
    383 
    384         if (SK_AlphaOPAQUE != SkColorGetA(paint.getColor())) {
    385             this->addAttribute("stroke-opacity", svg_opacity(paint.getColor()));
    386         }
    387     } else {
    388         SkASSERT(style == SkPaint::kFill_Style);
    389         this->addAttribute("stroke", "none");
    390     }
    391 }
    392 
    393 Resources SkSVGDevice::AutoElement::addResources(const SkDraw& draw, const SkPaint& paint) {
    394     Resources resources(paint);
    395 
    396     // FIXME: this is a weak heuristic and we end up with LOTS of redundant clips.
    397     bool hasClip   = !draw.fClipStack->isWideOpen();
    398     bool hasShader = SkToBool(paint.getShader());
    399 
    400     if (hasClip || hasShader) {
    401         AutoElement defs("defs", fWriter);
    402 
    403         if (hasClip) {
    404             this->addClipResources(draw, &resources);
    405         }
    406 
    407         if (hasShader) {
    408             this->addShaderResources(paint, &resources);
    409         }
    410     }
    411 
    412     return resources;
    413 }
    414 
    415 void SkSVGDevice::AutoElement::addShaderResources(const SkPaint& paint, Resources* resources) {
    416     const SkShader* shader = paint.getShader();
    417     SkASSERT(SkToBool(shader));
    418 
    419     SkShader::GradientInfo grInfo;
    420     grInfo.fColorCount = 0;
    421     if (SkShader::kLinear_GradientType != shader->asAGradient(&grInfo)) {
    422         // TODO: non-linear gradient support
    423         SkDebugf("unsupported shader type\n");
    424         return;
    425     }
    426 
    427     SkAutoSTArray<16, SkColor>  grColors(grInfo.fColorCount);
    428     SkAutoSTArray<16, SkScalar> grOffsets(grInfo.fColorCount);
    429     grInfo.fColors = grColors.get();
    430     grInfo.fColorOffsets = grOffsets.get();
    431 
    432     // One more call to get the actual colors/offsets.
    433     shader->asAGradient(&grInfo);
    434     SkASSERT(grInfo.fColorCount <= grColors.count());
    435     SkASSERT(grInfo.fColorCount <= grOffsets.count());
    436 
    437     resources->fPaintServer.printf("url(#%s)", addLinearGradientDef(grInfo, shader).c_str());
    438 }
    439 
    440 void SkSVGDevice::AutoElement::addClipResources(const SkDraw& draw, Resources* resources) {
    441     SkASSERT(!draw.fClipStack->isWideOpen());
    442 
    443     SkPath clipPath;
    444     (void) draw.fClipStack->asPath(&clipPath);
    445 
    446     SkString clipID = fResourceBucket->addClip();
    447     const char* clipRule = clipPath.getFillType() == SkPath::kEvenOdd_FillType ?
    448                            "evenodd" : "nonzero";
    449     {
    450         // clipPath is in device space, but since we're only pushing transform attributes
    451         // to the leaf nodes, so are all our elements => SVG userSpaceOnUse == device space.
    452         AutoElement clipPathElement("clipPath", fWriter);
    453         clipPathElement.addAttribute("id", clipID);
    454 
    455         SkRect clipRect = SkRect::MakeEmpty();
    456         if (clipPath.isEmpty() || clipPath.isRect(&clipRect)) {
    457             AutoElement rectElement("rect", fWriter);
    458             rectElement.addRectAttributes(clipRect);
    459             rectElement.addAttribute("clip-rule", clipRule);
    460         } else {
    461             AutoElement pathElement("path", fWriter);
    462             pathElement.addPathAttributes(clipPath);
    463             pathElement.addAttribute("clip-rule", clipRule);
    464         }
    465     }
    466 
    467     resources->fClip.printf("url(#%s)", clipID.c_str());
    468 }
    469 
    470 SkString SkSVGDevice::AutoElement::addLinearGradientDef(const SkShader::GradientInfo& info,
    471                                                         const SkShader* shader) {
    472     SkASSERT(fResourceBucket);
    473     SkString id = fResourceBucket->addLinearGradient();
    474 
    475     {
    476         AutoElement gradient("linearGradient", fWriter);
    477 
    478         gradient.addAttribute("id", id);
    479         gradient.addAttribute("gradientUnits", "userSpaceOnUse");
    480         gradient.addAttribute("x1", info.fPoint[0].x());
    481         gradient.addAttribute("y1", info.fPoint[0].y());
    482         gradient.addAttribute("x2", info.fPoint[1].x());
    483         gradient.addAttribute("y2", info.fPoint[1].y());
    484 
    485         if (!shader->getLocalMatrix().isIdentity()) {
    486             this->addAttribute("gradientTransform", svg_transform(shader->getLocalMatrix()));
    487         }
    488 
    489         SkASSERT(info.fColorCount >= 2);
    490         for (int i = 0; i < info.fColorCount; ++i) {
    491             SkColor color = info.fColors[i];
    492             SkString colorStr(svg_color(color));
    493 
    494             {
    495                 AutoElement stop("stop", fWriter);
    496                 stop.addAttribute("offset", info.fColorOffsets[i]);
    497                 stop.addAttribute("stop-color", colorStr.c_str());
    498 
    499                 if (SK_AlphaOPAQUE != SkColorGetA(color)) {
    500                     stop.addAttribute("stop-opacity", svg_opacity(color));
    501                 }
    502             }
    503         }
    504     }
    505 
    506     return id;
    507 }
    508 
    509 void SkSVGDevice::AutoElement::addRectAttributes(const SkRect& rect) {
    510     // x, y default to 0
    511     if (rect.x() != 0) {
    512         this->addAttribute("x", rect.x());
    513     }
    514     if (rect.y() != 0) {
    515         this->addAttribute("y", rect.y());
    516     }
    517 
    518     this->addAttribute("width", rect.width());
    519     this->addAttribute("height", rect.height());
    520 }
    521 
    522 void SkSVGDevice::AutoElement::addPathAttributes(const SkPath& path) {
    523     SkString pathData;
    524     SkParsePath::ToSVGString(path, &pathData);
    525     this->addAttribute("d", pathData);
    526 }
    527 
    528 void SkSVGDevice::AutoElement::addTextAttributes(const SkPaint& paint) {
    529     this->addAttribute("font-size", paint.getTextSize());
    530 
    531     if (const char* textAlign = svg_text_align(paint.getTextAlign())) {
    532         this->addAttribute("text-anchor", textAlign);
    533     }
    534 
    535     SkString familyName;
    536     SkTHashSet<SkString> familySet;
    537     SkAutoTUnref<const SkTypeface> tface(paint.getTypeface() ?
    538         SkRef(paint.getTypeface()) : SkTypeface::RefDefault());
    539 
    540     SkASSERT(tface);
    541     SkTypeface::Style style = tface->style();
    542     if (style & SkTypeface::kItalic) {
    543         this->addAttribute("font-style", "italic");
    544     }
    545     if (style & SkTypeface::kBold) {
    546         this->addAttribute("font-weight", "bold");
    547     }
    548 
    549     SkAutoTUnref<SkTypeface::LocalizedStrings> familyNameIter(tface->createFamilyNameIterator());
    550     SkTypeface::LocalizedString familyString;
    551     while (familyNameIter->next(&familyString)) {
    552         if (familySet.contains(familyString.fString)) {
    553             continue;
    554         }
    555         familySet.add(familyString.fString);
    556         familyName.appendf((familyName.isEmpty() ? "%s" : ", %s"), familyString.fString.c_str());
    557     }
    558 
    559     if (!familyName.isEmpty()) {
    560         this->addAttribute("font-family", familyName);
    561     }
    562 }
    563 
    564 SkBaseDevice* SkSVGDevice::Create(const SkISize& size, SkXMLWriter* writer) {
    565     if (!writer) {
    566         return nullptr;
    567     }
    568 
    569     return new SkSVGDevice(size, writer);
    570 }
    571 
    572 SkSVGDevice::SkSVGDevice(const SkISize& size, SkXMLWriter* writer)
    573     : INHERITED(SkSurfaceProps(0, kUnknown_SkPixelGeometry))
    574     , fWriter(writer)
    575     , fResourceBucket(new ResourceBucket) {
    576     SkASSERT(writer);
    577 
    578     fLegacyBitmap.setInfo(SkImageInfo::MakeUnknown(size.width(), size.height()));
    579 
    580     fWriter->writeHeader();
    581 
    582     // The root <svg> tag gets closed by the destructor.
    583     fRootElement.reset(new AutoElement("svg", fWriter));
    584 
    585     fRootElement->addAttribute("xmlns", "http://www.w3.org/2000/svg");
    586     fRootElement->addAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
    587     fRootElement->addAttribute("width", size.width());
    588     fRootElement->addAttribute("height", size.height());
    589 }
    590 
    591 SkSVGDevice::~SkSVGDevice() {
    592 }
    593 
    594 SkImageInfo SkSVGDevice::imageInfo() const {
    595     return fLegacyBitmap.info();
    596 }
    597 
    598 const SkBitmap& SkSVGDevice::onAccessBitmap() {
    599     return fLegacyBitmap;
    600 }
    601 
    602 void SkSVGDevice::drawPaint(const SkDraw& draw, const SkPaint& paint) {
    603     AutoElement rect("rect", fWriter, fResourceBucket, draw, paint);
    604     rect.addRectAttributes(SkRect::MakeWH(SkIntToScalar(this->width()),
    605                                           SkIntToScalar(this->height())));
    606 }
    607 
    608 void SkSVGDevice::drawPoints(const SkDraw& draw, SkCanvas::PointMode mode, size_t count,
    609                              const SkPoint pts[], const SkPaint& paint) {
    610     SkPath path;
    611 
    612     switch (mode) {
    613             // todo
    614         case SkCanvas::kPoints_PointMode:
    615             SkDebugf("unsupported operation: drawPoints(kPoints_PointMode)\n");
    616             break;
    617         case SkCanvas::kLines_PointMode:
    618             count -= 1;
    619             for (size_t i = 0; i < count; i += 2) {
    620                 path.rewind();
    621                 path.moveTo(pts[i]);
    622                 path.lineTo(pts[i+1]);
    623                 AutoElement elem("path", fWriter, fResourceBucket, draw, paint);
    624                 elem.addPathAttributes(path);
    625             }
    626             break;
    627         case SkCanvas::kPolygon_PointMode:
    628             if (count > 1) {
    629                 path.addPoly(pts, SkToInt(count), false);
    630                 path.moveTo(pts[0]);
    631                 AutoElement elem("path", fWriter, fResourceBucket, draw, paint);
    632                 elem.addPathAttributes(path);
    633             }
    634             break;
    635     }
    636 }
    637 
    638 void SkSVGDevice::drawRect(const SkDraw& draw, const SkRect& r, const SkPaint& paint) {
    639     AutoElement rect("rect", fWriter, fResourceBucket, draw, paint);
    640     rect.addRectAttributes(r);
    641 }
    642 
    643 void SkSVGDevice::drawOval(const SkDraw& draw, const SkRect& oval, const SkPaint& paint) {
    644     AutoElement ellipse("ellipse", fWriter, fResourceBucket, draw, paint);
    645     ellipse.addAttribute("cx", oval.centerX());
    646     ellipse.addAttribute("cy", oval.centerY());
    647     ellipse.addAttribute("rx", oval.width() / 2);
    648     ellipse.addAttribute("ry", oval.height() / 2);
    649 }
    650 
    651 void SkSVGDevice::drawRRect(const SkDraw& draw, const SkRRect& rr, const SkPaint& paint) {
    652     SkPath path;
    653     path.addRRect(rr);
    654 
    655     AutoElement elem("path", fWriter, fResourceBucket, draw, paint);
    656     elem.addPathAttributes(path);
    657 }
    658 
    659 void SkSVGDevice::drawPath(const SkDraw& draw, const SkPath& path, const SkPaint& paint,
    660                            const SkMatrix* prePathMatrix, bool pathIsMutable) {
    661     AutoElement elem("path", fWriter, fResourceBucket, draw, paint);
    662     elem.addPathAttributes(path);
    663 }
    664 
    665 void SkSVGDevice::drawBitmapCommon(const SkDraw& draw, const SkBitmap& bm,
    666                                    const SkPaint& paint) {
    667     SkAutoTUnref<const SkData> pngData(
    668         SkImageEncoder::EncodeData(bm, SkImageEncoder::kPNG_Type, SkImageEncoder::kDefaultQuality));
    669     if (!pngData) {
    670         return;
    671     }
    672 
    673     size_t b64Size = SkBase64::Encode(pngData->data(), pngData->size(), nullptr);
    674     SkAutoTMalloc<char> b64Data(b64Size);
    675     SkBase64::Encode(pngData->data(), pngData->size(), b64Data.get());
    676 
    677     SkString svgImageData("data:image/png;base64,");
    678     svgImageData.append(b64Data.get(), b64Size);
    679 
    680     SkString imageID = fResourceBucket->addImage();
    681     {
    682         AutoElement defs("defs", fWriter);
    683         {
    684             AutoElement image("image", fWriter);
    685             image.addAttribute("id", imageID);
    686             image.addAttribute("width", bm.width());
    687             image.addAttribute("height", bm.height());
    688             image.addAttribute("xlink:href", svgImageData);
    689         }
    690     }
    691 
    692     {
    693         AutoElement imageUse("use", fWriter, fResourceBucket, draw, paint);
    694         imageUse.addAttribute("xlink:href", SkStringPrintf("#%s", imageID.c_str()));
    695     }
    696 }
    697 
    698 void SkSVGDevice::drawBitmap(const SkDraw& draw, const SkBitmap& bitmap,
    699                              const SkMatrix& matrix, const SkPaint& paint) {
    700     SkMatrix adjustedMatrix = *draw.fMatrix;
    701     adjustedMatrix.preConcat(matrix);
    702     SkDraw adjustedDraw(draw);
    703     adjustedDraw.fMatrix = &adjustedMatrix;
    704 
    705     drawBitmapCommon(adjustedDraw, bitmap, paint);
    706 }
    707 
    708 void SkSVGDevice::drawSprite(const SkDraw& draw, const SkBitmap& bitmap,
    709                              int x, int y, const SkPaint& paint) {
    710     SkMatrix adjustedMatrix = *draw.fMatrix;
    711     adjustedMatrix.preTranslate(SkIntToScalar(x), SkIntToScalar(y));
    712     SkDraw adjustedDraw(draw);
    713     adjustedDraw.fMatrix = &adjustedMatrix;
    714 
    715     drawBitmapCommon(adjustedDraw, bitmap, paint);
    716 }
    717 
    718 void SkSVGDevice::drawBitmapRect(const SkDraw& draw, const SkBitmap& bm, const SkRect* srcOrNull,
    719                                  const SkRect& dst, const SkPaint& paint,
    720                                  SkCanvas::SrcRectConstraint) {
    721     SkMatrix adjustedMatrix;
    722     adjustedMatrix.setRectToRect(srcOrNull ? *srcOrNull : SkRect::Make(bm.bounds()),
    723                                  dst,
    724                                  SkMatrix::kFill_ScaleToFit);
    725     adjustedMatrix.postConcat(*draw.fMatrix);
    726 
    727     SkDraw adjustedDraw(draw);
    728     adjustedDraw.fMatrix = &adjustedMatrix;
    729 
    730     SkClipStack adjustedClipStack;
    731     if (srcOrNull && *srcOrNull != SkRect::Make(bm.bounds())) {
    732         SkRect devClipRect;
    733         draw.fMatrix->mapRect(&devClipRect, dst);
    734 
    735         adjustedClipStack = *draw.fClipStack;
    736         adjustedClipStack.clipDevRect(devClipRect, SkRegion::kIntersect_Op, paint.isAntiAlias());
    737         adjustedDraw.fClipStack = &adjustedClipStack;
    738     }
    739 
    740     drawBitmapCommon(adjustedDraw, bm, paint);
    741 }
    742 
    743 void SkSVGDevice::drawText(const SkDraw& draw, const void* text, size_t len,
    744                            SkScalar x, SkScalar y, const SkPaint& paint) {
    745     AutoElement elem("text", fWriter, fResourceBucket, draw, paint);
    746     elem.addTextAttributes(paint);
    747 
    748     SVGTextBuilder builder(text, len, paint, SkPoint::Make(x, y), 0);
    749     elem.addAttribute("x", builder.posX());
    750     elem.addAttribute("y", builder.posY());
    751     elem.addText(builder.text());
    752 }
    753 
    754 void SkSVGDevice::drawPosText(const SkDraw& draw, const void* text, size_t len,
    755                               const SkScalar pos[], int scalarsPerPos, const SkPoint& offset,
    756                               const SkPaint& paint) {
    757     SkASSERT(scalarsPerPos == 1 || scalarsPerPos == 2);
    758 
    759     AutoElement elem("text", fWriter, fResourceBucket, draw, paint);
    760     elem.addTextAttributes(paint);
    761 
    762     SVGTextBuilder builder(text, len, paint, offset, scalarsPerPos, pos);
    763     elem.addAttribute("x", builder.posX());
    764     elem.addAttribute("y", builder.posY());
    765     elem.addText(builder.text());
    766 }
    767 
    768 void SkSVGDevice::drawTextOnPath(const SkDraw&, const void* text, size_t len, const SkPath& path,
    769                                  const SkMatrix* matrix, const SkPaint& paint) {
    770     SkString pathID = fResourceBucket->addPath();
    771 
    772     {
    773         AutoElement defs("defs", fWriter);
    774         AutoElement pathElement("path", fWriter);
    775         pathElement.addAttribute("id", pathID);
    776         pathElement.addPathAttributes(path);
    777 
    778     }
    779 
    780     {
    781         AutoElement textElement("text", fWriter);
    782         textElement.addTextAttributes(paint);
    783 
    784         if (matrix && !matrix->isIdentity()) {
    785             textElement.addAttribute("transform", svg_transform(*matrix));
    786         }
    787 
    788         {
    789             AutoElement textPathElement("textPath", fWriter);
    790             textPathElement.addAttribute("xlink:href", SkStringPrintf("#%s", pathID.c_str()));
    791 
    792             if (paint.getTextAlign() != SkPaint::kLeft_Align) {
    793                 SkASSERT(paint.getTextAlign() == SkPaint::kCenter_Align ||
    794                          paint.getTextAlign() == SkPaint::kRight_Align);
    795                 textPathElement.addAttribute("startOffset",
    796                     paint.getTextAlign() == SkPaint::kCenter_Align ? "50%" : "100%");
    797             }
    798 
    799             SVGTextBuilder builder(text, len, paint, SkPoint::Make(0, 0), 0);
    800             textPathElement.addText(builder.text());
    801         }
    802     }
    803 }
    804 
    805 void SkSVGDevice::drawVertices(const SkDraw&, SkCanvas::VertexMode, int vertexCount,
    806                                const SkPoint verts[], const SkPoint texs[],
    807                                const SkColor colors[], SkXfermode* xmode,
    808                                const uint16_t indices[], int indexCount,
    809                                const SkPaint& paint) {
    810     // todo
    811     SkDebugf("unsupported operation: drawVertices()\n");
    812 }
    813 
    814 void SkSVGDevice::drawDevice(const SkDraw&, SkBaseDevice*, int x, int y,
    815                              const SkPaint&) {
    816     // todo
    817     SkDebugf("unsupported operation: drawDevice()\n");
    818 }
    819