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