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