2977 lines
96 KiB
C++
2977 lines
96 KiB
C++
/*
|
|
* 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 string* _copyId(const char* str)
|
|
{
|
|
if (!str) 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 = 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 inline 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 string* _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;
|
|
|
|
return new string(url, i);
|
|
}
|
|
|
|
|
|
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, string** 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) delete(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) delete(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) delete 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;
|
|
|
|
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) delete node->id;
|
|
node->id = _copyId(value);
|
|
} 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;
|
|
|
|
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) delete node->id;
|
|
node->id = _copyId(value);
|
|
} 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;
|
|
|
|
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;
|
|
|
|
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) delete 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) delete 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) delete 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) delete 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) delete 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) delete 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 string* _idFromHref(const char* href)
|
|
{
|
|
href = _skipSpace(href, nullptr);
|
|
if ((*href) == '#') href++;
|
|
return new string(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) delete 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 != nullptr) && !strcmp((*child)->id->c_str(), id)) return (*child);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
static SvgNode* _findNodeById(SvgNode *node, string* id)
|
|
{
|
|
SvgNode* result = nullptr;
|
|
if (node->id && !node->id->compare(*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 = new SvgStyleGradient;
|
|
grad->type = from->type;
|
|
grad->id = from->id ? _copyId(from->id->c_str()) : nullptr;
|
|
grad->ref = from->ref ? _copyId(from->ref->c_str()) : 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) delete(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 = new string(from->style->fill.paint.url->c_str());
|
|
if (from->style->stroke.paint.url) to->style->stroke.paint.url = new string(from->style->stroke.paint.url->c_str());
|
|
if (from->style->clipPath.url) to->style->clipPath.url = new string(from->style->clipPath.url->c_str());
|
|
if (from->style->mask.url) to->style->mask.url = new string(from->style->mask.url->c_str());
|
|
|
|
//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 = 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*)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 = new string(from->node.image.href->c_str());
|
|
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, string* id) {
|
|
SvgNodeIdPair nodeIdPair;
|
|
nodeIdPair.node = node;
|
|
nodeIdPair.id = id;
|
|
loader->cloneNodes.push(nodeIdPair);
|
|
}
|
|
|
|
|
|
static void _clonePostponedNodes(Array<SvgNodeIdPair>* cloneNodes) {
|
|
for (uint32_t i = 0; i < cloneNodes->count; ++i) {
|
|
SvgNodeIdPair nodeIdPair = cloneNodes->data[i];
|
|
SvgNode *defs = _getDefsNode(nodeIdPair.node);
|
|
SvgNode *nodeFrom = _findChildById(defs, nodeIdPair.id->c_str());
|
|
_cloneNode(nodeFrom, nodeIdPair.node);
|
|
delete 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;
|
|
string* 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->c_str());
|
|
if (nodeFrom) {
|
|
_cloneNode(nodeFrom, node);
|
|
delete 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 = new SvgStyleGradient;
|
|
loader->svgParse->styleGrad = grad;
|
|
|
|
grad->type = SvgGradientType::Radial;
|
|
grad->userSpace = false;
|
|
grad->radial = (SvgRadialGradient*)calloc(1, sizeof(SvgRadialGradient));
|
|
if (!grad->radial) {
|
|
delete(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 = new SvgStyleGradient;
|
|
loader->svgParse->styleGrad = grad;
|
|
|
|
grad->type = SvgGradientType::Linear;
|
|
grad->userSpace = false;
|
|
grad->linear = (SvgLinearGradient*)calloc(1, sizeof(SvgLinearGradient));
|
|
if (!grad->linear) {
|
|
delete(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->c_str());
|
|
} 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->c_str()) : 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 || node->node.path.path->empty()) 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 string* id)
|
|
{
|
|
SvgStyleGradient* result = nullptr;
|
|
|
|
auto gradList = gradients->data;
|
|
|
|
for (uint32_t i = 0; i < gradients->count; ++i) {
|
|
if (!((*gradList)->id->compare(*id))) {
|
|
result = _cloneGradient(*gradList);
|
|
break;
|
|
}
|
|
++gradList;
|
|
}
|
|
|
|
if (result && result->ref) {
|
|
gradList = gradients->data;
|
|
for (uint32_t i = 0; i < gradients->count; ++i) {
|
|
if (!((*gradList)->id->compare(*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) delete(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) delete(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.
|
|
delete(style->clipPath.url);
|
|
delete(style->mask.url);
|
|
|
|
if (style->fill.paint.gradient) delete(style->fill.paint.gradient);
|
|
if (style->stroke.paint.gradient) delete(style->stroke.paint.gradient);
|
|
delete(style->fill.paint.url);
|
|
delete(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();
|
|
|
|
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: {
|
|
_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) {
|
|
delete(*gradients);
|
|
++gradients;
|
|
}
|
|
node->node.defs.gradients.reset();
|
|
break;
|
|
}
|
|
case SvgNodeType::Image: {
|
|
delete 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) {
|
|
delete(*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;
|
|
}
|