1 /*
2  * Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved.
3 
4  * Permission is hereby granted, free of charge, to any person obtaining a copy
5  * of this software and associated documentation files (the "Software"), to deal
6  * in the Software without restriction, including without limitation the rights
7  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8  * copies of the Software, and to permit persons to whom the Software is
9  * furnished to do so, subject to the following conditions:
10 
11  * The above copyright notice and this permission notice shall be included in all
12  * copies or substantial portions of the Software.
13 
14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20  * SOFTWARE.
21  */
22 
23 #include "../../lv_conf_internal.h"
24 #if LV_USE_THORVG_INTERNAL
25 
26 #include "tvgMath.h" /* to include math.h before cstring */
27 #include <cstring>
28 #include <string>
29 #include "tvgShape.h"
30 #include "tvgCompressor.h"
31 #include "tvgPaint.h"
32 #include "tvgFill.h"
33 #include "tvgStr.h"
34 #include "tvgSvgLoaderCommon.h"
35 #include "tvgSvgSceneBuilder.h"
36 #include "tvgSvgPath.h"
37 #include "tvgSvgUtil.h"
38 
39 /************************************************************************/
40 /* Internal Class Implementation                                        */
41 /************************************************************************/
42 
43 static bool _appendShape(SvgLoaderData& loaderData, SvgNode* node, Shape* shape, const Box& vBox, const string& svgPath);
44 static bool _appendClipShape(SvgLoaderData& loaderData, SvgNode* node, Shape* shape, const Box& vBox, const string& svgPath, const Matrix* transform);
45 static unique_ptr<Scene> _sceneBuildHelper(SvgLoaderData& loaderData, const SvgNode* node, const Box& vBox, const string& svgPath, bool mask, int depth, bool* isMaskWhite = nullptr);
46 
47 
_isGroupType(SvgNodeType type)48 static inline bool _isGroupType(SvgNodeType type)
49 {
50     if (type == SvgNodeType::Doc || type == SvgNodeType::G || type == SvgNodeType::Use || type == SvgNodeType::ClipPath || type == SvgNodeType::Symbol) return true;
51     return false;
52 }
53 
54 
55 //According to: https://www.w3.org/TR/SVG11/coords.html#ObjectBoundingBoxUnits (the last paragraph)
56 //a stroke width should be ignored for bounding box calculations
_boundingBox(const Shape * shape)57 static Box _boundingBox(const Shape* shape)
58 {
59     float x, y, w, h;
60     shape->bounds(&x, &y, &w, &h, false);
61 
62     if (auto strokeW = shape->strokeWidth()) {
63         x += 0.5f * strokeW;
64         y += 0.5f * strokeW;
65         w -= strokeW;
66         h -= strokeW;
67     }
68 
69     return {x, y, w, h};
70 }
71 
72 
_boundingBox(const Text * text)73 static Box _boundingBox(const Text* text)
74 {
75     float x, y, w, h;
76     text->bounds(&x, &y, &w, &h, false);
77     return {x, y, w, h};
78 }
79 
80 
_transformMultiply(const Matrix * mBBox,Matrix * gradTransf)81 static void _transformMultiply(const Matrix* mBBox, Matrix* gradTransf)
82 {
83     gradTransf->e13 = gradTransf->e13 * mBBox->e11 + mBBox->e13;
84     gradTransf->e12 *= mBBox->e11;
85     gradTransf->e11 *= mBBox->e11;
86 
87     gradTransf->e23 = gradTransf->e23 * mBBox->e22 + mBBox->e23;
88     gradTransf->e22 *= mBBox->e22;
89     gradTransf->e21 *= mBBox->e22;
90 }
91 
92 
_applyLinearGradientProperty(SvgStyleGradient * g,const Box & vBox,int opacity)93 static unique_ptr<LinearGradient> _applyLinearGradientProperty(SvgStyleGradient* g, const Box& vBox, int opacity)
94 {
95     Fill::ColorStop* stops;
96     int stopCount = 0;
97     auto fillGrad = LinearGradient::gen();
98 
99     bool isTransform = (g->transform ? true : false);
100     Matrix finalTransform = {1, 0, 0, 0, 1, 0, 0, 0, 1};
101     if (isTransform) finalTransform = *g->transform;
102 
103     if (g->userSpace) {
104         g->linear->x1 = g->linear->x1 * vBox.w;
105         g->linear->y1 = g->linear->y1 * vBox.h;
106         g->linear->x2 = g->linear->x2 * vBox.w;
107         g->linear->y2 = g->linear->y2 * vBox.h;
108     } else {
109         Matrix m = {vBox.w, 0, vBox.x, 0, vBox.h, vBox.y, 0, 0, 1};
110         if (isTransform) _transformMultiply(&m, &finalTransform);
111         else {
112             finalTransform = m;
113             isTransform = true;
114         }
115     }
116 
117     if (isTransform) fillGrad->transform(finalTransform);
118 
119     fillGrad->linear(g->linear->x1, g->linear->y1, g->linear->x2, g->linear->y2);
120     fillGrad->spread(g->spread);
121 
122     //Update the stops
123     stopCount = g->stops.count;
124     if (stopCount > 0) {
125         stops = (Fill::ColorStop*)calloc(stopCount, sizeof(Fill::ColorStop));
126         if (!stops) return fillGrad;
127         auto prevOffset = 0.0f;
128         for (uint32_t i = 0; i < g->stops.count; ++i) {
129             auto colorStop = &g->stops[i];
130             //Use premultiplied color
131             stops[i].r = colorStop->r;
132             stops[i].g = colorStop->g;
133             stops[i].b = colorStop->b;
134             stops[i].a = static_cast<uint8_t>((colorStop->a * opacity) / 255);
135             stops[i].offset = colorStop->offset;
136             //check the offset corner cases - refer to: https://svgwg.org/svg2-draft/pservers.html#StopNotes
137             if (colorStop->offset < prevOffset) stops[i].offset = prevOffset;
138             else if (colorStop->offset > 1) stops[i].offset = 1;
139             prevOffset = stops[i].offset;
140         }
141         fillGrad->colorStops(stops, stopCount);
142         free(stops);
143     }
144     return fillGrad;
145 }
146 
147 
_applyRadialGradientProperty(SvgStyleGradient * g,const Box & vBox,int opacity)148 static unique_ptr<RadialGradient> _applyRadialGradientProperty(SvgStyleGradient* g, const Box& vBox, int opacity)
149 {
150     Fill::ColorStop *stops;
151     int stopCount = 0;
152     auto fillGrad = RadialGradient::gen();
153 
154     bool isTransform = (g->transform ? true : false);
155     Matrix finalTransform = {1, 0, 0, 0, 1, 0, 0, 0, 1};
156     if (isTransform) finalTransform = *g->transform;
157 
158     if (g->userSpace) {
159         //The radius scaling is done according to the Units section:
160         //https://www.w3.org/TR/2015/WD-SVG2-20150915/coords.html
161         g->radial->cx = g->radial->cx * vBox.w;
162         g->radial->cy = g->radial->cy * vBox.h;
163         g->radial->r = g->radial->r * sqrtf(powf(vBox.w, 2.0f) + powf(vBox.h, 2.0f)) / sqrtf(2.0f);
164         g->radial->fx = g->radial->fx * vBox.w;
165         g->radial->fy = g->radial->fy * vBox.h;
166         g->radial->fr = g->radial->fr * sqrtf(powf(vBox.w, 2.0f) + powf(vBox.h, 2.0f)) / sqrtf(2.0f);
167     } else {
168         Matrix m = {vBox.w, 0, vBox.x, 0, vBox.h, vBox.y, 0, 0, 1};
169         if (isTransform) _transformMultiply(&m, &finalTransform);
170         else {
171             finalTransform = m;
172             isTransform = true;
173         }
174     }
175 
176     if (isTransform) fillGrad->transform(finalTransform);
177 
178     P(fillGrad)->radial(g->radial->cx, g->radial->cy, g->radial->r, g->radial->fx, g->radial->fy, g->radial->fr);
179     fillGrad->spread(g->spread);
180 
181     //Update the stops
182     stopCount = g->stops.count;
183     if (stopCount > 0) {
184         stops = (Fill::ColorStop*)calloc(stopCount, sizeof(Fill::ColorStop));
185         if (!stops) return fillGrad;
186         auto prevOffset = 0.0f;
187         for (uint32_t i = 0; i < g->stops.count; ++i) {
188             auto colorStop = &g->stops[i];
189             //Use premultiplied color
190             stops[i].r = colorStop->r;
191             stops[i].g = colorStop->g;
192             stops[i].b = colorStop->b;
193             stops[i].a = static_cast<uint8_t>((colorStop->a * opacity) / 255);
194             stops[i].offset = colorStop->offset;
195             //check the offset corner cases - refer to: https://svgwg.org/svg2-draft/pservers.html#StopNotes
196             if (colorStop->offset < prevOffset) stops[i].offset = prevOffset;
197             else if (colorStop->offset > 1) stops[i].offset = 1;
198             prevOffset = stops[i].offset;
199         }
200         fillGrad->colorStops(stops, stopCount);
201         free(stops);
202     }
203     return fillGrad;
204 }
205 
206 
207 //The SVG standard allows only for 'use' nodes that point directly to a basic shape.
_appendClipUseNode(SvgLoaderData & loaderData,SvgNode * node,Shape * shape,const Box & vBox,const string & svgPath)208 static bool _appendClipUseNode(SvgLoaderData& loaderData, SvgNode* node, Shape* shape, const Box& vBox, const string& svgPath)
209 {
210     if (node->child.count != 1) return false;
211     auto child = *(node->child.data);
212 
213     Matrix finalTransform = {1, 0, 0, 0, 1, 0, 0, 0, 1};
214     if (node->transform) finalTransform = *node->transform;
215     if (node->node.use.x != 0.0f || node->node.use.y != 0.0f) {
216         Matrix m = {1, 0, node->node.use.x, 0, 1, node->node.use.y, 0, 0, 1};
217         finalTransform *= m;
218     }
219     if (child->transform) finalTransform = *child->transform * finalTransform;
220 
221     return _appendClipShape(loaderData, child, shape, vBox, svgPath, identity((const Matrix*)(&finalTransform)) ? nullptr : &finalTransform);
222 }
223 
224 
_appendClipChild(SvgLoaderData & loaderData,SvgNode * node,Shape * shape,const Box & vBox,const string & svgPath,bool clip)225 static bool _appendClipChild(SvgLoaderData& loaderData, SvgNode* node, Shape* shape, const Box& vBox, const string& svgPath, bool clip)
226 {
227     if (node->type == SvgNodeType::Use) {
228         return _appendClipUseNode(loaderData, node, shape, vBox, svgPath);
229     }
230     return _appendClipShape(loaderData, node, shape, vBox, svgPath, nullptr);
231 }
232 
233 
_compositionTransform(Paint * paint,const SvgNode * node,const SvgNode * compNode,SvgNodeType type)234 static Matrix _compositionTransform(Paint* paint, const SvgNode* node, const SvgNode* compNode, SvgNodeType type)
235 {
236     Matrix m = {1, 0, 0, 0, 1, 0, 0, 0, 1};
237     //The initial mask transformation ignored according to the SVG standard.
238     if (node->transform && type != SvgNodeType::Mask) {
239         m = *node->transform;
240     }
241     if (compNode->transform) {
242         m *= *compNode->transform;
243     }
244     if (!compNode->node.clip.userSpace) {
245         float x, y, w, h;
246         P(paint)->bounds(&x, &y, &w, &h, false, false);
247         Matrix mBBox = {w, 0, x, 0, h, y, 0, 0, 1};
248         m *= mBBox;
249     }
250     return m;
251 }
252 
253 
_applyComposition(SvgLoaderData & loaderData,Paint * paint,const SvgNode * node,const Box & vBox,const string & svgPath)254 static void _applyComposition(SvgLoaderData& loaderData, Paint* paint, const SvgNode* node, const Box& vBox, const string& svgPath)
255 {
256     /* ClipPath */
257     /* Do not drop in Circular Dependency for ClipPath.
258        Composition can be applied recursively if its children nodes have composition target to this one. */
259     if (node->style->clipPath.applying) {
260         TVGLOG("SVG", "Multiple Composition Tried! Check out Circular dependency?");
261     } else {
262         auto compNode = node->style->clipPath.node;
263         if (compNode && compNode->child.count > 0) {
264             node->style->clipPath.applying = true;
265 
266             auto comp = Shape::gen();
267 
268             auto child = compNode->child.data;
269             auto valid = false; //Composite only when valid shapes exist
270 
271             for (uint32_t i = 0; i < compNode->child.count; ++i, ++child) {
272                 if (_appendClipChild(loaderData, *child, comp.get(), vBox, svgPath, compNode->child.count > 1)) valid = true;
273             }
274 
275             if (valid) {
276                 Matrix finalTransform = _compositionTransform(paint, node, compNode, SvgNodeType::ClipPath);
277                 comp->transform(finalTransform);
278                 paint->clip(std::move(comp));
279             }
280 
281             node->style->clipPath.applying = false;
282         }
283     }
284 
285     /* Mask */
286     /* Do not drop in Circular Dependency for Mask.
287        Composition can be applied recursively if its children nodes have composition target to this one. */
288     if (node->style->mask.applying) {
289         TVGLOG("SVG", "Multiple Composition Tried! Check out Circular dependency?");
290     } else {
291         auto compNode = node->style->mask.node;
292         if (compNode && compNode->child.count > 0) {
293             node->style->mask.applying = true;
294 
295             bool isMaskWhite = true;
296             if (auto comp = _sceneBuildHelper(loaderData, compNode, vBox, svgPath, true, 0, &isMaskWhite)) {
297                 if (!compNode->node.mask.userSpace) {
298                     Matrix finalTransform = _compositionTransform(paint, node, compNode, SvgNodeType::Mask);
299                     comp->transform(finalTransform);
300                 } else {
301                     if (node->transform) comp->transform(*node->transform);
302                 }
303 
304                 if (compNode->node.mask.type == SvgMaskType::Luminance && !isMaskWhite) {
305                     paint->composite(std::move(comp), CompositeMethod::LumaMask);
306                 } else {
307                     paint->composite(std::move(comp), CompositeMethod::AlphaMask);
308                 }
309             }
310 
311             node->style->mask.applying = false;
312         }
313     }
314 }
315 
316 
_applyProperty(SvgLoaderData & loaderData,SvgNode * node,Shape * vg,const Box & vBox,const string & svgPath,bool clip)317 static void _applyProperty(SvgLoaderData& loaderData, SvgNode* node, Shape* vg, const Box& vBox, const string& svgPath, bool clip)
318 {
319     SvgStyleProperty* style = node->style;
320 
321     //Clip transformation is applied directly to the path in the _appendClipShape function
322     if (node->transform && !clip) vg->transform(*node->transform);
323     if (node->type == SvgNodeType::Doc || !node->style->display) return;
324 
325     //If fill property is nullptr then do nothing
326     if (style->fill.paint.none) {
327         //Do nothing
328     } else if (style->fill.paint.gradient) {
329         Box bBox = vBox;
330         if (!style->fill.paint.gradient->userSpace) bBox = _boundingBox(vg);
331 
332         if (style->fill.paint.gradient->type == SvgGradientType::Linear) {
333             auto linear = _applyLinearGradientProperty(style->fill.paint.gradient, bBox, style->fill.opacity);
334             vg->fill(std::move(linear));
335         } else if (style->fill.paint.gradient->type == SvgGradientType::Radial) {
336             auto radial = _applyRadialGradientProperty(style->fill.paint.gradient, bBox, style->fill.opacity);
337             vg->fill(std::move(radial));
338         }
339     } else if (style->fill.paint.url) {
340         //TODO: Apply the color pointed by url
341         TVGLOG("SVG", "The fill's url not supported.");
342     } else if (style->fill.paint.curColor) {
343         //Apply the current style color
344         vg->fill(style->color.r, style->color.g, style->color.b, style->fill.opacity);
345     } else {
346         //Apply the fill color
347         vg->fill(style->fill.paint.color.r, style->fill.paint.color.g, style->fill.paint.color.b, style->fill.opacity);
348     }
349 
350     //Apply the fill rule
351     vg->fill((tvg::FillRule)style->fill.fillRule);
352     //Rendering order
353     vg->order(!style->paintOrder);
354 
355     //Apply node opacity
356     if (style->opacity < 255) vg->opacity(style->opacity);
357 
358     if (node->type == SvgNodeType::G || node->type == SvgNodeType::Use) return;
359 
360     //Apply the stroke style property
361     vg->stroke(style->stroke.width);
362     vg->stroke(style->stroke.cap);
363     vg->stroke(style->stroke.join);
364     vg->strokeMiterlimit(style->stroke.miterlimit);
365     if (style->stroke.dash.array.count > 0) {
366         P(vg)->strokeDash(style->stroke.dash.array.data, style->stroke.dash.array.count, style->stroke.dash.offset);
367     }
368 
369     //If stroke property is nullptr then do nothing
370     if (style->stroke.paint.none) {
371         vg->stroke(0.0f);
372     } else if (style->stroke.paint.gradient) {
373         Box bBox = vBox;
374         if (!style->stroke.paint.gradient->userSpace) bBox = _boundingBox(vg);
375 
376         if (style->stroke.paint.gradient->type == SvgGradientType::Linear) {
377              auto linear = _applyLinearGradientProperty(style->stroke.paint.gradient, bBox, style->stroke.opacity);
378              vg->stroke(std::move(linear));
379         } else if (style->stroke.paint.gradient->type == SvgGradientType::Radial) {
380              auto radial = _applyRadialGradientProperty(style->stroke.paint.gradient, bBox, style->stroke.opacity);
381              vg->stroke(std::move(radial));
382         }
383     } else if (style->stroke.paint.url) {
384         //TODO: Apply the color pointed by url
385         TVGLOG("SVG", "The stroke's url not supported.");
386     } else if (style->stroke.paint.curColor) {
387         //Apply the current style color
388         vg->stroke(style->color.r, style->color.g, style->color.b, style->stroke.opacity);
389     } else {
390         //Apply the stroke color
391         vg->stroke(style->stroke.paint.color.r, style->stroke.paint.color.g, style->stroke.paint.color.b, style->stroke.opacity);
392     }
393 
394     _applyComposition(loaderData, vg, node, vBox, svgPath);
395 }
396 
397 
_shapeBuildHelper(SvgLoaderData & loaderData,SvgNode * node,const Box & vBox,const string & svgPath)398 static unique_ptr<Shape> _shapeBuildHelper(SvgLoaderData& loaderData, SvgNode* node, const Box& vBox, const string& svgPath)
399 {
400     auto shape = Shape::gen();
401     if (_appendShape(loaderData, node, shape.get(), vBox, svgPath)) return shape;
402     else return nullptr;
403 }
404 
405 
_recognizeShape(SvgNode * node,Shape * shape)406 static bool _recognizeShape(SvgNode* node, Shape* shape)
407 {
408     switch (node->type) {
409         case SvgNodeType::Path: {
410             if (node->node.path.path) {
411                 if (!svgPathToShape(node->node.path.path, shape)) {
412                     TVGERR("SVG", "Invalid path information.");
413                     return false;
414                 }
415             }
416             break;
417         }
418         case SvgNodeType::Ellipse: {
419             shape->appendCircle(node->node.ellipse.cx, node->node.ellipse.cy, node->node.ellipse.rx, node->node.ellipse.ry);
420             break;
421         }
422         case SvgNodeType::Polygon: {
423             if (node->node.polygon.pts.count < 2) break;
424             auto pts = node->node.polygon.pts.begin();
425             shape->moveTo(pts[0], pts[1]);
426             for (pts += 2; pts < node->node.polygon.pts.end(); pts += 2) {
427                 shape->lineTo(pts[0], pts[1]);
428             }
429             shape->close();
430             break;
431         }
432         case SvgNodeType::Polyline: {
433             if (node->node.polyline.pts.count < 2) break;
434             auto pts = node->node.polyline.pts.begin();
435             shape->moveTo(pts[0], pts[1]);
436             for (pts += 2; pts < node->node.polyline.pts.end(); pts += 2) {
437                 shape->lineTo(pts[0], pts[1]);
438             }
439             break;
440         }
441         case SvgNodeType::Circle: {
442             shape->appendCircle(node->node.circle.cx, node->node.circle.cy, node->node.circle.r, node->node.circle.r);
443             break;
444         }
445         case SvgNodeType::Rect: {
446             shape->appendRect(node->node.rect.x, node->node.rect.y, node->node.rect.w, node->node.rect.h, node->node.rect.rx, node->node.rect.ry);
447             break;
448         }
449         case SvgNodeType::Line: {
450             shape->moveTo(node->node.line.x1, node->node.line.y1);
451             shape->lineTo(node->node.line.x2, node->node.line.y2);
452             break;
453         }
454         default: {
455             return false;
456         }
457     }
458     return true;
459 }
460 
461 
_appendShape(SvgLoaderData & loaderData,SvgNode * node,Shape * shape,const Box & vBox,const string & svgPath)462 static bool _appendShape(SvgLoaderData& loaderData, SvgNode* node, Shape* shape, const Box& vBox, const string& svgPath)
463 {
464     if (!_recognizeShape(node, shape)) return false;
465 
466     _applyProperty(loaderData, node, shape, vBox, svgPath, false);
467     return true;
468 }
469 
470 
_appendClipShape(SvgLoaderData & loaderData,SvgNode * node,Shape * shape,const Box & vBox,const string & svgPath,const Matrix * transform)471 static bool _appendClipShape(SvgLoaderData& loaderData, SvgNode* node, Shape* shape, const Box& vBox, const string& svgPath, const Matrix* transform)
472 {
473     //The 'transform' matrix has higher priority than the node->transform, since it already contains it
474     auto m = transform ? transform : (node->transform ? node->transform : nullptr);
475 
476     uint32_t currentPtsCnt = 0;
477     if (m) {
478         const Point *tmp = nullptr;
479         currentPtsCnt = shape->pathCoords(&tmp);
480     }
481 
482     if (!_recognizeShape(node, shape)) return false;
483 
484     if (m) {
485         const Point *pts = nullptr;
486         auto ptsCnt = shape->pathCoords(&pts);
487 
488         auto p = const_cast<Point*>(pts) + currentPtsCnt;
489         while (currentPtsCnt++ < ptsCnt) {
490             *p *= *m;
491             ++p;
492         }
493     }
494 
495     _applyProperty(loaderData, node, shape, vBox, svgPath, true);
496     return true;
497 }
498 
499 
500 enum class imageMimeTypeEncoding
501 {
502     base64 = 0x1,
503     utf8 = 0x2
504 };
505 
operator |(imageMimeTypeEncoding a,imageMimeTypeEncoding b)506 constexpr imageMimeTypeEncoding operator|(imageMimeTypeEncoding a, imageMimeTypeEncoding b) {
507     return static_cast<imageMimeTypeEncoding>(static_cast<int>(a) | static_cast<int>(b));
508 }
509 
operator &(imageMimeTypeEncoding a,imageMimeTypeEncoding b)510 constexpr bool operator&(imageMimeTypeEncoding a, imageMimeTypeEncoding b) {
511     return (static_cast<int>(a) & static_cast<int>(b));
512 }
513 
514 
515 static constexpr struct
516 {
517     const char* name;
518     int sz;
519     imageMimeTypeEncoding encoding;
520 } imageMimeTypes[] = {
521     {"jpeg", sizeof("jpeg"), imageMimeTypeEncoding::base64},
522     {"png", sizeof("png"), imageMimeTypeEncoding::base64},
523     {"webp", sizeof("webp"), imageMimeTypeEncoding::base64},
524     {"svg+xml", sizeof("svg+xml"), imageMimeTypeEncoding::base64 | imageMimeTypeEncoding::utf8},
525 };
526 
527 
_isValidImageMimeTypeAndEncoding(const char ** href,const char ** mimetype,imageMimeTypeEncoding * encoding)528 static bool _isValidImageMimeTypeAndEncoding(const char** href, const char** mimetype, imageMimeTypeEncoding* encoding) {
529     if (strncmp(*href, "image/", sizeof("image/") - 1)) return false; //not allowed mime type
530     *href += sizeof("image/") - 1;
531 
532     //RFC2397 data:[<mediatype>][;base64],<data>
533     //mediatype  := [ type "/" subtype ] *( ";" parameter )
534     //parameter  := attribute "=" value
535     for (unsigned int i = 0; i < sizeof(imageMimeTypes) / sizeof(imageMimeTypes[0]); i++) {
536         if (!strncmp(*href, imageMimeTypes[i].name, imageMimeTypes[i].sz - 1)) {
537             *href += imageMimeTypes[i].sz  - 1;
538             *mimetype = imageMimeTypes[i].name;
539 
540             while (**href && **href != ',') {
541                 while (**href && **href != ';') ++(*href);
542                 if (!**href) return false;
543                 ++(*href);
544 
545                 if (imageMimeTypes[i].encoding & imageMimeTypeEncoding::base64) {
546                     if (!strncmp(*href, "base64,", sizeof("base64,") - 1)) {
547                         *href += sizeof("base64,") - 1;
548                         *encoding = imageMimeTypeEncoding::base64;
549                         return true; //valid base64
550                     }
551                 }
552                 if (imageMimeTypes[i].encoding & imageMimeTypeEncoding::utf8) {
553                     if (!strncmp(*href, "utf8,", sizeof("utf8,") - 1)) {
554                         *href += sizeof("utf8,") - 1;
555                         *encoding = imageMimeTypeEncoding::utf8;
556                         return true; //valid utf8
557                     }
558                 }
559             }
560             //no encoding defined
561             if (**href == ',' && (imageMimeTypes[i].encoding & imageMimeTypeEncoding::utf8)) {
562                 ++(*href);
563                 *encoding = imageMimeTypeEncoding::utf8;
564                 return true; //allow no encoding defined if utf8 expected
565             }
566             return false;
567         }
568     }
569     return false;
570 }
571 
572 #include "tvgTaskScheduler.h"
573 
_imageBuildHelper(SvgLoaderData & loaderData,SvgNode * node,const Box & vBox,const string & svgPath)574 static unique_ptr<Picture> _imageBuildHelper(SvgLoaderData& loaderData, SvgNode* node, const Box& vBox, const string& svgPath)
575 {
576     if (!node->node.image.href || !strlen(node->node.image.href)) return nullptr;
577     auto picture = Picture::gen();
578 
579     TaskScheduler::async(false);    //force to load a picture on the same thread
580 
581     const char* href = node->node.image.href;
582     if (!strncmp(href, "data:", sizeof("data:") - 1)) {
583         href += sizeof("data:") - 1;
584         const char* mimetype;
585         imageMimeTypeEncoding encoding;
586         if (!_isValidImageMimeTypeAndEncoding(&href, &mimetype, &encoding)) return nullptr; //not allowed mime type or encoding
587         char *decoded = nullptr;
588         if (encoding == imageMimeTypeEncoding::base64) {
589             auto size = b64Decode(href, strlen(href), &decoded);
590             if (picture->load(decoded, size, mimetype, false) != Result::Success) {
591                 free(decoded);
592                 TaskScheduler::async(true);
593                 return nullptr;
594             }
595         } else {
596             auto size = svgUtilURLDecode(href, &decoded);
597             if (picture->load(decoded, size, mimetype, false) != Result::Success) {
598                 free(decoded);
599                 TaskScheduler::async(true);
600                 return nullptr;
601             }
602         }
603         loaderData.images.push(decoded);
604     } else {
605         if (!strncmp(href, "file://", sizeof("file://") - 1)) href += sizeof("file://") - 1;
606         //TODO: protect against recursive svg image loading
607         //Temporarily disable embedded svg:
608         const char *dot = strrchr(href, '.');
609         if (dot && !strcmp(dot, ".svg")) {
610             TVGLOG("SVG", "Embedded svg file is disabled.");
611             TaskScheduler::async(true);
612             return nullptr;
613         }
614         string imagePath = href;
615         if (strncmp(href, "/", 1)) {
616             auto last = svgPath.find_last_of("/");
617             imagePath = svgPath.substr(0, (last == string::npos ? 0 : last + 1)) + imagePath;
618         }
619         if (picture->load(imagePath) != Result::Success) {
620             TaskScheduler::async(true);
621             return nullptr;
622         }
623     }
624 
625     TaskScheduler::async(true);
626 
627     float w, h;
628     Matrix m = {1, 0, 0, 0, 1, 0, 0, 0, 1};
629     if (picture->size(&w, &h) == Result::Success && w  > 0 && h > 0) {
630         auto sx = node->node.image.w / w;
631         auto sy = node->node.image.h / h;
632         m = {sx, 0, node->node.image.x, 0, sy, node->node.image.y, 0, 0, 1};
633     }
634     if (node->transform) m = *node->transform * m;
635     picture->transform(m);
636 
637     _applyComposition(loaderData, picture.get(), node, vBox, svgPath);
638 
639     return picture;
640 }
641 
642 
_calculateAspectRatioMatrix(AspectRatioAlign align,AspectRatioMeetOrSlice meetOrSlice,float width,float height,const Box & box)643 static Matrix _calculateAspectRatioMatrix(AspectRatioAlign align, AspectRatioMeetOrSlice meetOrSlice, float width, float height, const Box& box)
644 {
645     auto sx = width / box.w;
646     auto sy = height / box.h;
647     auto tvx = box.x * sx;
648     auto tvy = box.y * sy;
649 
650     if (align == AspectRatioAlign::None)
651         return {sx, 0, -tvx, 0, sy, -tvy, 0, 0, 1};
652 
653     //Scale
654     if (meetOrSlice == AspectRatioMeetOrSlice::Meet) {
655         if (sx < sy) sy = sx;
656         else sx = sy;
657     } else {
658         if (sx < sy) sx = sy;
659         else sy = sx;
660     }
661 
662     //Align
663     tvx = box.x * sx;
664     tvy = box.y * sy;
665     auto tvw = box.w * sx;
666     auto tvh = box.h * sy;
667 
668     switch (align) {
669         case AspectRatioAlign::XMinYMin: {
670             break;
671         }
672         case AspectRatioAlign::XMidYMin: {
673             tvx -= (width - tvw) * 0.5f;
674             break;
675         }
676         case AspectRatioAlign::XMaxYMin: {
677             tvx -= width - tvw;
678             break;
679         }
680         case AspectRatioAlign::XMinYMid: {
681             tvy -= (height - tvh) * 0.5f;
682             break;
683         }
684         case AspectRatioAlign::XMidYMid: {
685             tvx -= (width - tvw) * 0.5f;
686             tvy -= (height - tvh) * 0.5f;
687             break;
688         }
689         case AspectRatioAlign::XMaxYMid: {
690             tvx -= width - tvw;
691             tvy -= (height - tvh) * 0.5f;
692             break;
693         }
694         case AspectRatioAlign::XMinYMax: {
695             tvy -= height - tvh;
696             break;
697         }
698         case AspectRatioAlign::XMidYMax: {
699             tvx -= (width - tvw) * 0.5f;
700             tvy -= height - tvh;
701             break;
702         }
703         case AspectRatioAlign::XMaxYMax: {
704             tvx -= width - tvw;
705             tvy -= height - tvh;
706             break;
707         }
708         default: {
709             break;
710         }
711     }
712 
713     return {sx, 0, -tvx, 0, sy, -tvy, 0, 0, 1};
714 }
715 
716 
_useBuildHelper(SvgLoaderData & loaderData,const SvgNode * node,const Box & vBox,const string & svgPath,int depth,bool * isMaskWhite)717 static unique_ptr<Scene> _useBuildHelper(SvgLoaderData& loaderData, const SvgNode* node, const Box& vBox, const string& svgPath, int depth, bool* isMaskWhite)
718 {
719     auto scene = _sceneBuildHelper(loaderData, node, vBox, svgPath, false, depth + 1, isMaskWhite);
720 
721     // mUseTransform = mUseTransform * mTranslate
722     Matrix mUseTransform = {1, 0, 0, 0, 1, 0, 0, 0, 1};
723     if (node->transform) mUseTransform = *node->transform;
724     if (node->node.use.x != 0.0f || node->node.use.y != 0.0f) {
725         Matrix mTranslate = {1, 0, node->node.use.x, 0, 1, node->node.use.y, 0, 0, 1};
726         mUseTransform *= mTranslate;
727     }
728 
729     if (node->node.use.symbol) {
730         auto symbol = node->node.use.symbol->node.symbol;
731 
732         auto width = (symbol.hasWidth ? symbol.w : vBox.w);
733         if (node->node.use.isWidthSet) width = node->node.use.w;
734         auto height = (symbol.hasHeight ? symbol.h : vBox.h);;
735         if (node->node.use.isHeightSet) height = node->node.use.h;
736         auto vw = (symbol.hasViewBox ? symbol.vw : width);
737         auto vh = (symbol.hasViewBox ? symbol.vh : height);
738 
739         Matrix mViewBox = {1, 0, 0, 0, 1, 0, 0, 0, 1};
740         if ((!tvg::equal(width, vw) || !tvg::equal(height, vh)) && vw > 0 && vh > 0) {
741             Box box = {symbol.vx, symbol.vy, vw, vh};
742             mViewBox = _calculateAspectRatioMatrix(symbol.align, symbol.meetOrSlice, width, height, box);
743         } else if (!tvg::zero(symbol.vx) || !tvg::zero(symbol.vy)) {
744             mViewBox = {1, 0, -symbol.vx, 0, 1, -symbol.vy, 0, 0, 1};
745         }
746 
747         // mSceneTransform = mUseTransform * mSymbolTransform * mViewBox
748         Matrix mSceneTransform = mViewBox;
749         if (node->node.use.symbol->transform) {
750             mSceneTransform = *node->node.use.symbol->transform * mViewBox;
751         }
752         mSceneTransform = mUseTransform * mSceneTransform;
753         scene->transform(mSceneTransform);
754 
755         if (!node->node.use.symbol->node.symbol.overflowVisible) {
756             auto viewBoxClip = Shape::gen();
757             viewBoxClip->appendRect(0, 0, width, height, 0, 0);
758 
759             // mClipTransform = mUseTransform * mSymbolTransform
760             Matrix mClipTransform = mUseTransform;
761             if (node->node.use.symbol->transform) {
762                 mClipTransform = mUseTransform * *node->node.use.symbol->transform;
763             }
764             viewBoxClip->transform(mClipTransform);
765 
766             scene->clip(std::move(viewBoxClip));
767         }
768     } else {
769         scene->transform(mUseTransform);
770     }
771 
772     return scene;
773 }
774 
775 
_applyTextFill(SvgStyleProperty * style,Text * text,const Box & vBox)776 static void _applyTextFill(SvgStyleProperty* style, Text* text, const Box& vBox)
777 {
778     //If fill property is nullptr then do nothing
779     if (style->fill.paint.none) {
780         //Do nothing
781     } else if (style->fill.paint.gradient) {
782         Box bBox = vBox;
783         if (!style->fill.paint.gradient->userSpace) bBox = _boundingBox(text);
784 
785         if (style->fill.paint.gradient->type == SvgGradientType::Linear) {
786             auto linear = _applyLinearGradientProperty(style->fill.paint.gradient, bBox, style->fill.opacity);
787             text->fill(std::move(linear));
788         } else if (style->fill.paint.gradient->type == SvgGradientType::Radial) {
789             auto radial = _applyRadialGradientProperty(style->fill.paint.gradient, bBox, style->fill.opacity);
790             text->fill(std::move(radial));
791         }
792     } else if (style->fill.paint.url) {
793         //TODO: Apply the color pointed by url
794         TVGLOG("SVG", "The fill's url not supported.");
795     } else if (style->fill.paint.curColor) {
796         //Apply the current style color
797         text->fill(style->color.r, style->color.g, style->color.b);
798         text->opacity(style->fill.opacity);
799     } else {
800         //Apply the fill color
801         text->fill(style->fill.paint.color.r, style->fill.paint.color.g, style->fill.paint.color.b);
802         text->opacity(style->fill.opacity);
803     }
804 }
805 
806 
_textBuildHelper(SvgLoaderData & loaderData,const SvgNode * node,const Box & vBox,const string & svgPath)807 static unique_ptr<Text> _textBuildHelper(SvgLoaderData& loaderData, const SvgNode* node, const Box& vBox, const string& svgPath)
808 {
809     auto textNode = &node->node.text;
810     if (!textNode->text) return nullptr;
811     auto text = Text::gen();
812 
813     Matrix textTransform = {1, 0, 0, 0, 1, 0, 0, 0, 1};
814     if (node->transform) textTransform = *node->transform;
815     translateR(&textTransform, node->node.text.x, node->node.text.y - textNode->fontSize);
816     text->transform(textTransform);
817 
818     //TODO: handle def values of font and size as used in a system?
819     const float ptPerPx = 0.75f; //1 pt = 1/72; 1 in = 96 px; -> 72/96 = 0.75
820     auto fontSizePt = textNode->fontSize * ptPerPx;
821     if (textNode->fontFamily) text->font(textNode->fontFamily, fontSizePt);
822     text->text(textNode->text);
823 
824     _applyTextFill(node->style, text.get(), vBox);
825     _applyComposition(loaderData, text.get(), node, vBox, svgPath);
826 
827     return text;
828 }
829 
830 
_sceneBuildHelper(SvgLoaderData & loaderData,const SvgNode * node,const Box & vBox,const string & svgPath,bool mask,int depth,bool * isMaskWhite)831 static unique_ptr<Scene> _sceneBuildHelper(SvgLoaderData& loaderData, const SvgNode* node, const Box& vBox, const string& svgPath, bool mask, int depth, bool* isMaskWhite)
832 {
833     /* Exception handling: Prevent invalid SVG data input.
834        The size is the arbitrary value, we need an experimental size. */
835     if (depth > 2192) {
836         TVGERR("SVG", "Infinite recursive call - stopped after %d calls! Svg file may be incorrectly formatted.", depth);
837         return nullptr;
838     }
839 
840     if (_isGroupType(node->type) || mask) {
841         auto scene = Scene::gen();
842         // For a Symbol node, the viewBox transformation has to be applied first - see _useBuildHelper()
843         if (!mask && node->transform && node->type != SvgNodeType::Symbol) scene->transform(*node->transform);
844 
845         if (node->style->display && node->style->opacity != 0) {
846             auto child = node->child.data;
847             for (uint32_t i = 0; i < node->child.count; ++i, ++child) {
848                 if (_isGroupType((*child)->type)) {
849                     if ((*child)->type == SvgNodeType::Use)
850                         scene->push(_useBuildHelper(loaderData, *child, vBox, svgPath, depth + 1, isMaskWhite));
851                     else if (!((*child)->type == SvgNodeType::Symbol && node->type != SvgNodeType::Use))
852                         scene->push(_sceneBuildHelper(loaderData, *child, vBox, svgPath, false, depth + 1, isMaskWhite));
853                 } else if ((*child)->type == SvgNodeType::Image) {
854                     auto image = _imageBuildHelper(loaderData, *child, vBox, svgPath);
855                     if (image) {
856                         scene->push(std::move(image));
857                         if (isMaskWhite) *isMaskWhite = false;
858                     }
859                 } else if ((*child)->type == SvgNodeType::Text) {
860                     auto text = _textBuildHelper(loaderData, *child, vBox, svgPath);
861                     if (text) scene->push(std::move(text));
862                 } else if ((*child)->type != SvgNodeType::Mask) {
863                     auto shape = _shapeBuildHelper(loaderData, *child, vBox, svgPath);
864                     if (shape) {
865                         if (isMaskWhite) {
866                             uint8_t r, g, b;
867                             shape->fillColor(&r, &g, &b);
868                             if (shape->fill() || r < 255 || g < 255 || b < 255 || shape->strokeFill() ||
869                                 (shape->strokeColor(&r, &g, &b) == Result::Success && (r < 255 || g < 255 || b < 255))) {
870                                 *isMaskWhite = false;
871                             }
872                         }
873                         scene->push(std::move(shape));
874                     }
875                 }
876             }
877             _applyComposition(loaderData, scene.get(), node, vBox, svgPath);
878             scene->opacity(node->style->opacity);
879         }
880         return scene;
881     }
882     return nullptr;
883 }
884 
885 
_updateInvalidViewSize(const Scene * scene,Box & vBox,float & w,float & h,SvgViewFlag viewFlag)886 static void _updateInvalidViewSize(const Scene* scene, Box& vBox, float& w, float& h, SvgViewFlag viewFlag)
887 {
888     bool validWidth = (viewFlag & SvgViewFlag::Width);
889     bool validHeight = (viewFlag & SvgViewFlag::Height);
890 
891     float x, y;
892     scene->bounds(&x, &y, &vBox.w, &vBox.h, false);
893     if (!validWidth && !validHeight) {
894         vBox.x = x;
895         vBox.y = y;
896     } else {
897         if (validWidth) vBox.w = w;
898         if (validHeight) vBox.h = h;
899     }
900 
901     //the size would have 1x1 or percentage values.
902     if (!validWidth) w *= vBox.w;
903     if (!validHeight) h *= vBox.h;
904 }
905 
906 /************************************************************************/
907 /* External Class Implementation                                        */
908 /************************************************************************/
909 
svgSceneBuild(SvgLoaderData & loaderData,Box vBox,float w,float h,AspectRatioAlign align,AspectRatioMeetOrSlice meetOrSlice,const string & svgPath,SvgViewFlag viewFlag)910 Scene* svgSceneBuild(SvgLoaderData& loaderData, Box vBox, float w, float h, AspectRatioAlign align, AspectRatioMeetOrSlice meetOrSlice, const string& svgPath, SvgViewFlag viewFlag)
911 {
912     //TODO: aspect ratio is valid only if viewBox was set
913 
914     if (!loaderData.doc || (loaderData.doc->type != SvgNodeType::Doc)) return nullptr;
915 
916     auto docNode = _sceneBuildHelper(loaderData, loaderData.doc, vBox, svgPath, false, 0);
917 
918     if (!(viewFlag & SvgViewFlag::Viewbox)) _updateInvalidViewSize(docNode.get(), vBox, w, h, viewFlag);
919 
920     if (!tvg::equal(w, vBox.w) || !tvg::equal(h, vBox.h)) {
921         Matrix m = _calculateAspectRatioMatrix(align, meetOrSlice, w, h, vBox);
922         docNode->transform(m);
923     } else if (!tvg::zero(vBox.x) || !tvg::zero(vBox.y)) {
924         docNode->translate(-vBox.x, -vBox.y);
925     }
926 
927     auto viewBoxClip = Shape::gen();
928     viewBoxClip->appendRect(0, 0, w, h);
929 
930     auto compositeLayer = Scene::gen();
931     compositeLayer->clip(std::move(viewBoxClip));
932     compositeLayer->push(std::move(docNode));
933 
934     auto root = Scene::gen();
935     root->push(std::move(compositeLayer));
936 
937     loaderData.doc->node.doc.vx = vBox.x;
938     loaderData.doc->node.doc.vy = vBox.y;
939     loaderData.doc->node.doc.vw = vBox.w;
940     loaderData.doc->node.doc.vh = vBox.h;
941     loaderData.doc->node.doc.w = w;
942     loaderData.doc->node.doc.h = h;
943 
944     return root.release();
945 }
946 
947 #endif /* LV_USE_THORVG_INTERNAL */
948 
949