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 "SkBase64.h"
11 #include "SkBitmap.h"
12 #include "SkChecksum.h"
13 #include "SkClipStack.h"
14 #include "SkData.h"
15 #include "SkDraw.h"
16 #include "SkImageEncoder.h"
17 #include "SkPaint.h"
18 #include "SkParsePath.h"
19 #include "SkShader.h"
20 #include "SkStream.h"
21 #include "SkTHash.h"
22 #include "SkTypeface.h"
23 #include "SkUtils.h"
24 #include "SkXMLWriter.h"
25 #include "SkClipOpPriv.h"
26
27 namespace {
28
svg_color(SkColor color)29 static SkString svg_color(SkColor color) {
30 return SkStringPrintf("rgb(%u,%u,%u)",
31 SkColorGetR(color),
32 SkColorGetG(color),
33 SkColorGetB(color));
34 }
35
svg_opacity(SkColor color)36 static SkScalar svg_opacity(SkColor color) {
37 return SkIntToScalar(SkColorGetA(color)) / SK_AlphaOPAQUE;
38 }
39
40 // Keep in sync with SkPaint::Cap
41 static const char* cap_map[] = {
42 nullptr, // kButt_Cap (default)
43 "round", // kRound_Cap
44 "square" // kSquare_Cap
45 };
46 static_assert(SK_ARRAY_COUNT(cap_map) == SkPaint::kCapCount, "missing_cap_map_entry");
47
svg_cap(SkPaint::Cap cap)48 static const char* svg_cap(SkPaint::Cap cap) {
49 SkASSERT(cap < SK_ARRAY_COUNT(cap_map));
50 return cap_map[cap];
51 }
52
53 // Keep in sync with SkPaint::Join
54 static const char* join_map[] = {
55 nullptr, // kMiter_Join (default)
56 "round", // kRound_Join
57 "bevel" // kBevel_Join
58 };
59 static_assert(SK_ARRAY_COUNT(join_map) == SkPaint::kJoinCount, "missing_join_map_entry");
60
svg_join(SkPaint::Join join)61 static const char* svg_join(SkPaint::Join join) {
62 SkASSERT(join < SK_ARRAY_COUNT(join_map));
63 return join_map[join];
64 }
65
66 // Keep in sync with SkPaint::Align
67 static const char* text_align_map[] = {
68 nullptr, // kLeft_Align (default)
69 "middle", // kCenter_Align
70 "end" // kRight_Align
71 };
72 static_assert(SK_ARRAY_COUNT(text_align_map) == SkPaint::kAlignCount,
73 "missing_text_align_map_entry");
svg_text_align(SkPaint::Align align)74 static const char* svg_text_align(SkPaint::Align align) {
75 SkASSERT(align < SK_ARRAY_COUNT(text_align_map));
76 return text_align_map[align];
77 }
78
svg_transform(const SkMatrix & t)79 static SkString svg_transform(const SkMatrix& t) {
80 SkASSERT(!t.isIdentity());
81
82 SkString tstr;
83 switch (t.getType()) {
84 case SkMatrix::kPerspective_Mask:
85 SkDebugf("Can't handle perspective matrices.");
86 break;
87 case SkMatrix::kTranslate_Mask:
88 tstr.printf("translate(%g %g)", t.getTranslateX(), t.getTranslateY());
89 break;
90 case SkMatrix::kScale_Mask:
91 tstr.printf("scale(%g %g)", t.getScaleX(), t.getScaleY());
92 break;
93 default:
94 // http://www.w3.org/TR/SVG/coords.html#TransformMatrixDefined
95 // | a c e |
96 // | b d f |
97 // | 0 0 1 |
98 tstr.printf("matrix(%g %g %g %g %g %g)",
99 t.getScaleX(), t.getSkewY(),
100 t.getSkewX(), t.getScaleY(),
101 t.getTranslateX(), t.getTranslateY());
102 break;
103 }
104
105 return tstr;
106 }
107
108 struct Resources {
Resources__anond794dccc0111::Resources109 Resources(const SkPaint& paint)
110 : fPaintServer(svg_color(paint.getColor())) {}
111
112 SkString fPaintServer;
113 SkString fClip;
114 };
115
116 class SVGTextBuilder : SkNoncopyable {
117 public:
SVGTextBuilder(const void * text,size_t byteLen,const SkPaint & paint,const SkPoint & offset,unsigned scalarsPerPos,const SkScalar pos[]=nullptr)118 SVGTextBuilder(const void* text, size_t byteLen, const SkPaint& paint, const SkPoint& offset,
119 unsigned scalarsPerPos, const SkScalar pos[] = nullptr)
120 : fOffset(offset)
121 , fScalarsPerPos(scalarsPerPos)
122 , fPos(pos)
123 , fLastCharWasWhitespace(true) // start off in whitespace mode to strip all leading space
124 {
125 SkASSERT(scalarsPerPos <= 2);
126 SkASSERT(scalarsPerPos == 0 || SkToBool(pos));
127
128 int count = paint.countText(text, byteLen);
129
130 switch(paint.getTextEncoding()) {
131 case SkPaint::kGlyphID_TextEncoding: {
132 SkASSERT(count * sizeof(uint16_t) == byteLen);
133 SkAutoSTArray<64, SkUnichar> unichars(count);
134 paint.glyphsToUnichars((const uint16_t*)text, count, unichars.get());
135 for (int i = 0; i < count; ++i) {
136 this->appendUnichar(unichars[i]);
137 }
138 } break;
139 case SkPaint::kUTF8_TextEncoding: {
140 const char* c8 = reinterpret_cast<const char*>(text);
141 for (int i = 0; i < count; ++i) {
142 this->appendUnichar(SkUTF8_NextUnichar(&c8));
143 }
144 SkASSERT(reinterpret_cast<const char*>(text) + byteLen == c8);
145 } break;
146 case SkPaint::kUTF16_TextEncoding: {
147 const uint16_t* c16 = reinterpret_cast<const uint16_t*>(text);
148 for (int i = 0; i < count; ++i) {
149 this->appendUnichar(SkUTF16_NextUnichar(&c16));
150 }
151 SkASSERT(SkIsAlign2(byteLen));
152 SkASSERT(reinterpret_cast<const uint16_t*>(text) + (byteLen / 2) == c16);
153 } break;
154 case SkPaint::kUTF32_TextEncoding: {
155 SkASSERT(count * sizeof(uint32_t) == byteLen);
156 const uint32_t* c32 = reinterpret_cast<const uint32_t*>(text);
157 for (int i = 0; i < count; ++i) {
158 this->appendUnichar(c32[i]);
159 }
160 } break;
161 default:
162 SkFAIL("unknown text encoding");
163 }
164
165 if (scalarsPerPos < 2) {
166 SkASSERT(fPosY.isEmpty());
167 fPosY.appendScalar(offset.y()); // DrawText or DrawPosTextH (fixed Y).
168 }
169
170 if (scalarsPerPos < 1) {
171 SkASSERT(fPosX.isEmpty());
172 fPosX.appendScalar(offset.x()); // DrawText (X also fixed).
173 }
174 }
175
text() const176 const SkString& text() const { return fText; }
posX() const177 const SkString& posX() const { return fPosX; }
posY() const178 const SkString& posY() const { return fPosY; }
179
180 private:
appendUnichar(SkUnichar c)181 void appendUnichar(SkUnichar c) {
182 bool discardPos = false;
183 bool isWhitespace = false;
184
185 switch(c) {
186 case ' ':
187 case '\t':
188 // consolidate whitespace to match SVG's xml:space=default munging
189 // (http://www.w3.org/TR/SVG/text.html#WhiteSpace)
190 if (fLastCharWasWhitespace) {
191 discardPos = true;
192 } else {
193 fText.appendUnichar(c);
194 }
195 isWhitespace = true;
196 break;
197 case '\0':
198 // SkPaint::glyphsToUnichars() returns \0 for inconvertible glyphs, but these
199 // are not legal XML characters (http://www.w3.org/TR/REC-xml/#charsets)
200 discardPos = true;
201 isWhitespace = fLastCharWasWhitespace; // preserve whitespace consolidation
202 break;
203 case '&':
204 fText.append("&");
205 break;
206 case '"':
207 fText.append(""");
208 break;
209 case '\'':
210 fText.append("'");
211 break;
212 case '<':
213 fText.append("<");
214 break;
215 case '>':
216 fText.append(">");
217 break;
218 default:
219 fText.appendUnichar(c);
220 break;
221 }
222
223 this->advancePos(discardPos);
224 fLastCharWasWhitespace = isWhitespace;
225 }
226
advancePos(bool discard)227 void advancePos(bool discard) {
228 if (!discard && fScalarsPerPos > 0) {
229 fPosX.appendf("%.8g, ", fOffset.x() + fPos[0]);
230 if (fScalarsPerPos > 1) {
231 SkASSERT(fScalarsPerPos == 2);
232 fPosY.appendf("%.8g, ", fOffset.y() + fPos[1]);
233 }
234 }
235 fPos += fScalarsPerPos;
236 }
237
238 const SkPoint& fOffset;
239 const unsigned fScalarsPerPos;
240 const SkScalar* fPos;
241
242 SkString fText, fPosX, fPosY;
243 bool fLastCharWasWhitespace;
244 };
245
246 }
247
248 // For now all this does is serve unique serial IDs, but it will eventually evolve to track
249 // and deduplicate resources.
250 class SkSVGDevice::ResourceBucket : ::SkNoncopyable {
251 public:
ResourceBucket()252 ResourceBucket() : fGradientCount(0), fClipCount(0), fPathCount(0), fImageCount(0) {}
253
addLinearGradient()254 SkString addLinearGradient() {
255 return SkStringPrintf("gradient_%d", fGradientCount++);
256 }
257
addClip()258 SkString addClip() {
259 return SkStringPrintf("clip_%d", fClipCount++);
260 }
261
addPath()262 SkString addPath() {
263 return SkStringPrintf("path_%d", fPathCount++);
264 }
265
addImage()266 SkString addImage() {
267 return SkStringPrintf("img_%d", fImageCount++);
268 }
269
270 private:
271 uint32_t fGradientCount;
272 uint32_t fClipCount;
273 uint32_t fPathCount;
274 uint32_t fImageCount;
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->ctm()), 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[],SkXMLWriter * writer,ResourceBucket * bucket,const MxCp & mc,const SkPaint & paint)293 AutoElement(const char name[], SkXMLWriter* writer, ResourceBucket* bucket,
294 const MxCp& mc, const SkPaint& paint)
295 : fWriter(writer)
296 , fResourceBucket(bucket) {
297
298 Resources res = this->addResources(mc, paint);
299 if (!res.fClip.isEmpty()) {
300 // The clip is in device space. Apply it via a <g> wrapper to avoid local transform
301 // interference.
302 fClipGroup.reset(new AutoElement("g", fWriter));
303 fClipGroup->addAttribute("clip-path",res.fClip);
304 }
305
306 fWriter->startElement(name);
307
308 this->addPaint(paint, res);
309
310 if (!mc.fMatrix->isIdentity()) {
311 this->addAttribute("transform", svg_transform(*mc.fMatrix));
312 }
313 }
314
~AutoElement()315 ~AutoElement() {
316 fWriter->endElement();
317 }
318
addAttribute(const char name[],const char val[])319 void addAttribute(const char name[], const char val[]) {
320 fWriter->addAttribute(name, val);
321 }
322
addAttribute(const char name[],const SkString & val)323 void addAttribute(const char name[], const SkString& val) {
324 fWriter->addAttribute(name, val.c_str());
325 }
326
addAttribute(const char name[],int32_t val)327 void addAttribute(const char name[], int32_t val) {
328 fWriter->addS32Attribute(name, val);
329 }
330
addAttribute(const char name[],SkScalar val)331 void addAttribute(const char name[], SkScalar val) {
332 fWriter->addScalarAttribute(name, val);
333 }
334
addText(const SkString & text)335 void addText(const SkString& text) {
336 fWriter->addText(text.c_str(), text.size());
337 }
338
339 void addRectAttributes(const SkRect&);
340 void addPathAttributes(const SkPath&);
341 void addTextAttributes(const SkPaint&);
342
343 private:
344 Resources addResources(const MxCp&, const SkPaint& paint);
345 void addClipResources(const MxCp&, Resources* resources);
346 void addShaderResources(const SkPaint& paint, Resources* resources);
347
348 void addPaint(const SkPaint& paint, const Resources& resources);
349
350 SkString addLinearGradientDef(const SkShader::GradientInfo& info, const SkShader* shader);
351
352 SkXMLWriter* fWriter;
353 ResourceBucket* fResourceBucket;
354 std::unique_ptr<AutoElement> fClipGroup;
355 };
356
addPaint(const SkPaint & paint,const Resources & resources)357 void SkSVGDevice::AutoElement::addPaint(const SkPaint& paint, const Resources& resources) {
358 SkPaint::Style style = paint.getStyle();
359 if (style == SkPaint::kFill_Style || style == SkPaint::kStrokeAndFill_Style) {
360 this->addAttribute("fill", resources.fPaintServer);
361
362 if (SK_AlphaOPAQUE != SkColorGetA(paint.getColor())) {
363 this->addAttribute("fill-opacity", svg_opacity(paint.getColor()));
364 }
365 } else {
366 SkASSERT(style == SkPaint::kStroke_Style);
367 this->addAttribute("fill", "none");
368 }
369
370 if (style == SkPaint::kStroke_Style || style == SkPaint::kStrokeAndFill_Style) {
371 this->addAttribute("stroke", resources.fPaintServer);
372
373 SkScalar strokeWidth = paint.getStrokeWidth();
374 if (strokeWidth == 0) {
375 // Hairline stroke
376 strokeWidth = 1;
377 this->addAttribute("vector-effect", "non-scaling-stroke");
378 }
379 this->addAttribute("stroke-width", strokeWidth);
380
381 if (const char* cap = svg_cap(paint.getStrokeCap())) {
382 this->addAttribute("stroke-linecap", cap);
383 }
384
385 if (const char* join = svg_join(paint.getStrokeJoin())) {
386 this->addAttribute("stroke-linejoin", join);
387 }
388
389 if (paint.getStrokeJoin() == SkPaint::kMiter_Join) {
390 this->addAttribute("stroke-miterlimit", paint.getStrokeMiter());
391 }
392
393 if (SK_AlphaOPAQUE != SkColorGetA(paint.getColor())) {
394 this->addAttribute("stroke-opacity", svg_opacity(paint.getColor()));
395 }
396 } else {
397 SkASSERT(style == SkPaint::kFill_Style);
398 this->addAttribute("stroke", "none");
399 }
400 }
401
addResources(const MxCp & mc,const SkPaint & paint)402 Resources SkSVGDevice::AutoElement::addResources(const MxCp& mc, const SkPaint& paint) {
403 Resources resources(paint);
404
405 // FIXME: this is a weak heuristic and we end up with LOTS of redundant clips.
406 bool hasClip = !mc.fClipStack->isWideOpen();
407 bool hasShader = SkToBool(paint.getShader());
408
409 if (hasClip || hasShader) {
410 AutoElement defs("defs", fWriter);
411
412 if (hasClip) {
413 this->addClipResources(mc, &resources);
414 }
415
416 if (hasShader) {
417 this->addShaderResources(paint, &resources);
418 }
419 }
420
421 return resources;
422 }
423
addShaderResources(const SkPaint & paint,Resources * resources)424 void SkSVGDevice::AutoElement::addShaderResources(const SkPaint& paint, Resources* resources) {
425 const SkShader* shader = paint.getShader();
426 SkASSERT(SkToBool(shader));
427
428 SkShader::GradientInfo grInfo;
429 grInfo.fColorCount = 0;
430 if (SkShader::kLinear_GradientType != shader->asAGradient(&grInfo)) {
431 // TODO: non-linear gradient support
432 SkDebugf("unsupported shader type\n");
433 return;
434 }
435
436 SkAutoSTArray<16, SkColor> grColors(grInfo.fColorCount);
437 SkAutoSTArray<16, SkScalar> grOffsets(grInfo.fColorCount);
438 grInfo.fColors = grColors.get();
439 grInfo.fColorOffsets = grOffsets.get();
440
441 // One more call to get the actual colors/offsets.
442 shader->asAGradient(&grInfo);
443 SkASSERT(grInfo.fColorCount <= grColors.count());
444 SkASSERT(grInfo.fColorCount <= grOffsets.count());
445
446 resources->fPaintServer.printf("url(#%s)", addLinearGradientDef(grInfo, shader).c_str());
447 }
448
addClipResources(const MxCp & mc,Resources * resources)449 void SkSVGDevice::AutoElement::addClipResources(const MxCp& mc, Resources* resources) {
450 SkASSERT(!mc.fClipStack->isWideOpen());
451
452 SkPath clipPath;
453 (void) mc.fClipStack->asPath(&clipPath);
454
455 SkString clipID = fResourceBucket->addClip();
456 const char* clipRule = clipPath.getFillType() == SkPath::kEvenOdd_FillType ?
457 "evenodd" : "nonzero";
458 {
459 // clipPath is in device space, but since we're only pushing transform attributes
460 // to the leaf nodes, so are all our elements => SVG userSpaceOnUse == device space.
461 AutoElement clipPathElement("clipPath", fWriter);
462 clipPathElement.addAttribute("id", clipID);
463
464 SkRect clipRect = SkRect::MakeEmpty();
465 if (clipPath.isEmpty() || clipPath.isRect(&clipRect)) {
466 AutoElement rectElement("rect", fWriter);
467 rectElement.addRectAttributes(clipRect);
468 rectElement.addAttribute("clip-rule", clipRule);
469 } else {
470 AutoElement pathElement("path", fWriter);
471 pathElement.addPathAttributes(clipPath);
472 pathElement.addAttribute("clip-rule", clipRule);
473 }
474 }
475
476 resources->fClip.printf("url(#%s)", clipID.c_str());
477 }
478
addLinearGradientDef(const SkShader::GradientInfo & info,const SkShader * shader)479 SkString SkSVGDevice::AutoElement::addLinearGradientDef(const SkShader::GradientInfo& info,
480 const SkShader* shader) {
481 SkASSERT(fResourceBucket);
482 SkString id = fResourceBucket->addLinearGradient();
483
484 {
485 AutoElement gradient("linearGradient", fWriter);
486
487 gradient.addAttribute("id", id);
488 gradient.addAttribute("gradientUnits", "userSpaceOnUse");
489 gradient.addAttribute("x1", info.fPoint[0].x());
490 gradient.addAttribute("y1", info.fPoint[0].y());
491 gradient.addAttribute("x2", info.fPoint[1].x());
492 gradient.addAttribute("y2", info.fPoint[1].y());
493
494 if (!shader->getLocalMatrix().isIdentity()) {
495 this->addAttribute("gradientTransform", svg_transform(shader->getLocalMatrix()));
496 }
497
498 SkASSERT(info.fColorCount >= 2);
499 for (int i = 0; i < info.fColorCount; ++i) {
500 SkColor color = info.fColors[i];
501 SkString colorStr(svg_color(color));
502
503 {
504 AutoElement stop("stop", fWriter);
505 stop.addAttribute("offset", info.fColorOffsets[i]);
506 stop.addAttribute("stop-color", colorStr.c_str());
507
508 if (SK_AlphaOPAQUE != SkColorGetA(color)) {
509 stop.addAttribute("stop-opacity", svg_opacity(color));
510 }
511 }
512 }
513 }
514
515 return id;
516 }
517
addRectAttributes(const SkRect & rect)518 void SkSVGDevice::AutoElement::addRectAttributes(const SkRect& rect) {
519 // x, y default to 0
520 if (rect.x() != 0) {
521 this->addAttribute("x", rect.x());
522 }
523 if (rect.y() != 0) {
524 this->addAttribute("y", rect.y());
525 }
526
527 this->addAttribute("width", rect.width());
528 this->addAttribute("height", rect.height());
529 }
530
addPathAttributes(const SkPath & path)531 void SkSVGDevice::AutoElement::addPathAttributes(const SkPath& path) {
532 SkString pathData;
533 SkParsePath::ToSVGString(path, &pathData);
534 this->addAttribute("d", pathData);
535 }
536
addTextAttributes(const SkPaint & paint)537 void SkSVGDevice::AutoElement::addTextAttributes(const SkPaint& paint) {
538 this->addAttribute("font-size", paint.getTextSize());
539
540 if (const char* textAlign = svg_text_align(paint.getTextAlign())) {
541 this->addAttribute("text-anchor", textAlign);
542 }
543
544 SkString familyName;
545 SkTHashSet<SkString> familySet;
546 sk_sp<SkTypeface> tface(paint.getTypeface() ? paint.refTypeface() : SkTypeface::MakeDefault());
547
548 SkASSERT(tface);
549 SkTypeface::Style style = tface->style();
550 if (style & SkTypeface::kItalic) {
551 this->addAttribute("font-style", "italic");
552 }
553 if (style & SkTypeface::kBold) {
554 this->addAttribute("font-weight", "bold");
555 }
556
557 sk_sp<SkTypeface::LocalizedStrings> familyNameIter(tface->createFamilyNameIterator());
558 SkTypeface::LocalizedString familyString;
559 if (familyNameIter) {
560 while (familyNameIter->next(&familyString)) {
561 if (familySet.contains(familyString.fString)) {
562 continue;
563 }
564 familySet.add(familyString.fString);
565 familyName.appendf((familyName.isEmpty() ? "%s" : ", %s"), familyString.fString.c_str());
566 }
567 }
568 if (!familyName.isEmpty()) {
569 this->addAttribute("font-family", familyName);
570 }
571 }
572
Create(const SkISize & size,SkXMLWriter * writer)573 SkBaseDevice* SkSVGDevice::Create(const SkISize& size, SkXMLWriter* writer) {
574 if (!writer) {
575 return nullptr;
576 }
577
578 return new SkSVGDevice(size, writer);
579 }
580
SkSVGDevice(const SkISize & size,SkXMLWriter * writer)581 SkSVGDevice::SkSVGDevice(const SkISize& size, SkXMLWriter* writer)
582 : INHERITED(SkImageInfo::MakeUnknown(size.fWidth, size.fHeight),
583 SkSurfaceProps(0, kUnknown_SkPixelGeometry))
584 , fWriter(writer)
585 , fResourceBucket(new ResourceBucket)
586 {
587 SkASSERT(writer);
588
589 fWriter->writeHeader();
590
591 // The root <svg> tag gets closed by the destructor.
592 fRootElement.reset(new AutoElement("svg", fWriter));
593
594 fRootElement->addAttribute("xmlns", "http://www.w3.org/2000/svg");
595 fRootElement->addAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
596 fRootElement->addAttribute("width", size.width());
597 fRootElement->addAttribute("height", size.height());
598 }
599
~SkSVGDevice()600 SkSVGDevice::~SkSVGDevice() {
601 }
602
drawPaint(const SkPaint & paint)603 void SkSVGDevice::drawPaint(const SkPaint& paint) {
604 AutoElement rect("rect", fWriter, fResourceBucket.get(), MxCp(this), paint);
605 rect.addRectAttributes(SkRect::MakeWH(SkIntToScalar(this->width()),
606 SkIntToScalar(this->height())));
607 }
608
drawPoints(SkCanvas::PointMode mode,size_t count,const SkPoint pts[],const SkPaint & paint)609 void SkSVGDevice::drawPoints(SkCanvas::PointMode mode, size_t count,
610 const SkPoint pts[], const SkPaint& paint) {
611 SkPath path;
612
613 switch (mode) {
614 // todo
615 case SkCanvas::kPoints_PointMode:
616 SkDebugf("unsupported operation: drawPoints(kPoints_PointMode)\n");
617 break;
618 case SkCanvas::kLines_PointMode:
619 count -= 1;
620 for (size_t i = 0; i < count; i += 2) {
621 path.rewind();
622 path.moveTo(pts[i]);
623 path.lineTo(pts[i+1]);
624 AutoElement elem("path", fWriter, fResourceBucket.get(), MxCp(this), paint);
625 elem.addPathAttributes(path);
626 }
627 break;
628 case SkCanvas::kPolygon_PointMode:
629 if (count > 1) {
630 path.addPoly(pts, SkToInt(count), false);
631 path.moveTo(pts[0]);
632 AutoElement elem("path", fWriter, fResourceBucket.get(), MxCp(this), paint);
633 elem.addPathAttributes(path);
634 }
635 break;
636 }
637 }
638
drawRect(const SkRect & r,const SkPaint & paint)639 void SkSVGDevice::drawRect(const SkRect& r, const SkPaint& paint) {
640 AutoElement rect("rect", fWriter, fResourceBucket.get(), MxCp(this), paint);
641 rect.addRectAttributes(r);
642 }
643
drawOval(const SkRect & oval,const SkPaint & paint)644 void SkSVGDevice::drawOval(const SkRect& oval, const SkPaint& paint) {
645 AutoElement ellipse("ellipse", fWriter, fResourceBucket.get(), MxCp(this), paint);
646 ellipse.addAttribute("cx", oval.centerX());
647 ellipse.addAttribute("cy", oval.centerY());
648 ellipse.addAttribute("rx", oval.width() / 2);
649 ellipse.addAttribute("ry", oval.height() / 2);
650 }
651
drawRRect(const SkRRect & rr,const SkPaint & paint)652 void SkSVGDevice::drawRRect(const SkRRect& rr, const SkPaint& paint) {
653 SkPath path;
654 path.addRRect(rr);
655
656 AutoElement elem("path", fWriter, fResourceBucket.get(), MxCp(this), paint);
657 elem.addPathAttributes(path);
658 }
659
drawPath(const SkPath & path,const SkPaint & paint,const SkMatrix * prePathMatrix,bool pathIsMutable)660 void SkSVGDevice::drawPath(const SkPath& path, const SkPaint& paint,
661 const SkMatrix* prePathMatrix, bool pathIsMutable) {
662 AutoElement elem("path", fWriter, fResourceBucket.get(), MxCp(this), paint);
663 elem.addPathAttributes(path);
664
665 // TODO: inverse fill types?
666 if (path.getFillType() == SkPath::kEvenOdd_FillType) {
667 elem.addAttribute("fill-rule", "evenodd");
668 }
669 }
670
encode(const SkBitmap & src)671 static sk_sp<SkData> encode(const SkBitmap& src) {
672 SkDynamicMemoryWStream buf;
673 return SkEncodeImage(&buf, src, SkEncodedImageFormat::kPNG, 80) ? buf.detachAsData() : nullptr;
674 }
675
drawBitmapCommon(const MxCp & mc,const SkBitmap & bm,const SkPaint & paint)676 void SkSVGDevice::drawBitmapCommon(const MxCp& mc, const SkBitmap& bm, const SkPaint& paint) {
677 sk_sp<SkData> pngData = encode(bm);
678 if (!pngData) {
679 return;
680 }
681
682 size_t b64Size = SkBase64::Encode(pngData->data(), pngData->size(), nullptr);
683 SkAutoTMalloc<char> b64Data(b64Size);
684 SkBase64::Encode(pngData->data(), pngData->size(), b64Data.get());
685
686 SkString svgImageData("data:image/png;base64,");
687 svgImageData.append(b64Data.get(), b64Size);
688
689 SkString imageID = fResourceBucket->addImage();
690 {
691 AutoElement defs("defs", fWriter);
692 {
693 AutoElement image("image", fWriter);
694 image.addAttribute("id", imageID);
695 image.addAttribute("width", bm.width());
696 image.addAttribute("height", bm.height());
697 image.addAttribute("xlink:href", svgImageData);
698 }
699 }
700
701 {
702 AutoElement imageUse("use", fWriter, fResourceBucket.get(), mc, paint);
703 imageUse.addAttribute("xlink:href", SkStringPrintf("#%s", imageID.c_str()));
704 }
705 }
706
drawBitmap(const SkBitmap & bitmap,SkScalar x,SkScalar y,const SkPaint & paint)707 void SkSVGDevice::drawBitmap(const SkBitmap& bitmap, SkScalar x, SkScalar y,
708 const SkPaint& paint) {
709 MxCp mc(this);
710 SkMatrix adjustedMatrix = *mc.fMatrix;
711 adjustedMatrix.preTranslate(x, y);
712 mc.fMatrix = &adjustedMatrix;
713
714 drawBitmapCommon(mc, bitmap, paint);
715 }
716
drawSprite(const SkBitmap & bitmap,int x,int y,const SkPaint & paint)717 void SkSVGDevice::drawSprite(const SkBitmap& bitmap,
718 int x, int y, const SkPaint& paint) {
719 MxCp mc(this);
720 SkMatrix adjustedMatrix = *mc.fMatrix;
721 adjustedMatrix.preTranslate(SkIntToScalar(x), SkIntToScalar(y));
722 mc.fMatrix = &adjustedMatrix;
723
724 drawBitmapCommon(mc, bitmap, paint);
725 }
726
drawBitmapRect(const SkBitmap & bm,const SkRect * srcOrNull,const SkRect & dst,const SkPaint & paint,SkCanvas::SrcRectConstraint)727 void SkSVGDevice::drawBitmapRect(const SkBitmap& bm, const SkRect* srcOrNull,
728 const SkRect& dst, const SkPaint& paint,
729 SkCanvas::SrcRectConstraint) {
730 SkClipStack* cs = &this->cs();
731 SkClipStack::AutoRestore ar(cs, false);
732 if (srcOrNull && *srcOrNull != SkRect::Make(bm.bounds())) {
733 cs->save();
734 cs->clipRect(dst, this->ctm(), kIntersect_SkClipOp, paint.isAntiAlias());
735 }
736
737 SkMatrix adjustedMatrix;
738 adjustedMatrix.setRectToRect(srcOrNull ? *srcOrNull : SkRect::Make(bm.bounds()),
739 dst,
740 SkMatrix::kFill_ScaleToFit);
741 adjustedMatrix.postConcat(this->ctm());
742
743 drawBitmapCommon(MxCp(&adjustedMatrix, cs), bm, paint);
744 }
745
drawText(const void * text,size_t len,SkScalar x,SkScalar y,const SkPaint & paint)746 void SkSVGDevice::drawText(const void* text, size_t len,
747 SkScalar x, SkScalar y, const SkPaint& paint) {
748 AutoElement elem("text", fWriter, fResourceBucket.get(), MxCp(this), paint);
749 elem.addTextAttributes(paint);
750
751 SVGTextBuilder builder(text, len, paint, SkPoint::Make(x, y), 0);
752 elem.addAttribute("x", builder.posX());
753 elem.addAttribute("y", builder.posY());
754 elem.addText(builder.text());
755 }
756
drawPosText(const void * text,size_t len,const SkScalar pos[],int scalarsPerPos,const SkPoint & offset,const SkPaint & paint)757 void SkSVGDevice::drawPosText(const void* text, size_t len,
758 const SkScalar pos[], int scalarsPerPos, const SkPoint& offset,
759 const SkPaint& paint) {
760 SkASSERT(scalarsPerPos == 1 || scalarsPerPos == 2);
761
762 AutoElement elem("text", fWriter, fResourceBucket.get(), MxCp(this), paint);
763 elem.addTextAttributes(paint);
764
765 SVGTextBuilder builder(text, len, paint, offset, scalarsPerPos, pos);
766 elem.addAttribute("x", builder.posX());
767 elem.addAttribute("y", builder.posY());
768 elem.addText(builder.text());
769 }
770
drawTextOnPath(const void * text,size_t len,const SkPath & path,const SkMatrix * matrix,const SkPaint & paint)771 void SkSVGDevice::drawTextOnPath(const void* text, size_t len, const SkPath& path,
772 const SkMatrix* matrix, const SkPaint& paint) {
773 SkString pathID = fResourceBucket->addPath();
774
775 {
776 AutoElement defs("defs", fWriter);
777 AutoElement pathElement("path", fWriter);
778 pathElement.addAttribute("id", pathID);
779 pathElement.addPathAttributes(path);
780
781 }
782
783 {
784 AutoElement textElement("text", fWriter);
785 textElement.addTextAttributes(paint);
786
787 if (matrix && !matrix->isIdentity()) {
788 textElement.addAttribute("transform", svg_transform(*matrix));
789 }
790
791 {
792 AutoElement textPathElement("textPath", fWriter);
793 textPathElement.addAttribute("xlink:href", SkStringPrintf("#%s", pathID.c_str()));
794
795 if (paint.getTextAlign() != SkPaint::kLeft_Align) {
796 SkASSERT(paint.getTextAlign() == SkPaint::kCenter_Align ||
797 paint.getTextAlign() == SkPaint::kRight_Align);
798 textPathElement.addAttribute("startOffset",
799 paint.getTextAlign() == SkPaint::kCenter_Align ? "50%" : "100%");
800 }
801
802 SVGTextBuilder builder(text, len, paint, SkPoint::Make(0, 0), 0);
803 textPathElement.addText(builder.text());
804 }
805 }
806 }
807
drawVertices(const SkVertices *,SkBlendMode,const SkPaint &)808 void SkSVGDevice::drawVertices(const SkVertices*, SkBlendMode, const SkPaint&) {
809 // todo
810 SkDebugf("unsupported operation: drawVertices()\n");
811 }
812
drawDevice(SkBaseDevice *,int x,int y,const SkPaint &)813 void SkSVGDevice::drawDevice(SkBaseDevice*, int x, int y,
814 const SkPaint&) {
815 // todo
816 SkDebugf("unsupported operation: drawDevice()\n");
817 }
818