xref: /third_party/skia/src/svg/SkSVGDevice.cpp (revision cb93a386)
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 "src/svg/SkSVGDevice.h"
9
10#include <memory>
11
12#include "include/core/SkBitmap.h"
13#include "include/core/SkBlendMode.h"
14#include "include/core/SkColorFilter.h"
15#include "include/core/SkData.h"
16#include "include/core/SkImage.h"
17#include "include/core/SkImageEncoder.h"
18#include "include/core/SkPaint.h"
19#include "include/core/SkPathBuilder.h"
20#include "include/core/SkShader.h"
21#include "include/core/SkStream.h"
22#include "include/core/SkTypeface.h"
23#include "include/private/SkChecksum.h"
24#include "include/private/SkTHash.h"
25#include "include/private/SkTPin.h"
26#include "include/private/SkTo.h"
27#include "include/svg/SkSVGCanvas.h"
28#include "include/utils/SkBase64.h"
29#include "src/codec/SkJpegCodec.h"
30#include "src/core/SkAnnotationKeys.h"
31#include "src/core/SkClipStack.h"
32#include "src/core/SkDraw.h"
33#include "src/core/SkFontPriv.h"
34#include "src/core/SkUtils.h"
35#include "src/image/SkImage_Base.h"
36#include "src/shaders/SkShaderBase.h"
37#include "src/xml/SkXMLWriter.h"
38
39namespace {
40
41static SkString svg_color(SkColor color) {
42    // https://www.w3.org/TR/css-color-3/#html4
43    auto named_color = [](SkColor c) -> const char* {
44        switch (c & 0xffffff) {
45        case 0x000000: return "black";
46        case 0x000080: return "navy";
47        case 0x0000ff: return "blue";
48        case 0x008000: return "green";
49        case 0x008080: return "teal";
50        case 0x00ff00: return "lime";
51        case 0x00ffff: return "aqua";
52        case 0x800000: return "maroon";
53        case 0x800080: return "purple";
54        case 0x808000: return "olive";
55        case 0x808080: return "gray";
56        case 0xc0c0c0: return "silver";
57        case 0xff0000: return "red";
58        case 0xff00ff: return "fuchsia";
59        case 0xffff00: return "yellow";
60        case 0xffffff: return "white";
61        default: break;
62        }
63
64        return nullptr;
65    };
66
67    if (const auto* nc = named_color(color)) {
68        return SkString(nc);
69    }
70
71    uint8_t r = SkColorGetR(color);
72    uint8_t g = SkColorGetG(color);
73    uint8_t b = SkColorGetB(color);
74
75    // Some users care about every byte here, so we'll use hex colors with single-digit channels
76    // when possible.
77    uint8_t rh = r >> 4;
78    uint8_t rl = r & 0xf;
79    uint8_t gh = g >> 4;
80    uint8_t gl = g & 0xf;
81    uint8_t bh = b >> 4;
82    uint8_t bl = b & 0xf;
83    if ((rh == rl) && (gh == gl) && (bh == bl)) {
84        return SkStringPrintf("#%1X%1X%1X", rh, gh, bh);
85    }
86
87    return SkStringPrintf("#%02X%02X%02X", r, g, b);
88}
89
90static SkScalar svg_opacity(SkColor color) {
91    return SkIntToScalar(SkColorGetA(color)) / SK_AlphaOPAQUE;
92}
93
94// Keep in sync with SkPaint::Cap
95static const char* cap_map[]  = {
96    nullptr,    // kButt_Cap (default)
97    "round", // kRound_Cap
98    "square" // kSquare_Cap
99};
100static_assert(SK_ARRAY_COUNT(cap_map) == SkPaint::kCapCount, "missing_cap_map_entry");
101
102static const char* svg_cap(SkPaint::Cap cap) {
103    SkASSERT(cap < SK_ARRAY_COUNT(cap_map));
104    return cap_map[cap];
105}
106
107// Keep in sync with SkPaint::Join
108static const char* join_map[] = {
109    nullptr,    // kMiter_Join (default)
110    "round", // kRound_Join
111    "bevel"  // kBevel_Join
112};
113static_assert(SK_ARRAY_COUNT(join_map) == SkPaint::kJoinCount, "missing_join_map_entry");
114
115static const char* svg_join(SkPaint::Join join) {
116    SkASSERT(join < SK_ARRAY_COUNT(join_map));
117    return join_map[join];
118}
119
120static SkString svg_transform(const SkMatrix& t) {
121    SkASSERT(!t.isIdentity());
122
123    SkString tstr;
124    switch (t.getType()) {
125    case SkMatrix::kPerspective_Mask:
126        // TODO: handle perspective matrices?
127        break;
128    case SkMatrix::kTranslate_Mask:
129        tstr.printf("translate(%g %g)", t.getTranslateX(), t.getTranslateY());
130        break;
131    case SkMatrix::kScale_Mask:
132        tstr.printf("scale(%g %g)", t.getScaleX(), t.getScaleY());
133        break;
134    default:
135        // http://www.w3.org/TR/SVG/coords.html#TransformMatrixDefined
136        //    | a c e |
137        //    | b d f |
138        //    | 0 0 1 |
139        tstr.printf("matrix(%g %g %g %g %g %g)",
140                    t.getScaleX(),     t.getSkewY(),
141                    t.getSkewX(),      t.getScaleY(),
142                    t.getTranslateX(), t.getTranslateY());
143        break;
144    }
145
146    return tstr;
147}
148
149struct Resources {
150    Resources(const SkPaint& paint)
151        : fPaintServer(svg_color(paint.getColor())) {}
152
153    SkString fPaintServer;
154    SkString fColorFilter;
155};
156
157// Determine if the paint requires us to reset the viewport.
158// Currently, we do this whenever the paint shader calls
159// for a repeating image.
160bool RequiresViewportReset(const SkPaint& paint) {
161  SkShader* shader = paint.getShader();
162  if (!shader)
163    return false;
164
165  SkTileMode xy[2];
166  SkImage* image = shader->isAImage(nullptr, xy);
167
168  if (!image)
169    return false;
170
171  for (int i = 0; i < 2; i++) {
172    if (xy[i] == SkTileMode::kRepeat)
173      return true;
174  }
175  return false;
176}
177
178void AddPath(const SkGlyphRun& glyphRun, const SkPoint& offset, SkPath* path) {
179    struct Rec {
180        SkPath*        fPath;
181        const SkPoint  fOffset;
182        const SkPoint* fPos;
183    } rec = { path, offset, glyphRun.positions().data() };
184
185    glyphRun.font().getPaths(glyphRun.glyphsIDs().data(), SkToInt(glyphRun.glyphsIDs().size()),
186            [](const SkPath* path, const SkMatrix& mx, void* ctx) {
187                Rec* rec = reinterpret_cast<Rec*>(ctx);
188                if (path) {
189                    SkMatrix total = mx;
190                    total.postTranslate(rec->fPos->fX + rec->fOffset.fX,
191                                        rec->fPos->fY + rec->fOffset.fY);
192                    rec->fPath->addPath(*path, total);
193                } else {
194                    // TODO: this is going to drop color emojis.
195                }
196                rec->fPos += 1; // move to the next glyph's position
197            }, &rec);
198}
199
200}  // namespace
201
202// For now all this does is serve unique serial IDs, but it will eventually evolve to track
203// and deduplicate resources.
204class SkSVGDevice::ResourceBucket : ::SkNoncopyable {
205public:
206    ResourceBucket()
207            : fGradientCount(0)
208            , fPathCount(0)
209            , fImageCount(0)
210            , fPatternCount(0)
211            , fColorFilterCount(0) {}
212
213    SkString addLinearGradient() {
214        return SkStringPrintf("gradient_%d", fGradientCount++);
215    }
216
217    SkString addPath() {
218        return SkStringPrintf("path_%d", fPathCount++);
219    }
220
221    SkString addImage() {
222        return SkStringPrintf("img_%d", fImageCount++);
223    }
224
225    SkString addColorFilter() { return SkStringPrintf("cfilter_%d", fColorFilterCount++); }
226
227    SkString addPattern() {
228      return SkStringPrintf("pattern_%d", fPatternCount++);
229    }
230
231private:
232    uint32_t fGradientCount;
233    uint32_t fPathCount;
234    uint32_t fImageCount;
235    uint32_t fPatternCount;
236    uint32_t fColorFilterCount;
237};
238
239struct SkSVGDevice::MxCp {
240    const SkMatrix* fMatrix;
241    const SkClipStack*  fClipStack;
242
243    MxCp(const SkMatrix* mx, const SkClipStack* cs) : fMatrix(mx), fClipStack(cs) {}
244    MxCp(SkSVGDevice* device) : fMatrix(&device->localToDevice()), fClipStack(&device->cs()) {}
245};
246
247class SkSVGDevice::AutoElement : ::SkNoncopyable {
248public:
249    AutoElement(const char name[], SkXMLWriter* writer)
250        : fWriter(writer)
251        , fResourceBucket(nullptr) {
252        fWriter->startElement(name);
253    }
254
255    AutoElement(const char name[], const std::unique_ptr<SkXMLWriter>& writer)
256        : AutoElement(name, writer.get()) {}
257
258    AutoElement(const char name[], SkSVGDevice* svgdev,
259                ResourceBucket* bucket, const MxCp& mc, const SkPaint& paint)
260        : fWriter(svgdev->fWriter.get())
261        , fResourceBucket(bucket) {
262
263        svgdev->syncClipStack(*mc.fClipStack);
264        Resources res = this->addResources(mc, paint);
265
266        fWriter->startElement(name);
267
268        this->addPaint(paint, res);
269
270        if (!mc.fMatrix->isIdentity()) {
271            this->addAttribute("transform", svg_transform(*mc.fMatrix));
272        }
273    }
274
275    ~AutoElement() {
276        fWriter->endElement();
277    }
278
279    void addAttribute(const char name[], const char val[]) {
280        fWriter->addAttribute(name, val);
281    }
282
283    void addAttribute(const char name[], const SkString& val) {
284        fWriter->addAttribute(name, val.c_str());
285    }
286
287    void addAttribute(const char name[], int32_t val) {
288        fWriter->addS32Attribute(name, val);
289    }
290
291    void addAttribute(const char name[], SkScalar val) {
292        fWriter->addScalarAttribute(name, val);
293    }
294
295    void addText(const SkString& text) {
296        fWriter->addText(text.c_str(), text.size());
297    }
298
299    void addRectAttributes(const SkRect&);
300    void addPathAttributes(const SkPath&, SkParsePath::PathEncoding);
301    void addTextAttributes(const SkFont&);
302
303private:
304    Resources addResources(const MxCp&, const SkPaint& paint);
305    void addShaderResources(const SkPaint& paint, Resources* resources);
306    void addGradientShaderResources(const SkShader* shader, const SkPaint& paint,
307                                    Resources* resources);
308    void addColorFilterResources(const SkColorFilter& cf, Resources* resources);
309    void addImageShaderResources(const SkShader* shader, const SkPaint& paint,
310                                 Resources* resources);
311
312    void addPatternDef(const SkBitmap& bm);
313
314    void addPaint(const SkPaint& paint, const Resources& resources);
315
316
317    SkString addLinearGradientDef(const SkShader::GradientInfo& info, const SkShader* shader);
318
319    SkXMLWriter*               fWriter;
320    ResourceBucket*            fResourceBucket;
321};
322
323void SkSVGDevice::AutoElement::addPaint(const SkPaint& paint, const Resources& resources) {
324    // Path effects are applied to all vector graphics (rects, rrects, ovals,
325    // paths etc).  This should only happen when a path effect is attached to
326    // non-vector graphics (text, image) or a new vector graphics primitive is
327    //added that is not handled by base drawPath() routine.
328    if (paint.getPathEffect() != nullptr) {
329        SkDebugf("Unsupported path effect in addPaint.");
330    }
331    SkPaint::Style style = paint.getStyle();
332    if (style == SkPaint::kFill_Style || style == SkPaint::kStrokeAndFill_Style) {
333        static constexpr char kDefaultFill[] = "black";
334        if (!resources.fPaintServer.equals(kDefaultFill)) {
335            this->addAttribute("fill", resources.fPaintServer);
336
337            if (SK_AlphaOPAQUE != SkColorGetA(paint.getColor())) {
338                this->addAttribute("fill-opacity", svg_opacity(paint.getColor()));
339            }
340        }
341    } else {
342        SkASSERT(style == SkPaint::kStroke_Style);
343        this->addAttribute("fill", "none");
344    }
345
346    if (!resources.fColorFilter.isEmpty()) {
347        this->addAttribute("filter", resources.fColorFilter.c_str());
348    }
349
350    if (style == SkPaint::kStroke_Style || style == SkPaint::kStrokeAndFill_Style) {
351        this->addAttribute("stroke", resources.fPaintServer);
352
353        SkScalar strokeWidth = paint.getStrokeWidth();
354        if (strokeWidth == 0) {
355            // Hairline stroke
356            strokeWidth = 1;
357            this->addAttribute("vector-effect", "non-scaling-stroke");
358        }
359        this->addAttribute("stroke-width", strokeWidth);
360
361        if (const char* cap = svg_cap(paint.getStrokeCap())) {
362            this->addAttribute("stroke-linecap", cap);
363        }
364
365        if (const char* join = svg_join(paint.getStrokeJoin())) {
366            this->addAttribute("stroke-linejoin", join);
367        }
368
369        if (paint.getStrokeJoin() == SkPaint::kMiter_Join) {
370            this->addAttribute("stroke-miterlimit", paint.getStrokeMiter());
371        }
372
373        if (SK_AlphaOPAQUE != SkColorGetA(paint.getColor())) {
374            this->addAttribute("stroke-opacity", svg_opacity(paint.getColor()));
375        }
376    } else {
377        SkASSERT(style == SkPaint::kFill_Style);
378        // SVG default stroke value is "none".
379    }
380}
381
382Resources SkSVGDevice::AutoElement::addResources(const MxCp& mc, const SkPaint& paint) {
383    Resources resources(paint);
384
385    if (paint.getShader()) {
386        AutoElement defs("defs", fWriter);
387
388        this->addShaderResources(paint, &resources);
389    }
390
391    if (const SkColorFilter* cf = paint.getColorFilter()) {
392        // TODO: Implement skia color filters for blend modes other than SrcIn
393        SkBlendMode mode;
394        if (cf->asAColorMode(nullptr, &mode) && mode == SkBlendMode::kSrcIn) {
395            this->addColorFilterResources(*cf, &resources);
396        }
397    }
398
399    return resources;
400}
401
402void SkSVGDevice::AutoElement::addGradientShaderResources(const SkShader* shader,
403                                                          const SkPaint& paint,
404                                                          Resources* resources) {
405    SkShader::GradientInfo grInfo;
406    memset(&grInfo, 0, sizeof(grInfo));
407    const auto gradient_type = shader->asAGradient(&grInfo);
408
409    if (gradient_type != SkShader::kColor_GradientType &&
410        gradient_type != SkShader::kLinear_GradientType) {
411        // TODO: other gradient support
412        return;
413    }
414
415    SkAutoSTArray<16, SkColor>  grColors(grInfo.fColorCount);
416    SkAutoSTArray<16, SkScalar> grOffsets(grInfo.fColorCount);
417    grInfo.fColors = grColors.get();
418    grInfo.fColorOffsets = grOffsets.get();
419
420    // One more call to get the actual colors/offsets.
421    shader->asAGradient(&grInfo);
422    SkASSERT(grInfo.fColorCount <= grColors.count());
423    SkASSERT(grInfo.fColorCount <= grOffsets.count());
424
425    SkASSERT(grColors.size() > 0);
426    resources->fPaintServer = gradient_type == SkShader::kColor_GradientType
427            ? svg_color(grColors[0])
428            : SkStringPrintf("url(#%s)", addLinearGradientDef(grInfo, shader).c_str());
429}
430
431void SkSVGDevice::AutoElement::addColorFilterResources(const SkColorFilter& cf,
432                                                       Resources* resources) {
433    SkString colorfilterID = fResourceBucket->addColorFilter();
434    {
435        AutoElement filterElement("filter", fWriter);
436        filterElement.addAttribute("id", colorfilterID);
437        filterElement.addAttribute("x", "0%");
438        filterElement.addAttribute("y", "0%");
439        filterElement.addAttribute("width", "100%");
440        filterElement.addAttribute("height", "100%");
441
442        SkColor filterColor;
443        SkBlendMode mode;
444        bool asAColorMode = cf.asAColorMode(&filterColor, &mode);
445        SkAssertResult(asAColorMode);
446        SkASSERT(mode == SkBlendMode::kSrcIn);
447
448        {
449            // first flood with filter color
450            AutoElement floodElement("feFlood", fWriter);
451            floodElement.addAttribute("flood-color", svg_color(filterColor));
452            floodElement.addAttribute("flood-opacity", svg_opacity(filterColor));
453            floodElement.addAttribute("result", "flood");
454        }
455
456        {
457            // apply the transform to filter color
458            AutoElement compositeElement("feComposite", fWriter);
459            compositeElement.addAttribute("in", "flood");
460            compositeElement.addAttribute("operator", "in");
461        }
462    }
463    resources->fColorFilter.printf("url(#%s)", colorfilterID.c_str());
464}
465
466namespace {
467bool is_png(const void* bytes, size_t length) {
468    constexpr uint8_t kPngSig[] = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A };
469    return length >= sizeof(kPngSig) && !memcmp(bytes, kPngSig, sizeof(kPngSig));
470}
471}  // namespace
472
473// Returns data uri from bytes.
474// it will use any cached data if available, otherwise will
475// encode as png.
476sk_sp<SkData> AsDataUri(SkImage* image) {
477    sk_sp<SkData> imageData = image->encodeToData();
478    if (!imageData) {
479        return nullptr;
480    }
481
482    const char* selectedPrefix = nullptr;
483    size_t selectedPrefixLength = 0;
484
485#ifdef SK_CODEC_DECODES_JPEG
486    if (SkJpegCodec::IsJpeg(imageData->data(), imageData->size())) {
487        const static char jpgDataPrefix[] = "data:image/jpeg;base64,";
488        selectedPrefix = jpgDataPrefix;
489        selectedPrefixLength = sizeof(jpgDataPrefix);
490    }
491    else
492#endif
493    {
494        if (!is_png(imageData->data(), imageData->size())) {
495#ifdef SK_ENCODE_PNG
496            imageData = image->encodeToData(SkEncodedImageFormat::kPNG, 100);
497#else
498            return nullptr;
499#endif
500        }
501        const static char pngDataPrefix[] = "data:image/png;base64,";
502        selectedPrefix = pngDataPrefix;
503        selectedPrefixLength = sizeof(pngDataPrefix);
504    }
505
506    size_t b64Size = SkBase64::Encode(imageData->data(), imageData->size(), nullptr);
507    sk_sp<SkData> dataUri = SkData::MakeUninitialized(selectedPrefixLength + b64Size);
508    char* dest = (char*)dataUri->writable_data();
509    memcpy(dest, selectedPrefix, selectedPrefixLength);
510    SkBase64::Encode(imageData->data(), imageData->size(), dest + selectedPrefixLength - 1);
511    dest[dataUri->size() - 1] = 0;
512    return dataUri;
513}
514
515void SkSVGDevice::AutoElement::addImageShaderResources(const SkShader* shader, const SkPaint& paint,
516                                                       Resources* resources) {
517    SkMatrix outMatrix;
518
519    SkTileMode xy[2];
520    SkImage* image = shader->isAImage(&outMatrix, xy);
521    SkASSERT(image);
522
523    SkString patternDims[2];  // width, height
524
525    sk_sp<SkData> dataUri = AsDataUri(image);
526    if (!dataUri) {
527        return;
528    }
529    SkIRect imageSize = image->bounds();
530    for (int i = 0; i < 2; i++) {
531        int imageDimension = i == 0 ? imageSize.width() : imageSize.height();
532        switch (xy[i]) {
533            case SkTileMode::kRepeat:
534                patternDims[i].appendScalar(imageDimension);
535            break;
536            default:
537                // TODO: other tile modes?
538                patternDims[i] = "100%";
539        }
540    }
541
542    SkString patternID = fResourceBucket->addPattern();
543    {
544        AutoElement pattern("pattern", fWriter);
545        pattern.addAttribute("id", patternID);
546        pattern.addAttribute("patternUnits", "userSpaceOnUse");
547        pattern.addAttribute("patternContentUnits", "userSpaceOnUse");
548        pattern.addAttribute("width", patternDims[0]);
549        pattern.addAttribute("height", patternDims[1]);
550        pattern.addAttribute("x", 0);
551        pattern.addAttribute("y", 0);
552
553        {
554            SkString imageID = fResourceBucket->addImage();
555            AutoElement imageTag("image", fWriter);
556            imageTag.addAttribute("id", imageID);
557            imageTag.addAttribute("x", 0);
558            imageTag.addAttribute("y", 0);
559            imageTag.addAttribute("width", image->width());
560            imageTag.addAttribute("height", image->height());
561            imageTag.addAttribute("xlink:href", static_cast<const char*>(dataUri->data()));
562        }
563    }
564    resources->fPaintServer.printf("url(#%s)", patternID.c_str());
565}
566
567void SkSVGDevice::AutoElement::addShaderResources(const SkPaint& paint, Resources* resources) {
568    const SkShader* shader = paint.getShader();
569    SkASSERT(shader);
570
571    if (shader->asAGradient(nullptr) != SkShader::kNone_GradientType) {
572        this->addGradientShaderResources(shader, paint, resources);
573    } else if (shader->isAImage()) {
574        this->addImageShaderResources(shader, paint, resources);
575    }
576    // TODO: other shader types?
577}
578
579SkString SkSVGDevice::AutoElement::addLinearGradientDef(const SkShader::GradientInfo& info,
580                                                        const SkShader* shader) {
581    SkASSERT(fResourceBucket);
582    SkString id = fResourceBucket->addLinearGradient();
583
584    {
585        AutoElement gradient("linearGradient", fWriter);
586
587        gradient.addAttribute("id", id);
588        gradient.addAttribute("gradientUnits", "userSpaceOnUse");
589        gradient.addAttribute("x1", info.fPoint[0].x());
590        gradient.addAttribute("y1", info.fPoint[0].y());
591        gradient.addAttribute("x2", info.fPoint[1].x());
592        gradient.addAttribute("y2", info.fPoint[1].y());
593
594        if (!as_SB(shader)->getLocalMatrix().isIdentity()) {
595            this->addAttribute("gradientTransform", svg_transform(as_SB(shader)->getLocalMatrix()));
596        }
597
598        SkASSERT(info.fColorCount >= 2);
599        for (int i = 0; i < info.fColorCount; ++i) {
600            SkColor color = info.fColors[i];
601            SkString colorStr(svg_color(color));
602
603            {
604                AutoElement stop("stop", fWriter);
605                stop.addAttribute("offset", info.fColorOffsets[i]);
606                stop.addAttribute("stop-color", colorStr.c_str());
607
608                if (SK_AlphaOPAQUE != SkColorGetA(color)) {
609                    stop.addAttribute("stop-opacity", svg_opacity(color));
610                }
611            }
612        }
613    }
614
615    return id;
616}
617
618void SkSVGDevice::AutoElement::addRectAttributes(const SkRect& rect) {
619    // x, y default to 0
620    if (rect.x() != 0) {
621        this->addAttribute("x", rect.x());
622    }
623    if (rect.y() != 0) {
624        this->addAttribute("y", rect.y());
625    }
626
627    this->addAttribute("width", rect.width());
628    this->addAttribute("height", rect.height());
629}
630
631void SkSVGDevice::AutoElement::addPathAttributes(const SkPath& path,
632                                                 SkParsePath::PathEncoding encoding) {
633    SkString pathData;
634    SkParsePath::ToSVGString(path, &pathData, encoding);
635    this->addAttribute("d", pathData);
636}
637
638void SkSVGDevice::AutoElement::addTextAttributes(const SkFont& font) {
639    this->addAttribute("font-size", font.getSize());
640
641    SkString familyName;
642    SkTHashSet<SkString> familySet;
643    sk_sp<SkTypeface> tface = font.refTypefaceOrDefault();
644
645    SkASSERT(tface);
646    SkFontStyle style = tface->fontStyle();
647    if (style.slant() == SkFontStyle::kItalic_Slant) {
648        this->addAttribute("font-style", "italic");
649    } else if (style.slant() == SkFontStyle::kOblique_Slant) {
650        this->addAttribute("font-style", "oblique");
651    }
652    int weightIndex = (SkTPin(style.weight(), 100, 900) - 50) / 100;
653    if (weightIndex != 3) {
654        static constexpr const char* weights[] = {
655            "100", "200", "300", "normal", "400", "500", "600", "bold", "800", "900"
656        };
657        this->addAttribute("font-weight", weights[weightIndex]);
658    }
659    int stretchIndex = style.width() - 1;
660    if (stretchIndex != 4) {
661        static constexpr const char* stretches[] = {
662            "ultra-condensed", "extra-condensed", "condensed", "semi-condensed",
663            "normal",
664            "semi-expanded", "expanded", "extra-expanded", "ultra-expanded"
665        };
666        this->addAttribute("font-stretch", stretches[stretchIndex]);
667    }
668
669    sk_sp<SkTypeface::LocalizedStrings> familyNameIter(tface->createFamilyNameIterator());
670    SkTypeface::LocalizedString familyString;
671    if (familyNameIter) {
672        while (familyNameIter->next(&familyString)) {
673            if (familySet.contains(familyString.fString)) {
674                continue;
675            }
676            familySet.add(familyString.fString);
677            familyName.appendf((familyName.isEmpty() ? "%s" : ", %s"), familyString.fString.c_str());
678        }
679    }
680    if (!familyName.isEmpty()) {
681        this->addAttribute("font-family", familyName);
682    }
683}
684
685sk_sp<SkBaseDevice> SkSVGDevice::Make(const SkISize& size, std::unique_ptr<SkXMLWriter> writer,
686                                      uint32_t flags) {
687    return writer ? sk_sp<SkBaseDevice>(new SkSVGDevice(size, std::move(writer), flags))
688                  : nullptr;
689}
690
691SkSVGDevice::SkSVGDevice(const SkISize& size, std::unique_ptr<SkXMLWriter> writer, uint32_t flags)
692    : INHERITED(SkImageInfo::MakeUnknown(size.fWidth, size.fHeight),
693                SkSurfaceProps(0, kUnknown_SkPixelGeometry))
694    , fWriter(std::move(writer))
695    , fResourceBucket(new ResourceBucket)
696    , fFlags(flags)
697{
698    SkASSERT(fWriter);
699
700    fWriter->writeHeader();
701
702    // The root <svg> tag gets closed by the destructor.
703    fRootElement = std::make_unique<AutoElement>("svg", fWriter);
704
705    fRootElement->addAttribute("xmlns", "http://www.w3.org/2000/svg");
706    fRootElement->addAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
707    fRootElement->addAttribute("width", size.width());
708    fRootElement->addAttribute("height", size.height());
709}
710
711SkSVGDevice::~SkSVGDevice() {
712    // Pop order is important.
713    while (!fClipStack.empty()) {
714        fClipStack.pop_back();
715    }
716}
717
718SkParsePath::PathEncoding SkSVGDevice::pathEncoding() const {
719    return (fFlags & SkSVGCanvas::kRelativePathEncoding_Flag)
720        ? SkParsePath::PathEncoding::Relative
721        : SkParsePath::PathEncoding::Absolute;
722}
723
724void SkSVGDevice::syncClipStack(const SkClipStack& cs) {
725    SkClipStack::B2TIter iter(cs);
726
727    const SkClipStack::Element* elem;
728    size_t rec_idx = 0;
729
730    // First, find/preserve the common bottom.
731    while ((elem = iter.next()) && (rec_idx < fClipStack.size())) {
732        if (fClipStack[SkToInt(rec_idx)].fGenID != elem->getGenID()) {
733            break;
734        }
735        rec_idx++;
736    }
737
738    // Discard out-of-date stack top.
739    while (fClipStack.size() > rec_idx) {
740        fClipStack.pop_back();
741    }
742
743    auto define_clip = [this](const SkClipStack::Element* e) {
744        const auto cid = SkStringPrintf("cl_%x", e->getGenID());
745
746        AutoElement clip_path("clipPath", fWriter);
747        clip_path.addAttribute("id", cid);
748
749        // TODO: handle non-intersect clips.
750
751        switch (e->getDeviceSpaceType()) {
752        case SkClipStack::Element::DeviceSpaceType::kEmpty: {
753            // TODO: can we skip this?
754            AutoElement rect("rect", fWriter);
755        } break;
756        case SkClipStack::Element::DeviceSpaceType::kRect: {
757            AutoElement rect("rect", fWriter);
758            rect.addRectAttributes(e->getDeviceSpaceRect());
759        } break;
760        case SkClipStack::Element::DeviceSpaceType::kRRect: {
761            // TODO: complex rrect handling?
762            const auto& rr   = e->getDeviceSpaceRRect();
763            const auto radii = rr.getSimpleRadii();
764
765            AutoElement rrect("rect", fWriter);
766            rrect.addRectAttributes(rr.rect());
767            rrect.addAttribute("rx", radii.x());
768            rrect.addAttribute("ry", radii.y());
769        } break;
770        case SkClipStack::Element::DeviceSpaceType::kPath: {
771            const auto& p = e->getDeviceSpacePath();
772            AutoElement path("path", fWriter);
773            path.addPathAttributes(p, this->pathEncoding());
774            if (p.getFillType() == SkPathFillType::kEvenOdd) {
775                path.addAttribute("clip-rule", "evenodd");
776            }
777        } break;
778        case SkClipStack::Element::DeviceSpaceType::kShader:
779            // TODO: handle shader clipping, perhaps rasterize and apply as a mask image?
780            break;
781        }
782
783        return cid;
784    };
785
786    // Rebuild the top.
787    while (elem) {
788        const auto cid = define_clip(elem);
789
790        auto clip_grp = std::make_unique<AutoElement>("g", fWriter);
791        clip_grp->addAttribute("clip-path", SkStringPrintf("url(#%s)", cid.c_str()));
792
793        fClipStack.push_back({ std::move(clip_grp), elem->getGenID() });
794
795        elem = iter.next();
796    }
797}
798
799void SkSVGDevice::drawPaint(const SkPaint& paint) {
800    AutoElement rect("rect", this, fResourceBucket.get(), MxCp(this), paint);
801    rect.addRectAttributes(SkRect::MakeWH(SkIntToScalar(this->width()),
802                                          SkIntToScalar(this->height())));
803}
804
805void SkSVGDevice::drawAnnotation(const SkRect& rect, const char key[], SkData* value) {
806    if (!value) {
807        return;
808    }
809
810    if (!strcmp(SkAnnotationKeys::URL_Key(), key) ||
811        !strcmp(SkAnnotationKeys::Link_Named_Dest_Key(), key)) {
812        this->cs().save();
813        this->cs().clipRect(rect, this->localToDevice(), SkClipOp::kIntersect, true);
814        SkRect transformedRect = this->cs().bounds(this->getGlobalBounds());
815        this->cs().restore();
816        if (transformedRect.isEmpty()) {
817            return;
818        }
819
820        SkString url(static_cast<const char*>(value->data()), value->size() - 1);
821        AutoElement a("a", fWriter);
822        a.addAttribute("xlink:href", url.c_str());
823        {
824            AutoElement r("rect", fWriter);
825            r.addAttribute("fill-opacity", "0.0");
826            r.addRectAttributes(transformedRect);
827        }
828    }
829}
830
831void SkSVGDevice::drawPoints(SkCanvas::PointMode mode, size_t count,
832                             const SkPoint pts[], const SkPaint& paint) {
833    SkPathBuilder path;
834
835    switch (mode) {
836            // todo
837        case SkCanvas::kPoints_PointMode:
838            // TODO?
839            break;
840        case SkCanvas::kLines_PointMode:
841            count -= 1;
842            for (size_t i = 0; i < count; i += 2) {
843                path.moveTo(pts[i]);
844                path.lineTo(pts[i+1]);
845            }
846            break;
847        case SkCanvas::kPolygon_PointMode:
848            if (count > 1) {
849                path.addPolygon(pts, SkToInt(count), false);
850            }
851            break;
852    }
853
854    this->drawPath(path.detach(), paint, true);
855}
856
857void SkSVGDevice::drawRect(const SkRect& r, const SkPaint& paint) {
858    std::unique_ptr<AutoElement> svg;
859    if (RequiresViewportReset(paint)) {
860      svg = std::make_unique<AutoElement>("svg", this, fResourceBucket.get(), MxCp(this), paint);
861      svg->addRectAttributes(r);
862    }
863
864    AutoElement rect("rect", this, fResourceBucket.get(), MxCp(this), paint);
865
866    if (svg) {
867      rect.addAttribute("x", 0);
868      rect.addAttribute("y", 0);
869      rect.addAttribute("width", "100%");
870      rect.addAttribute("height", "100%");
871    } else {
872      rect.addRectAttributes(r);
873    }
874}
875
876void SkSVGDevice::drawOval(const SkRect& oval, const SkPaint& paint) {
877    AutoElement ellipse("ellipse", this, fResourceBucket.get(), MxCp(this), paint);
878    ellipse.addAttribute("cx", oval.centerX());
879    ellipse.addAttribute("cy", oval.centerY());
880    ellipse.addAttribute("rx", oval.width() / 2);
881    ellipse.addAttribute("ry", oval.height() / 2);
882}
883
884void SkSVGDevice::drawRRect(const SkRRect& rr, const SkPaint& paint) {
885    AutoElement elem("path", this, fResourceBucket.get(), MxCp(this), paint);
886    elem.addPathAttributes(SkPath::RRect(rr), this->pathEncoding());
887}
888
889void SkSVGDevice::drawPath(const SkPath& path, const SkPaint& paint, bool pathIsMutable) {
890    if (path.isInverseFillType()) {
891      SkDebugf("Inverse path fill type not yet implemented.");
892      return;
893    }
894
895    SkPath pathStorage;
896    SkPath* pathPtr = const_cast<SkPath*>(&path);
897    SkTCopyOnFirstWrite<SkPaint> path_paint(paint);
898
899    // Apply path effect from paint to path.
900    if (path_paint->getPathEffect()) {
901      if (!pathIsMutable) {
902        pathPtr = &pathStorage;
903      }
904      bool fill = path_paint->getFillPath(path, pathPtr);
905      if (fill) {
906        // Path should be filled.
907        path_paint.writable()->setStyle(SkPaint::kFill_Style);
908      } else {
909        // Path should be drawn with a hairline (width == 0).
910        path_paint.writable()->setStyle(SkPaint::kStroke_Style);
911        path_paint.writable()->setStrokeWidth(0);
912      }
913
914      path_paint.writable()->setPathEffect(nullptr); // path effect processed
915    }
916
917    // Create path element.
918    AutoElement elem("path", this, fResourceBucket.get(), MxCp(this), *path_paint);
919    elem.addPathAttributes(*pathPtr, this->pathEncoding());
920
921    // TODO: inverse fill types?
922    if (pathPtr->getFillType() == SkPathFillType::kEvenOdd) {
923        elem.addAttribute("fill-rule", "evenodd");
924    }
925}
926
927static sk_sp<SkData> encode(const SkBitmap& src) {
928    SkDynamicMemoryWStream buf;
929    return SkEncodeImage(&buf, src, SkEncodedImageFormat::kPNG, 80) ? buf.detachAsData() : nullptr;
930}
931
932void SkSVGDevice::drawBitmapCommon(const MxCp& mc, const SkBitmap& bm, const SkPaint& paint) {
933    sk_sp<SkData> pngData = encode(bm);
934    if (!pngData) {
935        return;
936    }
937
938    size_t b64Size = SkBase64::Encode(pngData->data(), pngData->size(), nullptr);
939    SkAutoTMalloc<char> b64Data(b64Size);
940    SkBase64::Encode(pngData->data(), pngData->size(), b64Data.get());
941
942    SkString svgImageData("data:image/png;base64,");
943    svgImageData.append(b64Data.get(), b64Size);
944
945    SkString imageID = fResourceBucket->addImage();
946    {
947        AutoElement defs("defs", fWriter);
948        {
949            AutoElement image("image", fWriter);
950            image.addAttribute("id", imageID);
951            image.addAttribute("width", bm.width());
952            image.addAttribute("height", bm.height());
953            image.addAttribute("xlink:href", svgImageData);
954        }
955    }
956
957    {
958        AutoElement imageUse("use", this, fResourceBucket.get(), mc, paint);
959        imageUse.addAttribute("xlink:href", SkStringPrintf("#%s", imageID.c_str()));
960    }
961}
962
963void SkSVGDevice::drawImageRect(const SkImage* image, const SkRect* src, const SkRect& dst,
964                                const SkSamplingOptions& sampling, const SkPaint& paint,
965                                SkCanvas::SrcRectConstraint constraint) {
966    SkBitmap bm;
967    // TODO: support gpu images
968    if (!as_IB(image)->getROPixels(nullptr, &bm)) {
969        return;
970    }
971
972    SkClipStack* cs = &this->cs();
973    SkClipStack::AutoRestore ar(cs, false);
974    if (src && *src != SkRect::Make(bm.bounds())) {
975        cs->save();
976        cs->clipRect(dst, this->localToDevice(), SkClipOp::kIntersect, paint.isAntiAlias());
977    }
978
979    SkMatrix adjustedMatrix = this->localToDevice()
980                            * SkMatrix::RectToRect(src ? *src : SkRect::Make(bm.bounds()), dst);
981
982    drawBitmapCommon(MxCp(&adjustedMatrix, cs), bm, paint);
983}
984
985class SVGTextBuilder : SkNoncopyable {
986public:
987    SVGTextBuilder(SkPoint origin, const SkGlyphRun& glyphRun)
988            : fOrigin(origin) {
989        auto runSize = glyphRun.runSize();
990        SkAutoSTArray<64, SkUnichar> unichars(runSize);
991        SkFontPriv::GlyphsToUnichars(glyphRun.font(), glyphRun.glyphsIDs().data(),
992                                     runSize, unichars.get());
993        auto positions = glyphRun.positions();
994        for (size_t i = 0; i < runSize; ++i) {
995            this->appendUnichar(unichars[i], positions[i]);
996        }
997    }
998
999    const SkString& text() const { return fText; }
1000    const SkString& posX() const { return fPosXStr; }
1001    const SkString& posY() const { return fHasConstY ? fConstYStr : fPosYStr; }
1002
1003private:
1004    void appendUnichar(SkUnichar c, SkPoint position) {
1005        bool discardPos = false;
1006        bool isWhitespace = false;
1007
1008        switch(c) {
1009            case ' ':
1010            case '\t':
1011                // consolidate whitespace to match SVG's xml:space=default munging
1012                // (http://www.w3.org/TR/SVG/text.html#WhiteSpace)
1013                if (fLastCharWasWhitespace) {
1014                    discardPos = true;
1015                } else {
1016                    fText.appendUnichar(c);
1017                }
1018                isWhitespace = true;
1019                break;
1020            case '\0':
1021                // SkPaint::glyphsToUnichars() returns \0 for inconvertible glyphs, but these
1022                // are not legal XML characters (http://www.w3.org/TR/REC-xml/#charsets)
1023                discardPos = true;
1024                isWhitespace = fLastCharWasWhitespace; // preserve whitespace consolidation
1025                break;
1026            case '&':
1027                fText.append("&amp;");
1028                break;
1029            case '"':
1030                fText.append("&quot;");
1031                break;
1032            case '\'':
1033                fText.append("&apos;");
1034                break;
1035            case '<':
1036                fText.append("&lt;");
1037                break;
1038            case '>':
1039                fText.append("&gt;");
1040                break;
1041            default:
1042                fText.appendUnichar(c);
1043                break;
1044        }
1045
1046        fLastCharWasWhitespace = isWhitespace;
1047
1048        if (discardPos) {
1049            return;
1050        }
1051
1052        position += fOrigin;
1053        fPosXStr.appendf("%.8g, ", position.fX);
1054        fPosYStr.appendf("%.8g, ", position.fY);
1055
1056        if (fConstYStr.isEmpty()) {
1057            fConstYStr = fPosYStr;
1058            fConstY    = position.fY;
1059        } else {
1060            fHasConstY &= SkScalarNearlyEqual(fConstY, position.fY);
1061        }
1062    }
1063
1064    const SkPoint   fOrigin;
1065
1066    SkString fText,
1067             fPosXStr, fPosYStr,
1068             fConstYStr;
1069    SkScalar fConstY;
1070    bool     fLastCharWasWhitespace = true, // start off in whitespace mode to strip leading space
1071             fHasConstY             = true;
1072};
1073
1074void SkSVGDevice::onDrawGlyphRunList(const SkGlyphRunList& glyphRunList, const SkPaint& paint)  {
1075    SkASSERT(!glyphRunList.hasRSXForm());
1076    const auto draw_as_path = (fFlags & SkSVGCanvas::kConvertTextToPaths_Flag) ||
1077                               paint.getPathEffect();
1078
1079    if (draw_as_path) {
1080        // Emit a single <path> element.
1081        SkPath path;
1082        for (auto& glyphRun : glyphRunList) {
1083            AddPath(glyphRun, glyphRunList.origin(), &path);
1084        }
1085
1086        this->drawPath(path, paint);
1087
1088        return;
1089    }
1090
1091    // Emit one <text> element for each run.
1092    for (auto& glyphRun : glyphRunList) {
1093        AutoElement elem("text", this, fResourceBucket.get(), MxCp(this), paint);
1094        elem.addTextAttributes(glyphRun.font());
1095
1096        SVGTextBuilder builder(glyphRunList.origin(), glyphRun);
1097        elem.addAttribute("x", builder.posX());
1098        elem.addAttribute("y", builder.posY());
1099        elem.addText(builder.text());
1100    }
1101}
1102
1103void SkSVGDevice::drawVertices(const SkVertices*, SkBlendMode, const SkPaint&) {
1104    // todo
1105}
1106