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