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
svg_color(SkColor color)41 static 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
svg_opacity(SkColor color)90 static SkScalar svg_opacity(SkColor color) {
91 return SkIntToScalar(SkColorGetA(color)) / SK_AlphaOPAQUE;
92 }
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
svg_cap(SkPaint::Cap cap)102 static 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
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 static const char* svg_join(SkPaint::Join join) {
116 SkASSERT(join < SK_ARRAY_COUNT(join_map));
117 return join_map[join];
118 }
119
120 static 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
149 struct 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.
160 bool 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
178 void 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.
204 class SkSVGDevice::ResourceBucket : ::SkNoncopyable {
205 public:
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
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 MxCp(const SkMatrix* mx, const SkClipStack* cs) : fMatrix(mx), fClipStack(cs) {}
244 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 , 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
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 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 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
382 Resources 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
402 void 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
431 void 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
466 namespace {
467 bool 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.
476 sk_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
515 void 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
567 void 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
579 SkString 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
618 void 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
631 void 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
638 void 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
685 sk_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
691 SkSVGDevice::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
711 SkSVGDevice::~SkSVGDevice() {
712 // Pop order is important.
713 while (!fClipStack.empty()) {
714 fClipStack.pop_back();
715 }
716 }
717
718 SkParsePath::PathEncoding SkSVGDevice::pathEncoding() const {
719 return (fFlags & SkSVGCanvas::kRelativePathEncoding_Flag)
720 ? SkParsePath::PathEncoding::Relative
721 : SkParsePath::PathEncoding::Absolute;
722 }
723
724 void 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
799 void 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
805 void 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
831 void 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
857 void 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
876 void 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
884 void 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
889 void 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
927 static sk_sp<SkData> encode(const SkBitmap& src) {
928 SkDynamicMemoryWStream buf;
929 return SkEncodeImage(&buf, src, SkEncodedImageFormat::kPNG, 80) ? buf.detachAsData() : nullptr;
930 }
931
932 void 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
963 void 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
985 class SVGTextBuilder : SkNoncopyable {
986 public:
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
1003 private:
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("&");
1028 break;
1029 case '"':
1030 fText.append(""");
1031 break;
1032 case '\'':
1033 fText.append("'");
1034 break;
1035 case '<':
1036 fText.append("<");
1037 break;
1038 case '>':
1039 fText.append(">");
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
1074 void 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
1103 void SkSVGDevice::drawVertices(const SkVertices*, SkBlendMode, const SkPaint&) {
1104 // todo
1105 }
1106