blob: 864f87e2b80ea6753ae647534fb12ec6a63f4d3a [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
Bryce Thomasfd5a5082018-02-06 14:53:05 -080010#include "SkAnnotationKeys.h"
fmalita827da232015-02-27 07:44:47 -080011#include "SkBase64.h"
fmalita93957f42015-01-30 09:03:29 -080012#include "SkBitmap.h"
Anas AlJabri84dd1832018-07-31 15:53:05 -070013#include "SkBlendMode.h"
fmalitaf89f60f2015-02-13 08:55:24 -080014#include "SkChecksum.h"
Bryce Thomasfd5a5082018-02-06 14:53:05 -080015#include "SkClipOpPriv.h"
bungemand3ebb482015-08-05 13:57:49 -070016#include "SkClipStack.h"
Anas AlJabri84dd1832018-07-31 15:53:05 -070017#include "SkColorFilter.h"
fmalita827da232015-02-27 07:44:47 -080018#include "SkData.h"
fmalita93957f42015-01-30 09:03:29 -080019#include "SkDraw.h"
Alexander Midlash77e3afc2018-03-06 17:21:28 -080020#include "SkImage.h"
fmalita827da232015-02-27 07:44:47 -080021#include "SkImageEncoder.h"
Alexander Midlash77e3afc2018-03-06 17:21:28 -080022#include "SkJpegCodec.h"
fmalita93957f42015-01-30 09:03:29 -080023#include "SkPaint.h"
Herb Derbyeb3f6742018-03-05 14:36:45 -050024#include "SkPaintPriv.h"
fmalita93957f42015-01-30 09:03:29 -080025#include "SkParsePath.h"
Alexander Midlash77e3afc2018-03-06 17:21:28 -080026#include "SkPngCodec.h"
fmalita532faa92015-02-03 05:44:40 -080027#include "SkShader.h"
fmalita93957f42015-01-30 09:03:29 -080028#include "SkStream.h"
fmalitaf89f60f2015-02-13 08:55:24 -080029#include "SkTHash.h"
Hal Canaryc640d0d2018-06-13 09:59:02 -040030#include "SkTo.h"
fmalitafe3f2602015-02-03 17:47:12 -080031#include "SkTypeface.h"
32#include "SkUtils.h"
fmalita93957f42015-01-30 09:03:29 -080033#include "SkXMLWriter.h"
34
35namespace {
36
fmalita532faa92015-02-03 05:44:40 -080037static SkString svg_color(SkColor color) {
fmalita1a481fe2015-02-04 07:39:34 -080038 return SkStringPrintf("rgb(%u,%u,%u)",
39 SkColorGetR(color),
40 SkColorGetG(color),
41 SkColorGetB(color));
fmalita532faa92015-02-03 05:44:40 -080042}
43
44static SkScalar svg_opacity(SkColor color) {
45 return SkIntToScalar(SkColorGetA(color)) / SK_AlphaOPAQUE;
46}
47
fmalita12753cc2015-02-04 14:56:35 -080048// Keep in sync with SkPaint::Cap
49static const char* cap_map[] = {
halcanary96fcdcc2015-08-27 07:41:13 -070050 nullptr, // kButt_Cap (default)
fmalita12753cc2015-02-04 14:56:35 -080051 "round", // kRound_Cap
52 "square" // kSquare_Cap
53};
bungeman99fe8222015-08-20 07:57:51 -070054static_assert(SK_ARRAY_COUNT(cap_map) == SkPaint::kCapCount, "missing_cap_map_entry");
fmalita12753cc2015-02-04 14:56:35 -080055
56static const char* svg_cap(SkPaint::Cap cap) {
57 SkASSERT(cap < SK_ARRAY_COUNT(cap_map));
58 return cap_map[cap];
59}
60
61// Keep in sync with SkPaint::Join
62static const char* join_map[] = {
halcanary96fcdcc2015-08-27 07:41:13 -070063 nullptr, // kMiter_Join (default)
fmalita12753cc2015-02-04 14:56:35 -080064 "round", // kRound_Join
65 "bevel" // kBevel_Join
66};
bungeman99fe8222015-08-20 07:57:51 -070067static_assert(SK_ARRAY_COUNT(join_map) == SkPaint::kJoinCount, "missing_join_map_entry");
fmalita12753cc2015-02-04 14:56:35 -080068
69static const char* svg_join(SkPaint::Join join) {
70 SkASSERT(join < SK_ARRAY_COUNT(join_map));
71 return join_map[join];
72}
73
fmalitaa9d9de42015-02-04 17:54:46 -080074static SkString svg_transform(const SkMatrix& t) {
75 SkASSERT(!t.isIdentity());
76
77 SkString tstr;
78 switch (t.getType()) {
79 case SkMatrix::kPerspective_Mask:
Mike Klein6b2b6502018-05-09 10:48:52 -040080 // TODO: handle perspective matrices?
fmalitaa9d9de42015-02-04 17:54:46 -080081 break;
82 case SkMatrix::kTranslate_Mask:
83 tstr.printf("translate(%g %g)", t.getTranslateX(), t.getTranslateY());
84 break;
85 case SkMatrix::kScale_Mask:
86 tstr.printf("scale(%g %g)", t.getScaleX(), t.getScaleY());
87 break;
88 default:
89 // http://www.w3.org/TR/SVG/coords.html#TransformMatrixDefined
90 // | a c e |
91 // | b d f |
92 // | 0 0 1 |
93 tstr.printf("matrix(%g %g %g %g %g %g)",
94 t.getScaleX(), t.getSkewY(),
95 t.getSkewX(), t.getScaleY(),
96 t.getTranslateX(), t.getTranslateY());
97 break;
98 }
99
100 return tstr;
101}
102
fmalita532faa92015-02-03 05:44:40 -0800103struct Resources {
104 Resources(const SkPaint& paint)
105 : fPaintServer(svg_color(paint.getColor())) {}
106
107 SkString fPaintServer;
fmalita1a481fe2015-02-04 07:39:34 -0800108 SkString fClip;
Anas AlJabri84dd1832018-07-31 15:53:05 -0700109 SkString fColorFilter;
fmalita532faa92015-02-03 05:44:40 -0800110};
111
Alexander Midlash77e3afc2018-03-06 17:21:28 -0800112// Determine if the paint requires us to reset the viewport.
113// Currently, we do this whenever the paint shader calls
114// for a repeating image.
115bool RequiresViewportReset(const SkPaint& paint) {
116 SkShader* shader = paint.getShader();
117 if (!shader)
118 return false;
119
120 SkShader::TileMode xy[2];
121 SkImage* image = shader->isAImage(nullptr, xy);
122
123 if (!image)
124 return false;
125
126 for (int i = 0; i < 2; i++) {
127 if (xy[i] == SkShader::kRepeat_TileMode)
128 return true;
129 }
130 return false;
fmalita532faa92015-02-03 05:44:40 -0800131}
132
Alexander Midlash77e3afc2018-03-06 17:21:28 -0800133} // namespace
134
fmalita532faa92015-02-03 05:44:40 -0800135// For now all this does is serve unique serial IDs, but it will eventually evolve to track
136// and deduplicate resources.
137class SkSVGDevice::ResourceBucket : ::SkNoncopyable {
138public:
Alexander Midlash77e3afc2018-03-06 17:21:28 -0800139 ResourceBucket()
Anas AlJabri84dd1832018-07-31 15:53:05 -0700140 : fGradientCount(0)
141 , fClipCount(0)
142 , fPathCount(0)
143 , fImageCount(0)
144 , fPatternCount(0)
145 , fColorFilterCount(0) {}
fmalita532faa92015-02-03 05:44:40 -0800146
147 SkString addLinearGradient() {
fmalita1a481fe2015-02-04 07:39:34 -0800148 return SkStringPrintf("gradient_%d", fGradientCount++);
149 }
150
151 SkString addClip() {
152 return SkStringPrintf("clip_%d", fClipCount++);
fmalita532faa92015-02-03 05:44:40 -0800153 }
154
fmalitaa9d9de42015-02-04 17:54:46 -0800155 SkString addPath() {
156 return SkStringPrintf("path_%d", fPathCount++);
157 }
158
fmalita827da232015-02-27 07:44:47 -0800159 SkString addImage() {
160 return SkStringPrintf("img_%d", fImageCount++);
161 }
162
Anas AlJabri84dd1832018-07-31 15:53:05 -0700163 SkString addColorFilter() { return SkStringPrintf("cfilter_%d", fColorFilterCount++); }
164
Alexander Midlash77e3afc2018-03-06 17:21:28 -0800165 SkString addPattern() {
166 return SkStringPrintf("pattern_%d", fPatternCount++);
167 }
168
fmalita532faa92015-02-03 05:44:40 -0800169private:
170 uint32_t fGradientCount;
fmalita1a481fe2015-02-04 07:39:34 -0800171 uint32_t fClipCount;
fmalitaa9d9de42015-02-04 17:54:46 -0800172 uint32_t fPathCount;
fmalita827da232015-02-27 07:44:47 -0800173 uint32_t fImageCount;
Alexander Midlash77e3afc2018-03-06 17:21:28 -0800174 uint32_t fPatternCount;
Anas AlJabri84dd1832018-07-31 15:53:05 -0700175 uint32_t fColorFilterCount;
fmalita532faa92015-02-03 05:44:40 -0800176};
177
Mike Reedc5e641c2017-02-17 14:38:11 -0500178struct SkSVGDevice::MxCp {
179 const SkMatrix* fMatrix;
180 const SkClipStack* fClipStack;
181
Mike Reedf880b682017-03-10 11:30:44 -0500182 MxCp(const SkMatrix* mx, const SkClipStack* cs) : fMatrix(mx), fClipStack(cs) {}
183 MxCp(SkSVGDevice* device) : fMatrix(&device->ctm()), fClipStack(&device->cs()) {}
Mike Reedc5e641c2017-02-17 14:38:11 -0500184};
185
fmalita532faa92015-02-03 05:44:40 -0800186class SkSVGDevice::AutoElement : ::SkNoncopyable {
fmalita93957f42015-01-30 09:03:29 -0800187public:
188 AutoElement(const char name[], SkXMLWriter* writer)
fmalita532faa92015-02-03 05:44:40 -0800189 : fWriter(writer)
halcanary96fcdcc2015-08-27 07:41:13 -0700190 , fResourceBucket(nullptr) {
fmalita93957f42015-01-30 09:03:29 -0800191 fWriter->startElement(name);
192 }
193
fmalita532faa92015-02-03 05:44:40 -0800194 AutoElement(const char name[], SkXMLWriter* writer, ResourceBucket* bucket,
Mike Reedc5e641c2017-02-17 14:38:11 -0500195 const MxCp& mc, const SkPaint& paint)
fmalita532faa92015-02-03 05:44:40 -0800196 : fWriter(writer)
197 , fResourceBucket(bucket) {
198
Mike Reedc5e641c2017-02-17 14:38:11 -0500199 Resources res = this->addResources(mc, paint);
Alexander Midlash77e3afc2018-03-06 17:21:28 -0800200
fmalita827da232015-02-27 07:44:47 -0800201 if (!res.fClip.isEmpty()) {
202 // The clip is in device space. Apply it via a <g> wrapper to avoid local transform
203 // interference.
halcanary385fe4d2015-08-26 13:07:48 -0700204 fClipGroup.reset(new AutoElement("g", fWriter));
fmalita827da232015-02-27 07:44:47 -0800205 fClipGroup->addAttribute("clip-path",res.fClip);
206 }
fmalita532faa92015-02-03 05:44:40 -0800207
208 fWriter->startElement(name);
209
210 this->addPaint(paint, res);
fmalitaa9d9de42015-02-04 17:54:46 -0800211
Mike Reedc5e641c2017-02-17 14:38:11 -0500212 if (!mc.fMatrix->isIdentity()) {
213 this->addAttribute("transform", svg_transform(*mc.fMatrix));
fmalitaa9d9de42015-02-04 17:54:46 -0800214 }
fmalita532faa92015-02-03 05:44:40 -0800215 }
216
fmalita93957f42015-01-30 09:03:29 -0800217 ~AutoElement() {
218 fWriter->endElement();
219 }
220
fmalita532faa92015-02-03 05:44:40 -0800221 void addAttribute(const char name[], const char val[]) {
222 fWriter->addAttribute(name, val);
223 }
224
225 void addAttribute(const char name[], const SkString& val) {
226 fWriter->addAttribute(name, val.c_str());
227 }
228
229 void addAttribute(const char name[], int32_t val) {
230 fWriter->addS32Attribute(name, val);
231 }
232
233 void addAttribute(const char name[], SkScalar val) {
234 fWriter->addScalarAttribute(name, val);
235 }
236
fmalitafe3f2602015-02-03 17:47:12 -0800237 void addText(const SkString& text) {
reede73da402015-02-04 18:29:27 -0800238 fWriter->addText(text.c_str(), text.size());
fmalitafe3f2602015-02-03 17:47:12 -0800239 }
240
fmalita1a481fe2015-02-04 07:39:34 -0800241 void addRectAttributes(const SkRect&);
fmalitaa9d9de42015-02-04 17:54:46 -0800242 void addPathAttributes(const SkPath&);
243 void addTextAttributes(const SkPaint&);
fmalitafe3f2602015-02-03 17:47:12 -0800244
fmalita93957f42015-01-30 09:03:29 -0800245private:
Mike Reedc5e641c2017-02-17 14:38:11 -0500246 Resources addResources(const MxCp&, const SkPaint& paint);
247 void addClipResources(const MxCp&, Resources* resources);
fmalita1a481fe2015-02-04 07:39:34 -0800248 void addShaderResources(const SkPaint& paint, Resources* resources);
Alexander Midlash77e3afc2018-03-06 17:21:28 -0800249 void addGradientShaderResources(const SkShader* shader, const SkPaint& paint,
250 Resources* resources);
Anas AlJabri84dd1832018-07-31 15:53:05 -0700251 void addColorFilterResources(const SkColorFilter& cf, Resources* resources);
Alexander Midlash77e3afc2018-03-06 17:21:28 -0800252 void addImageShaderResources(const SkShader* shader, const SkPaint& paint,
253 Resources* resources);
254
255 void addPatternDef(const SkBitmap& bm);
fmalita532faa92015-02-03 05:44:40 -0800256
257 void addPaint(const SkPaint& paint, const Resources& resources);
fmalita532faa92015-02-03 05:44:40 -0800258
Alexander Midlash77e3afc2018-03-06 17:21:28 -0800259
fmalita532faa92015-02-03 05:44:40 -0800260 SkString addLinearGradientDef(const SkShader::GradientInfo& info, const SkShader* shader);
261
fmalita827da232015-02-27 07:44:47 -0800262 SkXMLWriter* fWriter;
263 ResourceBucket* fResourceBucket;
Ben Wagner145dbcd2016-11-03 14:40:50 -0400264 std::unique_ptr<AutoElement> fClipGroup;
fmalita93957f42015-01-30 09:03:29 -0800265};
266
fmalita532faa92015-02-03 05:44:40 -0800267void SkSVGDevice::AutoElement::addPaint(const SkPaint& paint, const Resources& resources) {
268 SkPaint::Style style = paint.getStyle();
269 if (style == SkPaint::kFill_Style || style == SkPaint::kStrokeAndFill_Style) {
270 this->addAttribute("fill", resources.fPaintServer);
fmalita12753cc2015-02-04 14:56:35 -0800271
272 if (SK_AlphaOPAQUE != SkColorGetA(paint.getColor())) {
273 this->addAttribute("fill-opacity", svg_opacity(paint.getColor()));
274 }
fmalita532faa92015-02-03 05:44:40 -0800275 } else {
fmalita12753cc2015-02-04 14:56:35 -0800276 SkASSERT(style == SkPaint::kStroke_Style);
fmalita532faa92015-02-03 05:44:40 -0800277 this->addAttribute("fill", "none");
278 }
279
Anas AlJabri84dd1832018-07-31 15:53:05 -0700280 if (!resources.fColorFilter.isEmpty()) {
281 this->addAttribute("filter", resources.fColorFilter.c_str());
282 }
283
fmalita532faa92015-02-03 05:44:40 -0800284 if (style == SkPaint::kStroke_Style || style == SkPaint::kStrokeAndFill_Style) {
285 this->addAttribute("stroke", resources.fPaintServer);
fmalita532faa92015-02-03 05:44:40 -0800286
fmalita12753cc2015-02-04 14:56:35 -0800287 SkScalar strokeWidth = paint.getStrokeWidth();
288 if (strokeWidth == 0) {
289 // Hairline stroke
290 strokeWidth = 1;
291 this->addAttribute("vector-effect", "non-scaling-stroke");
292 }
293 this->addAttribute("stroke-width", strokeWidth);
294
295 if (const char* cap = svg_cap(paint.getStrokeCap())) {
296 this->addAttribute("stroke-linecap", cap);
297 }
298
299 if (const char* join = svg_join(paint.getStrokeJoin())) {
300 this->addAttribute("stroke-linejoin", join);
301 }
302
303 if (paint.getStrokeJoin() == SkPaint::kMiter_Join) {
304 this->addAttribute("stroke-miterlimit", paint.getStrokeMiter());
305 }
306
307 if (SK_AlphaOPAQUE != SkColorGetA(paint.getColor())) {
308 this->addAttribute("stroke-opacity", svg_opacity(paint.getColor()));
309 }
310 } else {
311 SkASSERT(style == SkPaint::kFill_Style);
312 this->addAttribute("stroke", "none");
fmalita532faa92015-02-03 05:44:40 -0800313 }
314}
315
Mike Reedc5e641c2017-02-17 14:38:11 -0500316Resources SkSVGDevice::AutoElement::addResources(const MxCp& mc, const SkPaint& paint) {
fmalita532faa92015-02-03 05:44:40 -0800317 Resources resources(paint);
fmalita1a481fe2015-02-04 07:39:34 -0800318
319 // FIXME: this is a weak heuristic and we end up with LOTS of redundant clips.
Mike Reedc5e641c2017-02-17 14:38:11 -0500320 bool hasClip = !mc.fClipStack->isWideOpen();
fmalita1a481fe2015-02-04 07:39:34 -0800321 bool hasShader = SkToBool(paint.getShader());
322
323 if (hasClip || hasShader) {
324 AutoElement defs("defs", fWriter);
325
326 if (hasClip) {
Mike Reedc5e641c2017-02-17 14:38:11 -0500327 this->addClipResources(mc, &resources);
fmalita1a481fe2015-02-04 07:39:34 -0800328 }
329
330 if (hasShader) {
331 this->addShaderResources(paint, &resources);
332 }
333 }
334
Anas AlJabri84dd1832018-07-31 15:53:05 -0700335 if (const SkColorFilter* cf = paint.getColorFilter()) {
336 // TODO: Implement skia color filters for blend modes other than SrcIn
337 SkBlendMode mode;
338 if (cf->asColorMode(nullptr, &mode) && mode == SkBlendMode::kSrcIn) {
339 this->addColorFilterResources(*cf, &resources);
340 }
341 }
fmalita532faa92015-02-03 05:44:40 -0800342 return resources;
343}
344
Alexander Midlash77e3afc2018-03-06 17:21:28 -0800345void SkSVGDevice::AutoElement::addGradientShaderResources(const SkShader* shader,
346 const SkPaint& paint,
347 Resources* resources) {
fmalita532faa92015-02-03 05:44:40 -0800348 SkShader::GradientInfo grInfo;
349 grInfo.fColorCount = 0;
350 if (SkShader::kLinear_GradientType != shader->asAGradient(&grInfo)) {
351 // TODO: non-linear gradient support
fmalita532faa92015-02-03 05:44:40 -0800352 return;
353 }
354
fmalita1a481fe2015-02-04 07:39:34 -0800355 SkAutoSTArray<16, SkColor> grColors(grInfo.fColorCount);
356 SkAutoSTArray<16, SkScalar> grOffsets(grInfo.fColorCount);
357 grInfo.fColors = grColors.get();
358 grInfo.fColorOffsets = grOffsets.get();
359
360 // One more call to get the actual colors/offsets.
361 shader->asAGradient(&grInfo);
362 SkASSERT(grInfo.fColorCount <= grColors.count());
363 SkASSERT(grInfo.fColorCount <= grOffsets.count());
364
365 resources->fPaintServer.printf("url(#%s)", addLinearGradientDef(grInfo, shader).c_str());
366}
367
Anas AlJabri84dd1832018-07-31 15:53:05 -0700368void SkSVGDevice::AutoElement::addColorFilterResources(const SkColorFilter& cf,
369 Resources* resources) {
370 SkString colorfilterID = fResourceBucket->addColorFilter();
371 {
372 AutoElement filterElement("filter", fWriter);
373 filterElement.addAttribute("id", colorfilterID);
374 filterElement.addAttribute("x", "0%");
375 filterElement.addAttribute("y", "0%");
376 filterElement.addAttribute("width", "100%");
377 filterElement.addAttribute("height", "100%");
378
379 SkColor filterColor;
380 SkBlendMode mode;
381 bool asColorMode = cf.asColorMode(&filterColor, &mode);
382 SkAssertResult(asColorMode);
383 SkASSERT(mode == SkBlendMode::kSrcIn);
384
385 {
386 // first flood with filter color
387 AutoElement floodElement("feFlood", fWriter);
388 floodElement.addAttribute("flood-color", svg_color(filterColor));
389 floodElement.addAttribute("flood-opacity", svg_opacity(filterColor));
390 floodElement.addAttribute("result", "flood");
391 }
392
393 {
394 // apply the transform to filter color
395 AutoElement compositeElement("feComposite", fWriter);
396 compositeElement.addAttribute("in", "flood");
397 compositeElement.addAttribute("operator", "in");
398 }
399 }
400 resources->fColorFilter.printf("url(#%s)", colorfilterID.c_str());
401}
402
Alexander Midlash77e3afc2018-03-06 17:21:28 -0800403// Returns data uri from bytes.
404// it will use any cached data if available, otherwise will
405// encode as png.
406sk_sp<SkData> AsDataUri(SkImage* image) {
407 sk_sp<SkData> imageData = image->encodeToData();
408 if (!imageData) {
Alexander Midlash77e3afc2018-03-06 17:21:28 -0800409 return nullptr;
410 }
411
412 const char* src = (char*)imageData->data();
413 const char* selectedPrefix = nullptr;
414 size_t selectedPrefixLength = 0;
415
416 const static char pngDataPrefix[] = "data:image/png;base64,";
417 const static char jpgDataPrefix[] = "data:image/jpeg;base64,";
418
419 if (SkJpegCodec::IsJpeg(src, imageData->size())) {
420 selectedPrefix = jpgDataPrefix;
421 selectedPrefixLength = sizeof(jpgDataPrefix);
422 } else {
423 if (!SkPngCodec::IsPng(src, imageData->size())) {
Alexander Midlash77e3afc2018-03-06 17:21:28 -0800424 imageData = image->encodeToData(SkEncodedImageFormat::kPNG, 100);
425 }
426 selectedPrefix = pngDataPrefix;
427 selectedPrefixLength = sizeof(pngDataPrefix);
428 }
429
430 size_t b64Size = SkBase64::Encode(imageData->data(), imageData->size(), nullptr);
431 sk_sp<SkData> dataUri = SkData::MakeUninitialized(selectedPrefixLength + b64Size);
432 char* dest = (char*)dataUri->writable_data();
433 memcpy(dest, selectedPrefix, selectedPrefixLength);
434 SkBase64::Encode(imageData->data(), imageData->size(), dest + selectedPrefixLength - 1);
435 dest[dataUri->size() - 1] = 0;
436 return dataUri;
437}
438
439void SkSVGDevice::AutoElement::addImageShaderResources(const SkShader* shader, const SkPaint& paint,
440 Resources* resources) {
441 SkMatrix outMatrix;
442
443 SkShader::TileMode xy[2];
444 SkImage* image = shader->isAImage(&outMatrix, xy);
445 SkASSERT(image);
446
447 SkString patternDims[2]; // width, height
448
449 sk_sp<SkData> dataUri = AsDataUri(image);
450 if (!dataUri) {
Alexander Midlash77e3afc2018-03-06 17:21:28 -0800451 return;
452 }
453 SkIRect imageSize = image->bounds();
454 for (int i = 0; i < 2; i++) {
455 int imageDimension = i == 0 ? imageSize.width() : imageSize.height();
456 switch (xy[i]) {
457 case SkShader::kRepeat_TileMode:
458 patternDims[i].appendScalar(imageDimension);
459 break;
460 default:
Mike Klein6b2b6502018-05-09 10:48:52 -0400461 // TODO: other tile modes?
Alexander Midlash77e3afc2018-03-06 17:21:28 -0800462 patternDims[i] = "100%";
Alexander Midlash77e3afc2018-03-06 17:21:28 -0800463 }
464 }
465
466 SkString patternID = fResourceBucket->addPattern();
467 {
468 AutoElement pattern("pattern", fWriter);
469 pattern.addAttribute("id", patternID);
470 pattern.addAttribute("patternUnits", "userSpaceOnUse");
471 pattern.addAttribute("patternContentUnits", "userSpaceOnUse");
472 pattern.addAttribute("width", patternDims[0]);
473 pattern.addAttribute("height", patternDims[1]);
474 pattern.addAttribute("x", 0);
475 pattern.addAttribute("y", 0);
476
477 {
478 SkString imageID = fResourceBucket->addImage();
479 AutoElement imageTag("image", fWriter);
480 imageTag.addAttribute("id", imageID);
481 imageTag.addAttribute("x", 0);
482 imageTag.addAttribute("y", 0);
483 imageTag.addAttribute("width", image->width());
484 imageTag.addAttribute("height", image->height());
485 imageTag.addAttribute("xlink:href", static_cast<const char*>(dataUri->data()));
486 }
487 }
488 resources->fPaintServer.printf("url(#%s)", patternID.c_str());
489}
490
491void SkSVGDevice::AutoElement::addShaderResources(const SkPaint& paint, Resources* resources) {
492 const SkShader* shader = paint.getShader();
493 SkASSERT(shader);
494
495 if (shader->asAGradient(nullptr) != SkShader::kNone_GradientType) {
Mike Klein6b2b6502018-05-09 10:48:52 -0400496 this->addGradientShaderResources(shader, paint, resources);
Alexander Midlash77e3afc2018-03-06 17:21:28 -0800497 } else if (shader->isAImage()) {
Mike Klein6b2b6502018-05-09 10:48:52 -0400498 this->addImageShaderResources(shader, paint, resources);
Alexander Midlash77e3afc2018-03-06 17:21:28 -0800499 }
Mike Klein6b2b6502018-05-09 10:48:52 -0400500 // TODO: other shader types?
Alexander Midlash77e3afc2018-03-06 17:21:28 -0800501}
502
Mike Reedc5e641c2017-02-17 14:38:11 -0500503void SkSVGDevice::AutoElement::addClipResources(const MxCp& mc, Resources* resources) {
504 SkASSERT(!mc.fClipStack->isWideOpen());
fmalita1a481fe2015-02-04 07:39:34 -0800505
506 SkPath clipPath;
Mike Reedc5e641c2017-02-17 14:38:11 -0500507 (void) mc.fClipStack->asPath(&clipPath);
fmalita1a481fe2015-02-04 07:39:34 -0800508
509 SkString clipID = fResourceBucket->addClip();
510 const char* clipRule = clipPath.getFillType() == SkPath::kEvenOdd_FillType ?
511 "evenodd" : "nonzero";
fmalita532faa92015-02-03 05:44:40 -0800512 {
fmalita1a481fe2015-02-04 07:39:34 -0800513 // clipPath is in device space, but since we're only pushing transform attributes
514 // to the leaf nodes, so are all our elements => SVG userSpaceOnUse == device space.
515 AutoElement clipPathElement("clipPath", fWriter);
516 clipPathElement.addAttribute("id", clipID);
fmalita532faa92015-02-03 05:44:40 -0800517
fmalita1a481fe2015-02-04 07:39:34 -0800518 SkRect clipRect = SkRect::MakeEmpty();
519 if (clipPath.isEmpty() || clipPath.isRect(&clipRect)) {
520 AutoElement rectElement("rect", fWriter);
521 rectElement.addRectAttributes(clipRect);
522 rectElement.addAttribute("clip-rule", clipRule);
523 } else {
524 AutoElement pathElement("path", fWriter);
fmalitaa9d9de42015-02-04 17:54:46 -0800525 pathElement.addPathAttributes(clipPath);
fmalita1a481fe2015-02-04 07:39:34 -0800526 pathElement.addAttribute("clip-rule", clipRule);
527 }
fmalita532faa92015-02-03 05:44:40 -0800528 }
fmalita1a481fe2015-02-04 07:39:34 -0800529
530 resources->fClip.printf("url(#%s)", clipID.c_str());
fmalita532faa92015-02-03 05:44:40 -0800531}
532
533SkString SkSVGDevice::AutoElement::addLinearGradientDef(const SkShader::GradientInfo& info,
534 const SkShader* shader) {
535 SkASSERT(fResourceBucket);
536 SkString id = fResourceBucket->addLinearGradient();
537
538 {
539 AutoElement gradient("linearGradient", fWriter);
540
541 gradient.addAttribute("id", id);
542 gradient.addAttribute("gradientUnits", "userSpaceOnUse");
543 gradient.addAttribute("x1", info.fPoint[0].x());
544 gradient.addAttribute("y1", info.fPoint[0].y());
545 gradient.addAttribute("x2", info.fPoint[1].x());
546 gradient.addAttribute("y2", info.fPoint[1].y());
fmalitaa9d9de42015-02-04 17:54:46 -0800547
548 if (!shader->getLocalMatrix().isIdentity()) {
549 this->addAttribute("gradientTransform", svg_transform(shader->getLocalMatrix()));
550 }
fmalita532faa92015-02-03 05:44:40 -0800551
552 SkASSERT(info.fColorCount >= 2);
553 for (int i = 0; i < info.fColorCount; ++i) {
554 SkColor color = info.fColors[i];
555 SkString colorStr(svg_color(color));
556
557 {
558 AutoElement stop("stop", fWriter);
559 stop.addAttribute("offset", info.fColorOffsets[i]);
560 stop.addAttribute("stop-color", colorStr.c_str());
561
562 if (SK_AlphaOPAQUE != SkColorGetA(color)) {
563 stop.addAttribute("stop-opacity", svg_opacity(color));
564 }
565 }
566 }
567 }
568
569 return id;
fmalita93957f42015-01-30 09:03:29 -0800570}
571
fmalita1a481fe2015-02-04 07:39:34 -0800572void SkSVGDevice::AutoElement::addRectAttributes(const SkRect& rect) {
573 // x, y default to 0
574 if (rect.x() != 0) {
575 this->addAttribute("x", rect.x());
576 }
577 if (rect.y() != 0) {
578 this->addAttribute("y", rect.y());
579 }
580
581 this->addAttribute("width", rect.width());
582 this->addAttribute("height", rect.height());
583}
584
fmalitaa9d9de42015-02-04 17:54:46 -0800585void SkSVGDevice::AutoElement::addPathAttributes(const SkPath& path) {
586 SkString pathData;
587 SkParsePath::ToSVGString(path, &pathData);
588 this->addAttribute("d", pathData);
589}
590
591void SkSVGDevice::AutoElement::addTextAttributes(const SkPaint& paint) {
fmalitafe3f2602015-02-03 17:47:12 -0800592 this->addAttribute("font-size", paint.getTextSize());
593
fmalitaf89f60f2015-02-13 08:55:24 -0800594 SkString familyName;
mtklein02f46cf2015-03-20 13:48:42 -0700595 SkTHashSet<SkString> familySet;
Herb Derbyeb3f6742018-03-05 14:36:45 -0500596 sk_sp<SkTypeface> tface = SkPaintPriv::RefTypefaceOrDefault(paint);
fmalita7a048692015-02-20 13:54:40 -0800597
598 SkASSERT(tface);
Ben Wagner71319502017-07-27 10:45:29 -0400599 SkFontStyle style = tface->fontStyle();
600 if (style.slant() == SkFontStyle::kItalic_Slant) {
fmalita7a048692015-02-20 13:54:40 -0800601 this->addAttribute("font-style", "italic");
Ben Wagner71319502017-07-27 10:45:29 -0400602 } else if (style.slant() == SkFontStyle::kOblique_Slant) {
603 this->addAttribute("font-style", "oblique");
fmalita7a048692015-02-20 13:54:40 -0800604 }
Ben Wagner71319502017-07-27 10:45:29 -0400605 int weightIndex = (SkTPin(style.weight(), 100, 900) - 50) / 100;
606 if (weightIndex != 3) {
607 static constexpr const char* weights[] = {
608 "100", "200", "300", "normal", "400", "500", "600", "bold", "800", "900"
609 };
610 this->addAttribute("font-weight", weights[weightIndex]);
611 }
612 int stretchIndex = style.width() - 1;
613 if (stretchIndex != 4) {
614 static constexpr const char* stretches[] = {
615 "ultra-condensed", "extra-condensed", "condensed", "semi-condensed",
616 "normal",
617 "semi-expanded", "expanded", "extra-expanded", "ultra-expanded"
618 };
619 this->addAttribute("font-stretch", stretches[stretchIndex]);
fmalita7a048692015-02-20 13:54:40 -0800620 }
621
Hal Canary67b39de2016-11-07 11:47:44 -0500622 sk_sp<SkTypeface::LocalizedStrings> familyNameIter(tface->createFamilyNameIterator());
fmalitaf89f60f2015-02-13 08:55:24 -0800623 SkTypeface::LocalizedString familyString;
Mike Reedf67c4592017-02-17 17:06:11 -0500624 if (familyNameIter) {
625 while (familyNameIter->next(&familyString)) {
626 if (familySet.contains(familyString.fString)) {
627 continue;
628 }
629 familySet.add(familyString.fString);
630 familyName.appendf((familyName.isEmpty() ? "%s" : ", %s"), familyString.fString.c_str());
fmalitaf89f60f2015-02-13 08:55:24 -0800631 }
fmalitaf89f60f2015-02-13 08:55:24 -0800632 }
fmalitaf89f60f2015-02-13 08:55:24 -0800633 if (!familyName.isEmpty()) {
634 this->addAttribute("font-family", familyName);
635 }
fmalitafe3f2602015-02-03 17:47:12 -0800636}
637
fmalita2aafe6f2015-02-06 12:51:10 -0800638SkBaseDevice* SkSVGDevice::Create(const SkISize& size, SkXMLWriter* writer) {
639 if (!writer) {
halcanary96fcdcc2015-08-27 07:41:13 -0700640 return nullptr;
fmalita93957f42015-01-30 09:03:29 -0800641 }
642
halcanary385fe4d2015-08-26 13:07:48 -0700643 return new SkSVGDevice(size, writer);
fmalita93957f42015-01-30 09:03:29 -0800644}
645
fmalita2aafe6f2015-02-06 12:51:10 -0800646SkSVGDevice::SkSVGDevice(const SkISize& size, SkXMLWriter* writer)
reed589a39e2016-08-20 07:59:19 -0700647 : INHERITED(SkImageInfo::MakeUnknown(size.fWidth, size.fHeight),
648 SkSurfaceProps(0, kUnknown_SkPixelGeometry))
robertphillips9a53fd72015-06-22 09:46:59 -0700649 , fWriter(writer)
robertphillips1f3923e2016-07-21 07:17:54 -0700650 , fResourceBucket(new ResourceBucket)
reed589a39e2016-08-20 07:59:19 -0700651{
fmalita2aafe6f2015-02-06 12:51:10 -0800652 SkASSERT(writer);
fmalita93957f42015-01-30 09:03:29 -0800653
fmalita93957f42015-01-30 09:03:29 -0800654 fWriter->writeHeader();
fmalita532faa92015-02-03 05:44:40 -0800655
656 // The root <svg> tag gets closed by the destructor.
halcanary385fe4d2015-08-26 13:07:48 -0700657 fRootElement.reset(new AutoElement("svg", fWriter));
fmalita532faa92015-02-03 05:44:40 -0800658
659 fRootElement->addAttribute("xmlns", "http://www.w3.org/2000/svg");
660 fRootElement->addAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
661 fRootElement->addAttribute("width", size.width());
662 fRootElement->addAttribute("height", size.height());
fmalita93957f42015-01-30 09:03:29 -0800663}
664
665SkSVGDevice::~SkSVGDevice() {
fmalita93957f42015-01-30 09:03:29 -0800666}
667
Mike Reeda1361362017-03-07 09:37:29 -0500668void SkSVGDevice::drawPaint(const SkPaint& paint) {
669 AutoElement rect("rect", fWriter, fResourceBucket.get(), MxCp(this), paint);
fmalita1a481fe2015-02-04 07:39:34 -0800670 rect.addRectAttributes(SkRect::MakeWH(SkIntToScalar(this->width()),
671 SkIntToScalar(this->height())));
fmalita93957f42015-01-30 09:03:29 -0800672}
673
Bryce Thomasfd5a5082018-02-06 14:53:05 -0800674void SkSVGDevice::drawAnnotation(const SkRect& rect, const char key[], SkData* value) {
675 if (!value) {
676 return;
677 }
678
679 if (!strcmp(SkAnnotationKeys::URL_Key(), key) ||
680 !strcmp(SkAnnotationKeys::Link_Named_Dest_Key(), key)) {
681 this->cs().save();
682 this->cs().clipRect(rect, this->ctm(), kIntersect_SkClipOp, true);
683 SkRect transformedRect = this->cs().bounds(this->getGlobalBounds());
684 this->cs().restore();
685 if (transformedRect.isEmpty()) {
686 return;
687 }
688
689 SkString url(static_cast<const char*>(value->data()), value->size() - 1);
690 AutoElement a("a", fWriter);
691 a.addAttribute("xlink:href", url.c_str());
692 {
693 AutoElement r("rect", fWriter);
694 r.addAttribute("fill-opacity", "0.0");
695 r.addRectAttributes(transformedRect);
696 }
697 }
698}
699
Mike Reeda1361362017-03-07 09:37:29 -0500700void SkSVGDevice::drawPoints(SkCanvas::PointMode mode, size_t count,
reed5f68c342015-05-15 10:11:11 -0700701 const SkPoint pts[], const SkPaint& paint) {
702 SkPath path;
703
704 switch (mode) {
705 // todo
706 case SkCanvas::kPoints_PointMode:
Mike Klein6b2b6502018-05-09 10:48:52 -0400707 // TODO?
reed5f68c342015-05-15 10:11:11 -0700708 break;
709 case SkCanvas::kLines_PointMode:
710 count -= 1;
711 for (size_t i = 0; i < count; i += 2) {
712 path.rewind();
713 path.moveTo(pts[i]);
714 path.lineTo(pts[i+1]);
Mike Reeda1361362017-03-07 09:37:29 -0500715 AutoElement elem("path", fWriter, fResourceBucket.get(), MxCp(this), paint);
reed5f68c342015-05-15 10:11:11 -0700716 elem.addPathAttributes(path);
717 }
718 break;
719 case SkCanvas::kPolygon_PointMode:
720 if (count > 1) {
721 path.addPoly(pts, SkToInt(count), false);
722 path.moveTo(pts[0]);
Mike Reeda1361362017-03-07 09:37:29 -0500723 AutoElement elem("path", fWriter, fResourceBucket.get(), MxCp(this), paint);
reed5f68c342015-05-15 10:11:11 -0700724 elem.addPathAttributes(path);
725 }
726 break;
727 }
fmalita93957f42015-01-30 09:03:29 -0800728}
729
Mike Reeda1361362017-03-07 09:37:29 -0500730void SkSVGDevice::drawRect(const SkRect& r, const SkPaint& paint) {
Alexander Midlash77e3afc2018-03-06 17:21:28 -0800731 std::unique_ptr<AutoElement> svg;
732 if (RequiresViewportReset(paint)) {
733 svg.reset(new AutoElement("svg", fWriter, fResourceBucket.get(), MxCp(this), paint));
734 svg->addRectAttributes(r);
735 }
736
Mike Reeda1361362017-03-07 09:37:29 -0500737 AutoElement rect("rect", fWriter, fResourceBucket.get(), MxCp(this), paint);
Alexander Midlash77e3afc2018-03-06 17:21:28 -0800738
739 if (svg) {
740 rect.addAttribute("x", 0);
741 rect.addAttribute("y", 0);
742 rect.addAttribute("width", "100%");
743 rect.addAttribute("height", "100%");
744 } else {
745 rect.addRectAttributes(r);
746 }
fmalita93957f42015-01-30 09:03:29 -0800747}
748
Mike Reeda1361362017-03-07 09:37:29 -0500749void SkSVGDevice::drawOval(const SkRect& oval, const SkPaint& paint) {
750 AutoElement ellipse("ellipse", fWriter, fResourceBucket.get(), MxCp(this), paint);
fmalita532faa92015-02-03 05:44:40 -0800751 ellipse.addAttribute("cx", oval.centerX());
752 ellipse.addAttribute("cy", oval.centerY());
753 ellipse.addAttribute("rx", oval.width() / 2);
754 ellipse.addAttribute("ry", oval.height() / 2);
fmalita93957f42015-01-30 09:03:29 -0800755}
756
Mike Reeda1361362017-03-07 09:37:29 -0500757void SkSVGDevice::drawRRect(const SkRRect& rr, const SkPaint& paint) {
reed5f68c342015-05-15 10:11:11 -0700758 SkPath path;
759 path.addRRect(rr);
760
Mike Reeda1361362017-03-07 09:37:29 -0500761 AutoElement elem("path", fWriter, fResourceBucket.get(), MxCp(this), paint);
reed5f68c342015-05-15 10:11:11 -0700762 elem.addPathAttributes(path);
fmalita93957f42015-01-30 09:03:29 -0800763}
764
Robert Phillips137ca522018-08-15 10:14:33 -0400765void SkSVGDevice::drawPath(const SkPath& path, const SkPaint& paint, bool pathIsMutable) {
Mike Reeda1361362017-03-07 09:37:29 -0500766 AutoElement elem("path", fWriter, fResourceBucket.get(), MxCp(this), paint);
fmalitaa9d9de42015-02-04 17:54:46 -0800767 elem.addPathAttributes(path);
fmalita221722b2016-09-06 14:37:02 -0700768
769 // TODO: inverse fill types?
770 if (path.getFillType() == SkPath::kEvenOdd_FillType) {
771 elem.addAttribute("fill-rule", "evenodd");
772 }
fmalita93957f42015-01-30 09:03:29 -0800773}
774
Hal Canarydb683012016-11-23 08:55:18 -0700775static sk_sp<SkData> encode(const SkBitmap& src) {
776 SkDynamicMemoryWStream buf;
777 return SkEncodeImage(&buf, src, SkEncodedImageFormat::kPNG, 80) ? buf.detachAsData() : nullptr;
778}
779
Mike Reedc5e641c2017-02-17 14:38:11 -0500780void SkSVGDevice::drawBitmapCommon(const MxCp& mc, const SkBitmap& bm, const SkPaint& paint) {
Hal Canarydb683012016-11-23 08:55:18 -0700781 sk_sp<SkData> pngData = encode(bm);
fmalita827da232015-02-27 07:44:47 -0800782 if (!pngData) {
783 return;
784 }
785
halcanary96fcdcc2015-08-27 07:41:13 -0700786 size_t b64Size = SkBase64::Encode(pngData->data(), pngData->size(), nullptr);
fmalita827da232015-02-27 07:44:47 -0800787 SkAutoTMalloc<char> b64Data(b64Size);
788 SkBase64::Encode(pngData->data(), pngData->size(), b64Data.get());
789
790 SkString svgImageData("data:image/png;base64,");
791 svgImageData.append(b64Data.get(), b64Size);
792
793 SkString imageID = fResourceBucket->addImage();
794 {
795 AutoElement defs("defs", fWriter);
796 {
797 AutoElement image("image", fWriter);
798 image.addAttribute("id", imageID);
799 image.addAttribute("width", bm.width());
800 image.addAttribute("height", bm.height());
801 image.addAttribute("xlink:href", svgImageData);
802 }
803 }
804
805 {
Mike Reedc5e641c2017-02-17 14:38:11 -0500806 AutoElement imageUse("use", fWriter, fResourceBucket.get(), mc, paint);
fmalita827da232015-02-27 07:44:47 -0800807 imageUse.addAttribute("xlink:href", SkStringPrintf("#%s", imageID.c_str()));
808 }
809}
810
Hal Canaryb9642382017-06-27 09:58:56 -0400811void SkSVGDevice::drawBitmap(const SkBitmap& bitmap, SkScalar x, SkScalar y,
812 const SkPaint& paint) {
Mike Reeda1361362017-03-07 09:37:29 -0500813 MxCp mc(this);
Mike Reedc5e641c2017-02-17 14:38:11 -0500814 SkMatrix adjustedMatrix = *mc.fMatrix;
Hal Canaryb9642382017-06-27 09:58:56 -0400815 adjustedMatrix.preTranslate(x, y);
Mike Reedc5e641c2017-02-17 14:38:11 -0500816 mc.fMatrix = &adjustedMatrix;
fmalita827da232015-02-27 07:44:47 -0800817
Mike Reedc5e641c2017-02-17 14:38:11 -0500818 drawBitmapCommon(mc, bitmap, paint);
fmalita93957f42015-01-30 09:03:29 -0800819}
820
Mike Reeda1361362017-03-07 09:37:29 -0500821void SkSVGDevice::drawSprite(const SkBitmap& bitmap,
fmalita93957f42015-01-30 09:03:29 -0800822 int x, int y, const SkPaint& paint) {
Mike Reeda1361362017-03-07 09:37:29 -0500823 MxCp mc(this);
Mike Reedc5e641c2017-02-17 14:38:11 -0500824 SkMatrix adjustedMatrix = *mc.fMatrix;
fmalita827da232015-02-27 07:44:47 -0800825 adjustedMatrix.preTranslate(SkIntToScalar(x), SkIntToScalar(y));
Mike Reedc5e641c2017-02-17 14:38:11 -0500826 mc.fMatrix = &adjustedMatrix;
fmalita827da232015-02-27 07:44:47 -0800827
Mike Reedc5e641c2017-02-17 14:38:11 -0500828 drawBitmapCommon(mc, bitmap, paint);
fmalita93957f42015-01-30 09:03:29 -0800829}
830
Mike Reeda1361362017-03-07 09:37:29 -0500831void SkSVGDevice::drawBitmapRect(const SkBitmap& bm, const SkRect* srcOrNull,
fmalita93957f42015-01-30 09:03:29 -0800832 const SkRect& dst, const SkPaint& paint,
reed562fe472015-07-28 07:35:14 -0700833 SkCanvas::SrcRectConstraint) {
Mike Reedf880b682017-03-10 11:30:44 -0500834 SkClipStack* cs = &this->cs();
835 SkClipStack::AutoRestore ar(cs, false);
Mike Reedc5e641c2017-02-17 14:38:11 -0500836 if (srcOrNull && *srcOrNull != SkRect::Make(bm.bounds())) {
Mike Reedf880b682017-03-10 11:30:44 -0500837 cs->save();
838 cs->clipRect(dst, this->ctm(), kIntersect_SkClipOp, paint.isAntiAlias());
Mike Reedc5e641c2017-02-17 14:38:11 -0500839 }
840
fmalita827da232015-02-27 07:44:47 -0800841 SkMatrix adjustedMatrix;
842 adjustedMatrix.setRectToRect(srcOrNull ? *srcOrNull : SkRect::Make(bm.bounds()),
843 dst,
844 SkMatrix::kFill_ScaleToFit);
Mike Reedf880b682017-03-10 11:30:44 -0500845 adjustedMatrix.postConcat(this->ctm());
fmalita827da232015-02-27 07:44:47 -0800846
Mike Reedf880b682017-03-10 11:30:44 -0500847 drawBitmapCommon(MxCp(&adjustedMatrix, cs), bm, paint);
fmalita93957f42015-01-30 09:03:29 -0800848}
849
Herb Derbyd708bd62018-08-29 15:59:34 -0400850class SVGTextBuilder : SkNoncopyable {
851public:
852 SVGTextBuilder(SkPoint origin, const SkGlyphRun& glyphRun)
853 : fOrigin(origin)
854 , fLastCharWasWhitespace(true) { // start off in whitespace mode to strip all leadingspace
Herb Derbyd708bd62018-08-29 15:59:34 -0400855 auto runSize = glyphRun.runSize();
856 SkAutoSTArray<64, SkUnichar> unichars(runSize);
Herb Derby95e17602018-12-06 17:11:43 -0500857 glyphRun.font().glyphsToUnichars(glyphRun.glyphsIDs().data(), runSize, unichars.get());
Herb Derbyd708bd62018-08-29 15:59:34 -0400858 auto positions = glyphRun.positions();
859 for (size_t i = 0; i < runSize; ++i) {
860 this->appendUnichar(unichars[i], positions[i]);
861 }
862 }
fmalitafe3f2602015-02-03 17:47:12 -0800863
Herb Derbyd708bd62018-08-29 15:59:34 -0400864 const SkString& text() const { return fText; }
865 const SkString& posX() const { return fPosX; }
866 const SkString& posY() const { return fPosY; }
fmalitafe3f2602015-02-03 17:47:12 -0800867
Herb Derbyd708bd62018-08-29 15:59:34 -0400868private:
869 void appendUnichar(SkUnichar c, SkPoint position) {
870 bool discardPos = false;
871 bool isWhitespace = false;
872
873 switch(c) {
874 case ' ':
875 case '\t':
876 // consolidate whitespace to match SVG's xml:space=default munging
877 // (http://www.w3.org/TR/SVG/text.html#WhiteSpace)
878 if (fLastCharWasWhitespace) {
879 discardPos = true;
880 } else {
881 fText.appendUnichar(c);
882 }
883 isWhitespace = true;
884 break;
885 case '\0':
886 // SkPaint::glyphsToUnichars() returns \0 for inconvertible glyphs, but these
887 // are not legal XML characters (http://www.w3.org/TR/REC-xml/#charsets)
888 discardPos = true;
889 isWhitespace = fLastCharWasWhitespace; // preserve whitespace consolidation
890 break;
891 case '&':
892 fText.append("&amp;");
893 break;
894 case '"':
895 fText.append("&quot;");
896 break;
897 case '\'':
898 fText.append("&apos;");
899 break;
900 case '<':
901 fText.append("&lt;");
902 break;
903 case '>':
904 fText.append("&gt;");
905 break;
906 default:
907 fText.appendUnichar(c);
908 break;
909 }
910
911 this->advancePos(discardPos, position);
912 fLastCharWasWhitespace = isWhitespace;
913 }
914
915 void advancePos(bool discard, SkPoint position) {
916 if (!discard) {
917 SkPoint finalPosition = fOrigin + position;
918 fPosX.appendf("%.8g, ", finalPosition.x());
919 fPosY.appendf("%.8g, ", finalPosition.y());
920 }
921 }
922
Robert Phillips94078642018-08-31 07:58:34 -0400923 const SkPoint fOrigin;
Herb Derbyd708bd62018-08-29 15:59:34 -0400924
925 SkString fText, fPosX, fPosY;
926 bool fLastCharWasWhitespace;
927};
928
929void SkSVGDevice::drawGlyphRunList(const SkGlyphRunList& glyphRunList) {
930
Herb Derby95e17602018-12-06 17:11:43 -0500931 auto processGlyphRun = [this]
932 (SkPoint origin, const SkGlyphRun& glyphRun, const SkPaint& runPaint) {
933 SkPaint paint{runPaint};
934 glyphRun.font().LEGACY_applyToPaint(&paint);
Herb Derbyd708bd62018-08-29 15:59:34 -0400935 AutoElement elem("text", fWriter, fResourceBucket.get(), MxCp(this), paint);
936 elem.addTextAttributes(paint);
937
938 SVGTextBuilder builder(origin, glyphRun);
939 elem.addAttribute("x", builder.posX());
940 elem.addAttribute("y", builder.posY());
941 elem.addText(builder.text());
942 };
943
944 for (auto& glyphRun : glyphRunList) {
Herb Derby95e17602018-12-06 17:11:43 -0500945 processGlyphRun(glyphRunList.origin(), glyphRun, glyphRunList.paint());
Herb Derbyd708bd62018-08-29 15:59:34 -0400946 }
fmalita93957f42015-01-30 09:03:29 -0800947}
948
Ruiqi Maoc97a3392018-08-15 10:44:19 -0400949void SkSVGDevice::drawVertices(const SkVertices*, const SkVertices::Bone[], int, SkBlendMode,
Ruiqi Maof5101492018-06-29 14:32:21 -0400950 const SkPaint&) {
fmalita93957f42015-01-30 09:03:29 -0800951 // todo
952}
953
Mike Reeda1361362017-03-07 09:37:29 -0500954void SkSVGDevice::drawDevice(SkBaseDevice*, int x, int y,
fmalita93957f42015-01-30 09:03:29 -0800955 const SkPaint&) {
956 // todo
fmalita93957f42015-01-30 09:03:29 -0800957}