From 3a8d6821baf6889eccd834d6613d9bf434b36e09 Mon Sep 17 00:00:00 2001 From: JunsuChoi Date: Wed, 24 Jun 2020 09:55:25 +0900 Subject: [PATCH] SvgLoader: Implement SVG Loader and SimpleXMLParser Load svg using fstream and parse it using SimpleXMLparser. And Make a document tree composed of SvgNodes using the parsed data. Change-Id: I5715b466638195844798f7b66f54f6015e7c3ae6 --- src/loaders/svg_loader/meson.build | 3 + src/loaders/svg_loader/tvgSimpleXmlParser.cpp | 346 +++ src/loaders/svg_loader/tvgSimpleXmlParser.h | 31 + src/loaders/svg_loader/tvgSvgLoader.cpp | 2260 ++++++++++++++++- src/loaders/svg_loader/tvgSvgLoader.h | 7 +- src/loaders/svg_loader/tvgSvgLoaderCommon.h | 337 +++ 6 files changed, 2966 insertions(+), 18 deletions(-) create mode 100644 src/loaders/svg_loader/tvgSimpleXmlParser.cpp create mode 100644 src/loaders/svg_loader/tvgSimpleXmlParser.h create mode 100644 src/loaders/svg_loader/tvgSvgLoaderCommon.h diff --git a/src/loaders/svg_loader/meson.build b/src/loaders/svg_loader/meson.build index 1bd40c10..88fd37a6 100644 --- a/src/loaders/svg_loader/meson.build +++ b/src/loaders/svg_loader/meson.build @@ -1,5 +1,8 @@ source_file = [ + 'tvgSimpleXmlParser.h', 'tvgSvgLoader.h', + 'tvgSvgLoaderCommon.h', + 'tvgSimpleXmlParser.cpp', 'tvgSvgLoader.cpp', ] diff --git a/src/loaders/svg_loader/tvgSimpleXmlParser.cpp b/src/loaders/svg_loader/tvgSimpleXmlParser.cpp new file mode 100644 index 00000000..40814535 --- /dev/null +++ b/src/loaders/svg_loader/tvgSimpleXmlParser.cpp @@ -0,0 +1,346 @@ +#include "tvgSimpleXmlParser.h" + +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* _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; +} + + +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) return false; + if (!func) return false; + + while (itr < itrEnd) { + const char* p = _simpleXmlSkipWhiteSpace(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++; + } + for (; value < itrEnd; value++) { + if (!isspace((unsigned char)*value)) break; + } + 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); + } + + memcpy(tmpBuf, key, keyEnd - key); + tmpBuf[keyEnd - key] = '\0'; + + tval = tmpBuf + (keyEnd - key) + 1; + memcpy(tval, value, valueEnd - value); + tval[valueEnd - value] = '\0'; + + if (!func((void*)data, tmpBuf, tval)) return false; + + itr = valueEnd + 1; + } + 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) return false; + if (!func) return false; + +#define CB(type, start, end) \ + do { \ + size_t _sz = end - start; \ + bool _ret; \ + _ret = func((void*)data, type, start, start - buf, _sz); \ + if (!_ret) \ + return false; \ + } while (0) + + while (itr < itrEnd) { + if (itr[0] == '<') { + if (itr + 1 >= itrEnd) { + CB(SimpleXMLType::Error, itr, itrEnd); + return false; + } else { + SimpleXMLType type; + size_t toff; + const char* p; + + if (itr[1] == '/') { + type = SimpleXMLType::Close; + toff = 1; + } else if (itr[1] == '?') { + type = SimpleXMLType::Processing; + toff = 1; + } else if (itr[1] == '!') { + if ((itr + sizeof("") - 1 < itrEnd) && (!memcmp(itr + 2, "DOCTYPE", sizeof("DOCTYPE") - 1)) && ((itr[2 + sizeof("DOCTYPE") - 1] == '>') || (isspace((unsigned char)itr[2 + sizeof("DOCTYPE") - 1])))) { + type = SimpleXMLType::Doctype; + toff = sizeof("!DOCTYPE") - 1; + } else if ((itr + sizeof("") - 1 < itrEnd) && (!memcmp(itr + 2, "--", sizeof("--") - 1))) { + type = SimpleXMLType::Comment; + toff = sizeof("!--") - 1; + } else if ((itr + sizeof("") - 1 < itrEnd) && (!memcmp(itr + 2, "[CDATA[", sizeof("[CDATA[") - 1))) { + type = SimpleXMLType::CData; + toff = sizeof("![CDATA[") - 1; + } else if (itr + sizeof("") - 1 < itrEnd) { + type = SimpleXMLType::DoctypeChild; + toff = sizeof("!") - 1; + } else { + type = SimpleXMLType::Open; + toff = 0; + } + } else { + type = SimpleXMLType::Open; + toff = 0; + } + + 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) && (*p == '<')) { + type = SimpleXMLType::Error; + toff = 0; + } + + if (p) { + 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; + } + case SimpleXMLType::OpenEmpty: + case SimpleXMLType::Close: + case SimpleXMLType::Data: + case SimpleXMLType::Error: + case SimpleXMLType::Doctype: + case SimpleXMLType::DoctypeChild: + case SimpleXMLType::Ignored: { + break; + } + } + + if ((strip) && (type != SimpleXMLType::Error) && (type != SimpleXMLType::CData)) { + start = _simpleXmlSkipWhiteSpace(start, end); + end = _simpleXmlUnskipWhiteSpace(end, start + 1); + } + + CB(type, start, end); + + if (type != SimpleXMLType::Error) itr = p + 1; + else itr = p; + } else { + CB(SimpleXMLType::Error, itr, itrEnd); + return false; + } + } + } else { + const char *p, *end; + + if (strip) { + p = _simpleXmlSkipWhiteSpace(itr, itrEnd); + if (p) { + CB(SimpleXMLType::Ignored, itr, p); + itr = p; + } + } + + p = _simpleXmlFindStartTag(itr, itrEnd); + if (!p) p = itrEnd; + + end = p; + if (strip) end = _simpleXmlUnskipWhiteSpace(end, itr); + + if (itr != end) CB(SimpleXMLType::Data, itr, end); + + if ((strip) && (end < p)) CB(SimpleXMLType::Ignored, end, p); + + itr = p; + } + } + +#undef CB + + 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]) { + if (!func((void*)data, key, val)) return false; + } + + 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 = _simpleXmlSkipWhiteSpace(itr + 1, itrEnd); + if (itr == itrEnd) return nullptr; + return itr; + } + } + + return nullptr; +} + diff --git a/src/loaders/svg_loader/tvgSimpleXmlParser.h b/src/loaders/svg_loader/tvgSimpleXmlParser.h new file mode 100644 index 00000000..4e3b3a73 --- /dev/null +++ b/src/loaders/svg_loader/tvgSimpleXmlParser.h @@ -0,0 +1,31 @@ +#ifndef _TVG_SIMPLE_XML_PARSER_H_ +#define _TVG_SIMPLE_XML_PARSER_H_ + +#include +#include +#include + +enum class SimpleXMLType +{ + Open = 0, //!< \ + OpenEmpty, //!< \ + Close, //!< \ + Data, //!< tag text data + CData, //!< \ + Error, //!< error contents + Processing, //!< \ \ + Doctype, //!< \ + Ignored, //!< whatever is ignored by parser, like whitespace + DoctypeChild //!< \ /************************************************************************/ /* Internal Class Implementation */ /************************************************************************/ +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 != nullptr && str < end) || (end == nullptr && *str != '\0')) && isspace(*str)) + ++str; + return (char*)str; +} + + +static string* _copyId(const char* str) +{ + if (str == nullptr) return nullptr; + + return new string(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 = strtof(*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 + * + * TODO + * Since this documentation is not obvious, more clean recalculation with dpi + * is required, but for now default w3 constants would be used + */ +static float _toFloat(SvgParser* svgParse, const char* str, SvgParserLengthType type) +{ + float parsedValue = strtof(str, nullptr); + + if (strstr(str, "cm")) parsedValue = parsedValue * 35.43307; + else if (strstr(str, "mm")) parsedValue = parsedValue * 3.543307; + else if (strstr(str, "pt")) parsedValue = parsedValue * 1.25; + else if (strstr(str, "pc")) parsedValue = parsedValue * 15; + else if (strstr(str, "in")) parsedValue = parsedValue * 90; + 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 = svgParse->global.w; + if (max < svgParse->global.h) + max = svgParse->global.h; + parsedValue = (parsedValue / 100.0) * max; + } + } + + //TODO: Implement 'em', 'ex' attributes + + return parsedValue; +} + + +static float _gradientToFloat(SvgParser* svgParse, const char* str, SvgParserLengthType type) +{ + char* end = nullptr; + + float parsedValue = strtof(str, &end); + float max = 1; + + /** + * That is according to Units in here + * + * https://www.w3.org/TR/2015/WD-SVG2-20150915/coords.html + */ + if (type == SvgParserLengthType::Vertical) max = svgParse->global.h; + else if (type == SvgParserLengthType::Horizontal) max = svgParse->global.w; + else if (type == SvgParserLengthType::Other) max = sqrt(pow(svgParse->global.h, 2) + pow(svgParse->global.w, 2)) / sqrt(2.0); + + if (strstr(str, "%")) parsedValue = parsedValue / 100.0; + else if (strstr(str, "cm")) parsedValue = parsedValue * 35.43307; + else if (strstr(str, "mm")) parsedValue = parsedValue * 3.543307; + else if (strstr(str, "pt")) parsedValue = parsedValue * 1.25; + else if (strstr(str, "pc")) parsedValue = parsedValue * 15; + else if (strstr(str, "in")) parsedValue = parsedValue * 90; + //TODO: Implement 'em', 'ex' attributes + + //Transform into global percentage + parsedValue = parsedValue / max; + + return parsedValue; +} + + +static float _toOffset(const char* str) +{ + char* end = nullptr; + + float parsedValue = strtof(str, &end); + + if (strstr(str, "%")) parsedValue = parsedValue / 100.0; + + return parsedValue; +} + + +static int _toOpacity(const char* str) +{ + char* end = nullptr; + int a = 0; + float opacity = strtof(str, &end); + + if (end && (*end == '\0')) a = lrint(opacity * 255); + return a; +} + + +#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 +{ + SvgFillRule fillRule; + const char* tag; +} fillRuleTags[] = { + { SvgFillRule::OddEven, "evenodd" } +}; + + +_PARSE_TAG(SvgFillRule, fillRule, FillRule, fillRuleTags, SvgFillRule::Winding); + + +static string* _idFromUrl(const char* url) +{ + char tmp[50]; + int i = 0; + + url = _skipSpace(url, nullptr); + if ((*url) == '(') { + ++url; + url = _skipSpace(url, nullptr); + } + + if ((*url) == '#') ++url; + + while ((*url) != ')') { + tmp[i++] = *url; + ++url; + } + tmp[i] = '\0'; + + return new string(tmp); +} + + +static unsigned char _parserColor(const char* value, char** end) +{ + float r; + + r = strtof(value + 4, 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, string** ref) +{ + unsigned int i, 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 (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]); + } + } + } +} + + +static char* _parseNumbersArray(char* str, float* points, int* ptCount) +{ + int count = 0; + char* end = nullptr; + + str = _skipSpace(str, nullptr); + while (isdigit(*str) || *str == '-' || *str == '+' || *str == '.') { + points[count++] = strtof(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) +{ + float a11, a12, a13, a21, a22, a23, a31, a32, a33; + + a11 = (m1->e11 * m2->e11) + (m1->e12 * m2->e21) + (m1->e13 * m2->e31); + a12 = (m1->e11 * m2->e12) + (m1->e12 * m2->e22) + (m1->e13 * m2->e32); + a13 = (m1->e11 * m2->e13) + (m1->e12 * m2->e23) + (m1->e13 * m2->e33); + + a21 = (m1->e21 * m2->e11) + (m1->e22 * m2->e21) + (m1->e23 * m2->e31); + a22 = (m1->e21 * m2->e12) + (m1->e22 * m2->e22) + (m1->e23 * m2->e32); + a23 = (m1->e21 * m2->e13) + (m1->e22 * m2->e23) + (m1->e23 * m2->e33); + + a31 = (m1->e31 * m2->e11) + (m1->e32 * m2->e21) + (m1->e33 * m2->e31); + a32 = (m1->e31 * m2->e12) + (m1->e32 * m2->e22) + (m1->e33 * m2->e32); + 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) +{ + unsigned int i; + float points[8]; + int ptCount = 0; + float sx, sy; + MatrixState state = MatrixState::Unknown; + Matrix* matrix = (Matrix*)calloc(1, sizeof(Matrix)); + char* str = (char*)value; + char* end = str + strlen(str); + + *matrix = { 1, 0, 0, 0, 1, 0, 0, 0, 1 }; + while (str < end) { + if (isspace(*str) || (*str == ',')) { + ++str; + continue; + } + for (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); + } + } + if (state == MatrixState::Unknown) goto error; + + str = _skipSpace(str, end); + if (*str != '(') goto error; + ++str; + str = _parseNumbersArray(str, points, &ptCount); + if (*str != ')') goto error; + ++str; + + if (state == MatrixState::Matrix) { + Matrix tmp; + + if (ptCount != 6) goto error; + + 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) { + Matrix tmp; + + if (ptCount == 1) { + tmp = { 1, 0, points[0], 0, 1, 0, 0, 0, 1 }; + _matrixCompose(matrix, &tmp, matrix); + } else if (ptCount == 2) { + tmp = { 1, 0, points[0], 0, 1, points[1], 0, 0, 1 }; + _matrixCompose(matrix, &tmp, matrix); + } else goto error; + } else if (state == MatrixState::Rotate) { + Matrix tmp; + float c, s; + //Transform to signed. + points[0] = fmod(points[0], 360); + if (points[0] < 0) points[0] += 360; + + c = cosf(points[0] * (M_PI / 180.0)); + s = sinf(points[0] * (M_PI / 180.0)); + if (ptCount == 1) { + tmp = { c, -s, 0, s, c, 0, 0, 0, 1 }; + _matrixCompose(matrix, &tmp, matrix); + } else if (ptCount == 3) { + tmp = { 1, 0, points[0], 0, 1, points[1], 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[0], 0, 1, points[1], 0, 0, 1 }; + _matrixCompose(matrix, &tmp, matrix); + } else { + goto error; + } + } else if (state == MatrixState::Scale) { + Matrix tmp; + if (ptCount < 1 || ptCount > 2) goto error; + + sx = points[0]; + sy = sx; + if (ptCount == 2) sy = points[1]; + tmp = { sx, 0, 0, 0, sy, 0, 0, 0, 1 }; + _matrixCompose(matrix, &tmp, matrix); + } + } +error: + return matrix; +} + + +#define LENGTH_DEF(Name, Value) \ + { \ +#Name, sizeof(#Name), Value \ + } + + +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) +{ + unsigned int i; + float value; + int sz = strlen(str); + + *type = SvgLengthType::Px; + for (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 = strtof(str, nullptr); + return value; +} + + +static bool _parseStyleAttr(void* data, const char* key, const char* value); + + +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); + SvgLengthType type; + + //@TODO handle lenght unit. + if (!strcmp(key, "width")) { + doc->w = _parseLength(value, &type); + } else if (!strcmp(key, "height")) { + doc->h = _parseLength(value, &type); + } 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 = doc->vh; + } + loader->svgParse->global.w = doc->vw; + } + loader->svgParse->global.y = doc->vy; + } + loader->svgParse->global.x = doc->vx; + } else if (!strcmp(key, "preserveAspectRatio")) { + if (!strcmp(value, "none")) doc->preserveAspect = false; + } else if (!strcmp(key, "style")) { + return simpleXmlParseW3CAttribute(value, _parseStyleAttr, loader); + } else { + return _parseStyleAttr(loader, key, value); + } + return true; +} + + +//https://www.w3.org/TR/SVGTiny12/painting.html#SpecifyingPaint +static void _handlePaintAttr(SvgLoaderData* loader, 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->r, &paint->g, &paint->b, &paint->url); +} + + +static void _handleColorAttr(SvgLoaderData* loader, SvgNode* node, const char* value) +{ + SvgStyleProperty* style = node->style; + _toColor(value, &style->r, &style->g, &style->b, nullptr); +} + + +static void _handleFillAttr(SvgLoaderData* loader, SvgNode* node, const char* value) +{ + SvgStyleProperty* style = node->style; + style->fill.flags = (SvgFillFlags)((int)style->fill.flags | (int)SvgFillFlags::Paint); + _handlePaintAttr(loader, &style->fill.paint, value); +} + + +static void _handleStrokeAttr(SvgLoaderData* loader, SvgNode* node, const char* value) +{ + SvgStyleProperty* style = node->style; + style->stroke.flags = (SvgStrokeFlags)((int)style->stroke.flags | (int)SvgStrokeFlags::Paint); + _handlePaintAttr(loader, &style->stroke.paint, value); +} + + +static void _handleStrokeOpacityAttr(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 _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(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(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(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(SvgLoaderData* loader, SvgNode* node, const char* value) +{ + node->style->opacity = _toOpacity(value); +} + + +static void _handleFillOpacityAttr(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(SvgLoaderData* loader, SvgNode* node, const char* value) +{ + node->transform = _parseTransformationMatrix(value); +} + + +static void _handleDisplayAttr(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) \ + { \ +#Name, sizeof(#Name), _handle##Name1##Attr \ + } + + +static constexpr struct +{ + const char* tag; + int sz; + styleMethod tagHandler; +} styleTags[] = { + STYLE_DEF(color, Color), + STYLE_DEF(fill, Fill), + STYLE_DEF(fill-rule, FillRule), + STYLE_DEF(fill-opacity, FillOpacity), + STYLE_DEF(opacity, Opacity), + STYLE_DEF(stroke, Stroke), + STYLE_DEF(stroke-width, StrokeWidth), + STYLE_DEF(stroke-linejoin, StrokeLineJoin), + STYLE_DEF(stroke-linecap, StrokeLineCap), + STYLE_DEF(stroke-opacity, StrokeOpacity), + STYLE_DEF(transform, Transform), + STYLE_DEF(display, Display) +}; + + +static bool _parseStyleAttr(void* data, const char* key, const char* value) +{ + SvgLoaderData* loader = (SvgLoaderData*)data; + SvgNode* node = loader->svgParse->node; + unsigned int i; + int sz; + if (!key || !value) return false; + + //Trim the white space + key = _skipSpace(key, nullptr); + + value = _skipSpace(value, nullptr); + + sz = strlen(key); + for (i = 0; i < sizeof(styleTags) / sizeof(styleTags[0]); i++) { + if (styleTags[i].sz - 1 == sz && !strncmp(styleTags[i].tag, key, sz)) { + styleTags[i].tagHandler(loader, node, value); + return true; + } + } + + return 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")) { + node->id = _copyId(value); + } else { + return _parseStyleAttr(loader, key, value); + } + return true; +} + + +static SvgNode* _createNode(SvgNode* parent, SvgNodeType type) +{ + SvgNode* node = (SvgNode*)calloc(1, sizeof(SvgNode)); + + //Default fill property + node->style = (SvgStyleProperty*)calloc(1, sizeof(SvgStyleProperty)); + + //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 fill rule is nonzero + node->style->fill.fillRule = SvgFillRule::Winding; + + //Default stroke is none + node->style->stroke.paint.none = true; + //Default stroke opacity is 1 + node->style->stroke.opacity = 255; + //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_back(node); + return node; +} + + +static SvgNode* _createDefsNode(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength) +{ + SvgNode* node = _createNode(nullptr, SvgNodeType::Defs); + simpleXmlParseAttributes(buf, bufLength, nullptr, node); + return node; +} + + +static SvgNode* _createGNode(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength) +{ + loader->svgParse->node = _createNode(parent, SvgNodeType::G); + + 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); + SvgDocNode* doc = &(loader->svgParse->node->node.doc); + + doc->preserveAspect = true; + simpleXmlParseAttributes(buf, bufLength, _attrParseSvgNode, 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, "id")) { + node->id = _copyId(value); + } else { + return _parseStyleAttr(loader, key, value); + } + return true; +} + + +static SvgNode* _createPathNode(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength) +{ + loader->svgParse->node = _createNode(parent, SvgNodeType::Path); + + simpleXmlParseAttributes(buf, bufLength, _attrParsePathNode, loader); + + return loader->svgParse->node; +} + + +#define CIRCLE_DEF(Name, Field, Type) \ + { \ +#Name, Type, sizeof(#Name), offsetof(SvgCircleNode, Field) \ + } + + +static constexpr struct +{ + const char* tag; + SvgParserLengthType type; + int sz; + size_t offset; +} circleTags[] = { + CIRCLE_DEF(cx, cx, SvgParserLengthType::Horizontal), + CIRCLE_DEF(cy, cy, SvgParserLengthType::Vertical), + CIRCLE_DEF(r, r, SvgParserLengthType::Other) +}; + + +/* 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 int i; + unsigned char* array; + int sz = strlen(key); + + array = (unsigned char*)circle; + for (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, "id")) { + node->id = _copyId(value); + } else { + return _parseStyleAttr(loader, key, value); + } + return true; +} + + +static SvgNode* _createCircleNode(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength) +{ + loader->svgParse->node = _createNode(parent, SvgNodeType::Circle); + + simpleXmlParseAttributes(buf, bufLength, _attrParseCircleNode, loader); + return loader->svgParse->node; +} + + +#define ELLIPSE_DEF(Name, Field, Type) \ + { \ +#Name, Type, sizeof(#Name), offsetof(SvgEllipseNode, Field) \ + } + + +static constexpr struct +{ + const char* tag; + SvgParserLengthType type; + int sz; + size_t offset; +} ellipseTags[] = { + ELLIPSE_DEF(cx, cx, SvgParserLengthType::Horizontal), + ELLIPSE_DEF(cy, cy, SvgParserLengthType::Vertical), + ELLIPSE_DEF(rx, rx, SvgParserLengthType::Horizontal), + ELLIPSE_DEF(ry, ry, SvgParserLengthType::Vertical) +}; + + +/* 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 int i; + unsigned char* array; + int sz = strlen(key); + + array = (unsigned char*)ellipse; + for (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")) { + node->id = _copyId(value); + } else if (!strcmp(key, "style")) { + return simpleXmlParseW3CAttribute(value, _parseStyleAttr, loader); + } else { + return _parseStyleAttr(loader, key, value); + } + return true; +} + + +static SvgNode* _createEllipseNode(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength) +{ + loader->svgParse->node = _createNode(parent, SvgNodeType::Ellipse); + + simpleXmlParseAttributes(buf, bufLength, _attrParseEllipseNode, loader); + return loader->svgParse->node; +} + + +static void _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; + +error_alloc: + printf("ERR : allocation for point array failed. out of memory"); + abort(); +} + + +/* 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")) { + _attrParsePolygonPoints(value, &polygon->points, &polygon->pointsCount); + } else if (!strcmp(key, "style")) { + return simpleXmlParseW3CAttribute(value, _parseStyleAttr, loader); + } else if (!strcmp(key, "id")) { + node->id = _copyId(value); + } else { + return _parseStyleAttr(loader, key, value); + } + return true; +} + + +static SvgNode* _createPolygonNode(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength) +{ + loader->svgParse->node = _createNode(parent, SvgNodeType::Polygon); + + 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); + + simpleXmlParseAttributes(buf, bufLength, _attrParsePolygonNode, loader); + return loader->svgParse->node; +} + + +#define RECT_DEF(Name, Field, Type) \ + { \ +#Name, Type, sizeof(#Name), offsetof(SvgRectNode, Field) \ + } + + +static constexpr struct +{ + const char* tag; + SvgParserLengthType type; + int sz; + size_t offset; +} rectTags[] = { + RECT_DEF(x, x, SvgParserLengthType::Horizontal), + RECT_DEF(y, y, SvgParserLengthType::Vertical), + RECT_DEF(w, w, SvgParserLengthType::Horizontal), + RECT_DEF(h, h, SvgParserLengthType::Vertical), + RECT_DEF(rx, rx, SvgParserLengthType::Horizontal), + RECT_DEF(ry, ry, SvgParserLengthType::Vertical) +}; + + +/* 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 int i; + unsigned char* array; + bool ret = true; + int sz = strlen(key); + + array = (unsigned char*)rect; + for (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); + return ret; + } + } + + + if (!strcmp(key, "id")) { + node->id = _copyId(value); + } else if (!strcmp(key, "style")) { + ret = simpleXmlParseW3CAttribute(value, _parseStyleAttr, loader); + } else { + ret = _parseStyleAttr(loader, key, value); + } + + if (!(rect->rx - 0 <= FLT_EPSILON) && (rect->ry - 0 <= FLT_EPSILON)) rect->ry = rect->rx; + if (!(rect->ry - 0 <= FLT_EPSILON) && (rect->rx - 0 <= FLT_EPSILON)) rect->rx = rect->ry; + + return ret; +} + + +static SvgNode* _createRectNode(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength) +{ + loader->svgParse->node = _createNode(parent, SvgNodeType::Rect); + + simpleXmlParseAttributes(buf, bufLength, _attrParseRectNode, loader); + return loader->svgParse->node; +} + + +#define LINE_DEF(Name, Field, Type) \ + { \ +#Name, Type, sizeof(#Name), offsetof(SvgLineNode, Field) \ + } + + +static constexpr struct +{ + const char* tag; + SvgParserLengthType type; + int sz; + size_t offset; +} lineTags[] = { + LINE_DEF(x1, x1, SvgParserLengthType::Horizontal), + LINE_DEF(y1, y1, SvgParserLengthType::Vertical), + LINE_DEF(x2, x2, SvgParserLengthType::Horizontal), + LINE_DEF(y2, y2, SvgParserLengthType::Vertical) +}; + + +/* parse the attributes for a rect 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 int i; + unsigned char* array; + int sz = strlen(key); + + array = (unsigned char*)line; + for (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")) { + node->id = _copyId(value); + } else if (!strcmp(key, "style")) { + return simpleXmlParseW3CAttribute(value, _parseStyleAttr, loader); + } else { + return _parseStyleAttr(loader, key, value); + } + return true; +} + + +static SvgNode* _createLineNode(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength) +{ + loader->svgParse->node = _createNode(parent, SvgNodeType::Line); + + simpleXmlParseAttributes(buf, bufLength, _attrParseLineNode, loader); + return loader->svgParse->node; +} + + +static string* _idFromHref(const char* href) +{ + href = _skipSpace(href, nullptr); + if ((*href) == '#') href++; + return new string(href); +} + + +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(SvgNode* node, const char* id) +{ + + if (!node) return nullptr; + + for (vector::iterator itrChild = node->child.begin(); itrChild != node->child.end(); itrChild++) { + if (((*itrChild)->id != nullptr) && !strcmp((*itrChild)->id->c_str(), id)) return *itrChild; + } + return nullptr; +} + + +static vector _cloneGradStops(vector src) +{ + vector dst; + copy(src.begin(), src.end(), dst.begin()); + return dst; +} + + +static SvgStyleGradient* _cloneGradient(SvgStyleGradient* from) +{ + SvgStyleGradient* grad; + + if (!from) return nullptr; + + grad = (SvgStyleGradient*)calloc(1, sizeof(SvgStyleGradient)); + grad->type = from->type; + grad->id = _copyId(from->id->c_str()); + grad->ref = _copyId(from->ref->c_str()); + grad->spread = from->spread; + grad->usePercentage = from->usePercentage; + grad->userSpace = from->userSpace; + if (from->transform) { + grad->transform = (Matrix*)calloc(1, sizeof(Matrix)); + memcpy(grad->transform, from->transform, sizeof(Matrix)); + } + grad->stops = _cloneGradStops(from->stops); + if (grad->type == SvgGradientType::Linear) { + grad->linear = (SvgLinearGradient*)calloc(1, sizeof(SvgLinearGradient)); + memcpy(grad->linear, from->linear, sizeof(SvgLinearGradient)); + } else if (grad->type == SvgGradientType::Radial) { + grad->radial = (SvgRadialGradient*)calloc(1, sizeof(SvgRadialGradient)); + memcpy(grad->radial, from->radial, sizeof(SvgRadialGradient)); + } + + return grad; +} + + +static void _copyAttr(SvgNode* to, SvgNode* from) +{ + //Copy matrix attribute + if (from->transform) { + to->transform = (Matrix*)calloc(1, sizeof(Matrix)); + memcpy(to->transform, from->transform, sizeof(Matrix)); + } + //Copy style attribute; + memcpy(to->style, from->style, sizeof(SvgStyleProperty)); + + //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; + 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: { + to->node.path.path = new string(from->node.path.path->c_str()); + break; + } + case SvgNodeType::Polygon: { + to->node.polygon.pointsCount = from->node.polygon.pointsCount; + to->node.polygon.points = (float*)calloc(to->node.polygon.pointsCount, sizeof(float)); + break; + } + case SvgNodeType::Polyline: { + to->node.polyline.pointsCount = from->node.polyline.pointsCount; + to->node.polyline.points = (float*)calloc(to->node.polyline.pointsCount, sizeof(float)); + break; + } + default: { + break; + } + } +} + + +static void _cloneNode(SvgNode* from, SvgNode* parent) +{ + SvgNode* newNode; + if (!from) return; + + newNode = _createNode(parent, from->type); + _copyAttr(newNode, from); + + for (vector::iterator itrChild = from->child.begin(); itrChild != from->child.end(); itrChild++) { + _cloneNode(*itrChild, newNode); + } +} + + +static bool _attrParseUseNode(void* data, const char* key, const char* value) +{ + SvgLoaderData* loader = (SvgLoaderData*)data; + SvgNode *defs, *nodeFrom, *node = loader->svgParse->node; + string* id; + + if (!strcmp(key, "xlink:href")) { + id = _idFromHref(value); + defs = _getDefsNode(node); + nodeFrom = _findChildById(defs, id->c_str()); + _cloneNode(nodeFrom, node); + delete id; + } else { + _attrParseGNode(data, key, value); + } + return true; +} + + +static SvgNode* _createUseNode(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength) +{ + loader->svgParse->node = _createNode(parent, SvgNodeType::G); + + simpleXmlParseAttributes(buf, bufLength, _attrParseUseNode, loader); + return loader->svgParse->node; +} + + +#define TAG_DEF(Name, Name1) \ + { \ +#Name, sizeof(#Name), _create##Name1##Node \ + } + + +//TODO: Implement 'text' primitive +static constexpr struct +{ + const char* tag; + int sz; + FactoryMethod tagHandler; +} graphicsTags[] = { + TAG_DEF(use, Use), + TAG_DEF(circle, Circle), + TAG_DEF(ellipse, Ellipse), + TAG_DEF(path, Path), + TAG_DEF(polygon, Polygon), + TAG_DEF(rect, Rect), + TAG_DEF(polyline, Polyline), + TAG_DEF(line, Line), +}; + + +static constexpr struct +{ + const char* tag; + int sz; + FactoryMethod tagHandler; +} groupTags[] = { + TAG_DEF(defs, Defs), + TAG_DEF(g, G), + TAG_DEF(svg, Svg), +}; + + +#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); + + +SvgGradientSpread _parseSpreadValue(const char* value) +{ + SvgGradientSpread spread = SvgGradientSpread::Pad; + + if (!strcmp(value, "reflect")) { + spread = SvgGradientSpread::Reflect; + } else if (!strcmp(value, "repeat")) { + spread = SvgGradientSpread::Repeat; + } + + return spread; +} + + +static void _handleRadialCxAttr(SvgLoaderData* loader, SvgRadialGradient* radial, const char* value) +{ + radial->cx = _gradientToFloat(loader->svgParse, value, SvgParserLengthType::Horizontal); + if (!loader->svgParse->gradient.parsedFx) radial->fx = radial->cx; +} + + +static void _handleRadialCyAttr(SvgLoaderData* loader, SvgRadialGradient* radial, const char* value) +{ + radial->cy = _gradientToFloat(loader->svgParse, value, SvgParserLengthType::Vertical); + if (!loader->svgParse->gradient.parsedFy) radial->fy = radial->cy; +} + + +static void _handleRadialFxAttr(SvgLoaderData* loader, SvgRadialGradient* radial, const char* value) +{ + radial->fx = _gradientToFloat(loader->svgParse, value, SvgParserLengthType::Horizontal); + loader->svgParse->gradient.parsedFx = true; +} + + +static void _handleRadialFyAttr(SvgLoaderData* loader, SvgRadialGradient* radial, const char* value) +{ + radial->fy = _gradientToFloat(loader->svgParse, value, SvgParserLengthType::Vertical); + loader->svgParse->gradient.parsedFy = true; +} + + +static void _handleRadialRAttr(SvgLoaderData* loader, SvgRadialGradient* radial, const char* value) +{ + radial->r = _gradientToFloat(loader->svgParse, value, SvgParserLengthType::Other); +} + + +static void _recalcRadialCxAttr(SvgLoaderData* loader, SvgRadialGradient* radial, bool userSpace) +{ + if (!userSpace) radial->cx = radial->cx * loader->svgParse->global.w; +} + + +static void _recalcRadialCyAttr(SvgLoaderData* loader, SvgRadialGradient* radial, bool userSpace) +{ + if (!userSpace) radial->cy = radial->cy * loader->svgParse->global.h; +} + + +static void _recalcRadialFxAttr(SvgLoaderData* loader, SvgRadialGradient* radial, bool userSpace) +{ + if (!userSpace) radial->fx = radial->fx * loader->svgParse->global.w; +} + + +static void _recalcRadialFyAttr(SvgLoaderData* loader, SvgRadialGradient* radial, bool userSpace) +{ + if (!userSpace) radial->fy = radial->fy * loader->svgParse->global.h; +} + + +static void _recalcRadialRAttr(SvgLoaderData* loader, SvgRadialGradient* radial, bool userSpace) +{ + if (!userSpace) radial->r = radial->r * (sqrt(pow(loader->svgParse->global.h, 2) + pow(loader->svgParse->global.w, 2)) / sqrt(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; + unsigned int i; + int sz = strlen(key); + + for (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, "xlink:href")) { + grad->ref = _idFromHref(value); + } else if (!strcmp(key, "gradientUnits") && !strcmp(value, "userSpaceOnUse")) { + grad->userSpace = true; + } + + return true; +} + + +static SvgStyleGradient* _createRadialGradient(SvgLoaderData* loader, const char* buf, unsigned bufLength) +{ + unsigned int i = 0; + SvgStyleGradient* grad = (SvgStyleGradient*)calloc(1, sizeof(SvgStyleGradient)); + loader->svgParse->styleGrad = grad; + + grad->type = SvgGradientType::Radial; + grad->userSpace = false; + grad->radial = (SvgRadialGradient*)calloc(1, sizeof(SvgRadialGradient)); + /** + * Default values of gradient + */ + grad->radial->cx = 0.5; + grad->radial->cy = 0.5; + grad->radial->fx = 0.5; + grad->radial->fy = 0.5; + grad->radial->r = 0.5; + + loader->svgParse->gradient.parsedFx = false; + loader->svgParse->gradient.parsedFy = false; + simpleXmlParseAttributes(buf, bufLength, + _attrParseRadialGradientNode, loader); + + for (i = 0; i < sizeof(radialTags) / sizeof(radialTags[0]); i++) { + radialTags[i].tagRecalc(loader, grad->radial, grad->userSpace); + } + + grad->usePercentage = true; + + return loader->svgParse->styleGrad; +} + + +static bool _attrParseStops(void* data, const char* key, const char* value) +{ + SvgLoaderData* loader = (SvgLoaderData*)data; + SvgGradientStop* stop = loader->svgParse->gradStop; + + if (!strcmp(key, "offset")) { + stop->offset = _toOffset(value); + } else if (!strcmp(key, "stop-opacity")) { + stop->a = _toOpacity(value); + } else if (!strcmp(key, "stop-color")) { + _toColor(value, &stop->r, &stop->g, &stop->b, nullptr); + } else if (!strcmp(key, "style")) { + simpleXmlParseW3CAttribute(value, + _attrParseStops, data); + } + + return true; +} + + +static void _handleLinearX1Attr(SvgLoaderData* loader, SvgLinearGradient* linear, const char* value) +{ + linear->x1 = _gradientToFloat(loader->svgParse, value, SvgParserLengthType::Horizontal); +} + + +static void _handleLinearY1Attr(SvgLoaderData* loader, SvgLinearGradient* linear, const char* value) +{ + linear->y1 = _gradientToFloat(loader->svgParse, value, SvgParserLengthType::Vertical); +} + + +static void _handleLinearX2Attr(SvgLoaderData* loader, SvgLinearGradient* linear, const char* value) +{ + linear->x2 = _gradientToFloat(loader->svgParse, value, SvgParserLengthType::Horizontal); +} + + +static void _handleLinearY2Attr(SvgLoaderData* loader, SvgLinearGradient* linear, const char* value) +{ + linear->y2 = _gradientToFloat(loader->svgParse, value, SvgParserLengthType::Vertical); +} + + +static void _recalcLinearX1Attr(SvgLoaderData* loader, SvgLinearGradient* linear, bool userSpace) +{ + if (!userSpace) linear->x1 = linear->x1 * loader->svgParse->global.w; +} + + +static void _recalcLinearY1Attr(SvgLoaderData* loader, SvgLinearGradient* linear, bool userSpace) +{ + if (!userSpace) linear->y1 = linear->y1 * loader->svgParse->global.h; +} + + +static void _recalcLinearX2Attr(SvgLoaderData* loader, SvgLinearGradient* linear, bool userSpace) +{ + if (!userSpace) linear->x2 = linear->x2 * loader->svgParse->global.w; +} + + +static void _recalcLinearY2Attr(SvgLoaderData* loader, SvgLinearGradient* linear, bool userSpace) +{ + if (!userSpace) 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; + unsigned int i; + int sz = strlen(key); + + for (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, "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); + } + + return true; +} + + +static SvgStyleGradient* _createLinearGradient(SvgLoaderData* loader, const char* buf, unsigned bufLength) +{ + SvgStyleGradient* grad = (SvgStyleGradient*)calloc(1, sizeof(SvgStyleGradient)); + loader->svgParse->styleGrad = grad; + unsigned int i; + + grad->type = SvgGradientType::Linear; + grad->userSpace = false; + grad->linear = (SvgLinearGradient*)calloc(1, sizeof(SvgLinearGradient)); + /** + * Default value of x2 is 100% + */ + grad->linear->x2 = 1; + simpleXmlParseAttributes(buf, bufLength, _attrParseLinearGradientNode, loader); + + for (i = 0; i < sizeof(linear_tags) / sizeof(linear_tags[0]); i++) { + linear_tags[i].tagRecalc(loader, grad->linear, grad->userSpace); + } + + grad->usePercentage = true; + + return loader->svgParse->styleGrad; +} + + +#define GRADIENT_DEF(Name, Name1) \ + { \ +#Name, sizeof(#Name), _create##Name1 \ + } + + +/** + * For all Gradients lengths would be calculated into percentages related to + * canvas width and height. + * + * if user then recalculate actual pixels into percentages + */ +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) +{ + unsigned int i; + int sz = strlen(name); + + for (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; +} + + +#define POP_TAG(Tag) \ + { \ +#Tag, sizeof(#Tag) \ + } + + +static constexpr struct +{ + const char* tag; + size_t sz; +} popArray[] = { + POP_TAG(g), + POP_TAG(svg), + POP_TAG(defs) +}; + + +static void _svgLoaderParerXmlClose(SvgLoaderData* loader, const char* content, unsigned int length) +{ + unsigned int i; + + content = _skipSpace(content, nullptr); + + for (i = 0; i < sizeof(popArray) / sizeof(popArray[0]); i++) { + if (!strncmp(content, popArray[i].tag, popArray[i].sz - 1)) { + loader->stack.pop_back(); + break; + } + } + + loader->level--; +} + + +static void _svgLoaderParserXmlOpen(SvgLoaderData* loader, const char* content, unsigned int length) +{ + 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; + attrsLength = length - sz; + while ((sz > 0) && (isspace(content[sz - 1]))) sz--; + strncpy(tagName, content, sz); + tagName[sz] = '\0'; + } + + 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 (loader->stack.size() > 0) parent = loader->stack.at(loader->stack.size() - 1); + node = method(loader, parent, attrs, attrsLength); + } + loader->stack.push_back(node); + + if (node->type == SvgNodeType::Defs) { + loader->doc->node.doc.defs = node; + loader->def = node; + } + } else if ((method = _findGraphicsFactory(tagName))) { + parent = loader->stack.at(loader->stack.size() - 1); + 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->doc->node.doc.defs) { + loader->def->node.defs.gradients.push_back(gradient); + } else { + loader->gradients.push_back(gradient); + } + loader->latestGradient = gradient; + } else if (!strcmp(tagName, "stop")) { + SvgGradientStop* stop = (SvgGradientStop*)calloc(1, sizeof(SvgGradientStop)); + loader->svgParse->gradStop = stop; + /* default value for opacity */ + stop->a = 255; + simpleXmlParseAttributes(attrs, attrsLength, _attrParseStops, loader); + if (loader->latestGradient) { + loader->latestGradient->stops.push_back(stop); + } + } +} + + +static bool _svgLoaderParser(void* data, SimpleXMLType type, const char* content, unsigned int offset, unsigned int length) +{ + SvgLoaderData* loader = (SvgLoaderData*)data; + + switch (type) { + case SimpleXMLType::Open: { + _svgLoaderParserXmlOpen(loader, content, length); + break; + } + case SimpleXMLType::OpenEmpty: { + _svgLoaderParserXmlOpen(loader, content, length); + break; + } + case SimpleXMLType::Close: { + _svgLoaderParerXmlClose(loader, content, length); + 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, 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.r = parent->fill.paint.r; + child->fill.paint.g = parent->fill.paint.g; + child->fill.paint.b = parent->fill.paint.b; + child->fill.paint.none = parent->fill.paint.none; + child->fill.paint.curColor = parent->fill.paint.curColor; + child->fill.paint.url = parent->fill.paint.url ? _copyId(parent->fill.paint.url->c_str()) : nullptr; + } + 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.r = parent->stroke.paint.r; + child->stroke.paint.g = parent->stroke.paint.g; + child->stroke.paint.b = parent->stroke.paint.b; + 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->c_str()) : nullptr; + } + 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::Cap)) { + child->stroke.cap = parent->stroke.cap; + } + if (!((int)child->stroke.flags & (int)SvgStrokeFlags::Join)) { + child->stroke.join = parent->stroke.join; + } +} + + +static void _updateStyle(SvgNode* node, SvgStyleProperty* parentStyle) +{ + _styleInherit(node->style, parentStyle); + + for (vector::iterator itrChild = node->child.begin(); itrChild != node->child.end(); itrChild++) { + _updateStyle(*itrChild, node->style); + } +} + + +static SvgStyleGradient* _gradientDup(vector gradList, const char* id) +{ + SvgStyleGradient* result = nullptr; + + for (vector::iterator itrGrad = gradList.begin(); itrGrad != gradList.end(); itrGrad++) { + if ((*itrGrad)->id->compare(string(id))) { + result = _cloneGradient(*itrGrad); + break; + } + } + + if (result && result->ref) { + for (vector::iterator itrGrad = gradList.begin(); itrGrad != gradList.end(); itrGrad++) { + if ((*itrGrad)->id->compare(*result->ref)) { + if (!result->stops.empty()) { + result->stops = _cloneGradStops((*itrGrad)->stops); + } + //TODO: Properly inherit other property + break; + } + } + } + + return result; +} + + +static void _updateGradient(SvgNode* node, vector gradList) +{ + if (node->child.empty()) { + for (vector::iterator itrChild = node->child.begin(); itrChild != node->child.end(); itrChild++) { + _updateGradient(*itrChild, gradList); + } + } else { + if (node->style->fill.paint.url) { + node->style->fill.paint.gradient = _gradientDup(gradList, node->style->fill.paint.url->c_str()); + } else if (node->style->stroke.paint.url) { + node->style->stroke.paint.gradient = _gradientDup(gradList, node->style->stroke.paint.url->c_str()); + } + } +} + +static void _freeGradientStyle(SvgStyleGradient* grad) +{ + if (!grad) return; + + delete grad->id; + delete grad->ref; + free(grad->radial); + free(grad->linear); + if (grad->transform) free(grad->transform); + + for(vector::iterator itrStop = grad->stops.begin(); itrStop != grad->stops.end(); itrStop++) { + free(*itrStop); + } + free(grad); +} + +static void _freeNodeStyle(SvgStyleProperty* style) +{ + if (!style) return; + + _freeGradientStyle(style->fill.paint.gradient); + delete style->fill.paint.url; + _freeGradientStyle(style->stroke.paint.gradient); + delete style->stroke.paint.url; + free(style); +} + +static void _freeSvgNode(SvgNode* node) +{ + if (!node) return; + + for(vector::iterator itrChild = node->child.begin(); itrChild != node->child.end(); itrChild++) { + _freeSvgNode(*itrChild); + } + node->child.clear(); + + delete node->id; + free(node->transform); + _freeNodeStyle(node->style); + switch (node->type) { + case SvgNodeType::Path: { + delete 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: { + _freeSvgNode(node->node.doc.defs); + break; + } + case SvgNodeType::Defs: { + for(vector::iterator itrGrad = node->node.defs.gradients.begin(); itrGrad != node->node.defs.gradients.end(); itrGrad++) { + _freeGradientStyle(*itrGrad); + } + break; + } + default: { + break; + } + } + if (!node->child.empty()) node->child.clear(); + free(node); +} /************************************************************************/ /* External Class Implementation */ /************************************************************************/ + SvgLoader::SvgLoader() { - cout << "SvgLoader()" << endl; } SvgLoader::~SvgLoader() { - cout << "~SvgLoader()" << endl; } bool SvgLoader::open(const char* path) { - //TODO: - cout << "SvgLoader::open()" << endl; + ifstream f; + f.open(path); - return false; + if (!f.is_open()) + { + cout << "ERROR: Failed to open file = " << path; + return false; + } else { + getline(f, content, '\0'); + f.close(); + + if (content.empty()) return false; + } + + return true; } bool SvgLoader::read() { - //TODO: - cout << "SvgLoader::read()" << endl; + if (content.empty()) return false; - return false; + loaderData = {vector(), + nullptr, + nullptr, + vector(), + nullptr, + nullptr, + 0, + false}; + + loaderData.svgParse = (SvgParser*)malloc(sizeof(SvgParser)); + + bool res = simpleXmlParse(content.c_str(), content.size(), true, _svgLoaderParser, &loaderData); + + if (loaderData.doc) { + SvgNode *defs; + _updateStyle(loaderData.doc, nullptr); + defs = loaderData.doc->node.doc.defs; + if (defs) _updateGradient(loaderData.doc, defs->node.defs.gradients); + else { + if (!loaderData.gradients.empty()) { + vector gradientList; + std::copy(loaderData.gradients.begin(), loaderData.gradients.end(), gradientList.begin()); + _updateGradient(loaderData.doc, gradientList); + gradientList.clear(); + } + } + } + + if (!res) return false; + return true; } bool SvgLoader::close() { - //TODO: - cout << "SvgLoader::close()" << endl; - + if (loaderData.svgParse) free(loaderData.svgParse); + _freeSvgNode(loaderData.doc); return false; } unique_ptr SvgLoader::data() { - //TODO: - cout << "SvgLoader::data()" << endl; - return nullptr; } -#endif //_TVG_SVG_LOADER_CPP_ \ No newline at end of file +#endif //_TVG_SVG_LOADER_CPP_ diff --git a/src/loaders/svg_loader/tvgSvgLoader.h b/src/loaders/svg_loader/tvgSvgLoader.h index 2b4d8b16..13441f38 100644 --- a/src/loaders/svg_loader/tvgSvgLoader.h +++ b/src/loaders/svg_loader/tvgSvgLoader.h @@ -17,12 +17,13 @@ #ifndef _TVG_SVG_LOADER_H_ #define _TVG_SVG_LOADER_H_ -#include "tvgCommon.h" +#include "tvgSvgLoaderCommon.h" class SvgLoader : public Loader { private: - //TODO: + string content; + SvgLoaderData loaderData; public: SvgLoader(); @@ -35,4 +36,4 @@ public: }; -#endif //_TVG_SVG_LOADER_H_ \ No newline at end of file +#endif //_TVG_SVG_LOADER_H_ diff --git a/src/loaders/svg_loader/tvgSvgLoaderCommon.h b/src/loaders/svg_loader/tvgSvgLoaderCommon.h new file mode 100644 index 00000000..a909e547 --- /dev/null +++ b/src/loaders/svg_loader/tvgSvgLoaderCommon.h @@ -0,0 +1,337 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#ifndef _TVG_SVG_LOADER_COMMON_H_ +#define _TVG_SVG_LOADER_COMMON_H_ + +#include "tvgCommon.h" +#include "tvgSimpleXmlParser.h" + +enum class SvgNodeType +{ + Doc, + G, + Defs, + //Switch, //Not support + Animation, + Arc, + Circle, + Ellipse, + Image, + Line, + Path, + Polygon, + Polyline, + Rect, + Text, + TextArea, + Tspan, + Use, + Video, + //Custome_command, //Not support + Unknown +}; + +enum class SvgLengthType +{ + Percent, + Px, + Pc, + Pt, + Mm, + Cm, + In, +}; + +enum class SvgFillFlags +{ + Paint = 0x1, + Opacity = 0x2, + Gradient = 0x4, + FillRule = 0x8 +}; + +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 SvgStyleType +{ + Quality, + Fill, + ViewportFill, + Font, + Stroke, + SolidColor, + Gradient, + Transform, + Opacity, + CompOp +}; + +enum class SvgGradientSpread +{ + Pad = 0, + Reflect, + Repeat, + Last +}; + +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 +}; + +typedef struct _SvgNode SvgNode; +typedef struct _SvgStyleGradient SvgStyleGradient; + +struct SvgDocNode +{ + float w; + float h; + float vx; + float vy; + float vw; + float vh; + SvgNode* defs; + bool preserveAspect; +}; + +struct SvgGNode +{ +}; + +struct SvgDefsNode +{ + vector gradients; +}; + +struct SvgArcNode +{ +}; + +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; +}; + +struct SvgLineNode +{ + float x1; + float y1; + float x2; + float y2; +}; + +struct SvgPathNode +{ + string* path; +}; + +struct SvgPolygonNode +{ + int pointsCount; + float* points; +}; + +struct SvgLinearGradient +{ + float x1; + float y1; + float x2; + float y2; +}; + +struct SvgRadialGradient +{ + float cx; + float cy; + float fx; + float fy; + float r; +}; + +struct SvgGradientStop +{ + float offset; + uint8_t r; + uint8_t g; + uint8_t b; + uint8_t a; +}; + +struct SvgPaint +{ + SvgStyleGradient* gradient; + string* url; + uint8_t r; + uint8_t g; + uint8_t b; + bool none; + bool curColor; +}; + +struct SvgDash +{ + float length; + float gap; +}; + +struct _SvgStyleGradient +{ + SvgGradientType type; + string* id; + string* ref; + SvgGradientSpread spread; + vector stops; + SvgRadialGradient* radial; + SvgLinearGradient* linear; + Matrix* transform; + bool userSpace; + bool usePercentage; +}; + +struct SvgStyleFill +{ + SvgFillFlags flags; + SvgPaint paint; + int opacity; + SvgFillRule 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; + int opacity; + uint8_t r; + uint8_t g; + uint8_t b; +}; + +struct _SvgNode +{ + SvgNodeType type; + SvgNode* parent; + vector child; + string* id; + SvgStyleProperty* style; + Matrix* transform; + union { + SvgGNode g; + SvgDocNode doc; + SvgDefsNode defs; + SvgArcNode arc; + SvgCircleNode circle; + SvgEllipseNode ellipse; + SvgPolygonNode polygon; + SvgPolygonNode polyline; + SvgRectNode rect; + SvgPathNode path; + SvgLineNode line; + } node; + bool display; +}; + +struct SvgParser +{ + SvgNode* node; + SvgStyleGradient* styleGrad; + SvgGradientStop* gradStop; + struct + { + int x, y; + uint32_t w, h; + } global; + struct + { + bool parsedFx; + bool parsedFy; + } gradient; +}; + +struct SvgLoaderData +{ + vector stack; + SvgNode* doc; + SvgNode* def; + vector gradients; + SvgStyleGradient* latestGradient; //For stops + SvgParser* svgParse; + int level; + bool result; +}; + +#endif