blob: 0635d30c7c81bf22bccf8cc4eba24dbc80f5acc9 [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"
24#include "SkParsePath.h"
Alexander Midlash77e3afc2018-03-06 17:21:28 -080025#include "SkPngCodec.h"
fmalita532faa92015-02-03 05:44:40 -080026#include "SkShader.h"
fmalita93957f42015-01-30 09:03:29 -080027#include "SkStream.h"
fmalitaf89f60f2015-02-13 08:55:24 -080028#include "SkTHash.h"
Hal Canaryc640d0d2018-06-13 09:59:02 -040029#include "SkTo.h"
fmalitafe3f2602015-02-03 17:47:12 -080030#include "SkTypeface.h"
31#include "SkUtils.h"
fmalita93957f42015-01-30 09:03:29 -080032#include "SkXMLWriter.h"
33
34namespace {
35
fmalita532faa92015-02-03 05:44:40 -080036static SkString svg_color(SkColor color) {
fmalita1a481fe2015-02-04 07:39:34 -080037 return SkStringPrintf("rgb(%u,%u,%u)",
38 SkColorGetR(color),
39 SkColorGetG(color),
40 SkColorGetB(color));
fmalita532faa92015-02-03 05:44:40 -080041}
42
43static SkScalar svg_opacity(SkColor color) {
44 return SkIntToScalar(SkColorGetA(color)) / SK_AlphaOPAQUE;
45}
46
fmalita12753cc2015-02-04 14:56:35 -080047// Keep in sync with SkPaint::Cap
48static const char* cap_map[] = {
halcanary96fcdcc2015-08-27 07:41:13 -070049 nullptr, // kButt_Cap (default)
fmalita12753cc2015-02-04 14:56:35 -080050 "round", // kRound_Cap
51 "square" // kSquare_Cap
52};
bungeman99fe8222015-08-20 07:57:51 -070053static_assert(SK_ARRAY_COUNT(cap_map) == SkPaint::kCapCount, "missing_cap_map_entry");
fmalita12753cc2015-02-04 14:56:35 -080054
55static const char* svg_cap(SkPaint::Cap cap) {
56 SkASSERT(cap < SK_ARRAY_COUNT(cap_map));
57 return cap_map[cap];
58}
59
60// Keep in sync with SkPaint::Join
61static const char* join_map[] = {
halcanary96fcdcc2015-08-27 07:41:13 -070062 nullptr, // kMiter_Join (default)
fmalita12753cc2015-02-04 14:56:35 -080063 "round", // kRound_Join
64 "bevel" // kBevel_Join
65};
bungeman99fe8222015-08-20 07:57:51 -070066static_assert(SK_ARRAY_COUNT(join_map) == SkPaint::kJoinCount, "missing_join_map_entry");
fmalita12753cc2015-02-04 14:56:35 -080067
68static const char* svg_join(SkPaint::Join join) {
69 SkASSERT(join < SK_ARRAY_COUNT(join_map));
70 return join_map[join];
71}
72
fmalitaa9d9de42015-02-04 17:54:46 -080073static SkString svg_transform(const SkMatrix& t) {
74 SkASSERT(!t.isIdentity());
75
76 SkString tstr;
77 switch (t.getType()) {
78 case SkMatrix::kPerspective_Mask:
Mike Klein6b2b6502018-05-09 10:48:52 -040079 // TODO: handle perspective matrices?
fmalitaa9d9de42015-02-04 17:54:46 -080080 break;
81 case SkMatrix::kTranslate_Mask:
82 tstr.printf("translate(%g %g)", t.getTranslateX(), t.getTranslateY());
83 break;
84 case SkMatrix::kScale_Mask:
85 tstr.printf("scale(%g %g)", t.getScaleX(), t.getScaleY());
86 break;
87 default:
88 // http://www.w3.org/TR/SVG/coords.html#TransformMatrixDefined
89 // | a c e |
90 // | b d f |
91 // | 0 0 1 |
92 tstr.printf("matrix(%g %g %g %g %g %g)",
93 t.getScaleX(), t.getSkewY(),
94 t.getSkewX(), t.getScaleY(),
95 t.getTranslateX(), t.getTranslateY());
96 break;
97 }
98
99 return tstr;
100}
101
fmalita532faa92015-02-03 05:44:40 -0800102struct Resources {
103 Resources(const SkPaint& paint)
104 : fPaintServer(svg_color(paint.getColor())) {}
105
106 SkString fPaintServer;
fmalita1a481fe2015-02-04 07:39:34 -0800107 SkString fClip;
Anas AlJabri84dd1832018-07-31 15:53:05 -0700108 SkString fColorFilter;
fmalita532faa92015-02-03 05:44:40 -0800109};
110
Alexander Midlash77e3afc2018-03-06 17:21:28 -0800111// Determine if the paint requires us to reset the viewport.
112// Currently, we do this whenever the paint shader calls
113// for a repeating image.
114bool RequiresViewportReset(const SkPaint& paint) {
115 SkShader* shader = paint.getShader();
116 if (!shader)
117 return false;
118
119 SkShader::TileMode xy[2];
120 SkImage* image = shader->isAImage(nullptr, xy);
121
122 if (!image)
123 return false;
124
125 for (int i = 0; i < 2; i++) {
126 if (xy[i] == SkShader::kRepeat_TileMode)
127 return true;
128 }
129 return false;
fmalita532faa92015-02-03 05:44:40 -0800130}
131
Alexander Midlash77e3afc2018-03-06 17:21:28 -0800132} // namespace
133
fmalita532faa92015-02-03 05:44:40 -0800134// For now all this does is serve unique serial IDs, but it will eventually evolve to track
135// and deduplicate resources.
136class SkSVGDevice::ResourceBucket : ::SkNoncopyable {
137public:
Alexander Midlash77e3afc2018-03-06 17:21:28 -0800138 ResourceBucket()
Anas AlJabri84dd1832018-07-31 15:53:05 -0700139 : fGradientCount(0)
140 , fClipCount(0)
141 , fPathCount(0)
142 , fImageCount(0)
143 , fPatternCount(0)
144 , fColorFilterCount(0) {}
fmalita532faa92015-02-03 05:44:40 -0800145
146 SkString addLinearGradient() {
fmalita1a481fe2015-02-04 07:39:34 -0800147 return SkStringPrintf("gradient_%d", fGradientCount++);
148 }
149
150 SkString addClip() {
151 return SkStringPrintf("clip_%d", fClipCount++);
fmalita532faa92015-02-03 05:44:40 -0800152 }
153
fmalitaa9d9de42015-02-04 17:54:46 -0800154 SkString addPath() {
155 return SkStringPrintf("path_%d", fPathCount++);
156 }
157
fmalita827da232015-02-27 07:44:47 -0800158 SkString addImage() {
159 return SkStringPrintf("img_%d", fImageCount++);
160 }
161
Anas AlJabri84dd1832018-07-31 15:53:05 -0700162 SkString addColorFilter() { return SkStringPrintf("cfilter_%d", fColorFilterCount++); }
163
Alexander Midlash77e3afc2018-03-06 17:21:28 -0800164 SkString addPattern() {
165 return SkStringPrintf("pattern_%d", fPatternCount++);
166 }
167
fmalita532faa92015-02-03 05:44:40 -0800168private:
169 uint32_t fGradientCount;
fmalita1a481fe2015-02-04 07:39:34 -0800170 uint32_t fClipCount;
fmalitaa9d9de42015-02-04 17:54:46 -0800171 uint32_t fPathCount;
fmalita827da232015-02-27 07:44:47 -0800172 uint32_t fImageCount;
Alexander Midlash77e3afc2018-03-06 17:21:28 -0800173 uint32_t fPatternCount;
Anas AlJabri84dd1832018-07-31 15:53:05 -0700174 uint32_t fColorFilterCount;
fmalita532faa92015-02-03 05:44:40 -0800175};
176
Mike Reedc5e641c2017-02-17 14:38:11 -0500177struct SkSVGDevice::MxCp {
178 const SkMatrix* fMatrix;
179 const SkClipStack* fClipStack;
180
Mike Reedf880b682017-03-10 11:30:44 -0500181 MxCp(const SkMatrix* mx, const SkClipStack* cs) : fMatrix(mx), fClipStack(cs) {}
182 MxCp(SkSVGDevice* device) : fMatrix(&device->ctm()), fClipStack(&device->cs()) {}
Mike Reedc5e641c2017-02-17 14:38:11 -0500183};
184
fmalita532faa92015-02-03 05:44:40 -0800185class SkSVGDevice::AutoElement : ::SkNoncopyable {
fmalita93957f42015-01-30 09:03:29 -0800186public:
187 AutoElement(const char name[], SkXMLWriter* writer)
fmalita532faa92015-02-03 05:44:40 -0800188 : fWriter(writer)
halcanary96fcdcc2015-08-27 07:41:13 -0700189 , fResourceBucket(nullptr) {
fmalita93957f42015-01-30 09:03:29 -0800190 fWriter->startElement(name);
191 }
192
fmalita532faa92015-02-03 05:44:40 -0800193 AutoElement(const char name[], SkXMLWriter* writer, ResourceBucket* bucket,
Mike Reedc5e641c2017-02-17 14:38:11 -0500194 const MxCp& mc, const SkPaint& paint)
fmalita532faa92015-02-03 05:44:40 -0800195 : fWriter(writer)
196 , fResourceBucket(bucket) {
197
Mike Reedc5e641c2017-02-17 14:38:11 -0500198 Resources res = this->addResources(mc, paint);
Alexander Midlash77e3afc2018-03-06 17:21:28 -0800199
fmalita827da232015-02-27 07:44:47 -0800200 if (!res.fClip.isEmpty()) {
201 // The clip is in device space. Apply it via a <g> wrapper to avoid local transform
202 // interference.
halcanary385fe4d2015-08-26 13:07:48 -0700203 fClipGroup.reset(new AutoElement("g", fWriter));
fmalita827da232015-02-27 07:44:47 -0800204 fClipGroup->addAttribute("clip-path",res.fClip);
205 }
fmalita532faa92015-02-03 05:44:40 -0800206
207 fWriter->startElement(name);
208
209 this->addPaint(paint, res);
fmalitaa9d9de42015-02-04 17:54:46 -0800210
Mike Reedc5e641c2017-02-17 14:38:11 -0500211 if (!mc.fMatrix->isIdentity()) {
212 this->addAttribute("transform", svg_transform(*mc.fMatrix));
fmalitaa9d9de42015-02-04 17:54:46 -0800213 }
fmalita532faa92015-02-03 05:44:40 -0800214 }
215
fmalita93957f42015-01-30 09:03:29 -0800216 ~AutoElement() {
217 fWriter->endElement();
218 }
219
fmalita532faa92015-02-03 05:44:40 -0800220 void addAttribute(const char name[], const char val[]) {
221 fWriter->addAttribute(name, val);
222 }
223
224 void addAttribute(const char name[], const SkString& val) {
225 fWriter->addAttribute(name, val.c_str());
226 }
227
228 void addAttribute(const char name[], int32_t val) {
229 fWriter->addS32Attribute(name, val);
230 }
231
232 void addAttribute(const char name[], SkScalar val) {
233 fWriter->addScalarAttribute(name, val);
234 }
235
fmalitafe3f2602015-02-03 17:47:12 -0800236 void addText(const SkString& text) {
reede73da402015-02-04 18:29:27 -0800237 fWriter->addText(text.c_str(), text.size());
fmalitafe3f2602015-02-03 17:47:12 -0800238 }
239
fmalita1a481fe2015-02-04 07:39:34 -0800240 void addRectAttributes(const SkRect&);
fmalitaa9d9de42015-02-04 17:54:46 -0800241 void addPathAttributes(const SkPath&);
Mike Reed96345a22019-01-02 21:30:29 -0500242 void addTextAttributes(const SkFont&);
fmalitafe3f2602015-02-03 17:47:12 -0800243
fmalita93957f42015-01-30 09:03:29 -0800244private:
Mike Reedc5e641c2017-02-17 14:38:11 -0500245 Resources addResources(const MxCp&, const SkPaint& paint);
246 void addClipResources(const MxCp&, Resources* resources);
fmalita1a481fe2015-02-04 07:39:34 -0800247 void addShaderResources(const SkPaint& paint, Resources* resources);
Alexander Midlash77e3afc2018-03-06 17:21:28 -0800248 void addGradientShaderResources(const SkShader* shader, const SkPaint& paint,
249 Resources* resources);
Anas AlJabri84dd1832018-07-31 15:53:05 -0700250 void addColorFilterResources(const SkColorFilter& cf, Resources* resources);
Alexander Midlash77e3afc2018-03-06 17:21:28 -0800251 void addImageShaderResources(const SkShader* shader, const SkPaint& paint,
252 Resources* resources);
253
254 void addPatternDef(const SkBitmap& bm);
fmalita532faa92015-02-03 05:44:40 -0800255
256 void addPaint(const SkPaint& paint, const Resources& resources);
fmalita532faa92015-02-03 05:44:40 -0800257
Alexander Midlash77e3afc2018-03-06 17:21:28 -0800258
fmalita532faa92015-02-03 05:44:40 -0800259 SkString addLinearGradientDef(const SkShader::GradientInfo& info, const SkShader* shader);
260
fmalita827da232015-02-27 07:44:47 -0800261 SkXMLWriter* fWriter;
262 ResourceBucket* fResourceBucket;
Ben Wagner145dbcd2016-11-03 14:40:50 -0400263 std::unique_ptr<AutoElement> fClipGroup;
fmalita93957f42015-01-30 09:03:29 -0800264};
265
fmalita532faa92015-02-03 05:44:40 -0800266void SkSVGDevice::AutoElement::addPaint(const SkPaint& paint, const Resources& resources) {
267 SkPaint::Style style = paint.getStyle();
268 if (style == SkPaint::kFill_Style || style == SkPaint::kStrokeAndFill_Style) {
269 this->addAttribute("fill", resources.fPaintServer);
fmalita12753cc2015-02-04 14:56:35 -0800270
271 if (SK_AlphaOPAQUE != SkColorGetA(paint.getColor())) {
272 this->addAttribute("fill-opacity", svg_opacity(paint.getColor()));
273 }
fmalita532faa92015-02-03 05:44:40 -0800274 } else {
fmalita12753cc2015-02-04 14:56:35 -0800275 SkASSERT(style == SkPaint::kStroke_Style);
fmalita532faa92015-02-03 05:44:40 -0800276 this->addAttribute("fill", "none");
277 }
278
Anas AlJabri84dd1832018-07-31 15:53:05 -0700279 if (!resources.fColorFilter.isEmpty()) {
280 this->addAttribute("filter", resources.fColorFilter.c_str());
281 }
282
fmalita532faa92015-02-03 05:44:40 -0800283 if (style == SkPaint::kStroke_Style || style == SkPaint::kStrokeAndFill_Style) {
284 this->addAttribute("stroke", resources.fPaintServer);
fmalita532faa92015-02-03 05:44:40 -0800285
fmalita12753cc2015-02-04 14:56:35 -0800286 SkScalar strokeWidth = paint.getStrokeWidth();
287 if (strokeWidth == 0) {
288 // Hairline stroke
289 strokeWidth = 1;
290 this->addAttribute("vector-effect", "non-scaling-stroke");
291 }
292 this->addAttribute("stroke-width", strokeWidth);
293
294 if (const char* cap = svg_cap(paint.getStrokeCap())) {
295 this->addAttribute("stroke-linecap", cap);
296 }
297
298 if (const char* join = svg_join(paint.getStrokeJoin())) {
299 this->addAttribute("stroke-linejoin", join);
300 }
301
302 if (paint.getStrokeJoin() == SkPaint::kMiter_Join) {
303 this->addAttribute("stroke-miterlimit", paint.getStrokeMiter());
304 }
305
306 if (SK_AlphaOPAQUE != SkColorGetA(paint.getColor())) {
307 this->addAttribute("stroke-opacity", svg_opacity(paint.getColor()));
308 }
309 } else {
310 SkASSERT(style == SkPaint::kFill_Style);
311 this->addAttribute("stroke", "none");
fmalita532faa92015-02-03 05:44:40 -0800312 }
313}
314
Mike Reedc5e641c2017-02-17 14:38:11 -0500315Resources SkSVGDevice::AutoElement::addResources(const MxCp& mc, const SkPaint& paint) {
fmalita532faa92015-02-03 05:44:40 -0800316 Resources resources(paint);
fmalita1a481fe2015-02-04 07:39:34 -0800317
318 // FIXME: this is a weak heuristic and we end up with LOTS of redundant clips.
Mike Reedc5e641c2017-02-17 14:38:11 -0500319 bool hasClip = !mc.fClipStack->isWideOpen();
fmalita1a481fe2015-02-04 07:39:34 -0800320 bool hasShader = SkToBool(paint.getShader());
321
322 if (hasClip || hasShader) {
323 AutoElement defs("defs", fWriter);
324
325 if (hasClip) {
Mike Reedc5e641c2017-02-17 14:38:11 -0500326 this->addClipResources(mc, &resources);
fmalita1a481fe2015-02-04 07:39:34 -0800327 }
328
329 if (hasShader) {
330 this->addShaderResources(paint, &resources);
331 }
332 }
333
Anas AlJabri84dd1832018-07-31 15:53:05 -0700334 if (const SkColorFilter* cf = paint.getColorFilter()) {
335 // TODO: Implement skia color filters for blend modes other than SrcIn
336 SkBlendMode mode;
337 if (cf->asColorMode(nullptr, &mode) && mode == SkBlendMode::kSrcIn) {
338 this->addColorFilterResources(*cf, &resources);
339 }
340 }
fmalita532faa92015-02-03 05:44:40 -0800341 return resources;
342}
343
Alexander Midlash77e3afc2018-03-06 17:21:28 -0800344void SkSVGDevice::AutoElement::addGradientShaderResources(const SkShader* shader,
345 const SkPaint& paint,
346 Resources* resources) {
fmalita532faa92015-02-03 05:44:40 -0800347 SkShader::GradientInfo grInfo;
348 grInfo.fColorCount = 0;
349 if (SkShader::kLinear_GradientType != shader->asAGradient(&grInfo)) {
350 // TODO: non-linear gradient support
fmalita532faa92015-02-03 05:44:40 -0800351 return;
352 }
353
fmalita1a481fe2015-02-04 07:39:34 -0800354 SkAutoSTArray<16, SkColor> grColors(grInfo.fColorCount);
355 SkAutoSTArray<16, SkScalar> grOffsets(grInfo.fColorCount);
356 grInfo.fColors = grColors.get();
357 grInfo.fColorOffsets = grOffsets.get();
358
359 // One more call to get the actual colors/offsets.
360 shader->asAGradient(&grInfo);
361 SkASSERT(grInfo.fColorCount <= grColors.count());
362 SkASSERT(grInfo.fColorCount <= grOffsets.count());
363
364 resources->fPaintServer.printf("url(#%s)", addLinearGradientDef(grInfo, shader).c_str());
365}
366
Anas AlJabri84dd1832018-07-31 15:53:05 -0700367void SkSVGDevice::AutoElement::addColorFilterResources(const SkColorFilter& cf,
368 Resources* resources) {
369 SkString colorfilterID = fResourceBucket->addColorFilter();
370 {
371 AutoElement filterElement("filter", fWriter);
372 filterElement.addAttribute("id", colorfilterID);
373 filterElement.addAttribute("x", "0%");
374 filterElement.addAttribute("y", "0%");
375 filterElement.addAttribute("width", "100%");
376 filterElement.addAttribute("height", "100%");
377
378 SkColor filterColor;
379 SkBlendMode mode;
380 bool asColorMode = cf.asColorMode(&filterColor, &mode);
381 SkAssertResult(asColorMode);
382 SkASSERT(mode == SkBlendMode::kSrcIn);
383
384 {
385 // first flood with filter color
386 AutoElement floodElement("feFlood", fWriter);
387 floodElement.addAttribute("flood-color", svg_color(filterColor));
388 floodElement.addAttribute("flood-opacity", svg_opacity(filterColor));
389 floodElement.addAttribute("result", "flood");
390 }
391
392 {
393 // apply the transform to filter color
394 AutoElement compositeElement("feComposite", fWriter);
395 compositeElement.addAttribute("in", "flood");
396 compositeElement.addAttribute("operator", "in");
397 }
398 }
399 resources->fColorFilter.printf("url(#%s)", colorfilterID.c_str());
400}
401
Alexander Midlash77e3afc2018-03-06 17:21:28 -0800402// Returns data uri from bytes.
403// it will use any cached data if available, otherwise will
404// encode as png.
405sk_sp<SkData> AsDataUri(SkImage* image) {
406 sk_sp<SkData> imageData = image->encodeToData();
407 if (!imageData) {
Alexander Midlash77e3afc2018-03-06 17:21:28 -0800408 return nullptr;
409 }
410
411 const char* src = (char*)imageData->data();
412 const char* selectedPrefix = nullptr;
413 size_t selectedPrefixLength = 0;
414
415 const static char pngDataPrefix[] = "data:image/png;base64,";
416 const static char jpgDataPrefix[] = "data:image/jpeg;base64,";
417
418 if (SkJpegCodec::IsJpeg(src, imageData->size())) {
419 selectedPrefix = jpgDataPrefix;
420 selectedPrefixLength = sizeof(jpgDataPrefix);
421 } else {
422 if (!SkPngCodec::IsPng(src, imageData->size())) {
Alexander Midlash77e3afc2018-03-06 17:21:28 -0800423 imageData = image->encodeToData(SkEncodedImageFormat::kPNG, 100);
424 }
425 selectedPrefix = pngDataPrefix;
426 selectedPrefixLength = sizeof(pngDataPrefix);
427 }
428
429 size_t b64Size = SkBase64::Encode(imageData->data(), imageData->size(), nullptr);
430 sk_sp<SkData> dataUri = SkData::MakeUninitialized(selectedPrefixLength + b64Size);
431 char* dest = (char*)dataUri->writable_data();
432 memcpy(dest, selectedPrefix, selectedPrefixLength);
433 SkBase64::Encode(imageData->data(), imageData->size(), dest + selectedPrefixLength - 1);
434 dest[dataUri->size() - 1] = 0;
435 return dataUri;
436}
437
438void SkSVGDevice::AutoElement::addImageShaderResources(const SkShader* shader, const SkPaint& paint,
439 Resources* resources) {
440 SkMatrix outMatrix;
441
442 SkShader::TileMode xy[2];
443 SkImage* image = shader->isAImage(&outMatrix, xy);
444 SkASSERT(image);
445
446 SkString patternDims[2]; // width, height
447
448 sk_sp<SkData> dataUri = AsDataUri(image);
449 if (!dataUri) {
Alexander Midlash77e3afc2018-03-06 17:21:28 -0800450 return;
451 }
452 SkIRect imageSize = image->bounds();
453 for (int i = 0; i < 2; i++) {
454 int imageDimension = i == 0 ? imageSize.width() : imageSize.height();
455 switch (xy[i]) {
456 case SkShader::kRepeat_TileMode:
457 patternDims[i].appendScalar(imageDimension);
458 break;
459 default:
Mike Klein6b2b6502018-05-09 10:48:52 -0400460 // TODO: other tile modes?
Alexander Midlash77e3afc2018-03-06 17:21:28 -0800461 patternDims[i] = "100%";
Alexander Midlash77e3afc2018-03-06 17:21:28 -0800462 }
463 }
464
465 SkString patternID = fResourceBucket->addPattern();
466 {
467 AutoElement pattern("pattern", fWriter);
468 pattern.addAttribute("id", patternID);
469 pattern.addAttribute("patternUnits", "userSpaceOnUse");
470 pattern.addAttribute("patternContentUnits", "userSpaceOnUse");
471 pattern.addAttribute("width", patternDims[0]);
472 pattern.addAttribute("height", patternDims[1]);
473 pattern.addAttribute("x", 0);
474 pattern.addAttribute("y", 0);
475
476 {
477 SkString imageID = fResourceBucket->addImage();
478 AutoElement imageTag("image", fWriter);
479 imageTag.addAttribute("id", imageID);
480 imageTag.addAttribute("x", 0);
481 imageTag.addAttribute("y", 0);
482 imageTag.addAttribute("width", image->width());
483 imageTag.addAttribute("height", image->height());
484 imageTag.addAttribute("xlink:href", static_cast<const char*>(dataUri->data()));
485 }
486 }
487 resources->fPaintServer.printf("url(#%s)", patternID.c_str());
488}
489
490void SkSVGDevice::AutoElement::addShaderResources(const SkPaint& paint, Resources* resources) {
491 const SkShader* shader = paint.getShader();
492 SkASSERT(shader);
493
494 if (shader->asAGradient(nullptr) != SkShader::kNone_GradientType) {
Mike Klein6b2b6502018-05-09 10:48:52 -0400495 this->addGradientShaderResources(shader, paint, resources);
Alexander Midlash77e3afc2018-03-06 17:21:28 -0800496 } else if (shader->isAImage()) {
Mike Klein6b2b6502018-05-09 10:48:52 -0400497 this->addImageShaderResources(shader, paint, resources);
Alexander Midlash77e3afc2018-03-06 17:21:28 -0800498 }
Mike Klein6b2b6502018-05-09 10:48:52 -0400499 // TODO: other shader types?
Alexander Midlash77e3afc2018-03-06 17:21:28 -0800500}
501
Mike Reedc5e641c2017-02-17 14:38:11 -0500502void SkSVGDevice::AutoElement::addClipResources(const MxCp& mc, Resources* resources) {
503 SkASSERT(!mc.fClipStack->isWideOpen());
fmalita1a481fe2015-02-04 07:39:34 -0800504
505 SkPath clipPath;
Mike Reedc5e641c2017-02-17 14:38:11 -0500506 (void) mc.fClipStack->asPath(&clipPath);
fmalita1a481fe2015-02-04 07:39:34 -0800507
508 SkString clipID = fResourceBucket->addClip();
509 const char* clipRule = clipPath.getFillType() == SkPath::kEvenOdd_FillType ?
510 "evenodd" : "nonzero";
fmalita532faa92015-02-03 05:44:40 -0800511 {
fmalita1a481fe2015-02-04 07:39:34 -0800512 // clipPath is in device space, but since we're only pushing transform attributes
513 // to the leaf nodes, so are all our elements => SVG userSpaceOnUse == device space.
514 AutoElement clipPathElement("clipPath", fWriter);
515 clipPathElement.addAttribute("id", clipID);
fmalita532faa92015-02-03 05:44:40 -0800516
fmalita1a481fe2015-02-04 07:39:34 -0800517 SkRect clipRect = SkRect::MakeEmpty();
518 if (clipPath.isEmpty() || clipPath.isRect(&clipRect)) {
519 AutoElement rectElement("rect", fWriter);
520 rectElement.addRectAttributes(clipRect);
521 rectElement.addAttribute("clip-rule", clipRule);
522 } else {
523 AutoElement pathElement("path", fWriter);
fmalitaa9d9de42015-02-04 17:54:46 -0800524 pathElement.addPathAttributes(clipPath);
fmalita1a481fe2015-02-04 07:39:34 -0800525 pathElement.addAttribute("clip-rule", clipRule);
526 }
fmalita532faa92015-02-03 05:44:40 -0800527 }
fmalita1a481fe2015-02-04 07:39:34 -0800528
529 resources->fClip.printf("url(#%s)", clipID.c_str());
fmalita532faa92015-02-03 05:44:40 -0800530}
531
532SkString SkSVGDevice::AutoElement::addLinearGradientDef(const SkShader::GradientInfo& info,
533 const SkShader* shader) {
534 SkASSERT(fResourceBucket);
535 SkString id = fResourceBucket->addLinearGradient();
536
537 {
538 AutoElement gradient("linearGradient", fWriter);
539
540 gradient.addAttribute("id", id);
541 gradient.addAttribute("gradientUnits", "userSpaceOnUse");
542 gradient.addAttribute("x1", info.fPoint[0].x());
543 gradient.addAttribute("y1", info.fPoint[0].y());
544 gradient.addAttribute("x2", info.fPoint[1].x());
545 gradient.addAttribute("y2", info.fPoint[1].y());
fmalitaa9d9de42015-02-04 17:54:46 -0800546
547 if (!shader->getLocalMatrix().isIdentity()) {
548 this->addAttribute("gradientTransform", svg_transform(shader->getLocalMatrix()));
549 }
fmalita532faa92015-02-03 05:44:40 -0800550
551 SkASSERT(info.fColorCount >= 2);
552 for (int i = 0; i < info.fColorCount; ++i) {
553 SkColor color = info.fColors[i];
554 SkString colorStr(svg_color(color));
555
556 {
557 AutoElement stop("stop", fWriter);
558 stop.addAttribute("offset", info.fColorOffsets[i]);
559 stop.addAttribute("stop-color", colorStr.c_str());
560
561 if (SK_AlphaOPAQUE != SkColorGetA(color)) {
562 stop.addAttribute("stop-opacity", svg_opacity(color));
563 }
564 }
565 }
566 }
567
568 return id;
fmalita93957f42015-01-30 09:03:29 -0800569}
570
fmalita1a481fe2015-02-04 07:39:34 -0800571void SkSVGDevice::AutoElement::addRectAttributes(const SkRect& rect) {
572 // x, y default to 0
573 if (rect.x() != 0) {
574 this->addAttribute("x", rect.x());
575 }
576 if (rect.y() != 0) {
577 this->addAttribute("y", rect.y());
578 }
579
580 this->addAttribute("width", rect.width());
581 this->addAttribute("height", rect.height());
582}
583
fmalitaa9d9de42015-02-04 17:54:46 -0800584void SkSVGDevice::AutoElement::addPathAttributes(const SkPath& path) {
585 SkString pathData;
586 SkParsePath::ToSVGString(path, &pathData);
587 this->addAttribute("d", pathData);
588}
589
Mike Reed96345a22019-01-02 21:30:29 -0500590void SkSVGDevice::AutoElement::addTextAttributes(const SkFont& font) {
591 this->addAttribute("font-size", font.getSize());
fmalitafe3f2602015-02-03 17:47:12 -0800592
fmalitaf89f60f2015-02-13 08:55:24 -0800593 SkString familyName;
mtklein02f46cf2015-03-20 13:48:42 -0700594 SkTHashSet<SkString> familySet;
Herb Derby087fad72019-01-22 14:45:16 -0500595 sk_sp<SkTypeface> tface = font.refTypefaceOrDefault();
fmalita7a048692015-02-20 13:54:40 -0800596
597 SkASSERT(tface);
Ben Wagner71319502017-07-27 10:45:29 -0400598 SkFontStyle style = tface->fontStyle();
599 if (style.slant() == SkFontStyle::kItalic_Slant) {
fmalita7a048692015-02-20 13:54:40 -0800600 this->addAttribute("font-style", "italic");
Ben Wagner71319502017-07-27 10:45:29 -0400601 } else if (style.slant() == SkFontStyle::kOblique_Slant) {
602 this->addAttribute("font-style", "oblique");
fmalita7a048692015-02-20 13:54:40 -0800603 }
Ben Wagner71319502017-07-27 10:45:29 -0400604 int weightIndex = (SkTPin(style.weight(), 100, 900) - 50) / 100;
605 if (weightIndex != 3) {
606 static constexpr const char* weights[] = {
607 "100", "200", "300", "normal", "400", "500", "600", "bold", "800", "900"
608 };
609 this->addAttribute("font-weight", weights[weightIndex]);
610 }
611 int stretchIndex = style.width() - 1;
612 if (stretchIndex != 4) {
613 static constexpr const char* stretches[] = {
614 "ultra-condensed", "extra-condensed", "condensed", "semi-condensed",
615 "normal",
616 "semi-expanded", "expanded", "extra-expanded", "ultra-expanded"
617 };
618 this->addAttribute("font-stretch", stretches[stretchIndex]);
fmalita7a048692015-02-20 13:54:40 -0800619 }
620
Hal Canary67b39de2016-11-07 11:47:44 -0500621 sk_sp<SkTypeface::LocalizedStrings> familyNameIter(tface->createFamilyNameIterator());
fmalitaf89f60f2015-02-13 08:55:24 -0800622 SkTypeface::LocalizedString familyString;
Mike Reedf67c4592017-02-17 17:06:11 -0500623 if (familyNameIter) {
624 while (familyNameIter->next(&familyString)) {
625 if (familySet.contains(familyString.fString)) {
626 continue;
627 }
628 familySet.add(familyString.fString);
629 familyName.appendf((familyName.isEmpty() ? "%s" : ", %s"), familyString.fString.c_str());
fmalitaf89f60f2015-02-13 08:55:24 -0800630 }
fmalitaf89f60f2015-02-13 08:55:24 -0800631 }
fmalitaf89f60f2015-02-13 08:55:24 -0800632 if (!familyName.isEmpty()) {
633 this->addAttribute("font-family", familyName);
634 }
fmalitafe3f2602015-02-03 17:47:12 -0800635}
636
KDr2e77142e2019-02-13 14:26:40 +0000637SkBaseDevice* SkSVGDevice::Create(const SkISize& size, SkXMLWriter* writer, bool ownsWriter) {
fmalita2aafe6f2015-02-06 12:51:10 -0800638 if (!writer) {
halcanary96fcdcc2015-08-27 07:41:13 -0700639 return nullptr;
fmalita93957f42015-01-30 09:03:29 -0800640 }
641
KDr2e77142e2019-02-13 14:26:40 +0000642 return new SkSVGDevice(size, writer, ownsWriter);
fmalita93957f42015-01-30 09:03:29 -0800643}
644
KDr2e77142e2019-02-13 14:26:40 +0000645SkSVGDevice::SkSVGDevice(const SkISize& size, SkXMLWriter* writer, bool ownsWriter)
reed589a39e2016-08-20 07:59:19 -0700646 : INHERITED(SkImageInfo::MakeUnknown(size.fWidth, size.fHeight),
647 SkSurfaceProps(0, kUnknown_SkPixelGeometry))
robertphillips9a53fd72015-06-22 09:46:59 -0700648 , fWriter(writer)
KDr2e77142e2019-02-13 14:26:40 +0000649 , fOwnsWriter(ownsWriter)
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() {
KDr2e77142e2019-02-13 14:26:40 +0000666 if (fOwnsWriter && fWriter) {
KDr2a0dcd292019-02-14 08:49:30 +0000667 fRootElement.reset(nullptr);
KDr2e77142e2019-02-13 14:26:40 +0000668 delete fWriter;
669 }
fmalita93957f42015-01-30 09:03:29 -0800670}
671
Mike Reeda1361362017-03-07 09:37:29 -0500672void SkSVGDevice::drawPaint(const SkPaint& paint) {
673 AutoElement rect("rect", fWriter, fResourceBucket.get(), MxCp(this), paint);
fmalita1a481fe2015-02-04 07:39:34 -0800674 rect.addRectAttributes(SkRect::MakeWH(SkIntToScalar(this->width()),
675 SkIntToScalar(this->height())));
fmalita93957f42015-01-30 09:03:29 -0800676}
677
Bryce Thomasfd5a5082018-02-06 14:53:05 -0800678void SkSVGDevice::drawAnnotation(const SkRect& rect, const char key[], SkData* value) {
679 if (!value) {
680 return;
681 }
682
683 if (!strcmp(SkAnnotationKeys::URL_Key(), key) ||
684 !strcmp(SkAnnotationKeys::Link_Named_Dest_Key(), key)) {
685 this->cs().save();
686 this->cs().clipRect(rect, this->ctm(), kIntersect_SkClipOp, true);
687 SkRect transformedRect = this->cs().bounds(this->getGlobalBounds());
688 this->cs().restore();
689 if (transformedRect.isEmpty()) {
690 return;
691 }
692
693 SkString url(static_cast<const char*>(value->data()), value->size() - 1);
694 AutoElement a("a", fWriter);
695 a.addAttribute("xlink:href", url.c_str());
696 {
697 AutoElement r("rect", fWriter);
698 r.addAttribute("fill-opacity", "0.0");
699 r.addRectAttributes(transformedRect);
700 }
701 }
702}
703
Mike Reeda1361362017-03-07 09:37:29 -0500704void SkSVGDevice::drawPoints(SkCanvas::PointMode mode, size_t count,
reed5f68c342015-05-15 10:11:11 -0700705 const SkPoint pts[], const SkPaint& paint) {
706 SkPath path;
707
708 switch (mode) {
709 // todo
710 case SkCanvas::kPoints_PointMode:
Mike Klein6b2b6502018-05-09 10:48:52 -0400711 // TODO?
reed5f68c342015-05-15 10:11:11 -0700712 break;
713 case SkCanvas::kLines_PointMode:
714 count -= 1;
715 for (size_t i = 0; i < count; i += 2) {
716 path.rewind();
717 path.moveTo(pts[i]);
718 path.lineTo(pts[i+1]);
Mike Reeda1361362017-03-07 09:37:29 -0500719 AutoElement elem("path", fWriter, fResourceBucket.get(), MxCp(this), paint);
reed5f68c342015-05-15 10:11:11 -0700720 elem.addPathAttributes(path);
721 }
722 break;
723 case SkCanvas::kPolygon_PointMode:
724 if (count > 1) {
725 path.addPoly(pts, SkToInt(count), false);
726 path.moveTo(pts[0]);
Mike Reeda1361362017-03-07 09:37:29 -0500727 AutoElement elem("path", fWriter, fResourceBucket.get(), MxCp(this), paint);
reed5f68c342015-05-15 10:11:11 -0700728 elem.addPathAttributes(path);
729 }
730 break;
731 }
fmalita93957f42015-01-30 09:03:29 -0800732}
733
Mike Reeda1361362017-03-07 09:37:29 -0500734void SkSVGDevice::drawRect(const SkRect& r, const SkPaint& paint) {
Alexander Midlash77e3afc2018-03-06 17:21:28 -0800735 std::unique_ptr<AutoElement> svg;
736 if (RequiresViewportReset(paint)) {
737 svg.reset(new AutoElement("svg", fWriter, fResourceBucket.get(), MxCp(this), paint));
738 svg->addRectAttributes(r);
739 }
740
Mike Reeda1361362017-03-07 09:37:29 -0500741 AutoElement rect("rect", fWriter, fResourceBucket.get(), MxCp(this), paint);
Alexander Midlash77e3afc2018-03-06 17:21:28 -0800742
743 if (svg) {
744 rect.addAttribute("x", 0);
745 rect.addAttribute("y", 0);
746 rect.addAttribute("width", "100%");
747 rect.addAttribute("height", "100%");
748 } else {
749 rect.addRectAttributes(r);
750 }
fmalita93957f42015-01-30 09:03:29 -0800751}
752
Mike Reeda1361362017-03-07 09:37:29 -0500753void SkSVGDevice::drawOval(const SkRect& oval, const SkPaint& paint) {
754 AutoElement ellipse("ellipse", fWriter, fResourceBucket.get(), MxCp(this), paint);
fmalita532faa92015-02-03 05:44:40 -0800755 ellipse.addAttribute("cx", oval.centerX());
756 ellipse.addAttribute("cy", oval.centerY());
757 ellipse.addAttribute("rx", oval.width() / 2);
758 ellipse.addAttribute("ry", oval.height() / 2);
fmalita93957f42015-01-30 09:03:29 -0800759}
760
Mike Reeda1361362017-03-07 09:37:29 -0500761void SkSVGDevice::drawRRect(const SkRRect& rr, const SkPaint& paint) {
reed5f68c342015-05-15 10:11:11 -0700762 SkPath path;
763 path.addRRect(rr);
764
Mike Reeda1361362017-03-07 09:37:29 -0500765 AutoElement elem("path", fWriter, fResourceBucket.get(), MxCp(this), paint);
reed5f68c342015-05-15 10:11:11 -0700766 elem.addPathAttributes(path);
fmalita93957f42015-01-30 09:03:29 -0800767}
768
Robert Phillips137ca522018-08-15 10:14:33 -0400769void SkSVGDevice::drawPath(const SkPath& path, const SkPaint& paint, bool pathIsMutable) {
Mike Reeda1361362017-03-07 09:37:29 -0500770 AutoElement elem("path", fWriter, fResourceBucket.get(), MxCp(this), paint);
fmalitaa9d9de42015-02-04 17:54:46 -0800771 elem.addPathAttributes(path);
fmalita221722b2016-09-06 14:37:02 -0700772
773 // TODO: inverse fill types?
774 if (path.getFillType() == SkPath::kEvenOdd_FillType) {
775 elem.addAttribute("fill-rule", "evenodd");
776 }
fmalita93957f42015-01-30 09:03:29 -0800777}
778
Hal Canarydb683012016-11-23 08:55:18 -0700779static sk_sp<SkData> encode(const SkBitmap& src) {
780 SkDynamicMemoryWStream buf;
781 return SkEncodeImage(&buf, src, SkEncodedImageFormat::kPNG, 80) ? buf.detachAsData() : nullptr;
782}
783
Mike Reedc5e641c2017-02-17 14:38:11 -0500784void SkSVGDevice::drawBitmapCommon(const MxCp& mc, const SkBitmap& bm, const SkPaint& paint) {
Hal Canarydb683012016-11-23 08:55:18 -0700785 sk_sp<SkData> pngData = encode(bm);
fmalita827da232015-02-27 07:44:47 -0800786 if (!pngData) {
787 return;
788 }
789
halcanary96fcdcc2015-08-27 07:41:13 -0700790 size_t b64Size = SkBase64::Encode(pngData->data(), pngData->size(), nullptr);
fmalita827da232015-02-27 07:44:47 -0800791 SkAutoTMalloc<char> b64Data(b64Size);
792 SkBase64::Encode(pngData->data(), pngData->size(), b64Data.get());
793
794 SkString svgImageData("data:image/png;base64,");
795 svgImageData.append(b64Data.get(), b64Size);
796
797 SkString imageID = fResourceBucket->addImage();
798 {
799 AutoElement defs("defs", fWriter);
800 {
801 AutoElement image("image", fWriter);
802 image.addAttribute("id", imageID);
803 image.addAttribute("width", bm.width());
804 image.addAttribute("height", bm.height());
805 image.addAttribute("xlink:href", svgImageData);
806 }
807 }
808
809 {
Mike Reedc5e641c2017-02-17 14:38:11 -0500810 AutoElement imageUse("use", fWriter, fResourceBucket.get(), mc, paint);
fmalita827da232015-02-27 07:44:47 -0800811 imageUse.addAttribute("xlink:href", SkStringPrintf("#%s", imageID.c_str()));
812 }
813}
814
Mike Reeda1361362017-03-07 09:37:29 -0500815void SkSVGDevice::drawSprite(const SkBitmap& bitmap,
fmalita93957f42015-01-30 09:03:29 -0800816 int x, int y, const SkPaint& paint) {
Mike Reeda1361362017-03-07 09:37:29 -0500817 MxCp mc(this);
Mike Reedc5e641c2017-02-17 14:38:11 -0500818 SkMatrix adjustedMatrix = *mc.fMatrix;
fmalita827da232015-02-27 07:44:47 -0800819 adjustedMatrix.preTranslate(SkIntToScalar(x), SkIntToScalar(y));
Mike Reedc5e641c2017-02-17 14:38:11 -0500820 mc.fMatrix = &adjustedMatrix;
fmalita827da232015-02-27 07:44:47 -0800821
Mike Reedc5e641c2017-02-17 14:38:11 -0500822 drawBitmapCommon(mc, bitmap, paint);
fmalita93957f42015-01-30 09:03:29 -0800823}
824
Mike Reeda1361362017-03-07 09:37:29 -0500825void SkSVGDevice::drawBitmapRect(const SkBitmap& bm, const SkRect* srcOrNull,
fmalita93957f42015-01-30 09:03:29 -0800826 const SkRect& dst, const SkPaint& paint,
reed562fe472015-07-28 07:35:14 -0700827 SkCanvas::SrcRectConstraint) {
Mike Reedf880b682017-03-10 11:30:44 -0500828 SkClipStack* cs = &this->cs();
829 SkClipStack::AutoRestore ar(cs, false);
Mike Reedc5e641c2017-02-17 14:38:11 -0500830 if (srcOrNull && *srcOrNull != SkRect::Make(bm.bounds())) {
Mike Reedf880b682017-03-10 11:30:44 -0500831 cs->save();
832 cs->clipRect(dst, this->ctm(), kIntersect_SkClipOp, paint.isAntiAlias());
Mike Reedc5e641c2017-02-17 14:38:11 -0500833 }
834
fmalita827da232015-02-27 07:44:47 -0800835 SkMatrix adjustedMatrix;
836 adjustedMatrix.setRectToRect(srcOrNull ? *srcOrNull : SkRect::Make(bm.bounds()),
837 dst,
838 SkMatrix::kFill_ScaleToFit);
Mike Reedf880b682017-03-10 11:30:44 -0500839 adjustedMatrix.postConcat(this->ctm());
fmalita827da232015-02-27 07:44:47 -0800840
Mike Reedf880b682017-03-10 11:30:44 -0500841 drawBitmapCommon(MxCp(&adjustedMatrix, cs), bm, paint);
fmalita93957f42015-01-30 09:03:29 -0800842}
843
Herb Derbyd708bd62018-08-29 15:59:34 -0400844class SVGTextBuilder : SkNoncopyable {
845public:
846 SVGTextBuilder(SkPoint origin, const SkGlyphRun& glyphRun)
847 : fOrigin(origin)
848 , fLastCharWasWhitespace(true) { // start off in whitespace mode to strip all leadingspace
Herb Derbyd708bd62018-08-29 15:59:34 -0400849 auto runSize = glyphRun.runSize();
850 SkAutoSTArray<64, SkUnichar> unichars(runSize);
Herb Derby95e17602018-12-06 17:11:43 -0500851 glyphRun.font().glyphsToUnichars(glyphRun.glyphsIDs().data(), runSize, unichars.get());
Herb Derbyd708bd62018-08-29 15:59:34 -0400852 auto positions = glyphRun.positions();
853 for (size_t i = 0; i < runSize; ++i) {
854 this->appendUnichar(unichars[i], positions[i]);
855 }
856 }
fmalitafe3f2602015-02-03 17:47:12 -0800857
Herb Derbyd708bd62018-08-29 15:59:34 -0400858 const SkString& text() const { return fText; }
859 const SkString& posX() const { return fPosX; }
860 const SkString& posY() const { return fPosY; }
fmalitafe3f2602015-02-03 17:47:12 -0800861
Herb Derbyd708bd62018-08-29 15:59:34 -0400862private:
863 void appendUnichar(SkUnichar c, SkPoint position) {
864 bool discardPos = false;
865 bool isWhitespace = false;
866
867 switch(c) {
868 case ' ':
869 case '\t':
870 // consolidate whitespace to match SVG's xml:space=default munging
871 // (http://www.w3.org/TR/SVG/text.html#WhiteSpace)
872 if (fLastCharWasWhitespace) {
873 discardPos = true;
874 } else {
875 fText.appendUnichar(c);
876 }
877 isWhitespace = true;
878 break;
879 case '\0':
880 // SkPaint::glyphsToUnichars() returns \0 for inconvertible glyphs, but these
881 // are not legal XML characters (http://www.w3.org/TR/REC-xml/#charsets)
882 discardPos = true;
883 isWhitespace = fLastCharWasWhitespace; // preserve whitespace consolidation
884 break;
885 case '&':
886 fText.append("&amp;");
887 break;
888 case '"':
889 fText.append("&quot;");
890 break;
891 case '\'':
892 fText.append("&apos;");
893 break;
894 case '<':
895 fText.append("&lt;");
896 break;
897 case '>':
898 fText.append("&gt;");
899 break;
900 default:
901 fText.appendUnichar(c);
902 break;
903 }
904
905 this->advancePos(discardPos, position);
906 fLastCharWasWhitespace = isWhitespace;
907 }
908
909 void advancePos(bool discard, SkPoint position) {
910 if (!discard) {
911 SkPoint finalPosition = fOrigin + position;
912 fPosX.appendf("%.8g, ", finalPosition.x());
913 fPosY.appendf("%.8g, ", finalPosition.y());
914 }
915 }
916
Robert Phillips94078642018-08-31 07:58:34 -0400917 const SkPoint fOrigin;
Herb Derbyd708bd62018-08-29 15:59:34 -0400918
919 SkString fText, fPosX, fPosY;
920 bool fLastCharWasWhitespace;
921};
922
923void SkSVGDevice::drawGlyphRunList(const SkGlyphRunList& glyphRunList) {
924
Herb Derby95e17602018-12-06 17:11:43 -0500925 auto processGlyphRun = [this]
926 (SkPoint origin, const SkGlyphRun& glyphRun, const SkPaint& runPaint) {
Mike Reed96345a22019-01-02 21:30:29 -0500927 AutoElement elem("text", fWriter, fResourceBucket.get(), MxCp(this), runPaint);
928 elem.addTextAttributes(glyphRun.font());
Herb Derbyd708bd62018-08-29 15:59:34 -0400929
930 SVGTextBuilder builder(origin, glyphRun);
931 elem.addAttribute("x", builder.posX());
932 elem.addAttribute("y", builder.posY());
933 elem.addText(builder.text());
934 };
935
936 for (auto& glyphRun : glyphRunList) {
Herb Derby95e17602018-12-06 17:11:43 -0500937 processGlyphRun(glyphRunList.origin(), glyphRun, glyphRunList.paint());
Herb Derbyd708bd62018-08-29 15:59:34 -0400938 }
fmalita93957f42015-01-30 09:03:29 -0800939}
940
Ruiqi Maoc97a3392018-08-15 10:44:19 -0400941void SkSVGDevice::drawVertices(const SkVertices*, const SkVertices::Bone[], int, SkBlendMode,
Ruiqi Maof5101492018-06-29 14:32:21 -0400942 const SkPaint&) {
fmalita93957f42015-01-30 09:03:29 -0800943 // todo
944}
945
Mike Reeda1361362017-03-07 09:37:29 -0500946void SkSVGDevice::drawDevice(SkBaseDevice*, int x, int y,
fmalita93957f42015-01-30 09:03:29 -0800947 const SkPaint&) {
948 // todo
fmalita93957f42015-01-30 09:03:29 -0800949}