blob: 203be3ae8b1b7338880d8f052f16d7fda35d6184 [file] [log] [blame]
fmalita93957f42015-01-30 09:03:29 -08001/*
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
fmalita827da232015-02-27 07:44:47 -080010#include "SkBase64.h"
fmalita93957f42015-01-30 09:03:29 -080011#include "SkBitmap.h"
fmalitaf89f60f2015-02-13 08:55:24 -080012#include "SkChecksum.h"
bungemand3ebb482015-08-05 13:57:49 -070013#include "SkClipStack.h"
fmalita827da232015-02-27 07:44:47 -080014#include "SkData.h"
fmalita93957f42015-01-30 09:03:29 -080015#include "SkDraw.h"
fmalita827da232015-02-27 07:44:47 -080016#include "SkImageEncoder.h"
fmalita93957f42015-01-30 09:03:29 -080017#include "SkPaint.h"
18#include "SkParsePath.h"
fmalita532faa92015-02-03 05:44:40 -080019#include "SkShader.h"
fmalita93957f42015-01-30 09:03:29 -080020#include "SkStream.h"
fmalitaf89f60f2015-02-13 08:55:24 -080021#include "SkTHash.h"
fmalitafe3f2602015-02-03 17:47:12 -080022#include "SkTypeface.h"
23#include "SkUtils.h"
fmalita93957f42015-01-30 09:03:29 -080024#include "SkXMLWriter.h"
25
26namespace {
27
fmalita532faa92015-02-03 05:44:40 -080028static SkString svg_color(SkColor color) {
fmalita1a481fe2015-02-04 07:39:34 -080029 return SkStringPrintf("rgb(%u,%u,%u)",
30 SkColorGetR(color),
31 SkColorGetG(color),
32 SkColorGetB(color));
fmalita532faa92015-02-03 05:44:40 -080033}
34
35static SkScalar svg_opacity(SkColor color) {
36 return SkIntToScalar(SkColorGetA(color)) / SK_AlphaOPAQUE;
37}
38
fmalita12753cc2015-02-04 14:56:35 -080039// Keep in sync with SkPaint::Cap
40static const char* cap_map[] = {
halcanary96fcdcc2015-08-27 07:41:13 -070041 nullptr, // kButt_Cap (default)
fmalita12753cc2015-02-04 14:56:35 -080042 "round", // kRound_Cap
43 "square" // kSquare_Cap
44};
bungeman99fe8222015-08-20 07:57:51 -070045static_assert(SK_ARRAY_COUNT(cap_map) == SkPaint::kCapCount, "missing_cap_map_entry");
fmalita12753cc2015-02-04 14:56:35 -080046
47static const char* svg_cap(SkPaint::Cap cap) {
48 SkASSERT(cap < SK_ARRAY_COUNT(cap_map));
49 return cap_map[cap];
50}
51
52// Keep in sync with SkPaint::Join
53static const char* join_map[] = {
halcanary96fcdcc2015-08-27 07:41:13 -070054 nullptr, // kMiter_Join (default)
fmalita12753cc2015-02-04 14:56:35 -080055 "round", // kRound_Join
56 "bevel" // kBevel_Join
57};
bungeman99fe8222015-08-20 07:57:51 -070058static_assert(SK_ARRAY_COUNT(join_map) == SkPaint::kJoinCount, "missing_join_map_entry");
fmalita12753cc2015-02-04 14:56:35 -080059
60static const char* svg_join(SkPaint::Join join) {
61 SkASSERT(join < SK_ARRAY_COUNT(join_map));
62 return join_map[join];
63}
64
fmalitaa9d9de42015-02-04 17:54:46 -080065// Keep in sync with SkPaint::Align
66static const char* text_align_map[] = {
halcanary96fcdcc2015-08-27 07:41:13 -070067 nullptr, // kLeft_Align (default)
fmalitaa9d9de42015-02-04 17:54:46 -080068 "middle", // kCenter_Align
69 "end" // kRight_Align
70};
bungeman99fe8222015-08-20 07:57:51 -070071static_assert(SK_ARRAY_COUNT(text_align_map) == SkPaint::kAlignCount,
72 "missing_text_align_map_entry");
fmalitaa9d9de42015-02-04 17:54:46 -080073static const char* svg_text_align(SkPaint::Align align) {
74 SkASSERT(align < SK_ARRAY_COUNT(text_align_map));
75 return text_align_map[align];
76}
77
78static SkString svg_transform(const SkMatrix& t) {
79 SkASSERT(!t.isIdentity());
80
81 SkString tstr;
82 switch (t.getType()) {
83 case SkMatrix::kPerspective_Mask:
84 SkDebugf("Can't handle perspective matrices.");
85 break;
86 case SkMatrix::kTranslate_Mask:
87 tstr.printf("translate(%g %g)", t.getTranslateX(), t.getTranslateY());
88 break;
89 case SkMatrix::kScale_Mask:
90 tstr.printf("scale(%g %g)", t.getScaleX(), t.getScaleY());
91 break;
92 default:
93 // http://www.w3.org/TR/SVG/coords.html#TransformMatrixDefined
94 // | a c e |
95 // | b d f |
96 // | 0 0 1 |
97 tstr.printf("matrix(%g %g %g %g %g %g)",
98 t.getScaleX(), t.getSkewY(),
99 t.getSkewX(), t.getScaleY(),
100 t.getTranslateX(), t.getTranslateY());
101 break;
102 }
103
104 return tstr;
105}
106
fmalita532faa92015-02-03 05:44:40 -0800107struct Resources {
108 Resources(const SkPaint& paint)
109 : fPaintServer(svg_color(paint.getColor())) {}
110
111 SkString fPaintServer;
fmalita1a481fe2015-02-04 07:39:34 -0800112 SkString fClip;
fmalita532faa92015-02-03 05:44:40 -0800113};
114
fmalita89747742015-02-19 18:44:51 -0800115class SVGTextBuilder : SkNoncopyable {
116public:
117 SVGTextBuilder(const void* text, size_t byteLen, const SkPaint& paint, const SkPoint& offset,
halcanary96fcdcc2015-08-27 07:41:13 -0700118 unsigned scalarsPerPos, const SkScalar pos[] = nullptr)
fmalita89747742015-02-19 18:44:51 -0800119 : fOffset(offset)
120 , fScalarsPerPos(scalarsPerPos)
121 , fPos(pos)
122 , fLastCharWasWhitespace(true) // start off in whitespace mode to strip all leading space
123 {
124 SkASSERT(scalarsPerPos <= 2);
125 SkASSERT(scalarsPerPos == 0 || SkToBool(pos));
126
127 int count = paint.countText(text, byteLen);
128
129 switch(paint.getTextEncoding()) {
130 case SkPaint::kGlyphID_TextEncoding: {
131 SkASSERT(count * sizeof(uint16_t) == byteLen);
132 SkAutoSTArray<64, SkUnichar> unichars(count);
133 paint.glyphsToUnichars((const uint16_t*)text, count, unichars.get());
134 for (int i = 0; i < count; ++i) {
135 this->appendUnichar(unichars[i]);
136 }
137 } break;
138 case SkPaint::kUTF8_TextEncoding: {
139 const char* c8 = reinterpret_cast<const char*>(text);
140 for (int i = 0; i < count; ++i) {
141 this->appendUnichar(SkUTF8_NextUnichar(&c8));
142 }
143 SkASSERT(reinterpret_cast<const char*>(text) + byteLen == c8);
144 } break;
145 case SkPaint::kUTF16_TextEncoding: {
146 const uint16_t* c16 = reinterpret_cast<const uint16_t*>(text);
147 for (int i = 0; i < count; ++i) {
148 this->appendUnichar(SkUTF16_NextUnichar(&c16));
149 }
150 SkASSERT(SkIsAlign2(byteLen));
151 SkASSERT(reinterpret_cast<const uint16_t*>(text) + (byteLen / 2) == c16);
152 } break;
153 case SkPaint::kUTF32_TextEncoding: {
154 SkASSERT(count * sizeof(uint32_t) == byteLen);
155 const uint32_t* c32 = reinterpret_cast<const uint32_t*>(text);
156 for (int i = 0; i < count; ++i) {
157 this->appendUnichar(c32[i]);
158 }
159 } break;
160 default:
161 SkFAIL("unknown text encoding");
162 }
163
164 if (scalarsPerPos < 2) {
165 SkASSERT(fPosY.isEmpty());
166 fPosY.appendScalar(offset.y()); // DrawText or DrawPosTextH (fixed Y).
167 }
168
169 if (scalarsPerPos < 1) {
170 SkASSERT(fPosX.isEmpty());
171 fPosX.appendScalar(offset.x()); // DrawText (X also fixed).
172 }
173 }
174
175 const SkString& text() const { return fText; }
176 const SkString& posX() const { return fPosX; }
177 const SkString& posY() const { return fPosY; }
178
179private:
180 void appendUnichar(SkUnichar c) {
181 bool discardPos = false;
182 bool isWhitespace = false;
183
184 switch(c) {
185 case ' ':
186 case '\t':
187 // consolidate whitespace to match SVG's xml:space=default munging
188 // (http://www.w3.org/TR/SVG/text.html#WhiteSpace)
189 if (fLastCharWasWhitespace) {
190 discardPos = true;
191 } else {
192 fText.appendUnichar(c);
193 }
194 isWhitespace = true;
195 break;
196 case '\0':
197 // SkPaint::glyphsToUnichars() returns \0 for inconvertible glyphs, but these
198 // are not legal XML characters (http://www.w3.org/TR/REC-xml/#charsets)
199 discardPos = true;
200 isWhitespace = fLastCharWasWhitespace; // preserve whitespace consolidation
201 break;
202 case '&':
203 fText.append("&amp;");
204 break;
205 case '"':
206 fText.append("&quot;");
207 break;
208 case '\'':
209 fText.append("&apos;");
210 break;
211 case '<':
212 fText.append("&lt;");
213 break;
214 case '>':
215 fText.append("&gt;");
216 break;
217 default:
218 fText.appendUnichar(c);
219 break;
220 }
221
222 this->advancePos(discardPos);
223 fLastCharWasWhitespace = isWhitespace;
224 }
225
226 void advancePos(bool discard) {
227 if (!discard && fScalarsPerPos > 0) {
228 fPosX.appendf("%.8g, ", fOffset.x() + fPos[0]);
229 if (fScalarsPerPos > 1) {
230 SkASSERT(fScalarsPerPos == 2);
231 fPosY.appendf("%.8g, ", fOffset.y() + fPos[1]);
232 }
233 }
234 fPos += fScalarsPerPos;
235 }
236
237 const SkPoint& fOffset;
238 const unsigned fScalarsPerPos;
239 const SkScalar* fPos;
240
241 SkString fText, fPosX, fPosY;
242 bool fLastCharWasWhitespace;
243};
244
fmalita532faa92015-02-03 05:44:40 -0800245}
246
247// For now all this does is serve unique serial IDs, but it will eventually evolve to track
248// and deduplicate resources.
249class SkSVGDevice::ResourceBucket : ::SkNoncopyable {
250public:
fmalita827da232015-02-27 07:44:47 -0800251 ResourceBucket() : fGradientCount(0), fClipCount(0), fPathCount(0), fImageCount(0) {}
fmalita532faa92015-02-03 05:44:40 -0800252
253 SkString addLinearGradient() {
fmalita1a481fe2015-02-04 07:39:34 -0800254 return SkStringPrintf("gradient_%d", fGradientCount++);
255 }
256
257 SkString addClip() {
258 return SkStringPrintf("clip_%d", fClipCount++);
fmalita532faa92015-02-03 05:44:40 -0800259 }
260
fmalitaa9d9de42015-02-04 17:54:46 -0800261 SkString addPath() {
262 return SkStringPrintf("path_%d", fPathCount++);
263 }
264
fmalita827da232015-02-27 07:44:47 -0800265 SkString addImage() {
266 return SkStringPrintf("img_%d", fImageCount++);
267 }
268
fmalita532faa92015-02-03 05:44:40 -0800269private:
270 uint32_t fGradientCount;
fmalita1a481fe2015-02-04 07:39:34 -0800271 uint32_t fClipCount;
fmalitaa9d9de42015-02-04 17:54:46 -0800272 uint32_t fPathCount;
fmalita827da232015-02-27 07:44:47 -0800273 uint32_t fImageCount;
fmalita532faa92015-02-03 05:44:40 -0800274};
275
276class SkSVGDevice::AutoElement : ::SkNoncopyable {
fmalita93957f42015-01-30 09:03:29 -0800277public:
278 AutoElement(const char name[], SkXMLWriter* writer)
fmalita532faa92015-02-03 05:44:40 -0800279 : fWriter(writer)
halcanary96fcdcc2015-08-27 07:41:13 -0700280 , fResourceBucket(nullptr) {
fmalita93957f42015-01-30 09:03:29 -0800281 fWriter->startElement(name);
282 }
283
fmalita532faa92015-02-03 05:44:40 -0800284 AutoElement(const char name[], SkXMLWriter* writer, ResourceBucket* bucket,
285 const SkDraw& draw, const SkPaint& paint)
286 : fWriter(writer)
287 , fResourceBucket(bucket) {
288
fmalita1a481fe2015-02-04 07:39:34 -0800289 Resources res = this->addResources(draw, paint);
fmalita827da232015-02-27 07:44:47 -0800290 if (!res.fClip.isEmpty()) {
291 // The clip is in device space. Apply it via a <g> wrapper to avoid local transform
292 // interference.
halcanary385fe4d2015-08-26 13:07:48 -0700293 fClipGroup.reset(new AutoElement("g", fWriter));
fmalita827da232015-02-27 07:44:47 -0800294 fClipGroup->addAttribute("clip-path",res.fClip);
295 }
fmalita532faa92015-02-03 05:44:40 -0800296
297 fWriter->startElement(name);
298
299 this->addPaint(paint, res);
fmalitaa9d9de42015-02-04 17:54:46 -0800300
301 if (!draw.fMatrix->isIdentity()) {
302 this->addAttribute("transform", svg_transform(*draw.fMatrix));
303 }
fmalita532faa92015-02-03 05:44:40 -0800304 }
305
fmalita93957f42015-01-30 09:03:29 -0800306 ~AutoElement() {
307 fWriter->endElement();
308 }
309
fmalita532faa92015-02-03 05:44:40 -0800310 void addAttribute(const char name[], const char val[]) {
311 fWriter->addAttribute(name, val);
312 }
313
314 void addAttribute(const char name[], const SkString& val) {
315 fWriter->addAttribute(name, val.c_str());
316 }
317
318 void addAttribute(const char name[], int32_t val) {
319 fWriter->addS32Attribute(name, val);
320 }
321
322 void addAttribute(const char name[], SkScalar val) {
323 fWriter->addScalarAttribute(name, val);
324 }
325
fmalitafe3f2602015-02-03 17:47:12 -0800326 void addText(const SkString& text) {
reede73da402015-02-04 18:29:27 -0800327 fWriter->addText(text.c_str(), text.size());
fmalitafe3f2602015-02-03 17:47:12 -0800328 }
329
fmalita1a481fe2015-02-04 07:39:34 -0800330 void addRectAttributes(const SkRect&);
fmalitaa9d9de42015-02-04 17:54:46 -0800331 void addPathAttributes(const SkPath&);
332 void addTextAttributes(const SkPaint&);
fmalitafe3f2602015-02-03 17:47:12 -0800333
fmalita93957f42015-01-30 09:03:29 -0800334private:
fmalita1a481fe2015-02-04 07:39:34 -0800335 Resources addResources(const SkDraw& draw, const SkPaint& paint);
336 void addClipResources(const SkDraw& draw, Resources* resources);
337 void addShaderResources(const SkPaint& paint, Resources* resources);
fmalita532faa92015-02-03 05:44:40 -0800338
339 void addPaint(const SkPaint& paint, const Resources& resources);
fmalita532faa92015-02-03 05:44:40 -0800340
341 SkString addLinearGradientDef(const SkShader::GradientInfo& info, const SkShader* shader);
342
fmalita827da232015-02-27 07:44:47 -0800343 SkXMLWriter* fWriter;
344 ResourceBucket* fResourceBucket;
Ben Wagner145dbcd2016-11-03 14:40:50 -0400345 std::unique_ptr<AutoElement> fClipGroup;
fmalita93957f42015-01-30 09:03:29 -0800346};
347
fmalita532faa92015-02-03 05:44:40 -0800348void SkSVGDevice::AutoElement::addPaint(const SkPaint& paint, const Resources& resources) {
349 SkPaint::Style style = paint.getStyle();
350 if (style == SkPaint::kFill_Style || style == SkPaint::kStrokeAndFill_Style) {
351 this->addAttribute("fill", resources.fPaintServer);
fmalita12753cc2015-02-04 14:56:35 -0800352
353 if (SK_AlphaOPAQUE != SkColorGetA(paint.getColor())) {
354 this->addAttribute("fill-opacity", svg_opacity(paint.getColor()));
355 }
fmalita532faa92015-02-03 05:44:40 -0800356 } else {
fmalita12753cc2015-02-04 14:56:35 -0800357 SkASSERT(style == SkPaint::kStroke_Style);
fmalita532faa92015-02-03 05:44:40 -0800358 this->addAttribute("fill", "none");
359 }
360
361 if (style == SkPaint::kStroke_Style || style == SkPaint::kStrokeAndFill_Style) {
362 this->addAttribute("stroke", resources.fPaintServer);
fmalita532faa92015-02-03 05:44:40 -0800363
fmalita12753cc2015-02-04 14:56:35 -0800364 SkScalar strokeWidth = paint.getStrokeWidth();
365 if (strokeWidth == 0) {
366 // Hairline stroke
367 strokeWidth = 1;
368 this->addAttribute("vector-effect", "non-scaling-stroke");
369 }
370 this->addAttribute("stroke-width", strokeWidth);
371
372 if (const char* cap = svg_cap(paint.getStrokeCap())) {
373 this->addAttribute("stroke-linecap", cap);
374 }
375
376 if (const char* join = svg_join(paint.getStrokeJoin())) {
377 this->addAttribute("stroke-linejoin", join);
378 }
379
380 if (paint.getStrokeJoin() == SkPaint::kMiter_Join) {
381 this->addAttribute("stroke-miterlimit", paint.getStrokeMiter());
382 }
383
384 if (SK_AlphaOPAQUE != SkColorGetA(paint.getColor())) {
385 this->addAttribute("stroke-opacity", svg_opacity(paint.getColor()));
386 }
387 } else {
388 SkASSERT(style == SkPaint::kFill_Style);
389 this->addAttribute("stroke", "none");
fmalita532faa92015-02-03 05:44:40 -0800390 }
391}
392
fmalita1a481fe2015-02-04 07:39:34 -0800393Resources SkSVGDevice::AutoElement::addResources(const SkDraw& draw, const SkPaint& paint) {
fmalita532faa92015-02-03 05:44:40 -0800394 Resources resources(paint);
fmalita1a481fe2015-02-04 07:39:34 -0800395
396 // FIXME: this is a weak heuristic and we end up with LOTS of redundant clips.
397 bool hasClip = !draw.fClipStack->isWideOpen();
398 bool hasShader = SkToBool(paint.getShader());
399
400 if (hasClip || hasShader) {
401 AutoElement defs("defs", fWriter);
402
403 if (hasClip) {
404 this->addClipResources(draw, &resources);
405 }
406
407 if (hasShader) {
408 this->addShaderResources(paint, &resources);
409 }
410 }
411
fmalita532faa92015-02-03 05:44:40 -0800412 return resources;
413}
414
fmalita1a481fe2015-02-04 07:39:34 -0800415void SkSVGDevice::AutoElement::addShaderResources(const SkPaint& paint, Resources* resources) {
fmalita532faa92015-02-03 05:44:40 -0800416 const SkShader* shader = paint.getShader();
fmalita1a481fe2015-02-04 07:39:34 -0800417 SkASSERT(SkToBool(shader));
fmalita532faa92015-02-03 05:44:40 -0800418
419 SkShader::GradientInfo grInfo;
420 grInfo.fColorCount = 0;
421 if (SkShader::kLinear_GradientType != shader->asAGradient(&grInfo)) {
422 // TODO: non-linear gradient support
423 SkDebugf("unsupported shader type\n");
424 return;
425 }
426
fmalita1a481fe2015-02-04 07:39:34 -0800427 SkAutoSTArray<16, SkColor> grColors(grInfo.fColorCount);
428 SkAutoSTArray<16, SkScalar> grOffsets(grInfo.fColorCount);
429 grInfo.fColors = grColors.get();
430 grInfo.fColorOffsets = grOffsets.get();
431
432 // One more call to get the actual colors/offsets.
433 shader->asAGradient(&grInfo);
434 SkASSERT(grInfo.fColorCount <= grColors.count());
435 SkASSERT(grInfo.fColorCount <= grOffsets.count());
436
437 resources->fPaintServer.printf("url(#%s)", addLinearGradientDef(grInfo, shader).c_str());
438}
439
440void SkSVGDevice::AutoElement::addClipResources(const SkDraw& draw, Resources* resources) {
441 SkASSERT(!draw.fClipStack->isWideOpen());
442
443 SkPath clipPath;
444 (void) draw.fClipStack->asPath(&clipPath);
445
446 SkString clipID = fResourceBucket->addClip();
447 const char* clipRule = clipPath.getFillType() == SkPath::kEvenOdd_FillType ?
448 "evenodd" : "nonzero";
fmalita532faa92015-02-03 05:44:40 -0800449 {
fmalita1a481fe2015-02-04 07:39:34 -0800450 // clipPath is in device space, but since we're only pushing transform attributes
451 // to the leaf nodes, so are all our elements => SVG userSpaceOnUse == device space.
452 AutoElement clipPathElement("clipPath", fWriter);
453 clipPathElement.addAttribute("id", clipID);
fmalita532faa92015-02-03 05:44:40 -0800454
fmalita1a481fe2015-02-04 07:39:34 -0800455 SkRect clipRect = SkRect::MakeEmpty();
456 if (clipPath.isEmpty() || clipPath.isRect(&clipRect)) {
457 AutoElement rectElement("rect", fWriter);
458 rectElement.addRectAttributes(clipRect);
459 rectElement.addAttribute("clip-rule", clipRule);
460 } else {
461 AutoElement pathElement("path", fWriter);
fmalitaa9d9de42015-02-04 17:54:46 -0800462 pathElement.addPathAttributes(clipPath);
fmalita1a481fe2015-02-04 07:39:34 -0800463 pathElement.addAttribute("clip-rule", clipRule);
464 }
fmalita532faa92015-02-03 05:44:40 -0800465 }
fmalita1a481fe2015-02-04 07:39:34 -0800466
467 resources->fClip.printf("url(#%s)", clipID.c_str());
fmalita532faa92015-02-03 05:44:40 -0800468}
469
470SkString SkSVGDevice::AutoElement::addLinearGradientDef(const SkShader::GradientInfo& info,
471 const SkShader* shader) {
472 SkASSERT(fResourceBucket);
473 SkString id = fResourceBucket->addLinearGradient();
474
475 {
476 AutoElement gradient("linearGradient", fWriter);
477
478 gradient.addAttribute("id", id);
479 gradient.addAttribute("gradientUnits", "userSpaceOnUse");
480 gradient.addAttribute("x1", info.fPoint[0].x());
481 gradient.addAttribute("y1", info.fPoint[0].y());
482 gradient.addAttribute("x2", info.fPoint[1].x());
483 gradient.addAttribute("y2", info.fPoint[1].y());
fmalitaa9d9de42015-02-04 17:54:46 -0800484
485 if (!shader->getLocalMatrix().isIdentity()) {
486 this->addAttribute("gradientTransform", svg_transform(shader->getLocalMatrix()));
487 }
fmalita532faa92015-02-03 05:44:40 -0800488
489 SkASSERT(info.fColorCount >= 2);
490 for (int i = 0; i < info.fColorCount; ++i) {
491 SkColor color = info.fColors[i];
492 SkString colorStr(svg_color(color));
493
494 {
495 AutoElement stop("stop", fWriter);
496 stop.addAttribute("offset", info.fColorOffsets[i]);
497 stop.addAttribute("stop-color", colorStr.c_str());
498
499 if (SK_AlphaOPAQUE != SkColorGetA(color)) {
500 stop.addAttribute("stop-opacity", svg_opacity(color));
501 }
502 }
503 }
504 }
505
506 return id;
fmalita93957f42015-01-30 09:03:29 -0800507}
508
fmalita1a481fe2015-02-04 07:39:34 -0800509void SkSVGDevice::AutoElement::addRectAttributes(const SkRect& rect) {
510 // x, y default to 0
511 if (rect.x() != 0) {
512 this->addAttribute("x", rect.x());
513 }
514 if (rect.y() != 0) {
515 this->addAttribute("y", rect.y());
516 }
517
518 this->addAttribute("width", rect.width());
519 this->addAttribute("height", rect.height());
520}
521
fmalitaa9d9de42015-02-04 17:54:46 -0800522void SkSVGDevice::AutoElement::addPathAttributes(const SkPath& path) {
523 SkString pathData;
524 SkParsePath::ToSVGString(path, &pathData);
525 this->addAttribute("d", pathData);
526}
527
528void SkSVGDevice::AutoElement::addTextAttributes(const SkPaint& paint) {
fmalitafe3f2602015-02-03 17:47:12 -0800529 this->addAttribute("font-size", paint.getTextSize());
530
fmalitaa9d9de42015-02-04 17:54:46 -0800531 if (const char* textAlign = svg_text_align(paint.getTextAlign())) {
532 this->addAttribute("text-anchor", textAlign);
533 }
fmalitaf89f60f2015-02-13 08:55:24 -0800534
535 SkString familyName;
mtklein02f46cf2015-03-20 13:48:42 -0700536 SkTHashSet<SkString> familySet;
bungeman13b9c952016-05-12 10:09:30 -0700537 sk_sp<const SkTypeface> tface(paint.getTypeface() ?
538 sk_ref_sp(paint.getTypeface()) : SkTypeface::MakeDefault());
fmalita7a048692015-02-20 13:54:40 -0800539
540 SkASSERT(tface);
541 SkTypeface::Style style = tface->style();
542 if (style & SkTypeface::kItalic) {
543 this->addAttribute("font-style", "italic");
544 }
545 if (style & SkTypeface::kBold) {
546 this->addAttribute("font-weight", "bold");
547 }
548
fmalitaf89f60f2015-02-13 08:55:24 -0800549 SkAutoTUnref<SkTypeface::LocalizedStrings> familyNameIter(tface->createFamilyNameIterator());
550 SkTypeface::LocalizedString familyString;
551 while (familyNameIter->next(&familyString)) {
552 if (familySet.contains(familyString.fString)) {
553 continue;
554 }
555 familySet.add(familyString.fString);
556 familyName.appendf((familyName.isEmpty() ? "%s" : ", %s"), familyString.fString.c_str());
557 }
558
559 if (!familyName.isEmpty()) {
560 this->addAttribute("font-family", familyName);
561 }
fmalitafe3f2602015-02-03 17:47:12 -0800562}
563
fmalita2aafe6f2015-02-06 12:51:10 -0800564SkBaseDevice* SkSVGDevice::Create(const SkISize& size, SkXMLWriter* writer) {
565 if (!writer) {
halcanary96fcdcc2015-08-27 07:41:13 -0700566 return nullptr;
fmalita93957f42015-01-30 09:03:29 -0800567 }
568
halcanary385fe4d2015-08-26 13:07:48 -0700569 return new SkSVGDevice(size, writer);
fmalita93957f42015-01-30 09:03:29 -0800570}
571
fmalita2aafe6f2015-02-06 12:51:10 -0800572SkSVGDevice::SkSVGDevice(const SkISize& size, SkXMLWriter* writer)
reed589a39e2016-08-20 07:59:19 -0700573 : INHERITED(SkImageInfo::MakeUnknown(size.fWidth, size.fHeight),
574 SkSurfaceProps(0, kUnknown_SkPixelGeometry))
robertphillips9a53fd72015-06-22 09:46:59 -0700575 , fWriter(writer)
robertphillips1f3923e2016-07-21 07:17:54 -0700576 , fResourceBucket(new ResourceBucket)
reed589a39e2016-08-20 07:59:19 -0700577{
fmalita2aafe6f2015-02-06 12:51:10 -0800578 SkASSERT(writer);
fmalita93957f42015-01-30 09:03:29 -0800579
fmalita93957f42015-01-30 09:03:29 -0800580 fWriter->writeHeader();
fmalita532faa92015-02-03 05:44:40 -0800581
582 // The root <svg> tag gets closed by the destructor.
halcanary385fe4d2015-08-26 13:07:48 -0700583 fRootElement.reset(new AutoElement("svg", fWriter));
fmalita532faa92015-02-03 05:44:40 -0800584
585 fRootElement->addAttribute("xmlns", "http://www.w3.org/2000/svg");
586 fRootElement->addAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
587 fRootElement->addAttribute("width", size.width());
588 fRootElement->addAttribute("height", size.height());
fmalita93957f42015-01-30 09:03:29 -0800589}
590
591SkSVGDevice::~SkSVGDevice() {
fmalita93957f42015-01-30 09:03:29 -0800592}
593
fmalita532faa92015-02-03 05:44:40 -0800594void SkSVGDevice::drawPaint(const SkDraw& draw, const SkPaint& paint) {
Ben Wagner145dbcd2016-11-03 14:40:50 -0400595 AutoElement rect("rect", fWriter, fResourceBucket.get(), draw, paint);
fmalita1a481fe2015-02-04 07:39:34 -0800596 rect.addRectAttributes(SkRect::MakeWH(SkIntToScalar(this->width()),
597 SkIntToScalar(this->height())));
fmalita93957f42015-01-30 09:03:29 -0800598}
599
reed5f68c342015-05-15 10:11:11 -0700600void SkSVGDevice::drawPoints(const SkDraw& draw, SkCanvas::PointMode mode, size_t count,
601 const SkPoint pts[], const SkPaint& paint) {
602 SkPath path;
603
604 switch (mode) {
605 // todo
606 case SkCanvas::kPoints_PointMode:
607 SkDebugf("unsupported operation: drawPoints(kPoints_PointMode)\n");
608 break;
609 case SkCanvas::kLines_PointMode:
610 count -= 1;
611 for (size_t i = 0; i < count; i += 2) {
612 path.rewind();
613 path.moveTo(pts[i]);
614 path.lineTo(pts[i+1]);
Ben Wagner145dbcd2016-11-03 14:40:50 -0400615 AutoElement elem("path", fWriter, fResourceBucket.get(), draw, paint);
reed5f68c342015-05-15 10:11:11 -0700616 elem.addPathAttributes(path);
617 }
618 break;
619 case SkCanvas::kPolygon_PointMode:
620 if (count > 1) {
621 path.addPoly(pts, SkToInt(count), false);
622 path.moveTo(pts[0]);
Ben Wagner145dbcd2016-11-03 14:40:50 -0400623 AutoElement elem("path", fWriter, fResourceBucket.get(), draw, paint);
reed5f68c342015-05-15 10:11:11 -0700624 elem.addPathAttributes(path);
625 }
626 break;
627 }
fmalita93957f42015-01-30 09:03:29 -0800628}
629
630void SkSVGDevice::drawRect(const SkDraw& draw, const SkRect& r, const SkPaint& paint) {
Ben Wagner145dbcd2016-11-03 14:40:50 -0400631 AutoElement rect("rect", fWriter, fResourceBucket.get(), draw, paint);
fmalita1a481fe2015-02-04 07:39:34 -0800632 rect.addRectAttributes(r);
fmalita93957f42015-01-30 09:03:29 -0800633}
634
fmalita532faa92015-02-03 05:44:40 -0800635void SkSVGDevice::drawOval(const SkDraw& draw, const SkRect& oval, const SkPaint& paint) {
Ben Wagner145dbcd2016-11-03 14:40:50 -0400636 AutoElement ellipse("ellipse", fWriter, fResourceBucket.get(), draw, paint);
fmalita532faa92015-02-03 05:44:40 -0800637 ellipse.addAttribute("cx", oval.centerX());
638 ellipse.addAttribute("cy", oval.centerY());
639 ellipse.addAttribute("rx", oval.width() / 2);
640 ellipse.addAttribute("ry", oval.height() / 2);
fmalita93957f42015-01-30 09:03:29 -0800641}
642
reed5f68c342015-05-15 10:11:11 -0700643void SkSVGDevice::drawRRect(const SkDraw& draw, const SkRRect& rr, const SkPaint& paint) {
644 SkPath path;
645 path.addRRect(rr);
646
Ben Wagner145dbcd2016-11-03 14:40:50 -0400647 AutoElement elem("path", fWriter, fResourceBucket.get(), draw, paint);
reed5f68c342015-05-15 10:11:11 -0700648 elem.addPathAttributes(path);
fmalita93957f42015-01-30 09:03:29 -0800649}
650
651void SkSVGDevice::drawPath(const SkDraw& draw, const SkPath& path, const SkPaint& paint,
652 const SkMatrix* prePathMatrix, bool pathIsMutable) {
Ben Wagner145dbcd2016-11-03 14:40:50 -0400653 AutoElement elem("path", fWriter, fResourceBucket.get(), draw, paint);
fmalitaa9d9de42015-02-04 17:54:46 -0800654 elem.addPathAttributes(path);
fmalita221722b2016-09-06 14:37:02 -0700655
656 // TODO: inverse fill types?
657 if (path.getFillType() == SkPath::kEvenOdd_FillType) {
658 elem.addAttribute("fill-rule", "evenodd");
659 }
fmalita93957f42015-01-30 09:03:29 -0800660}
661
fmalita827da232015-02-27 07:44:47 -0800662void SkSVGDevice::drawBitmapCommon(const SkDraw& draw, const SkBitmap& bm,
663 const SkPaint& paint) {
664 SkAutoTUnref<const SkData> pngData(
665 SkImageEncoder::EncodeData(bm, SkImageEncoder::kPNG_Type, SkImageEncoder::kDefaultQuality));
666 if (!pngData) {
667 return;
668 }
669
halcanary96fcdcc2015-08-27 07:41:13 -0700670 size_t b64Size = SkBase64::Encode(pngData->data(), pngData->size(), nullptr);
fmalita827da232015-02-27 07:44:47 -0800671 SkAutoTMalloc<char> b64Data(b64Size);
672 SkBase64::Encode(pngData->data(), pngData->size(), b64Data.get());
673
674 SkString svgImageData("data:image/png;base64,");
675 svgImageData.append(b64Data.get(), b64Size);
676
677 SkString imageID = fResourceBucket->addImage();
678 {
679 AutoElement defs("defs", fWriter);
680 {
681 AutoElement image("image", fWriter);
682 image.addAttribute("id", imageID);
683 image.addAttribute("width", bm.width());
684 image.addAttribute("height", bm.height());
685 image.addAttribute("xlink:href", svgImageData);
686 }
687 }
688
689 {
Ben Wagner145dbcd2016-11-03 14:40:50 -0400690 AutoElement imageUse("use", fWriter, fResourceBucket.get(), draw, paint);
fmalita827da232015-02-27 07:44:47 -0800691 imageUse.addAttribute("xlink:href", SkStringPrintf("#%s", imageID.c_str()));
692 }
693}
694
695void SkSVGDevice::drawBitmap(const SkDraw& draw, const SkBitmap& bitmap,
fmalita93957f42015-01-30 09:03:29 -0800696 const SkMatrix& matrix, const SkPaint& paint) {
fmalita827da232015-02-27 07:44:47 -0800697 SkMatrix adjustedMatrix = *draw.fMatrix;
698 adjustedMatrix.preConcat(matrix);
699 SkDraw adjustedDraw(draw);
700 adjustedDraw.fMatrix = &adjustedMatrix;
701
702 drawBitmapCommon(adjustedDraw, bitmap, paint);
fmalita93957f42015-01-30 09:03:29 -0800703}
704
fmalita827da232015-02-27 07:44:47 -0800705void SkSVGDevice::drawSprite(const SkDraw& draw, const SkBitmap& bitmap,
fmalita93957f42015-01-30 09:03:29 -0800706 int x, int y, const SkPaint& paint) {
fmalita827da232015-02-27 07:44:47 -0800707 SkMatrix adjustedMatrix = *draw.fMatrix;
708 adjustedMatrix.preTranslate(SkIntToScalar(x), SkIntToScalar(y));
709 SkDraw adjustedDraw(draw);
710 adjustedDraw.fMatrix = &adjustedMatrix;
711
712 drawBitmapCommon(adjustedDraw, bitmap, paint);
fmalita93957f42015-01-30 09:03:29 -0800713}
714
fmalita827da232015-02-27 07:44:47 -0800715void SkSVGDevice::drawBitmapRect(const SkDraw& draw, const SkBitmap& bm, const SkRect* srcOrNull,
fmalita93957f42015-01-30 09:03:29 -0800716 const SkRect& dst, const SkPaint& paint,
reed562fe472015-07-28 07:35:14 -0700717 SkCanvas::SrcRectConstraint) {
fmalita827da232015-02-27 07:44:47 -0800718 SkMatrix adjustedMatrix;
719 adjustedMatrix.setRectToRect(srcOrNull ? *srcOrNull : SkRect::Make(bm.bounds()),
720 dst,
721 SkMatrix::kFill_ScaleToFit);
722 adjustedMatrix.postConcat(*draw.fMatrix);
723
724 SkDraw adjustedDraw(draw);
725 adjustedDraw.fMatrix = &adjustedMatrix;
726
727 SkClipStack adjustedClipStack;
728 if (srcOrNull && *srcOrNull != SkRect::Make(bm.bounds())) {
fmalita827da232015-02-27 07:44:47 -0800729 adjustedClipStack = *draw.fClipStack;
Brian Salomona3b45d42016-10-03 11:36:16 -0400730 adjustedClipStack.clipRect(dst, *draw.fMatrix, SkCanvas::kIntersect_Op,
731 paint.isAntiAlias());
fmalita827da232015-02-27 07:44:47 -0800732 adjustedDraw.fClipStack = &adjustedClipStack;
733 }
734
735 drawBitmapCommon(adjustedDraw, bm, paint);
fmalita93957f42015-01-30 09:03:29 -0800736}
737
fmalitafe3f2602015-02-03 17:47:12 -0800738void SkSVGDevice::drawText(const SkDraw& draw, const void* text, size_t len,
fmalita93957f42015-01-30 09:03:29 -0800739 SkScalar x, SkScalar y, const SkPaint& paint) {
Ben Wagner145dbcd2016-11-03 14:40:50 -0400740 AutoElement elem("text", fWriter, fResourceBucket.get(), draw, paint);
fmalitaa9d9de42015-02-04 17:54:46 -0800741 elem.addTextAttributes(paint);
fmalita89747742015-02-19 18:44:51 -0800742
743 SVGTextBuilder builder(text, len, paint, SkPoint::Make(x, y), 0);
744 elem.addAttribute("x", builder.posX());
745 elem.addAttribute("y", builder.posY());
746 elem.addText(builder.text());
fmalita93957f42015-01-30 09:03:29 -0800747}
748
fmalitafe3f2602015-02-03 17:47:12 -0800749void SkSVGDevice::drawPosText(const SkDraw& draw, const void* text, size_t len,
750 const SkScalar pos[], int scalarsPerPos, const SkPoint& offset,
751 const SkPaint& paint) {
752 SkASSERT(scalarsPerPos == 1 || scalarsPerPos == 2);
753
Ben Wagner145dbcd2016-11-03 14:40:50 -0400754 AutoElement elem("text", fWriter, fResourceBucket.get(), draw, paint);
fmalitaa9d9de42015-02-04 17:54:46 -0800755 elem.addTextAttributes(paint);
fmalitafe3f2602015-02-03 17:47:12 -0800756
fmalita89747742015-02-19 18:44:51 -0800757 SVGTextBuilder builder(text, len, paint, offset, scalarsPerPos, pos);
758 elem.addAttribute("x", builder.posX());
759 elem.addAttribute("y", builder.posY());
760 elem.addText(builder.text());
fmalita93957f42015-01-30 09:03:29 -0800761}
762
763void SkSVGDevice::drawTextOnPath(const SkDraw&, const void* text, size_t len, const SkPath& path,
764 const SkMatrix* matrix, const SkPaint& paint) {
fmalitaa9d9de42015-02-04 17:54:46 -0800765 SkString pathID = fResourceBucket->addPath();
766
767 {
768 AutoElement defs("defs", fWriter);
769 AutoElement pathElement("path", fWriter);
770 pathElement.addAttribute("id", pathID);
771 pathElement.addPathAttributes(path);
772
773 }
774
775 {
776 AutoElement textElement("text", fWriter);
777 textElement.addTextAttributes(paint);
778
779 if (matrix && !matrix->isIdentity()) {
780 textElement.addAttribute("transform", svg_transform(*matrix));
781 }
782
783 {
784 AutoElement textPathElement("textPath", fWriter);
785 textPathElement.addAttribute("xlink:href", SkStringPrintf("#%s", pathID.c_str()));
786
787 if (paint.getTextAlign() != SkPaint::kLeft_Align) {
788 SkASSERT(paint.getTextAlign() == SkPaint::kCenter_Align ||
789 paint.getTextAlign() == SkPaint::kRight_Align);
790 textPathElement.addAttribute("startOffset",
791 paint.getTextAlign() == SkPaint::kCenter_Align ? "50%" : "100%");
792 }
793
fmalita89747742015-02-19 18:44:51 -0800794 SVGTextBuilder builder(text, len, paint, SkPoint::Make(0, 0), 0);
795 textPathElement.addText(builder.text());
fmalitaa9d9de42015-02-04 17:54:46 -0800796 }
797 }
fmalita93957f42015-01-30 09:03:29 -0800798}
799
800void SkSVGDevice::drawVertices(const SkDraw&, SkCanvas::VertexMode, int vertexCount,
801 const SkPoint verts[], const SkPoint texs[],
Mike Reedfaba3712016-11-03 14:45:31 -0400802 const SkColor colors[], SkBlendMode,
fmalita93957f42015-01-30 09:03:29 -0800803 const uint16_t indices[], int indexCount,
804 const SkPaint& paint) {
805 // todo
fmalitafe3f2602015-02-03 17:47:12 -0800806 SkDebugf("unsupported operation: drawVertices()\n");
fmalita93957f42015-01-30 09:03:29 -0800807}
808
809void SkSVGDevice::drawDevice(const SkDraw&, SkBaseDevice*, int x, int y,
810 const SkPaint&) {
811 // todo
fmalitafe3f2602015-02-03 17:47:12 -0800812 SkDebugf("unsupported operation: drawDevice()\n");
fmalita93957f42015-01-30 09:03:29 -0800813}