Coverage Report

Created: 2021-08-22 09:07

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