blob: 904a9a6ea01b5ffd2cb9dc718cc8c64487722e1a [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"
Mike Reedebfce6d2016-12-12 10:02:12 -050025#include "SkClipOpPriv.h"
fmalita93957f42015-01-30 09:03:29 -080026
27namespace {
28
fmalita532faa92015-02-03 05:44:40 -080029static SkString svg_color(SkColor color) {
fmalita1a481fe2015-02-04 07:39:34 -080030 return SkStringPrintf("rgb(%u,%u,%u)",
31 SkColorGetR(color),
32 SkColorGetG(color),
33 SkColorGetB(color));
fmalita532faa92015-02-03 05:44:40 -080034}
35
36static SkScalar svg_opacity(SkColor color) {
37 return SkIntToScalar(SkColorGetA(color)) / SK_AlphaOPAQUE;
38}
39
fmalita12753cc2015-02-04 14:56:35 -080040// Keep in sync with SkPaint::Cap
41static const char* cap_map[] = {
halcanary96fcdcc2015-08-27 07:41:13 -070042 nullptr, // kButt_Cap (default)
fmalita12753cc2015-02-04 14:56:35 -080043 "round", // kRound_Cap
44 "square" // kSquare_Cap
45};
bungeman99fe8222015-08-20 07:57:51 -070046static_assert(SK_ARRAY_COUNT(cap_map) == SkPaint::kCapCount, "missing_cap_map_entry");
fmalita12753cc2015-02-04 14:56:35 -080047
48static const char* svg_cap(SkPaint::Cap cap) {
49 SkASSERT(cap < SK_ARRAY_COUNT(cap_map));
50 return cap_map[cap];
51}
52
53// Keep in sync with SkPaint::Join
54static const char* join_map[] = {
halcanary96fcdcc2015-08-27 07:41:13 -070055 nullptr, // kMiter_Join (default)
fmalita12753cc2015-02-04 14:56:35 -080056 "round", // kRound_Join
57 "bevel" // kBevel_Join
58};
bungeman99fe8222015-08-20 07:57:51 -070059static_assert(SK_ARRAY_COUNT(join_map) == SkPaint::kJoinCount, "missing_join_map_entry");
fmalita12753cc2015-02-04 14:56:35 -080060
61static const char* svg_join(SkPaint::Join join) {
62 SkASSERT(join < SK_ARRAY_COUNT(join_map));
63 return join_map[join];
64}
65
fmalitaa9d9de42015-02-04 17:54:46 -080066// Keep in sync with SkPaint::Align
67static const char* text_align_map[] = {
halcanary96fcdcc2015-08-27 07:41:13 -070068 nullptr, // kLeft_Align (default)
fmalitaa9d9de42015-02-04 17:54:46 -080069 "middle", // kCenter_Align
70 "end" // kRight_Align
71};
bungeman99fe8222015-08-20 07:57:51 -070072static_assert(SK_ARRAY_COUNT(text_align_map) == SkPaint::kAlignCount,
73 "missing_text_align_map_entry");
fmalitaa9d9de42015-02-04 17:54:46 -080074static const char* svg_text_align(SkPaint::Align align) {
75 SkASSERT(align < SK_ARRAY_COUNT(text_align_map));
76 return text_align_map[align];
77}
78
79static SkString svg_transform(const SkMatrix& t) {
80 SkASSERT(!t.isIdentity());
81
82 SkString tstr;
83 switch (t.getType()) {
84 case SkMatrix::kPerspective_Mask:
85 SkDebugf("Can't handle perspective matrices.");
86 break;
87 case SkMatrix::kTranslate_Mask:
88 tstr.printf("translate(%g %g)", t.getTranslateX(), t.getTranslateY());
89 break;
90 case SkMatrix::kScale_Mask:
91 tstr.printf("scale(%g %g)", t.getScaleX(), t.getScaleY());
92 break;
93 default:
94 // http://www.w3.org/TR/SVG/coords.html#TransformMatrixDefined
95 // | a c e |
96 // | b d f |
97 // | 0 0 1 |
98 tstr.printf("matrix(%g %g %g %g %g %g)",
99 t.getScaleX(), t.getSkewY(),
100 t.getSkewX(), t.getScaleY(),
101 t.getTranslateX(), t.getTranslateY());
102 break;
103 }
104
105 return tstr;
106}
107
fmalita532faa92015-02-03 05:44:40 -0800108struct Resources {
109 Resources(const SkPaint& paint)
110 : fPaintServer(svg_color(paint.getColor())) {}
111
112 SkString fPaintServer;
fmalita1a481fe2015-02-04 07:39:34 -0800113 SkString fClip;
fmalita532faa92015-02-03 05:44:40 -0800114};
115
fmalita89747742015-02-19 18:44:51 -0800116class SVGTextBuilder : SkNoncopyable {
117public:
118 SVGTextBuilder(const void* text, size_t byteLen, const SkPaint& paint, const SkPoint& offset,
halcanary96fcdcc2015-08-27 07:41:13 -0700119 unsigned scalarsPerPos, const SkScalar pos[] = nullptr)
fmalita89747742015-02-19 18:44:51 -0800120 : fOffset(offset)
121 , fScalarsPerPos(scalarsPerPos)
122 , fPos(pos)
123 , fLastCharWasWhitespace(true) // start off in whitespace mode to strip all leading space
124 {
125 SkASSERT(scalarsPerPos <= 2);
126 SkASSERT(scalarsPerPos == 0 || SkToBool(pos));
127
128 int count = paint.countText(text, byteLen);
129
130 switch(paint.getTextEncoding()) {
131 case SkPaint::kGlyphID_TextEncoding: {
132 SkASSERT(count * sizeof(uint16_t) == byteLen);
133 SkAutoSTArray<64, SkUnichar> unichars(count);
134 paint.glyphsToUnichars((const uint16_t*)text, count, unichars.get());
135 for (int i = 0; i < count; ++i) {
136 this->appendUnichar(unichars[i]);
137 }
138 } break;
139 case SkPaint::kUTF8_TextEncoding: {
140 const char* c8 = reinterpret_cast<const char*>(text);
141 for (int i = 0; i < count; ++i) {
142 this->appendUnichar(SkUTF8_NextUnichar(&c8));
143 }
144 SkASSERT(reinterpret_cast<const char*>(text) + byteLen == c8);
145 } break;
146 case SkPaint::kUTF16_TextEncoding: {
147 const uint16_t* c16 = reinterpret_cast<const uint16_t*>(text);
148 for (int i = 0; i < count; ++i) {
149 this->appendUnichar(SkUTF16_NextUnichar(&c16));
150 }
151 SkASSERT(SkIsAlign2(byteLen));
152 SkASSERT(reinterpret_cast<const uint16_t*>(text) + (byteLen / 2) == c16);
153 } break;
154 case SkPaint::kUTF32_TextEncoding: {
155 SkASSERT(count * sizeof(uint32_t) == byteLen);
156 const uint32_t* c32 = reinterpret_cast<const uint32_t*>(text);
157 for (int i = 0; i < count; ++i) {
158 this->appendUnichar(c32[i]);
159 }
160 } break;
161 default:
162 SkFAIL("unknown text encoding");
163 }
164
165 if (scalarsPerPos < 2) {
166 SkASSERT(fPosY.isEmpty());
167 fPosY.appendScalar(offset.y()); // DrawText or DrawPosTextH (fixed Y).
168 }
169
170 if (scalarsPerPos < 1) {
171 SkASSERT(fPosX.isEmpty());
172 fPosX.appendScalar(offset.x()); // DrawText (X also fixed).
173 }
174 }
175
176 const SkString& text() const { return fText; }
177 const SkString& posX() const { return fPosX; }
178 const SkString& posY() const { return fPosY; }
179
180private:
181 void appendUnichar(SkUnichar c) {
182 bool discardPos = false;
183 bool isWhitespace = false;
184
185 switch(c) {
186 case ' ':
187 case '\t':
188 // consolidate whitespace to match SVG's xml:space=default munging
189 // (http://www.w3.org/TR/SVG/text.html#WhiteSpace)
190 if (fLastCharWasWhitespace) {
191 discardPos = true;
192 } else {
193 fText.appendUnichar(c);
194 }
195 isWhitespace = true;
196 break;
197 case '\0':
198 // SkPaint::glyphsToUnichars() returns \0 for inconvertible glyphs, but these
199 // are not legal XML characters (http://www.w3.org/TR/REC-xml/#charsets)
200 discardPos = true;
201 isWhitespace = fLastCharWasWhitespace; // preserve whitespace consolidation
202 break;
203 case '&':
204 fText.append("&amp;");
205 break;
206 case '"':
207 fText.append("&quot;");
208 break;
209 case '\'':
210 fText.append("&apos;");
211 break;
212 case '<':
213 fText.append("&lt;");
214 break;
215 case '>':
216 fText.append("&gt;");
217 break;
218 default:
219 fText.appendUnichar(c);
220 break;
221 }
222
223 this->advancePos(discardPos);
224 fLastCharWasWhitespace = isWhitespace;
225 }
226
227 void advancePos(bool discard) {
228 if (!discard && fScalarsPerPos > 0) {
229 fPosX.appendf("%.8g, ", fOffset.x() + fPos[0]);
230 if (fScalarsPerPos > 1) {
231 SkASSERT(fScalarsPerPos == 2);
232 fPosY.appendf("%.8g, ", fOffset.y() + fPos[1]);
233 }
234 }
235 fPos += fScalarsPerPos;
236 }
237
238 const SkPoint& fOffset;
239 const unsigned fScalarsPerPos;
240 const SkScalar* fPos;
241
242 SkString fText, fPosX, fPosY;
243 bool fLastCharWasWhitespace;
244};
245
fmalita532faa92015-02-03 05:44:40 -0800246}
247
248// For now all this does is serve unique serial IDs, but it will eventually evolve to track
249// and deduplicate resources.
250class SkSVGDevice::ResourceBucket : ::SkNoncopyable {
251public:
fmalita827da232015-02-27 07:44:47 -0800252 ResourceBucket() : fGradientCount(0), fClipCount(0), fPathCount(0), fImageCount(0) {}
fmalita532faa92015-02-03 05:44:40 -0800253
254 SkString addLinearGradient() {
fmalita1a481fe2015-02-04 07:39:34 -0800255 return SkStringPrintf("gradient_%d", fGradientCount++);
256 }
257
258 SkString addClip() {
259 return SkStringPrintf("clip_%d", fClipCount++);
fmalita532faa92015-02-03 05:44:40 -0800260 }
261
fmalitaa9d9de42015-02-04 17:54:46 -0800262 SkString addPath() {
263 return SkStringPrintf("path_%d", fPathCount++);
264 }
265
fmalita827da232015-02-27 07:44:47 -0800266 SkString addImage() {
267 return SkStringPrintf("img_%d", fImageCount++);
268 }
269
fmalita532faa92015-02-03 05:44:40 -0800270private:
271 uint32_t fGradientCount;
fmalita1a481fe2015-02-04 07:39:34 -0800272 uint32_t fClipCount;
fmalitaa9d9de42015-02-04 17:54:46 -0800273 uint32_t fPathCount;
fmalita827da232015-02-27 07:44:47 -0800274 uint32_t fImageCount;
fmalita532faa92015-02-03 05:44:40 -0800275};
276
Mike Reedc5e641c2017-02-17 14:38:11 -0500277struct SkSVGDevice::MxCp {
278 const SkMatrix* fMatrix;
279 const SkClipStack* fClipStack;
280
Mike Reedf880b682017-03-10 11:30:44 -0500281 MxCp(const SkMatrix* mx, const SkClipStack* cs) : fMatrix(mx), fClipStack(cs) {}
282 MxCp(SkSVGDevice* device) : fMatrix(&device->ctm()), fClipStack(&device->cs()) {}
Mike Reedc5e641c2017-02-17 14:38:11 -0500283};
284
fmalita532faa92015-02-03 05:44:40 -0800285class SkSVGDevice::AutoElement : ::SkNoncopyable {
fmalita93957f42015-01-30 09:03:29 -0800286public:
287 AutoElement(const char name[], SkXMLWriter* writer)
fmalita532faa92015-02-03 05:44:40 -0800288 : fWriter(writer)
halcanary96fcdcc2015-08-27 07:41:13 -0700289 , fResourceBucket(nullptr) {
fmalita93957f42015-01-30 09:03:29 -0800290 fWriter->startElement(name);
291 }
292
fmalita532faa92015-02-03 05:44:40 -0800293 AutoElement(const char name[], SkXMLWriter* writer, ResourceBucket* bucket,
Mike Reedc5e641c2017-02-17 14:38:11 -0500294 const MxCp& mc, const SkPaint& paint)
fmalita532faa92015-02-03 05:44:40 -0800295 : fWriter(writer)
296 , fResourceBucket(bucket) {
297
Mike Reedc5e641c2017-02-17 14:38:11 -0500298 Resources res = this->addResources(mc, paint);
fmalita827da232015-02-27 07:44:47 -0800299 if (!res.fClip.isEmpty()) {
300 // The clip is in device space. Apply it via a <g> wrapper to avoid local transform
301 // interference.
halcanary385fe4d2015-08-26 13:07:48 -0700302 fClipGroup.reset(new AutoElement("g", fWriter));
fmalita827da232015-02-27 07:44:47 -0800303 fClipGroup->addAttribute("clip-path",res.fClip);
304 }
fmalita532faa92015-02-03 05:44:40 -0800305
306 fWriter->startElement(name);
307
308 this->addPaint(paint, res);
fmalitaa9d9de42015-02-04 17:54:46 -0800309
Mike Reedc5e641c2017-02-17 14:38:11 -0500310 if (!mc.fMatrix->isIdentity()) {
311 this->addAttribute("transform", svg_transform(*mc.fMatrix));
fmalitaa9d9de42015-02-04 17:54:46 -0800312 }
fmalita532faa92015-02-03 05:44:40 -0800313 }
314
fmalita93957f42015-01-30 09:03:29 -0800315 ~AutoElement() {
316 fWriter->endElement();
317 }
318
fmalita532faa92015-02-03 05:44:40 -0800319 void addAttribute(const char name[], const char val[]) {
320 fWriter->addAttribute(name, val);
321 }
322
323 void addAttribute(const char name[], const SkString& val) {
324 fWriter->addAttribute(name, val.c_str());
325 }
326
327 void addAttribute(const char name[], int32_t val) {
328 fWriter->addS32Attribute(name, val);
329 }
330
331 void addAttribute(const char name[], SkScalar val) {
332 fWriter->addScalarAttribute(name, val);
333 }
334
fmalitafe3f2602015-02-03 17:47:12 -0800335 void addText(const SkString& text) {
reede73da402015-02-04 18:29:27 -0800336 fWriter->addText(text.c_str(), text.size());
fmalitafe3f2602015-02-03 17:47:12 -0800337 }
338
fmalita1a481fe2015-02-04 07:39:34 -0800339 void addRectAttributes(const SkRect&);
fmalitaa9d9de42015-02-04 17:54:46 -0800340 void addPathAttributes(const SkPath&);
341 void addTextAttributes(const SkPaint&);
fmalitafe3f2602015-02-03 17:47:12 -0800342
fmalita93957f42015-01-30 09:03:29 -0800343private:
Mike Reedc5e641c2017-02-17 14:38:11 -0500344 Resources addResources(const MxCp&, const SkPaint& paint);
345 void addClipResources(const MxCp&, Resources* resources);
fmalita1a481fe2015-02-04 07:39:34 -0800346 void addShaderResources(const SkPaint& paint, Resources* resources);
fmalita532faa92015-02-03 05:44:40 -0800347
348 void addPaint(const SkPaint& paint, const Resources& resources);
fmalita532faa92015-02-03 05:44:40 -0800349
350 SkString addLinearGradientDef(const SkShader::GradientInfo& info, const SkShader* shader);
351
fmalita827da232015-02-27 07:44:47 -0800352 SkXMLWriter* fWriter;
353 ResourceBucket* fResourceBucket;
Ben Wagner145dbcd2016-11-03 14:40:50 -0400354 std::unique_ptr<AutoElement> fClipGroup;
fmalita93957f42015-01-30 09:03:29 -0800355};
356
fmalita532faa92015-02-03 05:44:40 -0800357void SkSVGDevice::AutoElement::addPaint(const SkPaint& paint, const Resources& resources) {
358 SkPaint::Style style = paint.getStyle();
359 if (style == SkPaint::kFill_Style || style == SkPaint::kStrokeAndFill_Style) {
360 this->addAttribute("fill", resources.fPaintServer);
fmalita12753cc2015-02-04 14:56:35 -0800361
362 if (SK_AlphaOPAQUE != SkColorGetA(paint.getColor())) {
363 this->addAttribute("fill-opacity", svg_opacity(paint.getColor()));
364 }
fmalita532faa92015-02-03 05:44:40 -0800365 } else {
fmalita12753cc2015-02-04 14:56:35 -0800366 SkASSERT(style == SkPaint::kStroke_Style);
fmalita532faa92015-02-03 05:44:40 -0800367 this->addAttribute("fill", "none");
368 }
369
370 if (style == SkPaint::kStroke_Style || style == SkPaint::kStrokeAndFill_Style) {
371 this->addAttribute("stroke", resources.fPaintServer);
fmalita532faa92015-02-03 05:44:40 -0800372
fmalita12753cc2015-02-04 14:56:35 -0800373 SkScalar strokeWidth = paint.getStrokeWidth();
374 if (strokeWidth == 0) {
375 // Hairline stroke
376 strokeWidth = 1;
377 this->addAttribute("vector-effect", "non-scaling-stroke");
378 }
379 this->addAttribute("stroke-width", strokeWidth);
380
381 if (const char* cap = svg_cap(paint.getStrokeCap())) {
382 this->addAttribute("stroke-linecap", cap);
383 }
384
385 if (const char* join = svg_join(paint.getStrokeJoin())) {
386 this->addAttribute("stroke-linejoin", join);
387 }
388
389 if (paint.getStrokeJoin() == SkPaint::kMiter_Join) {
390 this->addAttribute("stroke-miterlimit", paint.getStrokeMiter());
391 }
392
393 if (SK_AlphaOPAQUE != SkColorGetA(paint.getColor())) {
394 this->addAttribute("stroke-opacity", svg_opacity(paint.getColor()));
395 }
396 } else {
397 SkASSERT(style == SkPaint::kFill_Style);
398 this->addAttribute("stroke", "none");
fmalita532faa92015-02-03 05:44:40 -0800399 }
400}
401
Mike Reedc5e641c2017-02-17 14:38:11 -0500402Resources SkSVGDevice::AutoElement::addResources(const MxCp& mc, const SkPaint& paint) {
fmalita532faa92015-02-03 05:44:40 -0800403 Resources resources(paint);
fmalita1a481fe2015-02-04 07:39:34 -0800404
405 // FIXME: this is a weak heuristic and we end up with LOTS of redundant clips.
Mike Reedc5e641c2017-02-17 14:38:11 -0500406 bool hasClip = !mc.fClipStack->isWideOpen();
fmalita1a481fe2015-02-04 07:39:34 -0800407 bool hasShader = SkToBool(paint.getShader());
408
409 if (hasClip || hasShader) {
410 AutoElement defs("defs", fWriter);
411
412 if (hasClip) {
Mike Reedc5e641c2017-02-17 14:38:11 -0500413 this->addClipResources(mc, &resources);
fmalita1a481fe2015-02-04 07:39:34 -0800414 }
415
416 if (hasShader) {
417 this->addShaderResources(paint, &resources);
418 }
419 }
420
fmalita532faa92015-02-03 05:44:40 -0800421 return resources;
422}
423
fmalita1a481fe2015-02-04 07:39:34 -0800424void SkSVGDevice::AutoElement::addShaderResources(const SkPaint& paint, Resources* resources) {
fmalita532faa92015-02-03 05:44:40 -0800425 const SkShader* shader = paint.getShader();
fmalita1a481fe2015-02-04 07:39:34 -0800426 SkASSERT(SkToBool(shader));
fmalita532faa92015-02-03 05:44:40 -0800427
428 SkShader::GradientInfo grInfo;
429 grInfo.fColorCount = 0;
430 if (SkShader::kLinear_GradientType != shader->asAGradient(&grInfo)) {
431 // TODO: non-linear gradient support
432 SkDebugf("unsupported shader type\n");
433 return;
434 }
435
fmalita1a481fe2015-02-04 07:39:34 -0800436 SkAutoSTArray<16, SkColor> grColors(grInfo.fColorCount);
437 SkAutoSTArray<16, SkScalar> grOffsets(grInfo.fColorCount);
438 grInfo.fColors = grColors.get();
439 grInfo.fColorOffsets = grOffsets.get();
440
441 // One more call to get the actual colors/offsets.
442 shader->asAGradient(&grInfo);
443 SkASSERT(grInfo.fColorCount <= grColors.count());
444 SkASSERT(grInfo.fColorCount <= grOffsets.count());
445
446 resources->fPaintServer.printf("url(#%s)", addLinearGradientDef(grInfo, shader).c_str());
447}
448
Mike Reedc5e641c2017-02-17 14:38:11 -0500449void SkSVGDevice::AutoElement::addClipResources(const MxCp& mc, Resources* resources) {
450 SkASSERT(!mc.fClipStack->isWideOpen());
fmalita1a481fe2015-02-04 07:39:34 -0800451
452 SkPath clipPath;
Mike Reedc5e641c2017-02-17 14:38:11 -0500453 (void) mc.fClipStack->asPath(&clipPath);
fmalita1a481fe2015-02-04 07:39:34 -0800454
455 SkString clipID = fResourceBucket->addClip();
456 const char* clipRule = clipPath.getFillType() == SkPath::kEvenOdd_FillType ?
457 "evenodd" : "nonzero";
fmalita532faa92015-02-03 05:44:40 -0800458 {
fmalita1a481fe2015-02-04 07:39:34 -0800459 // clipPath is in device space, but since we're only pushing transform attributes
460 // to the leaf nodes, so are all our elements => SVG userSpaceOnUse == device space.
461 AutoElement clipPathElement("clipPath", fWriter);
462 clipPathElement.addAttribute("id", clipID);
fmalita532faa92015-02-03 05:44:40 -0800463
fmalita1a481fe2015-02-04 07:39:34 -0800464 SkRect clipRect = SkRect::MakeEmpty();
465 if (clipPath.isEmpty() || clipPath.isRect(&clipRect)) {
466 AutoElement rectElement("rect", fWriter);
467 rectElement.addRectAttributes(clipRect);
468 rectElement.addAttribute("clip-rule", clipRule);
469 } else {
470 AutoElement pathElement("path", fWriter);
fmalitaa9d9de42015-02-04 17:54:46 -0800471 pathElement.addPathAttributes(clipPath);
fmalita1a481fe2015-02-04 07:39:34 -0800472 pathElement.addAttribute("clip-rule", clipRule);
473 }
fmalita532faa92015-02-03 05:44:40 -0800474 }
fmalita1a481fe2015-02-04 07:39:34 -0800475
476 resources->fClip.printf("url(#%s)", clipID.c_str());
fmalita532faa92015-02-03 05:44:40 -0800477}
478
479SkString SkSVGDevice::AutoElement::addLinearGradientDef(const SkShader::GradientInfo& info,
480 const SkShader* shader) {
481 SkASSERT(fResourceBucket);
482 SkString id = fResourceBucket->addLinearGradient();
483
484 {
485 AutoElement gradient("linearGradient", fWriter);
486
487 gradient.addAttribute("id", id);
488 gradient.addAttribute("gradientUnits", "userSpaceOnUse");
489 gradient.addAttribute("x1", info.fPoint[0].x());
490 gradient.addAttribute("y1", info.fPoint[0].y());
491 gradient.addAttribute("x2", info.fPoint[1].x());
492 gradient.addAttribute("y2", info.fPoint[1].y());
fmalitaa9d9de42015-02-04 17:54:46 -0800493
494 if (!shader->getLocalMatrix().isIdentity()) {
495 this->addAttribute("gradientTransform", svg_transform(shader->getLocalMatrix()));
496 }
fmalita532faa92015-02-03 05:44:40 -0800497
498 SkASSERT(info.fColorCount >= 2);
499 for (int i = 0; i < info.fColorCount; ++i) {
500 SkColor color = info.fColors[i];
501 SkString colorStr(svg_color(color));
502
503 {
504 AutoElement stop("stop", fWriter);
505 stop.addAttribute("offset", info.fColorOffsets[i]);
506 stop.addAttribute("stop-color", colorStr.c_str());
507
508 if (SK_AlphaOPAQUE != SkColorGetA(color)) {
509 stop.addAttribute("stop-opacity", svg_opacity(color));
510 }
511 }
512 }
513 }
514
515 return id;
fmalita93957f42015-01-30 09:03:29 -0800516}
517
fmalita1a481fe2015-02-04 07:39:34 -0800518void SkSVGDevice::AutoElement::addRectAttributes(const SkRect& rect) {
519 // x, y default to 0
520 if (rect.x() != 0) {
521 this->addAttribute("x", rect.x());
522 }
523 if (rect.y() != 0) {
524 this->addAttribute("y", rect.y());
525 }
526
527 this->addAttribute("width", rect.width());
528 this->addAttribute("height", rect.height());
529}
530
fmalitaa9d9de42015-02-04 17:54:46 -0800531void SkSVGDevice::AutoElement::addPathAttributes(const SkPath& path) {
532 SkString pathData;
533 SkParsePath::ToSVGString(path, &pathData);
534 this->addAttribute("d", pathData);
535}
536
537void SkSVGDevice::AutoElement::addTextAttributes(const SkPaint& paint) {
fmalitafe3f2602015-02-03 17:47:12 -0800538 this->addAttribute("font-size", paint.getTextSize());
539
fmalitaa9d9de42015-02-04 17:54:46 -0800540 if (const char* textAlign = svg_text_align(paint.getTextAlign())) {
541 this->addAttribute("text-anchor", textAlign);
542 }
fmalitaf89f60f2015-02-13 08:55:24 -0800543
544 SkString familyName;
mtklein02f46cf2015-03-20 13:48:42 -0700545 SkTHashSet<SkString> familySet;
Mike Reed693fdbd2017-01-12 10:13:40 -0500546 sk_sp<SkTypeface> tface(paint.getTypeface() ? paint.refTypeface() : SkTypeface::MakeDefault());
fmalita7a048692015-02-20 13:54:40 -0800547
548 SkASSERT(tface);
Ben Wagner71319502017-07-27 10:45:29 -0400549 SkFontStyle style = tface->fontStyle();
550 if (style.slant() == SkFontStyle::kItalic_Slant) {
fmalita7a048692015-02-20 13:54:40 -0800551 this->addAttribute("font-style", "italic");
Ben Wagner71319502017-07-27 10:45:29 -0400552 } else if (style.slant() == SkFontStyle::kOblique_Slant) {
553 this->addAttribute("font-style", "oblique");
fmalita7a048692015-02-20 13:54:40 -0800554 }
Ben Wagner71319502017-07-27 10:45:29 -0400555 int weightIndex = (SkTPin(style.weight(), 100, 900) - 50) / 100;
556 if (weightIndex != 3) {
557 static constexpr const char* weights[] = {
558 "100", "200", "300", "normal", "400", "500", "600", "bold", "800", "900"
559 };
560 this->addAttribute("font-weight", weights[weightIndex]);
561 }
562 int stretchIndex = style.width() - 1;
563 if (stretchIndex != 4) {
564 static constexpr const char* stretches[] = {
565 "ultra-condensed", "extra-condensed", "condensed", "semi-condensed",
566 "normal",
567 "semi-expanded", "expanded", "extra-expanded", "ultra-expanded"
568 };
569 this->addAttribute("font-stretch", stretches[stretchIndex]);
fmalita7a048692015-02-20 13:54:40 -0800570 }
571
Hal Canary67b39de2016-11-07 11:47:44 -0500572 sk_sp<SkTypeface::LocalizedStrings> familyNameIter(tface->createFamilyNameIterator());
fmalitaf89f60f2015-02-13 08:55:24 -0800573 SkTypeface::LocalizedString familyString;
Mike Reedf67c4592017-02-17 17:06:11 -0500574 if (familyNameIter) {
575 while (familyNameIter->next(&familyString)) {
576 if (familySet.contains(familyString.fString)) {
577 continue;
578 }
579 familySet.add(familyString.fString);
580 familyName.appendf((familyName.isEmpty() ? "%s" : ", %s"), familyString.fString.c_str());
fmalitaf89f60f2015-02-13 08:55:24 -0800581 }
fmalitaf89f60f2015-02-13 08:55:24 -0800582 }
fmalitaf89f60f2015-02-13 08:55:24 -0800583 if (!familyName.isEmpty()) {
584 this->addAttribute("font-family", familyName);
585 }
fmalitafe3f2602015-02-03 17:47:12 -0800586}
587
fmalita2aafe6f2015-02-06 12:51:10 -0800588SkBaseDevice* SkSVGDevice::Create(const SkISize& size, SkXMLWriter* writer) {
589 if (!writer) {
halcanary96fcdcc2015-08-27 07:41:13 -0700590 return nullptr;
fmalita93957f42015-01-30 09:03:29 -0800591 }
592
halcanary385fe4d2015-08-26 13:07:48 -0700593 return new SkSVGDevice(size, writer);
fmalita93957f42015-01-30 09:03:29 -0800594}
595
fmalita2aafe6f2015-02-06 12:51:10 -0800596SkSVGDevice::SkSVGDevice(const SkISize& size, SkXMLWriter* writer)
reed589a39e2016-08-20 07:59:19 -0700597 : INHERITED(SkImageInfo::MakeUnknown(size.fWidth, size.fHeight),
598 SkSurfaceProps(0, kUnknown_SkPixelGeometry))
robertphillips9a53fd72015-06-22 09:46:59 -0700599 , fWriter(writer)
robertphillips1f3923e2016-07-21 07:17:54 -0700600 , fResourceBucket(new ResourceBucket)
reed589a39e2016-08-20 07:59:19 -0700601{
fmalita2aafe6f2015-02-06 12:51:10 -0800602 SkASSERT(writer);
fmalita93957f42015-01-30 09:03:29 -0800603
fmalita93957f42015-01-30 09:03:29 -0800604 fWriter->writeHeader();
fmalita532faa92015-02-03 05:44:40 -0800605
606 // The root <svg> tag gets closed by the destructor.
halcanary385fe4d2015-08-26 13:07:48 -0700607 fRootElement.reset(new AutoElement("svg", fWriter));
fmalita532faa92015-02-03 05:44:40 -0800608
609 fRootElement->addAttribute("xmlns", "http://www.w3.org/2000/svg");
610 fRootElement->addAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
611 fRootElement->addAttribute("width", size.width());
612 fRootElement->addAttribute("height", size.height());
fmalita93957f42015-01-30 09:03:29 -0800613}
614
615SkSVGDevice::~SkSVGDevice() {
fmalita93957f42015-01-30 09:03:29 -0800616}
617
Mike Reeda1361362017-03-07 09:37:29 -0500618void SkSVGDevice::drawPaint(const SkPaint& paint) {
619 AutoElement rect("rect", fWriter, fResourceBucket.get(), MxCp(this), paint);
fmalita1a481fe2015-02-04 07:39:34 -0800620 rect.addRectAttributes(SkRect::MakeWH(SkIntToScalar(this->width()),
621 SkIntToScalar(this->height())));
fmalita93957f42015-01-30 09:03:29 -0800622}
623
Mike Reeda1361362017-03-07 09:37:29 -0500624void SkSVGDevice::drawPoints(SkCanvas::PointMode mode, size_t count,
reed5f68c342015-05-15 10:11:11 -0700625 const SkPoint pts[], const SkPaint& paint) {
626 SkPath path;
627
628 switch (mode) {
629 // todo
630 case SkCanvas::kPoints_PointMode:
631 SkDebugf("unsupported operation: drawPoints(kPoints_PointMode)\n");
632 break;
633 case SkCanvas::kLines_PointMode:
634 count -= 1;
635 for (size_t i = 0; i < count; i += 2) {
636 path.rewind();
637 path.moveTo(pts[i]);
638 path.lineTo(pts[i+1]);
Mike Reeda1361362017-03-07 09:37:29 -0500639 AutoElement elem("path", fWriter, fResourceBucket.get(), MxCp(this), paint);
reed5f68c342015-05-15 10:11:11 -0700640 elem.addPathAttributes(path);
641 }
642 break;
643 case SkCanvas::kPolygon_PointMode:
644 if (count > 1) {
645 path.addPoly(pts, SkToInt(count), false);
646 path.moveTo(pts[0]);
Mike Reeda1361362017-03-07 09:37:29 -0500647 AutoElement elem("path", fWriter, fResourceBucket.get(), MxCp(this), paint);
reed5f68c342015-05-15 10:11:11 -0700648 elem.addPathAttributes(path);
649 }
650 break;
651 }
fmalita93957f42015-01-30 09:03:29 -0800652}
653
Mike Reeda1361362017-03-07 09:37:29 -0500654void SkSVGDevice::drawRect(const SkRect& r, const SkPaint& paint) {
655 AutoElement rect("rect", fWriter, fResourceBucket.get(), MxCp(this), paint);
fmalita1a481fe2015-02-04 07:39:34 -0800656 rect.addRectAttributes(r);
fmalita93957f42015-01-30 09:03:29 -0800657}
658
Mike Reeda1361362017-03-07 09:37:29 -0500659void SkSVGDevice::drawOval(const SkRect& oval, const SkPaint& paint) {
660 AutoElement ellipse("ellipse", fWriter, fResourceBucket.get(), MxCp(this), paint);
fmalita532faa92015-02-03 05:44:40 -0800661 ellipse.addAttribute("cx", oval.centerX());
662 ellipse.addAttribute("cy", oval.centerY());
663 ellipse.addAttribute("rx", oval.width() / 2);
664 ellipse.addAttribute("ry", oval.height() / 2);
fmalita93957f42015-01-30 09:03:29 -0800665}
666
Mike Reeda1361362017-03-07 09:37:29 -0500667void SkSVGDevice::drawRRect(const SkRRect& rr, const SkPaint& paint) {
reed5f68c342015-05-15 10:11:11 -0700668 SkPath path;
669 path.addRRect(rr);
670
Mike Reeda1361362017-03-07 09:37:29 -0500671 AutoElement elem("path", fWriter, fResourceBucket.get(), MxCp(this), paint);
reed5f68c342015-05-15 10:11:11 -0700672 elem.addPathAttributes(path);
fmalita93957f42015-01-30 09:03:29 -0800673}
674
Mike Reeda1361362017-03-07 09:37:29 -0500675void SkSVGDevice::drawPath(const SkPath& path, const SkPaint& paint,
fmalita93957f42015-01-30 09:03:29 -0800676 const SkMatrix* prePathMatrix, bool pathIsMutable) {
Mike Reeda1361362017-03-07 09:37:29 -0500677 AutoElement elem("path", fWriter, fResourceBucket.get(), MxCp(this), paint);
fmalitaa9d9de42015-02-04 17:54:46 -0800678 elem.addPathAttributes(path);
fmalita221722b2016-09-06 14:37:02 -0700679
680 // TODO: inverse fill types?
681 if (path.getFillType() == SkPath::kEvenOdd_FillType) {
682 elem.addAttribute("fill-rule", "evenodd");
683 }
fmalita93957f42015-01-30 09:03:29 -0800684}
685
Hal Canarydb683012016-11-23 08:55:18 -0700686static sk_sp<SkData> encode(const SkBitmap& src) {
687 SkDynamicMemoryWStream buf;
688 return SkEncodeImage(&buf, src, SkEncodedImageFormat::kPNG, 80) ? buf.detachAsData() : nullptr;
689}
690
Mike Reedc5e641c2017-02-17 14:38:11 -0500691void SkSVGDevice::drawBitmapCommon(const MxCp& mc, const SkBitmap& bm, const SkPaint& paint) {
Hal Canarydb683012016-11-23 08:55:18 -0700692 sk_sp<SkData> pngData = encode(bm);
fmalita827da232015-02-27 07:44:47 -0800693 if (!pngData) {
694 return;
695 }
696
halcanary96fcdcc2015-08-27 07:41:13 -0700697 size_t b64Size = SkBase64::Encode(pngData->data(), pngData->size(), nullptr);
fmalita827da232015-02-27 07:44:47 -0800698 SkAutoTMalloc<char> b64Data(b64Size);
699 SkBase64::Encode(pngData->data(), pngData->size(), b64Data.get());
700
701 SkString svgImageData("data:image/png;base64,");
702 svgImageData.append(b64Data.get(), b64Size);
703
704 SkString imageID = fResourceBucket->addImage();
705 {
706 AutoElement defs("defs", fWriter);
707 {
708 AutoElement image("image", fWriter);
709 image.addAttribute("id", imageID);
710 image.addAttribute("width", bm.width());
711 image.addAttribute("height", bm.height());
712 image.addAttribute("xlink:href", svgImageData);
713 }
714 }
715
716 {
Mike Reedc5e641c2017-02-17 14:38:11 -0500717 AutoElement imageUse("use", fWriter, fResourceBucket.get(), mc, paint);
fmalita827da232015-02-27 07:44:47 -0800718 imageUse.addAttribute("xlink:href", SkStringPrintf("#%s", imageID.c_str()));
719 }
720}
721
Hal Canaryb9642382017-06-27 09:58:56 -0400722void SkSVGDevice::drawBitmap(const SkBitmap& bitmap, SkScalar x, SkScalar y,
723 const SkPaint& paint) {
Mike Reeda1361362017-03-07 09:37:29 -0500724 MxCp mc(this);
Mike Reedc5e641c2017-02-17 14:38:11 -0500725 SkMatrix adjustedMatrix = *mc.fMatrix;
Hal Canaryb9642382017-06-27 09:58:56 -0400726 adjustedMatrix.preTranslate(x, y);
Mike Reedc5e641c2017-02-17 14:38:11 -0500727 mc.fMatrix = &adjustedMatrix;
fmalita827da232015-02-27 07:44:47 -0800728
Mike Reedc5e641c2017-02-17 14:38:11 -0500729 drawBitmapCommon(mc, bitmap, paint);
fmalita93957f42015-01-30 09:03:29 -0800730}
731
Mike Reeda1361362017-03-07 09:37:29 -0500732void SkSVGDevice::drawSprite(const SkBitmap& bitmap,
fmalita93957f42015-01-30 09:03:29 -0800733 int x, int y, const SkPaint& paint) {
Mike Reeda1361362017-03-07 09:37:29 -0500734 MxCp mc(this);
Mike Reedc5e641c2017-02-17 14:38:11 -0500735 SkMatrix adjustedMatrix = *mc.fMatrix;
fmalita827da232015-02-27 07:44:47 -0800736 adjustedMatrix.preTranslate(SkIntToScalar(x), SkIntToScalar(y));
Mike Reedc5e641c2017-02-17 14:38:11 -0500737 mc.fMatrix = &adjustedMatrix;
fmalita827da232015-02-27 07:44:47 -0800738
Mike Reedc5e641c2017-02-17 14:38:11 -0500739 drawBitmapCommon(mc, bitmap, paint);
fmalita93957f42015-01-30 09:03:29 -0800740}
741
Mike Reeda1361362017-03-07 09:37:29 -0500742void SkSVGDevice::drawBitmapRect(const SkBitmap& bm, const SkRect* srcOrNull,
fmalita93957f42015-01-30 09:03:29 -0800743 const SkRect& dst, const SkPaint& paint,
reed562fe472015-07-28 07:35:14 -0700744 SkCanvas::SrcRectConstraint) {
Mike Reedf880b682017-03-10 11:30:44 -0500745 SkClipStack* cs = &this->cs();
746 SkClipStack::AutoRestore ar(cs, false);
Mike Reedc5e641c2017-02-17 14:38:11 -0500747 if (srcOrNull && *srcOrNull != SkRect::Make(bm.bounds())) {
Mike Reedf880b682017-03-10 11:30:44 -0500748 cs->save();
749 cs->clipRect(dst, this->ctm(), kIntersect_SkClipOp, paint.isAntiAlias());
Mike Reedc5e641c2017-02-17 14:38:11 -0500750 }
751
fmalita827da232015-02-27 07:44:47 -0800752 SkMatrix adjustedMatrix;
753 adjustedMatrix.setRectToRect(srcOrNull ? *srcOrNull : SkRect::Make(bm.bounds()),
754 dst,
755 SkMatrix::kFill_ScaleToFit);
Mike Reedf880b682017-03-10 11:30:44 -0500756 adjustedMatrix.postConcat(this->ctm());
fmalita827da232015-02-27 07:44:47 -0800757
Mike Reedf880b682017-03-10 11:30:44 -0500758 drawBitmapCommon(MxCp(&adjustedMatrix, cs), bm, paint);
fmalita93957f42015-01-30 09:03:29 -0800759}
760
Mike Reeda1361362017-03-07 09:37:29 -0500761void SkSVGDevice::drawText(const void* text, size_t len,
fmalita93957f42015-01-30 09:03:29 -0800762 SkScalar x, SkScalar y, const SkPaint& paint) {
Mike Reeda1361362017-03-07 09:37:29 -0500763 AutoElement elem("text", fWriter, fResourceBucket.get(), MxCp(this), paint);
fmalitaa9d9de42015-02-04 17:54:46 -0800764 elem.addTextAttributes(paint);
fmalita89747742015-02-19 18:44:51 -0800765
766 SVGTextBuilder builder(text, len, paint, SkPoint::Make(x, y), 0);
767 elem.addAttribute("x", builder.posX());
768 elem.addAttribute("y", builder.posY());
769 elem.addText(builder.text());
fmalita93957f42015-01-30 09:03:29 -0800770}
771
Mike Reeda1361362017-03-07 09:37:29 -0500772void SkSVGDevice::drawPosText(const void* text, size_t len,
fmalitafe3f2602015-02-03 17:47:12 -0800773 const SkScalar pos[], int scalarsPerPos, const SkPoint& offset,
774 const SkPaint& paint) {
775 SkASSERT(scalarsPerPos == 1 || scalarsPerPos == 2);
776
Mike Reeda1361362017-03-07 09:37:29 -0500777 AutoElement elem("text", fWriter, fResourceBucket.get(), MxCp(this), paint);
fmalitaa9d9de42015-02-04 17:54:46 -0800778 elem.addTextAttributes(paint);
fmalitafe3f2602015-02-03 17:47:12 -0800779
fmalita89747742015-02-19 18:44:51 -0800780 SVGTextBuilder builder(text, len, paint, offset, scalarsPerPos, pos);
781 elem.addAttribute("x", builder.posX());
782 elem.addAttribute("y", builder.posY());
783 elem.addText(builder.text());
fmalita93957f42015-01-30 09:03:29 -0800784}
785
Mike Reeda1361362017-03-07 09:37:29 -0500786void SkSVGDevice::drawTextOnPath(const void* text, size_t len, const SkPath& path,
fmalita93957f42015-01-30 09:03:29 -0800787 const SkMatrix* matrix, const SkPaint& paint) {
fmalitaa9d9de42015-02-04 17:54:46 -0800788 SkString pathID = fResourceBucket->addPath();
789
790 {
791 AutoElement defs("defs", fWriter);
792 AutoElement pathElement("path", fWriter);
793 pathElement.addAttribute("id", pathID);
794 pathElement.addPathAttributes(path);
795
796 }
797
798 {
799 AutoElement textElement("text", fWriter);
800 textElement.addTextAttributes(paint);
801
802 if (matrix && !matrix->isIdentity()) {
803 textElement.addAttribute("transform", svg_transform(*matrix));
804 }
805
806 {
807 AutoElement textPathElement("textPath", fWriter);
808 textPathElement.addAttribute("xlink:href", SkStringPrintf("#%s", pathID.c_str()));
809
810 if (paint.getTextAlign() != SkPaint::kLeft_Align) {
811 SkASSERT(paint.getTextAlign() == SkPaint::kCenter_Align ||
812 paint.getTextAlign() == SkPaint::kRight_Align);
813 textPathElement.addAttribute("startOffset",
814 paint.getTextAlign() == SkPaint::kCenter_Align ? "50%" : "100%");
815 }
816
fmalita89747742015-02-19 18:44:51 -0800817 SVGTextBuilder builder(text, len, paint, SkPoint::Make(0, 0), 0);
818 textPathElement.addText(builder.text());
fmalitaa9d9de42015-02-04 17:54:46 -0800819 }
820 }
fmalita93957f42015-01-30 09:03:29 -0800821}
822
Mike Reed2f6b5a42017-03-19 15:04:17 -0400823void SkSVGDevice::drawVertices(const SkVertices*, SkBlendMode, const SkPaint&) {
fmalita93957f42015-01-30 09:03:29 -0800824 // todo
fmalitafe3f2602015-02-03 17:47:12 -0800825 SkDebugf("unsupported operation: drawVertices()\n");
fmalita93957f42015-01-30 09:03:29 -0800826}
827
Mike Reeda1361362017-03-07 09:37:29 -0500828void SkSVGDevice::drawDevice(SkBaseDevice*, int x, int y,
fmalita93957f42015-01-30 09:03:29 -0800829 const SkPaint&) {
830 // todo
fmalitafe3f2602015-02-03 17:47:12 -0800831 SkDebugf("unsupported operation: drawDevice()\n");
fmalita93957f42015-01-30 09:03:29 -0800832}