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