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