blob: 6038353c9cf53b3579b200fa5e9546c4414a4321 [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"
fmalita827da232015-02-27 07:44:47 -080013#include "SkData.h"
fmalita93957f42015-01-30 09:03:29 -080014#include "SkDraw.h"
fmalita827da232015-02-27 07:44:47 -080015#include "SkImageEncoder.h"
fmalita93957f42015-01-30 09:03:29 -080016#include "SkPaint.h"
17#include "SkParsePath.h"
fmalita532faa92015-02-03 05:44:40 -080018#include "SkShader.h"
fmalita93957f42015-01-30 09:03:29 -080019#include "SkStream.h"
fmalitaf89f60f2015-02-13 08:55:24 -080020#include "SkTHash.h"
fmalitafe3f2602015-02-03 17:47:12 -080021#include "SkTypeface.h"
22#include "SkUtils.h"
fmalita93957f42015-01-30 09:03:29 -080023#include "SkXMLWriter.h"
24
25namespace {
26
fmalita532faa92015-02-03 05:44:40 -080027static SkString svg_color(SkColor color) {
fmalita1a481fe2015-02-04 07:39:34 -080028 return SkStringPrintf("rgb(%u,%u,%u)",
29 SkColorGetR(color),
30 SkColorGetG(color),
31 SkColorGetB(color));
fmalita532faa92015-02-03 05:44:40 -080032}
33
34static SkScalar svg_opacity(SkColor color) {
35 return SkIntToScalar(SkColorGetA(color)) / SK_AlphaOPAQUE;
36}
37
fmalita12753cc2015-02-04 14:56:35 -080038// Keep in sync with SkPaint::Cap
39static const char* cap_map[] = {
40 NULL, // kButt_Cap (default)
41 "round", // kRound_Cap
42 "square" // kSquare_Cap
43};
44SK_COMPILE_ASSERT(SK_ARRAY_COUNT(cap_map) == SkPaint::kCapCount, missing_cap_map_entry);
45
46static const char* svg_cap(SkPaint::Cap cap) {
47 SkASSERT(cap < SK_ARRAY_COUNT(cap_map));
48 return cap_map[cap];
49}
50
51// Keep in sync with SkPaint::Join
52static const char* join_map[] = {
53 NULL, // kMiter_Join (default)
54 "round", // kRound_Join
55 "bevel" // kBevel_Join
56};
57SK_COMPILE_ASSERT(SK_ARRAY_COUNT(join_map) == SkPaint::kJoinCount, missing_join_map_entry);
58
59static const char* svg_join(SkPaint::Join join) {
60 SkASSERT(join < SK_ARRAY_COUNT(join_map));
61 return join_map[join];
62}
63
fmalitaa9d9de42015-02-04 17:54:46 -080064// Keep in sync with SkPaint::Align
65static const char* text_align_map[] = {
66 NULL, // kLeft_Align (default)
67 "middle", // kCenter_Align
68 "end" // kRight_Align
69};
70SK_COMPILE_ASSERT(SK_ARRAY_COUNT(text_align_map) == SkPaint::kAlignCount,
71 missing_text_align_map_entry);
72static const char* svg_text_align(SkPaint::Align align) {
73 SkASSERT(align < SK_ARRAY_COUNT(text_align_map));
74 return text_align_map[align];
75}
76
77static SkString svg_transform(const SkMatrix& t) {
78 SkASSERT(!t.isIdentity());
79
80 SkString tstr;
81 switch (t.getType()) {
82 case SkMatrix::kPerspective_Mask:
83 SkDebugf("Can't handle perspective matrices.");
84 break;
85 case SkMatrix::kTranslate_Mask:
86 tstr.printf("translate(%g %g)", t.getTranslateX(), t.getTranslateY());
87 break;
88 case SkMatrix::kScale_Mask:
89 tstr.printf("scale(%g %g)", t.getScaleX(), t.getScaleY());
90 break;
91 default:
92 // http://www.w3.org/TR/SVG/coords.html#TransformMatrixDefined
93 // | a c e |
94 // | b d f |
95 // | 0 0 1 |
96 tstr.printf("matrix(%g %g %g %g %g %g)",
97 t.getScaleX(), t.getSkewY(),
98 t.getSkewX(), t.getScaleY(),
99 t.getTranslateX(), t.getTranslateY());
100 break;
101 }
102
103 return tstr;
104}
105
fmalita532faa92015-02-03 05:44:40 -0800106struct Resources {
107 Resources(const SkPaint& paint)
108 : fPaintServer(svg_color(paint.getColor())) {}
109
110 SkString fPaintServer;
fmalita1a481fe2015-02-04 07:39:34 -0800111 SkString fClip;
fmalita532faa92015-02-03 05:44:40 -0800112};
113
fmalita89747742015-02-19 18:44:51 -0800114class SVGTextBuilder : SkNoncopyable {
115public:
116 SVGTextBuilder(const void* text, size_t byteLen, const SkPaint& paint, const SkPoint& offset,
117 unsigned scalarsPerPos, const SkScalar pos[] = NULL)
118 : fOffset(offset)
119 , fScalarsPerPos(scalarsPerPos)
120 , fPos(pos)
121 , fLastCharWasWhitespace(true) // start off in whitespace mode to strip all leading space
122 {
123 SkASSERT(scalarsPerPos <= 2);
124 SkASSERT(scalarsPerPos == 0 || SkToBool(pos));
125
126 int count = paint.countText(text, byteLen);
127
128 switch(paint.getTextEncoding()) {
129 case SkPaint::kGlyphID_TextEncoding: {
130 SkASSERT(count * sizeof(uint16_t) == byteLen);
131 SkAutoSTArray<64, SkUnichar> unichars(count);
132 paint.glyphsToUnichars((const uint16_t*)text, count, unichars.get());
133 for (int i = 0; i < count; ++i) {
134 this->appendUnichar(unichars[i]);
135 }
136 } break;
137 case SkPaint::kUTF8_TextEncoding: {
138 const char* c8 = reinterpret_cast<const char*>(text);
139 for (int i = 0; i < count; ++i) {
140 this->appendUnichar(SkUTF8_NextUnichar(&c8));
141 }
142 SkASSERT(reinterpret_cast<const char*>(text) + byteLen == c8);
143 } break;
144 case SkPaint::kUTF16_TextEncoding: {
145 const uint16_t* c16 = reinterpret_cast<const uint16_t*>(text);
146 for (int i = 0; i < count; ++i) {
147 this->appendUnichar(SkUTF16_NextUnichar(&c16));
148 }
149 SkASSERT(SkIsAlign2(byteLen));
150 SkASSERT(reinterpret_cast<const uint16_t*>(text) + (byteLen / 2) == c16);
151 } break;
152 case SkPaint::kUTF32_TextEncoding: {
153 SkASSERT(count * sizeof(uint32_t) == byteLen);
154 const uint32_t* c32 = reinterpret_cast<const uint32_t*>(text);
155 for (int i = 0; i < count; ++i) {
156 this->appendUnichar(c32[i]);
157 }
158 } break;
159 default:
160 SkFAIL("unknown text encoding");
161 }
162
163 if (scalarsPerPos < 2) {
164 SkASSERT(fPosY.isEmpty());
165 fPosY.appendScalar(offset.y()); // DrawText or DrawPosTextH (fixed Y).
166 }
167
168 if (scalarsPerPos < 1) {
169 SkASSERT(fPosX.isEmpty());
170 fPosX.appendScalar(offset.x()); // DrawText (X also fixed).
171 }
172 }
173
174 const SkString& text() const { return fText; }
175 const SkString& posX() const { return fPosX; }
176 const SkString& posY() const { return fPosY; }
177
178private:
179 void appendUnichar(SkUnichar c) {
180 bool discardPos = false;
181 bool isWhitespace = false;
182
183 switch(c) {
184 case ' ':
185 case '\t':
186 // consolidate whitespace to match SVG's xml:space=default munging
187 // (http://www.w3.org/TR/SVG/text.html#WhiteSpace)
188 if (fLastCharWasWhitespace) {
189 discardPos = true;
190 } else {
191 fText.appendUnichar(c);
192 }
193 isWhitespace = true;
194 break;
195 case '\0':
196 // SkPaint::glyphsToUnichars() returns \0 for inconvertible glyphs, but these
197 // are not legal XML characters (http://www.w3.org/TR/REC-xml/#charsets)
198 discardPos = true;
199 isWhitespace = fLastCharWasWhitespace; // preserve whitespace consolidation
200 break;
201 case '&':
202 fText.append("&amp;");
203 break;
204 case '"':
205 fText.append("&quot;");
206 break;
207 case '\'':
208 fText.append("&apos;");
209 break;
210 case '<':
211 fText.append("&lt;");
212 break;
213 case '>':
214 fText.append("&gt;");
215 break;
216 default:
217 fText.appendUnichar(c);
218 break;
219 }
220
221 this->advancePos(discardPos);
222 fLastCharWasWhitespace = isWhitespace;
223 }
224
225 void advancePos(bool discard) {
226 if (!discard && fScalarsPerPos > 0) {
227 fPosX.appendf("%.8g, ", fOffset.x() + fPos[0]);
228 if (fScalarsPerPos > 1) {
229 SkASSERT(fScalarsPerPos == 2);
230 fPosY.appendf("%.8g, ", fOffset.y() + fPos[1]);
231 }
232 }
233 fPos += fScalarsPerPos;
234 }
235
236 const SkPoint& fOffset;
237 const unsigned fScalarsPerPos;
238 const SkScalar* fPos;
239
240 SkString fText, fPosX, fPosY;
241 bool fLastCharWasWhitespace;
242};
243
fmalita532faa92015-02-03 05:44:40 -0800244}
245
246// For now all this does is serve unique serial IDs, but it will eventually evolve to track
247// and deduplicate resources.
248class SkSVGDevice::ResourceBucket : ::SkNoncopyable {
249public:
fmalita827da232015-02-27 07:44:47 -0800250 ResourceBucket() : fGradientCount(0), fClipCount(0), fPathCount(0), fImageCount(0) {}
fmalita532faa92015-02-03 05:44:40 -0800251
252 SkString addLinearGradient() {
fmalita1a481fe2015-02-04 07:39:34 -0800253 return SkStringPrintf("gradient_%d", fGradientCount++);
254 }
255
256 SkString addClip() {
257 return SkStringPrintf("clip_%d", fClipCount++);
fmalita532faa92015-02-03 05:44:40 -0800258 }
259
fmalitaa9d9de42015-02-04 17:54:46 -0800260 SkString addPath() {
261 return SkStringPrintf("path_%d", fPathCount++);
262 }
263
fmalita827da232015-02-27 07:44:47 -0800264 SkString addImage() {
265 return SkStringPrintf("img_%d", fImageCount++);
266 }
267
fmalita532faa92015-02-03 05:44:40 -0800268private:
269 uint32_t fGradientCount;
fmalita1a481fe2015-02-04 07:39:34 -0800270 uint32_t fClipCount;
fmalitaa9d9de42015-02-04 17:54:46 -0800271 uint32_t fPathCount;
fmalita827da232015-02-27 07:44:47 -0800272 uint32_t fImageCount;
fmalita532faa92015-02-03 05:44:40 -0800273};
274
275class SkSVGDevice::AutoElement : ::SkNoncopyable {
fmalita93957f42015-01-30 09:03:29 -0800276public:
277 AutoElement(const char name[], SkXMLWriter* writer)
fmalita532faa92015-02-03 05:44:40 -0800278 : fWriter(writer)
279 , fResourceBucket(NULL) {
fmalita93957f42015-01-30 09:03:29 -0800280 fWriter->startElement(name);
281 }
282
fmalita532faa92015-02-03 05:44:40 -0800283 AutoElement(const char name[], SkXMLWriter* writer, ResourceBucket* bucket,
284 const SkDraw& draw, const SkPaint& paint)
285 : fWriter(writer)
286 , fResourceBucket(bucket) {
287
fmalita1a481fe2015-02-04 07:39:34 -0800288 Resources res = this->addResources(draw, paint);
fmalita827da232015-02-27 07:44:47 -0800289 if (!res.fClip.isEmpty()) {
290 // The clip is in device space. Apply it via a <g> wrapper to avoid local transform
291 // interference.
292 fClipGroup.reset(SkNEW_ARGS(AutoElement, ("g", fWriter)));
293 fClipGroup->addAttribute("clip-path",res.fClip);
294 }
fmalita532faa92015-02-03 05:44:40 -0800295
296 fWriter->startElement(name);
297
298 this->addPaint(paint, res);
fmalitaa9d9de42015-02-04 17:54:46 -0800299
300 if (!draw.fMatrix->isIdentity()) {
301 this->addAttribute("transform", svg_transform(*draw.fMatrix));
302 }
fmalita532faa92015-02-03 05:44:40 -0800303 }
304
fmalita93957f42015-01-30 09:03:29 -0800305 ~AutoElement() {
306 fWriter->endElement();
307 }
308
fmalita532faa92015-02-03 05:44:40 -0800309 void addAttribute(const char name[], const char val[]) {
310 fWriter->addAttribute(name, val);
311 }
312
313 void addAttribute(const char name[], const SkString& val) {
314 fWriter->addAttribute(name, val.c_str());
315 }
316
317 void addAttribute(const char name[], int32_t val) {
318 fWriter->addS32Attribute(name, val);
319 }
320
321 void addAttribute(const char name[], SkScalar val) {
322 fWriter->addScalarAttribute(name, val);
323 }
324
fmalitafe3f2602015-02-03 17:47:12 -0800325 void addText(const SkString& text) {
reede73da402015-02-04 18:29:27 -0800326 fWriter->addText(text.c_str(), text.size());
fmalitafe3f2602015-02-03 17:47:12 -0800327 }
328
fmalita1a481fe2015-02-04 07:39:34 -0800329 void addRectAttributes(const SkRect&);
fmalitaa9d9de42015-02-04 17:54:46 -0800330 void addPathAttributes(const SkPath&);
331 void addTextAttributes(const SkPaint&);
fmalitafe3f2602015-02-03 17:47:12 -0800332
fmalita93957f42015-01-30 09:03:29 -0800333private:
fmalita1a481fe2015-02-04 07:39:34 -0800334 Resources addResources(const SkDraw& draw, const SkPaint& paint);
335 void addClipResources(const SkDraw& draw, Resources* resources);
336 void addShaderResources(const SkPaint& paint, Resources* resources);
fmalita532faa92015-02-03 05:44:40 -0800337
338 void addPaint(const SkPaint& paint, const Resources& resources);
fmalita532faa92015-02-03 05:44:40 -0800339
340 SkString addLinearGradientDef(const SkShader::GradientInfo& info, const SkShader* shader);
341
fmalita827da232015-02-27 07:44:47 -0800342 SkXMLWriter* fWriter;
343 ResourceBucket* fResourceBucket;
344 SkAutoTDelete<AutoElement> fClipGroup;
fmalita93957f42015-01-30 09:03:29 -0800345};
346
fmalita532faa92015-02-03 05:44:40 -0800347void SkSVGDevice::AutoElement::addPaint(const SkPaint& paint, const Resources& resources) {
348 SkPaint::Style style = paint.getStyle();
349 if (style == SkPaint::kFill_Style || style == SkPaint::kStrokeAndFill_Style) {
350 this->addAttribute("fill", resources.fPaintServer);
fmalita12753cc2015-02-04 14:56:35 -0800351
352 if (SK_AlphaOPAQUE != SkColorGetA(paint.getColor())) {
353 this->addAttribute("fill-opacity", svg_opacity(paint.getColor()));
354 }
fmalita532faa92015-02-03 05:44:40 -0800355 } else {
fmalita12753cc2015-02-04 14:56:35 -0800356 SkASSERT(style == SkPaint::kStroke_Style);
fmalita532faa92015-02-03 05:44:40 -0800357 this->addAttribute("fill", "none");
358 }
359
360 if (style == SkPaint::kStroke_Style || style == SkPaint::kStrokeAndFill_Style) {
361 this->addAttribute("stroke", resources.fPaintServer);
fmalita532faa92015-02-03 05:44:40 -0800362
fmalita12753cc2015-02-04 14:56:35 -0800363 SkScalar strokeWidth = paint.getStrokeWidth();
364 if (strokeWidth == 0) {
365 // Hairline stroke
366 strokeWidth = 1;
367 this->addAttribute("vector-effect", "non-scaling-stroke");
368 }
369 this->addAttribute("stroke-width", strokeWidth);
370
371 if (const char* cap = svg_cap(paint.getStrokeCap())) {
372 this->addAttribute("stroke-linecap", cap);
373 }
374
375 if (const char* join = svg_join(paint.getStrokeJoin())) {
376 this->addAttribute("stroke-linejoin", join);
377 }
378
379 if (paint.getStrokeJoin() == SkPaint::kMiter_Join) {
380 this->addAttribute("stroke-miterlimit", paint.getStrokeMiter());
381 }
382
383 if (SK_AlphaOPAQUE != SkColorGetA(paint.getColor())) {
384 this->addAttribute("stroke-opacity", svg_opacity(paint.getColor()));
385 }
386 } else {
387 SkASSERT(style == SkPaint::kFill_Style);
388 this->addAttribute("stroke", "none");
fmalita532faa92015-02-03 05:44:40 -0800389 }
390}
391
fmalita1a481fe2015-02-04 07:39:34 -0800392Resources SkSVGDevice::AutoElement::addResources(const SkDraw& draw, const SkPaint& paint) {
fmalita532faa92015-02-03 05:44:40 -0800393 Resources resources(paint);
fmalita1a481fe2015-02-04 07:39:34 -0800394
395 // FIXME: this is a weak heuristic and we end up with LOTS of redundant clips.
396 bool hasClip = !draw.fClipStack->isWideOpen();
397 bool hasShader = SkToBool(paint.getShader());
398
399 if (hasClip || hasShader) {
400 AutoElement defs("defs", fWriter);
401
402 if (hasClip) {
403 this->addClipResources(draw, &resources);
404 }
405
406 if (hasShader) {
407 this->addShaderResources(paint, &resources);
408 }
409 }
410
fmalita532faa92015-02-03 05:44:40 -0800411 return resources;
412}
413
fmalita1a481fe2015-02-04 07:39:34 -0800414void SkSVGDevice::AutoElement::addShaderResources(const SkPaint& paint, Resources* resources) {
fmalita532faa92015-02-03 05:44:40 -0800415 const SkShader* shader = paint.getShader();
fmalita1a481fe2015-02-04 07:39:34 -0800416 SkASSERT(SkToBool(shader));
fmalita532faa92015-02-03 05:44:40 -0800417
418 SkShader::GradientInfo grInfo;
419 grInfo.fColorCount = 0;
420 if (SkShader::kLinear_GradientType != shader->asAGradient(&grInfo)) {
421 // TODO: non-linear gradient support
422 SkDebugf("unsupported shader type\n");
423 return;
424 }
425
fmalita1a481fe2015-02-04 07:39:34 -0800426 SkAutoSTArray<16, SkColor> grColors(grInfo.fColorCount);
427 SkAutoSTArray<16, SkScalar> grOffsets(grInfo.fColorCount);
428 grInfo.fColors = grColors.get();
429 grInfo.fColorOffsets = grOffsets.get();
430
431 // One more call to get the actual colors/offsets.
432 shader->asAGradient(&grInfo);
433 SkASSERT(grInfo.fColorCount <= grColors.count());
434 SkASSERT(grInfo.fColorCount <= grOffsets.count());
435
436 resources->fPaintServer.printf("url(#%s)", addLinearGradientDef(grInfo, shader).c_str());
437}
438
439void SkSVGDevice::AutoElement::addClipResources(const SkDraw& draw, Resources* resources) {
440 SkASSERT(!draw.fClipStack->isWideOpen());
441
442 SkPath clipPath;
443 (void) draw.fClipStack->asPath(&clipPath);
444
445 SkString clipID = fResourceBucket->addClip();
446 const char* clipRule = clipPath.getFillType() == SkPath::kEvenOdd_FillType ?
447 "evenodd" : "nonzero";
fmalita532faa92015-02-03 05:44:40 -0800448 {
fmalita1a481fe2015-02-04 07:39:34 -0800449 // clipPath is in device space, but since we're only pushing transform attributes
450 // to the leaf nodes, so are all our elements => SVG userSpaceOnUse == device space.
451 AutoElement clipPathElement("clipPath", fWriter);
452 clipPathElement.addAttribute("id", clipID);
fmalita532faa92015-02-03 05:44:40 -0800453
fmalita1a481fe2015-02-04 07:39:34 -0800454 SkRect clipRect = SkRect::MakeEmpty();
455 if (clipPath.isEmpty() || clipPath.isRect(&clipRect)) {
456 AutoElement rectElement("rect", fWriter);
457 rectElement.addRectAttributes(clipRect);
458 rectElement.addAttribute("clip-rule", clipRule);
459 } else {
460 AutoElement pathElement("path", fWriter);
fmalitaa9d9de42015-02-04 17:54:46 -0800461 pathElement.addPathAttributes(clipPath);
fmalita1a481fe2015-02-04 07:39:34 -0800462 pathElement.addAttribute("clip-rule", clipRule);
463 }
fmalita532faa92015-02-03 05:44:40 -0800464 }
fmalita1a481fe2015-02-04 07:39:34 -0800465
466 resources->fClip.printf("url(#%s)", clipID.c_str());
fmalita532faa92015-02-03 05:44:40 -0800467}
468
469SkString SkSVGDevice::AutoElement::addLinearGradientDef(const SkShader::GradientInfo& info,
470 const SkShader* shader) {
471 SkASSERT(fResourceBucket);
472 SkString id = fResourceBucket->addLinearGradient();
473
474 {
475 AutoElement gradient("linearGradient", fWriter);
476
477 gradient.addAttribute("id", id);
478 gradient.addAttribute("gradientUnits", "userSpaceOnUse");
479 gradient.addAttribute("x1", info.fPoint[0].x());
480 gradient.addAttribute("y1", info.fPoint[0].y());
481 gradient.addAttribute("x2", info.fPoint[1].x());
482 gradient.addAttribute("y2", info.fPoint[1].y());
fmalitaa9d9de42015-02-04 17:54:46 -0800483
484 if (!shader->getLocalMatrix().isIdentity()) {
485 this->addAttribute("gradientTransform", svg_transform(shader->getLocalMatrix()));
486 }
fmalita532faa92015-02-03 05:44:40 -0800487
488 SkASSERT(info.fColorCount >= 2);
489 for (int i = 0; i < info.fColorCount; ++i) {
490 SkColor color = info.fColors[i];
491 SkString colorStr(svg_color(color));
492
493 {
494 AutoElement stop("stop", fWriter);
495 stop.addAttribute("offset", info.fColorOffsets[i]);
496 stop.addAttribute("stop-color", colorStr.c_str());
497
498 if (SK_AlphaOPAQUE != SkColorGetA(color)) {
499 stop.addAttribute("stop-opacity", svg_opacity(color));
500 }
501 }
502 }
503 }
504
505 return id;
fmalita93957f42015-01-30 09:03:29 -0800506}
507
fmalita1a481fe2015-02-04 07:39:34 -0800508void SkSVGDevice::AutoElement::addRectAttributes(const SkRect& rect) {
509 // x, y default to 0
510 if (rect.x() != 0) {
511 this->addAttribute("x", rect.x());
512 }
513 if (rect.y() != 0) {
514 this->addAttribute("y", rect.y());
515 }
516
517 this->addAttribute("width", rect.width());
518 this->addAttribute("height", rect.height());
519}
520
fmalitaa9d9de42015-02-04 17:54:46 -0800521void SkSVGDevice::AutoElement::addPathAttributes(const SkPath& path) {
522 SkString pathData;
523 SkParsePath::ToSVGString(path, &pathData);
524 this->addAttribute("d", pathData);
525}
526
527void SkSVGDevice::AutoElement::addTextAttributes(const SkPaint& paint) {
fmalitafe3f2602015-02-03 17:47:12 -0800528 this->addAttribute("font-size", paint.getTextSize());
529
fmalitaa9d9de42015-02-04 17:54:46 -0800530 if (const char* textAlign = svg_text_align(paint.getTextAlign())) {
531 this->addAttribute("text-anchor", textAlign);
532 }
fmalitaf89f60f2015-02-13 08:55:24 -0800533
534 SkString familyName;
mtklein02f46cf2015-03-20 13:48:42 -0700535 SkTHashSet<SkString> familySet;
fmalitaf89f60f2015-02-13 08:55:24 -0800536 SkAutoTUnref<const SkTypeface> tface(paint.getTypeface() ?
fmalita7a048692015-02-20 13:54:40 -0800537 SkRef(paint.getTypeface()) : SkTypeface::RefDefault());
538
539 SkASSERT(tface);
540 SkTypeface::Style style = tface->style();
541 if (style & SkTypeface::kItalic) {
542 this->addAttribute("font-style", "italic");
543 }
544 if (style & SkTypeface::kBold) {
545 this->addAttribute("font-weight", "bold");
546 }
547
fmalitaf89f60f2015-02-13 08:55:24 -0800548 SkAutoTUnref<SkTypeface::LocalizedStrings> familyNameIter(tface->createFamilyNameIterator());
549 SkTypeface::LocalizedString familyString;
550 while (familyNameIter->next(&familyString)) {
551 if (familySet.contains(familyString.fString)) {
552 continue;
553 }
554 familySet.add(familyString.fString);
555 familyName.appendf((familyName.isEmpty() ? "%s" : ", %s"), familyString.fString.c_str());
556 }
557
558 if (!familyName.isEmpty()) {
559 this->addAttribute("font-family", familyName);
560 }
fmalitafe3f2602015-02-03 17:47:12 -0800561}
562
fmalita2aafe6f2015-02-06 12:51:10 -0800563SkBaseDevice* SkSVGDevice::Create(const SkISize& size, SkXMLWriter* writer) {
564 if (!writer) {
fmalita93957f42015-01-30 09:03:29 -0800565 return NULL;
566 }
567
fmalita2aafe6f2015-02-06 12:51:10 -0800568 return SkNEW_ARGS(SkSVGDevice, (size, writer));
fmalita93957f42015-01-30 09:03:29 -0800569}
570
fmalita2aafe6f2015-02-06 12:51:10 -0800571SkSVGDevice::SkSVGDevice(const SkISize& size, SkXMLWriter* writer)
robertphillips9a53fd72015-06-22 09:46:59 -0700572 : INHERITED(SkSurfaceProps(0, kUnknown_SkPixelGeometry))
573 , fWriter(writer)
fmalita532faa92015-02-03 05:44:40 -0800574 , fResourceBucket(SkNEW(ResourceBucket)) {
fmalita2aafe6f2015-02-06 12:51:10 -0800575 SkASSERT(writer);
fmalita93957f42015-01-30 09:03:29 -0800576
577 fLegacyBitmap.setInfo(SkImageInfo::MakeUnknown(size.width(), size.height()));
578
579 fWriter->writeHeader();
fmalita532faa92015-02-03 05:44:40 -0800580
581 // The root <svg> tag gets closed by the destructor.
582 fRootElement.reset(SkNEW_ARGS(AutoElement, ("svg", fWriter)));
583
584 fRootElement->addAttribute("xmlns", "http://www.w3.org/2000/svg");
585 fRootElement->addAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
586 fRootElement->addAttribute("width", size.width());
587 fRootElement->addAttribute("height", size.height());
fmalita93957f42015-01-30 09:03:29 -0800588}
589
590SkSVGDevice::~SkSVGDevice() {
fmalita93957f42015-01-30 09:03:29 -0800591}
592
593SkImageInfo SkSVGDevice::imageInfo() const {
594 return fLegacyBitmap.info();
595}
596
597const SkBitmap& SkSVGDevice::onAccessBitmap() {
598 return fLegacyBitmap;
599}
600
fmalita532faa92015-02-03 05:44:40 -0800601void SkSVGDevice::drawPaint(const SkDraw& draw, const SkPaint& paint) {
602 AutoElement rect("rect", fWriter, fResourceBucket, draw, paint);
fmalita1a481fe2015-02-04 07:39:34 -0800603 rect.addRectAttributes(SkRect::MakeWH(SkIntToScalar(this->width()),
604 SkIntToScalar(this->height())));
fmalita93957f42015-01-30 09:03:29 -0800605}
606
reed5f68c342015-05-15 10:11:11 -0700607void SkSVGDevice::drawPoints(const SkDraw& draw, SkCanvas::PointMode mode, size_t count,
608 const SkPoint pts[], const SkPaint& paint) {
609 SkPath path;
610
611 switch (mode) {
612 // todo
613 case SkCanvas::kPoints_PointMode:
614 SkDebugf("unsupported operation: drawPoints(kPoints_PointMode)\n");
615 break;
616 case SkCanvas::kLines_PointMode:
617 count -= 1;
618 for (size_t i = 0; i < count; i += 2) {
619 path.rewind();
620 path.moveTo(pts[i]);
621 path.lineTo(pts[i+1]);
622 AutoElement elem("path", fWriter, fResourceBucket, draw, paint);
623 elem.addPathAttributes(path);
624 }
625 break;
626 case SkCanvas::kPolygon_PointMode:
627 if (count > 1) {
628 path.addPoly(pts, SkToInt(count), false);
629 path.moveTo(pts[0]);
630 AutoElement elem("path", fWriter, fResourceBucket, draw, paint);
631 elem.addPathAttributes(path);
632 }
633 break;
634 }
fmalita93957f42015-01-30 09:03:29 -0800635}
636
637void SkSVGDevice::drawRect(const SkDraw& draw, const SkRect& r, const SkPaint& paint) {
fmalita532faa92015-02-03 05:44:40 -0800638 AutoElement rect("rect", fWriter, fResourceBucket, draw, paint);
fmalita1a481fe2015-02-04 07:39:34 -0800639 rect.addRectAttributes(r);
fmalita93957f42015-01-30 09:03:29 -0800640}
641
fmalita532faa92015-02-03 05:44:40 -0800642void SkSVGDevice::drawOval(const SkDraw& draw, const SkRect& oval, const SkPaint& paint) {
643 AutoElement ellipse("ellipse", fWriter, fResourceBucket, draw, paint);
644 ellipse.addAttribute("cx", oval.centerX());
645 ellipse.addAttribute("cy", oval.centerY());
646 ellipse.addAttribute("rx", oval.width() / 2);
647 ellipse.addAttribute("ry", oval.height() / 2);
fmalita93957f42015-01-30 09:03:29 -0800648}
649
reed5f68c342015-05-15 10:11:11 -0700650void SkSVGDevice::drawRRect(const SkDraw& draw, const SkRRect& rr, const SkPaint& paint) {
651 SkPath path;
652 path.addRRect(rr);
653
654 AutoElement elem("path", fWriter, fResourceBucket, draw, paint);
655 elem.addPathAttributes(path);
fmalita93957f42015-01-30 09:03:29 -0800656}
657
658void SkSVGDevice::drawPath(const SkDraw& draw, const SkPath& path, const SkPaint& paint,
659 const SkMatrix* prePathMatrix, bool pathIsMutable) {
fmalita532faa92015-02-03 05:44:40 -0800660 AutoElement elem("path", fWriter, fResourceBucket, draw, paint);
fmalitaa9d9de42015-02-04 17:54:46 -0800661 elem.addPathAttributes(path);
fmalita93957f42015-01-30 09:03:29 -0800662}
663
fmalita827da232015-02-27 07:44:47 -0800664void SkSVGDevice::drawBitmapCommon(const SkDraw& draw, const SkBitmap& bm,
665 const SkPaint& paint) {
666 SkAutoTUnref<const SkData> pngData(
667 SkImageEncoder::EncodeData(bm, SkImageEncoder::kPNG_Type, SkImageEncoder::kDefaultQuality));
668 if (!pngData) {
669 return;
670 }
671
672 size_t b64Size = SkBase64::Encode(pngData->data(), pngData->size(), NULL);
673 SkAutoTMalloc<char> b64Data(b64Size);
674 SkBase64::Encode(pngData->data(), pngData->size(), b64Data.get());
675
676 SkString svgImageData("data:image/png;base64,");
677 svgImageData.append(b64Data.get(), b64Size);
678
679 SkString imageID = fResourceBucket->addImage();
680 {
681 AutoElement defs("defs", fWriter);
682 {
683 AutoElement image("image", fWriter);
684 image.addAttribute("id", imageID);
685 image.addAttribute("width", bm.width());
686 image.addAttribute("height", bm.height());
687 image.addAttribute("xlink:href", svgImageData);
688 }
689 }
690
691 {
692 AutoElement imageUse("use", fWriter, fResourceBucket, draw, paint);
693 imageUse.addAttribute("xlink:href", SkStringPrintf("#%s", imageID.c_str()));
694 }
695}
696
697void SkSVGDevice::drawBitmap(const SkDraw& draw, const SkBitmap& bitmap,
fmalita93957f42015-01-30 09:03:29 -0800698 const SkMatrix& matrix, const SkPaint& paint) {
fmalita827da232015-02-27 07:44:47 -0800699 SkMatrix adjustedMatrix = *draw.fMatrix;
700 adjustedMatrix.preConcat(matrix);
701 SkDraw adjustedDraw(draw);
702 adjustedDraw.fMatrix = &adjustedMatrix;
703
704 drawBitmapCommon(adjustedDraw, bitmap, paint);
fmalita93957f42015-01-30 09:03:29 -0800705}
706
fmalita827da232015-02-27 07:44:47 -0800707void SkSVGDevice::drawSprite(const SkDraw& draw, const SkBitmap& bitmap,
fmalita93957f42015-01-30 09:03:29 -0800708 int x, int y, const SkPaint& paint) {
fmalita827da232015-02-27 07:44:47 -0800709 SkMatrix adjustedMatrix = *draw.fMatrix;
710 adjustedMatrix.preTranslate(SkIntToScalar(x), SkIntToScalar(y));
711 SkDraw adjustedDraw(draw);
712 adjustedDraw.fMatrix = &adjustedMatrix;
713
714 drawBitmapCommon(adjustedDraw, bitmap, paint);
fmalita93957f42015-01-30 09:03:29 -0800715}
716
fmalita827da232015-02-27 07:44:47 -0800717void SkSVGDevice::drawBitmapRect(const SkDraw& draw, const SkBitmap& bm, const SkRect* srcOrNull,
fmalita93957f42015-01-30 09:03:29 -0800718 const SkRect& dst, const SkPaint& paint,
fmalita827da232015-02-27 07:44:47 -0800719 SkCanvas::DrawBitmapRectFlags) {
720 SkMatrix adjustedMatrix;
721 adjustedMatrix.setRectToRect(srcOrNull ? *srcOrNull : SkRect::Make(bm.bounds()),
722 dst,
723 SkMatrix::kFill_ScaleToFit);
724 adjustedMatrix.postConcat(*draw.fMatrix);
725
726 SkDraw adjustedDraw(draw);
727 adjustedDraw.fMatrix = &adjustedMatrix;
728
729 SkClipStack adjustedClipStack;
730 if (srcOrNull && *srcOrNull != SkRect::Make(bm.bounds())) {
731 SkRect devClipRect;
732 draw.fMatrix->mapRect(&devClipRect, dst);
733
734 adjustedClipStack = *draw.fClipStack;
735 adjustedClipStack.clipDevRect(devClipRect, SkRegion::kIntersect_Op, paint.isAntiAlias());
736 adjustedDraw.fClipStack = &adjustedClipStack;
737 }
738
739 drawBitmapCommon(adjustedDraw, bm, paint);
fmalita93957f42015-01-30 09:03:29 -0800740}
741
fmalitafe3f2602015-02-03 17:47:12 -0800742void SkSVGDevice::drawText(const SkDraw& draw, const void* text, size_t len,
fmalita93957f42015-01-30 09:03:29 -0800743 SkScalar x, SkScalar y, const SkPaint& paint) {
fmalitafe3f2602015-02-03 17:47:12 -0800744 AutoElement elem("text", fWriter, fResourceBucket, draw, paint);
fmalitaa9d9de42015-02-04 17:54:46 -0800745 elem.addTextAttributes(paint);
fmalita89747742015-02-19 18:44:51 -0800746
747 SVGTextBuilder builder(text, len, paint, SkPoint::Make(x, y), 0);
748 elem.addAttribute("x", builder.posX());
749 elem.addAttribute("y", builder.posY());
750 elem.addText(builder.text());
fmalita93957f42015-01-30 09:03:29 -0800751}
752
fmalitafe3f2602015-02-03 17:47:12 -0800753void SkSVGDevice::drawPosText(const SkDraw& draw, const void* text, size_t len,
754 const SkScalar pos[], int scalarsPerPos, const SkPoint& offset,
755 const SkPaint& paint) {
756 SkASSERT(scalarsPerPos == 1 || scalarsPerPos == 2);
757
758 AutoElement elem("text", fWriter, fResourceBucket, draw, paint);
fmalitaa9d9de42015-02-04 17:54:46 -0800759 elem.addTextAttributes(paint);
fmalitafe3f2602015-02-03 17:47:12 -0800760
fmalita89747742015-02-19 18:44:51 -0800761 SVGTextBuilder builder(text, len, paint, offset, scalarsPerPos, pos);
762 elem.addAttribute("x", builder.posX());
763 elem.addAttribute("y", builder.posY());
764 elem.addText(builder.text());
fmalita93957f42015-01-30 09:03:29 -0800765}
766
767void SkSVGDevice::drawTextOnPath(const SkDraw&, const void* text, size_t len, const SkPath& path,
768 const SkMatrix* matrix, const SkPaint& paint) {
fmalitaa9d9de42015-02-04 17:54:46 -0800769 SkString pathID = fResourceBucket->addPath();
770
771 {
772 AutoElement defs("defs", fWriter);
773 AutoElement pathElement("path", fWriter);
774 pathElement.addAttribute("id", pathID);
775 pathElement.addPathAttributes(path);
776
777 }
778
779 {
780 AutoElement textElement("text", fWriter);
781 textElement.addTextAttributes(paint);
782
783 if (matrix && !matrix->isIdentity()) {
784 textElement.addAttribute("transform", svg_transform(*matrix));
785 }
786
787 {
788 AutoElement textPathElement("textPath", fWriter);
789 textPathElement.addAttribute("xlink:href", SkStringPrintf("#%s", pathID.c_str()));
790
791 if (paint.getTextAlign() != SkPaint::kLeft_Align) {
792 SkASSERT(paint.getTextAlign() == SkPaint::kCenter_Align ||
793 paint.getTextAlign() == SkPaint::kRight_Align);
794 textPathElement.addAttribute("startOffset",
795 paint.getTextAlign() == SkPaint::kCenter_Align ? "50%" : "100%");
796 }
797
fmalita89747742015-02-19 18:44:51 -0800798 SVGTextBuilder builder(text, len, paint, SkPoint::Make(0, 0), 0);
799 textPathElement.addText(builder.text());
fmalitaa9d9de42015-02-04 17:54:46 -0800800 }
801 }
fmalita93957f42015-01-30 09:03:29 -0800802}
803
804void SkSVGDevice::drawVertices(const SkDraw&, SkCanvas::VertexMode, int vertexCount,
805 const SkPoint verts[], const SkPoint texs[],
806 const SkColor colors[], SkXfermode* xmode,
807 const uint16_t indices[], int indexCount,
808 const SkPaint& paint) {
809 // todo
fmalitafe3f2602015-02-03 17:47:12 -0800810 SkDebugf("unsupported operation: drawVertices()\n");
fmalita93957f42015-01-30 09:03:29 -0800811}
812
813void SkSVGDevice::drawDevice(const SkDraw&, SkBaseDevice*, int x, int y,
814 const SkPaint&) {
815 // todo
fmalitafe3f2602015-02-03 17:47:12 -0800816 SkDebugf("unsupported operation: drawDevice()\n");
fmalita93957f42015-01-30 09:03:29 -0800817}