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