blob: 45e616b3f618b2717fb4fadb31690da12bcbf49c [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
fmalitaf89f60f2015-02-13 08:55:24 -0800106uint32_t hash_family_string(const SkString& family) {
107 // This is a lame hash function, but we don't really expect to see more than 1-2
108 // family names under normal circumstances.
109 return SkChecksum::Mix(SkToU32(family.size()));
110}
111
fmalita532faa92015-02-03 05:44:40 -0800112struct Resources {
113 Resources(const SkPaint& paint)
114 : fPaintServer(svg_color(paint.getColor())) {}
115
116 SkString fPaintServer;
fmalita1a481fe2015-02-04 07:39:34 -0800117 SkString fClip;
fmalita532faa92015-02-03 05:44:40 -0800118};
119
fmalita89747742015-02-19 18:44:51 -0800120class SVGTextBuilder : SkNoncopyable {
121public:
122 SVGTextBuilder(const void* text, size_t byteLen, const SkPaint& paint, const SkPoint& offset,
123 unsigned scalarsPerPos, const SkScalar pos[] = NULL)
124 : fOffset(offset)
125 , fScalarsPerPos(scalarsPerPos)
126 , fPos(pos)
127 , fLastCharWasWhitespace(true) // start off in whitespace mode to strip all leading space
128 {
129 SkASSERT(scalarsPerPos <= 2);
130 SkASSERT(scalarsPerPos == 0 || SkToBool(pos));
131
132 int count = paint.countText(text, byteLen);
133
134 switch(paint.getTextEncoding()) {
135 case SkPaint::kGlyphID_TextEncoding: {
136 SkASSERT(count * sizeof(uint16_t) == byteLen);
137 SkAutoSTArray<64, SkUnichar> unichars(count);
138 paint.glyphsToUnichars((const uint16_t*)text, count, unichars.get());
139 for (int i = 0; i < count; ++i) {
140 this->appendUnichar(unichars[i]);
141 }
142 } break;
143 case SkPaint::kUTF8_TextEncoding: {
144 const char* c8 = reinterpret_cast<const char*>(text);
145 for (int i = 0; i < count; ++i) {
146 this->appendUnichar(SkUTF8_NextUnichar(&c8));
147 }
148 SkASSERT(reinterpret_cast<const char*>(text) + byteLen == c8);
149 } break;
150 case SkPaint::kUTF16_TextEncoding: {
151 const uint16_t* c16 = reinterpret_cast<const uint16_t*>(text);
152 for (int i = 0; i < count; ++i) {
153 this->appendUnichar(SkUTF16_NextUnichar(&c16));
154 }
155 SkASSERT(SkIsAlign2(byteLen));
156 SkASSERT(reinterpret_cast<const uint16_t*>(text) + (byteLen / 2) == c16);
157 } break;
158 case SkPaint::kUTF32_TextEncoding: {
159 SkASSERT(count * sizeof(uint32_t) == byteLen);
160 const uint32_t* c32 = reinterpret_cast<const uint32_t*>(text);
161 for (int i = 0; i < count; ++i) {
162 this->appendUnichar(c32[i]);
163 }
164 } break;
165 default:
166 SkFAIL("unknown text encoding");
167 }
168
169 if (scalarsPerPos < 2) {
170 SkASSERT(fPosY.isEmpty());
171 fPosY.appendScalar(offset.y()); // DrawText or DrawPosTextH (fixed Y).
172 }
173
174 if (scalarsPerPos < 1) {
175 SkASSERT(fPosX.isEmpty());
176 fPosX.appendScalar(offset.x()); // DrawText (X also fixed).
177 }
178 }
179
180 const SkString& text() const { return fText; }
181 const SkString& posX() const { return fPosX; }
182 const SkString& posY() const { return fPosY; }
183
184private:
185 void appendUnichar(SkUnichar c) {
186 bool discardPos = false;
187 bool isWhitespace = false;
188
189 switch(c) {
190 case ' ':
191 case '\t':
192 // consolidate whitespace to match SVG's xml:space=default munging
193 // (http://www.w3.org/TR/SVG/text.html#WhiteSpace)
194 if (fLastCharWasWhitespace) {
195 discardPos = true;
196 } else {
197 fText.appendUnichar(c);
198 }
199 isWhitespace = true;
200 break;
201 case '\0':
202 // SkPaint::glyphsToUnichars() returns \0 for inconvertible glyphs, but these
203 // are not legal XML characters (http://www.w3.org/TR/REC-xml/#charsets)
204 discardPos = true;
205 isWhitespace = fLastCharWasWhitespace; // preserve whitespace consolidation
206 break;
207 case '&':
208 fText.append("&amp;");
209 break;
210 case '"':
211 fText.append("&quot;");
212 break;
213 case '\'':
214 fText.append("&apos;");
215 break;
216 case '<':
217 fText.append("&lt;");
218 break;
219 case '>':
220 fText.append("&gt;");
221 break;
222 default:
223 fText.appendUnichar(c);
224 break;
225 }
226
227 this->advancePos(discardPos);
228 fLastCharWasWhitespace = isWhitespace;
229 }
230
231 void advancePos(bool discard) {
232 if (!discard && fScalarsPerPos > 0) {
233 fPosX.appendf("%.8g, ", fOffset.x() + fPos[0]);
234 if (fScalarsPerPos > 1) {
235 SkASSERT(fScalarsPerPos == 2);
236 fPosY.appendf("%.8g, ", fOffset.y() + fPos[1]);
237 }
238 }
239 fPos += fScalarsPerPos;
240 }
241
242 const SkPoint& fOffset;
243 const unsigned fScalarsPerPos;
244 const SkScalar* fPos;
245
246 SkString fText, fPosX, fPosY;
247 bool fLastCharWasWhitespace;
248};
249
fmalita532faa92015-02-03 05:44:40 -0800250}
251
252// For now all this does is serve unique serial IDs, but it will eventually evolve to track
253// and deduplicate resources.
254class SkSVGDevice::ResourceBucket : ::SkNoncopyable {
255public:
fmalita827da232015-02-27 07:44:47 -0800256 ResourceBucket() : fGradientCount(0), fClipCount(0), fPathCount(0), fImageCount(0) {}
fmalita532faa92015-02-03 05:44:40 -0800257
258 SkString addLinearGradient() {
fmalita1a481fe2015-02-04 07:39:34 -0800259 return SkStringPrintf("gradient_%d", fGradientCount++);
260 }
261
262 SkString addClip() {
263 return SkStringPrintf("clip_%d", fClipCount++);
fmalita532faa92015-02-03 05:44:40 -0800264 }
265
fmalitaa9d9de42015-02-04 17:54:46 -0800266 SkString addPath() {
267 return SkStringPrintf("path_%d", fPathCount++);
268 }
269
fmalita827da232015-02-27 07:44:47 -0800270 SkString addImage() {
271 return SkStringPrintf("img_%d", fImageCount++);
272 }
273
fmalita532faa92015-02-03 05:44:40 -0800274private:
275 uint32_t fGradientCount;
fmalita1a481fe2015-02-04 07:39:34 -0800276 uint32_t fClipCount;
fmalitaa9d9de42015-02-04 17:54:46 -0800277 uint32_t fPathCount;
fmalita827da232015-02-27 07:44:47 -0800278 uint32_t fImageCount;
fmalita532faa92015-02-03 05:44:40 -0800279};
280
281class SkSVGDevice::AutoElement : ::SkNoncopyable {
fmalita93957f42015-01-30 09:03:29 -0800282public:
283 AutoElement(const char name[], SkXMLWriter* writer)
fmalita532faa92015-02-03 05:44:40 -0800284 : fWriter(writer)
285 , fResourceBucket(NULL) {
fmalita93957f42015-01-30 09:03:29 -0800286 fWriter->startElement(name);
287 }
288
fmalita532faa92015-02-03 05:44:40 -0800289 AutoElement(const char name[], SkXMLWriter* writer, ResourceBucket* bucket,
290 const SkDraw& draw, const SkPaint& paint)
291 : fWriter(writer)
292 , fResourceBucket(bucket) {
293
fmalita1a481fe2015-02-04 07:39:34 -0800294 Resources res = this->addResources(draw, paint);
fmalita827da232015-02-27 07:44:47 -0800295 if (!res.fClip.isEmpty()) {
296 // The clip is in device space. Apply it via a <g> wrapper to avoid local transform
297 // interference.
298 fClipGroup.reset(SkNEW_ARGS(AutoElement, ("g", fWriter)));
299 fClipGroup->addAttribute("clip-path",res.fClip);
300 }
fmalita532faa92015-02-03 05:44:40 -0800301
302 fWriter->startElement(name);
303
304 this->addPaint(paint, res);
fmalitaa9d9de42015-02-04 17:54:46 -0800305
306 if (!draw.fMatrix->isIdentity()) {
307 this->addAttribute("transform", svg_transform(*draw.fMatrix));
308 }
fmalita532faa92015-02-03 05:44:40 -0800309 }
310
fmalita93957f42015-01-30 09:03:29 -0800311 ~AutoElement() {
312 fWriter->endElement();
313 }
314
fmalita532faa92015-02-03 05:44:40 -0800315 void addAttribute(const char name[], const char val[]) {
316 fWriter->addAttribute(name, val);
317 }
318
319 void addAttribute(const char name[], const SkString& val) {
320 fWriter->addAttribute(name, val.c_str());
321 }
322
323 void addAttribute(const char name[], int32_t val) {
324 fWriter->addS32Attribute(name, val);
325 }
326
327 void addAttribute(const char name[], SkScalar val) {
328 fWriter->addScalarAttribute(name, val);
329 }
330
fmalitafe3f2602015-02-03 17:47:12 -0800331 void addText(const SkString& text) {
reede73da402015-02-04 18:29:27 -0800332 fWriter->addText(text.c_str(), text.size());
fmalitafe3f2602015-02-03 17:47:12 -0800333 }
334
fmalita1a481fe2015-02-04 07:39:34 -0800335 void addRectAttributes(const SkRect&);
fmalitaa9d9de42015-02-04 17:54:46 -0800336 void addPathAttributes(const SkPath&);
337 void addTextAttributes(const SkPaint&);
fmalitafe3f2602015-02-03 17:47:12 -0800338
fmalita93957f42015-01-30 09:03:29 -0800339private:
fmalita1a481fe2015-02-04 07:39:34 -0800340 Resources addResources(const SkDraw& draw, const SkPaint& paint);
341 void addClipResources(const SkDraw& draw, Resources* resources);
342 void addShaderResources(const SkPaint& paint, Resources* resources);
fmalita532faa92015-02-03 05:44:40 -0800343
344 void addPaint(const SkPaint& paint, const Resources& resources);
fmalita532faa92015-02-03 05:44:40 -0800345
346 SkString addLinearGradientDef(const SkShader::GradientInfo& info, const SkShader* shader);
347
fmalita827da232015-02-27 07:44:47 -0800348 SkXMLWriter* fWriter;
349 ResourceBucket* fResourceBucket;
350 SkAutoTDelete<AutoElement> fClipGroup;
fmalita93957f42015-01-30 09:03:29 -0800351};
352
fmalita532faa92015-02-03 05:44:40 -0800353void SkSVGDevice::AutoElement::addPaint(const SkPaint& paint, const Resources& resources) {
354 SkPaint::Style style = paint.getStyle();
355 if (style == SkPaint::kFill_Style || style == SkPaint::kStrokeAndFill_Style) {
356 this->addAttribute("fill", resources.fPaintServer);
fmalita12753cc2015-02-04 14:56:35 -0800357
358 if (SK_AlphaOPAQUE != SkColorGetA(paint.getColor())) {
359 this->addAttribute("fill-opacity", svg_opacity(paint.getColor()));
360 }
fmalita532faa92015-02-03 05:44:40 -0800361 } else {
fmalita12753cc2015-02-04 14:56:35 -0800362 SkASSERT(style == SkPaint::kStroke_Style);
fmalita532faa92015-02-03 05:44:40 -0800363 this->addAttribute("fill", "none");
364 }
365
366 if (style == SkPaint::kStroke_Style || style == SkPaint::kStrokeAndFill_Style) {
367 this->addAttribute("stroke", resources.fPaintServer);
fmalita532faa92015-02-03 05:44:40 -0800368
fmalita12753cc2015-02-04 14:56:35 -0800369 SkScalar strokeWidth = paint.getStrokeWidth();
370 if (strokeWidth == 0) {
371 // Hairline stroke
372 strokeWidth = 1;
373 this->addAttribute("vector-effect", "non-scaling-stroke");
374 }
375 this->addAttribute("stroke-width", strokeWidth);
376
377 if (const char* cap = svg_cap(paint.getStrokeCap())) {
378 this->addAttribute("stroke-linecap", cap);
379 }
380
381 if (const char* join = svg_join(paint.getStrokeJoin())) {
382 this->addAttribute("stroke-linejoin", join);
383 }
384
385 if (paint.getStrokeJoin() == SkPaint::kMiter_Join) {
386 this->addAttribute("stroke-miterlimit", paint.getStrokeMiter());
387 }
388
389 if (SK_AlphaOPAQUE != SkColorGetA(paint.getColor())) {
390 this->addAttribute("stroke-opacity", svg_opacity(paint.getColor()));
391 }
392 } else {
393 SkASSERT(style == SkPaint::kFill_Style);
394 this->addAttribute("stroke", "none");
fmalita532faa92015-02-03 05:44:40 -0800395 }
396}
397
fmalita1a481fe2015-02-04 07:39:34 -0800398Resources SkSVGDevice::AutoElement::addResources(const SkDraw& draw, const SkPaint& paint) {
fmalita532faa92015-02-03 05:44:40 -0800399 Resources resources(paint);
fmalita1a481fe2015-02-04 07:39:34 -0800400
401 // FIXME: this is a weak heuristic and we end up with LOTS of redundant clips.
402 bool hasClip = !draw.fClipStack->isWideOpen();
403 bool hasShader = SkToBool(paint.getShader());
404
405 if (hasClip || hasShader) {
406 AutoElement defs("defs", fWriter);
407
408 if (hasClip) {
409 this->addClipResources(draw, &resources);
410 }
411
412 if (hasShader) {
413 this->addShaderResources(paint, &resources);
414 }
415 }
416
fmalita532faa92015-02-03 05:44:40 -0800417 return resources;
418}
419
fmalita1a481fe2015-02-04 07:39:34 -0800420void SkSVGDevice::AutoElement::addShaderResources(const SkPaint& paint, Resources* resources) {
fmalita532faa92015-02-03 05:44:40 -0800421 const SkShader* shader = paint.getShader();
fmalita1a481fe2015-02-04 07:39:34 -0800422 SkASSERT(SkToBool(shader));
fmalita532faa92015-02-03 05:44:40 -0800423
424 SkShader::GradientInfo grInfo;
425 grInfo.fColorCount = 0;
426 if (SkShader::kLinear_GradientType != shader->asAGradient(&grInfo)) {
427 // TODO: non-linear gradient support
428 SkDebugf("unsupported shader type\n");
429 return;
430 }
431
fmalita1a481fe2015-02-04 07:39:34 -0800432 SkAutoSTArray<16, SkColor> grColors(grInfo.fColorCount);
433 SkAutoSTArray<16, SkScalar> grOffsets(grInfo.fColorCount);
434 grInfo.fColors = grColors.get();
435 grInfo.fColorOffsets = grOffsets.get();
436
437 // One more call to get the actual colors/offsets.
438 shader->asAGradient(&grInfo);
439 SkASSERT(grInfo.fColorCount <= grColors.count());
440 SkASSERT(grInfo.fColorCount <= grOffsets.count());
441
442 resources->fPaintServer.printf("url(#%s)", addLinearGradientDef(grInfo, shader).c_str());
443}
444
445void SkSVGDevice::AutoElement::addClipResources(const SkDraw& draw, Resources* resources) {
446 SkASSERT(!draw.fClipStack->isWideOpen());
447
448 SkPath clipPath;
449 (void) draw.fClipStack->asPath(&clipPath);
450
451 SkString clipID = fResourceBucket->addClip();
452 const char* clipRule = clipPath.getFillType() == SkPath::kEvenOdd_FillType ?
453 "evenodd" : "nonzero";
fmalita532faa92015-02-03 05:44:40 -0800454 {
fmalita1a481fe2015-02-04 07:39:34 -0800455 // clipPath is in device space, but since we're only pushing transform attributes
456 // to the leaf nodes, so are all our elements => SVG userSpaceOnUse == device space.
457 AutoElement clipPathElement("clipPath", fWriter);
458 clipPathElement.addAttribute("id", clipID);
fmalita532faa92015-02-03 05:44:40 -0800459
fmalita1a481fe2015-02-04 07:39:34 -0800460 SkRect clipRect = SkRect::MakeEmpty();
461 if (clipPath.isEmpty() || clipPath.isRect(&clipRect)) {
462 AutoElement rectElement("rect", fWriter);
463 rectElement.addRectAttributes(clipRect);
464 rectElement.addAttribute("clip-rule", clipRule);
465 } else {
466 AutoElement pathElement("path", fWriter);
fmalitaa9d9de42015-02-04 17:54:46 -0800467 pathElement.addPathAttributes(clipPath);
fmalita1a481fe2015-02-04 07:39:34 -0800468 pathElement.addAttribute("clip-rule", clipRule);
469 }
fmalita532faa92015-02-03 05:44:40 -0800470 }
fmalita1a481fe2015-02-04 07:39:34 -0800471
472 resources->fClip.printf("url(#%s)", clipID.c_str());
fmalita532faa92015-02-03 05:44:40 -0800473}
474
475SkString SkSVGDevice::AutoElement::addLinearGradientDef(const SkShader::GradientInfo& info,
476 const SkShader* shader) {
477 SkASSERT(fResourceBucket);
478 SkString id = fResourceBucket->addLinearGradient();
479
480 {
481 AutoElement gradient("linearGradient", fWriter);
482
483 gradient.addAttribute("id", id);
484 gradient.addAttribute("gradientUnits", "userSpaceOnUse");
485 gradient.addAttribute("x1", info.fPoint[0].x());
486 gradient.addAttribute("y1", info.fPoint[0].y());
487 gradient.addAttribute("x2", info.fPoint[1].x());
488 gradient.addAttribute("y2", info.fPoint[1].y());
fmalitaa9d9de42015-02-04 17:54:46 -0800489
490 if (!shader->getLocalMatrix().isIdentity()) {
491 this->addAttribute("gradientTransform", svg_transform(shader->getLocalMatrix()));
492 }
fmalita532faa92015-02-03 05:44:40 -0800493
494 SkASSERT(info.fColorCount >= 2);
495 for (int i = 0; i < info.fColorCount; ++i) {
496 SkColor color = info.fColors[i];
497 SkString colorStr(svg_color(color));
498
499 {
500 AutoElement stop("stop", fWriter);
501 stop.addAttribute("offset", info.fColorOffsets[i]);
502 stop.addAttribute("stop-color", colorStr.c_str());
503
504 if (SK_AlphaOPAQUE != SkColorGetA(color)) {
505 stop.addAttribute("stop-opacity", svg_opacity(color));
506 }
507 }
508 }
509 }
510
511 return id;
fmalita93957f42015-01-30 09:03:29 -0800512}
513
fmalita1a481fe2015-02-04 07:39:34 -0800514void SkSVGDevice::AutoElement::addRectAttributes(const SkRect& rect) {
515 // x, y default to 0
516 if (rect.x() != 0) {
517 this->addAttribute("x", rect.x());
518 }
519 if (rect.y() != 0) {
520 this->addAttribute("y", rect.y());
521 }
522
523 this->addAttribute("width", rect.width());
524 this->addAttribute("height", rect.height());
525}
526
fmalitaa9d9de42015-02-04 17:54:46 -0800527void SkSVGDevice::AutoElement::addPathAttributes(const SkPath& path) {
528 SkString pathData;
529 SkParsePath::ToSVGString(path, &pathData);
530 this->addAttribute("d", pathData);
531}
532
533void SkSVGDevice::AutoElement::addTextAttributes(const SkPaint& paint) {
fmalitafe3f2602015-02-03 17:47:12 -0800534 this->addAttribute("font-size", paint.getTextSize());
535
fmalitaa9d9de42015-02-04 17:54:46 -0800536 if (const char* textAlign = svg_text_align(paint.getTextAlign())) {
537 this->addAttribute("text-anchor", textAlign);
538 }
fmalitaf89f60f2015-02-13 08:55:24 -0800539
540 SkString familyName;
541 SkTHashSet<SkString, hash_family_string> familySet;
542 SkAutoTUnref<const SkTypeface> tface(paint.getTypeface() ?
fmalita7a048692015-02-20 13:54:40 -0800543 SkRef(paint.getTypeface()) : SkTypeface::RefDefault());
544
545 SkASSERT(tface);
546 SkTypeface::Style style = tface->style();
547 if (style & SkTypeface::kItalic) {
548 this->addAttribute("font-style", "italic");
549 }
550 if (style & SkTypeface::kBold) {
551 this->addAttribute("font-weight", "bold");
552 }
553
fmalitaf89f60f2015-02-13 08:55:24 -0800554 SkAutoTUnref<SkTypeface::LocalizedStrings> familyNameIter(tface->createFamilyNameIterator());
555 SkTypeface::LocalizedString familyString;
556 while (familyNameIter->next(&familyString)) {
557 if (familySet.contains(familyString.fString)) {
558 continue;
559 }
560 familySet.add(familyString.fString);
561 familyName.appendf((familyName.isEmpty() ? "%s" : ", %s"), familyString.fString.c_str());
562 }
563
564 if (!familyName.isEmpty()) {
565 this->addAttribute("font-family", familyName);
566 }
fmalitafe3f2602015-02-03 17:47:12 -0800567}
568
fmalita2aafe6f2015-02-06 12:51:10 -0800569SkBaseDevice* SkSVGDevice::Create(const SkISize& size, SkXMLWriter* writer) {
570 if (!writer) {
fmalita93957f42015-01-30 09:03:29 -0800571 return NULL;
572 }
573
fmalita2aafe6f2015-02-06 12:51:10 -0800574 return SkNEW_ARGS(SkSVGDevice, (size, writer));
fmalita93957f42015-01-30 09:03:29 -0800575}
576
fmalita2aafe6f2015-02-06 12:51:10 -0800577SkSVGDevice::SkSVGDevice(const SkISize& size, SkXMLWriter* writer)
578 : fWriter(writer)
fmalita532faa92015-02-03 05:44:40 -0800579 , fResourceBucket(SkNEW(ResourceBucket)) {
fmalita2aafe6f2015-02-06 12:51:10 -0800580 SkASSERT(writer);
fmalita93957f42015-01-30 09:03:29 -0800581
582 fLegacyBitmap.setInfo(SkImageInfo::MakeUnknown(size.width(), size.height()));
583
584 fWriter->writeHeader();
fmalita532faa92015-02-03 05:44:40 -0800585
586 // The root <svg> tag gets closed by the destructor.
587 fRootElement.reset(SkNEW_ARGS(AutoElement, ("svg", fWriter)));
588
589 fRootElement->addAttribute("xmlns", "http://www.w3.org/2000/svg");
590 fRootElement->addAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
591 fRootElement->addAttribute("width", size.width());
592 fRootElement->addAttribute("height", size.height());
fmalita93957f42015-01-30 09:03:29 -0800593}
594
595SkSVGDevice::~SkSVGDevice() {
fmalita93957f42015-01-30 09:03:29 -0800596}
597
598SkImageInfo SkSVGDevice::imageInfo() const {
599 return fLegacyBitmap.info();
600}
601
602const SkBitmap& SkSVGDevice::onAccessBitmap() {
603 return fLegacyBitmap;
604}
605
fmalita532faa92015-02-03 05:44:40 -0800606void SkSVGDevice::drawPaint(const SkDraw& draw, const SkPaint& paint) {
607 AutoElement rect("rect", fWriter, fResourceBucket, draw, paint);
fmalita1a481fe2015-02-04 07:39:34 -0800608 rect.addRectAttributes(SkRect::MakeWH(SkIntToScalar(this->width()),
609 SkIntToScalar(this->height())));
fmalita93957f42015-01-30 09:03:29 -0800610}
611
612void SkSVGDevice::drawPoints(const SkDraw&, SkCanvas::PointMode mode, size_t count,
613 const SkPoint[], const SkPaint& paint) {
614 // todo
fmalitafe3f2602015-02-03 17:47:12 -0800615 SkDebugf("unsupported operation: drawPoints()\n");
fmalita93957f42015-01-30 09:03:29 -0800616}
617
618void SkSVGDevice::drawRect(const SkDraw& draw, const SkRect& r, const SkPaint& paint) {
fmalita532faa92015-02-03 05:44:40 -0800619 AutoElement rect("rect", fWriter, fResourceBucket, draw, paint);
fmalita1a481fe2015-02-04 07:39:34 -0800620 rect.addRectAttributes(r);
fmalita93957f42015-01-30 09:03:29 -0800621}
622
fmalita532faa92015-02-03 05:44:40 -0800623void SkSVGDevice::drawOval(const SkDraw& draw, const SkRect& oval, const SkPaint& paint) {
624 AutoElement ellipse("ellipse", fWriter, fResourceBucket, draw, paint);
625 ellipse.addAttribute("cx", oval.centerX());
626 ellipse.addAttribute("cy", oval.centerY());
627 ellipse.addAttribute("rx", oval.width() / 2);
628 ellipse.addAttribute("ry", oval.height() / 2);
fmalita93957f42015-01-30 09:03:29 -0800629}
630
631void SkSVGDevice::drawRRect(const SkDraw&, const SkRRect& rr, const SkPaint& paint) {
632 // todo
fmalitafe3f2602015-02-03 17:47:12 -0800633 SkDebugf("unsupported operation: drawRRect()\n");
fmalita93957f42015-01-30 09:03:29 -0800634}
635
636void SkSVGDevice::drawPath(const SkDraw& draw, const SkPath& path, const SkPaint& paint,
637 const SkMatrix* prePathMatrix, bool pathIsMutable) {
fmalita532faa92015-02-03 05:44:40 -0800638 AutoElement elem("path", fWriter, fResourceBucket, draw, paint);
fmalitaa9d9de42015-02-04 17:54:46 -0800639 elem.addPathAttributes(path);
fmalita93957f42015-01-30 09:03:29 -0800640}
641
fmalita827da232015-02-27 07:44:47 -0800642void SkSVGDevice::drawBitmapCommon(const SkDraw& draw, const SkBitmap& bm,
643 const SkPaint& paint) {
644 SkAutoTUnref<const SkData> pngData(
645 SkImageEncoder::EncodeData(bm, SkImageEncoder::kPNG_Type, SkImageEncoder::kDefaultQuality));
646 if (!pngData) {
647 return;
648 }
649
650 size_t b64Size = SkBase64::Encode(pngData->data(), pngData->size(), NULL);
651 SkAutoTMalloc<char> b64Data(b64Size);
652 SkBase64::Encode(pngData->data(), pngData->size(), b64Data.get());
653
654 SkString svgImageData("data:image/png;base64,");
655 svgImageData.append(b64Data.get(), b64Size);
656
657 SkString imageID = fResourceBucket->addImage();
658 {
659 AutoElement defs("defs", fWriter);
660 {
661 AutoElement image("image", fWriter);
662 image.addAttribute("id", imageID);
663 image.addAttribute("width", bm.width());
664 image.addAttribute("height", bm.height());
665 image.addAttribute("xlink:href", svgImageData);
666 }
667 }
668
669 {
670 AutoElement imageUse("use", fWriter, fResourceBucket, draw, paint);
671 imageUse.addAttribute("xlink:href", SkStringPrintf("#%s", imageID.c_str()));
672 }
673}
674
675void SkSVGDevice::drawBitmap(const SkDraw& draw, const SkBitmap& bitmap,
fmalita93957f42015-01-30 09:03:29 -0800676 const SkMatrix& matrix, const SkPaint& paint) {
fmalita827da232015-02-27 07:44:47 -0800677 SkMatrix adjustedMatrix = *draw.fMatrix;
678 adjustedMatrix.preConcat(matrix);
679 SkDraw adjustedDraw(draw);
680 adjustedDraw.fMatrix = &adjustedMatrix;
681
682 drawBitmapCommon(adjustedDraw, bitmap, paint);
fmalita93957f42015-01-30 09:03:29 -0800683}
684
fmalita827da232015-02-27 07:44:47 -0800685void SkSVGDevice::drawSprite(const SkDraw& draw, const SkBitmap& bitmap,
fmalita93957f42015-01-30 09:03:29 -0800686 int x, int y, const SkPaint& paint) {
fmalita827da232015-02-27 07:44:47 -0800687 SkMatrix adjustedMatrix = *draw.fMatrix;
688 adjustedMatrix.preTranslate(SkIntToScalar(x), SkIntToScalar(y));
689 SkDraw adjustedDraw(draw);
690 adjustedDraw.fMatrix = &adjustedMatrix;
691
692 drawBitmapCommon(adjustedDraw, bitmap, paint);
fmalita93957f42015-01-30 09:03:29 -0800693}
694
fmalita827da232015-02-27 07:44:47 -0800695void SkSVGDevice::drawBitmapRect(const SkDraw& draw, const SkBitmap& bm, const SkRect* srcOrNull,
fmalita93957f42015-01-30 09:03:29 -0800696 const SkRect& dst, const SkPaint& paint,
fmalita827da232015-02-27 07:44:47 -0800697 SkCanvas::DrawBitmapRectFlags) {
698 SkMatrix adjustedMatrix;
699 adjustedMatrix.setRectToRect(srcOrNull ? *srcOrNull : SkRect::Make(bm.bounds()),
700 dst,
701 SkMatrix::kFill_ScaleToFit);
702 adjustedMatrix.postConcat(*draw.fMatrix);
703
704 SkDraw adjustedDraw(draw);
705 adjustedDraw.fMatrix = &adjustedMatrix;
706
707 SkClipStack adjustedClipStack;
708 if (srcOrNull && *srcOrNull != SkRect::Make(bm.bounds())) {
709 SkRect devClipRect;
710 draw.fMatrix->mapRect(&devClipRect, dst);
711
712 adjustedClipStack = *draw.fClipStack;
713 adjustedClipStack.clipDevRect(devClipRect, SkRegion::kIntersect_Op, paint.isAntiAlias());
714 adjustedDraw.fClipStack = &adjustedClipStack;
715 }
716
717 drawBitmapCommon(adjustedDraw, bm, paint);
fmalita93957f42015-01-30 09:03:29 -0800718}
719
fmalitafe3f2602015-02-03 17:47:12 -0800720void SkSVGDevice::drawText(const SkDraw& draw, const void* text, size_t len,
fmalita93957f42015-01-30 09:03:29 -0800721 SkScalar x, SkScalar y, const SkPaint& paint) {
fmalitafe3f2602015-02-03 17:47:12 -0800722 AutoElement elem("text", fWriter, fResourceBucket, draw, paint);
fmalitaa9d9de42015-02-04 17:54:46 -0800723 elem.addTextAttributes(paint);
fmalita89747742015-02-19 18:44:51 -0800724
725 SVGTextBuilder builder(text, len, paint, SkPoint::Make(x, y), 0);
726 elem.addAttribute("x", builder.posX());
727 elem.addAttribute("y", builder.posY());
728 elem.addText(builder.text());
fmalita93957f42015-01-30 09:03:29 -0800729}
730
fmalitafe3f2602015-02-03 17:47:12 -0800731void SkSVGDevice::drawPosText(const SkDraw& draw, const void* text, size_t len,
732 const SkScalar pos[], int scalarsPerPos, const SkPoint& offset,
733 const SkPaint& paint) {
734 SkASSERT(scalarsPerPos == 1 || scalarsPerPos == 2);
735
736 AutoElement elem("text", fWriter, fResourceBucket, draw, paint);
fmalitaa9d9de42015-02-04 17:54:46 -0800737 elem.addTextAttributes(paint);
fmalitafe3f2602015-02-03 17:47:12 -0800738
fmalita89747742015-02-19 18:44:51 -0800739 SVGTextBuilder builder(text, len, paint, offset, scalarsPerPos, pos);
740 elem.addAttribute("x", builder.posX());
741 elem.addAttribute("y", builder.posY());
742 elem.addText(builder.text());
fmalita93957f42015-01-30 09:03:29 -0800743}
744
745void SkSVGDevice::drawTextOnPath(const SkDraw&, const void* text, size_t len, const SkPath& path,
746 const SkMatrix* matrix, const SkPaint& paint) {
fmalitaa9d9de42015-02-04 17:54:46 -0800747 SkString pathID = fResourceBucket->addPath();
748
749 {
750 AutoElement defs("defs", fWriter);
751 AutoElement pathElement("path", fWriter);
752 pathElement.addAttribute("id", pathID);
753 pathElement.addPathAttributes(path);
754
755 }
756
757 {
758 AutoElement textElement("text", fWriter);
759 textElement.addTextAttributes(paint);
760
761 if (matrix && !matrix->isIdentity()) {
762 textElement.addAttribute("transform", svg_transform(*matrix));
763 }
764
765 {
766 AutoElement textPathElement("textPath", fWriter);
767 textPathElement.addAttribute("xlink:href", SkStringPrintf("#%s", pathID.c_str()));
768
769 if (paint.getTextAlign() != SkPaint::kLeft_Align) {
770 SkASSERT(paint.getTextAlign() == SkPaint::kCenter_Align ||
771 paint.getTextAlign() == SkPaint::kRight_Align);
772 textPathElement.addAttribute("startOffset",
773 paint.getTextAlign() == SkPaint::kCenter_Align ? "50%" : "100%");
774 }
775
fmalita89747742015-02-19 18:44:51 -0800776 SVGTextBuilder builder(text, len, paint, SkPoint::Make(0, 0), 0);
777 textPathElement.addText(builder.text());
fmalitaa9d9de42015-02-04 17:54:46 -0800778 }
779 }
fmalita93957f42015-01-30 09:03:29 -0800780}
781
782void SkSVGDevice::drawVertices(const SkDraw&, SkCanvas::VertexMode, int vertexCount,
783 const SkPoint verts[], const SkPoint texs[],
784 const SkColor colors[], SkXfermode* xmode,
785 const uint16_t indices[], int indexCount,
786 const SkPaint& paint) {
787 // todo
fmalitafe3f2602015-02-03 17:47:12 -0800788 SkDebugf("unsupported operation: drawVertices()\n");
fmalita93957f42015-01-30 09:03:29 -0800789}
790
791void SkSVGDevice::drawDevice(const SkDraw&, SkBaseDevice*, int x, int y,
792 const SkPaint&) {
793 // todo
fmalitafe3f2602015-02-03 17:47:12 -0800794 SkDebugf("unsupported operation: drawDevice()\n");
fmalita93957f42015-01-30 09:03:29 -0800795}