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