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