#include "vg_common.h" static int _evas_vg_loader_svg_log_dom = -1; #ifdef ERR # undef ERR #endif #define ERR(...) EINA_LOG_DOM_ERR(_evas_vg_loader_svg_log_dom, __VA_ARGS__) #ifdef INF # undef INF #endif #define INF(...) EINA_LOG_DOM_INFO(_evas_vg_loader_svg_log_dom, __VA_ARGS__) /* Global struct for working global cases during the parse */ typedef struct _Evas_SVG_Parser Evas_SVG_Parser; struct _Evas_SVG_Parser { struct { int x, y, width, height; } global; struct { Eina_Bool fx_parsed; Eina_Bool fy_parsed; } gradient; Svg_Node *node; Svg_Style_Gradient *style_grad; Efl_Gfx_Gradient_Stop *grad_stop; }; typedef struct _Evas_SVG_Loader Evas_SVG_Loader; struct _Evas_SVG_Loader { Eina_Array *stack; Svg_Node *doc; Svg_Node *def; Eina_List *gradients; Svg_Style_Gradient *latest_gradient; //for stops Evas_SVG_Parser *svg_parse; int level; Eina_Bool result:1; }; typedef Svg_Node *(*Factory_Method)(Evas_SVG_Loader *loader, Svg_Node *parent, const char *buf, unsigned buflen); typedef Svg_Style_Gradient *(*Gradient_Factory_Method)(Evas_SVG_Loader *loader, const char *buf, unsigned buflen); /* length type to recalculate %, pt, pc, mm, cm etc*/ typedef enum { SVG_PARSER_LENGTH_VERTICAL, SVG_PARSER_LENGTH_HORIZONTAL, /* in case of, for example, radius of radial gradient */ SVG_PARSER_LENGTH_OTHER } SVG_Parser_Length_Type; char * _skip_space(const char *str, const char *end) { while (((end != NULL && str < end) || (end == NULL && *str != '\0')) && isspace(*str)) ++str; return (char *)str; } static inline Eina_Stringshare * _copy_id(const char* str) { if (str == NULL) return NULL; return eina_stringshare_add(str); } static const char * _skipcomma(const char *content) { content = _skip_space(content, NULL); if (*content == ',') return content + 1; return content; } static inline Eina_Bool _parse_number(const char **content, double *number) { char *end = NULL; *number = eina_convert_strtod_c(*content, &end); // if the start of string is not number if ((*content) == end) return EINA_FALSE; //skip comma if any *content = _skipcomma(end); return EINA_TRUE; } /** * According to https://www.w3.org/TR/SVG/coords.html#Units * * TODO * Since this documentation is not obvious, more clean recalculation with dpi * is required, but for now default w3 constants would be used */ static inline double _to_double(Evas_SVG_Parser *svg_parse, const char *str, SVG_Parser_Length_Type type) { double parsed_value = eina_convert_strtod_c(str, NULL); if (strstr(str, "cm")) parsed_value = parsed_value * 35.43307; else if (strstr(str, "mm")) parsed_value = parsed_value * 3.543307; else if (strstr(str, "pt")) parsed_value = parsed_value * 1.25; else if (strstr(str, "pc")) parsed_value = parsed_value * 15; else if (strstr(str, "in")) parsed_value = parsed_value * 90; else if (strstr(str, "%")) { if (type == SVG_PARSER_LENGTH_VERTICAL) parsed_value = (parsed_value / 100.0) * svg_parse->global.height; else if (type == SVG_PARSER_LENGTH_HORIZONTAL) parsed_value = (parsed_value / 100.0) * svg_parse->global.width; else // if other then it's radius { double max = svg_parse->global.width; if (max < svg_parse->global.height) max = svg_parse->global.height; parsed_value = (parsed_value / 100.0) * max; } } //TODO: implement 'em', 'ex' attributes return parsed_value; } /** * Turn gradient variables into percentages */ static inline double _gradient_to_double(Evas_SVG_Parser *svg_parse, const char *str, SVG_Parser_Length_Type type) { char *end = NULL; double parsed_value = eina_convert_strtod_c(str, &end); double max = 1; /** * That is according to Units in here * * https://www.w3.org/TR/2015/WD-SVG2-20150915/coords.html */ if (type == SVG_PARSER_LENGTH_VERTICAL) max = svg_parse->global.height; else if (type == SVG_PARSER_LENGTH_HORIZONTAL) max = svg_parse->global.width; else if (type == SVG_PARSER_LENGTH_OTHER) max = sqrt(pow(svg_parse->global.height, 2) + pow(svg_parse->global.width, 2)) / sqrt(2.0); if (strstr(str, "%")) parsed_value = parsed_value / 100.0; else if (strstr(str, "cm")) parsed_value = parsed_value * 35.43307; else if (strstr(str, "mm")) parsed_value = parsed_value * 3.543307; else if (strstr(str, "pt")) parsed_value = parsed_value * 1.25; else if (strstr(str, "pc")) parsed_value = parsed_value * 15; else if (strstr(str, "in")) parsed_value = parsed_value * 90; //TODO: implement 'em', 'ex' attributes /* Transform into global percentage */ parsed_value = parsed_value / max; return parsed_value; } static inline double _to_offset(const char *str) { char *end = NULL; double parsed_value = eina_convert_strtod_c(str, &end); if (strstr(str, "%")) parsed_value = parsed_value / 100.0; return parsed_value; } static inline int _to_opacity(const char *str) { char *end = NULL; int a = 0; double opacity = eina_convert_strtod_c(str, &end); if (end && (*end == '\0')) a = lrint(opacity * 255); return a; } #define _PARSE_TAG(Type, Short_Name, Tags_Array, Default) \ static Type _to_##Short_Name(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].Short_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 struct { Efl_Gfx_Cap line_cap; const char *tag; } line_cap_tags[] = { { EFL_GFX_CAP_BUTT, "butt" }, { EFL_GFX_CAP_ROUND, "round" }, { EFL_GFX_CAP_SQUARE, "square" } }; _PARSE_TAG(Efl_Gfx_Cap, line_cap, line_cap_tags, EFL_GFX_CAP_LAST); /* 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 struct { Efl_Gfx_Join line_join; const char *tag; } line_join_tags[] = { { EFL_GFX_JOIN_MITER, "miter" }, { EFL_GFX_JOIN_ROUND, "round" }, { EFL_GFX_JOIN_BEVEL, "bevel" } }; _PARSE_TAG(Efl_Gfx_Join, line_join, line_join_tags, EFL_GFX_JOIN_LAST); /* parse the fill rule used during filling a path. * Value: nonzero | evenodd | inherit * Initial: nonzero * https://www.w3.org/TR/SVG/painting.html */ static struct { Efl_Gfx_Fill_Rule fill_rule; const char *tag; } fill_rule_tags[] = { { EFL_GFX_FILL_RULE_ODD_EVEN, "evenodd" } }; _PARSE_TAG(Efl_Gfx_Fill_Rule, fill_rule, fill_rule_tags, EFL_GFX_FILL_RULE_WINDING); #if 0 // unused at the moment /* parse the dash pattern used during stroking a path. * Value: none | | inherit * Initial: none * https://www.w3.org/TR/SVG/painting.html */ static inline void _parse_dash_array(const char *str, Efl_Gfx_Dash** dash, int *length) { double tmp[30]; char *end = NULL; int leni, gapi, count = 0, index = 0; while (*str) { // skip white space, comma str = _skipcomma(str); tmp[count++] = eina_convert_strtod_c(str, &end); str = _skipcomma(end); } if (count & 0x1) { // odd case. *length = count; *dash = calloc(*length, sizeof(Efl_Gfx_Dash)); while (index < count) { leni = (2 * index) % count; gapi = (2 * index + 1) % count; (*dash)[index].length = tmp[leni]; (*dash)[index].gap = tmp[gapi]; } } else { // even case *length = count/2; *dash = calloc(*length, sizeof(Efl_Gfx_Dash)); while (index < count) { (*dash)[index].length = tmp[2 * index]; (*dash)[index].gap = tmp[2 * index + 1]; } } } #endif static Eina_Stringshare * _id_from_url(const char *url) { char tmp[50]; int i = 0; url = _skip_space(url, NULL); if ((*url) == '(') { ++url; url = _skip_space(url, NULL); } if ((*url) == '#') ++url; while ((*url) != ')') { tmp[i++] = *url; ++url; } tmp[i] = '\0'; return eina_stringshare_add(tmp); } static unsigned char _color_parser(const char *value, char **end) { double r; r = eina_convert_strtod_c(value + 4, end); *end = _skip_space(*end, NULL); if (**end == '%') r = 255 * r / 100; *end = _skip_space(*end, NULL); if (r < 0 || r > 255) { *end = NULL; return 0; } return lrint(r); } static const 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 inline void _to_color(const char *str, int *r, int *g, int *b, Eina_Stringshare** ref) { unsigned int i, len = strlen(str); char *red, *green, *blue; unsigned char tr, tg, tb; if (len == 4 && str[0] == '#') { // case for "#456" should be interprete as "#445566" if (isxdigit(str[1]) && isxdigit(str[2]) && isxdigit(str[3])) { char tmp[3] = { '\0', '\0', '\0' }; tmp[0] = str[1]; tmp[1] = str[1]; *r = strtol(tmp, NULL, 16); tmp[0] = str[2]; tmp[1] = str[2]; *g = strtol(tmp, NULL, 16); tmp[0] = str[3]; tmp[1] = str[3]; *b = strtol(tmp, NULL, 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, NULL, 16); tmp[0] = str[3]; tmp[1] = str[4]; *g = strtol(tmp, NULL, 16); tmp[0] = str[5]; tmp[1] = str[6]; *b = strtol(tmp, NULL, 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 = _color_parser(str + 4, &red); if (red && *red == ',') { tg = _color_parser(red + 1, &green); if (green && *green == ',') { tb = _color_parser(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 = _id_from_url(str+3); } else { //handle named color for (i = 0; i < (sizeof (colors) / sizeof (colors[0])); i++) if (!strcasecmp(colors[i].name, str)) { *r = R_VAL(&(colors[i].value)); *g = G_VAL(&(colors[i].value)); *b = B_VAL(&(colors[i].value)); } } } static inline char * parse_numbers_array(char *str, double *points, int *pt_count) { int count = 0; char *end = NULL; str = _skip_space(str, NULL); while (isdigit(*str) || *str == '-' || *str == '+' || *str == '.') { points[count++] = eina_convert_strtod_c(str, &end); str = end; str = _skip_space(str, NULL); if (*str == ',') ++str; //eat the rest of space str = _skip_space(str, NULL); } *pt_count = count; return str; } typedef enum _Matrix_State { SVG_MATRIX_UNKNOWN, SVG_MATRIX_MATRIX, SVG_MATRIX_TRANSLATE, SVG_MATRIX_ROTATE, SVG_MATRIX_SCALE, SVG_MATRIX_SKEWX, SVG_MATRIX_SKEWY } Matrix_State; #define MATRIX_DEF(Name, Value) \ { #Name, sizeof (#Name), Value} static const struct { const char *tag; int sz; Matrix_State state; } matrix_tags[] = { MATRIX_DEF(matrix, SVG_MATRIX_MATRIX), MATRIX_DEF(translate, SVG_MATRIX_TRANSLATE), MATRIX_DEF(rotate, SVG_MATRIX_ROTATE), MATRIX_DEF(scale, SVG_MATRIX_SCALE), MATRIX_DEF(skewX, SVG_MATRIX_SKEWX), MATRIX_DEF(skewY, SVG_MATRIX_SKEWY) }; /* parse transform attribute * https://www.w3.org/TR/SVG/coords.html#TransformAttribute */ static Eina_Matrix3 * _parse_transformation_matrix(const char *value) { unsigned int i; double points[8]; int pt_count = 0; double sx, sy; Matrix_State state = SVG_MATRIX_UNKNOWN; Eina_Matrix3 *matrix = calloc(1, sizeof(Eina_Matrix3)); char *str = (char *)value; char *end = str + strlen(str); eina_matrix3_identity(matrix); while (str < end) { if (isspace(*str) || (*str == ',')) { ++str; continue; } for (i = 0; i < sizeof (matrix_tags) / sizeof(matrix_tags[0]); i++) if (!strncmp(matrix_tags[i].tag, str, matrix_tags[i].sz -1)) { state = matrix_tags[i].state; str += (matrix_tags[i].sz -1); } if ( state == SVG_MATRIX_UNKNOWN) goto error; str = _skip_space(str, end); if (*str != '(') goto error; ++str; str = parse_numbers_array(str, points, &pt_count); if (*str != ')') goto error; ++str; if (state == SVG_MATRIX_MATRIX) { Eina_Matrix3 tmp; if (pt_count != 6) goto error; eina_matrix3_identity(&tmp); eina_matrix3_values_set(&tmp, points[0], points[2], points[4], points[1], points[3], points[5], 0, 0, 1); eina_matrix3_compose(matrix, &tmp, matrix); } else if (state == SVG_MATRIX_TRANSLATE) { if (pt_count == 1) eina_matrix3_translate(matrix, points[0], 0); else if (pt_count == 2) eina_matrix3_translate(matrix, points[0], points[1]); else goto error; } else if (state == SVG_MATRIX_ROTATE) { //Transform to signed. points[0] = fmod(points[0], 360); if (points[0] < 0) points[0] += 360; if (pt_count == 1) { eina_matrix3_rotate(matrix, points[0] * (M_PI/180.0)); } else if (pt_count == 3) { eina_matrix3_translate(matrix, points[1], points[2]); eina_matrix3_rotate(matrix, points[0] * (M_PI/180.0)); eina_matrix3_translate(matrix, -points[1], -points[2]); } else { goto error; } } else if (state == SVG_MATRIX_SCALE) { if (pt_count < 1 || pt_count > 2) goto error; sx = points[0]; sy = sx; if (pt_count == 2) sy = points[1]; eina_matrix3_scale(matrix, sx, sy); } } error: return matrix; } #define LENGTH_DEF(Name, Value) \ { #Name, sizeof (#Name), Value} static const struct { const char *tag; int sz; Svg_Length_Type type; } length_tags[] = { LENGTH_DEF(%, SVG_LT_PERCENT), LENGTH_DEF(px, SVG_LT_PX), LENGTH_DEF(pc, SVG_LT_PC), LENGTH_DEF(pt, SVG_LT_PT), LENGTH_DEF(mm, SVG_LT_MM), LENGTH_DEF(cm, SVG_LT_CM), LENGTH_DEF(in, SVG_LT_IN) }; static double parse_length(const char *str, Svg_Length_Type *type) { unsigned int i; double value; int sz = strlen(str); *type = SVG_LT_PX; for (i = 0; i < sizeof (length_tags) / sizeof(length_tags[0]); i++) if (length_tags[i].sz - 1 == sz && !strncmp(length_tags[i].tag, str, sz)) { *type = length_tags[i].type; } value = eina_convert_strtod_c(str, NULL); return value; } static Eina_Bool _parse_style_attr(void *data, const char *key, const char *value); static Eina_Bool _attr_style_node(void *data, const char *str); static Eina_Bool _attr_parse_svg_node(void *data, const char *key, const char *value) { Evas_SVG_Loader *loader = data; Svg_Node *node = loader->svg_parse->node; Svg_Doc_Node *doc = &(node->node.doc); Svg_Length_Type type; // @TODO handle lenght unit. if (!strcmp(key, "width")) { doc->width = parse_length(value, &type); } else if (!strcmp(key, "height")) { doc->height = parse_length(value, &type); } else if (!strcmp(key, "viewBox")) { if (_parse_number(&value, &doc->vx)) { if (_parse_number(&value, &doc->vy)) { if (_parse_number(&value, &doc->vw)) { _parse_number(&value, &doc->vh); loader->svg_parse->global.height = doc->vh; } loader->svg_parse->global.width = doc->vw; } loader->svg_parse->global.y = doc->vy; } loader->svg_parse->global.x = doc->vx; } else if (!strcmp(key, "preserveAspectRatio")) { if (!strcmp(value, "none")) doc->preserve_aspect = EINA_FALSE; } else if (!strcmp(key, "style")) { _attr_style_node(loader, value); } else { _parse_style_attr(loader, key, value); } return EINA_TRUE; } //https://www.w3.org/TR/SVGTiny12/painting.html#SpecifyingPaint static void _handle_paint_attr(Evas_SVG_Loader *loader EINA_UNUSED, Svg_Paint* paint, const char *value) { if (!strcmp(value, "none")) { // no paint property paint->none = EINA_TRUE; return; } paint->none = EINA_FALSE; if (!strcmp(value, "currentColor")) { paint->cur_color = EINA_TRUE; return; } _to_color(value, &paint->r, &paint->g, &paint->b, &paint->url); } static void _handle_color_attr(Evas_SVG_Loader *loader EINA_UNUSED, Svg_Node* node, const char *value) { Svg_Style_Property *style = node->style; _to_color(value, &style->r, &style->g, &style->b, NULL); } static void _handle_fill_attr(Evas_SVG_Loader *loader EINA_UNUSED, Svg_Node* node, const char *value) { Svg_Style_Property *style = node->style; style->fill.flags |= SVG_FILL_FLAGS_PAINT; _handle_paint_attr(loader, &style->fill.paint, value); } static void _handle_stroke_attr(Evas_SVG_Loader *loader EINA_UNUSED, Svg_Node* node, const char *value) { Svg_Style_Property *style = node->style; style->stroke.flags |= SVG_STROKE_FLAGS_PAINT; _handle_paint_attr(loader, &style->stroke.paint, value); } static void _handle_stroke_opacity_attr(Evas_SVG_Loader *loader EINA_UNUSED, Svg_Node* node, const char *value) { node->style->stroke.flags |= SVG_STROKE_FLAGS_OPACITY; node->style->stroke.opacity = _to_opacity(value); } static void _handle_stroke_width_attr(Evas_SVG_Loader *loader, Svg_Node* node, const char *value) { node->style->stroke.flags |= SVG_STROKE_FLAGS_WIDTH; node->style->stroke.width = _to_double(loader->svg_parse, value, SVG_PARSER_LENGTH_HORIZONTAL); } static void _handle_stroke_linecap_attr(Evas_SVG_Loader *loader EINA_UNUSED, Svg_Node* node, const char *value) { node->style->stroke.flags |= SVG_STROKE_FLAGS_CAP; node->style->stroke.cap = _to_line_cap(value); } static void _handle_stroke_linejoin_attr(Evas_SVG_Loader *loader EINA_UNUSED, Svg_Node* node, const char *value) { node->style->stroke.flags |= SVG_STROKE_FLAGS_JOIN; node->style->stroke.join = _to_line_join(value); } static void _handle_fill_rule_attr(Evas_SVG_Loader *loader EINA_UNUSED, Svg_Node* node, const char *value) { node->style->fill.flags |= SVG_FILL_FLAGS_FILL_RULE; node->style->fill.fill_rule = _to_fill_rule(value); } static void _handle_opacity_attr(Evas_SVG_Loader *loader EINA_UNUSED, Svg_Node* node, const char *value) { node->style->opacity = _to_opacity(value); } static void _handle_fill_opacity_attr(Evas_SVG_Loader *loader EINA_UNUSED, Svg_Node* node, const char *value) { node->style->fill.flags |= SVG_FILL_FLAGS_OPACITY; node->style->fill.opacity = _to_opacity(value); } static void _handle_transform_attr(Evas_SVG_Loader *loader EINA_UNUSED, Svg_Node* node, const char *value) { node->transform = _parse_transformation_matrix(value); } static void _handle_display_attr(Evas_SVG_Loader *loader EINA_UNUSED, Svg_Node* 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 = EINA_FALSE; else node->display = EINA_TRUE; } typedef void (*Style_Method)(Evas_SVG_Loader *loader, Svg_Node *node, const char *value); #define STYLE_DEF(Name, Name1) \ { #Name, sizeof (#Name), _handle_##Name1##_attr} static const struct { const char *tag; int sz; Style_Method tag_handler;; } style_tags[] = { STYLE_DEF(color, color), STYLE_DEF(fill, fill), STYLE_DEF(fill-rule, fill_rule), STYLE_DEF(fill-opacity, fill_opacity), STYLE_DEF(opacity, opacity), STYLE_DEF(stroke, stroke), STYLE_DEF(stroke-width, stroke_width), STYLE_DEF(stroke-linejoin, stroke_linejoin), STYLE_DEF(stroke-linecap, stroke_linecap), STYLE_DEF(stroke-opacity, stroke_opacity), STYLE_DEF(transform, transform), STYLE_DEF(display, display) }; static Eina_Bool _parse_style_attr(void *data, const char *key, const char *value) { Evas_SVG_Loader *loader = data; Svg_Node* node = loader->svg_parse->node; unsigned int i; int sz; // trim the white space key = _skip_space(key, NULL); value = _skip_space(value, NULL); sz = strlen(key); for (i = 0; i < sizeof (style_tags) / sizeof(style_tags[0]); i++) if (style_tags[i].sz - 1 == sz && !strncmp(style_tags[i].tag, key, sz)) { style_tags[i].tag_handler(loader, node, value); return EINA_TRUE; } return EINA_TRUE; } static Eina_Bool _attr_style_node(void *data, const char *str) { eina_simple_xml_attribute_w3c_parse(str, _parse_style_attr, data); return EINA_TRUE; } /* parse g node * https://www.w3.org/TR/SVG/struct.html#Groups */ static Eina_Bool _attr_parse_g_node(void *data, const char *key, const char *value) { Evas_SVG_Loader *loader = data; Svg_Node* node = loader->svg_parse->node; if (!strcmp(key, "style")) { return _attr_style_node(loader, value); } else if (!strcmp(key, "transform")) { node->transform = _parse_transformation_matrix(value); } else if (!strcmp(key, "id")) { node->id = _copy_id(value); } else { _parse_style_attr(loader, key, value); } return EINA_TRUE; } static Svg_Node * _create_node(Svg_Node *parent, Svg_Node_Type type) { Svg_Node *node = calloc(1, sizeof(Svg_Node)); // default fill property node->style = calloc(1, sizeof(Svg_Style_Property)); // update the default value of stroke and fill //https://www.w3.org/TR/SVGTiny12/painting.html#SpecifyingPaint // default fill color is black //node->style->fill.paint.r = 0; //node->style->fill.paint.g = 0; //node->style->fill.paint.b = 0; node->style->fill.paint.none = EINA_FALSE; // default fill opacity is 1 node->style->fill.opacity = 255; node->style->opacity = 255; // default fill rule is nonzero node->style->fill.fill_rule = EFL_GFX_FILL_RULE_WINDING; // default stroke is none node->style->stroke.paint.none = EINA_TRUE; // default stroke opacity is 1 node->style->stroke.opacity = 255; // default stroke width is 1 node->style->stroke.width = 1; // default line cap is butt node->style->stroke.cap = EFL_GFX_CAP_BUTT; // default line join is miter node->style->stroke.join = EFL_GFX_JOIN_MITER; node->style->stroke.scale = 1.0; // default display is true("inline"). node->display = EINA_TRUE; node->parent = parent; node->type = type; node->child = NULL; if (parent) parent->child = eina_list_append(parent->child, node); return node; } static Svg_Node * _create_defs_node(Evas_SVG_Loader *loader EINA_UNUSED, Svg_Node *parent EINA_UNUSED, const char *buf EINA_UNUSED, unsigned buflen EINA_UNUSED) { Svg_Node *node = _create_node(NULL, SVG_NODE_DEFS); eina_simple_xml_attributes_parse(buf, buflen, NULL, node); return node; } static Svg_Node * _create_g_node(Evas_SVG_Loader *loader, Svg_Node *parent, const char *buf, unsigned buflen) { loader->svg_parse->node = _create_node(parent, SVG_NODE_G); eina_simple_xml_attributes_parse(buf, buflen, _attr_parse_g_node, loader); return loader->svg_parse->node; } static Svg_Node * _create_svg_node(Evas_SVG_Loader *loader, Svg_Node *parent, const char *buf, unsigned buflen) { loader->svg_parse->node = _create_node(parent, SVG_NODE_DOC); Svg_Doc_Node *doc = &(loader->svg_parse->node->node.doc); doc->preserve_aspect = EINA_TRUE; eina_simple_xml_attributes_parse(buf, buflen, _attr_parse_svg_node, loader); return loader->svg_parse->node; } static Svg_Node * _create_switch_node(Evas_SVG_Loader *loader EINA_UNUSED, Svg_Node *parent EINA_UNUSED, const char *buf EINA_UNUSED, unsigned buflen EINA_UNUSED) { return NULL; } static Eina_Bool _attr_parse_path_node(void *data, const char *key, const char *value) { Evas_SVG_Loader *loader = data; Svg_Node* node = loader->svg_parse->node; Svg_Path_Node *path = &(node->node.path); if (!strcmp(key, "d")) { path->path = eina_stringshare_add(value); } else if (!strcmp(key, "style")) { _attr_style_node(loader, value); } else if (!strcmp(key, "id")) { node->id = _copy_id(value); } else { _parse_style_attr(loader, key, value); } return EINA_TRUE; } static Svg_Node * _create_path_node(Evas_SVG_Loader *loader, Svg_Node *parent, const char *buf, unsigned buflen) { loader->svg_parse->node = _create_node(parent, SVG_NODE_PATH); eina_simple_xml_attributes_parse(buf, buflen, _attr_parse_path_node, loader); return loader->svg_parse->node; } #define CIRCLE_DEF(Name, Field, Type) \ { #Name, Type, sizeof (#Name), offsetof(Svg_Circle_Node, Field)} static const struct { const char *tag; SVG_Parser_Length_Type type; int sz; size_t offset; } circle_tags[] = { CIRCLE_DEF(cx, cx, SVG_PARSER_LENGTH_HORIZONTAL), CIRCLE_DEF(cy, cy, SVG_PARSER_LENGTH_VERTICAL), CIRCLE_DEF(r, r, SVG_PARSER_LENGTH_OTHER) }; /* parse the attributes for a circle element. * https://www.w3.org/TR/SVG/shapes.html#CircleElement */ static Eina_Bool _attr_parse_circle_node(void *data, const char *key, const char *value) { Evas_SVG_Loader *loader = data; Svg_Node* node = loader->svg_parse->node; Svg_Circle_Node *circle = &(node->node.circle); unsigned int i; unsigned char *array; int sz = strlen(key); array = (unsigned char*) circle; for (i = 0; i < sizeof (circle_tags) / sizeof(circle_tags[0]); i++) if (circle_tags[i].sz - 1 == sz && !strncmp(circle_tags[i].tag, key, sz)) { *((double*) (array + circle_tags[i].offset)) = _to_double(loader->svg_parse, value, circle_tags[i].type); return EINA_TRUE; } if (!strcmp(key, "style")) { _attr_style_node(loader, value); } else if (!strcmp(key, "id")) { node->id = _copy_id(value); } else { _parse_style_attr(loader, key, value); } return EINA_TRUE; } static Svg_Node * _create_circle_node(Evas_SVG_Loader *loader, Svg_Node *parent, const char *buf, unsigned buflen) { loader->svg_parse->node = _create_node(parent, SVG_NODE_CIRCLE); eina_simple_xml_attributes_parse(buf, buflen, _attr_parse_circle_node, loader); return loader->svg_parse->node; } #define ELLIPSE_DEF(Name, Field, Type) \ { #Name, Type, sizeof (#Name), offsetof(Svg_Ellipse_Node, Field)} static const struct { const char *tag; SVG_Parser_Length_Type type; int sz; size_t offset; } ellipse_tags[] = { ELLIPSE_DEF(cx,cx, SVG_PARSER_LENGTH_HORIZONTAL), ELLIPSE_DEF(cy,cy, SVG_PARSER_LENGTH_VERTICAL), ELLIPSE_DEF(rx,rx, SVG_PARSER_LENGTH_HORIZONTAL), ELLIPSE_DEF(ry,ry, SVG_PARSER_LENGTH_VERTICAL) }; /* parse the attributes for an ellipse element. * https://www.w3.org/TR/SVG/shapes.html#EllipseElement */ static Eina_Bool _attr_parse_ellipse_node(void *data, const char *key, const char *value) { Evas_SVG_Loader *loader = data; Svg_Node* node = loader->svg_parse->node; Svg_Ellipse_Node *ellipse = &(node->node.ellipse); unsigned int i; unsigned char *array; int sz = strlen(key); array = (unsigned char*) ellipse; for (i = 0; i < sizeof (ellipse_tags) / sizeof(ellipse_tags[0]); i++) if (ellipse_tags[i].sz - 1 == sz && !strncmp(ellipse_tags[i].tag, key, sz)) { *((double*) (array + ellipse_tags[i].offset)) = _to_double(loader->svg_parse, value, ellipse_tags[i].type); return EINA_TRUE; } if (!strcmp(key, "id")) { node->id = _copy_id(value); } else if (!strcmp(key, "style")) { _attr_style_node(loader, value); } else { _parse_style_attr(loader, key, value); } return EINA_TRUE; } static Svg_Node * _create_ellipse_node(Evas_SVG_Loader *loader, Svg_Node *parent, const char *buf, unsigned buflen) { loader->svg_parse->node = _create_node(parent, SVG_NODE_ELLIPSE); eina_simple_xml_attributes_parse(buf, buflen, _attr_parse_ellipse_node, loader); return loader->svg_parse->node; } static void _attr_parse_polygon_points(const char *str, double **points, int *point_count) { double tmp[50]; int tmp_count=0; int count = 0; double num; double *point_array = NULL, *tmp_array; while (_parse_number(&str, &num)) { tmp[tmp_count++] = num; if (tmp_count == 50) { tmp_array = realloc(point_array, (count + tmp_count) * sizeof(double)); if (!tmp_array) goto error_alloc; point_array = tmp_array; memcpy(&point_array[count], tmp, tmp_count * sizeof(double)); count += tmp_count; tmp_count = 0; } } if (tmp_count > 0) { tmp_array = realloc(point_array, (count + tmp_count) * sizeof(double)); if (!tmp_array) goto error_alloc; point_array = tmp_array; memcpy(&point_array[count], tmp, tmp_count * sizeof(double)); count += tmp_count; } *point_count = count; *points = point_array; return; error_alloc: ERR("allocation for point array failed. out of memory"); abort(); } /* parse the attributes for a polygon element. * https://www.w3.org/TR/SVG/shapes.html#PolylineElement */ static Eina_Bool _attr_parse_polygon_node(void *data, const char *key, const char *value) { Evas_SVG_Loader *loader = data; Svg_Node *node = loader->svg_parse->node; Svg_Polygon_Node *polygon = NULL; if (node->type == SVG_NODE_POLYGON) polygon = &(node->node.polygon); else polygon = &(node->node.polyline); if (!strcmp(key, "points")) { _attr_parse_polygon_points(value, &polygon->points, &polygon->points_count); } else if (!strcmp(key, "style")) { _attr_style_node(loader, value); } else if (!strcmp(key, "id")) { node->id = _copy_id(value); } else { _parse_style_attr(loader, key, value); } return EINA_TRUE; } static Svg_Node * _create_polygon_node(Evas_SVG_Loader *loader, Svg_Node *parent, const char *buf, unsigned buflen) { loader->svg_parse->node = _create_node(parent, SVG_NODE_POLYGON); eina_simple_xml_attributes_parse(buf, buflen, _attr_parse_polygon_node, loader); return loader->svg_parse->node; } static Svg_Node * _create_polyline_node(Evas_SVG_Loader *loader, Svg_Node *parent, const char *buf, unsigned buflen) { loader->svg_parse->node = _create_node(parent, SVG_NODE_POLYLINE); eina_simple_xml_attributes_parse(buf, buflen, _attr_parse_polygon_node, loader); return loader->svg_parse->node; } #define RECT_DEF(Name, Field, Type) \ { #Name, Type, sizeof (#Name), offsetof(Svg_Rect_Node, Field)} static const struct { const char *tag; SVG_Parser_Length_Type type; int sz; size_t offset; } rect_tags[] = { RECT_DEF(x,x, SVG_PARSER_LENGTH_HORIZONTAL), RECT_DEF(y, y, SVG_PARSER_LENGTH_VERTICAL), RECT_DEF(width, w, SVG_PARSER_LENGTH_HORIZONTAL), RECT_DEF(height, h, SVG_PARSER_LENGTH_VERTICAL), RECT_DEF(rx, rx, SVG_PARSER_LENGTH_HORIZONTAL), RECT_DEF(ry, ry, SVG_PARSER_LENGTH_VERTICAL) }; /* parse the attributes for a rect element. * https://www.w3.org/TR/SVG/shapes.html#RectElement */ static Eina_Bool _attr_parse_rect_node(void *data, const char *key, const char *value) { Evas_SVG_Loader *loader = data; Svg_Node *node = loader->svg_parse->node; Svg_Rect_Node *rect = & (node->node.rect); unsigned int i; unsigned char *array; int sz = strlen(key); array = (unsigned char*) rect; for (i = 0; i < sizeof (rect_tags) / sizeof(rect_tags[0]); i++) if (rect_tags[i].sz - 1 == sz && !strncmp(rect_tags[i].tag, key, sz)) { *((double*) (array + rect_tags[i].offset)) = _to_double(loader->svg_parse, value, rect_tags[i].type); return EINA_TRUE; } if (!strcmp(key, "id")) { node->id = _copy_id(value); } else if (!strcmp(key, "style")) { _attr_style_node(loader, value); } else { _parse_style_attr(loader, key, value); } if (!EINA_DBL_EQ(rect->rx, 0) && EINA_DBL_EQ(rect->ry, 0)) rect->ry = rect->rx; if (!EINA_DBL_EQ(rect->ry, 0) && EINA_DBL_EQ(rect->rx, 0)) rect->rx = rect->ry; return EINA_TRUE; } static Svg_Node * _create_rect_node(Evas_SVG_Loader *loader, Svg_Node *parent, const char *buf, unsigned buflen) { loader->svg_parse->node = _create_node(parent, SVG_NODE_RECT); eina_simple_xml_attributes_parse(buf, buflen, _attr_parse_rect_node, loader); return loader->svg_parse->node; } #define LINE_DEF(Name, Field, Type) \ { #Name, Type, sizeof (#Name), offsetof(Svg_Line_Node, Field)} static const struct { const char *tag; SVG_Parser_Length_Type type; int sz; size_t offset; } line_tags[] = { LINE_DEF(x1, x1, SVG_PARSER_LENGTH_HORIZONTAL), LINE_DEF(y1, y1, SVG_PARSER_LENGTH_VERTICAL), LINE_DEF(x2, x2, SVG_PARSER_LENGTH_HORIZONTAL), LINE_DEF(y2, y2, SVG_PARSER_LENGTH_VERTICAL) }; /* parse the attributes for a rect element. * https://www.w3.org/TR/SVG/shapes.html#LineElement */ static Eina_Bool _attr_parse_line_node(void *data, const char *key, const char *value) { Evas_SVG_Loader *loader = data; Svg_Node *node = loader->svg_parse->node; Svg_Line_Node *line = & (node->node.line); unsigned int i; unsigned char *array; int sz = strlen(key); array = (unsigned char*) line; for (i = 0; i < sizeof (line_tags) / sizeof(line_tags[0]); i++) if (line_tags[i].sz - 1 == sz && !strncmp(line_tags[i].tag, key, sz)) { *((double*) (array + line_tags[i].offset)) = _to_double(loader->svg_parse, value, line_tags[i].type); return EINA_TRUE; } if (!strcmp(key, "id")) { node->id = _copy_id(value); } else if (!strcmp(key, "style")) { _attr_style_node(loader, value); } else { _parse_style_attr(loader, key, value); } return EINA_TRUE; } static Svg_Node * _create_line_node(Evas_SVG_Loader *loader, Svg_Node *parent, const char *buf, unsigned buflen) { loader->svg_parse->node = _create_node(parent, SVG_NODE_LINE); eina_simple_xml_attributes_parse(buf, buflen, _attr_parse_line_node, loader); return loader->svg_parse->node; } static Eina_Stringshare * _id_from_href(const char *href) { href = _skip_space(href, NULL); if ((*href) == '#') href++; return eina_stringshare_add(href); } static Svg_Node* _get_defs_node(Svg_Node *node) { if (!node) return NULL; while (node->parent != NULL) { node = node->parent; } if (node->type == SVG_NODE_DOC) return node->node.doc.defs; return NULL; } static Svg_Node* _find_child_by_id(Svg_Node *node, const char *id) { Eina_List *l; Svg_Node *child; if (!node) return NULL; EINA_LIST_FOREACH(node->child, l, child) { if ((child->id != NULL) && !strcmp(child->id, id)) return child; } return NULL; } static Eina_List * _clone_grad_stops(Eina_List *from) { Efl_Gfx_Gradient_Stop *stop; Eina_List *l; Eina_List *res = NULL; EINA_LIST_FOREACH(from, l, stop) { Efl_Gfx_Gradient_Stop *new_stop; new_stop = calloc(1, sizeof(Efl_Gfx_Gradient_Stop)); memcpy(new_stop, stop, sizeof(Efl_Gfx_Gradient_Stop)); res = eina_list_append(res, new_stop); } return res; } static Svg_Style_Gradient * _clone_gradient(Svg_Style_Gradient *from) { Svg_Style_Gradient *grad; if (!from) return NULL; grad= calloc(1, sizeof(Svg_Style_Gradient)); grad->type = from->type; grad->id = _copy_id(from->id); grad->ref = _copy_id(from->ref); grad->spread = from->spread; grad->use_percentage = from->use_percentage; grad->user_space = from->user_space; if (from->transform) { grad->transform = calloc(1, sizeof(Eina_Matrix3)); eina_matrix3_copy(grad->transform, from->transform); } grad->stops = _clone_grad_stops(from->stops); if (grad->type == SVG_LINEAR_GRADIENT) { grad->linear = calloc(1, sizeof(Svg_Linear_Gradient)); memcpy(grad->linear, from->linear, sizeof(Svg_Linear_Gradient)); } else if (grad->type == SVG_RADIAL_GRADIENT) { grad->radial = calloc(1, sizeof(Svg_Radial_Gradient)); memcpy(grad->radial, from->radial, sizeof(Svg_Radial_Gradient)); } return grad; } static void _copy_attribute(Svg_Node *to, Svg_Node *from) { // copy matrix attribute if (from->transform) { to->transform = calloc(1, sizeof(Eina_Matrix3)); eina_matrix3_copy(to->transform, from->transform); } // copy style attribute; memcpy(to->style, from->style, sizeof(Svg_Style_Property)); // copy node attribute switch (from->type) { case SVG_NODE_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 SVG_NODE_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 SVG_NODE_RECT: to->node.rect.x = from->node.rect.x; to->node.rect.y = from->node.rect.y; to->node.rect.w = from->node.rect.w; to->node.rect.h = from->node.rect.h; to->node.rect.rx = from->node.rect.rx; to->node.rect.ry = from->node.rect.ry; break; case SVG_NODE_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 SVG_NODE_PATH: to->node.path.path = eina_stringshare_add(from->node.path.path); break; case SVG_NODE_POLYGON: to->node.polygon.points_count = from->node.polygon.points_count; to->node.polygon.points = calloc(to->node.polygon.points_count, sizeof(double)); break; case SVG_NODE_POLYLINE: to->node.polyline.points_count = from->node.polyline.points_count; to->node.polyline.points = calloc(to->node.polyline.points_count, sizeof(double)); break; default: break; } } static void _clone_node(Svg_Node *from, Svg_Node *parent) { Svg_Node *new_node; Eina_List *l; Svg_Node *child; if (!from) return; new_node = _create_node(parent, from->type); _copy_attribute(new_node, from); EINA_LIST_FOREACH(from->child, l, child) { _clone_node(child, new_node); } } static Eina_Bool _attr_parse_use_node(void *data, const char *key, const char *value) { Evas_SVG_Loader *loader = data; Svg_Node *defs, *node_from, *node = loader->svg_parse->node; Eina_Stringshare *id; if (!strcmp(key, "xlink:href")) { id = _id_from_href(value); defs = _get_defs_node(node); node_from = _find_child_by_id(defs, id); _clone_node(node_from, node); eina_stringshare_del(id); } else { _attr_parse_g_node(data, key, value); } return EINA_TRUE; } static Svg_Node * _create_use_node(Evas_SVG_Loader *loader, Svg_Node *parent, const char *buf, unsigned buflen) { loader->svg_parse->node = _create_node(parent, SVG_NODE_G); eina_simple_xml_attributes_parse(buf, buflen, _attr_parse_use_node, loader); return loader->svg_parse->node; } #define TAG_DEF(Name) \ { #Name, sizeof (#Name), _create_##Name##_node } //TODO: implement 'text' primitive static const struct { const char *tag; int sz; Factory_Method tag_handler; } graphics_tags[] = { TAG_DEF(use), TAG_DEF(circle), TAG_DEF(ellipse), TAG_DEF(path), TAG_DEF(polygon), TAG_DEF(rect), TAG_DEF(polyline), TAG_DEF(line), }; static const struct { const char *tag; int sz; Factory_Method tag_handler; } group_tags[] = { TAG_DEF(defs), TAG_DEF(g), TAG_DEF(svg), TAG_DEF(switch) }; #define FIND_FACTORY(Short_Name, Tags_Array) \ static Factory_Method \ _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].tag_handler; \ } \ return NULL; \ } FIND_FACTORY(group, group_tags); FIND_FACTORY(graphics, graphics_tags); Efl_Gfx_Gradient_Spread _parse_spread_value(const char *value) { Efl_Gfx_Gradient_Spread spread = EFL_GFX_GRADIENT_SPREAD_PAD; if (!strcmp(value, "reflect")) { spread = EFL_GFX_GRADIENT_SPREAD_REFLECT; } else if (!strcmp(value, "repeat")) { spread = EFL_GFX_GRADIENT_SPREAD_REPEAT; } return spread; } static void _handle_radial_cx_attr(Evas_SVG_Loader *loader, Svg_Radial_Gradient* radial, const char *value) { radial->cx = _gradient_to_double(loader->svg_parse, value, SVG_PARSER_LENGTH_HORIZONTAL); if (!loader->svg_parse->gradient.fx_parsed) radial->fx = radial->cx; } static void _handle_radial_cy_attr(Evas_SVG_Loader *loader, Svg_Radial_Gradient* radial, const char *value) { radial->cy = _gradient_to_double(loader->svg_parse, value, SVG_PARSER_LENGTH_VERTICAL); if (!loader->svg_parse->gradient.fy_parsed) radial->fy = radial->cy; } static void _handle_radial_fx_attr(Evas_SVG_Loader *loader, Svg_Radial_Gradient* radial, const char *value) { radial->fx = _gradient_to_double(loader->svg_parse, value, SVG_PARSER_LENGTH_HORIZONTAL); loader->svg_parse->gradient.fx_parsed = EINA_TRUE; } static void _handle_radial_fy_attr(Evas_SVG_Loader *loader, Svg_Radial_Gradient* radial, const char *value) { radial->fy = _gradient_to_double(loader->svg_parse, value, SVG_PARSER_LENGTH_VERTICAL); loader->svg_parse->gradient.fy_parsed = EINA_TRUE; } static void _handle_radial_r_attr(Evas_SVG_Loader *loader, Svg_Radial_Gradient* radial, const char *value) { radial->r = _gradient_to_double(loader->svg_parse, value, SVG_PARSER_LENGTH_OTHER); } static void _recalc_radial_cx_attr(Evas_SVG_Loader *loader, Svg_Radial_Gradient* radial, Eina_Bool user_space) { if (!user_space) { radial->cx = radial->cx * loader->svg_parse->global.width; } } static void _recalc_radial_cy_attr(Evas_SVG_Loader *loader, Svg_Radial_Gradient* radial, Eina_Bool user_space) { if (!user_space) { radial->cy = radial->cy * loader->svg_parse->global.height; } } static void _recalc_radial_fx_attr(Evas_SVG_Loader *loader, Svg_Radial_Gradient* radial, Eina_Bool user_space) { if (!user_space) { radial->fx = radial->fx * loader->svg_parse->global.width; } } static void _recalc_radial_fy_attr(Evas_SVG_Loader *loader, Svg_Radial_Gradient* radial, Eina_Bool user_space) { if (!user_space) { radial->fy = radial->fy * loader->svg_parse->global.height; } } static void _recalc_radial_r_attr(Evas_SVG_Loader *loader, Svg_Radial_Gradient* radial, Eina_Bool user_space) { if (!user_space) { radial->r = radial->r * (sqrt(pow(loader->svg_parse->global.height, 2) + pow(loader->svg_parse->global.width, 2)) / sqrt(2.0)); } } typedef void (*Radial_Method)(Evas_SVG_Loader *loader, Svg_Radial_Gradient *radial, const char *value); typedef void (*Radial_Method_Recalc)(Evas_SVG_Loader *loader, Svg_Radial_Gradient *radial, Eina_Bool user_space); #define RADIAL_DEF(Name) \ { #Name, sizeof (#Name), _handle_radial_##Name##_attr, _recalc_radial_##Name##_attr} static const struct { const char *tag; int sz; Radial_Method tag_handler;; Radial_Method_Recalc tag_recalc; } radial_tags[] = { RADIAL_DEF(cx), RADIAL_DEF(cy), RADIAL_DEF(fx), RADIAL_DEF(fy), RADIAL_DEF(r) }; static Eina_Bool _attr_parse_radial_gradient_node(void *data, const char *key, const char *value) { Evas_SVG_Loader *loader = data; Svg_Style_Gradient *grad = loader->svg_parse->style_grad; Svg_Radial_Gradient *radial = grad->radial; unsigned int i; int sz = strlen(key); for (i = 0; i < sizeof (radial_tags) / sizeof(radial_tags[0]); i++) if (radial_tags[i].sz - 1 == sz && !strncmp(radial_tags[i].tag, key, sz)) { radial_tags[i].tag_handler(loader, radial, value); return EINA_TRUE; } if (!strcmp(key, "id")) { grad->id = _copy_id(value); } else if (!strcmp(key, "spreadMethod")) { grad->spread = _parse_spread_value(value); } else if (!strcmp(key, "xlink:href")) { grad->ref = _id_from_href(value); } else if (!strcmp(key, "gradientUnits") && !strcmp(value, "userSpaceOnUse")) { grad->user_space = EINA_TRUE; } return EINA_TRUE; } static Svg_Style_Gradient * _create_radialGradient(Evas_SVG_Loader *loader, const char *buf, unsigned buflen) { unsigned int i = 0; Svg_Style_Gradient *grad = calloc(1, sizeof(Svg_Style_Gradient)); loader->svg_parse->style_grad = grad; grad->type = SVG_RADIAL_GRADIENT; grad->user_space = EINA_FALSE; grad->radial = calloc(1, sizeof(Svg_Radial_Gradient)); /** * Default values of gradient */ grad->radial->cx = 0.5; grad->radial->cy = 0.5; grad->radial->fx = 0.5; grad->radial->fy = 0.5; grad->radial->r = 0.5; loader->svg_parse->gradient.fx_parsed = EINA_FALSE; loader->svg_parse->gradient.fy_parsed = EINA_FALSE; eina_simple_xml_attributes_parse(buf, buflen, _attr_parse_radial_gradient_node, loader); for (i = 0; i < sizeof (radial_tags) / sizeof(radial_tags[0]); i++) radial_tags[i].tag_recalc(loader, grad->radial, grad->user_space); grad->use_percentage = EINA_TRUE; return loader->svg_parse->style_grad; } static Eina_Bool _attr_parse_stops(void *data, const char *key, const char *value) { Evas_SVG_Loader *loader = data; Efl_Gfx_Gradient_Stop *stop = loader->svg_parse->grad_stop; if (!strcmp(key, "offset")) { stop->offset = _to_offset(value); } else if (!strcmp(key, "stop-opacity")) { stop->a = _to_opacity(value); } else if (!strcmp(key, "stop-color")) { _to_color(value, &stop->r, &stop->g, &stop->b, NULL); } else if (!strcmp(key, "style")) { eina_simple_xml_attribute_w3c_parse(value, _attr_parse_stops, data); } return EINA_TRUE; } static void _handle_linear_x1_attr(Evas_SVG_Loader *loader, Svg_Linear_Gradient* linear, const char *value) { linear->x1 = _gradient_to_double(loader->svg_parse, value, SVG_PARSER_LENGTH_HORIZONTAL); } static void _handle_linear_y1_attr(Evas_SVG_Loader *loader, Svg_Linear_Gradient* linear, const char *value) { linear->y1 = _gradient_to_double(loader->svg_parse, value, SVG_PARSER_LENGTH_VERTICAL); } static void _handle_linear_x2_attr(Evas_SVG_Loader *loader, Svg_Linear_Gradient* linear, const char *value) { linear->x2 = _gradient_to_double(loader->svg_parse, value, SVG_PARSER_LENGTH_HORIZONTAL); } static void _handle_linear_y2_attr(Evas_SVG_Loader *loader, Svg_Linear_Gradient* linear, const char *value) { linear->y2 = _gradient_to_double(loader->svg_parse, value, SVG_PARSER_LENGTH_VERTICAL); } static void _recalc_linear_x1_attr(Evas_SVG_Loader *loader, Svg_Linear_Gradient* linear, Eina_Bool user_space) { if (!user_space) { linear->x1 = linear->x1 * loader->svg_parse->global.width; } } static void _recalc_linear_y1_attr(Evas_SVG_Loader *loader, Svg_Linear_Gradient* linear, Eina_Bool user_space) { if (!user_space) { linear->y1 = linear->y1 * loader->svg_parse->global.height; } } static void _recalc_linear_x2_attr(Evas_SVG_Loader *loader, Svg_Linear_Gradient* linear, Eina_Bool user_space) { if (!user_space) { linear->x2 = linear->x2 * loader->svg_parse->global.width; } } static void _recalc_linear_y2_attr(Evas_SVG_Loader *loader, Svg_Linear_Gradient* linear, Eina_Bool user_space) { if (!user_space) { linear->y2 = linear->y2 * loader->svg_parse->global.height; } } typedef void (*Linear_Method)(Evas_SVG_Loader *loader, Svg_Linear_Gradient *linear, const char *value); typedef void (*Linear_Method_Recalc)(Evas_SVG_Loader *loader, Svg_Linear_Gradient *linear, Eina_Bool user_space); #define LINEAR_DEF(Name) \ { #Name, sizeof (#Name), _handle_linear_##Name##_attr, _recalc_linear_##Name##_attr} static const struct { const char *tag; int sz; Linear_Method tag_handler; Linear_Method_Recalc tag_recalc; } linear_tags[] = { LINEAR_DEF(x1), LINEAR_DEF(y1), LINEAR_DEF(x2), LINEAR_DEF(y2) }; static Eina_Bool _attr_parse_linear_gradient_node(void *data, const char *key, const char *value) { Evas_SVG_Loader *loader = data; Svg_Style_Gradient *grad = loader->svg_parse->style_grad; Svg_Linear_Gradient *linear = grad->linear; unsigned int i; int sz = strlen(key); for (i = 0; i < sizeof (linear_tags) / sizeof(linear_tags[0]); i++) if (linear_tags[i].sz - 1 == sz && !strncmp(linear_tags[i].tag, key, sz)) { linear_tags[i].tag_handler(loader, linear, value); return EINA_TRUE; } if (!strcmp(key, "id")) { grad->id = _copy_id(value); } else if (!strcmp(key, "spreadMethod")) { grad->spread = _parse_spread_value(value); } else if (!strcmp(key, "xlink:href")) { grad->ref = _id_from_href(value); } else if (!strcmp(key, "gradientUnits") && !strcmp(value, "userSpaceOnUse")) { grad->user_space = EINA_TRUE; } else if (!strcmp(key, "gradientTransform")) { grad->transform = _parse_transformation_matrix(value); } return EINA_TRUE; } static Svg_Style_Gradient * _create_linearGradient(Evas_SVG_Loader *loader, const char *buf, unsigned buflen) { Svg_Style_Gradient *grad = calloc(1, sizeof(Svg_Style_Gradient)); loader->svg_parse->style_grad = grad; unsigned int i; grad->type = SVG_LINEAR_GRADIENT; grad->user_space = EINA_FALSE; grad->linear = calloc(1, sizeof(Svg_Linear_Gradient)); /** * Default value of x2 is 100% */ grad->linear->x2 = 1; eina_simple_xml_attributes_parse(buf, buflen, _attr_parse_linear_gradient_node, loader); for (i = 0; i < sizeof (linear_tags) / sizeof(linear_tags[0]); i++) linear_tags[i].tag_recalc(loader, grad->linear, grad->user_space); grad->use_percentage = EINA_TRUE; return loader->svg_parse->style_grad; } #define GRADIENT_DEF(Name) \ { #Name, sizeof (#Name), _create_##Name } /** * For all Gradients lengths would be calculated into percentages related to * canvas width and height. * * if user then recalculate actual pixels into percentages */ static const struct { const char *tag; int sz; Gradient_Factory_Method tag_handler; } gradient_tags[] = { GRADIENT_DEF(linearGradient), GRADIENT_DEF(radialGradient) }; static Gradient_Factory_Method _find_gradient_factory(const char *name) { unsigned int i; int sz = strlen(name); for (i = 0; i < sizeof (gradient_tags) / sizeof(gradient_tags[0]); i++) if (gradient_tags[i].sz - 1 == sz && !strncmp(gradient_tags[i].tag, name, sz)) { return gradient_tags[i].tag_handler; } return NULL; } static void _evas_svg_loader_xml_open_parser(Evas_SVG_Loader *loader, const char *content, unsigned int length) { const char *attrs = NULL; int attrs_length = 0; int sz = length; char tag_name[20] = ""; Factory_Method method; Gradient_Factory_Method gradient_method; Svg_Node *node = NULL, *parent; loader->level++; attrs = eina_simple_xml_tag_attributes_find(content, length); if (!attrs) { // parse the empty tag attrs = content; while ((attrs != NULL) && *attrs != '>') attrs++; } if (attrs) { // find out the tag name starting from content till sz length sz = attrs - content; attrs_length = length - sz; while ((sz > 0) && (isspace(content[sz - 1]))) sz--; strncpy(tag_name, content, sz); tag_name[sz] = '\0'; } if ((method = _find_group_factory(tag_name))) { //group if (!loader->doc) { if (strcmp(tag_name, "svg")) return; // Not a valid svg document node = method(loader, NULL, attrs, attrs_length); loader->doc = node; } else { parent = eina_array_data_get(loader->stack, eina_array_count(loader->stack) - 1); node = method(loader, parent, attrs, attrs_length); } eina_array_push(loader->stack, node); if (node->type == SVG_NODE_DEFS) { loader->doc->node.doc.defs = node; loader->def = node; } } else if ((method = _find_graphics_factory(tag_name))) { parent = eina_array_data_get(loader->stack, eina_array_count(loader->stack) - 1); node = method(loader, parent, attrs, attrs_length); } else if ((gradient_method = _find_gradient_factory(tag_name))) { Svg_Style_Gradient *gradient; gradient = gradient_method(loader, attrs, attrs_length); /*FIXME: The current parsing structure does not distinguish end tags. There is no way to know if the currently parsed gradient is in defs. If a gradient is declared outside of defs after defs is set, it is included in the gradients of defs. But finally, the loader has a gradient style list regardless of defs. This is only to support this when multiple gradients are declared, even if no defs are declared. refer to: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/defs */ if (loader->doc->node.doc.defs) { loader->def->node.defs.gradients = eina_list_append(loader->def->node.defs.gradients, gradient); } else { loader->gradients = eina_list_append(loader->gradients, gradient); } loader->latest_gradient = gradient; } else if (!strcmp(tag_name, "stop")) { Efl_Gfx_Gradient_Stop *stop = calloc(1, sizeof(Efl_Gfx_Gradient_Stop)); loader->svg_parse->grad_stop = stop; /* default value for opacity */ stop->a = 255; eina_simple_xml_attributes_parse(attrs, attrs_length, _attr_parse_stops, loader); if (loader->latest_gradient) { loader->latest_gradient->stops = eina_list_append(loader->latest_gradient->stops, stop); } } } #define POP_TAG(Tag) \ { #Tag, sizeof (#Tag) } static const struct { const char *tag; size_t sz; } pop_array[] = { POP_TAG(g), POP_TAG(svg), POP_TAG(defs) }; static void _evas_svg_loader_xml_close_parser(Evas_SVG_Loader *loader, const char *content, unsigned int length EINA_UNUSED) { unsigned int i; content = _skip_space(content, NULL); for (i = 0; i < sizeof (pop_array) / sizeof (pop_array[0]); i++) if (!strncmp(content, pop_array[i].tag, pop_array[i].sz - 1)) { eina_array_pop(loader->stack); break ; } loader->level--; } static Eina_Bool _evas_svg_loader_parser(void *data, Eina_Simple_XML_Type type, const char *content, unsigned int offset EINA_UNUSED, unsigned int length) { Evas_SVG_Loader *loader = data; switch (type) { case EINA_SIMPLE_XML_OPEN: _evas_svg_loader_xml_open_parser(loader, content, length); break; case EINA_SIMPLE_XML_OPEN_EMPTY: _evas_svg_loader_xml_open_parser(loader, content, length); break; case EINA_SIMPLE_XML_CLOSE: _evas_svg_loader_xml_close_parser(loader, content, length); break; case EINA_SIMPLE_XML_DATA: case EINA_SIMPLE_XML_CDATA: case EINA_SIMPLE_XML_DOCTYPE_CHILD: break; case EINA_SIMPLE_XML_IGNORED: case EINA_SIMPLE_XML_COMMENT: case EINA_SIMPLE_XML_DOCTYPE: break; default: break; } return EINA_TRUE; } static void _inherit_style(Svg_Style_Property *child, Svg_Style_Property *parent) { if (parent == NULL) return; // inherit the property of parent if not present in child. // fill if (!(child->fill.flags & SVG_FILL_FLAGS_PAINT)) { child->fill.paint.r = parent->fill.paint.r; child->fill.paint.g = parent->fill.paint.g; child->fill.paint.b = parent->fill.paint.b; child->fill.paint.none = parent->fill.paint.none; child->fill.paint.cur_color = parent->fill.paint.cur_color; child->fill.paint.url = _copy_id(parent->fill.paint.url); } if (!(child->fill.flags & SVG_FILL_FLAGS_OPACITY)) { child->fill.opacity = parent->fill.opacity; } if (!(child->fill.flags & SVG_FILL_FLAGS_FILL_RULE)) { child->fill.fill_rule = parent->fill.fill_rule; } // stroke if (!(child->stroke.flags & SVG_STROKE_FLAGS_PAINT)) { child->stroke.paint.r = parent->stroke.paint.r; child->stroke.paint.g = parent->stroke.paint.g; child->stroke.paint.b = parent->stroke.paint.b; child->stroke.paint.none = parent->stroke.paint.none; child->stroke.paint.cur_color = parent->stroke.paint.cur_color; child->stroke.paint.url = _copy_id(parent->stroke.paint.url); } if (!(child->stroke.flags & SVG_STROKE_FLAGS_OPACITY)) { child->stroke.opacity = parent->stroke.opacity; } if (!(child->stroke.flags & SVG_STROKE_FLAGS_WIDTH)) { child->stroke.width = parent->stroke.width; } if (!(child->stroke.flags & SVG_STROKE_FLAGS_CAP)) { child->stroke.cap = parent->stroke.cap; } if (!(child->stroke.flags & SVG_STROKE_FLAGS_JOIN)) { child->stroke.join = parent->stroke.join; } } void _update_style(Svg_Node *node, Svg_Style_Property *parent_style) { Eina_List *l; Svg_Node *child; _inherit_style(node->style, parent_style); EINA_LIST_FOREACH(node->child, l, child) { _update_style(child, node->style); } } static Svg_Style_Gradient* _dup_gradient(Eina_List *grad_list, const char *id) { Svg_Style_Gradient *grad; Svg_Style_Gradient *result = NULL; Eina_List *l; EINA_LIST_FOREACH(grad_list, l, grad) { if (!strcmp(grad->id, id)) { result = _clone_gradient(grad); break; } } if (result && result->ref) { EINA_LIST_FOREACH(grad_list, l, grad) { if (!strcmp(grad->id, result->ref)) { if (!result->stops) { result->stops = _clone_grad_stops(grad->stops); } //TODO properly inherit other property break; } } } return result; } void _update_gradient(Svg_Node *node, Eina_List *grad_list) { Eina_List *l; Svg_Node *child; if (node->child) { EINA_LIST_FOREACH(node->child, l, child) { _update_gradient(child, grad_list); } } else { if (node->style->fill.paint.url) { node->style->fill.paint.gradient = _dup_gradient(grad_list, node->style->fill.paint.url); } else if (node->style->stroke.paint.url) { node->style->stroke.paint.gradient = _dup_gradient(grad_list, node->style->stroke.paint.url); } } } static Eina_Bool evas_vg_load_file_data_svg(Vg_File_Data *vfd EINA_UNUSED) { return EINA_TRUE; } static Eina_Bool evas_vg_load_file_close_svg(Vg_File_Data *vfd) { if (vfd->root) efl_unref(vfd->root); free(vfd); return EINA_TRUE; } static Vg_File_Data* evas_vg_load_file_open_svg(Eina_File *file, const char *key EINA_UNUSED, int *error EINA_UNUSED) { Evas_SVG_Loader loader = { NULL, NULL, NULL, NULL, NULL, 0, EINA_FALSE }; const char *content; unsigned int length; Svg_Node *defs; loader.svg_parse = calloc(1, sizeof(Evas_SVG_Parser)); length = eina_file_size_get(file); content = eina_file_map_all(file, EINA_FILE_SEQUENTIAL); if (content) { loader.stack = eina_array_new(8); eina_simple_xml_parse(content, length, EINA_TRUE, _evas_svg_loader_parser, &loader); eina_array_free(loader.stack); eina_file_map_free(file, (void*) content); } if (loader.doc) { _update_style(loader.doc, NULL); defs = loader.doc->node.doc.defs; if (defs) _update_gradient(loader.doc, defs->node.defs.gradients); else { if (loader.gradients) { Eina_List* gradient_list = loader.gradients; _update_gradient(loader.doc, gradient_list); eina_list_free(gradient_list); } } *error = EVAS_LOAD_ERROR_NONE; } else { *error = EVAS_LOAD_ERROR_GENERIC; } free(loader.svg_parse); Vg_File_Data* result = vg_common_svg_create_vg_node(loader.doc); vg_common_svg_node_free(loader.doc); return result; } static Evas_Vg_Load_Func evas_vg_load_svg_func = { evas_vg_load_file_open_svg, evas_vg_load_file_close_svg, evas_vg_load_file_data_svg, }; static int module_open(Evas_Module *em) { if (!em) return 0; em->functions = (void *)(&evas_vg_load_svg_func); _evas_vg_loader_svg_log_dom = eina_log_domain_register ("vg-load-svg", EVAS_DEFAULT_LOG_COLOR); if (_evas_vg_loader_svg_log_dom < 0) { EINA_LOG_ERR("Can not create a module log domain."); return 0; } return 1; } static void module_close(Evas_Module *em EINA_UNUSED) { if (_evas_vg_loader_svg_log_dom >= 0) { eina_log_domain_unregister(_evas_vg_loader_svg_log_dom); _evas_vg_loader_svg_log_dom = -1; } } static Evas_Module_Api evas_modapi = { EVAS_MODULE_API_VERSION, "svg", "none", { module_open, module_close } }; EVAS_MODULE_DEFINE(EVAS_MODULE_TYPE_VG_LOADER, vg_loader, svg); #ifndef EVAS_STATIC_BUILD_VG_SVG EVAS_EINA_MODULE_DEFINE(vg_loader, svg); #endif