2 * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
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:
11 * The above copyright notice and this permission notice shall be included in all
12 * copies or substantial portions of the Software.
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
23 #define _USE_MATH_DEFINES //Math Constants are not defined in Standard C/C++.
28 #include "tvgLoader.h"
29 #include "tvgXmlParser.h"
30 #include "tvgSvgLoader.h"
31 #include "tvgSvgSceneBuilder.h"
32 #include "tvgSvgUtil.h"
34 /************************************************************************/
35 /* Internal Class Implementation */
36 /************************************************************************/
38 typedef SvgNode* (*FactoryMethod)(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength);
39 typedef SvgStyleGradient* (*GradientFactoryMethod)(SvgLoaderData* loader, const char* buf, unsigned bufLength);
42 static char* _skipSpace(const char* str, const char* end)
44 while (((end && str < end) || (!end && *str != '\0')) && isspace(*str)) {
51 static string* _copyId(const char* str)
53 if (!str) return nullptr;
55 return new string(str);
59 static const char* _skipComma(const char* content)
61 content = _skipSpace(content, nullptr);
62 if (*content == ',') return content + 1;
67 static bool _parseNumber(const char** content, float* number)
71 *number = svgUtilStrtof(*content, &end);
72 //If the start of string is not number
73 if ((*content) == end) return false;
75 *content = _skipComma(end);
80 * According to https://www.w3.org/TR/SVG/coords.html#Units
83 * Since this documentation is not obvious, more clean recalculation with dpi
84 * is required, but for now default w3 constants would be used
86 static float _toFloat(const SvgParser* svgParse, const char* str, SvgParserLengthType type)
88 float parsedValue = svgUtilStrtof(str, nullptr);
90 if (strstr(str, "cm")) parsedValue = parsedValue * 35.43307;
91 else if (strstr(str, "mm")) parsedValue = parsedValue * 3.543307;
92 else if (strstr(str, "pt")) parsedValue = parsedValue * 1.25;
93 else if (strstr(str, "pc")) parsedValue = parsedValue * 15;
94 else if (strstr(str, "in")) parsedValue = parsedValue * 90;
95 else if (strstr(str, "%")) {
96 if (type == SvgParserLengthType::Vertical) parsedValue = (parsedValue / 100.0) * svgParse->global.h;
97 else if (type == SvgParserLengthType::Horizontal) parsedValue = (parsedValue / 100.0) * svgParse->global.w;
98 else //if other then it's radius
100 float max = svgParse->global.w;
101 if (max < svgParse->global.h)
102 max = svgParse->global.h;
103 parsedValue = (parsedValue / 100.0) * max;
107 //TODO: Implement 'em', 'ex' attributes
113 static float _gradientToFloat(const SvgParser* svgParse, const char* str, SvgParserLengthType type)
117 float parsedValue = svgUtilStrtof(str, &end);
121 * That is according to Units in here
123 * https://www.w3.org/TR/2015/WD-SVG2-20150915/coords.html
125 if (type == SvgParserLengthType::Vertical) max = svgParse->global.h;
126 else if (type == SvgParserLengthType::Horizontal) max = svgParse->global.w;
127 else if (type == SvgParserLengthType::Other) max = sqrt(pow(svgParse->global.h, 2) + pow(svgParse->global.w, 2)) / sqrt(2.0);
129 if (strstr(str, "%")) parsedValue = parsedValue / 100.0;
130 else if (strstr(str, "cm")) parsedValue = parsedValue * 35.43307;
131 else if (strstr(str, "mm")) parsedValue = parsedValue * 3.543307;
132 else if (strstr(str, "pt")) parsedValue = parsedValue * 1.25;
133 else if (strstr(str, "pc")) parsedValue = parsedValue * 15;
134 else if (strstr(str, "in")) parsedValue = parsedValue * 90;
135 //TODO: Implement 'em', 'ex' attributes
137 //Transform into global percentage
138 parsedValue = parsedValue / max;
144 static float _toOffset(const char* str)
147 auto strEnd = str + strlen(str);
149 float parsedValue = svgUtilStrtof(str, &end);
151 end = _skipSpace(end, nullptr);
152 auto ptr = strstr(str, "%");
155 parsedValue = parsedValue / 100.0;
156 if (end != ptr || (end + 1) != strEnd) return 0;
157 } else if (end != strEnd) return 0;
163 static int _toOpacity(const char* str)
167 float opacity = svgUtilStrtof(str, &end);
169 if (end && (*end == '\0')) a = lrint(opacity * 255);
174 #define _PARSE_TAG(Type, Name, Name1, Tags_Array, Default) \
175 static Type _to##Name1(const char* str) \
179 for (i = 0; i < sizeof(Tags_Array) / sizeof(Tags_Array[0]); i++) { \
180 if (!strcmp(str, Tags_Array[i].tag)) return Tags_Array[i].Name; \
186 /* parse the line cap used during stroking a path.
187 * Value: butt | round | square | inherit
189 * https://www.w3.org/TR/SVG/painting.html
191 static constexpr struct
196 { StrokeCap::Butt, "butt" },
197 { StrokeCap::Round, "round" },
198 { StrokeCap::Square, "square" }
202 _PARSE_TAG(StrokeCap, lineCap, LineCap, lineCapTags, StrokeCap::Butt)
205 /* parse the line join used during stroking a path.
206 * Value: miter | round | bevel | inherit
208 * https://www.w3.org/TR/SVG/painting.html
210 static constexpr struct
215 { StrokeJoin::Miter, "miter" },
216 { StrokeJoin::Round, "round" },
217 { StrokeJoin::Bevel, "bevel" }
221 _PARSE_TAG(StrokeJoin, lineJoin, LineJoin, lineJoinTags, StrokeJoin::Miter)
224 /* parse the fill rule used during filling a path.
225 * Value: nonzero | evenodd | inherit
227 * https://www.w3.org/TR/SVG/painting.html
229 static constexpr struct
234 { FillRule::EvenOdd, "evenodd" }
238 _PARSE_TAG(FillRule, fillRule, FillRule, fillRuleTags, FillRule::Winding)
241 /* parse the dash pattern used during stroking a path.
242 * Value: none | <dasharray> | inherit
244 * https://www.w3.org/TR/SVG/painting.html
247 _parseDashArray(SvgLoaderData* loader, const char *str, SvgDash* dash)
249 if (!strncmp(str, "none", 4)) return;
254 str = _skipComma(str);
255 float parsedValue = svgUtilStrtof(str, &end);
256 if (str == end) break;
257 if (parsedValue <= 0.0f) break;
260 //Refers to the diagonal length of the viewport.
261 //https://www.w3.org/TR/SVG2/coords.html#Units
262 parsedValue = (sqrt(pow(loader->svgParse->global.w, 2) + pow(loader->svgParse->global.h, 2)) / sqrt(2.0f)) * (parsedValue / 100.0f);
264 (*dash).array.push(parsedValue);
267 //If dash array size is 1, it means that dash and gap size are the same.
268 if ((*dash).array.count == 1) (*dash).array.push((*dash).array.data[0]);
271 static string* _idFromUrl(const char* url)
276 url = _skipSpace(url, nullptr);
279 url = _skipSpace(url, nullptr);
282 if ((*url) == '#') ++url;
284 while ((*url) != ')') {
290 return new string(tmp);
294 static unsigned char _parserColor(const char* value, char** end)
298 r = svgUtilStrtof(value, end);
299 *end = _skipSpace(*end, nullptr);
300 if (**end == '%') r = 255 * r / 100;
301 *end = _skipSpace(*end, nullptr);
303 if (r < 0 || r > 255) {
312 static constexpr struct
317 { "aliceblue", 0xfff0f8ff },
318 { "antiquewhite", 0xfffaebd7 },
319 { "aqua", 0xff00ffff },
320 { "aquamarine", 0xff7fffd4 },
321 { "azure", 0xfff0ffff },
322 { "beige", 0xfff5f5dc },
323 { "bisque", 0xffffe4c4 },
324 { "black", 0xff000000 },
325 { "blanchedalmond", 0xffffebcd },
326 { "blue", 0xff0000ff },
327 { "blueviolet", 0xff8a2be2 },
328 { "brown", 0xffa52a2a },
329 { "burlywood", 0xffdeb887 },
330 { "cadetblue", 0xff5f9ea0 },
331 { "chartreuse", 0xff7fff00 },
332 { "chocolate", 0xffd2691e },
333 { "coral", 0xffff7f50 },
334 { "cornflowerblue", 0xff6495ed },
335 { "cornsilk", 0xfffff8dc },
336 { "crimson", 0xffdc143c },
337 { "cyan", 0xff00ffff },
338 { "darkblue", 0xff00008b },
339 { "darkcyan", 0xff008b8b },
340 { "darkgoldenrod", 0xffb8860b },
341 { "darkgray", 0xffa9a9a9 },
342 { "darkgrey", 0xffa9a9a9 },
343 { "darkgreen", 0xff006400 },
344 { "darkkhaki", 0xffbdb76b },
345 { "darkmagenta", 0xff8b008b },
346 { "darkolivegreen", 0xff556b2f },
347 { "darkorange", 0xffff8c00 },
348 { "darkorchid", 0xff9932cc },
349 { "darkred", 0xff8b0000 },
350 { "darksalmon", 0xffe9967a },
351 { "darkseagreen", 0xff8fbc8f },
352 { "darkslateblue", 0xff483d8b },
353 { "darkslategray", 0xff2f4f4f },
354 { "darkslategrey", 0xff2f4f4f },
355 { "darkturquoise", 0xff00ced1 },
356 { "darkviolet", 0xff9400d3 },
357 { "deeppink", 0xffff1493 },
358 { "deepskyblue", 0xff00bfff },
359 { "dimgray", 0xff696969 },
360 { "dimgrey", 0xff696969 },
361 { "dodgerblue", 0xff1e90ff },
362 { "firebrick", 0xffb22222 },
363 { "floralwhite", 0xfffffaf0 },
364 { "forestgreen", 0xff228b22 },
365 { "fuchsia", 0xffff00ff },
366 { "gainsboro", 0xffdcdcdc },
367 { "ghostwhite", 0xfff8f8ff },
368 { "gold", 0xffffd700 },
369 { "goldenrod", 0xffdaa520 },
370 { "gray", 0xff808080 },
371 { "grey", 0xff808080 },
372 { "green", 0xff008000 },
373 { "greenyellow", 0xffadff2f },
374 { "honeydew", 0xfff0fff0 },
375 { "hotpink", 0xffff69b4 },
376 { "indianred", 0xffcd5c5c },
377 { "indigo", 0xff4b0082 },
378 { "ivory", 0xfffffff0 },
379 { "khaki", 0xfff0e68c },
380 { "lavender", 0xffe6e6fa },
381 { "lavenderblush", 0xfffff0f5 },
382 { "lawngreen", 0xff7cfc00 },
383 { "lemonchiffon", 0xfffffacd },
384 { "lightblue", 0xffadd8e6 },
385 { "lightcoral", 0xfff08080 },
386 { "lightcyan", 0xffe0ffff },
387 { "lightgoldenrodyellow", 0xfffafad2 },
388 { "lightgray", 0xffd3d3d3 },
389 { "lightgrey", 0xffd3d3d3 },
390 { "lightgreen", 0xff90ee90 },
391 { "lightpink", 0xffffb6c1 },
392 { "lightsalmon", 0xffffa07a },
393 { "lightseagreen", 0xff20b2aa },
394 { "lightskyblue", 0xff87cefa },
395 { "lightslategray", 0xff778899 },
396 { "lightslategrey", 0xff778899 },
397 { "lightsteelblue", 0xffb0c4de },
398 { "lightyellow", 0xffffffe0 },
399 { "lime", 0xff00ff00 },
400 { "limegreen", 0xff32cd32 },
401 { "linen", 0xfffaf0e6 },
402 { "magenta", 0xffff00ff },
403 { "maroon", 0xff800000 },
404 { "mediumaquamarine", 0xff66cdaa },
405 { "mediumblue", 0xff0000cd },
406 { "mediumorchid", 0xffba55d3 },
407 { "mediumpurple", 0xff9370d8 },
408 { "mediumseagreen", 0xff3cb371 },
409 { "mediumslateblue", 0xff7b68ee },
410 { "mediumspringgreen", 0xff00fa9a },
411 { "mediumturquoise", 0xff48d1cc },
412 { "mediumvioletred", 0xffc71585 },
413 { "midnightblue", 0xff191970 },
414 { "mintcream", 0xfff5fffa },
415 { "mistyrose", 0xffffe4e1 },
416 { "moccasin", 0xffffe4b5 },
417 { "navajowhite", 0xffffdead },
418 { "navy", 0xff000080 },
419 { "oldlace", 0xfffdf5e6 },
420 { "olive", 0xff808000 },
421 { "olivedrab", 0xff6b8e23 },
422 { "orange", 0xffffa500 },
423 { "orangered", 0xffff4500 },
424 { "orchid", 0xffda70d6 },
425 { "palegoldenrod", 0xffeee8aa },
426 { "palegreen", 0xff98fb98 },
427 { "paleturquoise", 0xffafeeee },
428 { "palevioletred", 0xffd87093 },
429 { "papayawhip", 0xffffefd5 },
430 { "peachpuff", 0xffffdab9 },
431 { "peru", 0xffcd853f },
432 { "pink", 0xffffc0cb },
433 { "plum", 0xffdda0dd },
434 { "powderblue", 0xffb0e0e6 },
435 { "purple", 0xff800080 },
436 { "red", 0xffff0000 },
437 { "rosybrown", 0xffbc8f8f },
438 { "royalblue", 0xff4169e1 },
439 { "saddlebrown", 0xff8b4513 },
440 { "salmon", 0xfffa8072 },
441 { "sandybrown", 0xfff4a460 },
442 { "seagreen", 0xff2e8b57 },
443 { "seashell", 0xfffff5ee },
444 { "sienna", 0xffa0522d },
445 { "silver", 0xffc0c0c0 },
446 { "skyblue", 0xff87ceeb },
447 { "slateblue", 0xff6a5acd },
448 { "slategray", 0xff708090 },
449 { "slategrey", 0xff708090 },
450 { "snow", 0xfffffafa },
451 { "springgreen", 0xff00ff7f },
452 { "steelblue", 0xff4682b4 },
453 { "tan", 0xffd2b48c },
454 { "teal", 0xff008080 },
455 { "thistle", 0xffd8bfd8 },
456 { "tomato", 0xffff6347 },
457 { "turquoise", 0xff40e0d0 },
458 { "violet", 0xffee82ee },
459 { "wheat", 0xfff5deb3 },
460 { "white", 0xffffffff },
461 { "whitesmoke", 0xfff5f5f5 },
462 { "yellow", 0xffffff00 },
463 { "yellowgreen", 0xff9acd32 }
467 static void _toColor(const char* str, uint8_t* r, uint8_t* g, uint8_t* b, string** ref)
469 unsigned int len = strlen(str);
470 char *red, *green, *blue;
471 unsigned char tr, tg, tb;
473 if (len == 4 && str[0] == '#') {
474 //Case for "#456" should be interprete as "#445566"
475 if (isxdigit(str[1]) && isxdigit(str[2]) && isxdigit(str[3])) {
476 char tmp[3] = { '\0', '\0', '\0' };
479 *r = strtol(tmp, nullptr, 16);
482 *g = strtol(tmp, nullptr, 16);
485 *b = strtol(tmp, nullptr, 16);
487 } else if (len == 7 && str[0] == '#') {
488 if (isxdigit(str[1]) && isxdigit(str[2]) && isxdigit(str[3]) && isxdigit(str[4]) && isxdigit(str[5]) && isxdigit(str[6])) {
489 char tmp[3] = { '\0', '\0', '\0' };
492 *r = strtol(tmp, nullptr, 16);
495 *g = strtol(tmp, nullptr, 16);
498 *b = strtol(tmp, nullptr, 16);
500 } else if (len >= 10 && (str[0] == 'r' || str[0] == 'R') && (str[1] == 'g' || str[1] == 'G') && (str[2] == 'b' || str[2] == 'B') && str[3] == '(' && str[len - 1] == ')') {
501 tr = _parserColor(str + 4, &red);
502 if (red && *red == ',') {
503 tg = _parserColor(red + 1, &green);
504 if (green && *green == ',') {
505 tb = _parserColor(green + 1, &blue);
506 if (blue && blue[0] == ')' && blue[1] == '\0') {
513 } else if (len >= 3 && !strncmp(str, "url", 3)) {
514 *ref = _idFromUrl((const char*)(str + 3));
517 for (unsigned int i = 0; i < (sizeof(colors) / sizeof(colors[0])); i++) {
518 if (!strcasecmp(colors[i].name, str)) {
519 *r = (((uint8_t*)(&(colors[i].value)))[2]);
520 *g = (((uint8_t*)(&(colors[i].value)))[1]);
521 *b = (((uint8_t*)(&(colors[i].value)))[0]);
529 static char* _parseNumbersArray(char* str, float* points, int* ptCount, int len)
534 str = _skipSpace(str, nullptr);
535 while ((count < len) && (isdigit(*str) || *str == '-' || *str == '+' || *str == '.')) {
536 points[count++] = svgUtilStrtof(str, &end);
538 str = _skipSpace(str, nullptr);
539 if (*str == ',') ++str;
540 //Eat the rest of space
541 str = _skipSpace(str, nullptr);
548 enum class MatrixState {
559 #define MATRIX_DEF(Name, Value) \
561 #Name, sizeof(#Name), Value \
565 static constexpr struct
571 MATRIX_DEF(matrix, MatrixState::Matrix),
572 MATRIX_DEF(translate, MatrixState::Translate),
573 MATRIX_DEF(rotate, MatrixState::Rotate),
574 MATRIX_DEF(scale, MatrixState::Scale),
575 MATRIX_DEF(skewX, MatrixState::SkewX),
576 MATRIX_DEF(skewY, MatrixState::SkewY)
580 static void _matrixCompose(const Matrix* m1, const Matrix* m2, Matrix* dst)
582 auto a11 = (m1->e11 * m2->e11) + (m1->e12 * m2->e21) + (m1->e13 * m2->e31);
583 auto a12 = (m1->e11 * m2->e12) + (m1->e12 * m2->e22) + (m1->e13 * m2->e32);
584 auto a13 = (m1->e11 * m2->e13) + (m1->e12 * m2->e23) + (m1->e13 * m2->e33);
586 auto a21 = (m1->e21 * m2->e11) + (m1->e22 * m2->e21) + (m1->e23 * m2->e31);
587 auto a22 = (m1->e21 * m2->e12) + (m1->e22 * m2->e22) + (m1->e23 * m2->e32);
588 auto a23 = (m1->e21 * m2->e13) + (m1->e22 * m2->e23) + (m1->e23 * m2->e33);
590 auto a31 = (m1->e31 * m2->e11) + (m1->e32 * m2->e21) + (m1->e33 * m2->e31);
591 auto a32 = (m1->e31 * m2->e12) + (m1->e32 * m2->e22) + (m1->e33 * m2->e32);
592 auto a33 = (m1->e31 * m2->e13) + (m1->e32 * m2->e23) + (m1->e33 * m2->e33);
606 /* parse transform attribute
607 * https://www.w3.org/TR/SVG/coords.html#TransformAttribute
609 static Matrix* _parseTransformationMatrix(const char* value)
611 const int POINT_CNT = 8;
613 auto matrix = (Matrix*)malloc(sizeof(Matrix));
614 if (!matrix) return nullptr;
615 *matrix = {1, 0, 0, 0, 1, 0, 0, 0, 1};
617 float points[POINT_CNT];
619 char* str = (char*)value;
620 char* end = str + strlen(str);
623 auto state = MatrixState::Unknown;
625 if (isspace(*str) || (*str == ',')) {
629 for (unsigned int i = 0; i < sizeof(matrixTags) / sizeof(matrixTags[0]); i++) {
630 if (!strncmp(matrixTags[i].tag, str, matrixTags[i].sz - 1)) {
631 state = matrixTags[i].state;
632 str += (matrixTags[i].sz - 1);
636 if (state == MatrixState::Unknown) goto error;
638 str = _skipSpace(str, end);
639 if (*str != '(') goto error;
641 str = _parseNumbersArray(str, points, &ptCount, POINT_CNT);
642 if (*str != ')') goto error;
645 if (state == MatrixState::Matrix) {
646 if (ptCount != 6) goto error;
647 Matrix tmp = {points[0], points[2], points[4], points[1], points[3], points[5], 0, 0, 1};
648 _matrixCompose(matrix, &tmp, matrix);
649 } else if (state == MatrixState::Translate) {
651 Matrix tmp = {1, 0, points[0], 0, 1, 0, 0, 0, 1};
652 _matrixCompose(matrix, &tmp, matrix);
653 } else if (ptCount == 2) {
654 Matrix tmp = {1, 0, points[0], 0, 1, points[1], 0, 0, 1};
655 _matrixCompose(matrix, &tmp, matrix);
657 } else if (state == MatrixState::Rotate) {
658 //Transform to signed.
659 points[0] = fmod(points[0], 360);
660 if (points[0] < 0) points[0] += 360;
661 auto c = cosf(points[0] * (M_PI / 180.0));
662 auto s = sinf(points[0] * (M_PI / 180.0));
664 Matrix tmp = { c, -s, 0, s, c, 0, 0, 0, 1 };
665 _matrixCompose(matrix, &tmp, matrix);
666 } else if (ptCount == 3) {
667 Matrix tmp = { 1, 0, points[1], 0, 1, points[2], 0, 0, 1 };
668 _matrixCompose(matrix, &tmp, matrix);
669 tmp = { c, -s, 0, s, c, 0, 0, 0, 1 };
670 _matrixCompose(matrix, &tmp, matrix);
671 tmp = { 1, 0, -points[1], 0, 1, -points[2], 0, 0, 1 };
672 _matrixCompose(matrix, &tmp, matrix);
676 } else if (state == MatrixState::Scale) {
677 if (ptCount < 1 || ptCount > 2) goto error;
680 if (ptCount == 2) sy = points[1];
681 Matrix tmp = { sx, 0, 0, 0, sy, 0, 0, 0, 1 };
682 _matrixCompose(matrix, &tmp, matrix);
687 if (matrix) free(matrix);
692 #define LENGTH_DEF(Name, Value) \
694 #Name, sizeof(#Name), Value \
698 static constexpr struct
704 LENGTH_DEF(%, SvgLengthType::Percent),
705 LENGTH_DEF(px, SvgLengthType::Px),
706 LENGTH_DEF(pc, SvgLengthType::Pc),
707 LENGTH_DEF(pt, SvgLengthType::Pt),
708 LENGTH_DEF(mm, SvgLengthType::Mm),
709 LENGTH_DEF(cm, SvgLengthType::Cm),
710 LENGTH_DEF(in, SvgLengthType::In)
714 static float _parseLength(const char* str, SvgLengthType* type)
717 int sz = strlen(str);
719 *type = SvgLengthType::Px;
720 for (unsigned int i = 0; i < sizeof(lengthTags) / sizeof(lengthTags[0]); i++) {
721 if (lengthTags[i].sz - 1 == sz && !strncmp(lengthTags[i].tag, str, sz)) *type = lengthTags[i].type;
723 value = svgUtilStrtof(str, nullptr);
728 static bool _parseStyleAttr(void* data, const char* key, const char* value);
729 static bool _parseStyleAttr(void* data, const char* key, const char* value, bool style);
732 static bool _attrParseSvgNode(void* data, const char* key, const char* value)
734 SvgLoaderData* loader = (SvgLoaderData*)data;
735 SvgNode* node = loader->svgParse->node;
736 SvgDocNode* doc = &(node->node.doc);
739 //TODO: handle length unit.
740 if (!strcmp(key, "width")) {
741 doc->w = _parseLength(value, &type);
742 } else if (!strcmp(key, "height")) {
743 doc->h = _parseLength(value, &type);
744 } else if (!strcmp(key, "viewBox")) {
745 if (_parseNumber(&value, &doc->vx)) {
746 if (_parseNumber(&value, &doc->vy)) {
747 if (_parseNumber(&value, &doc->vw)) {
748 _parseNumber(&value, &doc->vh);
749 loader->svgParse->global.h = doc->vh;
751 loader->svgParse->global.w = doc->vw;
753 loader->svgParse->global.y = doc->vy;
755 loader->svgParse->global.x = doc->vx;
756 } else if (!strcmp(key, "preserveAspectRatio")) {
757 if (!strcmp(value, "none")) doc->preserveAspect = false;
758 } else if (!strcmp(key, "style")) {
759 return simpleXmlParseW3CAttribute(value, _parseStyleAttr, loader);
761 #ifdef THORVG_LOG_ENABLED
762 else if (!strcmp(key, "x") || !strcmp(key, "y")) {
763 if (0.0f == _parseLength(value, &type)) TVGLOG("SVG", "Unsupported attributes used [Elements type: Svg][Attribute: %s][Value: %s]", key, value);
767 return _parseStyleAttr(loader, key, value, false);
773 //https://www.w3.org/TR/SVGTiny12/painting.html#SpecifyingPaint
774 static void _handlePaintAttr(SvgPaint* paint, const char* value)
776 if (!strcmp(value, "none")) {
782 if (!strcmp(value, "currentColor")) {
783 paint->curColor = true;
786 _toColor(value, &paint->color.r, &paint->color.g, &paint->color.b, &paint->url);
790 static void _handleColorAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, const char* value)
792 SvgStyleProperty* style = node->style;
793 style->curColorSet = true;
794 _toColor(value, &style->color.r, &style->color.g, &style->color.b, nullptr);
798 static void _handleFillAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, const char* value)
800 SvgStyleProperty* style = node->style;
801 style->fill.flags = (SvgFillFlags)((int)style->fill.flags | (int)SvgFillFlags::Paint);
802 _handlePaintAttr(&style->fill.paint, value);
806 static void _handleStrokeAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, const char* value)
808 SvgStyleProperty* style = node->style;
809 style->stroke.flags = (SvgStrokeFlags)((int)style->stroke.flags | (int)SvgStrokeFlags::Paint);
810 _handlePaintAttr(&style->stroke.paint, value);
814 static void _handleStrokeOpacityAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, const char* value)
816 node->style->stroke.flags = (SvgStrokeFlags)((int)node->style->stroke.flags | (int)SvgStrokeFlags::Opacity);
817 node->style->stroke.opacity = _toOpacity(value);
820 static void _handleStrokeDashArrayAttr(SvgLoaderData* loader, SvgNode* node, const char* value)
822 node->style->stroke.flags = (SvgStrokeFlags)((int)node->style->stroke.flags | (int)SvgStrokeFlags::Dash);
823 _parseDashArray(loader, value, &node->style->stroke.dash);
826 static void _handleStrokeWidthAttr(SvgLoaderData* loader, SvgNode* node, const char* value)
828 node->style->stroke.flags = (SvgStrokeFlags)((int)node->style->stroke.flags | (int)SvgStrokeFlags::Width);
829 node->style->stroke.width = _toFloat(loader->svgParse, value, SvgParserLengthType::Horizontal);
833 static void _handleStrokeLineCapAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, const char* value)
835 node->style->stroke.flags = (SvgStrokeFlags)((int)node->style->stroke.flags | (int)SvgStrokeFlags::Cap);
836 node->style->stroke.cap = _toLineCap(value);
840 static void _handleStrokeLineJoinAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, const char* value)
842 node->style->stroke.flags = (SvgStrokeFlags)((int)node->style->stroke.flags | (int)SvgStrokeFlags::Join);
843 node->style->stroke.join = _toLineJoin(value);
847 static void _handleFillRuleAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, const char* value)
849 node->style->fill.flags = (SvgFillFlags)((int)node->style->fill.flags | (int)SvgFillFlags::FillRule);
850 node->style->fill.fillRule = _toFillRule(value);
854 static void _handleOpacityAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, const char* value)
856 node->style->opacity = _toOpacity(value);
860 static void _handleFillOpacityAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, const char* value)
862 node->style->fill.flags = (SvgFillFlags)((int)node->style->fill.flags | (int)SvgFillFlags::Opacity);
863 node->style->fill.opacity = _toOpacity(value);
867 static void _handleTransformAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, const char* value)
869 node->transform = _parseTransformationMatrix(value);
873 static void _handleClipPathAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, const char* value)
875 SvgStyleProperty* style = node->style;
876 int len = strlen(value);
877 if (len >= 3 && !strncmp(value, "url", 3)) {
878 if (style->clipPath.url) delete(style->clipPath.url);
879 style->clipPath.url = _idFromUrl((const char*)(value + 3));
884 static void _handleMaskAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, const char* value)
886 SvgStyleProperty* style = node->style;
887 int len = strlen(value);
888 if (len >= 3 && !strncmp(value, "url", 3)) {
889 if (style->mask.url) delete(style->mask.url);
890 style->mask.url = _idFromUrl((const char*)(value + 3));
895 static void _handleDisplayAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, const char* value)
897 //TODO : The display attribute can have various values as well as "none".
898 // The default is "inline" which means visible and "none" means invisible.
899 // Depending on the type of node, additional functionality may be required.
900 // refer to https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/display
901 if (!strcmp(value, "none")) node->display = false;
902 else node->display = true;
906 typedef void (*styleMethod)(SvgLoaderData* loader, SvgNode* node, const char* value);
908 #define STYLE_DEF(Name, Name1, Flag) { #Name, sizeof(#Name), _handle##Name1##Attr, Flag }
911 static constexpr struct
915 styleMethod tagHandler;
918 STYLE_DEF(color, Color, SvgStyleFlags::Color),
919 STYLE_DEF(fill, Fill, SvgStyleFlags::Fill),
920 STYLE_DEF(fill-rule, FillRule, SvgStyleFlags::FillRule),
921 STYLE_DEF(fill-opacity, FillOpacity, SvgStyleFlags::FillOpacity),
922 STYLE_DEF(opacity, Opacity, SvgStyleFlags::Opacity),
923 STYLE_DEF(stroke, Stroke, SvgStyleFlags::Stroke),
924 STYLE_DEF(stroke-width, StrokeWidth, SvgStyleFlags::StrokeWidth),
925 STYLE_DEF(stroke-linejoin, StrokeLineJoin, SvgStyleFlags::StrokeLineJoin),
926 STYLE_DEF(stroke-linecap, StrokeLineCap, SvgStyleFlags::StrokeLineCap),
927 STYLE_DEF(stroke-opacity, StrokeOpacity, SvgStyleFlags::StrokeOpacity),
928 STYLE_DEF(stroke-dasharray, StrokeDashArray, SvgStyleFlags::StrokeDashArray),
929 STYLE_DEF(transform, Transform, SvgStyleFlags::Transform),
930 STYLE_DEF(clip-path, ClipPath, SvgStyleFlags::ClipPath),
931 STYLE_DEF(mask, Mask, SvgStyleFlags::Mask),
932 STYLE_DEF(display, Display, SvgStyleFlags::Display)
936 static bool _parseStyleAttr(void* data, const char* key, const char* value, bool style)
938 SvgLoaderData* loader = (SvgLoaderData*)data;
939 SvgNode* node = loader->svgParse->node;
941 if (!key || !value) return false;
943 //Trim the white space
944 key = _skipSpace(key, nullptr);
945 value = _skipSpace(value, nullptr);
948 for (unsigned int i = 0; i < sizeof(styleTags) / sizeof(styleTags[0]); i++) {
949 if (styleTags[i].sz - 1 == sz && !strncmp(styleTags[i].tag, key, sz)) {
951 styleTags[i].tagHandler(loader, node, value);
952 node->style->flags = (SvgStyleFlags)((int)node->style->flags | (int)styleTags[i].flag);
953 } else if (!((int)node->style->flags & (int)styleTags[i].flag)) {
954 styleTags[i].tagHandler(loader, node, value);
964 static bool _parseStyleAttr(void* data, const char* key, const char* value)
966 return _parseStyleAttr(data, key, value, true);
971 * https://www.w3.org/TR/SVG/struct.html#Groups
973 static bool _attrParseGNode(void* data, const char* key, const char* value)
975 SvgLoaderData* loader = (SvgLoaderData*)data;
976 SvgNode* node = loader->svgParse->node;
978 if (!strcmp(key, "style")) {
979 return simpleXmlParseW3CAttribute(value, _parseStyleAttr, loader);
980 } else if (!strcmp(key, "transform")) {
981 node->transform = _parseTransformationMatrix(value);
982 } else if (!strcmp(key, "id")) {
983 if (node->id && value) delete node->id;
984 node->id = _copyId(value);
985 } else if (!strcmp(key, "clip-path")) {
986 _handleClipPathAttr(loader, node, value);
987 } else if (!strcmp(key, "mask")) {
988 _handleMaskAttr(loader, node, value);
990 return _parseStyleAttr(loader, key, value, false);
996 /* parse clipPath node
997 * https://www.w3.org/TR/SVG/struct.html#Groups
999 static bool _attrParseClipPathNode(void* data, const char* key, const char* value)
1001 SvgLoaderData* loader = (SvgLoaderData*)data;
1002 SvgNode* node = loader->svgParse->node;
1004 if (!strcmp(key, "style")) {
1005 return simpleXmlParseW3CAttribute(value, _parseStyleAttr, loader);
1006 } else if (!strcmp(key, "transform")) {
1007 node->transform = _parseTransformationMatrix(value);
1008 } else if (!strcmp(key, "id")) {
1009 if (node->id && value) delete node->id;
1010 node->id = _copyId(value);
1012 return _parseStyleAttr(loader, key, value, false);
1018 static bool _attrParseMaskNode(void* data, const char* key, const char* value)
1020 SvgLoaderData* loader = (SvgLoaderData*)data;
1021 SvgNode* node = loader->svgParse->node;
1023 if (!strcmp(key, "style")) {
1024 return simpleXmlParseW3CAttribute(value, _parseStyleAttr, loader);
1025 } else if (!strcmp(key, "transform")) {
1026 node->transform = _parseTransformationMatrix(value);
1027 } else if (!strcmp(key, "id")) {
1028 if (node->id && value) delete node->id;
1029 node->id = _copyId(value);
1031 return _parseStyleAttr(loader, key, value, false);
1037 static SvgNode* _createNode(SvgNode* parent, SvgNodeType type)
1039 SvgNode* node = (SvgNode*)calloc(1, sizeof(SvgNode));
1041 if (!node) return nullptr;
1043 //Default fill property
1044 node->style = (SvgStyleProperty*)calloc(1, sizeof(SvgStyleProperty));
1051 //Update the default value of stroke and fill
1052 //https://www.w3.org/TR/SVGTiny12/painting.html#SpecifyingPaint
1053 node->style->fill.paint.none = false;
1054 //Default fill opacity is 1
1055 node->style->fill.opacity = 255;
1056 node->style->opacity = 255;
1057 //Default current color is not set
1058 node->style->fill.paint.curColor = false;
1059 node->style->curColorSet = false;
1060 //Default fill rule is nonzero
1061 node->style->fill.fillRule = FillRule::Winding;
1063 //Default stroke is none
1064 node->style->stroke.paint.none = true;
1065 //Default stroke opacity is 1
1066 node->style->stroke.opacity = 255;
1067 //Default stroke current color is not set
1068 node->style->stroke.paint.curColor = false;
1069 //Default stroke width is 1
1070 node->style->stroke.width = 1;
1071 //Default line cap is butt
1072 node->style->stroke.cap = StrokeCap::Butt;
1073 //Default line join is miter
1074 node->style->stroke.join = StrokeJoin::Miter;
1075 node->style->stroke.scale = 1.0;
1077 //Default display is true("inline").
1078 node->display = true;
1080 node->parent = parent;
1083 if (parent) parent->child.push(node);
1088 static SvgNode* _createDefsNode(TVG_UNUSED SvgLoaderData* loader, TVG_UNUSED SvgNode* parent, const char* buf, unsigned bufLength)
1090 if (loader->def && loader->doc->node.doc.defs) return loader->def;
1091 SvgNode* node = _createNode(nullptr, SvgNodeType::Defs);
1094 loader->doc->node.doc.defs = node;
1099 static SvgNode* _createGNode(TVG_UNUSED SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength)
1101 loader->svgParse->node = _createNode(parent, SvgNodeType::G);
1102 if (!loader->svgParse->node) return nullptr;
1104 simpleXmlParseAttributes(buf, bufLength, _attrParseGNode, loader);
1105 return loader->svgParse->node;
1109 static SvgNode* _createSvgNode(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength)
1111 loader->svgParse->node = _createNode(parent, SvgNodeType::Doc);
1112 if (!loader->svgParse->node) return nullptr;
1113 SvgDocNode* doc = &(loader->svgParse->node->node.doc);
1115 loader->svgParse->global.w = 0;
1116 loader->svgParse->global.h = 0;
1118 doc->preserveAspect = true;
1119 simpleXmlParseAttributes(buf, bufLength, _attrParseSvgNode, loader);
1121 if (loader->svgParse->global.w == 0) {
1122 if (doc->w < FLT_EPSILON) loader->svgParse->global.w = 1;
1123 else loader->svgParse->global.w = doc->w;
1125 if (loader->svgParse->global.h == 0) {
1126 if (doc->h < FLT_EPSILON) loader->svgParse->global.h = 1;
1127 else loader->svgParse->global.h =doc->h;
1130 return loader->svgParse->node;
1134 static SvgNode* _createMaskNode(SvgLoaderData* loader, SvgNode* parent, TVG_UNUSED const char* buf, TVG_UNUSED unsigned bufLength)
1136 loader->svgParse->node = _createNode(parent, SvgNodeType::Mask);
1137 if (!loader->svgParse->node) return nullptr;
1139 simpleXmlParseAttributes(buf, bufLength, _attrParseMaskNode, loader);
1141 return loader->svgParse->node;
1145 static SvgNode* _createClipPathNode(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength)
1147 loader->svgParse->node = _createNode(parent, SvgNodeType::ClipPath);
1149 if (!loader->svgParse->node) return nullptr;
1151 loader->svgParse->node->display = false;
1153 simpleXmlParseAttributes(buf, bufLength, _attrParseClipPathNode, loader);
1155 return loader->svgParse->node;
1158 static bool _attrParsePathNode(void* data, const char* key, const char* value)
1160 SvgLoaderData* loader = (SvgLoaderData*)data;
1161 SvgNode* node = loader->svgParse->node;
1162 SvgPathNode* path = &(node->node.path);
1164 if (!strcmp(key, "d")) {
1165 //Temporary: need to copy
1166 path->path = _copyId(value);
1167 } else if (!strcmp(key, "style")) {
1168 return simpleXmlParseW3CAttribute(value, _parseStyleAttr, loader);
1169 } else if (!strcmp(key, "clip-path")) {
1170 _handleClipPathAttr(loader, node, value);
1171 } else if (!strcmp(key, "mask")) {
1172 _handleMaskAttr(loader, node, value);
1173 } else if (!strcmp(key, "id")) {
1174 if (node->id && value) delete node->id;
1175 node->id = _copyId(value);
1177 return _parseStyleAttr(loader, key, value, false);
1183 static SvgNode* _createPathNode(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength)
1185 loader->svgParse->node = _createNode(parent, SvgNodeType::Path);
1187 if (!loader->svgParse->node) return nullptr;
1189 simpleXmlParseAttributes(buf, bufLength, _attrParsePathNode, loader);
1191 return loader->svgParse->node;
1195 static constexpr struct
1198 SvgParserLengthType type;
1202 {"cx", SvgParserLengthType::Horizontal, sizeof("cx"), offsetof(SvgCircleNode, cx)},
1203 {"cy", SvgParserLengthType::Vertical, sizeof("cy"), offsetof(SvgCircleNode, cy)},
1204 {"r", SvgParserLengthType::Other, sizeof("r"), offsetof(SvgCircleNode, r)}
1208 /* parse the attributes for a circle element.
1209 * https://www.w3.org/TR/SVG/shapes.html#CircleElement
1211 static bool _attrParseCircleNode(void* data, const char* key, const char* value)
1213 SvgLoaderData* loader = (SvgLoaderData*)data;
1214 SvgNode* node = loader->svgParse->node;
1215 SvgCircleNode* circle = &(node->node.circle);
1216 unsigned char* array;
1217 int sz = strlen(key);
1219 array = (unsigned char*)circle;
1220 for (unsigned int i = 0; i < sizeof(circleTags) / sizeof(circleTags[0]); i++) {
1221 if (circleTags[i].sz - 1 == sz && !strncmp(circleTags[i].tag, key, sz)) {
1222 *((float*)(array + circleTags[i].offset)) = _toFloat(loader->svgParse, value, circleTags[i].type);
1227 if (!strcmp(key, "style")) {
1228 return simpleXmlParseW3CAttribute(value, _parseStyleAttr, loader);
1229 } else if (!strcmp(key, "clip-path")) {
1230 _handleClipPathAttr(loader, node, value);
1231 } else if (!strcmp(key, "mask")) {
1232 _handleMaskAttr(loader, node, value);
1233 } else if (!strcmp(key, "id")) {
1234 if (node->id && value) delete node->id;
1235 node->id = _copyId(value);
1237 return _parseStyleAttr(loader, key, value, false);
1243 static SvgNode* _createCircleNode(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength)
1245 loader->svgParse->node = _createNode(parent, SvgNodeType::Circle);
1247 if (!loader->svgParse->node) return nullptr;
1249 simpleXmlParseAttributes(buf, bufLength, _attrParseCircleNode, loader);
1250 return loader->svgParse->node;
1254 static constexpr struct
1257 SvgParserLengthType type;
1261 {"cx", SvgParserLengthType::Horizontal, sizeof("cx"), offsetof(SvgEllipseNode, cx)},
1262 {"cy", SvgParserLengthType::Vertical, sizeof("cy"), offsetof(SvgEllipseNode, cy)},
1263 {"rx", SvgParserLengthType::Horizontal, sizeof("rx"), offsetof(SvgEllipseNode, rx)},
1264 {"ry", SvgParserLengthType::Vertical, sizeof("ry"), offsetof(SvgEllipseNode, ry)}
1268 /* parse the attributes for an ellipse element.
1269 * https://www.w3.org/TR/SVG/shapes.html#EllipseElement
1271 static bool _attrParseEllipseNode(void* data, const char* key, const char* value)
1273 SvgLoaderData* loader = (SvgLoaderData*)data;
1274 SvgNode* node = loader->svgParse->node;
1275 SvgEllipseNode* ellipse = &(node->node.ellipse);
1276 unsigned char* array;
1277 int sz = strlen(key);
1279 array = (unsigned char*)ellipse;
1280 for (unsigned int i = 0; i < sizeof(ellipseTags) / sizeof(ellipseTags[0]); i++) {
1281 if (ellipseTags[i].sz - 1 == sz && !strncmp(ellipseTags[i].tag, key, sz)) {
1282 *((float*)(array + ellipseTags[i].offset)) = _toFloat(loader->svgParse, value, ellipseTags[i].type);
1287 if (!strcmp(key, "id")) {
1288 if (node->id && value) delete node->id;
1289 node->id = _copyId(value);
1290 } else if (!strcmp(key, "style")) {
1291 return simpleXmlParseW3CAttribute(value, _parseStyleAttr, loader);
1292 } else if (!strcmp(key, "clip-path")) {
1293 _handleClipPathAttr(loader, node, value);
1294 } else if (!strcmp(key, "mask")) {
1295 _handleMaskAttr(loader, node, value);
1297 return _parseStyleAttr(loader, key, value, false);
1303 static SvgNode* _createEllipseNode(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength)
1305 loader->svgParse->node = _createNode(parent, SvgNodeType::Ellipse);
1307 if (!loader->svgParse->node) return nullptr;
1309 simpleXmlParseAttributes(buf, bufLength, _attrParseEllipseNode, loader);
1310 return loader->svgParse->node;
1314 static bool _attrParsePolygonPoints(const char* str, float** points, int* ptCount)
1320 float *pointArray = nullptr, *tmpArray;
1322 while (_parseNumber(&str, &num)) {
1323 tmp[tmpCount++] = num;
1324 if (tmpCount == 50) {
1325 tmpArray = (float*)realloc(pointArray, (count + tmpCount) * sizeof(float));
1326 if (!tmpArray) goto error_alloc;
1327 pointArray = tmpArray;
1328 memcpy(&pointArray[count], tmp, tmpCount * sizeof(float));
1335 tmpArray = (float*)realloc(pointArray, (count + tmpCount) * sizeof(float));
1336 if (!tmpArray) goto error_alloc;
1337 pointArray = tmpArray;
1338 memcpy(&pointArray[count], tmp, tmpCount * sizeof(float));
1342 *points = pointArray;
1350 /* parse the attributes for a polygon element.
1351 * https://www.w3.org/TR/SVG/shapes.html#PolylineElement
1353 static bool _attrParsePolygonNode(void* data, const char* key, const char* value)
1355 SvgLoaderData* loader = (SvgLoaderData*)data;
1356 SvgNode* node = loader->svgParse->node;
1357 SvgPolygonNode* polygon = nullptr;
1359 if (node->type == SvgNodeType::Polygon) polygon = &(node->node.polygon);
1360 else polygon = &(node->node.polyline);
1362 if (!strcmp(key, "points")) {
1363 return _attrParsePolygonPoints(value, &polygon->points, &polygon->pointsCount);
1364 } else if (!strcmp(key, "style")) {
1365 return simpleXmlParseW3CAttribute(value, _parseStyleAttr, loader);
1366 } else if (!strcmp(key, "clip-path")) {
1367 _handleClipPathAttr(loader, node, value);
1368 } else if (!strcmp(key, "mask")) {
1369 _handleMaskAttr(loader, node, value);
1370 } else if (!strcmp(key, "id")) {
1371 if (node->id && value) delete node->id;
1372 node->id = _copyId(value);
1374 return _parseStyleAttr(loader, key, value, false);
1380 static SvgNode* _createPolygonNode(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength)
1382 loader->svgParse->node = _createNode(parent, SvgNodeType::Polygon);
1384 if (!loader->svgParse->node) return nullptr;
1386 simpleXmlParseAttributes(buf, bufLength, _attrParsePolygonNode, loader);
1387 return loader->svgParse->node;
1391 static SvgNode* _createPolylineNode(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength)
1393 loader->svgParse->node = _createNode(parent, SvgNodeType::Polyline);
1395 if (!loader->svgParse->node) return nullptr;
1397 simpleXmlParseAttributes(buf, bufLength, _attrParsePolygonNode, loader);
1398 return loader->svgParse->node;
1401 static constexpr struct
1404 SvgParserLengthType type;
1408 {"x", SvgParserLengthType::Horizontal, sizeof("x"), offsetof(SvgRectNode, x)},
1409 {"y", SvgParserLengthType::Vertical, sizeof("y"), offsetof(SvgRectNode, y)},
1410 {"width", SvgParserLengthType::Horizontal, sizeof("width"), offsetof(SvgRectNode, w)},
1411 {"height", SvgParserLengthType::Vertical, sizeof("height"), offsetof(SvgRectNode, h)},
1412 {"rx", SvgParserLengthType::Horizontal, sizeof("rx"), offsetof(SvgRectNode, rx)},
1413 {"ry", SvgParserLengthType::Vertical, sizeof("ry"), offsetof(SvgRectNode, ry)}
1417 /* parse the attributes for a rect element.
1418 * https://www.w3.org/TR/SVG/shapes.html#RectElement
1420 static bool _attrParseRectNode(void* data, const char* key, const char* value)
1422 SvgLoaderData* loader = (SvgLoaderData*)data;
1423 SvgNode* node = loader->svgParse->node;
1424 SvgRectNode* rect = &(node->node.rect);
1425 unsigned char* array;
1427 int sz = strlen(key);
1429 array = (unsigned char*)rect;
1430 for (unsigned int i = 0; i < sizeof(rectTags) / sizeof(rectTags[0]); i++) {
1431 if (rectTags[i].sz - 1 == sz && !strncmp(rectTags[i].tag, key, sz)) {
1432 *((float*)(array + rectTags[i].offset)) = _toFloat(loader->svgParse, value, rectTags[i].type);
1434 //Case if only rx or ry is declared
1435 if (!strncmp(rectTags[i].tag, "rx", sz)) rect->hasRx = true;
1436 if (!strncmp(rectTags[i].tag, "ry", sz)) rect->hasRy = true;
1438 if ((rect->rx > FLT_EPSILON) && (rect->ry <= FLT_EPSILON) && rect->hasRx && !rect->hasRy) rect->ry = rect->rx;
1439 if ((rect->ry > FLT_EPSILON) && (rect->rx <= FLT_EPSILON) && !rect->hasRx && rect->hasRy) rect->rx = rect->ry;
1444 if (!strcmp(key, "id")) {
1445 if (node->id && value) delete node->id;
1446 node->id = _copyId(value);
1447 } else if (!strcmp(key, "style")) {
1448 ret = simpleXmlParseW3CAttribute(value, _parseStyleAttr, loader);
1449 } else if (!strcmp(key, "clip-path")) {
1450 _handleClipPathAttr(loader, node, value);
1451 } else if (!strcmp(key, "mask")) {
1452 _handleMaskAttr(loader, node, value);
1454 ret = _parseStyleAttr(loader, key, value, false);
1461 static SvgNode* _createRectNode(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength)
1463 loader->svgParse->node = _createNode(parent, SvgNodeType::Rect);
1465 if (!loader->svgParse->node) return nullptr;
1467 loader->svgParse->node->node.rect.hasRx = loader->svgParse->node->node.rect.hasRy = false;
1469 simpleXmlParseAttributes(buf, bufLength, _attrParseRectNode, loader);
1470 return loader->svgParse->node;
1474 static constexpr struct
1477 SvgParserLengthType type;
1481 {"x1", SvgParserLengthType::Horizontal, sizeof("x1"), offsetof(SvgLineNode, x1)},
1482 {"y1", SvgParserLengthType::Vertical, sizeof("y1"), offsetof(SvgLineNode, y1)},
1483 {"x2", SvgParserLengthType::Horizontal, sizeof("x2"), offsetof(SvgLineNode, x2)},
1484 {"y2", SvgParserLengthType::Vertical, sizeof("y2"), offsetof(SvgLineNode, y2)}
1488 /* parse the attributes for a line element.
1489 * https://www.w3.org/TR/SVG/shapes.html#LineElement
1491 static bool _attrParseLineNode(void* data, const char* key, const char* value)
1493 SvgLoaderData* loader = (SvgLoaderData*)data;
1494 SvgNode* node = loader->svgParse->node;
1495 SvgLineNode* line = &(node->node.line);
1496 unsigned char* array;
1497 int sz = strlen(key);
1499 array = (unsigned char*)line;
1500 for (unsigned int i = 0; i < sizeof(lineTags) / sizeof(lineTags[0]); i++) {
1501 if (lineTags[i].sz - 1 == sz && !strncmp(lineTags[i].tag, key, sz)) {
1502 *((float*)(array + lineTags[i].offset)) = _toFloat(loader->svgParse, value, lineTags[i].type);
1507 if (!strcmp(key, "id")) {
1508 if (node->id && value) delete node->id;
1509 node->id = _copyId(value);
1510 } else if (!strcmp(key, "style")) {
1511 return simpleXmlParseW3CAttribute(value, _parseStyleAttr, loader);
1512 } else if (!strcmp(key, "clip-path")) {
1513 _handleClipPathAttr(loader, node, value);
1514 } else if (!strcmp(key, "mask")) {
1515 _handleMaskAttr(loader, node, value);
1517 return _parseStyleAttr(loader, key, value, false);
1523 static SvgNode* _createLineNode(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength)
1525 loader->svgParse->node = _createNode(parent, SvgNodeType::Line);
1527 if (!loader->svgParse->node) return nullptr;
1529 simpleXmlParseAttributes(buf, bufLength, _attrParseLineNode, loader);
1530 return loader->svgParse->node;
1534 static string* _idFromHref(const char* href)
1536 href = _skipSpace(href, nullptr);
1537 if ((*href) == '#') href++;
1538 return new string(href);
1542 static constexpr struct
1545 SvgParserLengthType type;
1549 {"x", SvgParserLengthType::Horizontal, sizeof("x"), offsetof(SvgRectNode, x)},
1550 {"y", SvgParserLengthType::Vertical, sizeof("y"), offsetof(SvgRectNode, y)},
1551 {"width", SvgParserLengthType::Horizontal, sizeof("width"), offsetof(SvgRectNode, w)},
1552 {"height", SvgParserLengthType::Vertical, sizeof("height"), offsetof(SvgRectNode, h)},
1556 /* parse the attributes for a image element.
1557 * https://www.w3.org/TR/SVG/embedded.html#ImageElement
1559 static bool _attrParseImageNode(void* data, const char* key, const char* value)
1561 SvgLoaderData* loader = (SvgLoaderData*)data;
1562 SvgNode* node = loader->svgParse->node;
1563 SvgImageNode* image = &(node->node.image);
1564 unsigned char* array;
1565 int sz = strlen(key);
1567 array = (unsigned char*)image;
1568 for (unsigned int i = 0; i < sizeof(imageTags) / sizeof(imageTags[0]); i++) {
1569 if (imageTags[i].sz - 1 == sz && !strncmp(imageTags[i].tag, key, sz)) {
1570 *((float*)(array + imageTags[i].offset)) = _toFloat(loader->svgParse, value, imageTags[i].type);
1575 if (!strcmp(key, "href") || !strcmp(key, "xlink:href")) {
1576 image->href = _idFromHref(value);
1577 } else if (!strcmp(key, "id")) {
1578 if (node->id && value) delete node->id;
1579 node->id = _copyId(value);
1580 } else if (!strcmp(key, "style")) {
1581 return simpleXmlParseW3CAttribute(value, _parseStyleAttr, loader);
1582 } else if (!strcmp(key, "clip-path")) {
1583 _handleClipPathAttr(loader, node, value);
1584 } else if (!strcmp(key, "mask")) {
1585 _handleMaskAttr(loader, node, value);
1587 return _parseStyleAttr(loader, key, value);
1593 static SvgNode* _createImageNode(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength)
1595 loader->svgParse->node = _createNode(parent, SvgNodeType::Image);
1597 if (!loader->svgParse->node) return nullptr;
1599 simpleXmlParseAttributes(buf, bufLength, _attrParseImageNode, loader);
1600 return loader->svgParse->node;
1604 static SvgNode* _getDefsNode(SvgNode* node)
1606 if (!node) return nullptr;
1608 while (node->parent != nullptr) {
1609 node = node->parent;
1612 if (node->type == SvgNodeType::Doc) return node->node.doc.defs;
1618 static SvgNode* _findChildById(const SvgNode* node, const char* id)
1620 if (!node) return nullptr;
1622 auto child = node->child.data;
1623 for (uint32_t i = 0; i < node->child.count; ++i, ++child) {
1624 if (((*child)->id != nullptr) && !strcmp((*child)->id->c_str(), id)) return (*child);
1629 static SvgNode* _findNodeById(SvgNode *node, string* id)
1631 SvgNode* result = nullptr;
1632 if (node->id && !node->id->compare(*id)) return node;
1634 if (node->child.count > 0) {
1635 auto child = node->child.data;
1636 for (uint32_t i = 0; i < node->child.count; ++i, ++child) {
1637 result = _findNodeById(*child, id);
1644 static void _cloneGradStops(Array<Fill::ColorStop>& dst, const Array<Fill::ColorStop>& src)
1646 for (uint32_t i = 0; i < src.count; ++i) {
1647 dst.push(src.data[i]);
1652 static SvgStyleGradient* _cloneGradient(SvgStyleGradient* from)
1654 if (!from) return nullptr;
1656 auto grad = new SvgStyleGradient;
1657 if (!grad) return nullptr;
1659 grad->type = from->type;
1660 grad->id = from->id ? _copyId(from->id->c_str()) : nullptr;
1661 grad->ref = from->ref ? _copyId(from->ref->c_str()) : nullptr;
1662 grad->spread = from->spread;
1663 grad->usePercentage = from->usePercentage;
1664 grad->userSpace = from->userSpace;
1666 if (from->transform) {
1667 grad->transform = (Matrix*)calloc(1, sizeof(Matrix));
1668 if (grad->transform) memcpy(grad->transform, from->transform, sizeof(Matrix));
1671 if (grad->type == SvgGradientType::Linear) {
1672 grad->linear = (SvgLinearGradient*)calloc(1, sizeof(SvgLinearGradient));
1673 if (!grad->linear) goto error_grad_alloc;
1674 memcpy(grad->linear, from->linear, sizeof(SvgLinearGradient));
1675 } else if (grad->type == SvgGradientType::Radial) {
1676 grad->radial = (SvgRadialGradient*)calloc(1, sizeof(SvgRadialGradient));
1677 if (!grad->radial) goto error_grad_alloc;
1678 memcpy(grad->radial, from->radial, sizeof(SvgRadialGradient));
1681 _cloneGradStops(grad->stops, from->stops);
1686 if (grad) delete(grad);
1691 static void _copyAttr(SvgNode* to, const SvgNode* from)
1693 //Copy matrix attribute
1694 if (from->transform) {
1695 to->transform = (Matrix*)malloc(sizeof(Matrix));
1696 if (to->transform) *to->transform = *from->transform;
1698 //Copy style attribute
1699 *to->style = *from->style;
1700 if (from->style->fill.paint.url) to->style->fill.paint.url = new string(from->style->fill.paint.url->c_str());
1701 if (from->style->stroke.paint.url) to->style->stroke.paint.url = new string(from->style->stroke.paint.url->c_str());
1702 if (from->style->clipPath.url) to->style->clipPath.url = new string(from->style->clipPath.url->c_str());
1703 if (from->style->mask.url) to->style->mask.url = new string(from->style->mask.url->c_str());
1705 //Copy node attribute
1706 switch (from->type) {
1707 case SvgNodeType::Circle: {
1708 to->node.circle.cx = from->node.circle.cx;
1709 to->node.circle.cy = from->node.circle.cy;
1710 to->node.circle.r = from->node.circle.r;
1713 case SvgNodeType::Ellipse: {
1714 to->node.ellipse.cx = from->node.ellipse.cx;
1715 to->node.ellipse.cy = from->node.ellipse.cy;
1716 to->node.ellipse.rx = from->node.ellipse.rx;
1717 to->node.ellipse.ry = from->node.ellipse.ry;
1720 case SvgNodeType::Rect: {
1721 to->node.rect.x = from->node.rect.x;
1722 to->node.rect.y = from->node.rect.y;
1723 to->node.rect.w = from->node.rect.w;
1724 to->node.rect.h = from->node.rect.h;
1725 to->node.rect.rx = from->node.rect.rx;
1726 to->node.rect.ry = from->node.rect.ry;
1727 to->node.rect.hasRx = from->node.rect.hasRx;
1728 to->node.rect.hasRy = from->node.rect.hasRy;
1731 case SvgNodeType::Line: {
1732 to->node.line.x1 = from->node.line.x1;
1733 to->node.line.y1 = from->node.line.y1;
1734 to->node.line.x2 = from->node.line.x2;
1735 to->node.line.y2 = from->node.line.y2;
1738 case SvgNodeType::Path: {
1739 if (from->node.path.path) to->node.path.path = new string(from->node.path.path->c_str());
1742 case SvgNodeType::Polygon: {
1743 to->node.polygon.pointsCount = from->node.polygon.pointsCount;
1744 to->node.polygon.points = (float*)malloc(to->node.polygon.pointsCount * sizeof(float));
1745 memcpy(to->node.polygon.points, from->node.polygon.points, to->node.polygon.pointsCount * sizeof(float));
1748 case SvgNodeType::Polyline: {
1749 to->node.polyline.pointsCount = from->node.polyline.pointsCount;
1750 to->node.polyline.points = (float*)malloc(to->node.polyline.pointsCount * sizeof(float));
1751 memcpy(to->node.polyline.points, from->node.polyline.points, to->node.polyline.pointsCount * sizeof(float));
1754 case SvgNodeType::Image: {
1755 to->node.image.x = from->node.image.x;
1756 to->node.image.y = from->node.image.y;
1757 to->node.image.w = from->node.image.w;
1758 to->node.image.h = from->node.image.h;
1759 if (from->node.image.href) to->node.image.href = new string(from->node.image.href->c_str());
1769 static void _cloneNode(SvgNode* from, SvgNode* parent)
1772 if (!from || !parent) return;
1774 newNode = _createNode(parent, from->type);
1776 if (!newNode) return;
1778 _copyAttr(newNode, from);
1780 auto child = from->child.data;
1781 for (uint32_t i = 0; i < from->child.count; ++i, ++child) {
1782 _cloneNode(*child, newNode);
1787 static void _postponeCloneNode(SvgLoaderData* loader, SvgNode *node, string* id) {
1788 SvgNodeIdPair nodeIdPair;
1789 nodeIdPair.node = node;
1791 loader->cloneNodes.push(nodeIdPair);
1795 static void _clonePostponedNodes(Array<SvgNodeIdPair>* cloneNodes) {
1796 for (uint32_t i = 0; i < cloneNodes->count; ++i) {
1797 SvgNodeIdPair nodeIdPair = cloneNodes->data[i];
1798 SvgNode *defs = _getDefsNode(nodeIdPair.node);
1799 SvgNode *nodeFrom = _findChildById(defs, nodeIdPair.id->c_str());
1800 _cloneNode(nodeFrom, nodeIdPair.node);
1801 delete nodeIdPair.id;
1806 static bool _attrParseUseNode(void* data, const char* key, const char* value)
1808 SvgLoaderData* loader = (SvgLoaderData*)data;
1809 SvgNode *defs, *nodeFrom, *node = loader->svgParse->node;
1812 if (!strcmp(key, "href") || !strcmp(key, "xlink:href")) {
1813 id = _idFromHref(value);
1814 defs = _getDefsNode(node);
1815 nodeFrom = _findChildById(defs, id->c_str());
1817 _cloneNode(nodeFrom, node);
1820 //some svg export software include <defs> element at the end of the file
1821 //if so the 'from' element won't be found now and we have to repeat finding
1822 //after the whole file is parsed
1823 _postponeCloneNode(loader, node, id);
1825 } else if (!strcmp(key, "clip-path")) {
1826 _handleClipPathAttr(loader, node, value);
1827 } else if (!strcmp(key, "mask")) {
1828 _handleMaskAttr(loader, node, value);
1830 return _attrParseGNode(data, key, value);
1836 static SvgNode* _createUseNode(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength)
1838 loader->svgParse->node = _createNode(parent, SvgNodeType::Use);
1840 if (!loader->svgParse->node) return nullptr;
1842 simpleXmlParseAttributes(buf, bufLength, _attrParseUseNode, loader);
1843 return loader->svgParse->node;
1846 //TODO: Implement 'text' primitive
1847 static constexpr struct
1851 FactoryMethod tagHandler;
1852 } graphicsTags[] = {
1853 {"use", sizeof("use"), _createUseNode},
1854 {"circle", sizeof("circle"), _createCircleNode},
1855 {"ellipse", sizeof("ellipse"), _createEllipseNode},
1856 {"path", sizeof("path"), _createPathNode},
1857 {"polygon", sizeof("polygon"), _createPolygonNode},
1858 {"rect", sizeof("rect"), _createRectNode},
1859 {"polyline", sizeof("polyline"), _createPolylineNode},
1860 {"line", sizeof("line"), _createLineNode},
1861 {"image", sizeof("image"), _createImageNode}
1865 static constexpr struct
1869 FactoryMethod tagHandler;
1871 {"defs", sizeof("defs"), _createDefsNode},
1872 {"g", sizeof("g"), _createGNode},
1873 {"svg", sizeof("svg"), _createSvgNode},
1874 {"mask", sizeof("mask"), _createMaskNode},
1875 {"clipPath", sizeof("clipPath"), _createClipPathNode}
1879 #define FIND_FACTORY(Short_Name, Tags_Array) \
1880 static FactoryMethod \
1881 _find##Short_Name##Factory(const char* name) \
1884 int sz = strlen(name); \
1886 for (i = 0; i < sizeof(Tags_Array) / sizeof(Tags_Array[0]); i++) { \
1887 if (Tags_Array[i].sz - 1 == sz && !strncmp(Tags_Array[i].tag, name, sz)) { \
1888 return Tags_Array[i].tagHandler; \
1894 FIND_FACTORY(Group, groupTags)
1895 FIND_FACTORY(Graphics, graphicsTags)
1898 FillSpread _parseSpreadValue(const char* value)
1900 auto spread = FillSpread::Pad;
1902 if (!strcmp(value, "reflect")) {
1903 spread = FillSpread::Reflect;
1904 } else if (!strcmp(value, "repeat")) {
1905 spread = FillSpread::Repeat;
1912 static void _handleRadialCxAttr(SvgLoaderData* loader, SvgRadialGradient* radial, const char* value)
1914 radial->cx = _gradientToFloat(loader->svgParse, value, SvgParserLengthType::Horizontal);
1915 if (!loader->svgParse->gradient.parsedFx) radial->fx = radial->cx;
1919 static void _handleRadialCyAttr(SvgLoaderData* loader, SvgRadialGradient* radial, const char* value)
1921 radial->cy = _gradientToFloat(loader->svgParse, value, SvgParserLengthType::Vertical);
1922 if (!loader->svgParse->gradient.parsedFy) radial->fy = radial->cy;
1926 static void _handleRadialFxAttr(SvgLoaderData* loader, SvgRadialGradient* radial, const char* value)
1928 radial->fx = _gradientToFloat(loader->svgParse, value, SvgParserLengthType::Horizontal);
1929 loader->svgParse->gradient.parsedFx = true;
1933 static void _handleRadialFyAttr(SvgLoaderData* loader, SvgRadialGradient* radial, const char* value)
1935 radial->fy = _gradientToFloat(loader->svgParse, value, SvgParserLengthType::Vertical);
1936 loader->svgParse->gradient.parsedFy = true;
1940 static void _handleRadialRAttr(SvgLoaderData* loader, SvgRadialGradient* radial, const char* value)
1942 radial->r = _gradientToFloat(loader->svgParse, value, SvgParserLengthType::Other);
1946 static void _recalcRadialCxAttr(SvgLoaderData* loader, SvgRadialGradient* radial, bool userSpace)
1948 if (!userSpace) radial->cx = radial->cx * loader->svgParse->global.w;
1952 static void _recalcRadialCyAttr(SvgLoaderData* loader, SvgRadialGradient* radial, bool userSpace)
1954 if (!userSpace) radial->cy = radial->cy * loader->svgParse->global.h;
1958 static void _recalcRadialFxAttr(SvgLoaderData* loader, SvgRadialGradient* radial, bool userSpace)
1960 if (!userSpace) radial->fx = radial->fx * loader->svgParse->global.w;
1964 static void _recalcRadialFyAttr(SvgLoaderData* loader, SvgRadialGradient* radial, bool userSpace)
1966 if (!userSpace) radial->fy = radial->fy * loader->svgParse->global.h;
1970 static void _recalcRadialRAttr(SvgLoaderData* loader, SvgRadialGradient* radial, bool userSpace)
1972 if (!userSpace) radial->r = radial->r * (sqrt(pow(loader->svgParse->global.h, 2) + pow(loader->svgParse->global.w, 2)) / sqrt(2.0));
1976 typedef void (*radialMethod)(SvgLoaderData* loader, SvgRadialGradient* radial, const char* value);
1977 typedef void (*radialMethodRecalc)(SvgLoaderData* loader, SvgRadialGradient* radial, bool userSpace);
1980 #define RADIAL_DEF(Name, Name1) \
1982 #Name, sizeof(#Name), _handleRadial##Name1##Attr, _recalcRadial##Name1##Attr \
1986 static constexpr struct
1990 radialMethod tagHandler;
1991 radialMethodRecalc tagRecalc;
2001 static bool _attrParseRadialGradientNode(void* data, const char* key, const char* value)
2003 SvgLoaderData* loader = (SvgLoaderData*)data;
2004 SvgStyleGradient* grad = loader->svgParse->styleGrad;
2005 SvgRadialGradient* radial = grad->radial;
2006 int sz = strlen(key);
2008 for (unsigned int i = 0; i < sizeof(radialTags) / sizeof(radialTags[0]); i++) {
2009 if (radialTags[i].sz - 1 == sz && !strncmp(radialTags[i].tag, key, sz)) {
2010 radialTags[i].tagHandler(loader, radial, value);
2015 if (!strcmp(key, "id")) {
2016 grad->id = _copyId(value);
2017 } else if (!strcmp(key, "spreadMethod")) {
2018 grad->spread = _parseSpreadValue(value);
2019 } else if (!strcmp(key, "href") || !strcmp(key, "xlink:href")) {
2020 grad->ref = _idFromHref(value);
2021 } else if (!strcmp(key, "gradientUnits") && !strcmp(value, "userSpaceOnUse")) {
2022 grad->userSpace = true;
2023 } else if (!strcmp(key, "gradientTransform")) {
2024 grad->transform = _parseTransformationMatrix(value);
2033 static SvgStyleGradient* _createRadialGradient(SvgLoaderData* loader, const char* buf, unsigned bufLength)
2035 auto grad = new SvgStyleGradient;
2036 if (!grad) return nullptr;
2038 loader->svgParse->styleGrad = grad;
2040 grad->type = SvgGradientType::Radial;
2041 grad->userSpace = false;
2042 grad->radial = (SvgRadialGradient*)calloc(1, sizeof(SvgRadialGradient));
2043 if (!grad->radial) {
2048 * Default values of gradient transformed into global percentage
2050 grad->radial->cx = 0.5f / loader->svgParse->global.w;
2051 grad->radial->cy = 0.5f / loader->svgParse->global.h;
2052 grad->radial->fx = 0.5f / loader->svgParse->global.w;
2053 grad->radial->fy = 0.5f / loader->svgParse->global.h;
2054 grad->radial->r = 0.5f / (sqrt(pow(loader->svgParse->global.h, 2) + pow(loader->svgParse->global.w, 2)) / sqrt(2.0f));
2056 loader->svgParse->gradient.parsedFx = false;
2057 loader->svgParse->gradient.parsedFy = false;
2058 simpleXmlParseAttributes(buf, bufLength,
2059 _attrParseRadialGradientNode, loader);
2061 for (unsigned int i = 0; i < sizeof(radialTags) / sizeof(radialTags[0]); i++) {
2062 radialTags[i].tagRecalc(loader, grad->radial, grad->userSpace);
2065 grad->usePercentage = true;
2067 return loader->svgParse->styleGrad;
2071 static bool _attrParseStopsStyle(void* data, const char* key, const char* value)
2073 SvgLoaderData* loader = (SvgLoaderData*)data;
2074 auto stop = &loader->svgParse->gradStop;
2076 if (!strcmp(key, "stop-opacity")) {
2077 stop->a = _toOpacity(value);
2078 loader->svgParse->flags = (SvgStopStyleFlags)((int)loader->svgParse->flags | (int)SvgStopStyleFlags::StopOpacity);
2079 } else if (!strcmp(key, "stop-color")) {
2080 _toColor(value, &stop->r, &stop->g, &stop->b, nullptr);
2081 loader->svgParse->flags = (SvgStopStyleFlags)((int)loader->svgParse->flags | (int)SvgStopStyleFlags::StopColor);
2090 static bool _attrParseStops(void* data, const char* key, const char* value)
2092 SvgLoaderData* loader = (SvgLoaderData*)data;
2093 auto stop = &loader->svgParse->gradStop;
2095 if (!strcmp(key, "offset")) {
2096 stop->offset = _toOffset(value);
2097 } else if (!strcmp(key, "stop-opacity")) {
2098 if (!((int)loader->svgParse->flags & (int)SvgStopStyleFlags::StopOpacity)) {
2099 stop->a = _toOpacity(value);
2101 } else if (!strcmp(key, "stop-color")) {
2102 if (!((int)loader->svgParse->flags & (int)SvgStopStyleFlags::StopColor)) {
2103 _toColor(value, &stop->r, &stop->g, &stop->b, nullptr);
2105 } else if (!strcmp(key, "style")) {
2106 simpleXmlParseW3CAttribute(value, _attrParseStopsStyle, data);
2115 static void _handleLinearX1Attr(SvgLoaderData* loader, SvgLinearGradient* linear, const char* value)
2117 linear->x1 = _gradientToFloat(loader->svgParse, value, SvgParserLengthType::Horizontal);
2121 static void _handleLinearY1Attr(SvgLoaderData* loader, SvgLinearGradient* linear, const char* value)
2123 linear->y1 = _gradientToFloat(loader->svgParse, value, SvgParserLengthType::Vertical);
2127 static void _handleLinearX2Attr(SvgLoaderData* loader, SvgLinearGradient* linear, const char* value)
2129 linear->x2 = _gradientToFloat(loader->svgParse, value, SvgParserLengthType::Horizontal);
2133 static void _handleLinearY2Attr(SvgLoaderData* loader, SvgLinearGradient* linear, const char* value)
2135 linear->y2 = _gradientToFloat(loader->svgParse, value, SvgParserLengthType::Vertical);
2139 static void _recalcLinearX1Attr(SvgLoaderData* loader, SvgLinearGradient* linear, bool userSpace)
2141 if (!userSpace) linear->x1 = linear->x1 * loader->svgParse->global.w;
2145 static void _recalcLinearY1Attr(SvgLoaderData* loader, SvgLinearGradient* linear, bool userSpace)
2147 if (!userSpace) linear->y1 = linear->y1 * loader->svgParse->global.h;
2151 static void _recalcLinearX2Attr(SvgLoaderData* loader, SvgLinearGradient* linear, bool userSpace)
2153 if (!userSpace) linear->x2 = linear->x2 * loader->svgParse->global.w;
2157 static void _recalcLinearY2Attr(SvgLoaderData* loader, SvgLinearGradient* linear, bool userSpace)
2159 if (!userSpace) linear->y2 = linear->y2 * loader->svgParse->global.h;
2163 typedef void (*Linear_Method)(SvgLoaderData* loader, SvgLinearGradient* linear, const char* value);
2164 typedef void (*Linear_Method_Recalc)(SvgLoaderData* loader, SvgLinearGradient* linear, bool userSpace);
2167 #define LINEAR_DEF(Name, Name1) \
2169 #Name, sizeof(#Name), _handleLinear##Name1##Attr, _recalcLinear##Name1##Attr \
2173 static constexpr struct
2177 Linear_Method tagHandler;
2178 Linear_Method_Recalc tagRecalc;
2187 static bool _attrParseLinearGradientNode(void* data, const char* key, const char* value)
2189 SvgLoaderData* loader = (SvgLoaderData*)data;
2190 SvgStyleGradient* grad = loader->svgParse->styleGrad;
2191 SvgLinearGradient* linear = grad->linear;
2192 int sz = strlen(key);
2194 for (unsigned int i = 0; i < sizeof(linear_tags) / sizeof(linear_tags[0]); i++) {
2195 if (linear_tags[i].sz - 1 == sz && !strncmp(linear_tags[i].tag, key, sz)) {
2196 linear_tags[i].tagHandler(loader, linear, value);
2201 if (!strcmp(key, "id")) {
2202 grad->id = _copyId(value);
2203 } else if (!strcmp(key, "spreadMethod")) {
2204 grad->spread = _parseSpreadValue(value);
2205 } else if (!strcmp(key, "href") || !strcmp(key, "xlink:href")) {
2206 grad->ref = _idFromHref(value);
2207 } else if (!strcmp(key, "gradientUnits") && !strcmp(value, "userSpaceOnUse")) {
2208 grad->userSpace = true;
2209 } else if (!strcmp(key, "gradientTransform")) {
2210 grad->transform = _parseTransformationMatrix(value);
2219 static SvgStyleGradient* _createLinearGradient(SvgLoaderData* loader, const char* buf, unsigned bufLength)
2221 auto grad = new SvgStyleGradient;
2222 if (!grad) return nullptr;
2224 loader->svgParse->styleGrad = grad;
2226 grad->type = SvgGradientType::Linear;
2227 grad->userSpace = false;
2228 grad->linear = (SvgLinearGradient*)calloc(1, sizeof(SvgLinearGradient));
2229 if (!grad->linear) {
2234 * Default value of x2 is 100% - transformed to the global percentage
2236 grad->linear->x2 = 1.0f / loader->svgParse->global.w;
2237 simpleXmlParseAttributes(buf, bufLength, _attrParseLinearGradientNode, loader);
2239 for (unsigned int i = 0; i < sizeof(linear_tags) / sizeof(linear_tags[0]); i++) {
2240 linear_tags[i].tagRecalc(loader, grad->linear, grad->userSpace);
2243 grad->usePercentage = true;
2245 return loader->svgParse->styleGrad;
2249 #define GRADIENT_DEF(Name, Name1) \
2251 #Name, sizeof(#Name), _create##Name1 \
2256 * For all Gradients lengths would be calculated into percentages related to
2257 * canvas width and height.
2259 * if user then recalculate actual pixels into percentages
2261 static constexpr struct
2265 GradientFactoryMethod tagHandler;
2266 } gradientTags[] = {
2267 GRADIENT_DEF(linearGradient, LinearGradient),
2268 GRADIENT_DEF(radialGradient, RadialGradient)
2272 static GradientFactoryMethod _findGradientFactory(const char* name)
2274 int sz = strlen(name);
2276 for (unsigned int i = 0; i < sizeof(gradientTags) / sizeof(gradientTags[0]); i++) {
2277 if (gradientTags[i].sz - 1 == sz && !strncmp(gradientTags[i].tag, name, sz)) {
2278 return gradientTags[i].tagHandler;
2285 static constexpr struct
2291 {"svg", sizeof("svg")},
2292 {"defs", sizeof("defs")},
2293 {"mask", sizeof("mask")},
2294 {"clipPath", sizeof("clipPath")}
2298 static void _svgLoaderParerXmlClose(SvgLoaderData* loader, const char* content)
2300 content = _skipSpace(content, nullptr);
2302 for (unsigned int i = 0; i < sizeof(popArray) / sizeof(popArray[0]); i++) {
2303 if (!strncmp(content, popArray[i].tag, popArray[i].sz - 1)) {
2304 loader->stack.pop();
2313 static void _svgLoaderParserXmlOpen(SvgLoaderData* loader, const char* content, unsigned int length, bool empty)
2315 const char* attrs = nullptr;
2316 int attrsLength = 0;
2318 char tagName[20] = "";
2319 FactoryMethod method;
2320 GradientFactoryMethod gradientMethod;
2321 SvgNode *node = nullptr, *parent = nullptr;
2323 attrs = simpleXmlFindAttributesTag(content, length);
2326 //Parse the empty tag
2328 while ((attrs != nullptr) && *attrs != '>') attrs++;
2332 //Find out the tag name starting from content till sz length
2333 sz = attrs - content;
2334 while ((sz > 0) && (isspace(content[sz - 1]))) sz--;
2335 if ((unsigned)sz >= sizeof(tagName)) return;
2336 strncpy(tagName, content, sz);
2338 attrsLength = length - sz;
2341 if ((method = _findGroupFactory(tagName))) {
2344 if (strcmp(tagName, "svg")) return; //Not a valid svg document
2345 node = method(loader, nullptr, attrs, attrsLength);
2348 if (!strcmp(tagName, "svg")) return; //Already loaded <svg>(SvgNodeType::Doc) tag
2349 if (loader->stack.count > 0) parent = loader->stack.data[loader->stack.count - 1];
2350 else parent = loader->doc;
2351 node = method(loader, parent, attrs, attrsLength);
2355 if (node->type != SvgNodeType::Defs || !empty) {
2356 loader->stack.push(node);
2358 } else if ((method = _findGraphicsFactory(tagName))) {
2359 if (loader->stack.count > 0) parent = loader->stack.data[loader->stack.count - 1];
2360 else parent = loader->doc;
2361 node = method(loader, parent, attrs, attrsLength);
2362 } else if ((gradientMethod = _findGradientFactory(tagName))) {
2363 SvgStyleGradient* gradient;
2364 gradient = gradientMethod(loader, attrs, attrsLength);
2365 //FIXME: The current parsing structure does not distinguish end tags.
2366 // There is no way to know if the currently parsed gradient is in defs.
2367 // If a gradient is declared outside of defs after defs is set, it is included in the gradients of defs.
2368 // But finally, the loader has a gradient style list regardless of defs.
2369 // This is only to support this when multiple gradients are declared, even if no defs are declared.
2370 // refer to: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/defs
2371 if (loader->def && loader->doc->node.doc.defs) {
2372 loader->def->node.defs.gradients.push(gradient);
2374 loader->gradients.push(gradient);
2376 loader->latestGradient = gradient;
2377 } else if (!strcmp(tagName, "stop")) {
2378 if (!loader->latestGradient) {
2379 TVGLOG("SVG", "Stop element is used outside of the Gradient element");
2382 /* default value for opacity */
2383 loader->svgParse->gradStop = {0.0f, 0, 0, 0, 255};
2384 simpleXmlParseAttributes(attrs, attrsLength, _attrParseStops, loader);
2385 loader->latestGradient->stops.push(loader->svgParse->gradStop);
2386 } else if (!isIgnoreUnsupportedLogElements(tagName)) {
2387 TVGLOG("SVG", "Unsupported elements used [Elements: %s]", tagName);
2392 static bool _svgLoaderParser(void* data, SimpleXMLType type, const char* content, unsigned int length)
2394 SvgLoaderData* loader = (SvgLoaderData*)data;
2397 case SimpleXMLType::Open: {
2398 _svgLoaderParserXmlOpen(loader, content, length, false);
2401 case SimpleXMLType::OpenEmpty: {
2402 _svgLoaderParserXmlOpen(loader, content, length, true);
2405 case SimpleXMLType::Close: {
2406 _svgLoaderParerXmlClose(loader, content);
2409 case SimpleXMLType::Data:
2410 case SimpleXMLType::CData:
2411 case SimpleXMLType::DoctypeChild: {
2414 case SimpleXMLType::Ignored:
2415 case SimpleXMLType::Comment:
2416 case SimpleXMLType::Doctype: {
2428 static void _styleInherit(SvgStyleProperty* child, const SvgStyleProperty* parent)
2430 if (parent == nullptr) return;
2431 //Inherit the property of parent if not present in child.
2433 if (!((int)child->fill.flags & (int)SvgFillFlags::Paint)) {
2434 child->fill.paint.color = parent->fill.paint.color;
2435 child->fill.paint.none = parent->fill.paint.none;
2436 child->fill.paint.curColor = parent->fill.paint.curColor;
2437 if (parent->fill.paint.url) child->fill.paint.url = _copyId(parent->fill.paint.url->c_str());
2438 } else if (child->fill.paint.curColor && !child->curColorSet) {
2439 child->color = parent->color;
2441 if (!((int)child->fill.flags & (int)SvgFillFlags::Opacity)) {
2442 child->fill.opacity = parent->fill.opacity;
2444 if (!((int)child->fill.flags & (int)SvgFillFlags::FillRule)) {
2445 child->fill.fillRule = parent->fill.fillRule;
2448 if (!((int)child->stroke.flags & (int)SvgStrokeFlags::Paint)) {
2449 child->stroke.paint.color = parent->stroke.paint.color;
2450 child->stroke.paint.none = parent->stroke.paint.none;
2451 child->stroke.paint.curColor = parent->stroke.paint.curColor;
2452 child->stroke.paint.url = parent->stroke.paint.url ? _copyId(parent->stroke.paint.url->c_str()) : nullptr;
2453 } else if (child->stroke.paint.curColor && !child->curColorSet) {
2454 child->color = parent->color;
2456 if (!((int)child->stroke.flags & (int)SvgStrokeFlags::Opacity)) {
2457 child->stroke.opacity = parent->stroke.opacity;
2459 if (!((int)child->stroke.flags & (int)SvgStrokeFlags::Width)) {
2460 child->stroke.width = parent->stroke.width;
2462 if (!((int)child->stroke.flags & (int)SvgStrokeFlags::Dash)) {
2463 if (parent->stroke.dash.array.count > 0) {
2464 child->stroke.dash.array.clear();
2465 child->stroke.dash.array.reserve(parent->stroke.dash.array.count);
2466 for (uint32_t i = 0; i < parent->stroke.dash.array.count; ++i) {
2467 child->stroke.dash.array.push(parent->stroke.dash.array.data[i]);
2471 if (!((int)child->stroke.flags & (int)SvgStrokeFlags::Cap)) {
2472 child->stroke.cap = parent->stroke.cap;
2474 if (!((int)child->stroke.flags & (int)SvgStrokeFlags::Join)) {
2475 child->stroke.join = parent->stroke.join;
2480 static void _inefficientNodeCheck(TVG_UNUSED SvgNode* node){
2481 #ifdef THORVG_LOG_ENABLED
2482 auto type = simpleXmlNodeTypeToString(node->type);
2484 if (!node->display && node->type != SvgNodeType::ClipPath) TVGLOG("SVG", "Inefficient elements used [Display is none][Node Type : %s]", type);
2485 if (node->style->opacity == 0) TVGLOG("SVG", "Inefficient elements used [Opacity is zero][Node Type : %s]", type);
2486 if (node->style->fill.opacity == 0 && node->style->stroke.opacity == 0) TVGLOG("SVG", "Inefficient elements used [Fill opacity and stroke opacity are zero][Node Type : %s]", type);
2488 switch (node->type) {
2489 case SvgNodeType::Path: {
2490 if (!node->node.path.path || node->node.path.path->empty()) TVGLOG("SVG", "Inefficient elements used [Empty path][Node Type : %s]", type);
2493 case SvgNodeType::Ellipse: {
2494 if (node->node.ellipse.rx == 0 && node->node.ellipse.ry == 0) TVGLOG("SVG", "Inefficient elements used [Size is zero][Node Type : %s]", type);
2497 case SvgNodeType::Polygon:
2498 case SvgNodeType::Polyline: {
2499 if (node->node.polygon.pointsCount < 2) TVGLOG("SVG", "Inefficient elements used [Invalid Polygon][Node Type : %s]", type);
2502 case SvgNodeType::Circle: {
2503 if (node->node.circle.r == 0) TVGLOG("SVG", "Inefficient elements used [Size is zero][Node Type : %s]", type);
2506 case SvgNodeType::Rect: {
2507 if (node->node.rect.w == 0 && node->node.rect.h) TVGLOG("SVG", "Inefficient elements used [Size is zero][Node Type : %s]", type);
2510 case SvgNodeType::Line: {
2511 if (node->node.line.x1 == node->node.line.x2 && node->node.line.y1 == node->node.line.y2) TVGLOG("SVG", "Inefficient elements used [Size is zero][Node Type : %s]", type);
2520 static void _updateStyle(SvgNode* node, SvgStyleProperty* parentStyle)
2522 _styleInherit(node->style, parentStyle);
2523 _inefficientNodeCheck(node);
2525 auto child = node->child.data;
2526 for (uint32_t i = 0; i < node->child.count; ++i, ++child) {
2527 _updateStyle(*child, node->style);
2532 static SvgStyleGradient* _gradientDup(Array<SvgStyleGradient*>* gradients, const string* id)
2534 SvgStyleGradient* result = nullptr;
2536 auto gradList = gradients->data;
2538 for (uint32_t i = 0; i < gradients->count; ++i) {
2539 if (!((*gradList)->id->compare(*id))) {
2540 result = _cloneGradient(*gradList);
2546 if (result && result->ref) {
2547 gradList = gradients->data;
2548 for (uint32_t i = 0; i < gradients->count; ++i) {
2549 if (!((*gradList)->id->compare(*result->ref))) {
2550 if (result->stops.count == 0) {
2551 _cloneGradStops(result->stops, (*gradList)->stops);
2553 //TODO: Properly inherit other property
2564 static void _updateGradient(SvgNode* node, Array<SvgStyleGradient*>* gradients)
2566 if (node->child.count > 0) {
2567 auto child = node->child.data;
2568 for (uint32_t i = 0; i < node->child.count; ++i, ++child) {
2569 _updateGradient(*child, gradients);
2572 if (node->style->fill.paint.url) {
2573 if (node->style->fill.paint.gradient) delete(node->style->fill.paint.gradient);
2574 node->style->fill.paint.gradient = _gradientDup(gradients, node->style->fill.paint.url);
2576 if (node->style->stroke.paint.url) {
2577 if (node->style->stroke.paint.gradient) delete(node->style->stroke.paint.gradient);
2578 node->style->stroke.paint.gradient = _gradientDup(gradients, node->style->stroke.paint.url);
2584 static void _updateComposite(SvgNode* node, SvgNode* root)
2586 if (node->style->clipPath.url && !node->style->clipPath.node) {
2587 SvgNode *findResult = _findNodeById(root, node->style->clipPath.url);
2588 if (findResult) node->style->clipPath.node = findResult;
2590 if (node->style->mask.url && !node->style->mask.node) {
2591 SvgNode *findResult = _findNodeById(root, node->style->mask.url);
2592 if (findResult) node->style->mask.node = findResult;
2594 if (node->child.count > 0) {
2595 auto child = node->child.data;
2596 for (uint32_t i = 0; i < node->child.count; ++i, ++child) {
2597 _updateComposite(*child, root);
2603 static void _freeNodeStyle(SvgStyleProperty* style)
2607 //style->clipPath.node and style->mask.node has only the addresses of node. Therefore, node is released from _freeNode.
2608 delete(style->clipPath.url);
2609 delete(style->mask.url);
2611 if (style->fill.paint.gradient) delete(style->fill.paint.gradient);
2612 if (style->stroke.paint.gradient) delete(style->stroke.paint.gradient);
2613 delete(style->fill.paint.url);
2614 delete(style->stroke.paint.url);
2615 style->stroke.dash.array.reset();
2620 static void _freeNode(SvgNode* node)
2624 auto child = node->child.data;
2625 for (uint32_t i = 0; i < node->child.count; ++i, ++child) {
2628 node->child.reset();
2631 free(node->transform);
2632 _freeNodeStyle(node->style);
2633 switch (node->type) {
2634 case SvgNodeType::Path: {
2635 delete node->node.path.path;
2638 case SvgNodeType::Polygon: {
2639 free(node->node.polygon.points);
2642 case SvgNodeType::Polyline: {
2643 free(node->node.polyline.points);
2646 case SvgNodeType::Doc: {
2647 _freeNode(node->node.doc.defs);
2650 case SvgNodeType::Defs: {
2651 auto gradients = node->node.defs.gradients.data;
2652 for (size_t i = 0; i < node->node.defs.gradients.count; ++i) {
2656 node->node.defs.gradients.reset();
2659 case SvgNodeType::Image: {
2660 delete node->node.image.href;
2671 static bool _svgLoaderParserForValidCheckXmlOpen(SvgLoaderData* loader, const char* content, unsigned int length)
2673 const char* attrs = nullptr;
2675 char tagName[20] = "";
2676 FactoryMethod method;
2677 SvgNode *node = nullptr;
2678 int attrsLength = 0;
2680 attrs = simpleXmlFindAttributesTag(content, length);
2683 //Parse the empty tag
2685 while ((attrs != nullptr) && *attrs != '>') attrs++;
2689 sz = attrs - content;
2690 while ((sz > 0) && (isspace(content[sz - 1]))) sz--;
2691 if ((unsigned)sz >= sizeof(tagName)) return false;
2692 strncpy(tagName, content, sz);
2694 attrsLength = length - sz;
2697 if ((method = _findGroupFactory(tagName))) {
2699 if (strcmp(tagName, "svg")) return true; //Not a valid svg document
2700 node = method(loader, nullptr, attrs, attrsLength);
2702 loader->stack.push(node);
2710 static bool _svgLoaderParserForValidCheck(void* data, SimpleXMLType type, const char* content, unsigned int length)
2712 SvgLoaderData* loader = (SvgLoaderData*)data;
2716 case SimpleXMLType::Open:
2717 case SimpleXMLType::OpenEmpty: {
2718 //If 'res' is false, it means <svg> tag is found.
2719 res = _svgLoaderParserForValidCheckXmlOpen(loader, content, length);
2731 void SvgLoader::clear()
2733 if (copy) free((char*)content);
2740 /************************************************************************/
2741 /* External Class Implementation */
2742 /************************************************************************/
2744 SvgLoader::SvgLoader()
2749 SvgLoader::~SvgLoader()
2755 void SvgLoader::run(unsigned tid)
2757 if (!simpleXmlParse(content, size, true, _svgLoaderParser, &(loaderData))) return;
2759 if (loaderData.doc) {
2760 _updateStyle(loaderData.doc, nullptr);
2761 auto defs = loaderData.doc->node.doc.defs;
2762 if (defs) _updateGradient(loaderData.doc, &defs->node.defs.gradients);
2764 if (loaderData.gradients.count > 0) _updateGradient(loaderData.doc, &loaderData.gradients);
2766 _updateComposite(loaderData.doc, loaderData.doc);
2767 if (defs) _updateComposite(loaderData.doc, defs);
2769 if (loaderData.cloneNodes.count > 0) _clonePostponedNodes(&loaderData.cloneNodes);
2771 root = svgSceneBuild(loaderData.doc, vx, vy, vw, vh);
2775 bool SvgLoader::header()
2777 //For valid check, only <svg> tag is parsed first.
2778 //If the <svg> tag is found, the loaded file is valid and stores viewbox information.
2779 //After that, the remaining content data is parsed in order with async.
2780 loaderData.svgParse = (SvgParser*)malloc(sizeof(SvgParser));
2781 if (!loaderData.svgParse) return false;
2783 loaderData.svgParse->flags = SvgStopStyleFlags::StopDefault;
2785 simpleXmlParse(content, size, true, _svgLoaderParserForValidCheck, &(loaderData));
2787 if (loaderData.doc && loaderData.doc->type == SvgNodeType::Doc) {
2788 //Return the brief resource info such as viewbox:
2789 vx = loaderData.doc->node.doc.vx;
2790 vy = loaderData.doc->node.doc.vy;
2791 w = vw = loaderData.doc->node.doc.vw;
2792 h = vh = loaderData.doc->node.doc.vh;
2795 if (loaderData.doc->node.doc.w > 0) {
2796 w = loaderData.doc->node.doc.w;
2797 if (vw < FLT_EPSILON) vw = w;
2799 if (loaderData.doc->node.doc.h > 0) {
2800 h = loaderData.doc->node.doc.h;
2801 if (vh < FLT_EPSILON) vh = h;
2804 preserveAspect = loaderData.doc->node.doc.preserveAspect;
2806 TVGLOG("SVG", "No SVG File. There is no <svg/>");
2814 bool SvgLoader::open(const char* data, uint32_t size, bool copy)
2819 content = (char*)malloc(size);
2820 if (!content) return false;
2821 memcpy((char*)content, data, size);
2822 } else content = data;
2831 bool SvgLoader::open(const string& path)
2838 if (!f.is_open()) return false;
2840 getline(f, filePath, '\0');
2843 if (filePath.empty()) return false;
2845 content = filePath.c_str();
2846 size = filePath.size();
2852 bool SvgLoader::resize(Paint* paint, float w, float h)
2854 if (!paint) return false;
2859 if (preserveAspect) {
2861 auto scale = sx < sy ? sx : sy;
2862 paint->scale(scale);
2864 auto vx = this->vx * scale;
2865 auto vy = this->vy * scale;
2866 auto vw = this->vw * scale;
2867 auto vh = this->vh * scale;
2868 if (vw > vh) vy -= (h - vh) * 0.5f;
2869 else vx -= (w - vw) * 0.5f;
2870 paint->translate(-vx, -vy);
2873 auto vx = this->vx * sx;
2874 auto vy = this->vy * sy;
2875 auto vw = this->vw * sx;
2876 auto vh = this->vh * sy;
2877 if (vw > vh) vy -= (h - vh) * 0.5f;
2878 else vx -= (w - vw) * 0.5f;
2880 Matrix m = {sx, 0, -vx, 0, sy, -vy, 0, 0, 1};
2881 paint->transform(m);
2887 bool SvgLoader::read()
2889 if (!content || size == 0) return false;
2891 TaskScheduler::request(this);
2897 bool SvgLoader::close()
2901 if (loaderData.svgParse) {
2902 free(loaderData.svgParse);
2903 loaderData.svgParse = nullptr;
2905 auto gradients = loaderData.gradients.data;
2906 for (size_t i = 0; i < loaderData.gradients.count; ++i) {
2910 loaderData.gradients.reset();
2912 _freeNode(loaderData.doc);
2913 loaderData.doc = nullptr;
2914 loaderData.stack.reset();
2922 unique_ptr<Paint> SvgLoader::paint()
2925 if (root) return move(root);
2926 else return nullptr;