/src/skia/src/svg/SkSVGDevice.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
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 "src/svg/SkSVGDevice.h" |
9 | | |
10 | | #include <memory> |
11 | | |
12 | | #include "include/core/SkBitmap.h" |
13 | | #include "include/core/SkBlendMode.h" |
14 | | #include "include/core/SkColorFilter.h" |
15 | | #include "include/core/SkData.h" |
16 | | #include "include/core/SkImage.h" |
17 | | #include "include/core/SkImageEncoder.h" |
18 | | #include "include/core/SkPaint.h" |
19 | | #include "include/core/SkPathBuilder.h" |
20 | | #include "include/core/SkShader.h" |
21 | | #include "include/core/SkStream.h" |
22 | | #include "include/core/SkTypeface.h" |
23 | | #include "include/private/SkChecksum.h" |
24 | | #include "include/private/SkTHash.h" |
25 | | #include "include/private/SkTPin.h" |
26 | | #include "include/private/SkTo.h" |
27 | | #include "include/svg/SkSVGCanvas.h" |
28 | | #include "include/utils/SkBase64.h" |
29 | | #include "src/codec/SkJpegCodec.h" |
30 | | #include "src/core/SkAnnotationKeys.h" |
31 | | #include "src/core/SkClipStack.h" |
32 | | #include "src/core/SkDraw.h" |
33 | | #include "src/core/SkFontPriv.h" |
34 | | #include "src/core/SkUtils.h" |
35 | | #include "src/image/SkImage_Base.h" |
36 | | #include "src/shaders/SkShaderBase.h" |
37 | | #include "src/xml/SkXMLWriter.h" |
38 | | |
39 | | namespace { |
40 | | |
41 | 30.2k | static SkString svg_color(SkColor color) { |
42 | | // https://www.w3.org/TR/css-color-3/#html4 |
43 | 30.2k | auto named_color = [](SkColor c) -> const char* { |
44 | 30.2k | switch (c & 0xffffff) { |
45 | 22.3k | case 0x000000: return "black"; |
46 | 24 | case 0x000080: return "navy"; |
47 | 238 | case 0x0000ff: return "blue"; |
48 | 20 | case 0x008000: return "green"; |
49 | 16 | case 0x008080: return "teal"; |
50 | 23 | case 0x00ff00: return "lime"; |
51 | 218 | case 0x00ffff: return "aqua"; |
52 | 28 | case 0x800000: return "maroon"; |
53 | 15 | case 0x800080: return "purple"; |
54 | 8 | case 0x808000: return "olive"; |
55 | 27 | case 0x808080: return "gray"; |
56 | 7 | case 0xc0c0c0: return "silver"; |
57 | 80 | case 0xff0000: return "red"; |
58 | 38 | case 0xff00ff: return "fuchsia"; |
59 | 47 | case 0xffff00: return "yellow"; |
60 | 951 | case 0xffffff: return "white"; |
61 | 6.11k | default: break; |
62 | 6.11k | } |
63 | | |
64 | 6.11k | return nullptr; |
65 | 6.11k | }; |
66 | | |
67 | 30.2k | if (const auto* nc = named_color(color)) { |
68 | 24.1k | return SkString(nc); |
69 | 24.1k | } |
70 | | |
71 | 6.11k | uint8_t r = SkColorGetR(color); |
72 | 6.11k | uint8_t g = SkColorGetG(color); |
73 | 6.11k | uint8_t b = SkColorGetB(color); |
74 | | |
75 | | // Some users care about every byte here, so we'll use hex colors with single-digit channels |
76 | | // when possible. |
77 | 6.11k | uint8_t rh = r >> 4; |
78 | 6.11k | uint8_t rl = r & 0xf; |
79 | 6.11k | uint8_t gh = g >> 4; |
80 | 6.11k | uint8_t gl = g & 0xf; |
81 | 6.11k | uint8_t bh = b >> 4; |
82 | 6.11k | uint8_t bl = b & 0xf; |
83 | 6.11k | if ((rh == rl) && (gh == gl) && (bh == bl)) { |
84 | 97 | return SkStringPrintf("#%1X%1X%1X", rh, gh, bh); |
85 | 97 | } |
86 | | |
87 | 6.02k | return SkStringPrintf("#%02X%02X%02X", r, g, b); |
88 | 6.02k | } |
89 | | |
90 | 8.81k | static SkScalar svg_opacity(SkColor color) { |
91 | 8.81k | return SkIntToScalar(SkColorGetA(color)) / SK_AlphaOPAQUE; |
92 | 8.81k | } |
93 | | |
94 | | // Keep in sync with SkPaint::Cap |
95 | | static const char* cap_map[] = { |
96 | | nullptr, // kButt_Cap (default) |
97 | | "round", // kRound_Cap |
98 | | "square" // kSquare_Cap |
99 | | }; |
100 | | static_assert(SK_ARRAY_COUNT(cap_map) == SkPaint::kCapCount, "missing_cap_map_entry"); |
101 | | |
102 | 3.23k | static const char* svg_cap(SkPaint::Cap cap) { |
103 | 3.23k | SkASSERT(cap < SK_ARRAY_COUNT(cap_map)); |
104 | 3.23k | return cap_map[cap]; |
105 | 3.23k | } |
106 | | |
107 | | // Keep in sync with SkPaint::Join |
108 | | static const char* join_map[] = { |
109 | | nullptr, // kMiter_Join (default) |
110 | | "round", // kRound_Join |
111 | | "bevel" // kBevel_Join |
112 | | }; |
113 | | static_assert(SK_ARRAY_COUNT(join_map) == SkPaint::kJoinCount, "missing_join_map_entry"); |
114 | | |
115 | 3.23k | static const char* svg_join(SkPaint::Join join) { |
116 | 3.23k | SkASSERT(join < SK_ARRAY_COUNT(join_map)); |
117 | 3.23k | return join_map[join]; |
118 | 3.23k | } |
119 | | |
120 | 10.0k | static SkString svg_transform(const SkMatrix& t) { |
121 | 10.0k | SkASSERT(!t.isIdentity()); |
122 | | |
123 | 10.0k | SkString tstr; |
124 | 10.0k | switch (t.getType()) { |
125 | 0 | case SkMatrix::kPerspective_Mask: |
126 | | // TODO: handle perspective matrices? |
127 | 0 | break; |
128 | 4.28k | case SkMatrix::kTranslate_Mask: |
129 | 4.28k | tstr.printf("translate(%g %g)", t.getTranslateX(), t.getTranslateY()); |
130 | 4.28k | break; |
131 | 1.80k | case SkMatrix::kScale_Mask: |
132 | 1.80k | tstr.printf("scale(%g %g)", t.getScaleX(), t.getScaleY()); |
133 | 1.80k | break; |
134 | 3.93k | default: |
135 | | // http://www.w3.org/TR/SVG/coords.html#TransformMatrixDefined |
136 | | // | a c e | |
137 | | // | b d f | |
138 | | // | 0 0 1 | |
139 | 3.93k | tstr.printf("matrix(%g %g %g %g %g %g)", |
140 | 3.93k | t.getScaleX(), t.getSkewY(), |
141 | 3.93k | t.getSkewX(), t.getScaleY(), |
142 | 3.93k | t.getTranslateX(), t.getTranslateY()); |
143 | 3.93k | break; |
144 | 10.0k | } |
145 | | |
146 | 10.0k | return tstr; |
147 | 10.0k | } |
148 | | |
149 | | struct Resources { |
150 | | Resources(const SkPaint& paint) |
151 | 29.5k | : fPaintServer(svg_color(paint.getColor())) {} |
152 | | |
153 | | SkString fPaintServer; |
154 | | SkString fColorFilter; |
155 | | }; |
156 | | |
157 | | // Determine if the paint requires us to reset the viewport. |
158 | | // Currently, we do this whenever the paint shader calls |
159 | | // for a repeating image. |
160 | 714 | bool RequiresViewportReset(const SkPaint& paint) { |
161 | 714 | SkShader* shader = paint.getShader(); |
162 | 714 | if (!shader) |
163 | 680 | return false; |
164 | | |
165 | 34 | SkTileMode xy[2]; |
166 | 34 | SkImage* image = shader->isAImage(nullptr, xy); |
167 | | |
168 | 34 | if (!image) |
169 | 33 | return false; |
170 | | |
171 | 3 | for (int i = 0; i < 2; i++) { |
172 | 2 | if (xy[i] == SkTileMode::kRepeat) |
173 | 0 | return true; |
174 | 2 | } |
175 | 1 | return false; |
176 | 1 | } |
177 | | |
178 | 1.03k | void AddPath(const SkGlyphRun& glyphRun, const SkPoint& offset, SkPath* path) { |
179 | 1.03k | struct Rec { |
180 | 1.03k | SkPath* fPath; |
181 | 1.03k | const SkPoint fOffset; |
182 | 1.03k | const SkPoint* fPos; |
183 | 1.03k | } rec = { path, offset, glyphRun.positions().data() }; |
184 | | |
185 | 1.03k | glyphRun.font().getPaths(glyphRun.glyphsIDs().data(), SkToInt(glyphRun.glyphsIDs().size()), |
186 | 11.6k | [](const SkPath* path, const SkMatrix& mx, void* ctx) { |
187 | 11.6k | Rec* rec = reinterpret_cast<Rec*>(ctx); |
188 | 11.6k | if (path) { |
189 | 10.9k | SkMatrix total = mx; |
190 | 10.9k | total.postTranslate(rec->fPos->fX + rec->fOffset.fX, |
191 | 10.9k | rec->fPos->fY + rec->fOffset.fY); |
192 | 10.9k | rec->fPath->addPath(*path, total); |
193 | 666 | } else { |
194 | | // TODO: this is going to drop color emojis. |
195 | 666 | } |
196 | 11.6k | rec->fPos += 1; // move to the next glyph's position |
197 | 11.6k | }, &rec); |
198 | 1.03k | } |
199 | | |
200 | | } // namespace |
201 | | |
202 | | // For now all this does is serve unique serial IDs, but it will eventually evolve to track |
203 | | // and deduplicate resources. |
204 | | class SkSVGDevice::ResourceBucket : ::SkNoncopyable { |
205 | | public: |
206 | | ResourceBucket() |
207 | | : fGradientCount(0) |
208 | | , fPathCount(0) |
209 | | , fImageCount(0) |
210 | | , fPatternCount(0) |
211 | 12.9k | , fColorFilterCount(0) {} |
212 | | |
213 | 86 | SkString addLinearGradient() { |
214 | 86 | return SkStringPrintf("gradient_%d", fGradientCount++); |
215 | 86 | } |
216 | | |
217 | 0 | SkString addPath() { |
218 | 0 | return SkStringPrintf("path_%d", fPathCount++); |
219 | 0 | } |
220 | | |
221 | 18.0k | SkString addImage() { |
222 | 18.0k | return SkStringPrintf("img_%d", fImageCount++); |
223 | 18.0k | } |
224 | | |
225 | 1 | SkString addColorFilter() { return SkStringPrintf("cfilter_%d", fColorFilterCount++); } |
226 | | |
227 | 1.35k | SkString addPattern() { |
228 | 1.35k | return SkStringPrintf("pattern_%d", fPatternCount++); |
229 | 1.35k | } |
230 | | |
231 | | private: |
232 | | uint32_t fGradientCount; |
233 | | uint32_t fPathCount; |
234 | | uint32_t fImageCount; |
235 | | uint32_t fPatternCount; |
236 | | uint32_t fColorFilterCount; |
237 | | }; |
238 | | |
239 | | struct SkSVGDevice::MxCp { |
240 | | const SkMatrix* fMatrix; |
241 | | const SkClipStack* fClipStack; |
242 | | |
243 | 16.6k | MxCp(const SkMatrix* mx, const SkClipStack* cs) : fMatrix(mx), fClipStack(cs) {} |
244 | 12.8k | MxCp(SkSVGDevice* device) : fMatrix(&device->localToDevice()), fClipStack(&device->cs()) {} |
245 | | }; |
246 | | |
247 | | class SkSVGDevice::AutoElement : ::SkNoncopyable { |
248 | | public: |
249 | | AutoElement(const char name[], SkXMLWriter* writer) |
250 | | : fWriter(writer) |
251 | 76.5k | , fResourceBucket(nullptr) { |
252 | 76.5k | fWriter->startElement(name); |
253 | 76.5k | } |
254 | | |
255 | | AutoElement(const char name[], const std::unique_ptr<SkXMLWriter>& writer) |
256 | 69.9k | : AutoElement(name, writer.get()) {} |
257 | | |
258 | | AutoElement(const char name[], SkSVGDevice* svgdev, |
259 | | ResourceBucket* bucket, const MxCp& mc, const SkPaint& paint) |
260 | | : fWriter(svgdev->fWriter.get()) |
261 | 29.5k | , fResourceBucket(bucket) { |
262 | | |
263 | 29.5k | svgdev->syncClipStack(*mc.fClipStack); |
264 | 29.5k | Resources res = this->addResources(mc, paint); |
265 | | |
266 | 29.5k | fWriter->startElement(name); |
267 | | |
268 | 29.5k | this->addPaint(paint, res); |
269 | | |
270 | 29.5k | if (!mc.fMatrix->isIdentity()) { |
271 | 10.0k | this->addAttribute("transform", svg_transform(*mc.fMatrix)); |
272 | 10.0k | } |
273 | 29.5k | } |
274 | | |
275 | 106k | ~AutoElement() { |
276 | 106k | fWriter->endElement(); |
277 | 106k | } |
278 | | |
279 | 37.1k | void addAttribute(const char name[], const char val[]) { |
280 | 37.1k | fWriter->addAttribute(name, val); |
281 | 37.1k | } |
282 | | |
283 | 122k | void addAttribute(const char name[], const SkString& val) { |
284 | 122k | fWriter->addAttribute(name, val.c_str()); |
285 | 122k | } |
286 | | |
287 | 67.4k | void addAttribute(const char name[], int32_t val) { |
288 | 67.4k | fWriter->addS32Attribute(name, val); |
289 | 67.4k | } |
290 | | |
291 | 40.5k | void addAttribute(const char name[], SkScalar val) { |
292 | 40.5k | fWriter->addScalarAttribute(name, val); |
293 | 40.5k | } |
294 | | |
295 | 7.89k | void addText(const SkString& text) { |
296 | 7.89k | fWriter->addText(text.c_str(), text.size()); |
297 | 7.89k | } |
298 | | |
299 | | void addRectAttributes(const SkRect&); |
300 | | void addPathAttributes(const SkPath&, SkParsePath::PathEncoding); |
301 | | void addTextAttributes(const SkFont&); |
302 | | |
303 | | private: |
304 | | Resources addResources(const MxCp&, const SkPaint& paint); |
305 | | void addShaderResources(const SkPaint& paint, Resources* resources); |
306 | | void addGradientShaderResources(const SkShader* shader, const SkPaint& paint, |
307 | | Resources* resources); |
308 | | void addColorFilterResources(const SkColorFilter& cf, Resources* resources); |
309 | | void addImageShaderResources(const SkShader* shader, const SkPaint& paint, |
310 | | Resources* resources); |
311 | | |
312 | | void addPatternDef(const SkBitmap& bm); |
313 | | |
314 | | void addPaint(const SkPaint& paint, const Resources& resources); |
315 | | |
316 | | |
317 | | SkString addLinearGradientDef(const SkShader::GradientInfo& info, const SkShader* shader); |
318 | | |
319 | | SkXMLWriter* fWriter; |
320 | | ResourceBucket* fResourceBucket; |
321 | | }; |
322 | | |
323 | 29.5k | void SkSVGDevice::AutoElement::addPaint(const SkPaint& paint, const Resources& resources) { |
324 | | // Path effects are applied to all vector graphics (rects, rrects, ovals, |
325 | | // paths etc). This should only happen when a path effect is attached to |
326 | | // non-vector graphics (text, image) or a new vector graphics primitive is |
327 | | //added that is not handled by base drawPath() routine. |
328 | 29.5k | if (paint.getPathEffect() != nullptr) { |
329 | 110 | SkDebugf("Unsupported path effect in addPaint."); |
330 | 110 | } |
331 | 29.5k | SkPaint::Style style = paint.getStyle(); |
332 | 29.5k | if (style == SkPaint::kFill_Style || style == SkPaint::kStrokeAndFill_Style) { |
333 | 28.6k | static constexpr char kDefaultFill[] = "black"; |
334 | 28.6k | if (!resources.fPaintServer.equals(kDefaultFill)) { |
335 | 6.83k | this->addAttribute("fill", resources.fPaintServer); |
336 | | |
337 | 6.83k | if (SK_AlphaOPAQUE != SkColorGetA(paint.getColor())) { |
338 | 5.32k | this->addAttribute("fill-opacity", svg_opacity(paint.getColor())); |
339 | 5.32k | } |
340 | 6.83k | } |
341 | 881 | } else { |
342 | 881 | SkASSERT(style == SkPaint::kStroke_Style); |
343 | 881 | this->addAttribute("fill", "none"); |
344 | 881 | } |
345 | | |
346 | 29.5k | if (!resources.fColorFilter.isEmpty()) { |
347 | 1 | this->addAttribute("filter", resources.fColorFilter.c_str()); |
348 | 1 | } |
349 | | |
350 | 29.5k | if (style == SkPaint::kStroke_Style || style == SkPaint::kStrokeAndFill_Style) { |
351 | 3.23k | this->addAttribute("stroke", resources.fPaintServer); |
352 | | |
353 | 3.23k | SkScalar strokeWidth = paint.getStrokeWidth(); |
354 | 3.23k | if (strokeWidth == 0) { |
355 | | // Hairline stroke |
356 | 2.47k | strokeWidth = 1; |
357 | 2.47k | this->addAttribute("vector-effect", "non-scaling-stroke"); |
358 | 2.47k | } |
359 | 3.23k | this->addAttribute("stroke-width", strokeWidth); |
360 | | |
361 | 3.23k | if (const char* cap = svg_cap(paint.getStrokeCap())) { |
362 | 1.37k | this->addAttribute("stroke-linecap", cap); |
363 | 1.37k | } |
364 | | |
365 | 3.23k | if (const char* join = svg_join(paint.getStrokeJoin())) { |
366 | 909 | this->addAttribute("stroke-linejoin", join); |
367 | 909 | } |
368 | | |
369 | 3.23k | if (paint.getStrokeJoin() == SkPaint::kMiter_Join) { |
370 | 2.32k | this->addAttribute("stroke-miterlimit", paint.getStrokeMiter()); |
371 | 2.32k | } |
372 | | |
373 | 3.23k | if (SK_AlphaOPAQUE != SkColorGetA(paint.getColor())) { |
374 | 2.90k | this->addAttribute("stroke-opacity", svg_opacity(paint.getColor())); |
375 | 2.90k | } |
376 | 26.3k | } else { |
377 | 26.3k | SkASSERT(style == SkPaint::kFill_Style); |
378 | | // SVG default stroke value is "none". |
379 | 26.3k | } |
380 | 29.5k | } |
381 | | |
382 | 29.5k | Resources SkSVGDevice::AutoElement::addResources(const MxCp& mc, const SkPaint& paint) { |
383 | 29.5k | Resources resources(paint); |
384 | | |
385 | 29.5k | if (paint.getShader()) { |
386 | 3.13k | AutoElement defs("defs", fWriter); |
387 | | |
388 | 3.13k | this->addShaderResources(paint, &resources); |
389 | 3.13k | } |
390 | | |
391 | 29.5k | if (const SkColorFilter* cf = paint.getColorFilter()) { |
392 | | // TODO: Implement skia color filters for blend modes other than SrcIn |
393 | 1.58k | SkBlendMode mode; |
394 | 1.58k | if (cf->asAColorMode(nullptr, &mode) && mode == SkBlendMode::kSrcIn) { |
395 | 1 | this->addColorFilterResources(*cf, &resources); |
396 | 1 | } |
397 | 1.58k | } |
398 | | |
399 | 29.5k | return resources; |
400 | 29.5k | } |
401 | | |
402 | | void SkSVGDevice::AutoElement::addGradientShaderResources(const SkShader* shader, |
403 | | const SkPaint& paint, |
404 | 154 | Resources* resources) { |
405 | 154 | SkShader::GradientInfo grInfo; |
406 | 154 | memset(&grInfo, 0, sizeof(grInfo)); |
407 | 154 | if (SkShader::kLinear_GradientType != shader->asAGradient(&grInfo)) { |
408 | | // TODO: non-linear gradient support |
409 | 68 | return; |
410 | 68 | } |
411 | | |
412 | 86 | SkAutoSTArray<16, SkColor> grColors(grInfo.fColorCount); |
413 | 86 | SkAutoSTArray<16, SkScalar> grOffsets(grInfo.fColorCount); |
414 | 86 | grInfo.fColors = grColors.get(); |
415 | 86 | grInfo.fColorOffsets = grOffsets.get(); |
416 | | |
417 | | // One more call to get the actual colors/offsets. |
418 | 86 | shader->asAGradient(&grInfo); |
419 | 86 | SkASSERT(grInfo.fColorCount <= grColors.count()); |
420 | 86 | SkASSERT(grInfo.fColorCount <= grOffsets.count()); |
421 | | |
422 | 86 | resources->fPaintServer.printf("url(#%s)", addLinearGradientDef(grInfo, shader).c_str()); |
423 | 86 | } |
424 | | |
425 | | void SkSVGDevice::AutoElement::addColorFilterResources(const SkColorFilter& cf, |
426 | 1 | Resources* resources) { |
427 | 1 | SkString colorfilterID = fResourceBucket->addColorFilter(); |
428 | 1 | { |
429 | 1 | AutoElement filterElement("filter", fWriter); |
430 | 1 | filterElement.addAttribute("id", colorfilterID); |
431 | 1 | filterElement.addAttribute("x", "0%"); |
432 | 1 | filterElement.addAttribute("y", "0%"); |
433 | 1 | filterElement.addAttribute("width", "100%"); |
434 | 1 | filterElement.addAttribute("height", "100%"); |
435 | | |
436 | 1 | SkColor filterColor; |
437 | 1 | SkBlendMode mode; |
438 | 1 | bool asAColorMode = cf.asAColorMode(&filterColor, &mode); |
439 | 1 | SkAssertResult(asAColorMode); |
440 | 1 | SkASSERT(mode == SkBlendMode::kSrcIn); |
441 | | |
442 | 1 | { |
443 | | // first flood with filter color |
444 | 1 | AutoElement floodElement("feFlood", fWriter); |
445 | 1 | floodElement.addAttribute("flood-color", svg_color(filterColor)); |
446 | 1 | floodElement.addAttribute("flood-opacity", svg_opacity(filterColor)); |
447 | 1 | floodElement.addAttribute("result", "flood"); |
448 | 1 | } |
449 | | |
450 | 1 | { |
451 | | // apply the transform to filter color |
452 | 1 | AutoElement compositeElement("feComposite", fWriter); |
453 | 1 | compositeElement.addAttribute("in", "flood"); |
454 | 1 | compositeElement.addAttribute("operator", "in"); |
455 | 1 | } |
456 | 1 | } |
457 | 1 | resources->fColorFilter.printf("url(#%s)", colorfilterID.c_str()); |
458 | 1 | } |
459 | | |
460 | | namespace { |
461 | 1.35k | bool is_png(const void* bytes, size_t length) { |
462 | 1.35k | constexpr uint8_t kPngSig[] = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A }; |
463 | 1.35k | return length >= sizeof(kPngSig) && !memcmp(bytes, kPngSig, sizeof(kPngSig)); |
464 | 1.35k | } |
465 | | } // namespace |
466 | | |
467 | | // Returns data uri from bytes. |
468 | | // it will use any cached data if available, otherwise will |
469 | | // encode as png. |
470 | 1.35k | sk_sp<SkData> AsDataUri(SkImage* image) { |
471 | 1.35k | sk_sp<SkData> imageData = image->encodeToData(); |
472 | 1.35k | if (!imageData) { |
473 | 0 | return nullptr; |
474 | 0 | } |
475 | | |
476 | 1.35k | const char* selectedPrefix = nullptr; |
477 | 1.35k | size_t selectedPrefixLength = 0; |
478 | | |
479 | | #ifdef SK_CODEC_DECODES_JPEG |
480 | | if (SkJpegCodec::IsJpeg(imageData->data(), imageData->size())) { |
481 | | const static char jpgDataPrefix[] = "data:image/jpeg;base64,"; |
482 | | selectedPrefix = jpgDataPrefix; |
483 | | selectedPrefixLength = sizeof(jpgDataPrefix); |
484 | | } |
485 | | else |
486 | | #endif |
487 | 1.35k | { |
488 | 1.35k | if (!is_png(imageData->data(), imageData->size())) { |
489 | | #ifdef SK_ENCODE_PNG |
490 | | imageData = image->encodeToData(SkEncodedImageFormat::kPNG, 100); |
491 | | #else |
492 | 0 | return nullptr; |
493 | 0 | #endif |
494 | 0 | } |
495 | 1.35k | const static char pngDataPrefix[] = "data:image/png;base64,"; |
496 | 1.35k | selectedPrefix = pngDataPrefix; |
497 | 1.35k | selectedPrefixLength = sizeof(pngDataPrefix); |
498 | 1.35k | } |
499 | | |
500 | 1.35k | size_t b64Size = SkBase64::Encode(imageData->data(), imageData->size(), nullptr); |
501 | 1.35k | sk_sp<SkData> dataUri = SkData::MakeUninitialized(selectedPrefixLength + b64Size); |
502 | 1.35k | char* dest = (char*)dataUri->writable_data(); |
503 | 1.35k | memcpy(dest, selectedPrefix, selectedPrefixLength); |
504 | 1.35k | SkBase64::Encode(imageData->data(), imageData->size(), dest + selectedPrefixLength - 1); |
505 | 1.35k | dest[dataUri->size() - 1] = 0; |
506 | 1.35k | return dataUri; |
507 | 1.35k | } |
508 | | |
509 | | void SkSVGDevice::AutoElement::addImageShaderResources(const SkShader* shader, const SkPaint& paint, |
510 | 1.35k | Resources* resources) { |
511 | 1.35k | SkMatrix outMatrix; |
512 | | |
513 | 1.35k | SkTileMode xy[2]; |
514 | 1.35k | SkImage* image = shader->isAImage(&outMatrix, xy); |
515 | 1.35k | SkASSERT(image); |
516 | | |
517 | 1.35k | SkString patternDims[2]; // width, height |
518 | | |
519 | 1.35k | sk_sp<SkData> dataUri = AsDataUri(image); |
520 | 1.35k | if (!dataUri) { |
521 | 0 | return; |
522 | 0 | } |
523 | 1.35k | SkIRect imageSize = image->bounds(); |
524 | 4.06k | for (int i = 0; i < 2; i++) { |
525 | 1.35k | int imageDimension = i == 0 ? imageSize.width() : imageSize.height(); |
526 | 2.71k | switch (xy[i]) { |
527 | 7 | case SkTileMode::kRepeat: |
528 | 7 | patternDims[i].appendScalar(imageDimension); |
529 | 7 | break; |
530 | 2.70k | default: |
531 | | // TODO: other tile modes? |
532 | 2.70k | patternDims[i] = "100%"; |
533 | 2.71k | } |
534 | 2.71k | } |
535 | | |
536 | 1.35k | SkString patternID = fResourceBucket->addPattern(); |
537 | 1.35k | { |
538 | 1.35k | AutoElement pattern("pattern", fWriter); |
539 | 1.35k | pattern.addAttribute("id", patternID); |
540 | 1.35k | pattern.addAttribute("patternUnits", "userSpaceOnUse"); |
541 | 1.35k | pattern.addAttribute("patternContentUnits", "userSpaceOnUse"); |
542 | 1.35k | pattern.addAttribute("width", patternDims[0]); |
543 | 1.35k | pattern.addAttribute("height", patternDims[1]); |
544 | 1.35k | pattern.addAttribute("x", 0); |
545 | 1.35k | pattern.addAttribute("y", 0); |
546 | | |
547 | 1.35k | { |
548 | 1.35k | SkString imageID = fResourceBucket->addImage(); |
549 | 1.35k | AutoElement imageTag("image", fWriter); |
550 | 1.35k | imageTag.addAttribute("id", imageID); |
551 | 1.35k | imageTag.addAttribute("x", 0); |
552 | 1.35k | imageTag.addAttribute("y", 0); |
553 | 1.35k | imageTag.addAttribute("width", image->width()); |
554 | 1.35k | imageTag.addAttribute("height", image->height()); |
555 | 1.35k | imageTag.addAttribute("xlink:href", static_cast<const char*>(dataUri->data())); |
556 | 1.35k | } |
557 | 1.35k | } |
558 | 1.35k | resources->fPaintServer.printf("url(#%s)", patternID.c_str()); |
559 | 1.35k | } |
560 | | |
561 | 3.13k | void SkSVGDevice::AutoElement::addShaderResources(const SkPaint& paint, Resources* resources) { |
562 | 3.13k | const SkShader* shader = paint.getShader(); |
563 | 3.13k | SkASSERT(shader); |
564 | | |
565 | 3.13k | if (shader->asAGradient(nullptr) != SkShader::kNone_GradientType) { |
566 | 154 | this->addGradientShaderResources(shader, paint, resources); |
567 | 2.98k | } else if (shader->isAImage()) { |
568 | 1.35k | this->addImageShaderResources(shader, paint, resources); |
569 | 1.35k | } |
570 | | // TODO: other shader types? |
571 | 3.13k | } |
572 | | |
573 | | SkString SkSVGDevice::AutoElement::addLinearGradientDef(const SkShader::GradientInfo& info, |
574 | 86 | const SkShader* shader) { |
575 | 86 | SkASSERT(fResourceBucket); |
576 | 86 | SkString id = fResourceBucket->addLinearGradient(); |
577 | | |
578 | 86 | { |
579 | 86 | AutoElement gradient("linearGradient", fWriter); |
580 | | |
581 | 86 | gradient.addAttribute("id", id); |
582 | 86 | gradient.addAttribute("gradientUnits", "userSpaceOnUse"); |
583 | 86 | gradient.addAttribute("x1", info.fPoint[0].x()); |
584 | 86 | gradient.addAttribute("y1", info.fPoint[0].y()); |
585 | 86 | gradient.addAttribute("x2", info.fPoint[1].x()); |
586 | 86 | gradient.addAttribute("y2", info.fPoint[1].y()); |
587 | | |
588 | 86 | if (!as_SB(shader)->getLocalMatrix().isIdentity()) { |
589 | 2 | this->addAttribute("gradientTransform", svg_transform(as_SB(shader)->getLocalMatrix())); |
590 | 2 | } |
591 | | |
592 | 86 | SkASSERT(info.fColorCount >= 2); |
593 | 804 | for (int i = 0; i < info.fColorCount; ++i) { |
594 | 718 | SkColor color = info.fColors[i]; |
595 | 718 | SkString colorStr(svg_color(color)); |
596 | | |
597 | 718 | { |
598 | 718 | AutoElement stop("stop", fWriter); |
599 | 718 | stop.addAttribute("offset", info.fColorOffsets[i]); |
600 | 718 | stop.addAttribute("stop-color", colorStr.c_str()); |
601 | | |
602 | 718 | if (SK_AlphaOPAQUE != SkColorGetA(color)) { |
603 | 576 | stop.addAttribute("stop-opacity", svg_opacity(color)); |
604 | 576 | } |
605 | 718 | } |
606 | 718 | } |
607 | 86 | } |
608 | | |
609 | 86 | return id; |
610 | 86 | } |
611 | | |
612 | 4.85k | void SkSVGDevice::AutoElement::addRectAttributes(const SkRect& rect) { |
613 | | // x, y default to 0 |
614 | 4.85k | if (rect.x() != 0) { |
615 | 2.02k | this->addAttribute("x", rect.x()); |
616 | 2.02k | } |
617 | 4.85k | if (rect.y() != 0) { |
618 | 1.46k | this->addAttribute("y", rect.y()); |
619 | 1.46k | } |
620 | | |
621 | 4.85k | this->addAttribute("width", rect.width()); |
622 | 4.85k | this->addAttribute("height", rect.height()); |
623 | 4.85k | } |
624 | | |
625 | | void SkSVGDevice::AutoElement::addPathAttributes(const SkPath& path, |
626 | 7.51k | SkParsePath::PathEncoding encoding) { |
627 | 7.51k | SkString pathData; |
628 | 7.51k | SkParsePath::ToSVGString(path, &pathData, encoding); |
629 | 7.51k | this->addAttribute("d", pathData); |
630 | 7.51k | } |
631 | | |
632 | 7.89k | void SkSVGDevice::AutoElement::addTextAttributes(const SkFont& font) { |
633 | 7.89k | this->addAttribute("font-size", font.getSize()); |
634 | | |
635 | 7.89k | SkString familyName; |
636 | 7.89k | SkTHashSet<SkString> familySet; |
637 | 7.89k | sk_sp<SkTypeface> tface = font.refTypefaceOrDefault(); |
638 | | |
639 | 7.89k | SkASSERT(tface); |
640 | 7.89k | SkFontStyle style = tface->fontStyle(); |
641 | 7.89k | if (style.slant() == SkFontStyle::kItalic_Slant) { |
642 | 336 | this->addAttribute("font-style", "italic"); |
643 | 7.55k | } else if (style.slant() == SkFontStyle::kOblique_Slant) { |
644 | 0 | this->addAttribute("font-style", "oblique"); |
645 | 0 | } |
646 | 7.89k | int weightIndex = (SkTPin(style.weight(), 100, 900) - 50) / 100; |
647 | 7.89k | if (weightIndex != 3) { |
648 | 335 | static constexpr const char* weights[] = { |
649 | 335 | "100", "200", "300", "normal", "400", "500", "600", "bold", "800", "900" |
650 | 335 | }; |
651 | 335 | this->addAttribute("font-weight", weights[weightIndex]); |
652 | 335 | } |
653 | 7.89k | int stretchIndex = style.width() - 1; |
654 | 7.89k | if (stretchIndex != 4) { |
655 | 0 | static constexpr const char* stretches[] = { |
656 | 0 | "ultra-condensed", "extra-condensed", "condensed", "semi-condensed", |
657 | 0 | "normal", |
658 | 0 | "semi-expanded", "expanded", "extra-expanded", "ultra-expanded" |
659 | 0 | }; |
660 | 0 | this->addAttribute("font-stretch", stretches[stretchIndex]); |
661 | 0 | } |
662 | | |
663 | 7.89k | sk_sp<SkTypeface::LocalizedStrings> familyNameIter(tface->createFamilyNameIterator()); |
664 | 7.89k | SkTypeface::LocalizedString familyString; |
665 | 7.89k | if (familyNameIter) { |
666 | 15.7k | while (familyNameIter->next(&familyString)) { |
667 | 7.89k | if (familySet.contains(familyString.fString)) { |
668 | 0 | continue; |
669 | 0 | } |
670 | 7.89k | familySet.add(familyString.fString); |
671 | 7.89k | familyName.appendf((familyName.isEmpty() ? "%s" : ", %s"), familyString.fString.c_str()); |
672 | 7.89k | } |
673 | 7.89k | } |
674 | 7.89k | if (!familyName.isEmpty()) { |
675 | 7.89k | this->addAttribute("font-family", familyName); |
676 | 7.89k | } |
677 | 7.89k | } |
678 | | |
679 | | sk_sp<SkBaseDevice> SkSVGDevice::Make(const SkISize& size, std::unique_ptr<SkXMLWriter> writer, |
680 | 12.9k | uint32_t flags) { |
681 | 12.9k | return writer ? sk_sp<SkBaseDevice>(new SkSVGDevice(size, std::move(writer), flags)) |
682 | 0 | : nullptr; |
683 | 12.9k | } |
684 | | |
685 | | SkSVGDevice::SkSVGDevice(const SkISize& size, std::unique_ptr<SkXMLWriter> writer, uint32_t flags) |
686 | | : INHERITED(SkImageInfo::MakeUnknown(size.fWidth, size.fHeight), |
687 | | SkSurfaceProps(0, kUnknown_SkPixelGeometry)) |
688 | | , fWriter(std::move(writer)) |
689 | | , fResourceBucket(new ResourceBucket) |
690 | | , fFlags(flags) |
691 | 12.9k | { |
692 | 12.9k | SkASSERT(fWriter); |
693 | | |
694 | 12.9k | fWriter->writeHeader(); |
695 | | |
696 | | // The root <svg> tag gets closed by the destructor. |
697 | 12.9k | fRootElement = std::make_unique<AutoElement>("svg", fWriter); |
698 | | |
699 | 12.9k | fRootElement->addAttribute("xmlns", "http://www.w3.org/2000/svg"); |
700 | 12.9k | fRootElement->addAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink"); |
701 | 12.9k | fRootElement->addAttribute("width", size.width()); |
702 | 12.9k | fRootElement->addAttribute("height", size.height()); |
703 | 12.9k | } |
704 | | |
705 | 12.9k | SkSVGDevice::~SkSVGDevice() { |
706 | | // Pop order is important. |
707 | 19.8k | while (!fClipStack.empty()) { |
708 | 6.85k | fClipStack.pop_back(); |
709 | 6.85k | } |
710 | 12.9k | } |
711 | | |
712 | 7.51k | SkParsePath::PathEncoding SkSVGDevice::pathEncoding() const { |
713 | 7.51k | return (fFlags & SkSVGCanvas::kRelativePathEncoding_Flag) |
714 | 0 | ? SkParsePath::PathEncoding::Relative |
715 | 7.51k | : SkParsePath::PathEncoding::Absolute; |
716 | 7.51k | } |
717 | | |
718 | 29.5k | void SkSVGDevice::syncClipStack(const SkClipStack& cs) { |
719 | 29.5k | SkClipStack::B2TIter iter(cs); |
720 | | |
721 | 29.5k | const SkClipStack::Element* elem; |
722 | 29.5k | size_t rec_idx = 0; |
723 | | |
724 | | // First, find/preserve the common bottom. |
725 | 65.3k | while ((elem = iter.next()) && (rec_idx < fClipStack.size())) { |
726 | 36.2k | if (fClipStack[SkToInt(rec_idx)].fGenID != elem->getGenID()) { |
727 | 508 | break; |
728 | 508 | } |
729 | 35.7k | rec_idx++; |
730 | 35.7k | } |
731 | | |
732 | | // Discard out-of-date stack top. |
733 | 30.5k | while (fClipStack.size() > rec_idx) { |
734 | 1.01k | fClipStack.pop_back(); |
735 | 1.01k | } |
736 | | |
737 | 7.86k | auto define_clip = [this](const SkClipStack::Element* e) { |
738 | 7.86k | const auto cid = SkStringPrintf("cl_%x", e->getGenID()); |
739 | | |
740 | 7.86k | AutoElement clip_path("clipPath", fWriter); |
741 | 7.86k | clip_path.addAttribute("id", cid); |
742 | | |
743 | | // TODO: handle non-intersect clips. |
744 | | |
745 | 7.86k | switch (e->getDeviceSpaceType()) { |
746 | 87 | case SkClipStack::Element::DeviceSpaceType::kEmpty: { |
747 | | // TODO: can we skip this? |
748 | 87 | AutoElement rect("rect", fWriter); |
749 | 87 | } break; |
750 | 2.40k | case SkClipStack::Element::DeviceSpaceType::kRect: { |
751 | 2.40k | AutoElement rect("rect", fWriter); |
752 | 2.40k | rect.addRectAttributes(e->getDeviceSpaceRect()); |
753 | 2.40k | } break; |
754 | 1.28k | case SkClipStack::Element::DeviceSpaceType::kRRect: { |
755 | | // TODO: complex rrect handling? |
756 | 1.28k | const auto& rr = e->getDeviceSpaceRRect(); |
757 | 1.28k | const auto radii = rr.getSimpleRadii(); |
758 | | |
759 | 1.28k | AutoElement rrect("rect", fWriter); |
760 | 1.28k | rrect.addRectAttributes(rr.rect()); |
761 | 1.28k | rrect.addAttribute("rx", radii.x()); |
762 | 1.28k | rrect.addAttribute("ry", radii.y()); |
763 | 1.28k | } break; |
764 | 4.09k | case SkClipStack::Element::DeviceSpaceType::kPath: { |
765 | 4.09k | const auto& p = e->getDeviceSpacePath(); |
766 | 4.09k | AutoElement path("path", fWriter); |
767 | 4.09k | path.addPathAttributes(p, this->pathEncoding()); |
768 | 4.09k | if (p.getFillType() == SkPathFillType::kEvenOdd) { |
769 | 48 | path.addAttribute("clip-rule", "evenodd"); |
770 | 48 | } |
771 | 4.09k | } break; |
772 | 0 | case SkClipStack::Element::DeviceSpaceType::kShader: |
773 | | // TODO: handle shader clipping, perhaps rasterize and apply as a mask image? |
774 | 0 | break; |
775 | 7.86k | } |
776 | | |
777 | 7.86k | return cid; |
778 | 7.86k | }; |
779 | | |
780 | | // Rebuild the top. |
781 | 37.3k | while (elem) { |
782 | 7.86k | const auto cid = define_clip(elem); |
783 | | |
784 | 7.86k | auto clip_grp = std::make_unique<AutoElement>("g", fWriter); |
785 | 7.86k | clip_grp->addAttribute("clip-path", SkStringPrintf("url(#%s)", cid.c_str())); |
786 | | |
787 | 7.86k | fClipStack.push_back({ std::move(clip_grp), elem->getGenID() }); |
788 | | |
789 | 7.86k | elem = iter.next(); |
790 | 7.86k | } |
791 | 29.5k | } |
792 | | |
793 | 454 | void SkSVGDevice::drawPaint(const SkPaint& paint) { |
794 | 454 | AutoElement rect("rect", this, fResourceBucket.get(), MxCp(this), paint); |
795 | 454 | rect.addRectAttributes(SkRect::MakeWH(SkIntToScalar(this->width()), |
796 | 454 | SkIntToScalar(this->height()))); |
797 | 454 | } |
798 | | |
799 | 0 | void SkSVGDevice::drawAnnotation(const SkRect& rect, const char key[], SkData* value) { |
800 | 0 | if (!value) { |
801 | 0 | return; |
802 | 0 | } |
803 | | |
804 | 0 | if (!strcmp(SkAnnotationKeys::URL_Key(), key) || |
805 | 0 | !strcmp(SkAnnotationKeys::Link_Named_Dest_Key(), key)) { |
806 | 0 | this->cs().save(); |
807 | 0 | this->cs().clipRect(rect, this->localToDevice(), SkClipOp::kIntersect, true); |
808 | 0 | SkRect transformedRect = this->cs().bounds(this->getGlobalBounds()); |
809 | 0 | this->cs().restore(); |
810 | 0 | if (transformedRect.isEmpty()) { |
811 | 0 | return; |
812 | 0 | } |
813 | | |
814 | 0 | SkString url(static_cast<const char*>(value->data()), value->size() - 1); |
815 | 0 | AutoElement a("a", fWriter); |
816 | 0 | a.addAttribute("xlink:href", url.c_str()); |
817 | 0 | { |
818 | 0 | AutoElement r("rect", fWriter); |
819 | 0 | r.addAttribute("fill-opacity", "0.0"); |
820 | 0 | r.addRectAttributes(transformedRect); |
821 | 0 | } |
822 | 0 | } |
823 | 0 | } |
824 | | |
825 | | void SkSVGDevice::drawPoints(SkCanvas::PointMode mode, size_t count, |
826 | 635 | const SkPoint pts[], const SkPaint& paint) { |
827 | 635 | SkPathBuilder path; |
828 | | |
829 | 635 | switch (mode) { |
830 | | // todo |
831 | 153 | case SkCanvas::kPoints_PointMode: |
832 | | // TODO? |
833 | 153 | break; |
834 | 267 | case SkCanvas::kLines_PointMode: |
835 | 267 | count -= 1; |
836 | 538 | for (size_t i = 0; i < count; i += 2) { |
837 | 271 | path.moveTo(pts[i]); |
838 | 271 | path.lineTo(pts[i+1]); |
839 | 271 | } |
840 | 267 | break; |
841 | 215 | case SkCanvas::kPolygon_PointMode: |
842 | 215 | if (count > 1) { |
843 | 214 | path.addPolygon(pts, SkToInt(count), false); |
844 | 214 | } |
845 | 215 | break; |
846 | 635 | } |
847 | | |
848 | 635 | this->drawPath(path.detach(), paint, true); |
849 | 635 | } |
850 | | |
851 | 714 | void SkSVGDevice::drawRect(const SkRect& r, const SkPaint& paint) { |
852 | 714 | std::unique_ptr<AutoElement> svg; |
853 | 714 | if (RequiresViewportReset(paint)) { |
854 | 0 | svg = std::make_unique<AutoElement>("svg", this, fResourceBucket.get(), MxCp(this), paint); |
855 | 0 | svg->addRectAttributes(r); |
856 | 0 | } |
857 | | |
858 | 714 | AutoElement rect("rect", this, fResourceBucket.get(), MxCp(this), paint); |
859 | | |
860 | 714 | if (svg) { |
861 | 0 | rect.addAttribute("x", 0); |
862 | 0 | rect.addAttribute("y", 0); |
863 | 0 | rect.addAttribute("width", "100%"); |
864 | 0 | rect.addAttribute("height", "100%"); |
865 | 714 | } else { |
866 | 714 | rect.addRectAttributes(r); |
867 | 714 | } |
868 | 714 | } |
869 | | |
870 | 363 | void SkSVGDevice::drawOval(const SkRect& oval, const SkPaint& paint) { |
871 | 363 | AutoElement ellipse("ellipse", this, fResourceBucket.get(), MxCp(this), paint); |
872 | 363 | ellipse.addAttribute("cx", oval.centerX()); |
873 | 363 | ellipse.addAttribute("cy", oval.centerY()); |
874 | 363 | ellipse.addAttribute("rx", oval.width() / 2); |
875 | 363 | ellipse.addAttribute("ry", oval.height() / 2); |
876 | 363 | } |
877 | | |
878 | 301 | void SkSVGDevice::drawRRect(const SkRRect& rr, const SkPaint& paint) { |
879 | 301 | AutoElement elem("path", this, fResourceBucket.get(), MxCp(this), paint); |
880 | 301 | elem.addPathAttributes(SkPath::RRect(rr), this->pathEncoding()); |
881 | 301 | } |
882 | | |
883 | 3.29k | void SkSVGDevice::drawPath(const SkPath& path, const SkPaint& paint, bool pathIsMutable) { |
884 | 3.29k | if (path.isInverseFillType()) { |
885 | 170 | SkDebugf("Inverse path fill type not yet implemented."); |
886 | 170 | return; |
887 | 170 | } |
888 | | |
889 | 3.12k | SkPath pathStorage; |
890 | 3.12k | SkPath* pathPtr = const_cast<SkPath*>(&path); |
891 | 3.12k | SkTCopyOnFirstWrite<SkPaint> path_paint(paint); |
892 | | |
893 | | // Apply path effect from paint to path. |
894 | 3.12k | if (path_paint->getPathEffect()) { |
895 | 2.16k | if (!pathIsMutable) { |
896 | 1.98k | pathPtr = &pathStorage; |
897 | 1.98k | } |
898 | 2.16k | bool fill = path_paint->getFillPath(path, pathPtr); |
899 | 2.16k | if (fill) { |
900 | | // Path should be filled. |
901 | 1.88k | path_paint.writable()->setStyle(SkPaint::kFill_Style); |
902 | 282 | } else { |
903 | | // Path should be drawn with a hairline (width == 0). |
904 | 282 | path_paint.writable()->setStyle(SkPaint::kStroke_Style); |
905 | 282 | path_paint.writable()->setStrokeWidth(0); |
906 | 282 | } |
907 | | |
908 | 2.16k | path_paint.writable()->setPathEffect(nullptr); // path effect processed |
909 | 2.16k | } |
910 | | |
911 | | // Create path element. |
912 | 3.12k | AutoElement elem("path", this, fResourceBucket.get(), MxCp(this), *path_paint); |
913 | 3.12k | elem.addPathAttributes(*pathPtr, this->pathEncoding()); |
914 | | |
915 | | // TODO: inverse fill types? |
916 | 3.12k | if (pathPtr->getFillType() == SkPathFillType::kEvenOdd) { |
917 | 12 | elem.addAttribute("fill-rule", "evenodd"); |
918 | 12 | } |
919 | 3.12k | } |
920 | | |
921 | 16.6k | static sk_sp<SkData> encode(const SkBitmap& src) { |
922 | 16.6k | SkDynamicMemoryWStream buf; |
923 | 16.6k | return SkEncodeImage(&buf, src, SkEncodedImageFormat::kPNG, 80) ? buf.detachAsData() : nullptr; |
924 | 16.6k | } |
925 | | |
926 | 16.6k | void SkSVGDevice::drawBitmapCommon(const MxCp& mc, const SkBitmap& bm, const SkPaint& paint) { |
927 | 16.6k | sk_sp<SkData> pngData = encode(bm); |
928 | 16.6k | if (!pngData) { |
929 | 0 | return; |
930 | 0 | } |
931 | | |
932 | 16.6k | size_t b64Size = SkBase64::Encode(pngData->data(), pngData->size(), nullptr); |
933 | 16.6k | SkAutoTMalloc<char> b64Data(b64Size); |
934 | 16.6k | SkBase64::Encode(pngData->data(), pngData->size(), b64Data.get()); |
935 | | |
936 | 16.6k | SkString svgImageData("data:image/png;base64,"); |
937 | 16.6k | svgImageData.append(b64Data.get(), b64Size); |
938 | | |
939 | 16.6k | SkString imageID = fResourceBucket->addImage(); |
940 | 16.6k | { |
941 | 16.6k | AutoElement defs("defs", fWriter); |
942 | 16.6k | { |
943 | 16.6k | AutoElement image("image", fWriter); |
944 | 16.6k | image.addAttribute("id", imageID); |
945 | 16.6k | image.addAttribute("width", bm.width()); |
946 | 16.6k | image.addAttribute("height", bm.height()); |
947 | 16.6k | image.addAttribute("xlink:href", svgImageData); |
948 | 16.6k | } |
949 | 16.6k | } |
950 | | |
951 | 16.6k | { |
952 | 16.6k | AutoElement imageUse("use", this, fResourceBucket.get(), mc, paint); |
953 | 16.6k | imageUse.addAttribute("xlink:href", SkStringPrintf("#%s", imageID.c_str())); |
954 | 16.6k | } |
955 | 16.6k | } |
956 | | |
957 | | void SkSVGDevice::drawImageRect(const SkImage* image, const SkRect* src, const SkRect& dst, |
958 | | const SkSamplingOptions& sampling, const SkPaint& paint, |
959 | 16.6k | SkCanvas::SrcRectConstraint constraint) { |
960 | 16.6k | SkBitmap bm; |
961 | | // TODO: support gpu images |
962 | 16.6k | if (!as_IB(image)->getROPixels(nullptr, &bm)) { |
963 | 0 | return; |
964 | 0 | } |
965 | | |
966 | 16.6k | SkClipStack* cs = &this->cs(); |
967 | 16.6k | SkClipStack::AutoRestore ar(cs, false); |
968 | 16.6k | if (src && *src != SkRect::Make(bm.bounds())) { |
969 | 181 | cs->save(); |
970 | 181 | cs->clipRect(dst, this->localToDevice(), SkClipOp::kIntersect, paint.isAntiAlias()); |
971 | 181 | } |
972 | | |
973 | 16.6k | SkMatrix adjustedMatrix = this->localToDevice() |
974 | 16.3k | * SkMatrix::RectToRect(src ? *src : SkRect::Make(bm.bounds()), dst); |
975 | | |
976 | 16.6k | drawBitmapCommon(MxCp(&adjustedMatrix, cs), bm, paint); |
977 | 16.6k | } |
978 | | |
979 | | class SVGTextBuilder : SkNoncopyable { |
980 | | public: |
981 | | SVGTextBuilder(SkPoint origin, const SkGlyphRun& glyphRun) |
982 | 7.89k | : fOrigin(origin) { |
983 | 7.89k | auto runSize = glyphRun.runSize(); |
984 | 7.89k | SkAutoSTArray<64, SkUnichar> unichars(runSize); |
985 | 7.89k | SkFontPriv::GlyphsToUnichars(glyphRun.font(), glyphRun.glyphsIDs().data(), |
986 | 7.89k | runSize, unichars.get()); |
987 | 7.89k | auto positions = glyphRun.positions(); |
988 | 75.1k | for (size_t i = 0; i < runSize; ++i) { |
989 | 67.3k | this->appendUnichar(unichars[i], positions[i]); |
990 | 67.3k | } |
991 | 7.89k | } |
992 | | |
993 | 7.89k | const SkString& text() const { return fText; } |
994 | 7.89k | const SkString& posX() const { return fPosXStr; } |
995 | 7.89k | const SkString& posY() const { return fHasConstY ? fConstYStr : fPosYStr; } |
996 | | |
997 | | private: |
998 | 67.3k | void appendUnichar(SkUnichar c, SkPoint position) { |
999 | 67.3k | bool discardPos = false; |
1000 | 67.3k | bool isWhitespace = false; |
1001 | | |
1002 | 67.3k | switch(c) { |
1003 | 40.1k | case ' ': |
1004 | 40.1k | case '\t': |
1005 | | // consolidate whitespace to match SVG's xml:space=default munging |
1006 | | // (http://www.w3.org/TR/SVG/text.html#WhiteSpace) |
1007 | 40.1k | if (fLastCharWasWhitespace) { |
1008 | 38.2k | discardPos = true; |
1009 | 1.91k | } else { |
1010 | 1.91k | fText.appendUnichar(c); |
1011 | 1.91k | } |
1012 | 40.1k | isWhitespace = true; |
1013 | 40.1k | break; |
1014 | 21.8k | case '\0': |
1015 | | // SkPaint::glyphsToUnichars() returns \0 for inconvertible glyphs, but these |
1016 | | // are not legal XML characters (http://www.w3.org/TR/REC-xml/#charsets) |
1017 | 21.8k | discardPos = true; |
1018 | 21.8k | isWhitespace = fLastCharWasWhitespace; // preserve whitespace consolidation |
1019 | 21.8k | break; |
1020 | 217 | case '&': |
1021 | 217 | fText.append("&"); |
1022 | 217 | break; |
1023 | 221 | case '"': |
1024 | 221 | fText.append("""); |
1025 | 221 | break; |
1026 | 58 | case '\'': |
1027 | 58 | fText.append("'"); |
1028 | 58 | break; |
1029 | 64 | case '<': |
1030 | 64 | fText.append("<"); |
1031 | 64 | break; |
1032 | 74 | case '>': |
1033 | 74 | fText.append(">"); |
1034 | 74 | break; |
1035 | 4.70k | default: |
1036 | 4.70k | fText.appendUnichar(c); |
1037 | 4.70k | break; |
1038 | 67.3k | } |
1039 | | |
1040 | 67.3k | fLastCharWasWhitespace = isWhitespace; |
1041 | | |
1042 | 67.3k | if (discardPos) { |
1043 | 60.0k | return; |
1044 | 60.0k | } |
1045 | | |
1046 | 7.24k | position += fOrigin; |
1047 | 7.24k | fPosXStr.appendf("%.8g, ", position.fX); |
1048 | 7.24k | fPosYStr.appendf("%.8g, ", position.fY); |
1049 | | |
1050 | 7.24k | if (fConstYStr.isEmpty()) { |
1051 | 1.76k | fConstYStr = fPosYStr; |
1052 | 1.76k | fConstY = position.fY; |
1053 | 5.48k | } else { |
1054 | 5.48k | fHasConstY &= SkScalarNearlyEqual(fConstY, position.fY); |
1055 | 5.48k | } |
1056 | 7.24k | } |
1057 | | |
1058 | | const SkPoint fOrigin; |
1059 | | |
1060 | | SkString fText, |
1061 | | fPosXStr, fPosYStr, |
1062 | | fConstYStr; |
1063 | | SkScalar fConstY; |
1064 | | bool fLastCharWasWhitespace = true, // start off in whitespace mode to strip leading space |
1065 | | fHasConstY = true; |
1066 | | }; |
1067 | | |
1068 | 2.75k | void SkSVGDevice::onDrawGlyphRunList(const SkGlyphRunList& glyphRunList, const SkPaint& paint) { |
1069 | 2.75k | SkASSERT(!glyphRunList.hasRSXForm()); |
1070 | 2.75k | const auto draw_as_path = (fFlags & SkSVGCanvas::kConvertTextToPaths_Flag) || |
1071 | 2.75k | paint.getPathEffect(); |
1072 | | |
1073 | 2.75k | if (draw_as_path) { |
1074 | | // Emit a single <path> element. |
1075 | 556 | SkPath path; |
1076 | 1.03k | for (auto& glyphRun : glyphRunList) { |
1077 | 1.03k | AddPath(glyphRun, glyphRunList.origin(), &path); |
1078 | 1.03k | } |
1079 | | |
1080 | 556 | this->drawPath(path, paint); |
1081 | | |
1082 | 556 | return; |
1083 | 556 | } |
1084 | | |
1085 | | // Emit one <text> element for each run. |
1086 | 7.89k | for (auto& glyphRun : glyphRunList) { |
1087 | 7.89k | AutoElement elem("text", this, fResourceBucket.get(), MxCp(this), paint); |
1088 | 7.89k | elem.addTextAttributes(glyphRun.font()); |
1089 | | |
1090 | 7.89k | SVGTextBuilder builder(glyphRunList.origin(), glyphRun); |
1091 | 7.89k | elem.addAttribute("x", builder.posX()); |
1092 | 7.89k | elem.addAttribute("y", builder.posY()); |
1093 | 7.89k | elem.addText(builder.text()); |
1094 | 7.89k | } |
1095 | 2.20k | } |
1096 | | |
1097 | 46.6k | void SkSVGDevice::drawVertices(const SkVertices*, SkBlendMode, const SkPaint&) { |
1098 | | // todo |
1099 | 46.6k | } |