summaryrefslogtreecommitdiff
path: root/thirdparty/thorvg/src/loaders/svg
diff options
context:
space:
mode:
authorK. S. Ernest (iFire) Lee <ernest.lee@chibifire.com>2022-01-13 13:54:19 +0100
committerRémi Verschelde <rverschelde@gmail.com>2022-01-14 15:49:39 +0100
commit8d02759c720c3a91663e56979273feabad1dc051 (patch)
treeb939f28feb3224d4c4d2e39d12ef191157e2664b /thirdparty/thorvg/src/loaders/svg
parent9b3535a33a1dda541a3a39e7786b8428fadbff6c (diff)
Use ThorVG instead of NanoSVG for importing SVGs
ThorVG is a platform-independent portable library for drawing vector-based scene and animation. Co-authored-by: Rémi Verschelde <rverschelde@gmail.com>
Diffstat (limited to 'thirdparty/thorvg/src/loaders/svg')
-rw-r--r--thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp3007
-rw-r--r--thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.h59
-rw-r--r--thirdparty/thorvg/src/loaders/svg/tvgSvgLoaderCommon.h412
-rw-r--r--thirdparty/thorvg/src/loaders/svg/tvgSvgPath.cpp563
-rw-r--r--thirdparty/thorvg/src/loaders/svg/tvgSvgPath.h30
-rw-r--r--thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.cpp652
-rw-r--r--thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.h30
-rw-r--r--thirdparty/thorvg/src/loaders/svg/tvgSvgUtil.cpp272
-rw-r--r--thirdparty/thorvg/src/loaders/svg/tvgSvgUtil.h33
-rw-r--r--thirdparty/thorvg/src/loaders/svg/tvgXmlParser.cpp527
-rw-r--r--thirdparty/thorvg/src/loaders/svg/tvgXmlParser.h57
11 files changed, 5642 insertions, 0 deletions
diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp b/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp
new file mode 100644
index 0000000000..def8ae169a
--- /dev/null
+++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp
@@ -0,0 +1,3007 @@
+/*
+ * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/*
+ * Copyright notice for the EFL:
+
+ * Copyright (C) EFL developers (see AUTHORS)
+
+ * All rights reserved.
+
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
+ * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+
+#define _USE_MATH_DEFINES //Math Constants are not defined in Standard C/C++.
+
+#include <fstream>
+#include <float.h>
+#include <math.h>
+#include "tvgLoader.h"
+#include "tvgXmlParser.h"
+#include "tvgSvgLoader.h"
+#include "tvgSvgSceneBuilder.h"
+#include "tvgSvgUtil.h"
+
+/************************************************************************/
+/* Internal Class Implementation */
+/************************************************************************/
+
+/*
+ * According to: https://www.w3.org/TR/SVG2/coords.html#Units
+ * and: https://www.w3.org/TR/css-values-4/#absolute-lengths
+ */
+#define PX_PER_IN 96 //1 in = 96 px
+#define PX_PER_PC 16 //1 pc = 1/6 in -> PX_PER_IN/6
+#define PX_PER_PT 1.333333f //1 pt = 1/72 in -> PX_PER_IN/72
+#define PX_PER_MM 3.779528f //1 in = 25.4 mm -> PX_PER_IN/25.4
+#define PX_PER_CM 37.79528f //1 in = 2.54 cm -> PX_PER_IN/2.54
+
+
+typedef SvgNode* (*FactoryMethod)(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength);
+typedef SvgStyleGradient* (*GradientFactoryMethod)(SvgLoaderData* loader, const char* buf, unsigned bufLength);
+
+
+static char* _skipSpace(const char* str, const char* end)
+{
+ while (((end && str < end) || (!end && *str != '\0')) && isspace(*str)) {
+ ++str;
+ }
+ return (char*) str;
+}
+
+
+static char* _copyId(const char* str)
+{
+ if (!str) return nullptr;
+
+ return strdup(str);
+}
+
+
+static const char* _skipComma(const char* content)
+{
+ content = _skipSpace(content, nullptr);
+ if (*content == ',') return content + 1;
+ return content;
+}
+
+
+static bool _parseNumber(const char** content, float* number)
+{
+ char* end = nullptr;
+
+ *number = svgUtilStrtof(*content, &end);
+ //If the start of string is not number
+ if ((*content) == end) return false;
+ //Skip comma if any
+ *content = _skipComma(end);
+ return true;
+}
+
+/**
+ * According to https://www.w3.org/TR/SVG/coords.html#Units
+ */
+static float _toFloat(const SvgParser* svgParse, const char* str, SvgParserLengthType type)
+{
+ float parsedValue = svgUtilStrtof(str, nullptr);
+
+ if (strstr(str, "cm")) parsedValue *= PX_PER_CM;
+ else if (strstr(str, "mm")) parsedValue *= PX_PER_MM;
+ else if (strstr(str, "pt")) parsedValue *= PX_PER_PT;
+ else if (strstr(str, "pc")) parsedValue *= PX_PER_PC;
+ else if (strstr(str, "in")) parsedValue *= PX_PER_IN;
+ else if (strstr(str, "%")) {
+ if (type == SvgParserLengthType::Vertical) parsedValue = (parsedValue / 100.0) * svgParse->global.h;
+ else if (type == SvgParserLengthType::Horizontal) parsedValue = (parsedValue / 100.0) * svgParse->global.w;
+ else //if other then it's radius
+ {
+ float max = (float)svgParse->global.w;
+ if (max < svgParse->global.h)
+ max = (float)svgParse->global.h;
+ parsedValue = (parsedValue / 100.0) * max;
+ }
+ }
+ //TODO: Implement 'em', 'ex' attributes
+
+ return parsedValue;
+}
+
+
+static float _gradientToFloat(const SvgParser* svgParse, const char* str, bool& isPercentage)
+{
+ char* end = nullptr;
+
+ float parsedValue = svgUtilStrtof(str, &end);
+ isPercentage = false;
+
+ if (strstr(str, "%")) {
+ parsedValue = parsedValue / 100.0;
+ isPercentage = true;
+ }
+ else if (strstr(str, "cm")) parsedValue *= PX_PER_CM;
+ else if (strstr(str, "mm")) parsedValue *= PX_PER_MM;
+ else if (strstr(str, "pt")) parsedValue *= PX_PER_PT;
+ else if (strstr(str, "pc")) parsedValue *= PX_PER_PC;
+ else if (strstr(str, "in")) parsedValue *= PX_PER_IN;
+ //TODO: Implement 'em', 'ex' attributes
+
+ return parsedValue;
+}
+
+
+static float _toOffset(const char* str)
+{
+ char* end = nullptr;
+ auto strEnd = str + strlen(str);
+
+ float parsedValue = svgUtilStrtof(str, &end);
+
+ end = _skipSpace(end, nullptr);
+ auto ptr = strstr(str, "%");
+
+ if (ptr) {
+ parsedValue = parsedValue / 100.0;
+ if (end != ptr || (end + 1) != strEnd) return 0;
+ } else if (end != strEnd) return 0;
+
+ return parsedValue;
+}
+
+
+static int _toOpacity(const char* str)
+{
+ char* end = nullptr;
+ float opacity = svgUtilStrtof(str, &end);
+
+ if (end) {
+ if (end[0] == '%' && end[1] == '\0') return lrint(opacity * 2.55f);
+ else if (*end == '\0') return lrint(opacity * 255);
+ }
+ return 255;
+}
+
+
+#define _PARSE_TAG(Type, Name, Name1, Tags_Array, Default) \
+ static Type _to##Name1(const char* str) \
+ { \
+ unsigned int i; \
+ \
+ for (i = 0; i < sizeof(Tags_Array) / sizeof(Tags_Array[0]); i++) { \
+ if (!strcmp(str, Tags_Array[i].tag)) return Tags_Array[i].Name; \
+ } \
+ return Default; \
+ }
+
+
+/* parse the line cap used during stroking a path.
+ * Value: butt | round | square | inherit
+ * Initial: butt
+ * https://www.w3.org/TR/SVG/painting.html
+ */
+static constexpr struct
+{
+ StrokeCap lineCap;
+ const char* tag;
+} lineCapTags[] = {
+ { StrokeCap::Butt, "butt" },
+ { StrokeCap::Round, "round" },
+ { StrokeCap::Square, "square" }
+};
+
+
+_PARSE_TAG(StrokeCap, lineCap, LineCap, lineCapTags, StrokeCap::Butt)
+
+
+/* parse the line join used during stroking a path.
+ * Value: miter | round | bevel | inherit
+ * Initial: miter
+ * https://www.w3.org/TR/SVG/painting.html
+ */
+static constexpr struct
+{
+ StrokeJoin lineJoin;
+ const char* tag;
+} lineJoinTags[] = {
+ { StrokeJoin::Miter, "miter" },
+ { StrokeJoin::Round, "round" },
+ { StrokeJoin::Bevel, "bevel" }
+};
+
+
+_PARSE_TAG(StrokeJoin, lineJoin, LineJoin, lineJoinTags, StrokeJoin::Miter)
+
+
+/* parse the fill rule used during filling a path.
+ * Value: nonzero | evenodd | inherit
+ * Initial: nonzero
+ * https://www.w3.org/TR/SVG/painting.html
+ */
+static constexpr struct
+{
+ FillRule fillRule;
+ const char* tag;
+} fillRuleTags[] = {
+ { FillRule::EvenOdd, "evenodd" }
+};
+
+
+_PARSE_TAG(FillRule, fillRule, FillRule, fillRuleTags, FillRule::Winding)
+
+
+/* parse the dash pattern used during stroking a path.
+ * Value: none | <dasharray> | inherit
+ * Initial: none
+ * https://www.w3.org/TR/SVG/painting.html
+ */
+static void _parseDashArray(SvgLoaderData* loader, const char *str, SvgDash* dash)
+{
+ if (!strncmp(str, "none", 4)) return;
+
+ char *end = nullptr;
+
+ while (*str) {
+ str = _skipComma(str);
+ float parsedValue = svgUtilStrtof(str, &end);
+ if (str == end) break;
+ if (parsedValue <= 0.0f) break;
+ if (*end == '%') {
+ ++end;
+ //Refers to the diagonal length of the viewport.
+ //https://www.w3.org/TR/SVG2/coords.html#Units
+ parsedValue = (sqrtf(pow(loader->svgParse->global.w, 2) + pow(loader->svgParse->global.h, 2)) / sqrtf(2.0f)) * (parsedValue / 100.0f);
+ }
+ (*dash).array.push(parsedValue);
+ str = end;
+ }
+ //If dash array size is 1, it means that dash and gap size are the same.
+ if ((*dash).array.count == 1) (*dash).array.push((*dash).array.data[0]);
+}
+
+
+static char* _idFromUrl(const char* url)
+{
+ url = _skipSpace(url, nullptr);
+ if ((*url) == '(') {
+ ++url;
+ url = _skipSpace(url, nullptr);
+ }
+
+ if ((*url) == '\'') ++url;
+ if ((*url) == '#') ++url;
+
+ int i = 0;
+ while (url[i] > ' ' && url[i] != ')' && url[i] != '\'') ++i;
+
+ //custom strndup() for portability
+ int len = strlen(url);
+ if (i < len) len = i;
+
+ auto ret = (char*) malloc(len + 1);
+ if (!ret) return 0;
+ ret[len] = '\0';
+ return (char*) memcpy(ret, url, len);
+}
+
+
+static unsigned char _parserColor(const char* value, char** end)
+{
+ float r;
+
+ r = svgUtilStrtof(value, end);
+ *end = _skipSpace(*end, nullptr);
+ if (**end == '%') r = 255 * r / 100;
+ *end = _skipSpace(*end, nullptr);
+
+ if (r < 0 || r > 255) {
+ *end = nullptr;
+ return 0;
+ }
+
+ return lrint(r);
+}
+
+
+static constexpr struct
+{
+ const char* name;
+ unsigned int value;
+} colors[] = {
+ { "aliceblue", 0xfff0f8ff },
+ { "antiquewhite", 0xfffaebd7 },
+ { "aqua", 0xff00ffff },
+ { "aquamarine", 0xff7fffd4 },
+ { "azure", 0xfff0ffff },
+ { "beige", 0xfff5f5dc },
+ { "bisque", 0xffffe4c4 },
+ { "black", 0xff000000 },
+ { "blanchedalmond", 0xffffebcd },
+ { "blue", 0xff0000ff },
+ { "blueviolet", 0xff8a2be2 },
+ { "brown", 0xffa52a2a },
+ { "burlywood", 0xffdeb887 },
+ { "cadetblue", 0xff5f9ea0 },
+ { "chartreuse", 0xff7fff00 },
+ { "chocolate", 0xffd2691e },
+ { "coral", 0xffff7f50 },
+ { "cornflowerblue", 0xff6495ed },
+ { "cornsilk", 0xfffff8dc },
+ { "crimson", 0xffdc143c },
+ { "cyan", 0xff00ffff },
+ { "darkblue", 0xff00008b },
+ { "darkcyan", 0xff008b8b },
+ { "darkgoldenrod", 0xffb8860b },
+ { "darkgray", 0xffa9a9a9 },
+ { "darkgrey", 0xffa9a9a9 },
+ { "darkgreen", 0xff006400 },
+ { "darkkhaki", 0xffbdb76b },
+ { "darkmagenta", 0xff8b008b },
+ { "darkolivegreen", 0xff556b2f },
+ { "darkorange", 0xffff8c00 },
+ { "darkorchid", 0xff9932cc },
+ { "darkred", 0xff8b0000 },
+ { "darksalmon", 0xffe9967a },
+ { "darkseagreen", 0xff8fbc8f },
+ { "darkslateblue", 0xff483d8b },
+ { "darkslategray", 0xff2f4f4f },
+ { "darkslategrey", 0xff2f4f4f },
+ { "darkturquoise", 0xff00ced1 },
+ { "darkviolet", 0xff9400d3 },
+ { "deeppink", 0xffff1493 },
+ { "deepskyblue", 0xff00bfff },
+ { "dimgray", 0xff696969 },
+ { "dimgrey", 0xff696969 },
+ { "dodgerblue", 0xff1e90ff },
+ { "firebrick", 0xffb22222 },
+ { "floralwhite", 0xfffffaf0 },
+ { "forestgreen", 0xff228b22 },
+ { "fuchsia", 0xffff00ff },
+ { "gainsboro", 0xffdcdcdc },
+ { "ghostwhite", 0xfff8f8ff },
+ { "gold", 0xffffd700 },
+ { "goldenrod", 0xffdaa520 },
+ { "gray", 0xff808080 },
+ { "grey", 0xff808080 },
+ { "green", 0xff008000 },
+ { "greenyellow", 0xffadff2f },
+ { "honeydew", 0xfff0fff0 },
+ { "hotpink", 0xffff69b4 },
+ { "indianred", 0xffcd5c5c },
+ { "indigo", 0xff4b0082 },
+ { "ivory", 0xfffffff0 },
+ { "khaki", 0xfff0e68c },
+ { "lavender", 0xffe6e6fa },
+ { "lavenderblush", 0xfffff0f5 },
+ { "lawngreen", 0xff7cfc00 },
+ { "lemonchiffon", 0xfffffacd },
+ { "lightblue", 0xffadd8e6 },
+ { "lightcoral", 0xfff08080 },
+ { "lightcyan", 0xffe0ffff },
+ { "lightgoldenrodyellow", 0xfffafad2 },
+ { "lightgray", 0xffd3d3d3 },
+ { "lightgrey", 0xffd3d3d3 },
+ { "lightgreen", 0xff90ee90 },
+ { "lightpink", 0xffffb6c1 },
+ { "lightsalmon", 0xffffa07a },
+ { "lightseagreen", 0xff20b2aa },
+ { "lightskyblue", 0xff87cefa },
+ { "lightslategray", 0xff778899 },
+ { "lightslategrey", 0xff778899 },
+ { "lightsteelblue", 0xffb0c4de },
+ { "lightyellow", 0xffffffe0 },
+ { "lime", 0xff00ff00 },
+ { "limegreen", 0xff32cd32 },
+ { "linen", 0xfffaf0e6 },
+ { "magenta", 0xffff00ff },
+ { "maroon", 0xff800000 },
+ { "mediumaquamarine", 0xff66cdaa },
+ { "mediumblue", 0xff0000cd },
+ { "mediumorchid", 0xffba55d3 },
+ { "mediumpurple", 0xff9370d8 },
+ { "mediumseagreen", 0xff3cb371 },
+ { "mediumslateblue", 0xff7b68ee },
+ { "mediumspringgreen", 0xff00fa9a },
+ { "mediumturquoise", 0xff48d1cc },
+ { "mediumvioletred", 0xffc71585 },
+ { "midnightblue", 0xff191970 },
+ { "mintcream", 0xfff5fffa },
+ { "mistyrose", 0xffffe4e1 },
+ { "moccasin", 0xffffe4b5 },
+ { "navajowhite", 0xffffdead },
+ { "navy", 0xff000080 },
+ { "oldlace", 0xfffdf5e6 },
+ { "olive", 0xff808000 },
+ { "olivedrab", 0xff6b8e23 },
+ { "orange", 0xffffa500 },
+ { "orangered", 0xffff4500 },
+ { "orchid", 0xffda70d6 },
+ { "palegoldenrod", 0xffeee8aa },
+ { "palegreen", 0xff98fb98 },
+ { "paleturquoise", 0xffafeeee },
+ { "palevioletred", 0xffd87093 },
+ { "papayawhip", 0xffffefd5 },
+ { "peachpuff", 0xffffdab9 },
+ { "peru", 0xffcd853f },
+ { "pink", 0xffffc0cb },
+ { "plum", 0xffdda0dd },
+ { "powderblue", 0xffb0e0e6 },
+ { "purple", 0xff800080 },
+ { "red", 0xffff0000 },
+ { "rosybrown", 0xffbc8f8f },
+ { "royalblue", 0xff4169e1 },
+ { "saddlebrown", 0xff8b4513 },
+ { "salmon", 0xfffa8072 },
+ { "sandybrown", 0xfff4a460 },
+ { "seagreen", 0xff2e8b57 },
+ { "seashell", 0xfffff5ee },
+ { "sienna", 0xffa0522d },
+ { "silver", 0xffc0c0c0 },
+ { "skyblue", 0xff87ceeb },
+ { "slateblue", 0xff6a5acd },
+ { "slategray", 0xff708090 },
+ { "slategrey", 0xff708090 },
+ { "snow", 0xfffffafa },
+ { "springgreen", 0xff00ff7f },
+ { "steelblue", 0xff4682b4 },
+ { "tan", 0xffd2b48c },
+ { "teal", 0xff008080 },
+ { "thistle", 0xffd8bfd8 },
+ { "tomato", 0xffff6347 },
+ { "turquoise", 0xff40e0d0 },
+ { "violet", 0xffee82ee },
+ { "wheat", 0xfff5deb3 },
+ { "white", 0xffffffff },
+ { "whitesmoke", 0xfff5f5f5 },
+ { "yellow", 0xffffff00 },
+ { "yellowgreen", 0xff9acd32 }
+};
+
+
+static void _toColor(const char* str, uint8_t* r, uint8_t* g, uint8_t* b, char** ref)
+{
+ unsigned int len = strlen(str);
+ char *red, *green, *blue;
+ unsigned char tr, tg, tb;
+
+ if (len == 4 && str[0] == '#') {
+ //Case for "#456" should be interprete as "#445566"
+ if (isxdigit(str[1]) && isxdigit(str[2]) && isxdigit(str[3])) {
+ char tmp[3] = { '\0', '\0', '\0' };
+ tmp[0] = str[1];
+ tmp[1] = str[1];
+ *r = strtol(tmp, nullptr, 16);
+ tmp[0] = str[2];
+ tmp[1] = str[2];
+ *g = strtol(tmp, nullptr, 16);
+ tmp[0] = str[3];
+ tmp[1] = str[3];
+ *b = strtol(tmp, nullptr, 16);
+ }
+ } else if (len == 7 && str[0] == '#') {
+ if (isxdigit(str[1]) && isxdigit(str[2]) && isxdigit(str[3]) && isxdigit(str[4]) && isxdigit(str[5]) && isxdigit(str[6])) {
+ char tmp[3] = { '\0', '\0', '\0' };
+ tmp[0] = str[1];
+ tmp[1] = str[2];
+ *r = strtol(tmp, nullptr, 16);
+ tmp[0] = str[3];
+ tmp[1] = str[4];
+ *g = strtol(tmp, nullptr, 16);
+ tmp[0] = str[5];
+ tmp[1] = str[6];
+ *b = strtol(tmp, nullptr, 16);
+ }
+ } 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] == ')') {
+ tr = _parserColor(str + 4, &red);
+ if (red && *red == ',') {
+ tg = _parserColor(red + 1, &green);
+ if (green && *green == ',') {
+ tb = _parserColor(green + 1, &blue);
+ if (blue && blue[0] == ')' && blue[1] == '\0') {
+ *r = tr;
+ *g = tg;
+ *b = tb;
+ }
+ }
+ }
+ } else if (len >= 3 && !strncmp(str, "url", 3)) {
+ *ref = _idFromUrl((const char*)(str + 3));
+ } else {
+ //Handle named color
+ for (unsigned int i = 0; i < (sizeof(colors) / sizeof(colors[0])); i++) {
+ if (!strcasecmp(colors[i].name, str)) {
+ *r = (((uint8_t*)(&(colors[i].value)))[2]);
+ *g = (((uint8_t*)(&(colors[i].value)))[1]);
+ *b = (((uint8_t*)(&(colors[i].value)))[0]);
+ return;
+ }
+ }
+ }
+}
+
+
+static char* _parseNumbersArray(char* str, float* points, int* ptCount, int len)
+{
+ int count = 0;
+ char* end = nullptr;
+
+ str = _skipSpace(str, nullptr);
+ while ((count < len) && (isdigit(*str) || *str == '-' || *str == '+' || *str == '.')) {
+ points[count++] = svgUtilStrtof(str, &end);
+ str = end;
+ str = _skipSpace(str, nullptr);
+ if (*str == ',') ++str;
+ //Eat the rest of space
+ str = _skipSpace(str, nullptr);
+ }
+ *ptCount = count;
+ return str;
+}
+
+
+enum class MatrixState {
+ Unknown,
+ Matrix,
+ Translate,
+ Rotate,
+ Scale,
+ SkewX,
+ SkewY
+};
+
+
+#define MATRIX_DEF(Name, Value) \
+ { \
+#Name, sizeof(#Name), Value \
+ }
+
+
+static constexpr struct
+{
+ const char* tag;
+ int sz;
+ MatrixState state;
+} matrixTags[] = {
+ MATRIX_DEF(matrix, MatrixState::Matrix),
+ MATRIX_DEF(translate, MatrixState::Translate),
+ MATRIX_DEF(rotate, MatrixState::Rotate),
+ MATRIX_DEF(scale, MatrixState::Scale),
+ MATRIX_DEF(skewX, MatrixState::SkewX),
+ MATRIX_DEF(skewY, MatrixState::SkewY)
+};
+
+
+static void _matrixCompose(const Matrix* m1, const Matrix* m2, Matrix* dst)
+{
+ auto a11 = (m1->e11 * m2->e11) + (m1->e12 * m2->e21) + (m1->e13 * m2->e31);
+ auto a12 = (m1->e11 * m2->e12) + (m1->e12 * m2->e22) + (m1->e13 * m2->e32);
+ auto a13 = (m1->e11 * m2->e13) + (m1->e12 * m2->e23) + (m1->e13 * m2->e33);
+
+ auto a21 = (m1->e21 * m2->e11) + (m1->e22 * m2->e21) + (m1->e23 * m2->e31);
+ auto a22 = (m1->e21 * m2->e12) + (m1->e22 * m2->e22) + (m1->e23 * m2->e32);
+ auto a23 = (m1->e21 * m2->e13) + (m1->e22 * m2->e23) + (m1->e23 * m2->e33);
+
+ auto a31 = (m1->e31 * m2->e11) + (m1->e32 * m2->e21) + (m1->e33 * m2->e31);
+ auto a32 = (m1->e31 * m2->e12) + (m1->e32 * m2->e22) + (m1->e33 * m2->e32);
+ auto a33 = (m1->e31 * m2->e13) + (m1->e32 * m2->e23) + (m1->e33 * m2->e33);
+
+ dst->e11 = a11;
+ dst->e12 = a12;
+ dst->e13 = a13;
+ dst->e21 = a21;
+ dst->e22 = a22;
+ dst->e23 = a23;
+ dst->e31 = a31;
+ dst->e32 = a32;
+ dst->e33 = a33;
+}
+
+
+/* parse transform attribute
+ * https://www.w3.org/TR/SVG/coords.html#TransformAttribute
+ */
+static Matrix* _parseTransformationMatrix(const char* value)
+{
+ const int POINT_CNT = 8;
+
+ auto matrix = (Matrix*)malloc(sizeof(Matrix));
+ if (!matrix) return nullptr;
+ *matrix = {1, 0, 0, 0, 1, 0, 0, 0, 1};
+
+ float points[POINT_CNT];
+ int ptCount = 0;
+ char* str = (char*)value;
+ char* end = str + strlen(str);
+
+ while (str < end) {
+ auto state = MatrixState::Unknown;
+
+ if (isspace(*str) || (*str == ',')) {
+ ++str;
+ continue;
+ }
+ for (unsigned int i = 0; i < sizeof(matrixTags) / sizeof(matrixTags[0]); i++) {
+ if (!strncmp(matrixTags[i].tag, str, matrixTags[i].sz - 1)) {
+ state = matrixTags[i].state;
+ str += (matrixTags[i].sz - 1);
+ break;
+ }
+ }
+ if (state == MatrixState::Unknown) goto error;
+
+ str = _skipSpace(str, end);
+ if (*str != '(') goto error;
+ ++str;
+ str = _parseNumbersArray(str, points, &ptCount, POINT_CNT);
+ if (*str != ')') goto error;
+ ++str;
+
+ if (state == MatrixState::Matrix) {
+ if (ptCount != 6) goto error;
+ Matrix tmp = {points[0], points[2], points[4], points[1], points[3], points[5], 0, 0, 1};
+ _matrixCompose(matrix, &tmp, matrix);
+ } else if (state == MatrixState::Translate) {
+ if (ptCount == 1) {
+ Matrix tmp = {1, 0, points[0], 0, 1, 0, 0, 0, 1};
+ _matrixCompose(matrix, &tmp, matrix);
+ } else if (ptCount == 2) {
+ Matrix tmp = {1, 0, points[0], 0, 1, points[1], 0, 0, 1};
+ _matrixCompose(matrix, &tmp, matrix);
+ } else goto error;
+ } else if (state == MatrixState::Rotate) {
+ //Transform to signed.
+ points[0] = fmod(points[0], 360);
+ if (points[0] < 0) points[0] += 360;
+ auto c = cosf(points[0] * (M_PI / 180.0));
+ auto s = sinf(points[0] * (M_PI / 180.0));
+ if (ptCount == 1) {
+ Matrix tmp = { c, -s, 0, s, c, 0, 0, 0, 1 };
+ _matrixCompose(matrix, &tmp, matrix);
+ } else if (ptCount == 3) {
+ Matrix tmp = { 1, 0, points[1], 0, 1, points[2], 0, 0, 1 };
+ _matrixCompose(matrix, &tmp, matrix);
+ tmp = { c, -s, 0, s, c, 0, 0, 0, 1 };
+ _matrixCompose(matrix, &tmp, matrix);
+ tmp = { 1, 0, -points[1], 0, 1, -points[2], 0, 0, 1 };
+ _matrixCompose(matrix, &tmp, matrix);
+ } else {
+ goto error;
+ }
+ } else if (state == MatrixState::Scale) {
+ if (ptCount < 1 || ptCount > 2) goto error;
+ auto sx = points[0];
+ auto sy = sx;
+ if (ptCount == 2) sy = points[1];
+ Matrix tmp = { sx, 0, 0, 0, sy, 0, 0, 0, 1 };
+ _matrixCompose(matrix, &tmp, matrix);
+ }
+ }
+ return matrix;
+error:
+ if (matrix) free(matrix);
+ return nullptr;
+}
+
+
+#define LENGTH_DEF(Name, Value) \
+ { \
+#Name, sizeof(#Name), Value \
+ }
+
+
+/*
+// TODO - remove?
+static constexpr struct
+{
+ const char* tag;
+ int sz;
+ SvgLengthType type;
+} lengthTags[] = {
+ LENGTH_DEF(%, SvgLengthType::Percent),
+ LENGTH_DEF(px, SvgLengthType::Px),
+ LENGTH_DEF(pc, SvgLengthType::Pc),
+ LENGTH_DEF(pt, SvgLengthType::Pt),
+ LENGTH_DEF(mm, SvgLengthType::Mm),
+ LENGTH_DEF(cm, SvgLengthType::Cm),
+ LENGTH_DEF(in, SvgLengthType::In)
+};
+
+static float _parseLength(const char* str, SvgLengthType* type)
+{
+ float value;
+ int sz = strlen(str);
+
+ *type = SvgLengthType::Px;
+ for (unsigned int i = 0; i < sizeof(lengthTags) / sizeof(lengthTags[0]); i++) {
+ if (lengthTags[i].sz - 1 == sz && !strncmp(lengthTags[i].tag, str, sz)) *type = lengthTags[i].type;
+ }
+ value = svgUtilStrtof(str, nullptr);
+ return value;
+}
+*/
+
+static bool _parseStyleAttr(void* data, const char* key, const char* value);
+static bool _parseStyleAttr(void* data, const char* key, const char* value, bool style);
+
+
+static bool _attrParseSvgNode(void* data, const char* key, const char* value)
+{
+ SvgLoaderData* loader = (SvgLoaderData*)data;
+ SvgNode* node = loader->svgParse->node;
+ SvgDocNode* doc = &(node->node.doc);
+
+ if (!strcmp(key, "width")) {
+ doc->w = _toFloat(loader->svgParse, value, SvgParserLengthType::Horizontal);
+ } else if (!strcmp(key, "height")) {
+ doc->h = _toFloat(loader->svgParse, value, SvgParserLengthType::Vertical);
+ } else if (!strcmp(key, "viewBox")) {
+ if (_parseNumber(&value, &doc->vx)) {
+ if (_parseNumber(&value, &doc->vy)) {
+ if (_parseNumber(&value, &doc->vw)) {
+ _parseNumber(&value, &doc->vh);
+ loader->svgParse->global.h = (uint32_t)doc->vh;
+ }
+ loader->svgParse->global.w = (uint32_t)doc->vw;
+ }
+ loader->svgParse->global.y = (int)doc->vy;
+ }
+ loader->svgParse->global.x = (int)doc->vx;
+ } else if (!strcmp(key, "preserveAspectRatio")) {
+ if (!strcmp(value, "none")) doc->preserveAspect = false;
+ } else if (!strcmp(key, "style")) {
+ return simpleXmlParseW3CAttribute(value, _parseStyleAttr, loader);
+ }
+#ifdef THORVG_LOG_ENABLED
+ else if ((!strcmp(key, "x") || !strcmp(key, "y")) && fabsf(svgUtilStrtof(value, nullptr)) > FLT_EPSILON ) {
+ TVGLOG("SVG", "Unsupported attributes used [Elements type: Svg][Attribute: %s][Value: %s]", key, value);
+ }
+#endif
+ else {
+ return _parseStyleAttr(loader, key, value, false);
+ }
+ return true;
+}
+
+
+//https://www.w3.org/TR/SVGTiny12/painting.html#SpecifyingPaint
+static void _handlePaintAttr(SvgPaint* paint, const char* value)
+{
+ if (!strcmp(value, "none")) {
+ //No paint property
+ paint->none = true;
+ return;
+ }
+ paint->none = false;
+ if (!strcmp(value, "currentColor")) {
+ paint->curColor = true;
+ return;
+ }
+ _toColor(value, &paint->color.r, &paint->color.g, &paint->color.b, &paint->url);
+}
+
+
+static void _handleColorAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, const char* value)
+{
+ SvgStyleProperty* style = node->style;
+ style->curColorSet = true;
+ _toColor(value, &style->color.r, &style->color.g, &style->color.b, nullptr);
+}
+
+
+static void _handleFillAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, const char* value)
+{
+ SvgStyleProperty* style = node->style;
+ style->fill.flags = (SvgFillFlags)((int)style->fill.flags | (int)SvgFillFlags::Paint);
+ _handlePaintAttr(&style->fill.paint, value);
+}
+
+
+static void _handleStrokeAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, const char* value)
+{
+ SvgStyleProperty* style = node->style;
+ style->stroke.flags = (SvgStrokeFlags)((int)style->stroke.flags | (int)SvgStrokeFlags::Paint);
+ _handlePaintAttr(&style->stroke.paint, value);
+}
+
+
+static void _handleStrokeOpacityAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, const char* value)
+{
+ node->style->stroke.flags = (SvgStrokeFlags)((int)node->style->stroke.flags | (int)SvgStrokeFlags::Opacity);
+ node->style->stroke.opacity = _toOpacity(value);
+}
+
+static void _handleStrokeDashArrayAttr(SvgLoaderData* loader, SvgNode* node, const char* value)
+{
+ node->style->stroke.flags = (SvgStrokeFlags)((int)node->style->stroke.flags | (int)SvgStrokeFlags::Dash);
+ _parseDashArray(loader, value, &node->style->stroke.dash);
+}
+
+static void _handleStrokeWidthAttr(SvgLoaderData* loader, SvgNode* node, const char* value)
+{
+ node->style->stroke.flags = (SvgStrokeFlags)((int)node->style->stroke.flags | (int)SvgStrokeFlags::Width);
+ node->style->stroke.width = _toFloat(loader->svgParse, value, SvgParserLengthType::Horizontal);
+}
+
+
+static void _handleStrokeLineCapAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, const char* value)
+{
+ node->style->stroke.flags = (SvgStrokeFlags)((int)node->style->stroke.flags | (int)SvgStrokeFlags::Cap);
+ node->style->stroke.cap = _toLineCap(value);
+}
+
+
+static void _handleStrokeLineJoinAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, const char* value)
+{
+ node->style->stroke.flags = (SvgStrokeFlags)((int)node->style->stroke.flags | (int)SvgStrokeFlags::Join);
+ node->style->stroke.join = _toLineJoin(value);
+}
+
+
+static void _handleFillRuleAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, const char* value)
+{
+ node->style->fill.flags = (SvgFillFlags)((int)node->style->fill.flags | (int)SvgFillFlags::FillRule);
+ node->style->fill.fillRule = _toFillRule(value);
+}
+
+
+static void _handleOpacityAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, const char* value)
+{
+ node->style->opacity = _toOpacity(value);
+}
+
+
+static void _handleFillOpacityAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, const char* value)
+{
+ node->style->fill.flags = (SvgFillFlags)((int)node->style->fill.flags | (int)SvgFillFlags::Opacity);
+ node->style->fill.opacity = _toOpacity(value);
+}
+
+
+static void _handleTransformAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, const char* value)
+{
+ node->transform = _parseTransformationMatrix(value);
+}
+
+
+static void _handleClipPathAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, const char* value)
+{
+ SvgStyleProperty* style = node->style;
+ int len = strlen(value);
+ if (len >= 3 && !strncmp(value, "url", 3)) {
+ if (style->clipPath.url) free(style->clipPath.url);
+ style->clipPath.url = _idFromUrl((const char*)(value + 3));
+ }
+}
+
+
+static void _handleMaskAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, const char* value)
+{
+ SvgStyleProperty* style = node->style;
+ int len = strlen(value);
+ if (len >= 3 && !strncmp(value, "url", 3)) {
+ if (style->mask.url) free(style->mask.url);
+ style->mask.url = _idFromUrl((const char*)(value + 3));
+ }
+}
+
+
+static void _handleDisplayAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, const char* value)
+{
+ //TODO : The display attribute can have various values as well as "none".
+ // The default is "inline" which means visible and "none" means invisible.
+ // Depending on the type of node, additional functionality may be required.
+ // refer to https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/display
+ if (!strcmp(value, "none")) node->display = false;
+ else node->display = true;
+}
+
+
+typedef void (*styleMethod)(SvgLoaderData* loader, SvgNode* node, const char* value);
+
+#define STYLE_DEF(Name, Name1, Flag) { #Name, sizeof(#Name), _handle##Name1##Attr, Flag }
+
+
+static constexpr struct
+{
+ const char* tag;
+ int sz;
+ styleMethod tagHandler;
+ SvgStyleFlags flag;
+} styleTags[] = {
+ STYLE_DEF(color, Color, SvgStyleFlags::Color),
+ STYLE_DEF(fill, Fill, SvgStyleFlags::Fill),
+ STYLE_DEF(fill-rule, FillRule, SvgStyleFlags::FillRule),
+ STYLE_DEF(fill-opacity, FillOpacity, SvgStyleFlags::FillOpacity),
+ STYLE_DEF(opacity, Opacity, SvgStyleFlags::Opacity),
+ STYLE_DEF(stroke, Stroke, SvgStyleFlags::Stroke),
+ STYLE_DEF(stroke-width, StrokeWidth, SvgStyleFlags::StrokeWidth),
+ STYLE_DEF(stroke-linejoin, StrokeLineJoin, SvgStyleFlags::StrokeLineJoin),
+ STYLE_DEF(stroke-linecap, StrokeLineCap, SvgStyleFlags::StrokeLineCap),
+ STYLE_DEF(stroke-opacity, StrokeOpacity, SvgStyleFlags::StrokeOpacity),
+ STYLE_DEF(stroke-dasharray, StrokeDashArray, SvgStyleFlags::StrokeDashArray),
+ STYLE_DEF(transform, Transform, SvgStyleFlags::Transform),
+ STYLE_DEF(clip-path, ClipPath, SvgStyleFlags::ClipPath),
+ STYLE_DEF(mask, Mask, SvgStyleFlags::Mask),
+ STYLE_DEF(display, Display, SvgStyleFlags::Display)
+};
+
+
+static bool _parseStyleAttr(void* data, const char* key, const char* value, bool style)
+{
+ SvgLoaderData* loader = (SvgLoaderData*)data;
+ SvgNode* node = loader->svgParse->node;
+ int sz;
+ if (!key || !value) return false;
+
+ //Trim the white space
+ key = _skipSpace(key, nullptr);
+ value = _skipSpace(value, nullptr);
+
+ sz = strlen(key);
+ for (unsigned int i = 0; i < sizeof(styleTags) / sizeof(styleTags[0]); i++) {
+ if (styleTags[i].sz - 1 == sz && !strncmp(styleTags[i].tag, key, sz)) {
+ if (style) {
+ styleTags[i].tagHandler(loader, node, value);
+ node->style->flags = (SvgStyleFlags)((int)node->style->flags | (int)styleTags[i].flag);
+ } else if (!((int)node->style->flags & (int)styleTags[i].flag)) {
+ styleTags[i].tagHandler(loader, node, value);
+ }
+ return true;
+ }
+ }
+
+ return false;
+}
+
+
+static bool _parseStyleAttr(void* data, const char* key, const char* value)
+{
+ return _parseStyleAttr(data, key, value, true);
+}
+
+
+/* parse g node
+ * https://www.w3.org/TR/SVG/struct.html#Groups
+ */
+static bool _attrParseGNode(void* data, const char* key, const char* value)
+{
+ SvgLoaderData* loader = (SvgLoaderData*)data;
+ SvgNode* node = loader->svgParse->node;
+
+ if (!strcmp(key, "style")) {
+ return simpleXmlParseW3CAttribute(value, _parseStyleAttr, loader);
+ } else if (!strcmp(key, "transform")) {
+ node->transform = _parseTransformationMatrix(value);
+ } else if (!strcmp(key, "id")) {
+ if (node->id && value) free(node->id);
+ node->id = _copyId(value);
+ } else if (!strcmp(key, "clip-path")) {
+ _handleClipPathAttr(loader, node, value);
+ } else if (!strcmp(key, "mask")) {
+ _handleMaskAttr(loader, node, value);
+ } else {
+ return _parseStyleAttr(loader, key, value, false);
+ }
+ return true;
+}
+
+
+/* parse clipPath node
+ * https://www.w3.org/TR/SVG/struct.html#Groups
+ */
+static bool _attrParseClipPathNode(void* data, const char* key, const char* value)
+{
+ SvgLoaderData* loader = (SvgLoaderData*)data;
+ SvgNode* node = loader->svgParse->node;
+ SvgCompositeNode* comp = &(node->node.comp);
+
+ if (!strcmp(key, "style")) {
+ return simpleXmlParseW3CAttribute(value, _parseStyleAttr, loader);
+ } else if (!strcmp(key, "transform")) {
+ node->transform = _parseTransformationMatrix(value);
+ } else if (!strcmp(key, "id")) {
+ if (node->id && value) free(node->id);
+ node->id = _copyId(value);
+ } else if (!strcmp(key, "clipPathUnits")) {
+ if (!strcmp(value, "objectBoundingBox")) comp->userSpace = false;
+ } else {
+ return _parseStyleAttr(loader, key, value, false);
+ }
+ return true;
+}
+
+
+static bool _attrParseMaskNode(void* data, const char* key, const char* value)
+{
+ SvgLoaderData* loader = (SvgLoaderData*)data;
+ SvgNode* node = loader->svgParse->node;
+ SvgCompositeNode* comp = &(node->node.comp);
+
+ if (!strcmp(key, "style")) {
+ return simpleXmlParseW3CAttribute(value, _parseStyleAttr, loader);
+ } else if (!strcmp(key, "transform")) {
+ node->transform = _parseTransformationMatrix(value);
+ } else if (!strcmp(key, "id")) {
+ if (node->id && value) free(node->id);
+ node->id = _copyId(value);
+ } else if (!strcmp(key, "maskContentUnits")) {
+ if (!strcmp(value, "objectBoundingBox")) comp->userSpace = false;
+ } else {
+ return _parseStyleAttr(loader, key, value, false);
+ }
+ return true;
+}
+
+
+static SvgNode* _createNode(SvgNode* parent, SvgNodeType type)
+{
+ SvgNode* node = (SvgNode*)calloc(1, sizeof(SvgNode));
+
+ if (!node) return nullptr;
+
+ //Default fill property
+ node->style = (SvgStyleProperty*)calloc(1, sizeof(SvgStyleProperty));
+
+ if (!node->style) {
+ free(node);
+ return nullptr;
+ }
+
+ //Update the default value of stroke and fill
+ //https://www.w3.org/TR/SVGTiny12/painting.html#SpecifyingPaint
+ node->style->fill.paint.none = false;
+ //Default fill opacity is 1
+ node->style->fill.opacity = 255;
+ node->style->opacity = 255;
+ //Default current color is not set
+ node->style->fill.paint.curColor = false;
+ node->style->curColorSet = false;
+ //Default fill rule is nonzero
+ node->style->fill.fillRule = FillRule::Winding;
+
+ //Default stroke is none
+ node->style->stroke.paint.none = true;
+ //Default stroke opacity is 1
+ node->style->stroke.opacity = 255;
+ //Default stroke current color is not set
+ node->style->stroke.paint.curColor = false;
+ //Default stroke width is 1
+ node->style->stroke.width = 1;
+ //Default line cap is butt
+ node->style->stroke.cap = StrokeCap::Butt;
+ //Default line join is miter
+ node->style->stroke.join = StrokeJoin::Miter;
+ node->style->stroke.scale = 1.0;
+
+ //Default display is true("inline").
+ node->display = true;
+
+ node->parent = parent;
+ node->type = type;
+
+ if (parent) parent->child.push(node);
+ return node;
+}
+
+
+static SvgNode* _createDefsNode(TVG_UNUSED SvgLoaderData* loader, TVG_UNUSED SvgNode* parent, const char* buf, unsigned bufLength)
+{
+ if (loader->def && loader->doc->node.doc.defs) return loader->def;
+ SvgNode* node = _createNode(nullptr, SvgNodeType::Defs);
+
+ loader->def = node;
+ loader->doc->node.doc.defs = node;
+ return node;
+}
+
+
+static SvgNode* _createGNode(TVG_UNUSED SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength)
+{
+ loader->svgParse->node = _createNode(parent, SvgNodeType::G);
+ if (!loader->svgParse->node) return nullptr;
+
+ simpleXmlParseAttributes(buf, bufLength, _attrParseGNode, loader);
+ return loader->svgParse->node;
+}
+
+
+static SvgNode* _createSvgNode(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength)
+{
+ loader->svgParse->node = _createNode(parent, SvgNodeType::Doc);
+ if (!loader->svgParse->node) return nullptr;
+ SvgDocNode* doc = &(loader->svgParse->node->node.doc);
+
+ loader->svgParse->global.w = 0;
+ loader->svgParse->global.h = 0;
+
+ doc->preserveAspect = true;
+ simpleXmlParseAttributes(buf, bufLength, _attrParseSvgNode, loader);
+
+ if (loader->svgParse->global.w == 0) {
+ if (doc->w < FLT_EPSILON) loader->svgParse->global.w = 1;
+ else loader->svgParse->global.w = (uint32_t)doc->w;
+ }
+ if (loader->svgParse->global.h == 0) {
+ if (doc->h < FLT_EPSILON) loader->svgParse->global.h = 1;
+ else loader->svgParse->global.h = (uint32_t)doc->h;
+ }
+
+ return loader->svgParse->node;
+}
+
+
+static SvgNode* _createMaskNode(SvgLoaderData* loader, SvgNode* parent, TVG_UNUSED const char* buf, TVG_UNUSED unsigned bufLength)
+{
+ loader->svgParse->node = _createNode(parent, SvgNodeType::Mask);
+ if (!loader->svgParse->node) return nullptr;
+
+ loader->svgParse->node->node.comp.userSpace = true;
+
+ simpleXmlParseAttributes(buf, bufLength, _attrParseMaskNode, loader);
+
+ return loader->svgParse->node;
+}
+
+
+static SvgNode* _createClipPathNode(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength)
+{
+ loader->svgParse->node = _createNode(parent, SvgNodeType::ClipPath);
+ if (!loader->svgParse->node) return nullptr;
+
+ loader->svgParse->node->display = false;
+ loader->svgParse->node->node.comp.userSpace = true;
+
+ simpleXmlParseAttributes(buf, bufLength, _attrParseClipPathNode, loader);
+
+ return loader->svgParse->node;
+}
+
+static bool _attrParsePathNode(void* data, const char* key, const char* value)
+{
+ SvgLoaderData* loader = (SvgLoaderData*)data;
+ SvgNode* node = loader->svgParse->node;
+ SvgPathNode* path = &(node->node.path);
+
+ if (!strcmp(key, "d")) {
+ //Temporary: need to copy
+ path->path = _copyId(value);
+ } else if (!strcmp(key, "style")) {
+ return simpleXmlParseW3CAttribute(value, _parseStyleAttr, loader);
+ } else if (!strcmp(key, "clip-path")) {
+ _handleClipPathAttr(loader, node, value);
+ } else if (!strcmp(key, "mask")) {
+ _handleMaskAttr(loader, node, value);
+ } else if (!strcmp(key, "id")) {
+ if (node->id && value) free(node->id);
+ node->id = _copyId(value);
+ } else {
+ return _parseStyleAttr(loader, key, value, false);
+ }
+ return true;
+}
+
+
+static SvgNode* _createPathNode(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength)
+{
+ loader->svgParse->node = _createNode(parent, SvgNodeType::Path);
+
+ if (!loader->svgParse->node) return nullptr;
+
+ simpleXmlParseAttributes(buf, bufLength, _attrParsePathNode, loader);
+
+ return loader->svgParse->node;
+}
+
+
+static constexpr struct
+{
+ const char* tag;
+ SvgParserLengthType type;
+ int sz;
+ size_t offset;
+} circleTags[] = {
+ {"cx", SvgParserLengthType::Horizontal, sizeof("cx"), offsetof(SvgCircleNode, cx)},
+ {"cy", SvgParserLengthType::Vertical, sizeof("cy"), offsetof(SvgCircleNode, cy)},
+ {"r", SvgParserLengthType::Other, sizeof("r"), offsetof(SvgCircleNode, r)}
+};
+
+
+/* parse the attributes for a circle element.
+ * https://www.w3.org/TR/SVG/shapes.html#CircleElement
+ */
+static bool _attrParseCircleNode(void* data, const char* key, const char* value)
+{
+ SvgLoaderData* loader = (SvgLoaderData*)data;
+ SvgNode* node = loader->svgParse->node;
+ SvgCircleNode* circle = &(node->node.circle);
+ unsigned char* array;
+ int sz = strlen(key);
+
+ array = (unsigned char*)circle;
+ for (unsigned int i = 0; i < sizeof(circleTags) / sizeof(circleTags[0]); i++) {
+ if (circleTags[i].sz - 1 == sz && !strncmp(circleTags[i].tag, key, sz)) {
+ *((float*)(array + circleTags[i].offset)) = _toFloat(loader->svgParse, value, circleTags[i].type);
+ return true;
+ }
+ }
+
+ if (!strcmp(key, "style")) {
+ return simpleXmlParseW3CAttribute(value, _parseStyleAttr, loader);
+ } else if (!strcmp(key, "clip-path")) {
+ _handleClipPathAttr(loader, node, value);
+ } else if (!strcmp(key, "mask")) {
+ _handleMaskAttr(loader, node, value);
+ } else if (!strcmp(key, "id")) {
+ if (node->id && value) free(node->id);
+ node->id = _copyId(value);
+ } else {
+ return _parseStyleAttr(loader, key, value, false);
+ }
+ return true;
+}
+
+
+static SvgNode* _createCircleNode(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength)
+{
+ loader->svgParse->node = _createNode(parent, SvgNodeType::Circle);
+
+ if (!loader->svgParse->node) return nullptr;
+
+ simpleXmlParseAttributes(buf, bufLength, _attrParseCircleNode, loader);
+ return loader->svgParse->node;
+}
+
+
+static constexpr struct
+{
+ const char* tag;
+ SvgParserLengthType type;
+ int sz;
+ size_t offset;
+} ellipseTags[] = {
+ {"cx", SvgParserLengthType::Horizontal, sizeof("cx"), offsetof(SvgEllipseNode, cx)},
+ {"cy", SvgParserLengthType::Vertical, sizeof("cy"), offsetof(SvgEllipseNode, cy)},
+ {"rx", SvgParserLengthType::Horizontal, sizeof("rx"), offsetof(SvgEllipseNode, rx)},
+ {"ry", SvgParserLengthType::Vertical, sizeof("ry"), offsetof(SvgEllipseNode, ry)}
+};
+
+
+/* parse the attributes for an ellipse element.
+ * https://www.w3.org/TR/SVG/shapes.html#EllipseElement
+ */
+static bool _attrParseEllipseNode(void* data, const char* key, const char* value)
+{
+ SvgLoaderData* loader = (SvgLoaderData*)data;
+ SvgNode* node = loader->svgParse->node;
+ SvgEllipseNode* ellipse = &(node->node.ellipse);
+ unsigned char* array;
+ int sz = strlen(key);
+
+ array = (unsigned char*)ellipse;
+ for (unsigned int i = 0; i < sizeof(ellipseTags) / sizeof(ellipseTags[0]); i++) {
+ if (ellipseTags[i].sz - 1 == sz && !strncmp(ellipseTags[i].tag, key, sz)) {
+ *((float*)(array + ellipseTags[i].offset)) = _toFloat(loader->svgParse, value, ellipseTags[i].type);
+ return true;
+ }
+ }
+
+ if (!strcmp(key, "id")) {
+ if (node->id && value) free(node->id);
+ node->id = _copyId(value);
+ } else if (!strcmp(key, "style")) {
+ return simpleXmlParseW3CAttribute(value, _parseStyleAttr, loader);
+ } else if (!strcmp(key, "clip-path")) {
+ _handleClipPathAttr(loader, node, value);
+ } else if (!strcmp(key, "mask")) {
+ _handleMaskAttr(loader, node, value);
+ } else {
+ return _parseStyleAttr(loader, key, value, false);
+ }
+ return true;
+}
+
+
+static SvgNode* _createEllipseNode(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength)
+{
+ loader->svgParse->node = _createNode(parent, SvgNodeType::Ellipse);
+
+ if (!loader->svgParse->node) return nullptr;
+
+ simpleXmlParseAttributes(buf, bufLength, _attrParseEllipseNode, loader);
+ return loader->svgParse->node;
+}
+
+
+static bool _attrParsePolygonPoints(const char* str, float** points, int* ptCount)
+{
+ float tmp[50];
+ int tmpCount = 0;
+ int count = 0;
+ float num;
+ float *pointArray = nullptr, *tmpArray;
+
+ while (_parseNumber(&str, &num)) {
+ tmp[tmpCount++] = num;
+ if (tmpCount == 50) {
+ tmpArray = (float*)realloc(pointArray, (count + tmpCount) * sizeof(float));
+ if (!tmpArray) goto error_alloc;
+ pointArray = tmpArray;
+ memcpy(&pointArray[count], tmp, tmpCount * sizeof(float));
+ count += tmpCount;
+ tmpCount = 0;
+ }
+ }
+
+ if (tmpCount > 0) {
+ tmpArray = (float*)realloc(pointArray, (count + tmpCount) * sizeof(float));
+ if (!tmpArray) goto error_alloc;
+ pointArray = tmpArray;
+ memcpy(&pointArray[count], tmp, tmpCount * sizeof(float));
+ count += tmpCount;
+ }
+ *ptCount = count;
+ *points = pointArray;
+ return true;
+
+error_alloc:
+ return false;
+}
+
+
+/* parse the attributes for a polygon element.
+ * https://www.w3.org/TR/SVG/shapes.html#PolylineElement
+ */
+static bool _attrParsePolygonNode(void* data, const char* key, const char* value)
+{
+ SvgLoaderData* loader = (SvgLoaderData*)data;
+ SvgNode* node = loader->svgParse->node;
+ SvgPolygonNode* polygon = nullptr;
+
+ if (node->type == SvgNodeType::Polygon) polygon = &(node->node.polygon);
+ else polygon = &(node->node.polyline);
+
+ if (!strcmp(key, "points")) {
+ return _attrParsePolygonPoints(value, &polygon->points, &polygon->pointsCount);
+ } else if (!strcmp(key, "style")) {
+ return simpleXmlParseW3CAttribute(value, _parseStyleAttr, loader);
+ } else if (!strcmp(key, "clip-path")) {
+ _handleClipPathAttr(loader, node, value);
+ } else if (!strcmp(key, "mask")) {
+ _handleMaskAttr(loader, node, value);
+ } else if (!strcmp(key, "id")) {
+ if (node->id && value) free(node->id);
+ node->id = _copyId(value);
+ } else {
+ return _parseStyleAttr(loader, key, value, false);
+ }
+ return true;
+}
+
+
+static SvgNode* _createPolygonNode(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength)
+{
+ loader->svgParse->node = _createNode(parent, SvgNodeType::Polygon);
+
+ if (!loader->svgParse->node) return nullptr;
+
+ simpleXmlParseAttributes(buf, bufLength, _attrParsePolygonNode, loader);
+ return loader->svgParse->node;
+}
+
+
+static SvgNode* _createPolylineNode(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength)
+{
+ loader->svgParse->node = _createNode(parent, SvgNodeType::Polyline);
+
+ if (!loader->svgParse->node) return nullptr;
+
+ simpleXmlParseAttributes(buf, bufLength, _attrParsePolygonNode, loader);
+ return loader->svgParse->node;
+}
+
+static constexpr struct
+{
+ const char* tag;
+ SvgParserLengthType type;
+ int sz;
+ size_t offset;
+} rectTags[] = {
+ {"x", SvgParserLengthType::Horizontal, sizeof("x"), offsetof(SvgRectNode, x)},
+ {"y", SvgParserLengthType::Vertical, sizeof("y"), offsetof(SvgRectNode, y)},
+ {"width", SvgParserLengthType::Horizontal, sizeof("width"), offsetof(SvgRectNode, w)},
+ {"height", SvgParserLengthType::Vertical, sizeof("height"), offsetof(SvgRectNode, h)},
+ {"rx", SvgParserLengthType::Horizontal, sizeof("rx"), offsetof(SvgRectNode, rx)},
+ {"ry", SvgParserLengthType::Vertical, sizeof("ry"), offsetof(SvgRectNode, ry)}
+};
+
+
+/* parse the attributes for a rect element.
+ * https://www.w3.org/TR/SVG/shapes.html#RectElement
+ */
+static bool _attrParseRectNode(void* data, const char* key, const char* value)
+{
+ SvgLoaderData* loader = (SvgLoaderData*)data;
+ SvgNode* node = loader->svgParse->node;
+ SvgRectNode* rect = &(node->node.rect);
+ unsigned char* array;
+ bool ret = true;
+ int sz = strlen(key);
+
+ array = (unsigned char*)rect;
+ for (unsigned int i = 0; i < sizeof(rectTags) / sizeof(rectTags[0]); i++) {
+ if (rectTags[i].sz - 1 == sz && !strncmp(rectTags[i].tag, key, sz)) {
+ *((float*)(array + rectTags[i].offset)) = _toFloat(loader->svgParse, value, rectTags[i].type);
+
+ //Case if only rx or ry is declared
+ if (!strncmp(rectTags[i].tag, "rx", sz)) rect->hasRx = true;
+ if (!strncmp(rectTags[i].tag, "ry", sz)) rect->hasRy = true;
+
+ if ((rect->rx >= FLT_EPSILON) && (rect->ry < FLT_EPSILON) && rect->hasRx && !rect->hasRy) rect->ry = rect->rx;
+ if ((rect->ry >= FLT_EPSILON) && (rect->rx < FLT_EPSILON) && !rect->hasRx && rect->hasRy) rect->rx = rect->ry;
+ return ret;
+ }
+ }
+
+ if (!strcmp(key, "id")) {
+ if (node->id && value) free(node->id);
+ node->id = _copyId(value);
+ } else if (!strcmp(key, "style")) {
+ ret = simpleXmlParseW3CAttribute(value, _parseStyleAttr, loader);
+ } else if (!strcmp(key, "clip-path")) {
+ _handleClipPathAttr(loader, node, value);
+ } else if (!strcmp(key, "mask")) {
+ _handleMaskAttr(loader, node, value);
+ } else {
+ ret = _parseStyleAttr(loader, key, value, false);
+ }
+
+ return ret;
+}
+
+
+static SvgNode* _createRectNode(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength)
+{
+ loader->svgParse->node = _createNode(parent, SvgNodeType::Rect);
+
+ if (!loader->svgParse->node) return nullptr;
+
+ loader->svgParse->node->node.rect.hasRx = loader->svgParse->node->node.rect.hasRy = false;
+
+ simpleXmlParseAttributes(buf, bufLength, _attrParseRectNode, loader);
+ return loader->svgParse->node;
+}
+
+
+static constexpr struct
+{
+ const char* tag;
+ SvgParserLengthType type;
+ int sz;
+ size_t offset;
+} lineTags[] = {
+ {"x1", SvgParserLengthType::Horizontal, sizeof("x1"), offsetof(SvgLineNode, x1)},
+ {"y1", SvgParserLengthType::Vertical, sizeof("y1"), offsetof(SvgLineNode, y1)},
+ {"x2", SvgParserLengthType::Horizontal, sizeof("x2"), offsetof(SvgLineNode, x2)},
+ {"y2", SvgParserLengthType::Vertical, sizeof("y2"), offsetof(SvgLineNode, y2)}
+};
+
+
+/* parse the attributes for a line element.
+ * https://www.w3.org/TR/SVG/shapes.html#LineElement
+ */
+static bool _attrParseLineNode(void* data, const char* key, const char* value)
+{
+ SvgLoaderData* loader = (SvgLoaderData*)data;
+ SvgNode* node = loader->svgParse->node;
+ SvgLineNode* line = &(node->node.line);
+ unsigned char* array;
+ int sz = strlen(key);
+
+ array = (unsigned char*)line;
+ for (unsigned int i = 0; i < sizeof(lineTags) / sizeof(lineTags[0]); i++) {
+ if (lineTags[i].sz - 1 == sz && !strncmp(lineTags[i].tag, key, sz)) {
+ *((float*)(array + lineTags[i].offset)) = _toFloat(loader->svgParse, value, lineTags[i].type);
+ return true;
+ }
+ }
+
+ if (!strcmp(key, "id")) {
+ if (node->id && value) free(node->id);
+ node->id = _copyId(value);
+ } else if (!strcmp(key, "style")) {
+ return simpleXmlParseW3CAttribute(value, _parseStyleAttr, loader);
+ } else if (!strcmp(key, "clip-path")) {
+ _handleClipPathAttr(loader, node, value);
+ } else if (!strcmp(key, "mask")) {
+ _handleMaskAttr(loader, node, value);
+ } else {
+ return _parseStyleAttr(loader, key, value, false);
+ }
+ return true;
+}
+
+
+static SvgNode* _createLineNode(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength)
+{
+ loader->svgParse->node = _createNode(parent, SvgNodeType::Line);
+
+ if (!loader->svgParse->node) return nullptr;
+
+ simpleXmlParseAttributes(buf, bufLength, _attrParseLineNode, loader);
+ return loader->svgParse->node;
+}
+
+
+static char* _idFromHref(const char* href)
+{
+ href = _skipSpace(href, nullptr);
+ if ((*href) == '#') href++;
+ return strdup(href);
+}
+
+
+static constexpr struct
+{
+ const char* tag;
+ SvgParserLengthType type;
+ int sz;
+ size_t offset;
+} imageTags[] = {
+ {"x", SvgParserLengthType::Horizontal, sizeof("x"), offsetof(SvgRectNode, x)},
+ {"y", SvgParserLengthType::Vertical, sizeof("y"), offsetof(SvgRectNode, y)},
+ {"width", SvgParserLengthType::Horizontal, sizeof("width"), offsetof(SvgRectNode, w)},
+ {"height", SvgParserLengthType::Vertical, sizeof("height"), offsetof(SvgRectNode, h)},
+};
+
+
+/* parse the attributes for a image element.
+ * https://www.w3.org/TR/SVG/embedded.html#ImageElement
+ */
+static bool _attrParseImageNode(void* data, const char* key, const char* value)
+{
+ SvgLoaderData* loader = (SvgLoaderData*)data;
+ SvgNode* node = loader->svgParse->node;
+ SvgImageNode* image = &(node->node.image);
+ unsigned char* array;
+ int sz = strlen(key);
+
+ array = (unsigned char*)image;
+ for (unsigned int i = 0; i < sizeof(imageTags) / sizeof(imageTags[0]); i++) {
+ if (imageTags[i].sz - 1 == sz && !strncmp(imageTags[i].tag, key, sz)) {
+ *((float*)(array + imageTags[i].offset)) = _toFloat(loader->svgParse, value, imageTags[i].type);
+ return true;
+ }
+ }
+
+ if (!strcmp(key, "href") || !strcmp(key, "xlink:href")) {
+ image->href = _idFromHref(value);
+ } else if (!strcmp(key, "id")) {
+ if (node->id && value) free(node->id);
+ node->id = _copyId(value);
+ } else if (!strcmp(key, "style")) {
+ return simpleXmlParseW3CAttribute(value, _parseStyleAttr, loader);
+ } else if (!strcmp(key, "clip-path")) {
+ _handleClipPathAttr(loader, node, value);
+ } else if (!strcmp(key, "mask")) {
+ _handleMaskAttr(loader, node, value);
+ } else {
+ return _parseStyleAttr(loader, key, value);
+ }
+ return true;
+}
+
+
+static SvgNode* _createImageNode(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength)
+{
+ loader->svgParse->node = _createNode(parent, SvgNodeType::Image);
+
+ if (!loader->svgParse->node) return nullptr;
+
+ simpleXmlParseAttributes(buf, bufLength, _attrParseImageNode, loader);
+ return loader->svgParse->node;
+}
+
+
+static SvgNode* _getDefsNode(SvgNode* node)
+{
+ if (!node) return nullptr;
+
+ while (node->parent != nullptr) {
+ node = node->parent;
+ }
+
+ if (node->type == SvgNodeType::Doc) return node->node.doc.defs;
+
+ return nullptr;
+}
+
+
+static SvgNode* _findChildById(const SvgNode* node, const char* id)
+{
+ if (!node) return nullptr;
+
+ auto child = node->child.data;
+ for (uint32_t i = 0; i < node->child.count; ++i, ++child) {
+ if (((*child)->id) && !strcmp((*child)->id, id)) return (*child);
+ }
+ return nullptr;
+}
+
+static SvgNode* _findNodeById(SvgNode *node, const char* id)
+{
+ SvgNode* result = nullptr;
+ if (node->id && !strcmp(node->id, id)) return node;
+
+ if (node->child.count > 0) {
+ auto child = node->child.data;
+ for (uint32_t i = 0; i < node->child.count; ++i, ++child) {
+ result = _findNodeById(*child, id);
+ if (result) break;
+ }
+ }
+ return result;
+}
+
+static void _cloneGradStops(Array<Fill::ColorStop>& dst, const Array<Fill::ColorStop>& src)
+{
+ for (uint32_t i = 0; i < src.count; ++i) {
+ dst.push(src.data[i]);
+ }
+}
+
+
+static SvgStyleGradient* _cloneGradient(SvgStyleGradient* from)
+{
+ if (!from) return nullptr;
+
+ auto grad = (SvgStyleGradient*)(calloc(1, sizeof(SvgStyleGradient)));
+ if (!grad) return nullptr;
+
+ grad->type = from->type;
+ grad->id = from->id ? _copyId(from->id) : nullptr;
+ grad->ref = from->ref ? _copyId(from->ref) : nullptr;
+ grad->spread = from->spread;
+ grad->userSpace = from->userSpace;
+
+ if (from->transform) {
+ grad->transform = (Matrix*)calloc(1, sizeof(Matrix));
+ if (grad->transform) memcpy(grad->transform, from->transform, sizeof(Matrix));
+ }
+
+ if (grad->type == SvgGradientType::Linear) {
+ grad->linear = (SvgLinearGradient*)calloc(1, sizeof(SvgLinearGradient));
+ if (!grad->linear) goto error_grad_alloc;
+ memcpy(grad->linear, from->linear, sizeof(SvgLinearGradient));
+ } else if (grad->type == SvgGradientType::Radial) {
+ grad->radial = (SvgRadialGradient*)calloc(1, sizeof(SvgRadialGradient));
+ if (!grad->radial) goto error_grad_alloc;
+ memcpy(grad->radial, from->radial, sizeof(SvgRadialGradient));
+ }
+
+ _cloneGradStops(grad->stops, from->stops);
+
+ return grad;
+
+error_grad_alloc:
+ if (grad) {
+ grad->clear();
+ free(grad);
+ }
+ return nullptr;
+}
+
+
+static void _copyAttr(SvgNode* to, const SvgNode* from)
+{
+ //Copy matrix attribute
+ if (from->transform) {
+ to->transform = (Matrix*)malloc(sizeof(Matrix));
+ if (to->transform) *to->transform = *from->transform;
+ }
+ //Copy style attribute
+ *to->style = *from->style;
+ if (from->style->fill.paint.url) to->style->fill.paint.url = strdup(from->style->fill.paint.url);
+ if (from->style->stroke.paint.url) to->style->stroke.paint.url = strdup(from->style->stroke.paint.url);
+ if (from->style->clipPath.url) to->style->clipPath.url = strdup(from->style->clipPath.url);
+ if (from->style->mask.url) to->style->mask.url = strdup(from->style->mask.url);
+
+ //Copy node attribute
+ switch (from->type) {
+ case SvgNodeType::Circle: {
+ to->node.circle.cx = from->node.circle.cx;
+ to->node.circle.cy = from->node.circle.cy;
+ to->node.circle.r = from->node.circle.r;
+ break;
+ }
+ case SvgNodeType::Ellipse: {
+ to->node.ellipse.cx = from->node.ellipse.cx;
+ to->node.ellipse.cy = from->node.ellipse.cy;
+ to->node.ellipse.rx = from->node.ellipse.rx;
+ to->node.ellipse.ry = from->node.ellipse.ry;
+ break;
+ }
+ case SvgNodeType::Rect: {
+ to->node.rect.x = from->node.rect.x;
+ to->node.rect.y = from->node.rect.y;
+ to->node.rect.w = from->node.rect.w;
+ to->node.rect.h = from->node.rect.h;
+ to->node.rect.rx = from->node.rect.rx;
+ to->node.rect.ry = from->node.rect.ry;
+ to->node.rect.hasRx = from->node.rect.hasRx;
+ to->node.rect.hasRy = from->node.rect.hasRy;
+ break;
+ }
+ case SvgNodeType::Line: {
+ to->node.line.x1 = from->node.line.x1;
+ to->node.line.y1 = from->node.line.y1;
+ to->node.line.x2 = from->node.line.x2;
+ to->node.line.y2 = from->node.line.y2;
+ break;
+ }
+ case SvgNodeType::Path: {
+ if (from->node.path.path) to->node.path.path = strdup(from->node.path.path);
+ break;
+ }
+ case SvgNodeType::Polygon: {
+ to->node.polygon.pointsCount = from->node.polygon.pointsCount;
+ to->node.polygon.points = (float*)malloc(to->node.polygon.pointsCount * sizeof(float));
+ memcpy(to->node.polygon.points, from->node.polygon.points, to->node.polygon.pointsCount * sizeof(float));
+ break;
+ }
+ case SvgNodeType::Polyline: {
+ to->node.polyline.pointsCount = from->node.polyline.pointsCount;
+ to->node.polyline.points = (float*)malloc(to->node.polyline.pointsCount * sizeof(float));
+ memcpy(to->node.polyline.points, from->node.polyline.points, to->node.polyline.pointsCount * sizeof(float));
+ break;
+ }
+ case SvgNodeType::Image: {
+ to->node.image.x = from->node.image.x;
+ to->node.image.y = from->node.image.y;
+ to->node.image.w = from->node.image.w;
+ to->node.image.h = from->node.image.h;
+ if (from->node.image.href) to->node.image.href = strdup(from->node.image.href);
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+}
+
+
+static void _cloneNode(SvgNode* from, SvgNode* parent)
+{
+ SvgNode* newNode;
+ if (!from || !parent) return;
+
+ newNode = _createNode(parent, from->type);
+
+ if (!newNode) return;
+
+ _copyAttr(newNode, from);
+
+ auto child = from->child.data;
+ for (uint32_t i = 0; i < from->child.count; ++i, ++child) {
+ _cloneNode(*child, newNode);
+ }
+}
+
+
+static void _postponeCloneNode(SvgLoaderData* loader, SvgNode *node, char* id) {
+ loader->cloneNodes.push({node, id});
+}
+
+
+static void _clonePostponedNodes(Array<SvgNodeIdPair>* cloneNodes) {
+ for (uint32_t i = 0; i < cloneNodes->count; ++i) {
+ auto nodeIdPair = cloneNodes->data[i];
+ auto defs = _getDefsNode(nodeIdPair.node);
+ auto nodeFrom = _findChildById(defs, nodeIdPair.id);
+ _cloneNode(nodeFrom, nodeIdPair.node);
+ free(nodeIdPair.id);
+ }
+}
+
+
+static constexpr struct
+{
+ const char* tag;
+ SvgParserLengthType type;
+ int sz;
+ size_t offset;
+} useTags[] = {
+ {"x", SvgParserLengthType::Horizontal, sizeof("x"), offsetof(SvgRectNode, x)},
+ {"y", SvgParserLengthType::Vertical, sizeof("y"), offsetof(SvgRectNode, y)},
+ {"width", SvgParserLengthType::Horizontal, sizeof("width"), offsetof(SvgRectNode, w)},
+ {"height", SvgParserLengthType::Vertical, sizeof("height"), offsetof(SvgRectNode, h)}
+};
+
+
+static bool _attrParseUseNode(void* data, const char* key, const char* value)
+{
+ SvgLoaderData* loader = (SvgLoaderData*)data;
+ SvgNode *defs, *nodeFrom, *node = loader->svgParse->node;
+ char* id;
+
+ SvgUseNode* use = &(node->node.use);
+ int sz = strlen(key);
+ unsigned char* array = (unsigned char*)use;
+ for (unsigned int i = 0; i < sizeof(useTags) / sizeof(useTags[0]); i++) {
+ if (useTags[i].sz - 1 == sz && !strncmp(useTags[i].tag, key, sz)) {
+ *((float*)(array + useTags[i].offset)) = _toFloat(loader->svgParse, value, useTags[i].type);
+ return true;
+ }
+ }
+
+ if (!strcmp(key, "href") || !strcmp(key, "xlink:href")) {
+ id = _idFromHref(value);
+ defs = _getDefsNode(node);
+ nodeFrom = _findChildById(defs, id);
+ if (nodeFrom) {
+ _cloneNode(nodeFrom, node);
+ free(id);
+ } else {
+ //some svg export software include <defs> element at the end of the file
+ //if so the 'from' element won't be found now and we have to repeat finding
+ //after the whole file is parsed
+ _postponeCloneNode(loader, node, id);
+ }
+ } else if (!strcmp(key, "clip-path")) {
+ _handleClipPathAttr(loader, node, value);
+ } else if (!strcmp(key, "mask")) {
+ _handleMaskAttr(loader, node, value);
+ } else {
+ return _attrParseGNode(data, key, value);
+ }
+ return true;
+}
+
+
+static SvgNode* _createUseNode(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength)
+{
+ loader->svgParse->node = _createNode(parent, SvgNodeType::Use);
+
+ if (!loader->svgParse->node) return nullptr;
+
+ simpleXmlParseAttributes(buf, bufLength, _attrParseUseNode, loader);
+ return loader->svgParse->node;
+}
+
+//TODO: Implement 'text' primitive
+static constexpr struct
+{
+ const char* tag;
+ int sz;
+ FactoryMethod tagHandler;
+} graphicsTags[] = {
+ {"use", sizeof("use"), _createUseNode},
+ {"circle", sizeof("circle"), _createCircleNode},
+ {"ellipse", sizeof("ellipse"), _createEllipseNode},
+ {"path", sizeof("path"), _createPathNode},
+ {"polygon", sizeof("polygon"), _createPolygonNode},
+ {"rect", sizeof("rect"), _createRectNode},
+ {"polyline", sizeof("polyline"), _createPolylineNode},
+ {"line", sizeof("line"), _createLineNode},
+ {"image", sizeof("image"), _createImageNode}
+};
+
+
+static constexpr struct
+{
+ const char* tag;
+ int sz;
+ FactoryMethod tagHandler;
+} groupTags[] = {
+ {"defs", sizeof("defs"), _createDefsNode},
+ {"g", sizeof("g"), _createGNode},
+ {"svg", sizeof("svg"), _createSvgNode},
+ {"mask", sizeof("mask"), _createMaskNode},
+ {"clipPath", sizeof("clipPath"), _createClipPathNode}
+};
+
+
+#define FIND_FACTORY(Short_Name, Tags_Array) \
+ static FactoryMethod \
+ _find##Short_Name##Factory(const char* name) \
+ { \
+ unsigned int i; \
+ int sz = strlen(name); \
+ \
+ for (i = 0; i < sizeof(Tags_Array) / sizeof(Tags_Array[0]); i++) { \
+ if (Tags_Array[i].sz - 1 == sz && !strncmp(Tags_Array[i].tag, name, sz)) { \
+ return Tags_Array[i].tagHandler; \
+ } \
+ } \
+ return nullptr; \
+ }
+
+FIND_FACTORY(Group, groupTags)
+FIND_FACTORY(Graphics, graphicsTags)
+
+
+FillSpread _parseSpreadValue(const char* value)
+{
+ auto spread = FillSpread::Pad;
+
+ if (!strcmp(value, "reflect")) {
+ spread = FillSpread::Reflect;
+ } else if (!strcmp(value, "repeat")) {
+ spread = FillSpread::Repeat;
+ }
+
+ return spread;
+}
+
+
+static void _handleRadialCxAttr(SvgLoaderData* loader, SvgRadialGradient* radial, const char* value)
+{
+ radial->cx = _gradientToFloat(loader->svgParse, value, radial->isCxPercentage);
+ if (!loader->svgParse->gradient.parsedFx) {
+ radial->fx = radial->cx;
+ radial->isFxPercentage = radial->isCxPercentage;
+ }
+}
+
+
+static void _handleRadialCyAttr(SvgLoaderData* loader, SvgRadialGradient* radial, const char* value)
+{
+ radial->cy = _gradientToFloat(loader->svgParse, value, radial->isCyPercentage);
+ if (!loader->svgParse->gradient.parsedFy) {
+ radial->fy = radial->cy;
+ radial->isFyPercentage = radial->isCyPercentage;
+ }
+}
+
+
+static void _handleRadialFxAttr(SvgLoaderData* loader, SvgRadialGradient* radial, const char* value)
+{
+ radial->fx = _gradientToFloat(loader->svgParse, value, radial->isFxPercentage);
+ loader->svgParse->gradient.parsedFx = true;
+}
+
+
+static void _handleRadialFyAttr(SvgLoaderData* loader, SvgRadialGradient* radial, const char* value)
+{
+ radial->fy = _gradientToFloat(loader->svgParse, value, radial->isFyPercentage);
+ loader->svgParse->gradient.parsedFy = true;
+}
+
+
+static void _handleRadialRAttr(SvgLoaderData* loader, SvgRadialGradient* radial, const char* value)
+{
+ radial->r = _gradientToFloat(loader->svgParse, value, radial->isRPercentage);
+}
+
+
+static void _recalcRadialCxAttr(SvgLoaderData* loader, SvgRadialGradient* radial, bool userSpace)
+{
+ if (userSpace && !radial->isCxPercentage) radial->cx = radial->cx / loader->svgParse->global.w;
+}
+
+
+static void _recalcRadialCyAttr(SvgLoaderData* loader, SvgRadialGradient* radial, bool userSpace)
+{
+ if (userSpace && !radial->isCyPercentage) radial->cy = radial->cy / loader->svgParse->global.h;
+}
+
+
+static void _recalcRadialFxAttr(SvgLoaderData* loader, SvgRadialGradient* radial, bool userSpace)
+{
+ if (userSpace && !radial->isFxPercentage) radial->fx = radial->fx / loader->svgParse->global.w;
+}
+
+
+static void _recalcRadialFyAttr(SvgLoaderData* loader, SvgRadialGradient* radial, bool userSpace)
+{
+ if (userSpace && !radial->isFyPercentage) radial->fy = radial->fy / loader->svgParse->global.h;
+}
+
+
+static void _recalcRadialRAttr(SvgLoaderData* loader, SvgRadialGradient* radial, bool userSpace)
+{
+ // scaling factor based on the Units paragraph from : https://www.w3.org/TR/2015/WD-SVG2-20150915/coords.html
+ if (userSpace && !radial->isRPercentage) radial->r = radial->r / (sqrtf(pow(loader->svgParse->global.h, 2) + pow(loader->svgParse->global.w, 2)) / sqrtf(2.0));
+}
+
+
+typedef void (*radialMethod)(SvgLoaderData* loader, SvgRadialGradient* radial, const char* value);
+typedef void (*radialMethodRecalc)(SvgLoaderData* loader, SvgRadialGradient* radial, bool userSpace);
+
+
+#define RADIAL_DEF(Name, Name1) \
+ { \
+#Name, sizeof(#Name), _handleRadial##Name1##Attr, _recalcRadial##Name1##Attr \
+ }
+
+
+static constexpr struct
+{
+ const char* tag;
+ int sz;
+ radialMethod tagHandler;
+ radialMethodRecalc tagRecalc;
+} radialTags[] = {
+ RADIAL_DEF(cx, Cx),
+ RADIAL_DEF(cy, Cy),
+ RADIAL_DEF(fx, Fx),
+ RADIAL_DEF(fy, Fy),
+ RADIAL_DEF(r, R)
+};
+
+
+static bool _attrParseRadialGradientNode(void* data, const char* key, const char* value)
+{
+ SvgLoaderData* loader = (SvgLoaderData*)data;
+ SvgStyleGradient* grad = loader->svgParse->styleGrad;
+ SvgRadialGradient* radial = grad->radial;
+ int sz = strlen(key);
+
+ for (unsigned int i = 0; i < sizeof(radialTags) / sizeof(radialTags[0]); i++) {
+ if (radialTags[i].sz - 1 == sz && !strncmp(radialTags[i].tag, key, sz)) {
+ radialTags[i].tagHandler(loader, radial, value);
+ return true;
+ }
+ }
+
+ if (!strcmp(key, "id")) {
+ grad->id = _copyId(value);
+ } else if (!strcmp(key, "spreadMethod")) {
+ grad->spread = _parseSpreadValue(value);
+ } else if (!strcmp(key, "href") || !strcmp(key, "xlink:href")) {
+ grad->ref = _idFromHref(value);
+ } else if (!strcmp(key, "gradientUnits") && !strcmp(value, "userSpaceOnUse")) {
+ grad->userSpace = true;
+ } else if (!strcmp(key, "gradientTransform")) {
+ grad->transform = _parseTransformationMatrix(value);
+ } else {
+ return false;
+ }
+
+ return true;
+}
+
+
+static SvgStyleGradient* _createRadialGradient(SvgLoaderData* loader, const char* buf, unsigned bufLength)
+{
+ auto grad = (SvgStyleGradient*)(calloc(1, sizeof(SvgStyleGradient)));
+ loader->svgParse->styleGrad = grad;
+
+ grad->type = SvgGradientType::Radial;
+ grad->userSpace = false;
+ grad->radial = (SvgRadialGradient*)calloc(1, sizeof(SvgRadialGradient));
+ if (!grad->radial) {
+ grad->clear();
+ free(grad);
+ return nullptr;
+ }
+ /**
+ * Default values of gradient transformed into global percentage
+ */
+ grad->radial->cx = 0.5f;
+ grad->radial->cy = 0.5f;
+ grad->radial->fx = 0.5f;
+ grad->radial->fy = 0.5f;
+ grad->radial->r = 0.5f;
+ grad->radial->isCxPercentage = true;
+ grad->radial->isCyPercentage = true;
+ grad->radial->isFxPercentage = true;
+ grad->radial->isFyPercentage = true;
+ grad->radial->isRPercentage = true;
+
+ loader->svgParse->gradient.parsedFx = false;
+ loader->svgParse->gradient.parsedFy = false;
+ simpleXmlParseAttributes(buf, bufLength,
+ _attrParseRadialGradientNode, loader);
+
+ for (unsigned int i = 0; i < sizeof(radialTags) / sizeof(radialTags[0]); i++) {
+ radialTags[i].tagRecalc(loader, grad->radial, grad->userSpace);
+ }
+
+ return loader->svgParse->styleGrad;
+}
+
+
+static bool _attrParseStopsStyle(void* data, const char* key, const char* value)
+{
+ SvgLoaderData* loader = (SvgLoaderData*)data;
+ auto stop = &loader->svgParse->gradStop;
+
+ if (!strcmp(key, "stop-opacity")) {
+ stop->a = _toOpacity(value);
+ loader->svgParse->flags = (SvgStopStyleFlags)((int)loader->svgParse->flags | (int)SvgStopStyleFlags::StopOpacity);
+ } else if (!strcmp(key, "stop-color")) {
+ _toColor(value, &stop->r, &stop->g, &stop->b, nullptr);
+ loader->svgParse->flags = (SvgStopStyleFlags)((int)loader->svgParse->flags | (int)SvgStopStyleFlags::StopColor);
+ } else {
+ return false;
+ }
+
+ return true;
+}
+
+
+static bool _attrParseStops(void* data, const char* key, const char* value)
+{
+ SvgLoaderData* loader = (SvgLoaderData*)data;
+ auto stop = &loader->svgParse->gradStop;
+
+ if (!strcmp(key, "offset")) {
+ stop->offset = _toOffset(value);
+ } else if (!strcmp(key, "stop-opacity")) {
+ if (!((int)loader->svgParse->flags & (int)SvgStopStyleFlags::StopOpacity)) {
+ stop->a = _toOpacity(value);
+ }
+ } else if (!strcmp(key, "stop-color")) {
+ if (!((int)loader->svgParse->flags & (int)SvgStopStyleFlags::StopColor)) {
+ _toColor(value, &stop->r, &stop->g, &stop->b, nullptr);
+ }
+ } else if (!strcmp(key, "style")) {
+ simpleXmlParseW3CAttribute(value, _attrParseStopsStyle, data);
+ } else {
+ return false;
+ }
+
+ return true;
+}
+
+
+static void _handleLinearX1Attr(SvgLoaderData* loader, SvgLinearGradient* linear, const char* value)
+{
+ linear->x1 = _gradientToFloat(loader->svgParse, value, linear->isX1Percentage);
+}
+
+
+static void _handleLinearY1Attr(SvgLoaderData* loader, SvgLinearGradient* linear, const char* value)
+{
+ linear->y1 = _gradientToFloat(loader->svgParse, value, linear->isY1Percentage);
+}
+
+
+static void _handleLinearX2Attr(SvgLoaderData* loader, SvgLinearGradient* linear, const char* value)
+{
+ linear->x2 = _gradientToFloat(loader->svgParse, value, linear->isX2Percentage);
+}
+
+
+static void _handleLinearY2Attr(SvgLoaderData* loader, SvgLinearGradient* linear, const char* value)
+{
+ linear->y2 = _gradientToFloat(loader->svgParse, value, linear->isY2Percentage);
+}
+
+
+static void _recalcLinearX1Attr(SvgLoaderData* loader, SvgLinearGradient* linear, bool userSpace)
+{
+ if (userSpace && !linear->isX1Percentage) linear->x1 = linear->x1 / loader->svgParse->global.w;
+}
+
+
+static void _recalcLinearY1Attr(SvgLoaderData* loader, SvgLinearGradient* linear, bool userSpace)
+{
+ if (userSpace && !linear->isY1Percentage) linear->y1 = linear->y1 / loader->svgParse->global.h;
+}
+
+
+static void _recalcLinearX2Attr(SvgLoaderData* loader, SvgLinearGradient* linear, bool userSpace)
+{
+ if (userSpace && !linear->isX2Percentage) linear->x2 = linear->x2 / loader->svgParse->global.w;
+}
+
+
+static void _recalcLinearY2Attr(SvgLoaderData* loader, SvgLinearGradient* linear, bool userSpace)
+{
+ if (userSpace && !linear->isY2Percentage) linear->y2 = linear->y2 / loader->svgParse->global.h;
+}
+
+
+typedef void (*Linear_Method)(SvgLoaderData* loader, SvgLinearGradient* linear, const char* value);
+typedef void (*Linear_Method_Recalc)(SvgLoaderData* loader, SvgLinearGradient* linear, bool userSpace);
+
+
+#define LINEAR_DEF(Name, Name1) \
+ { \
+#Name, sizeof(#Name), _handleLinear##Name1##Attr, _recalcLinear##Name1##Attr \
+ }
+
+
+static constexpr struct
+{
+ const char* tag;
+ int sz;
+ Linear_Method tagHandler;
+ Linear_Method_Recalc tagRecalc;
+} linear_tags[] = {
+ LINEAR_DEF(x1, X1),
+ LINEAR_DEF(y1, Y1),
+ LINEAR_DEF(x2, X2),
+ LINEAR_DEF(y2, Y2)
+};
+
+
+static bool _attrParseLinearGradientNode(void* data, const char* key, const char* value)
+{
+ SvgLoaderData* loader = (SvgLoaderData*)data;
+ SvgStyleGradient* grad = loader->svgParse->styleGrad;
+ SvgLinearGradient* linear = grad->linear;
+ int sz = strlen(key);
+
+ for (unsigned int i = 0; i < sizeof(linear_tags) / sizeof(linear_tags[0]); i++) {
+ if (linear_tags[i].sz - 1 == sz && !strncmp(linear_tags[i].tag, key, sz)) {
+ linear_tags[i].tagHandler(loader, linear, value);
+ return true;
+ }
+ }
+
+ if (!strcmp(key, "id")) {
+ grad->id = _copyId(value);
+ } else if (!strcmp(key, "spreadMethod")) {
+ grad->spread = _parseSpreadValue(value);
+ } else if (!strcmp(key, "href") || !strcmp(key, "xlink:href")) {
+ grad->ref = _idFromHref(value);
+ } else if (!strcmp(key, "gradientUnits") && !strcmp(value, "userSpaceOnUse")) {
+ grad->userSpace = true;
+ } else if (!strcmp(key, "gradientTransform")) {
+ grad->transform = _parseTransformationMatrix(value);
+ } else {
+ return false;
+ }
+
+ return true;
+}
+
+
+static SvgStyleGradient* _createLinearGradient(SvgLoaderData* loader, const char* buf, unsigned bufLength)
+{
+ auto grad = (SvgStyleGradient*)(calloc(1, sizeof(SvgStyleGradient)));
+ loader->svgParse->styleGrad = grad;
+
+ grad->type = SvgGradientType::Linear;
+ grad->userSpace = false;
+ grad->linear = (SvgLinearGradient*)calloc(1, sizeof(SvgLinearGradient));
+ if (!grad->linear) {
+ grad->clear();
+ free(grad);
+ return nullptr;
+ }
+ /**
+ * Default value of x2 is 100% - transformed to the global percentage
+ */
+ grad->linear->x2 = 1.0f;
+ grad->linear->isX2Percentage = true;
+
+ simpleXmlParseAttributes(buf, bufLength, _attrParseLinearGradientNode, loader);
+
+ for (unsigned int i = 0; i < sizeof(linear_tags) / sizeof(linear_tags[0]); i++) {
+ linear_tags[i].tagRecalc(loader, grad->linear, grad->userSpace);
+ }
+
+ return loader->svgParse->styleGrad;
+}
+
+
+#define GRADIENT_DEF(Name, Name1) \
+ { \
+#Name, sizeof(#Name), _create##Name1 \
+ }
+
+
+/**
+ * In the case when the gradients lengths are given as numbers (not percentages)
+ * in the current user coordinate system, they are recalculated into percentages
+ * related to the canvas width and height.
+ */
+static constexpr struct
+{
+ const char* tag;
+ int sz;
+ GradientFactoryMethod tagHandler;
+} gradientTags[] = {
+ GRADIENT_DEF(linearGradient, LinearGradient),
+ GRADIENT_DEF(radialGradient, RadialGradient)
+};
+
+
+static GradientFactoryMethod _findGradientFactory(const char* name)
+{
+ int sz = strlen(name);
+
+ for (unsigned int i = 0; i < sizeof(gradientTags) / sizeof(gradientTags[0]); i++) {
+ if (gradientTags[i].sz - 1 == sz && !strncmp(gradientTags[i].tag, name, sz)) {
+ return gradientTags[i].tagHandler;
+ }
+ }
+ return nullptr;
+}
+
+
+static constexpr struct
+{
+ const char* tag;
+ size_t sz;
+} popArray[] = {
+ {"g", sizeof("g")},
+ {"svg", sizeof("svg")},
+ {"defs", sizeof("defs")},
+ {"mask", sizeof("mask")},
+ {"clipPath", sizeof("clipPath")}
+};
+
+
+static void _svgLoaderParerXmlClose(SvgLoaderData* loader, const char* content)
+{
+ content = _skipSpace(content, nullptr);
+
+ for (unsigned int i = 0; i < sizeof(popArray) / sizeof(popArray[0]); i++) {
+ if (!strncmp(content, popArray[i].tag, popArray[i].sz - 1)) {
+ loader->stack.pop();
+ break;
+ }
+ }
+
+ loader->level--;
+}
+
+
+static void _svgLoaderParserXmlOpen(SvgLoaderData* loader, const char* content, unsigned int length, bool empty)
+{
+ const char* attrs = nullptr;
+ int attrsLength = 0;
+ int sz = length;
+ char tagName[20] = "";
+ FactoryMethod method;
+ GradientFactoryMethod gradientMethod;
+ SvgNode *node = nullptr, *parent = nullptr;
+ loader->level++;
+ attrs = simpleXmlFindAttributesTag(content, length);
+
+ if (!attrs) {
+ //Parse the empty tag
+ attrs = content;
+ while ((attrs != nullptr) && *attrs != '>') attrs++;
+ }
+
+ if (attrs) {
+ //Find out the tag name starting from content till sz length
+ sz = attrs - content;
+ while ((sz > 0) && (isspace(content[sz - 1]))) sz--;
+ if ((unsigned)sz >= sizeof(tagName)) return;
+ strncpy(tagName, content, sz);
+ tagName[sz] = '\0';
+ attrsLength = length - sz;
+ }
+
+ if ((method = _findGroupFactory(tagName))) {
+ //Group
+ if (!loader->doc) {
+ if (strcmp(tagName, "svg")) return; //Not a valid svg document
+ node = method(loader, nullptr, attrs, attrsLength);
+ loader->doc = node;
+ } else {
+ if (!strcmp(tagName, "svg")) return; //Already loaded <svg>(SvgNodeType::Doc) tag
+ if (loader->stack.count > 0) parent = loader->stack.data[loader->stack.count - 1];
+ else parent = loader->doc;
+ node = method(loader, parent, attrs, attrsLength);
+ }
+
+ if (!node) return;
+ if (node->type != SvgNodeType::Defs || !empty) {
+ loader->stack.push(node);
+ }
+ } else if ((method = _findGraphicsFactory(tagName))) {
+ if (loader->stack.count > 0) parent = loader->stack.data[loader->stack.count - 1];
+ else parent = loader->doc;
+ node = method(loader, parent, attrs, attrsLength);
+ } else if ((gradientMethod = _findGradientFactory(tagName))) {
+ SvgStyleGradient* gradient;
+ gradient = gradientMethod(loader, attrs, attrsLength);
+ //FIXME: The current parsing structure does not distinguish end tags.
+ // There is no way to know if the currently parsed gradient is in defs.
+ // If a gradient is declared outside of defs after defs is set, it is included in the gradients of defs.
+ // But finally, the loader has a gradient style list regardless of defs.
+ // This is only to support this when multiple gradients are declared, even if no defs are declared.
+ // refer to: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/defs
+ if (loader->def && loader->doc->node.doc.defs) {
+ loader->def->node.defs.gradients.push(gradient);
+ } else {
+ loader->gradients.push(gradient);
+ }
+ loader->latestGradient = gradient;
+ } else if (!strcmp(tagName, "stop")) {
+ if (!loader->latestGradient) {
+ TVGLOG("SVG", "Stop element is used outside of the Gradient element");
+ return;
+ }
+ /* default value for opacity */
+ loader->svgParse->gradStop = {0.0f, 0, 0, 0, 255};
+ simpleXmlParseAttributes(attrs, attrsLength, _attrParseStops, loader);
+ loader->latestGradient->stops.push(loader->svgParse->gradStop);
+ } else if (!isIgnoreUnsupportedLogElements(tagName)) {
+ TVGLOG("SVG", "Unsupported elements used [Elements: %s]", tagName);
+ }
+}
+
+
+static bool _svgLoaderParser(void* data, SimpleXMLType type, const char* content, unsigned int length)
+{
+ SvgLoaderData* loader = (SvgLoaderData*)data;
+
+ switch (type) {
+ case SimpleXMLType::Open: {
+ _svgLoaderParserXmlOpen(loader, content, length, false);
+ break;
+ }
+ case SimpleXMLType::OpenEmpty: {
+ _svgLoaderParserXmlOpen(loader, content, length, true);
+ break;
+ }
+ case SimpleXMLType::Close: {
+ _svgLoaderParerXmlClose(loader, content);
+ break;
+ }
+ case SimpleXMLType::Data:
+ case SimpleXMLType::CData:
+ case SimpleXMLType::DoctypeChild: {
+ break;
+ }
+ case SimpleXMLType::Ignored:
+ case SimpleXMLType::Comment:
+ case SimpleXMLType::Doctype: {
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+
+ return true;
+}
+
+
+static void _styleInherit(SvgStyleProperty* child, const SvgStyleProperty* parent)
+{
+ if (parent == nullptr) return;
+ //Inherit the property of parent if not present in child.
+ //Fill
+ if (!((int)child->fill.flags & (int)SvgFillFlags::Paint)) {
+ child->fill.paint.color = parent->fill.paint.color;
+ child->fill.paint.none = parent->fill.paint.none;
+ child->fill.paint.curColor = parent->fill.paint.curColor;
+ if (parent->fill.paint.url) child->fill.paint.url = _copyId(parent->fill.paint.url);
+ } else if (child->fill.paint.curColor && !child->curColorSet) {
+ child->color = parent->color;
+ }
+ if (!((int)child->fill.flags & (int)SvgFillFlags::Opacity)) {
+ child->fill.opacity = parent->fill.opacity;
+ }
+ if (!((int)child->fill.flags & (int)SvgFillFlags::FillRule)) {
+ child->fill.fillRule = parent->fill.fillRule;
+ }
+ //Stroke
+ if (!((int)child->stroke.flags & (int)SvgStrokeFlags::Paint)) {
+ child->stroke.paint.color = parent->stroke.paint.color;
+ child->stroke.paint.none = parent->stroke.paint.none;
+ child->stroke.paint.curColor = parent->stroke.paint.curColor;
+ child->stroke.paint.url = parent->stroke.paint.url ? _copyId(parent->stroke.paint.url) : nullptr;
+ } else if (child->stroke.paint.curColor && !child->curColorSet) {
+ child->color = parent->color;
+ }
+ if (!((int)child->stroke.flags & (int)SvgStrokeFlags::Opacity)) {
+ child->stroke.opacity = parent->stroke.opacity;
+ }
+ if (!((int)child->stroke.flags & (int)SvgStrokeFlags::Width)) {
+ child->stroke.width = parent->stroke.width;
+ }
+ if (!((int)child->stroke.flags & (int)SvgStrokeFlags::Dash)) {
+ if (parent->stroke.dash.array.count > 0) {
+ child->stroke.dash.array.clear();
+ child->stroke.dash.array.reserve(parent->stroke.dash.array.count);
+ for (uint32_t i = 0; i < parent->stroke.dash.array.count; ++i) {
+ child->stroke.dash.array.push(parent->stroke.dash.array.data[i]);
+ }
+ }
+ }
+ if (!((int)child->stroke.flags & (int)SvgStrokeFlags::Cap)) {
+ child->stroke.cap = parent->stroke.cap;
+ }
+ if (!((int)child->stroke.flags & (int)SvgStrokeFlags::Join)) {
+ child->stroke.join = parent->stroke.join;
+ }
+}
+
+
+static void _inefficientNodeCheck(TVG_UNUSED SvgNode* node){
+#ifdef THORVG_LOG_ENABLED
+ auto type = simpleXmlNodeTypeToString(node->type);
+
+ if (!node->display && node->type != SvgNodeType::ClipPath) TVGLOG("SVG", "Inefficient elements used [Display is none][Node Type : %s]", type);
+ if (node->style->opacity == 0) TVGLOG("SVG", "Inefficient elements used [Opacity is zero][Node Type : %s]", type);
+ 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);
+
+ switch (node->type) {
+ case SvgNodeType::Path: {
+ if (!node->node.path.path) TVGLOG("SVG", "Inefficient elements used [Empty path][Node Type : %s]", type);
+ break;
+ }
+ case SvgNodeType::Ellipse: {
+ if (node->node.ellipse.rx == 0 && node->node.ellipse.ry == 0) TVGLOG("SVG", "Inefficient elements used [Size is zero][Node Type : %s]", type);
+ break;
+ }
+ case SvgNodeType::Polygon:
+ case SvgNodeType::Polyline: {
+ if (node->node.polygon.pointsCount < 2) TVGLOG("SVG", "Inefficient elements used [Invalid Polygon][Node Type : %s]", type);
+ break;
+ }
+ case SvgNodeType::Circle: {
+ if (node->node.circle.r == 0) TVGLOG("SVG", "Inefficient elements used [Size is zero][Node Type : %s]", type);
+ break;
+ }
+ case SvgNodeType::Rect: {
+ if (node->node.rect.w == 0 && node->node.rect.h) TVGLOG("SVG", "Inefficient elements used [Size is zero][Node Type : %s]", type);
+ break;
+ }
+ case SvgNodeType::Line: {
+ 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);
+ break;
+ }
+ default: break;
+ }
+#endif
+}
+
+
+static void _updateStyle(SvgNode* node, SvgStyleProperty* parentStyle)
+{
+ _styleInherit(node->style, parentStyle);
+ _inefficientNodeCheck(node);
+
+ auto child = node->child.data;
+ for (uint32_t i = 0; i < node->child.count; ++i, ++child) {
+ _updateStyle(*child, node->style);
+ }
+}
+
+
+static SvgStyleGradient* _gradientDup(Array<SvgStyleGradient*>* gradients, const char* id)
+{
+ SvgStyleGradient* result = nullptr;
+
+ auto gradList = gradients->data;
+
+ for (uint32_t i = 0; i < gradients->count; ++i) {
+ if (!strcmp((*gradList)->id, id)) {
+ result = _cloneGradient(*gradList);
+ break;
+ }
+ ++gradList;
+ }
+
+ if (result && result->ref) {
+ gradList = gradients->data;
+ for (uint32_t i = 0; i < gradients->count; ++i) {
+ if (!strcmp((*gradList)->id, result->ref)) {
+ if (result->stops.count == 0) _cloneGradStops(result->stops, (*gradList)->stops);
+ //TODO: Properly inherit other property
+ break;
+ }
+ ++gradList;
+ }
+ }
+
+ return result;
+}
+
+
+static void _updateGradient(SvgNode* node, Array<SvgStyleGradient*>* gradients)
+{
+ if (node->child.count > 0) {
+ auto child = node->child.data;
+ for (uint32_t i = 0; i < node->child.count; ++i, ++child) {
+ _updateGradient(*child, gradients);
+ }
+ } else {
+ if (node->style->fill.paint.url) {
+ if (node->style->fill.paint.gradient) {
+ node->style->fill.paint.gradient->clear();
+ free(node->style->fill.paint.gradient);
+ }
+ node->style->fill.paint.gradient = _gradientDup(gradients, node->style->fill.paint.url);
+ }
+ if (node->style->stroke.paint.url) {
+ if (node->style->stroke.paint.gradient) {
+ node->style->stroke.paint.gradient->clear();
+ free(node->style->stroke.paint.gradient);
+ }
+ node->style->stroke.paint.gradient = _gradientDup(gradients, node->style->stroke.paint.url);
+ }
+ }
+}
+
+
+static void _updateComposite(SvgNode* node, SvgNode* root)
+{
+ if (node->style->clipPath.url && !node->style->clipPath.node) {
+ SvgNode* findResult = _findNodeById(root, node->style->clipPath.url);
+ if (findResult) node->style->clipPath.node = findResult;
+ }
+ if (node->style->mask.url && !node->style->mask.node) {
+ SvgNode* findResult = _findNodeById(root, node->style->mask.url);
+ if (findResult) node->style->mask.node = findResult;
+ }
+ if (node->child.count > 0) {
+ auto child = node->child.data;
+ for (uint32_t i = 0; i < node->child.count; ++i, ++child) {
+ _updateComposite(*child, root);
+ }
+ }
+}
+
+
+static void _freeNodeStyle(SvgStyleProperty* style)
+{
+ if (!style) return;
+
+ //style->clipPath.node and style->mask.node has only the addresses of node. Therefore, node is released from _freeNode.
+ free(style->clipPath.url);
+ free(style->mask.url);
+
+ if (style->fill.paint.gradient) {
+ style->fill.paint.gradient->clear();
+ free(style->fill.paint.gradient);
+ }
+ if (style->stroke.paint.gradient) {
+ style->stroke.paint.gradient->clear();
+ free(style->stroke.paint.gradient);
+ }
+ free(style->fill.paint.url);
+ free(style->stroke.paint.url);
+ style->stroke.dash.array.reset();
+ free(style);
+}
+
+
+static void _freeNode(SvgNode* node)
+{
+ if (!node) return;
+
+ auto child = node->child.data;
+ for (uint32_t i = 0; i < node->child.count; ++i, ++child) {
+ _freeNode(*child);
+ }
+ node->child.reset();
+
+ free(node->id);
+ free(node->transform);
+ _freeNodeStyle(node->style);
+ switch (node->type) {
+ case SvgNodeType::Path: {
+ free(node->node.path.path);
+ break;
+ }
+ case SvgNodeType::Polygon: {
+ free(node->node.polygon.points);
+ break;
+ }
+ case SvgNodeType::Polyline: {
+ free(node->node.polyline.points);
+ break;
+ }
+ case SvgNodeType::Doc: {
+ _freeNode(node->node.doc.defs);
+ break;
+ }
+ case SvgNodeType::Defs: {
+ auto gradients = node->node.defs.gradients.data;
+ for (size_t i = 0; i < node->node.defs.gradients.count; ++i) {
+ (*gradients)->clear();
+ free(*gradients);
+ ++gradients;
+ }
+ node->node.defs.gradients.reset();
+ break;
+ }
+ case SvgNodeType::Image: {
+ free(node->node.image.href);
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+ free(node);
+}
+
+
+static bool _svgLoaderParserForValidCheckXmlOpen(SvgLoaderData* loader, const char* content, unsigned int length)
+{
+ const char* attrs = nullptr;
+ int sz = length;
+ char tagName[20] = "";
+ FactoryMethod method;
+ SvgNode *node = nullptr;
+ int attrsLength = 0;
+ loader->level++;
+ attrs = simpleXmlFindAttributesTag(content, length);
+
+ if (!attrs) {
+ //Parse the empty tag
+ attrs = content;
+ while ((attrs != nullptr) && *attrs != '>') attrs++;
+ }
+
+ if (attrs) {
+ sz = attrs - content;
+ while ((sz > 0) && (isspace(content[sz - 1]))) sz--;
+ if ((unsigned)sz >= sizeof(tagName)) return false;
+ strncpy(tagName, content, sz);
+ tagName[sz] = '\0';
+ attrsLength = length - sz;
+ }
+
+ if ((method = _findGroupFactory(tagName))) {
+ if (!loader->doc) {
+ if (strcmp(tagName, "svg")) return true; //Not a valid svg document
+ node = method(loader, nullptr, attrs, attrsLength);
+ loader->doc = node;
+ loader->stack.push(node);
+ return false;
+ }
+ }
+ return true;
+}
+
+
+static bool _svgLoaderParserForValidCheck(void* data, SimpleXMLType type, const char* content, unsigned int length)
+{
+ SvgLoaderData* loader = (SvgLoaderData*)data;
+ bool res = true;;
+
+ switch (type) {
+ case SimpleXMLType::Open:
+ case SimpleXMLType::OpenEmpty: {
+ //If 'res' is false, it means <svg> tag is found.
+ res = _svgLoaderParserForValidCheckXmlOpen(loader, content, length);
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+
+ return res;
+}
+
+
+void SvgLoader::clear()
+{
+ if (copy) free((char*)content);
+ size = 0;
+ content = nullptr;
+ copy = false;
+}
+
+
+/************************************************************************/
+/* External Class Implementation */
+/************************************************************************/
+
+SvgLoader::SvgLoader()
+{
+}
+
+
+SvgLoader::~SvgLoader()
+{
+ close();
+}
+
+
+void SvgLoader::run(unsigned tid)
+{
+ if (!simpleXmlParse(content, size, true, _svgLoaderParser, &(loaderData))) return;
+
+ if (loaderData.doc) {
+ _updateStyle(loaderData.doc, nullptr);
+ auto defs = loaderData.doc->node.doc.defs;
+ if (defs) _updateGradient(loaderData.doc, &defs->node.defs.gradients);
+
+ if (loaderData.gradients.count > 0) _updateGradient(loaderData.doc, &loaderData.gradients);
+
+ _updateComposite(loaderData.doc, loaderData.doc);
+ if (defs) _updateComposite(loaderData.doc, defs);
+
+ if (loaderData.cloneNodes.count > 0) _clonePostponedNodes(&loaderData.cloneNodes);
+ }
+ root = svgSceneBuild(loaderData.doc, vx, vy, vw, vh, w, h, preserveAspect, svgPath);
+}
+
+
+bool SvgLoader::header()
+{
+ //For valid check, only <svg> tag is parsed first.
+ //If the <svg> tag is found, the loaded file is valid and stores viewbox information.
+ //After that, the remaining content data is parsed in order with async.
+ loaderData.svgParse = (SvgParser*)malloc(sizeof(SvgParser));
+ if (!loaderData.svgParse) return false;
+
+ loaderData.svgParse->flags = SvgStopStyleFlags::StopDefault;
+
+ simpleXmlParse(content, size, true, _svgLoaderParserForValidCheck, &(loaderData));
+
+ if (loaderData.doc && loaderData.doc->type == SvgNodeType::Doc) {
+ //Return the brief resource info such as viewbox:
+ vx = loaderData.doc->node.doc.vx;
+ vy = loaderData.doc->node.doc.vy;
+ w = vw = loaderData.doc->node.doc.vw;
+ h = vh = loaderData.doc->node.doc.vh;
+
+ //Override size
+ if (loaderData.doc->node.doc.w > 0) {
+ w = loaderData.doc->node.doc.w;
+ if (vw < FLT_EPSILON) vw = w;
+ }
+ if (loaderData.doc->node.doc.h > 0) {
+ h = loaderData.doc->node.doc.h;
+ if (vh < FLT_EPSILON) vh = h;
+ }
+
+ preserveAspect = loaderData.doc->node.doc.preserveAspect;
+ } else {
+ TVGLOG("SVG", "No SVG File. There is no <svg/>");
+ return false;
+ }
+
+ return true;
+}
+
+
+bool SvgLoader::open(const char* data, uint32_t size, bool copy)
+{
+ clear();
+
+ if (copy) {
+ content = (char*)malloc(size);
+ if (!content) return false;
+ memcpy((char*)content, data, size);
+ } else content = data;
+
+ this->size = size;
+ this->copy = copy;
+
+ return header();
+}
+
+
+bool SvgLoader::open(const string& path)
+{
+ clear();
+
+ ifstream f;
+ f.open(path);
+
+ if (!f.is_open()) return false;
+
+ svgPath = path;
+ getline(f, filePath, '\0');
+ f.close();
+
+ if (filePath.empty()) return false;
+
+ content = filePath.c_str();
+ size = filePath.size();
+
+ return header();
+}
+
+
+bool SvgLoader::resize(Paint* paint, float w, float h)
+{
+ if (!paint) return false;
+
+ auto sx = w / this->w;
+ auto sy = h / this->h;
+
+ if (preserveAspect) {
+ //Scale
+ auto scale = sx < sy ? sx : sy;
+ paint->scale(scale);
+ //Align
+ auto tx = 0.0f;
+ auto ty = 0.0f;
+ auto tw = this->w * scale;
+ auto th = this->h * scale;
+ if (tw > th) ty -= (h - th) * 0.5f;
+ else tx -= (w - tw) * 0.5f;
+ paint->translate(-tx, -ty);
+ } else {
+ //Align
+ auto tx = 0.0f;
+ auto ty = 0.0f;
+ auto tw = this->w * sx;
+ auto th = this->h * sy;
+ if (tw > th) ty -= (h - th) * 0.5f;
+ else tx -= (w - tw) * 0.5f;
+
+ Matrix m = {sx, 0, -tx, 0, sy, -ty, 0, 0, 1};
+ paint->transform(m);
+ }
+ return true;
+}
+
+
+bool SvgLoader::read()
+{
+ if (!content || size == 0) return false;
+
+ TaskScheduler::request(this);
+
+ return true;
+}
+
+
+bool SvgLoader::close()
+{
+ this->done();
+
+ if (loaderData.svgParse) {
+ free(loaderData.svgParse);
+ loaderData.svgParse = nullptr;
+ }
+ auto gradients = loaderData.gradients.data;
+ for (size_t i = 0; i < loaderData.gradients.count; ++i) {
+ (*gradients)->clear();
+ free(*gradients);
+ ++gradients;
+ }
+ loaderData.gradients.reset();
+
+ _freeNode(loaderData.doc);
+ loaderData.doc = nullptr;
+ loaderData.stack.reset();
+
+ clear();
+
+ return true;
+}
+
+
+unique_ptr<Paint> SvgLoader::paint()
+{
+ this->done();
+ if (root) return move(root);
+ else return nullptr;
+}
diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.h b/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.h
new file mode 100644
index 0000000000..468f05801d
--- /dev/null
+++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#ifndef _TVG_SVG_LOADER_H_
+#define _TVG_SVG_LOADER_H_
+
+#include "tvgTaskScheduler.h"
+#include "tvgSvgLoaderCommon.h"
+
+class SvgLoader : public LoadModule, public Task
+{
+public:
+ string filePath;
+ string svgPath = "";
+ const char* content = nullptr;
+ uint32_t size = 0;
+
+ SvgLoaderData loaderData;
+ unique_ptr<Scene> root;
+
+ bool copy = false;
+
+ SvgLoader();
+ ~SvgLoader();
+
+ using LoadModule::open;
+ bool open(const string& path) override;
+ bool open(const char* data, uint32_t size, bool copy) override;
+ bool resize(Paint* paint, float w, float h) override;
+ bool read() override;
+ bool close() override;
+ unique_ptr<Paint> paint() override;
+
+private:
+ bool header();
+ void clear();
+ void run(unsigned tid) override;
+};
+
+
+#endif //_TVG_SVG_LOADER_H_
diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgLoaderCommon.h b/thirdparty/thorvg/src/loaders/svg/tvgSvgLoaderCommon.h
new file mode 100644
index 0000000000..cceef915f0
--- /dev/null
+++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgLoaderCommon.h
@@ -0,0 +1,412 @@
+/*
+ * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#ifndef _TVG_SVG_LOADER_COMMON_H_
+#define _TVG_SVG_LOADER_COMMON_H_
+
+#include "tvgCommon.h"
+#include "tvgArray.h"
+
+struct SvgNode;
+struct SvgStyleGradient;
+
+//NOTE: Please update simpleXmlNodeTypeToString() as well.
+enum class SvgNodeType
+{
+ Doc,
+ G,
+ Defs,
+ Animation,
+ Arc,
+ Circle,
+ Ellipse,
+ Image,
+ Line,
+ Path,
+ Polygon,
+ Polyline,
+ Rect,
+ Text,
+ TextArea,
+ Tspan,
+ Use,
+ Video,
+ ClipPath,
+ Mask,
+ Unknown
+};
+
+/*
+// TODO - remove?
+enum class SvgLengthType
+{
+ Percent,
+ Px,
+ Pc,
+ Pt,
+ Mm,
+ Cm,
+ In,
+};
+*/
+
+enum class SvgFillFlags
+{
+ Paint = 0x01,
+ Opacity = 0x02,
+ Gradient = 0x04,
+ FillRule = 0x08,
+ ClipPath = 0x16
+};
+
+enum class SvgStrokeFlags
+{
+ Paint = 0x1,
+ Opacity = 0x2,
+ Gradient = 0x4,
+ Scale = 0x8,
+ Width = 0x10,
+ Cap = 0x20,
+ Join = 0x40,
+ Dash = 0x80,
+};
+
+enum class SvgGradientType
+{
+ Linear,
+ Radial
+};
+
+enum class SvgStyleFlags
+{
+ Color = 0x01,
+ Fill = 0x02,
+ FillRule = 0x04,
+ FillOpacity = 0x08,
+ Opacity = 0x010,
+ Stroke = 0x20,
+ StrokeWidth = 0x40,
+ StrokeLineJoin = 0x80,
+ StrokeLineCap = 0x100,
+ StrokeOpacity = 0x200,
+ StrokeDashArray = 0x400,
+ Transform = 0x800,
+ ClipPath = 0x1000,
+ Mask = 0x2000,
+ Display = 0x4000
+};
+
+enum class SvgStopStyleFlags
+{
+ StopDefault = 0x0,
+ StopOpacity = 0x01,
+ StopColor = 0x02
+};
+
+enum class SvgFillRule
+{
+ Winding = 0,
+ OddEven = 1
+};
+
+//Length type to recalculate %, pt, pc, mm, cm etc
+enum class SvgParserLengthType
+{
+ Vertical,
+ Horizontal,
+ //In case of, for example, radius of radial gradient
+ Other
+};
+
+struct SvgDocNode
+{
+ float w;
+ float h;
+ float vx;
+ float vy;
+ float vw;
+ float vh;
+ SvgNode* defs;
+ bool preserveAspect;
+};
+
+struct SvgGNode
+{
+};
+
+struct SvgDefsNode
+{
+ Array<SvgStyleGradient*> gradients;
+};
+
+struct SvgUseNode
+{
+ float x, y, w, h;
+};
+
+
+struct SvgEllipseNode
+{
+ float cx;
+ float cy;
+ float rx;
+ float ry;
+};
+
+struct SvgCircleNode
+{
+ float cx;
+ float cy;
+ float r;
+};
+
+struct SvgRectNode
+{
+ float x;
+ float y;
+ float w;
+ float h;
+ float rx;
+ float ry;
+ bool hasRx;
+ bool hasRy;
+};
+
+struct SvgLineNode
+{
+ float x1;
+ float y1;
+ float x2;
+ float y2;
+};
+
+struct SvgImageNode
+{
+ float x, y, w, h;
+ char* href;
+};
+
+struct SvgPathNode
+{
+ char* path;
+};
+
+struct SvgPolygonNode
+{
+ int pointsCount;
+ float* points;
+};
+
+struct SvgCompositeNode
+{
+ bool userSpace;
+};
+
+struct SvgLinearGradient
+{
+ float x1;
+ float y1;
+ float x2;
+ float y2;
+ bool isX1Percentage;
+ bool isY1Percentage;
+ bool isX2Percentage;
+ bool isY2Percentage;
+};
+
+struct SvgRadialGradient
+{
+ float cx;
+ float cy;
+ float fx;
+ float fy;
+ float r;
+ bool isCxPercentage;
+ bool isCyPercentage;
+ bool isFxPercentage;
+ bool isFyPercentage;
+ bool isRPercentage;
+};
+
+struct SvgComposite
+{
+ char *url;
+ SvgNode* node;
+ bool applying; //flag for checking circular dependency.
+};
+
+struct SvgColor
+{
+ uint8_t r;
+ uint8_t g;
+ uint8_t b;
+};
+
+struct SvgPaint
+{
+ SvgStyleGradient* gradient;
+ char *url;
+ SvgColor color;
+ bool none;
+ bool curColor;
+};
+
+struct SvgDash
+{
+ Array<float> array;
+};
+
+struct SvgStyleGradient
+{
+ SvgGradientType type;
+ char* id;
+ char* ref;
+ FillSpread spread;
+ SvgRadialGradient* radial;
+ SvgLinearGradient* linear;
+ Matrix* transform;
+ Array<Fill::ColorStop> stops;
+ bool userSpace;
+
+ void clear()
+ {
+ stops.reset();
+ free(transform);
+ free(radial);
+ free(linear);
+ free(ref);
+ free(id);
+ }
+};
+
+struct SvgStyleFill
+{
+ SvgFillFlags flags;
+ SvgPaint paint;
+ int opacity;
+ FillRule fillRule;
+};
+
+struct SvgStyleStroke
+{
+ SvgStrokeFlags flags;
+ SvgPaint paint;
+ int opacity;
+ float scale;
+ float width;
+ float centered;
+ StrokeCap cap;
+ StrokeJoin join;
+ SvgDash dash;
+ int dashCount;
+};
+
+struct SvgStyleProperty
+{
+ SvgStyleFill fill;
+ SvgStyleStroke stroke;
+ SvgComposite clipPath;
+ SvgComposite mask;
+ int opacity;
+ SvgColor color;
+ bool curColorSet;
+ SvgStyleFlags flags;
+};
+
+struct SvgNode
+{
+ SvgNodeType type;
+ SvgNode* parent;
+ Array<SvgNode*> child;
+ char *id;
+ SvgStyleProperty *style;
+ Matrix* transform;
+ union {
+ SvgGNode g;
+ SvgDocNode doc;
+ SvgDefsNode defs;
+ SvgUseNode use;
+ SvgCircleNode circle;
+ SvgEllipseNode ellipse;
+ SvgPolygonNode polygon;
+ SvgPolygonNode polyline;
+ SvgRectNode rect;
+ SvgPathNode path;
+ SvgLineNode line;
+ SvgImageNode image;
+ SvgCompositeNode comp;
+ } node;
+ bool display;
+ ~SvgNode();
+};
+
+struct SvgParser
+{
+ SvgNode* node;
+ SvgStyleGradient* styleGrad;
+ Fill::ColorStop gradStop;
+ SvgStopStyleFlags flags;
+ struct
+ {
+ int x, y;
+ uint32_t w, h;
+ } global;
+ struct
+ {
+ bool parsedFx;
+ bool parsedFy;
+ } gradient;
+};
+
+struct SvgNodeIdPair
+{
+ SvgNode* node;
+ char *id;
+};
+
+struct SvgLoaderData
+{
+ Array<SvgNode *> stack = {nullptr, 0, 0};
+ SvgNode* doc = nullptr;
+ SvgNode* def = nullptr;
+ Array<SvgStyleGradient*> gradients;
+ SvgStyleGradient* latestGradient = nullptr; //For stops
+ SvgParser* svgParse = nullptr;
+ Array<SvgNodeIdPair> cloneNodes;
+ int level = 0;
+ bool result = false;
+};
+
+/*
+ * https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/strtof-strtod-l-wcstod-wcstod-l?view=vs-2017
+ *
+ * src should be one of the following form :
+ *
+ * [whitespace] [sign] {digits [radix digits] | radix digits} [{e | E} [sign] digits]
+ * [whitespace] [sign] {INF | INFINITY}
+ * [whitespace] [sign] NAN [sequence]
+ *
+ * No hexadecimal form supported
+ * no sequence supported after NAN
+ */
+float customStrtof(const char *nptr, char **endptr);
+
+#endif
diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgPath.cpp b/thirdparty/thorvg/src/loaders/svg/tvgSvgPath.cpp
new file mode 100644
index 0000000000..2b62315de8
--- /dev/null
+++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgPath.cpp
@@ -0,0 +1,563 @@
+/*
+ * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/*
+ * Copyright notice for the EFL:
+
+ * Copyright (C) EFL developers (see AUTHORS)
+
+ * All rights reserved.
+
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
+ * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#define _USE_MATH_DEFINES //Math Constants are not defined in Standard C/C++.
+
+#include <math.h>
+#include <clocale>
+#include <ctype.h>
+#include "tvgSvgLoaderCommon.h"
+#include "tvgSvgPath.h"
+#include "tvgSvgUtil.h"
+
+/************************************************************************/
+/* Internal Class Implementation */
+/************************************************************************/
+
+static char* _skipComma(const char* content)
+{
+ while (*content && isspace(*content)) {
+ content++;
+ }
+ if (*content == ',') return (char*)content + 1;
+ return (char*)content;
+}
+
+
+static bool _parseNumber(char** content, float* number)
+{
+ char* end = NULL;
+ *number = svgUtilStrtof(*content, &end);
+ //If the start of string is not number
+ if ((*content) == end) return false;
+ //Skip comma if any
+ *content = _skipComma(end);
+ return true;
+}
+
+
+static bool _parseFlag(char** content, int* number)
+{
+ char* end = NULL;
+ if (*(*content) != '0' && *(*content) != '1') return false;
+ *number = *(*content) - '0';
+ *content += 1;
+ end = *content;
+ *content = _skipComma(end);
+
+ return true;
+}
+
+
+void _pathAppendArcTo(Array<PathCommand>* cmds, Array<Point>* pts, Point* cur, Point* curCtl, float x, float y, float rx, float ry, float angle, bool largeArc, bool sweep)
+{
+ float cxp, cyp, cx, cy;
+ float sx, sy;
+ float cosPhi, sinPhi;
+ float dx2, dy2;
+ float x1p, y1p;
+ float x1p2, y1p2;
+ float rx2, ry2;
+ float lambda;
+ float c;
+ float at;
+ float theta1, deltaTheta;
+ float nat;
+ float delta, bcp;
+ float cosPhiRx, cosPhiRy;
+ float sinPhiRx, sinPhiRy;
+ float cosTheta1, sinTheta1;
+ int segments;
+
+ //Some helpful stuff is available here:
+ //http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes
+ sx = cur->x;
+ sy = cur->y;
+
+ //If start and end points are identical, then no arc is drawn
+ if ((fabsf(x - sx) < (1.0f / 256.0f)) && (fabsf(y - sy) < (1.0f / 256.0f))) return;
+
+ //Correction of out-of-range radii, see F6.6.1 (step 2)
+ rx = fabsf(rx);
+ ry = fabsf(ry);
+
+ angle = angle * M_PI / 180.0f;
+ cosPhi = cosf(angle);
+ sinPhi = sinf(angle);
+ dx2 = (sx - x) / 2.0f;
+ dy2 = (sy - y) / 2.0f;
+ x1p = cosPhi * dx2 + sinPhi * dy2;
+ y1p = cosPhi * dy2 - sinPhi * dx2;
+ x1p2 = x1p * x1p;
+ y1p2 = y1p * y1p;
+ rx2 = rx * rx;
+ ry2 = ry * ry;
+ lambda = (x1p2 / rx2) + (y1p2 / ry2);
+
+ //Correction of out-of-range radii, see F6.6.2 (step 4)
+ if (lambda > 1.0f) {
+ //See F6.6.3
+ float lambdaRoot = sqrtf(lambda);
+
+ rx *= lambdaRoot;
+ ry *= lambdaRoot;
+ //Update rx2 and ry2
+ rx2 = rx * rx;
+ ry2 = ry * ry;
+ }
+
+ c = (rx2 * ry2) - (rx2 * y1p2) - (ry2 * x1p2);
+
+ //Check if there is no possible solution
+ //(i.e. we can't do a square root of a negative value)
+ if (c < 0.0f) {
+ //Scale uniformly until we have a single solution
+ //(see F6.2) i.e. when c == 0.0
+ float scale = sqrtf(1.0f - c / (rx2 * ry2));
+ rx *= scale;
+ ry *= scale;
+ //Update rx2 and ry2
+ rx2 = rx * rx;
+ ry2 = ry * ry;
+
+ //Step 2 (F6.5.2) - simplified since c == 0.0
+ cxp = 0.0f;
+ cyp = 0.0f;
+ //Step 3 (F6.5.3 first part) - simplified since cxp and cyp == 0.0
+ cx = 0.0f;
+ cy = 0.0f;
+ } else {
+ //Complete c calculation
+ c = sqrtf(c / ((rx2 * y1p2) + (ry2 * x1p2)));
+ //Inverse sign if Fa == Fs
+ if (largeArc == sweep) c = -c;
+
+ //Step 2 (F6.5.2)
+ cxp = c * (rx * y1p / ry);
+ cyp = c * (-ry * x1p / rx);
+
+ //Step 3 (F6.5.3 first part)
+ cx = cosPhi * cxp - sinPhi * cyp;
+ cy = sinPhi * cxp + cosPhi * cyp;
+ }
+
+ //Step 3 (F6.5.3 second part) we now have the center point of the ellipse
+ cx += (sx + x) / 2.0f;
+ cy += (sy + y) / 2.0f;
+
+ //Sstep 4 (F6.5.4)
+ //We dont' use arccos (as per w3c doc), see
+ //http://www.euclideanspace.com/maths/algebra/vectors/angleBetween/index.htm
+ //Note: atan2 (0.0, 1.0) == 0.0
+ at = atan2(((y1p - cyp) / ry), ((x1p - cxp) / rx));
+ theta1 = (at < 0.0f) ? 2.0f * M_PI + at : at;
+
+ nat = atan2(((-y1p - cyp) / ry), ((-x1p - cxp) / rx));
+ deltaTheta = (nat < at) ? 2.0f * M_PI - at + nat : nat - at;
+
+ if (sweep) {
+ //Ensure delta theta < 0 or else add 360 degrees
+ if (deltaTheta < 0.0f) deltaTheta += (float)(2.0f * M_PI);
+ } else {
+ //Ensure delta theta > 0 or else substract 360 degrees
+ if (deltaTheta > 0.0f) deltaTheta -= (float)(2.0f * M_PI);
+ }
+
+ //Add several cubic bezier to approximate the arc
+ //(smaller than 90 degrees)
+ //We add one extra segment because we want something
+ //Smaller than 90deg (i.e. not 90 itself)
+ segments = static_cast<int>(fabsf(deltaTheta / float(M_PI_2)) + 1.0f);
+ delta = deltaTheta / segments;
+
+ //http://www.stillhq.com/ctpfaq/2001/comp.text.pdf-faq-2001-04.txt (section 2.13)
+ bcp = 4.0f / 3.0f * (1.0f - cosf(delta / 2.0f)) / sinf(delta / 2.0f);
+
+ cosPhiRx = cosPhi * rx;
+ cosPhiRy = cosPhi * ry;
+ sinPhiRx = sinPhi * rx;
+ sinPhiRy = sinPhi * ry;
+
+ cosTheta1 = cosf(theta1);
+ sinTheta1 = sinf(theta1);
+
+ for (int i = 0; i < segments; ++i) {
+ //End angle (for this segment) = current + delta
+ float c1x, c1y, ex, ey, c2x, c2y;
+ float theta2 = theta1 + delta;
+ float cosTheta2 = cosf(theta2);
+ float sinTheta2 = sinf(theta2);
+ Point p[3];
+
+ //First control point (based on start point sx,sy)
+ c1x = sx - bcp * (cosPhiRx * sinTheta1 + sinPhiRy * cosTheta1);
+ c1y = sy + bcp * (cosPhiRy * cosTheta1 - sinPhiRx * sinTheta1);
+
+ //End point (for this segment)
+ ex = cx + (cosPhiRx * cosTheta2 - sinPhiRy * sinTheta2);
+ ey = cy + (sinPhiRx * cosTheta2 + cosPhiRy * sinTheta2);
+
+ //Second control point (based on end point ex,ey)
+ c2x = ex + bcp * (cosPhiRx * sinTheta2 + sinPhiRy * cosTheta2);
+ c2y = ey + bcp * (sinPhiRx * sinTheta2 - cosPhiRy * cosTheta2);
+ cmds->push(PathCommand::CubicTo);
+ p[0] = {c1x, c1y};
+ p[1] = {c2x, c2y};
+ p[2] = {ex, ey};
+ pts->push(p[0]);
+ pts->push(p[1]);
+ pts->push(p[2]);
+ *curCtl = p[1];
+ *cur = p[2];
+
+ //Next start point is the current end point (same for angle)
+ sx = ex;
+ sy = ey;
+ theta1 = theta2;
+ //Avoid recomputations
+ cosTheta1 = cosTheta2;
+ sinTheta1 = sinTheta2;
+ }
+}
+
+static int _numberCount(char cmd)
+{
+ int count = 0;
+ switch (cmd) {
+ case 'M':
+ case 'm':
+ case 'L':
+ case 'l':
+ case 'T':
+ case 't': {
+ count = 2;
+ break;
+ }
+ case 'C':
+ case 'c':
+ case 'E':
+ case 'e': {
+ count = 6;
+ break;
+ }
+ case 'H':
+ case 'h':
+ case 'V':
+ case 'v': {
+ count = 1;
+ break;
+ }
+ case 'S':
+ case 's':
+ case 'Q':
+ case 'q': {
+ count = 4;
+ break;
+ }
+ case 'A':
+ case 'a': {
+ count = 7;
+ break;
+ }
+ default:
+ break;
+ }
+ return count;
+}
+
+
+static bool _processCommand(Array<PathCommand>* cmds, Array<Point>* pts, char cmd, float* arr, int count, Point* cur, Point* curCtl, Point* startPoint, bool *isQuadratic)
+{
+ switch (cmd) {
+ case 'm':
+ case 'l':
+ case 'c':
+ case 's':
+ case 'q':
+ case 't': {
+ for (int i = 0; i < count - 1; i += 2) {
+ arr[i] = arr[i] + cur->x;
+ arr[i + 1] = arr[i + 1] + cur->y;
+ }
+ break;
+ }
+ case 'h': {
+ arr[0] = arr[0] + cur->x;
+ break;
+ }
+ case 'v': {
+ arr[0] = arr[0] + cur->y;
+ break;
+ }
+ case 'a': {
+ arr[5] = arr[5] + cur->x;
+ arr[6] = arr[6] + cur->y;
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+
+ switch (cmd) {
+ case 'm':
+ case 'M': {
+ Point p = {arr[0], arr[1]};
+ cmds->push(PathCommand::MoveTo);
+ pts->push(p);
+ *cur = {arr[0], arr[1]};
+ *startPoint = {arr[0], arr[1]};
+ break;
+ }
+ case 'l':
+ case 'L': {
+ Point p = {arr[0], arr[1]};
+ cmds->push(PathCommand::LineTo);
+ pts->push(p);
+ *cur = {arr[0], arr[1]};
+ break;
+ }
+ case 'c':
+ case 'C': {
+ Point p[3];
+ cmds->push(PathCommand::CubicTo);
+ p[0] = {arr[0], arr[1]};
+ p[1] = {arr[2], arr[3]};
+ p[2] = {arr[4], arr[5]};
+ pts->push(p[0]);
+ pts->push(p[1]);
+ pts->push(p[2]);
+ *curCtl = p[1];
+ *cur = p[2];
+ *isQuadratic = false;
+ break;
+ }
+ case 's':
+ case 'S': {
+ Point p[3], ctrl;
+ if ((cmds->count > 1) && (cmds->data[cmds->count - 1] == PathCommand::CubicTo) &&
+ !(*isQuadratic)) {
+ ctrl.x = 2 * cur->x - curCtl->x;
+ ctrl.y = 2 * cur->y - curCtl->y;
+ } else {
+ ctrl = *cur;
+ }
+ cmds->push(PathCommand::CubicTo);
+ p[0] = ctrl;
+ p[1] = {arr[0], arr[1]};
+ p[2] = {arr[2], arr[3]};
+ pts->push(p[0]);
+ pts->push(p[1]);
+ pts->push(p[2]);
+ *curCtl = p[1];
+ *cur = p[2];
+ *isQuadratic = false;
+ break;
+ }
+ case 'q':
+ case 'Q': {
+ Point p[3];
+ float ctrl_x0 = (cur->x + 2 * arr[0]) * (1.0 / 3.0);
+ float ctrl_y0 = (cur->y + 2 * arr[1]) * (1.0 / 3.0);
+ float ctrl_x1 = (arr[2] + 2 * arr[0]) * (1.0 / 3.0);
+ float ctrl_y1 = (arr[3] + 2 * arr[1]) * (1.0 / 3.0);
+ cmds->push(PathCommand::CubicTo);
+ p[0] = {ctrl_x0, ctrl_y0};
+ p[1] = {ctrl_x1, ctrl_y1};
+ p[2] = {arr[2], arr[3]};
+ pts->push(p[0]);
+ pts->push(p[1]);
+ pts->push(p[2]);
+ *curCtl = {arr[0], arr[1]};
+ *cur = p[2];
+ *isQuadratic = true;
+ break;
+ }
+ case 't':
+ case 'T': {
+ Point p[3], ctrl;
+ if ((cmds->count > 1) && (cmds->data[cmds->count - 1] == PathCommand::CubicTo) &&
+ *isQuadratic) {
+ ctrl.x = 2 * cur->x - curCtl->x;
+ ctrl.y = 2 * cur->y - curCtl->y;
+ } else {
+ ctrl = *cur;
+ }
+ float ctrl_x0 = (cur->x + 2 * ctrl.x) * (1.0 / 3.0);
+ float ctrl_y0 = (cur->y + 2 * ctrl.y) * (1.0 / 3.0);
+ float ctrl_x1 = (arr[0] + 2 * ctrl.x) * (1.0 / 3.0);
+ float ctrl_y1 = (arr[1] + 2 * ctrl.y) * (1.0 / 3.0);
+ cmds->push(PathCommand::CubicTo);
+ p[0] = {ctrl_x0, ctrl_y0};
+ p[1] = {ctrl_x1, ctrl_y1};
+ p[2] = {arr[0], arr[1]};
+ pts->push(p[0]);
+ pts->push(p[1]);
+ pts->push(p[2]);
+ *curCtl = {ctrl.x, ctrl.y};
+ *cur = p[2];
+ *isQuadratic = true;
+ break;
+ }
+ case 'h':
+ case 'H': {
+ Point p = {arr[0], cur->y};
+ cmds->push(PathCommand::LineTo);
+ pts->push(p);
+ cur->x = arr[0];
+ break;
+ }
+ case 'v':
+ case 'V': {
+ Point p = {cur->x, arr[0]};
+ cmds->push(PathCommand::LineTo);
+ pts->push(p);
+ cur->y = arr[0];
+ break;
+ }
+ case 'z':
+ case 'Z': {
+ cmds->push(PathCommand::Close);
+ *cur = *startPoint;
+ break;
+ }
+ case 'a':
+ case 'A': {
+ _pathAppendArcTo(cmds, pts, cur, curCtl, arr[5], arr[6], arr[0], arr[1], arr[2], arr[3], arr[4]);
+ *cur = *curCtl = {arr[5], arr[6]};
+ *isQuadratic = false;
+ break;
+ }
+ default: {
+ return false;
+ }
+ }
+ return true;
+}
+
+
+static char* _nextCommand(char* path, char* cmd, float* arr, int* count)
+{
+ int large, sweep;
+
+ path = _skipComma(path);
+ if (isalpha(*path)) {
+ *cmd = *path;
+ path++;
+ *count = _numberCount(*cmd);
+ } else {
+ if (*cmd == 'm') *cmd = 'l';
+ else if (*cmd == 'M') *cmd = 'L';
+ }
+ if (*count == 7) {
+ //Special case for arc command
+ if (_parseNumber(&path, &arr[0])) {
+ if (_parseNumber(&path, &arr[1])) {
+ if (_parseNumber(&path, &arr[2])) {
+ if (_parseFlag(&path, &large)) {
+ if (_parseFlag(&path, &sweep)) {
+ if (_parseNumber(&path, &arr[5])) {
+ if (_parseNumber(&path, &arr[6])) {
+ arr[3] = (float)large;
+ arr[4] = (float)sweep;
+ return path;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ *count = 0;
+ return NULL;
+ }
+ for (int i = 0; i < *count; i++) {
+ if (!_parseNumber(&path, &arr[i])) {
+ *count = 0;
+ return NULL;
+ }
+ path = _skipComma(path);
+ }
+ return path;
+}
+
+
+/************************************************************************/
+/* External Class Implementation */
+/************************************************************************/
+
+
+bool svgPathToTvgPath(const char* svgPath, Array<PathCommand>& cmds, Array<Point>& pts)
+{
+ float numberArray[7];
+ int numberCount = 0;
+ Point cur = { 0, 0 };
+ Point curCtl = { 0, 0 };
+ Point startPoint = { 0, 0 };
+ char cmd = 0;
+ bool isQuadratic = false;
+ char* path = (char*)svgPath;
+ char* curLocale;
+
+ curLocale = setlocale(LC_NUMERIC, NULL);
+ if (curLocale) curLocale = strdup(curLocale);
+ setlocale(LC_NUMERIC, "POSIX");
+
+ while ((path[0] != '\0')) {
+ path = _nextCommand(path, &cmd, numberArray, &numberCount);
+ if (!path) break;
+ if (!_processCommand(&cmds, &pts, cmd, numberArray, numberCount, &cur, &curCtl, &startPoint, &isQuadratic)) break;
+ }
+
+ setlocale(LC_NUMERIC, curLocale);
+ if (curLocale) free(curLocale);
+
+ return true;
+}
diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgPath.h b/thirdparty/thorvg/src/loaders/svg/tvgSvgPath.h
new file mode 100644
index 0000000000..7f26c4a213
--- /dev/null
+++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgPath.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef _TVG_SVG_PATH_H_
+#define _TVG_SVG_PATH_H_
+
+#include <tvgCommon.h>
+
+bool svgPathToTvgPath(const char* svgPath, Array<PathCommand>& cmds, Array<Point>& pts);
+
+#endif //_TVG_SVG_PATH_H_
diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.cpp b/thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.cpp
new file mode 100644
index 0000000000..8701fe32b1
--- /dev/null
+++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.cpp
@@ -0,0 +1,652 @@
+/*
+ * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/*
+ * Copyright notice for the EFL:
+
+ * Copyright (C) EFL developers (see AUTHORS)
+
+ * All rights reserved.
+
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
+ * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+
+#include <string>
+#include "tvgMath.h"
+#include "tvgSvgLoaderCommon.h"
+#include "tvgSvgSceneBuilder.h"
+#include "tvgSvgPath.h"
+#include "tvgSvgUtil.h"
+
+/************************************************************************/
+/* Internal Class Implementation */
+/************************************************************************/
+
+struct Box
+{
+ float x, y, w, h;
+};
+
+
+static bool _appendShape(SvgNode* node, Shape* shape, const Box& vBox, const string& svgPath);
+static unique_ptr<Scene> _sceneBuildHelper(const SvgNode* node, const Box& vBox, const string& svgPath, bool mask);
+
+
+static inline bool _isGroupType(SvgNodeType type)
+{
+ if (type == SvgNodeType::Doc || type == SvgNodeType::G || type == SvgNodeType::Use || type == SvgNodeType::ClipPath) return true;
+ return false;
+}
+
+
+//According to: https://www.w3.org/TR/SVG11/coords.html#ObjectBoundingBoxUnits (the last paragraph)
+//a stroke width should be ignored for bounding box calculations
+static Box _boundingBox(const Shape* shape)
+{
+ float x, y, w, h;
+ shape->bounds(&x, &y, &w, &h, false);
+
+ if (auto strokeW = shape->strokeWidth()) {
+ x += 0.5f * strokeW;
+ y += 0.5f * strokeW;
+ w -= strokeW;
+ h -= strokeW;
+ }
+
+ return {x, y, w, h};
+}
+
+
+static void _transformMultiply(const Matrix* mBBox, Matrix* gradTransf)
+{
+ gradTransf->e13 = gradTransf->e13 * mBBox->e11 + mBBox->e13;
+ gradTransf->e12 *= mBBox->e11;
+ gradTransf->e11 *= mBBox->e11;
+
+ gradTransf->e23 = gradTransf->e23 * mBBox->e22 + mBBox->e23;
+ gradTransf->e22 *= mBBox->e22;
+ gradTransf->e21 *= mBBox->e22;
+}
+
+
+static unique_ptr<LinearGradient> _applyLinearGradientProperty(SvgStyleGradient* g, const Shape* vg, const Box& vBox, int opacity)
+{
+ Fill::ColorStop* stops;
+ int stopCount = 0;
+ auto fillGrad = LinearGradient::gen();
+
+ bool isTransform = (g->transform ? true : false);
+ Matrix finalTransform = {1, 0, 0, 0, 1, 0, 0, 0, 1};
+ if (isTransform) finalTransform = *g->transform;
+
+ if (g->userSpace) {
+ g->linear->x1 = g->linear->x1 * vBox.w;
+ g->linear->y1 = g->linear->y1 * vBox.h;
+ g->linear->x2 = g->linear->x2 * vBox.w;
+ g->linear->y2 = g->linear->y2 * vBox.h;
+ } else {
+ Matrix m = {vBox.w, 0, vBox.x, 0, vBox.h, vBox.y, 0, 0, 1};
+ if (isTransform) _transformMultiply(&m, &finalTransform);
+ else {
+ finalTransform = m;
+ isTransform = true;
+ }
+ }
+
+ if (isTransform) fillGrad->transform(finalTransform);
+
+ fillGrad->linear(g->linear->x1, g->linear->y1, g->linear->x2, g->linear->y2);
+ fillGrad->spread(g->spread);
+
+ //Update the stops
+ stopCount = g->stops.count;
+ if (stopCount > 0) {
+ stops = (Fill::ColorStop*)calloc(stopCount, sizeof(Fill::ColorStop));
+ if (!stops) return fillGrad;
+ auto prevOffset = 0.0f;
+ for (uint32_t i = 0; i < g->stops.count; ++i) {
+ auto colorStop = &g->stops.data[i];
+ //Use premultiplied color
+ stops[i].r = colorStop->r;
+ stops[i].g = colorStop->g;
+ stops[i].b = colorStop->b;
+ stops[i].a = static_cast<uint8_t>((colorStop->a * opacity) / 255);
+ stops[i].offset = colorStop->offset;
+ //check the offset corner cases - refer to: https://svgwg.org/svg2-draft/pservers.html#StopNotes
+ if (colorStop->offset < prevOffset) stops[i].offset = prevOffset;
+ else if (colorStop->offset > 1) stops[i].offset = 1;
+ prevOffset = stops[i].offset;
+ }
+ fillGrad->colorStops(stops, stopCount);
+ free(stops);
+ }
+ return fillGrad;
+}
+
+
+static unique_ptr<RadialGradient> _applyRadialGradientProperty(SvgStyleGradient* g, const Shape* vg, const Box& vBox, int opacity)
+{
+ Fill::ColorStop *stops;
+ int stopCount = 0;
+ auto fillGrad = RadialGradient::gen();
+
+ bool isTransform = (g->transform ? true : false);
+ Matrix finalTransform = {1, 0, 0, 0, 1, 0, 0, 0, 1};
+ if (isTransform) finalTransform = *g->transform;
+
+ if (g->userSpace) {
+ //The radius scalling is done according to the Units section:
+ //https://www.w3.org/TR/2015/WD-SVG2-20150915/coords.html
+ g->radial->cx = g->radial->cx * vBox.w;
+ g->radial->cy = g->radial->cy * vBox.h;
+ g->radial->r = g->radial->r * sqrtf(powf(vBox.w, 2.0f) + powf(vBox.h, 2.0f)) / sqrtf(2.0f);
+ g->radial->fx = g->radial->fx * vBox.w;
+ g->radial->fy = g->radial->fy * vBox.h;
+ } else {
+ Matrix m = {vBox.w, 0, vBox.x, 0, vBox.h, vBox.y, 0, 0, 1};
+ if (isTransform) _transformMultiply(&m, &finalTransform);
+ else {
+ finalTransform = m;
+ isTransform = true;
+ }
+ }
+
+ if (isTransform) fillGrad->transform(finalTransform);
+
+ //TODO: Tvg is not support to focal
+ //if (g->radial->fx != 0 && g->radial->fy != 0) {
+ // fillGrad->radial(g->radial->fx, g->radial->fy, g->radial->r);
+ //}
+ fillGrad->radial(g->radial->cx, g->radial->cy, g->radial->r);
+ fillGrad->spread(g->spread);
+
+ //Update the stops
+ stopCount = g->stops.count;
+ if (stopCount > 0) {
+ stops = (Fill::ColorStop*)calloc(stopCount, sizeof(Fill::ColorStop));
+ if (!stops) return fillGrad;
+ auto prevOffset = 0.0f;
+ for (uint32_t i = 0; i < g->stops.count; ++i) {
+ auto colorStop = &g->stops.data[i];
+ //Use premultiplied color
+ stops[i].r = colorStop->r;
+ stops[i].g = colorStop->g;
+ stops[i].b = colorStop->b;
+ stops[i].a = static_cast<uint8_t>((colorStop->a * opacity) / 255);
+ stops[i].offset = colorStop->offset;
+ //check the offset corner cases - refer to: https://svgwg.org/svg2-draft/pservers.html#StopNotes
+ if (colorStop->offset < prevOffset) stops[i].offset = prevOffset;
+ else if (colorStop->offset > 1) stops[i].offset = 1;
+ prevOffset = stops[i].offset;
+ }
+ fillGrad->colorStops(stops, stopCount);
+ free(stops);
+ }
+ return fillGrad;
+}
+
+
+static bool _appendChildShape(SvgNode* node, Shape* shape, const Box& vBox, const string& svgPath)
+{
+ auto valid = false;
+
+ if (_appendShape(node, shape, vBox, svgPath)) valid = true;
+
+ if (node->child.count > 0) {
+ auto child = node->child.data;
+ for (uint32_t i = 0; i < node->child.count; ++i, ++child) {
+ if (_appendChildShape(*child, shape, vBox, svgPath)) valid = true;
+ }
+ }
+
+ return valid;
+}
+
+
+static void _applyComposition(Paint* paint, const SvgNode* node, const Box& vBox, const string& svgPath)
+{
+ /* ClipPath */
+ /* Do not drop in Circular Dependency for ClipPath.
+ Composition can be applied recursively if its children nodes have composition target to this one. */
+ if (node->style->clipPath.applying) {
+ TVGLOG("SVG", "Multiple Composition Tried! Check out Circular dependency?");
+ } else {
+ auto compNode = node->style->clipPath.node;
+ if (compNode && compNode->child.count > 0) {
+ node->style->clipPath.applying = true;
+
+ auto comp = Shape::gen();
+ comp->fill(255, 255, 255, 255);
+ if (node->transform) comp->transform(*node->transform);
+
+ auto child = compNode->child.data;
+ auto valid = false; //Composite only when valid shapes are existed
+
+ for (uint32_t i = 0; i < compNode->child.count; ++i, ++child) {
+ if (_appendChildShape(*child, comp.get(), vBox, svgPath)) valid = true;
+ }
+
+ if (valid) paint->composite(move(comp), CompositeMethod::ClipPath);
+
+ node->style->clipPath.applying = false;
+ }
+ }
+
+ /* Mask */
+ /* Do not drop in Circular Dependency for Mask.
+ Composition can be applied recursively if its children nodes have composition target to this one. */
+ if (node->style->mask.applying) {
+ TVGLOG("SVG", "Multiple Composition Tried! Check out Circular dependency?");
+ } else {
+ auto compNode = node->style->mask.node;
+ if (compNode && compNode->child.count > 0) {
+ node->style->mask.applying = true;
+
+ auto comp = _sceneBuildHelper(compNode, vBox, svgPath, true);
+ if (comp) {
+ if (node->transform) comp->transform(*node->transform);
+ paint->composite(move(comp), CompositeMethod::AlphaMask);
+ }
+
+ node->style->mask.applying = false;
+ }
+ }
+}
+
+
+static void _applyProperty(SvgNode* node, Shape* vg, const Box& vBox, const string& svgPath)
+{
+ SvgStyleProperty* style = node->style;
+
+ if (node->transform) vg->transform(*node->transform);
+ if (node->type == SvgNodeType::Doc || !node->display) return;
+
+ //If fill property is nullptr then do nothing
+ if (style->fill.paint.none) {
+ //Do nothing
+ } else if (style->fill.paint.gradient) {
+ Box bBox = vBox;
+ if (!style->fill.paint.gradient->userSpace) bBox = _boundingBox(vg);
+
+ if (style->fill.paint.gradient->type == SvgGradientType::Linear) {
+ auto linear = _applyLinearGradientProperty(style->fill.paint.gradient, vg, bBox, style->fill.opacity);
+ vg->fill(move(linear));
+ } else if (style->fill.paint.gradient->type == SvgGradientType::Radial) {
+ auto radial = _applyRadialGradientProperty(style->fill.paint.gradient, vg, bBox, style->fill.opacity);
+ vg->fill(move(radial));
+ }
+ } else if (style->fill.paint.url) {
+ //TODO: Apply the color pointed by url
+ } else if (style->fill.paint.curColor) {
+ //Apply the current style color
+ vg->fill(style->color.r, style->color.g, style->color.b, style->fill.opacity);
+ } else {
+ //Apply the fill color
+ vg->fill(style->fill.paint.color.r, style->fill.paint.color.g, style->fill.paint.color.b, style->fill.opacity);
+ }
+
+ //Apply the fill rule
+ vg->fill((tvg::FillRule)style->fill.fillRule);
+
+ //Apply node opacity
+ if (style->opacity < 255) vg->opacity(style->opacity);
+
+ if (node->type == SvgNodeType::G || node->type == SvgNodeType::Use) return;
+
+ //Apply the stroke style property
+ vg->stroke(style->stroke.width);
+ vg->stroke(style->stroke.cap);
+ vg->stroke(style->stroke.join);
+ if (style->stroke.dash.array.count > 0) {
+ vg->stroke(style->stroke.dash.array.data, style->stroke.dash.array.count);
+ }
+
+ //If stroke property is nullptr then do nothing
+ if (style->stroke.paint.none) {
+ //Do nothing
+ } else if (style->stroke.paint.gradient) {
+ Box bBox = vBox;
+ if (!style->stroke.paint.gradient->userSpace) bBox = _boundingBox(vg);
+
+ if (style->stroke.paint.gradient->type == SvgGradientType::Linear) {
+ auto linear = _applyLinearGradientProperty(style->stroke.paint.gradient, vg, bBox, style->stroke.opacity);
+ vg->stroke(move(linear));
+ } else if (style->stroke.paint.gradient->type == SvgGradientType::Radial) {
+ auto radial = _applyRadialGradientProperty(style->stroke.paint.gradient, vg, bBox, style->stroke.opacity);
+ vg->stroke(move(radial));
+ }
+ } else if (style->stroke.paint.url) {
+ //TODO: Apply the color pointed by url
+ } else if (style->stroke.paint.curColor) {
+ //Apply the current style color
+ vg->stroke(style->color.r, style->color.g, style->color.b, style->stroke.opacity);
+ } else {
+ //Apply the stroke color
+ vg->stroke(style->stroke.paint.color.r, style->stroke.paint.color.g, style->stroke.paint.color.b, style->stroke.opacity);
+ }
+
+ _applyComposition(vg, node, vBox, svgPath);
+}
+
+
+static unique_ptr<Shape> _shapeBuildHelper(SvgNode* node, const Box& vBox, const string& svgPath)
+{
+ auto shape = Shape::gen();
+ if (_appendShape(node, shape.get(), vBox, svgPath)) return shape;
+ else return nullptr;
+}
+
+
+static bool _appendShape(SvgNode* node, Shape* shape, const Box& vBox, const string& svgPath)
+{
+ Array<PathCommand> cmds;
+ Array<Point> pts;
+
+ switch (node->type) {
+ case SvgNodeType::Path: {
+ if (node->node.path.path) {
+ if (svgPathToTvgPath(node->node.path.path, cmds, pts)) {
+ shape->appendPath(cmds.data, cmds.count, pts.data, pts.count);
+ }
+ }
+ break;
+ }
+ case SvgNodeType::Ellipse: {
+ shape->appendCircle(node->node.ellipse.cx, node->node.ellipse.cy, node->node.ellipse.rx, node->node.ellipse.ry);
+ break;
+ }
+ case SvgNodeType::Polygon: {
+ if (node->node.polygon.pointsCount < 2) break;
+ shape->moveTo(node->node.polygon.points[0], node->node.polygon.points[1]);
+ for (int i = 2; i < node->node.polygon.pointsCount - 1; i += 2) {
+ shape->lineTo(node->node.polygon.points[i], node->node.polygon.points[i + 1]);
+ }
+ shape->close();
+ break;
+ }
+ case SvgNodeType::Polyline: {
+ if (node->node.polygon.pointsCount < 2) break;
+ shape->moveTo(node->node.polygon.points[0], node->node.polygon.points[1]);
+ for (int i = 2; i < node->node.polygon.pointsCount - 1; i += 2) {
+ shape->lineTo(node->node.polygon.points[i], node->node.polygon.points[i + 1]);
+ }
+ break;
+ }
+ case SvgNodeType::Circle: {
+ shape->appendCircle(node->node.circle.cx, node->node.circle.cy, node->node.circle.r, node->node.circle.r);
+ break;
+ }
+ case SvgNodeType::Rect: {
+ 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);
+ break;
+ }
+ case SvgNodeType::Line: {
+ shape->moveTo(node->node.line.x1, node->node.line.y1);
+ shape->lineTo(node->node.line.x2, node->node.line.y2);
+ break;
+ }
+ default: {
+ return false;
+ }
+ }
+
+ _applyProperty(node, shape, vBox, svgPath);
+ return true;
+}
+
+
+enum class imageMimeTypeEncoding
+{
+ base64 = 0x1,
+ utf8 = 0x2
+};
+constexpr imageMimeTypeEncoding operator|(imageMimeTypeEncoding a, imageMimeTypeEncoding b) {
+ return static_cast<imageMimeTypeEncoding>(static_cast<int>(a) | static_cast<int>(b));
+}
+constexpr bool operator&(imageMimeTypeEncoding a, imageMimeTypeEncoding b) {
+ return (static_cast<int>(a) & static_cast<int>(b));
+}
+
+
+static constexpr struct
+{
+ const char* name;
+ int sz;
+ imageMimeTypeEncoding encoding;
+} imageMimeTypes[] = {
+ {"jpeg", sizeof("jpeg"), imageMimeTypeEncoding::base64},
+ {"png", sizeof("png"), imageMimeTypeEncoding::base64},
+ {"svg+xml", sizeof("svg+xml"), imageMimeTypeEncoding::base64 | imageMimeTypeEncoding::utf8},
+};
+
+
+static bool _isValidImageMimeTypeAndEncoding(const char** href, const char** mimetype, imageMimeTypeEncoding* encoding) {
+ if (strncmp(*href, "image/", sizeof("image/") - 1)) return false; //not allowed mime type
+ *href += sizeof("image/") - 1;
+
+ //RFC2397 data:[<mediatype>][;base64],<data>
+ //mediatype := [ type "/" subtype ] *( ";" parameter )
+ //parameter := attribute "=" value
+ for (unsigned int i = 0; i < sizeof(imageMimeTypes) / sizeof(imageMimeTypes[0]); i++) {
+ if (!strncmp(*href, imageMimeTypes[i].name, imageMimeTypes[i].sz - 1)) {
+ *href += imageMimeTypes[i].sz - 1;
+ *mimetype = imageMimeTypes[i].name;
+
+ while (**href && **href != ',') {
+ while (**href && **href != ';') ++(*href);
+ if (!**href) return false;
+ ++(*href);
+
+ if (imageMimeTypes[i].encoding & imageMimeTypeEncoding::base64) {
+ if (!strncmp(*href, "base64,", sizeof("base64,") - 1)) {
+ *href += sizeof("base64,") - 1;
+ *encoding = imageMimeTypeEncoding::base64;
+ return true; //valid base64
+ }
+ }
+ if (imageMimeTypes[i].encoding & imageMimeTypeEncoding::utf8) {
+ if (!strncmp(*href, "utf8,", sizeof("utf8,") - 1)) {
+ *href += sizeof("utf8,") - 1;
+ *encoding = imageMimeTypeEncoding::utf8;
+ return true; //valid utf8
+ }
+ }
+ }
+ //no encoding defined
+ if (**href == ',' && (imageMimeTypes[i].encoding & imageMimeTypeEncoding::utf8)) {
+ ++(*href);
+ *encoding = imageMimeTypeEncoding::utf8;
+ return true; //allow no encoding defined if utf8 expected
+ }
+ return false;
+ }
+ }
+ return false;
+}
+
+
+static unique_ptr<Picture> _imageBuildHelper(SvgNode* node, const Box& vBox, const string& svgPath)
+{
+ if (!node->node.image.href) return nullptr;
+ auto picture = Picture::gen();
+
+ const char* href = node->node.image.href;
+ if (!strncmp(href, "data:", sizeof("data:") - 1)) {
+ href += sizeof("data:") - 1;
+ const char* mimetype;
+ imageMimeTypeEncoding encoding;
+ if (!_isValidImageMimeTypeAndEncoding(&href, &mimetype, &encoding)) return nullptr; //not allowed mime type or encoding
+ if (encoding == imageMimeTypeEncoding::base64) {
+ string decoded = svgUtilBase64Decode(href);
+ if (picture->load(decoded.c_str(), decoded.size(), mimetype, true) != Result::Success) return nullptr;
+ } else {
+ string decoded = svgUtilURLDecode(href);
+ if (picture->load(decoded.c_str(), decoded.size(), mimetype, true) != Result::Success) return nullptr;
+ }
+ } else {
+ if (!strncmp(href, "file://", sizeof("file://") - 1)) href += sizeof("file://") - 1;
+ //TODO: protect against recursive svg image loading
+ //Temporarily disable embedded svg:
+ const char *dot = strrchr(href, '.');
+ if (dot && !strcmp(dot, ".svg")) {
+ TVGLOG("SVG", "Embedded svg file is disabled.");
+ return nullptr;
+ }
+ string imagePath = href;
+ if (strncmp(href, "/", 1)) {
+ auto last = svgPath.find_last_of("/");
+ imagePath = svgPath.substr(0, (last == string::npos ? 0 : last + 1 )) + imagePath;
+ }
+ if (picture->load(imagePath) != Result::Success) return nullptr;
+ }
+
+ float w, h;
+ if (picture->size(&w, &h) == Result::Success && w > 0 && h > 0) {
+ auto sx = node->node.image.w / w;
+ auto sy = node->node.image.h / h;
+ Matrix m = {sx, 0, node->node.image.x, 0, sy, node->node.image.y, 0, 0, 1};
+ picture->transform(m);
+ }
+
+ _applyComposition(picture.get(), node, vBox, svgPath);
+ return picture;
+}
+
+
+static unique_ptr<Scene> _useBuildHelper(const SvgNode* node, const Box& vBox, const string& svgPath)
+{
+ auto scene = _sceneBuildHelper(node, vBox, svgPath, false);
+ if (node->node.use.x != 0.0f || node->node.use.y != 0.0f) {
+ scene->translate(node->node.use.x, node->node.use.y);
+ }
+ if (node->node.use.w > 0.0f && node->node.use.h > 0.0f) {
+ //TODO: handle width/height properties
+ }
+ return scene;
+}
+
+
+static unique_ptr<Scene> _sceneBuildHelper(const SvgNode* node, const Box& vBox, const string& svgPath, bool mask)
+{
+ if (_isGroupType(node->type) || mask) {
+ auto scene = Scene::gen();
+ if (!mask && node->transform) scene->transform(*node->transform);
+
+ if (node->display && node->style->opacity != 0) {
+ auto child = node->child.data;
+ for (uint32_t i = 0; i < node->child.count; ++i, ++child) {
+ if (_isGroupType((*child)->type)) {
+ if ((*child)->type == SvgNodeType::Use)
+ scene->push(_useBuildHelper(*child, vBox, svgPath));
+ else
+ scene->push(_sceneBuildHelper(*child, vBox, svgPath, false));
+ } else if ((*child)->type == SvgNodeType::Image) {
+ auto image = _imageBuildHelper(*child, vBox, svgPath);
+ if (image) scene->push(move(image));
+ } else if ((*child)->type != SvgNodeType::Mask) {
+ auto shape = _shapeBuildHelper(*child, vBox, svgPath);
+ if (shape) scene->push(move(shape));
+ }
+ }
+ _applyComposition(scene.get(), node, vBox, svgPath);
+ scene->opacity(node->style->opacity);
+ }
+ return scene;
+ }
+ return nullptr;
+}
+
+
+/************************************************************************/
+/* External Class Implementation */
+/************************************************************************/
+
+unique_ptr<Scene> svgSceneBuild(SvgNode* node, float vx, float vy, float vw, float vh, float w, float h, bool preserveAspect, const string& svgPath)
+{
+ if (!node || (node->type != SvgNodeType::Doc)) return nullptr;
+
+ Box vBox = {vx, vy, vw, vh};
+ auto docNode = _sceneBuildHelper(node, vBox, svgPath, false);
+
+ if (!mathEqual(w, vw) || !mathEqual(h, vh)) {
+ auto sx = w / vw;
+ auto sy = h / vh;
+
+ if (preserveAspect) {
+ //Scale
+ auto scale = sx < sy ? sx : sy;
+ docNode->scale(scale);
+ //Align
+ auto tvx = vx * scale;
+ auto tvy = vy * scale;
+ auto tvw = vw * scale;
+ auto tvh = vh * scale;
+ if (vw > vh) tvy -= (h - tvh) * 0.5f;
+ else tvx -= (w - tvw) * 0.5f;
+ docNode->translate(-tvx, -tvy);
+ } else {
+ //Align
+ auto tvx = vx * sx;
+ auto tvy = vy * sy;
+ auto tvw = vw * sx;
+ auto tvh = vh * sy;
+ if (tvw > tvh) tvy -= (h - tvh) * 0.5f;
+ else tvx -= (w - tvw) * 0.5f;
+ Matrix m = {sx, 0, -tvx, 0, sy, -tvy, 0, 0, 1};
+ docNode->transform(m);
+ }
+ } else if (!mathZero(vx) || !mathZero(vy)) {
+ docNode->translate(-vx, -vy);
+ }
+
+ auto viewBoxClip = Shape::gen();
+ viewBoxClip->appendRect(0, 0, w, h, 0, 0);
+ viewBoxClip->fill(0, 0, 0, 255);
+
+ auto compositeLayer = Scene::gen();
+ compositeLayer->composite(move(viewBoxClip), CompositeMethod::ClipPath);
+ compositeLayer->push(move(docNode));
+
+ auto root = Scene::gen();
+ root->push(move(compositeLayer));
+
+ return root;
+}
diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.h b/thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.h
new file mode 100644
index 0000000000..4232aca612
--- /dev/null
+++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef _TVG_SVG_SCENE_BUILDER_H_
+#define _TVG_SVG_SCENE_BUILDER_H_
+
+#include "tvgCommon.h"
+
+unique_ptr<Scene> svgSceneBuild(SvgNode* node, float vx, float vy, float vw, float vh, float w, float h, bool preserveAspect, const string& svgPath);
+
+#endif //_TVG_SVG_SCENE_BUILDER_H_
diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgUtil.cpp b/thirdparty/thorvg/src/loaders/svg/tvgSvgUtil.cpp
new file mode 100644
index 0000000000..d5b9cdcf7b
--- /dev/null
+++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgUtil.cpp
@@ -0,0 +1,272 @@
+/*
+ * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <math.h>
+#include <memory.h>
+#include "tvgSvgUtil.h"
+
+/************************************************************************/
+/* Internal Class Implementation */
+/************************************************************************/
+
+static inline bool _floatExact(float a, float b)
+{
+ return memcmp(&a, &b, sizeof(float)) == 0;
+}
+
+static uint8_t _hexCharToDec(const char c)
+{
+ if (c >= 'a') return c - 'a' + 10;
+ else if (c >= 'A') return c - 'A' + 10;
+ else return c - '0';
+}
+
+static uint8_t _base64Value(const char chr)
+{
+ if (chr >= 'A' && chr <= 'Z') return chr - 'A';
+ else if (chr >= 'a' && chr <= 'z') return chr - 'a' + ('Z' - 'A') + 1;
+ else if (chr >= '0' && chr <= '9') return chr - '0' + ('Z' - 'A') + ('z' - 'a') + 2;
+ else if (chr == '+' || chr == '-') return 62;
+ else return 63;
+}
+
+
+/************************************************************************/
+/* External Class Implementation */
+/************************************************************************/
+
+
+/*
+ * https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/strtof-strtof-l-wcstof-wcstof-l?view=msvc-160
+ *
+ * src should be one of the following form :
+ *
+ * [whitespace] [sign] {digits [radix digits] | radix digits} [{e | E} [sign] digits]
+ * [whitespace] [sign] {INF | INFINITY}
+ * [whitespace] [sign] NAN [sequence]
+ *
+ * No hexadecimal form supported
+ * no sequence supported after NAN
+ */
+float svgUtilStrtof(const char *nPtr, char **endPtr)
+{
+ if (endPtr) *endPtr = (char*)(nPtr);
+ if (!nPtr) return 0.0f;
+
+ auto a = nPtr;
+ auto iter = nPtr;
+ auto val = 0.0f;
+ unsigned long long integerPart = 0;
+ int minus = 1;
+
+ //ignore leading whitespaces
+ while (isspace(*iter)) iter++;
+
+ //signed or not
+ if (*iter == '-') {
+ minus = -1;
+ iter++;
+ } else if (*iter == '+') {
+ iter++;
+ }
+
+ if (tolower(*iter) == 'i') {
+ if ((tolower(*(iter + 1)) == 'n') && (tolower(*(iter + 2)) == 'f')) iter += 3;
+ else goto error;
+
+ if (tolower(*(iter + 3)) == 'i') {
+ if ((tolower(*(iter + 4)) == 'n') && (tolower(*(iter + 5)) == 'i') && (tolower(*(iter + 6)) == 't') && (tolower(*(iter + 7)) == 'y')) iter += 5;
+ else goto error;
+ }
+ if (endPtr) *endPtr = (char *)(iter);
+ return (minus == -1) ? -INFINITY : INFINITY;
+ }
+
+ if (tolower(*iter) == 'n') {
+ if ((tolower(*(iter + 1)) == 'a') && (tolower(*(iter + 2)) == 'n')) iter += 3;
+ else goto error;
+
+ if (endPtr) *endPtr = (char *)(iter);
+ return (minus == -1) ? -NAN : NAN;
+ }
+
+ //Optional: integer part before dot
+ if (isdigit(*iter)) {
+ for (; isdigit(*iter); iter++) {
+ integerPart = integerPart * 10ULL + (unsigned long long)(*iter - '0');
+ }
+ a = iter;
+ } else if (*iter != '.') {
+ goto success;
+ }
+
+ val = static_cast<float>(integerPart);
+
+ //Optional: decimal part after dot
+ if (*iter == '.') {
+ unsigned long long decimalPart = 0;
+ unsigned long long pow10 = 1;
+ int count = 0;
+
+ iter++;
+
+ if (isdigit(*iter)) {
+ for (; isdigit(*iter); iter++, count++) {
+ if (count < 19) {
+ decimalPart = decimalPart * 10ULL + + static_cast<unsigned long long>(*iter - '0');
+ pow10 *= 10ULL;
+ }
+ }
+ }
+ val += static_cast<float>(decimalPart) / static_cast<float>(pow10);
+ a = iter;
+ }
+
+ //Optional: exponent
+ if (*iter == 'e' || *iter == 'E') {
+ ++iter;
+
+ //Exception: svg may have 'em' unit for fonts. ex) 5em, 10.5em
+ if ((*iter == 'm') || (*iter == 'M')) {
+ //TODO: We don't support font em unit now, but has to multiply val * font size later...
+ a = iter + 1;
+ goto success;
+ }
+
+ //signed or not
+ int minus_e = 1;
+
+ if (*iter == '-') {
+ minus_e = -1;
+ ++iter;
+ } else if (*iter == '+') {
+ iter++;
+ }
+
+ unsigned int exponentPart = 0;
+
+ if (isdigit(*iter)) {
+ while (*iter == '0') iter++;
+ for (; isdigit(*iter); iter++) {
+ exponentPart = exponentPart * 10U + static_cast<unsigned int>(*iter - '0');
+ }
+ } else if (!isdigit(*(a - 1))) {
+ a = nPtr;
+ goto success;
+ } else if (*iter == 0) {
+ goto success;
+ }
+
+ //if ((_floatExact(val, 2.2250738585072011f)) && ((minus_e * static_cast<int>(exponentPart)) <= -308)) {
+ if ((_floatExact(val, 1.175494351f)) && ((minus_e * static_cast<int>(exponentPart)) <= -38)) {
+ //val *= 1.0e-308f;
+ val *= 1.0e-38f;
+ a = iter;
+ goto success;
+ }
+
+ a = iter;
+ auto scale = 1.0f;
+
+ while (exponentPart >= 8U) {
+ scale *= 1E8;
+ exponentPart -= 8U;
+ }
+ while (exponentPart > 0U) {
+ scale *= 10.0f;
+ exponentPart--;
+ }
+ val = (minus_e == -1) ? (val / scale) : (val * scale);
+ } else if ((iter > nPtr) && !isdigit(*(iter - 1))) {
+ a = nPtr;
+ goto success;
+ }
+
+success:
+ if (endPtr) *endPtr = (char*)(a);
+ return minus * val;
+
+error:
+ if (endPtr) *endPtr = (char*)(nPtr);
+ return 0.0f;
+}
+
+
+string svgUtilURLDecode(const char *src)
+{
+ if (!src) return nullptr;
+
+ auto length = strlen(src);
+ if (length == 0) return nullptr;
+
+ string decoded;
+ decoded.reserve(length);
+
+ char a, b;
+ while (*src) {
+ if (*src == '%' &&
+ ((a = src[1]) && (b = src[2])) &&
+ (isxdigit(a) && isxdigit(b))) {
+ decoded += (_hexCharToDec(a) << 4) + _hexCharToDec(b);
+ src+=3;
+ } else if (*src == '+') {
+ decoded += ' ';
+ src++;
+ } else {
+ decoded += *src++;
+ }
+ }
+ return decoded;
+}
+
+
+string svgUtilBase64Decode(const char *src)
+{
+ if (!src) return nullptr;
+
+ auto length = strlen(src);
+ if (length == 0) return nullptr;
+
+ string decoded;
+ decoded.reserve(3*(1+(length >> 2)));
+
+ while (*src && *(src+1)) {
+ if (*src <= 0x20) {
+ ++src;
+ continue;
+ }
+
+ auto value1 = _base64Value(src[0]);
+ auto value2 = _base64Value(src[1]);
+ decoded += (value1 << 2) + ((value2 & 0x30) >> 4);
+
+ if (!src[2] || src[2] == '=' || src[2] == '.') break;
+ auto value3 = _base64Value(src[2]);
+ decoded += ((value2 & 0x0f) << 4) + ((value3 & 0x3c) >> 2);
+
+ if (!src[3] || src[3] == '=' || src[3] == '.') break;
+ auto value4 = _base64Value(src[3]);
+ decoded += ((value3 & 0x03) << 6) + value4;
+ src += 4;
+ }
+ return decoded;
+}
diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgUtil.h b/thirdparty/thorvg/src/loaders/svg/tvgSvgUtil.h
new file mode 100644
index 0000000000..4320cfed4e
--- /dev/null
+++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgUtil.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef _TVG_SVG_UTIL_H_
+#define _TVG_SVG_UTIL_H_
+
+#include "tvgCommon.h"
+
+float svgUtilStrtof(const char *nPtr, char **endPtr);
+
+string svgUtilURLDecode(const char *src);
+string svgUtilBase64Decode(const char *src);
+
+#endif //_TVG_SVG_UTIL_H_
diff --git a/thirdparty/thorvg/src/loaders/svg/tvgXmlParser.cpp b/thirdparty/thorvg/src/loaders/svg/tvgXmlParser.cpp
new file mode 100644
index 0000000000..2e3d5928d9
--- /dev/null
+++ b/thirdparty/thorvg/src/loaders/svg/tvgXmlParser.cpp
@@ -0,0 +1,527 @@
+/*
+ * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <ctype.h>
+#include <string>
+
+#ifdef _WIN32
+ #include <malloc.h>
+#else
+ #include <alloca.h>
+#endif
+
+#include "tvgXmlParser.h"
+
+/************************************************************************/
+/* Internal Class Implementation */
+/************************************************************************/
+
+bool _isIgnoreUnsupportedLogAttributes(TVG_UNUSED const char* tagAttribute, TVG_UNUSED const char* tagValue)
+{
+#ifdef THORVG_LOG_ENABLED
+ const auto attributesNum = 6;
+ const struct
+ {
+ const char* tag;
+ bool tagWildcard; //If true, it is assumed that a wildcard is used after the tag. (ex: tagName*)
+ const char* value;
+ } attributes[] = {
+ {"id", false, nullptr},
+ {"data-name", false, nullptr},
+ {"overflow", false, "visible"},
+ {"version", false, nullptr},
+ {"xmlns", true, nullptr},
+ {"xml:space", false, nullptr},
+ };
+
+ for (unsigned int i = 0; i < attributesNum; ++i) {
+ if (!strncmp(tagAttribute, attributes[i].tag, attributes[i].tagWildcard ? strlen(attributes[i].tag) : strlen(tagAttribute))) {
+ if (attributes[i].value && tagValue) {
+ if (!strncmp(tagValue, attributes[i].value, strlen(tagValue))) {
+ return true;
+ } else continue;
+ }
+ return true;
+ }
+ }
+ return false;
+#endif
+ return true;
+}
+
+
+static const char* _simpleXmlFindWhiteSpace(const char* itr, const char* itrEnd)
+{
+ for (; itr < itrEnd; itr++) {
+ if (isspace((unsigned char)*itr)) break;
+ }
+ return itr;
+}
+
+
+static const char* _simpleXmlSkipWhiteSpace(const char* itr, const char* itrEnd)
+{
+ for (; itr < itrEnd; itr++) {
+ if (!isspace((unsigned char)*itr)) break;
+ }
+ return itr;
+}
+
+
+static const char* _simpleXmlUnskipWhiteSpace(const char* itr, const char* itrStart)
+{
+ for (itr--; itr > itrStart; itr--) {
+ if (!isspace((unsigned char)*itr)) break;
+ }
+ return itr + 1;
+}
+
+
+static const char* _simpleXmlSkipXmlEntities(const char* itr, const char* itrEnd)
+{
+ auto p = itr;
+ while (itr < itrEnd && *itr == '&') {
+ for (int i = 0; i < NUMBER_OF_XML_ENTITIES; ++i) {
+ if (strncmp(itr, xmlEntity[i], xmlEntityLength[i]) == 0) {
+ itr += xmlEntityLength[i];
+ break;
+ }
+ }
+ if (itr == p) break;
+ p = itr;
+ }
+ return itr;
+}
+
+
+static const char* _simpleXmlUnskipXmlEntities(const char* itr, const char* itrStart)
+{
+ auto p = itr;
+ while (itr > itrStart && *(itr - 1) == ';') {
+ for (int i = 0; i < NUMBER_OF_XML_ENTITIES; ++i) {
+ if (itr - xmlEntityLength[i] > itrStart &&
+ strncmp(itr - xmlEntityLength[i], xmlEntity[i], xmlEntityLength[i]) == 0) {
+ itr -= xmlEntityLength[i];
+ break;
+ }
+ }
+ if (itr == p) break;
+ p = itr;
+ }
+ return itr;
+}
+
+
+static const char* _skipWhiteSpacesAndXmlEntities(const char* itr, const char* itrEnd)
+{
+ itr = _simpleXmlSkipWhiteSpace(itr, itrEnd);
+ auto p = itr;
+ while (true) {
+ if (p != (itr = _simpleXmlSkipXmlEntities(itr, itrEnd))) p = itr;
+ else break;
+ if (p != (itr = _simpleXmlSkipWhiteSpace(itr, itrEnd))) p = itr;
+ else break;
+ }
+ return itr;
+}
+
+
+static const char* _unskipWhiteSpacesAndXmlEntities(const char* itr, const char* itrStart)
+{
+ itr = _simpleXmlUnskipWhiteSpace(itr, itrStart);
+ auto p = itr;
+ while (true) {
+ if (p != (itr = _simpleXmlUnskipXmlEntities(itr, itrStart))) p = itr;
+ else break;
+ if (p != (itr = _simpleXmlUnskipWhiteSpace(itr, itrStart))) p = itr;
+ else break;
+ }
+ return itr;
+}
+
+
+static const char* _simpleXmlFindStartTag(const char* itr, const char* itrEnd)
+{
+ return (const char*)memchr(itr, '<', itrEnd - itr);
+}
+
+
+static const char* _simpleXmlFindEndTag(const char* itr, const char* itrEnd)
+{
+ bool insideQuote = false;
+ for (; itr < itrEnd; itr++) {
+ if (*itr == '"') insideQuote = !insideQuote;
+ if (!insideQuote) {
+ if ((*itr == '>') || (*itr == '<'))
+ return itr;
+ }
+ }
+ return nullptr;
+}
+
+
+static const char* _simpleXmlFindEndCommentTag(const char* itr, const char* itrEnd)
+{
+ for (; itr < itrEnd; itr++) {
+ if ((*itr == '-') && ((itr + 1 < itrEnd) && (*(itr + 1) == '-')) && ((itr + 2 < itrEnd) && (*(itr + 2) == '>'))) return itr + 2;
+ }
+ return nullptr;
+}
+
+
+static const char* _simpleXmlFindEndCdataTag(const char* itr, const char* itrEnd)
+{
+ for (; itr < itrEnd; itr++) {
+ if ((*itr == ']') && ((itr + 1 < itrEnd) && (*(itr + 1) == ']')) && ((itr + 2 < itrEnd) && (*(itr + 2) == '>'))) return itr + 2;
+ }
+ return nullptr;
+}
+
+
+static const char* _simpleXmlFindDoctypeChildEndTag(const char* itr, const char* itrEnd)
+{
+ for (; itr < itrEnd; itr++) {
+ if (*itr == '>') return itr;
+ }
+ return nullptr;
+}
+
+
+static SimpleXMLType _getXMLType(const char* itr, const char* itrEnd, size_t &toff)
+{
+ toff = 0;
+ if (itr[1] == '/') {
+ toff = 1;
+ return SimpleXMLType::Close;
+ } else if (itr[1] == '?') {
+ toff = 1;
+ return SimpleXMLType::Processing;
+ } else if (itr[1] == '!') {
+ if ((itr + sizeof("<!DOCTYPE>") - 1 < itrEnd) && (!memcmp(itr + 2, "DOCTYPE", sizeof("DOCTYPE") - 1)) && ((itr[2 + sizeof("DOCTYPE") - 1] == '>') || (isspace((unsigned char)itr[2 + sizeof("DOCTYPE") - 1])))) {
+ toff = sizeof("!DOCTYPE") - 1;
+ return SimpleXMLType::Doctype;
+ } else if (itr + sizeof("<!>") - 1 < itrEnd) {
+ toff = sizeof("!") - 1;
+ return SimpleXMLType::DoctypeChild;
+ } else if ((itr + sizeof("<![CDATA[]]>") - 1 < itrEnd) && (!memcmp(itr + 2, "[CDATA[", sizeof("[CDATA[") - 1))) {
+ toff = sizeof("![CDATA[") - 1;
+ return SimpleXMLType::CData;
+ } else if ((itr + sizeof("<!---->") - 1 < itrEnd) && (!memcmp(itr + 2, "--", sizeof("--") - 1))) {
+ toff = sizeof("!--") - 1;
+ return SimpleXMLType::Comment;
+ }
+ return SimpleXMLType::Open;
+ }
+ return SimpleXMLType::Open;
+}
+
+
+/************************************************************************/
+/* External Class Implementation */
+/************************************************************************/
+
+const char* simpleXmlNodeTypeToString(TVG_UNUSED SvgNodeType type)
+{
+#ifdef THORVG_LOG_ENABLED
+ static const char* TYPE_NAMES[] = {
+ "Svg",
+ "G",
+ "Defs",
+ "Animation",
+ "Arc",
+ "Circle",
+ "Ellipse",
+ "Image",
+ "Line",
+ "Path",
+ "Polygon",
+ "Polyline",
+ "Rect",
+ "Text",
+ "TextArea",
+ "Tspan",
+ "Use",
+ "Video",
+ "ClipPath",
+ "Mask",
+ "Unknown",
+ };
+ return TYPE_NAMES[(int) type];
+#endif
+ return nullptr;
+}
+
+
+bool isIgnoreUnsupportedLogElements(TVG_UNUSED const char* tagName)
+{
+#ifdef THORVG_LOG_ENABLED
+ const auto elementsNum = 1;
+ const char* const elements[] = { "title" };
+
+ for (unsigned int i = 0; i < elementsNum; ++i) {
+ if (!strncmp(tagName, elements[i], strlen(tagName))) {
+ return true;
+ }
+ }
+ return false;
+#else
+ return true;
+#endif
+}
+
+
+bool simpleXmlParseAttributes(const char* buf, unsigned bufLength, simpleXMLAttributeCb func, const void* data)
+{
+ const char *itr = buf, *itrEnd = buf + bufLength;
+ char* tmpBuf = (char*)alloca(bufLength + 1);
+
+ if (!buf || !func) return false;
+
+ while (itr < itrEnd) {
+ const char* p = _skipWhiteSpacesAndXmlEntities(itr, itrEnd);
+ const char *key, *keyEnd, *value, *valueEnd;
+ char* tval;
+
+ if (p == itrEnd) return true;
+
+ key = p;
+ for (keyEnd = key; keyEnd < itrEnd; keyEnd++) {
+ if ((*keyEnd == '=') || (isspace((unsigned char)*keyEnd))) break;
+ }
+ if (keyEnd == itrEnd) return false;
+ if (keyEnd == key) continue;
+
+ if (*keyEnd == '=') value = keyEnd + 1;
+ else {
+ value = (const char*)memchr(keyEnd, '=', itrEnd - keyEnd);
+ if (!value) return false;
+ value++;
+ }
+ keyEnd = _simpleXmlUnskipXmlEntities(keyEnd, key);
+
+ value = _skipWhiteSpacesAndXmlEntities(value, itrEnd);
+ if (value == itrEnd) return false;
+
+ if ((*value == '"') || (*value == '\'')) {
+ valueEnd = (const char*)memchr(value + 1, *value, itrEnd - value);
+ if (!valueEnd) return false;
+ value++;
+ } else {
+ valueEnd = _simpleXmlFindWhiteSpace(value, itrEnd);
+ }
+
+ itr = valueEnd + 1;
+
+ value = _skipWhiteSpacesAndXmlEntities(value, itrEnd);
+ valueEnd = _unskipWhiteSpacesAndXmlEntities(valueEnd, value);
+
+ memcpy(tmpBuf, key, keyEnd - key);
+ tmpBuf[keyEnd - key] = '\0';
+
+ tval = tmpBuf + (keyEnd - key) + 1;
+ int i = 0;
+ while (value < valueEnd) {
+ value = _simpleXmlSkipXmlEntities(value, valueEnd);
+ tval[i++] = *value;
+ value++;
+ }
+ tval[i] = '\0';
+
+ if (!func((void*)data, tmpBuf, tval)) {
+ if (!_isIgnoreUnsupportedLogAttributes(tmpBuf, tval)) {
+ TVGLOG("SVG", "Unsupported attributes used [Elements type: %s][Id : %s][Attribute: %s][Value: %s]", simpleXmlNodeTypeToString(((SvgLoaderData*)data)->svgParse->node->type), ((SvgLoaderData*)data)->svgParse->node->id ? ((SvgLoaderData*)data)->svgParse->node->id : "NO_ID", tmpBuf, tval ? tval : "NONE");
+ }
+ }
+ }
+ return true;
+}
+
+
+bool simpleXmlParse(const char* buf, unsigned bufLength, bool strip, simpleXMLCb func, const void* data)
+{
+ const char *itr = buf, *itrEnd = buf + bufLength;
+
+ if (!buf || !func) return false;
+
+ while (itr < itrEnd) {
+ if (itr[0] == '<') {
+ //Invalid case
+ if (itr + 1 >= itrEnd) return false;
+
+ size_t toff = 0;
+ SimpleXMLType type = _getXMLType(itr, itrEnd, toff);
+
+ const char* p;
+ if (type == SimpleXMLType::CData) p = _simpleXmlFindEndCdataTag(itr + 1 + toff, itrEnd);
+ else if (type == SimpleXMLType::DoctypeChild) p = _simpleXmlFindDoctypeChildEndTag(itr + 1 + toff, itrEnd);
+ else if (type == SimpleXMLType::Comment) p = _simpleXmlFindEndCommentTag(itr + 1 + toff, itrEnd);
+ else p = _simpleXmlFindEndTag(itr + 1 + toff, itrEnd);
+
+ if (p) {
+ //Invalid case: '<' nested
+ if (*p == '<') return false;
+ const char *start, *end;
+
+ start = itr + 1 + toff;
+ end = p;
+
+ switch (type) {
+ case SimpleXMLType::Open: {
+ if (p[-1] == '/') {
+ type = SimpleXMLType::OpenEmpty;
+ end--;
+ }
+ break;
+ }
+ case SimpleXMLType::CData: {
+ if (!memcmp(p - 2, "]]", 2)) end -= 2;
+ break;
+ }
+ case SimpleXMLType::Processing: {
+ if (p[-1] == '?') end--;
+ break;
+ }
+ case SimpleXMLType::Comment: {
+ if (!memcmp(p - 2, "--", 2)) end -= 2;
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+
+ if (strip && (type != SimpleXMLType::CData)) {
+ start = _skipWhiteSpacesAndXmlEntities(start, end);
+ end = _unskipWhiteSpacesAndXmlEntities(end, start);
+ }
+
+ if (!func((void*)data, type, start, (unsigned int)(end - start))) return false;
+
+ itr = p + 1;
+ } else {
+ return false;
+ }
+ } else {
+ const char *p, *end;
+
+ if (strip) {
+ p = itr;
+ p = _skipWhiteSpacesAndXmlEntities(p, itrEnd);
+ if (p) {
+ if (!func((void*)data, SimpleXMLType::Ignored, itr, (unsigned int)(p - itr))) return false;
+ itr = p;
+ }
+ }
+
+ p = _simpleXmlFindStartTag(itr, itrEnd);
+ if (!p) p = itrEnd;
+
+ end = p;
+ if (strip) end = _unskipWhiteSpacesAndXmlEntities(end, itr);
+
+ if (itr != end && !func((void*)data, SimpleXMLType::Data, itr, (unsigned int)(end - itr))) return false;
+
+ if (strip && (end < p) && !func((void*)data, SimpleXMLType::Ignored, end, (unsigned int)(p - end))) return false;
+
+ itr = p;
+ }
+ }
+ return true;
+}
+
+
+bool simpleXmlParseW3CAttribute(const char* buf, simpleXMLAttributeCb func, const void* data)
+{
+ const char* end;
+ char* key;
+ char* val;
+ char* next;
+
+ if (!buf) return false;
+
+ end = buf + strlen(buf);
+ key = (char*)alloca(end - buf + 1);
+ val = (char*)alloca(end - buf + 1);
+
+ if (buf == end) return true;
+
+ do {
+ char* sep = (char*)strchr(buf, ':');
+ next = (char*)strchr(buf, ';');
+
+ key[0] = '\0';
+ val[0] = '\0';
+
+ if (next == nullptr && sep != nullptr) {
+ memcpy(key, buf, sep - buf);
+ key[sep - buf] = '\0';
+
+ memcpy(val, sep + 1, end - sep - 1);
+ val[end - sep - 1] = '\0';
+ } else if (sep < next && sep != nullptr) {
+ memcpy(key, buf, sep - buf);
+ key[sep - buf] = '\0';
+
+ memcpy(val, sep + 1, next - sep - 1);
+ val[next - sep - 1] = '\0';
+ } else if (next) {
+ memcpy(key, buf, next - buf);
+ key[next - buf] = '\0';
+ }
+
+ if (key[0]) {
+ key = const_cast<char*>(_simpleXmlSkipWhiteSpace(key, key + strlen(key)));
+ key[_simpleXmlUnskipWhiteSpace(key + strlen(key) , key) - key] = '\0';
+ val = const_cast<char*>(_simpleXmlSkipWhiteSpace(val, val + strlen(val)));
+ val[_simpleXmlUnskipWhiteSpace(val + strlen(val) , val) - val] = '\0';
+
+ if (!func((void*)data, key, val)) {
+ if (!_isIgnoreUnsupportedLogAttributes(key, val)) {
+ TVGLOG("SVG", "Unsupported attributes used [Elements type: %s][Id : %s][Attribute: %s][Value: %s]", simpleXmlNodeTypeToString(((SvgLoaderData*)data)->svgParse->node->type), ((SvgLoaderData*)data)->svgParse->node->id ? ((SvgLoaderData*)data)->svgParse->node->id : "NO_ID", key, val ? val : "NONE");
+ }
+ }
+ }
+
+ buf = next + 1;
+ } while (next != nullptr);
+
+ return true;
+}
+
+
+const char* simpleXmlFindAttributesTag(const char* buf, unsigned bufLength)
+{
+ const char *itr = buf, *itrEnd = buf + bufLength;
+
+ for (; itr < itrEnd; itr++) {
+ if (!isspace((unsigned char)*itr)) {
+ //User skip tagname and already gave it the attributes.
+ if (*itr == '=') return buf;
+ } else {
+ itr = _simpleXmlUnskipXmlEntities(itr, buf);
+ if (itr == itrEnd) return nullptr;
+ return itr;
+ }
+ }
+
+ return nullptr;
+}
diff --git a/thirdparty/thorvg/src/loaders/svg/tvgXmlParser.h b/thirdparty/thorvg/src/loaders/svg/tvgXmlParser.h
new file mode 100644
index 0000000000..d96a631528
--- /dev/null
+++ b/thirdparty/thorvg/src/loaders/svg/tvgXmlParser.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef _TVG_SIMPLE_XML_PARSER_H_
+#define _TVG_SIMPLE_XML_PARSER_H_
+
+#include "tvgSvgLoaderCommon.h"
+
+#define NUMBER_OF_XML_ENTITIES 8
+const char* const xmlEntity[] = {"&quot;", "&nbsp;", "&apos;", "&amp;", "&lt;", "&gt;", "&#035;", "&#039;"};
+const int xmlEntityLength[] = {6, 6, 6, 5, 4, 4, 6, 6};
+
+enum class SimpleXMLType
+{
+ Open = 0, //!< \<tag attribute="value"\>
+ OpenEmpty, //!< \<tag attribute="value" /\>
+ Close, //!< \</tag\>
+ Data, //!< tag text data
+ CData, //!< \<![cdata[something]]\>
+ Error, //!< error contents
+ Processing, //!< \<?xml ... ?\> \<?php .. ?\>
+ Doctype, //!< \<!doctype html
+ Comment, //!< \<!-- something --\>
+ Ignored, //!< whatever is ignored by parser, like whitespace
+ DoctypeChild //!< \<!doctype_child
+};
+
+typedef bool (*simpleXMLCb)(void* data, SimpleXMLType type, const char* content, unsigned int length);
+typedef bool (*simpleXMLAttributeCb)(void* data, const char* key, const char* value);
+
+bool simpleXmlParseAttributes(const char* buf, unsigned buflen, simpleXMLAttributeCb func, const void* data);
+bool simpleXmlParse(const char* buf, unsigned buflen, bool strip, simpleXMLCb func, const void* data);
+bool simpleXmlParseW3CAttribute(const char* buf, simpleXMLAttributeCb func, const void* data);
+const char *simpleXmlFindAttributesTag(const char* buf, unsigned buflen);
+bool isIgnoreUnsupportedLogElements(const char* tagName);
+const char* simpleXmlNodeTypeToString(SvgNodeType type);
+
+#endif //_TVG_SIMPLE_XML_PARSER_H_