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