diff --git a/Kconfig b/Kconfig index 20fd5a53eb..020b94a450 100644 --- a/Kconfig +++ b/Kconfig @@ -1299,6 +1299,14 @@ menu "LVGL configuration" bool "Decode whole image to RAM for bin decoder" default n + config LV_USE_SVG + bool "SVG library" + depends on LV_USE_VECTOR_GRAPHIC + + config LV_USE_SVG_ANIMATION + bool "SVG animation" + depends on LV_USE_SVG + config LV_USE_RLE bool "LVGL's version of RLE compression method" diff --git a/docs/libs/svg.rst b/docs/libs/svg.rst new file mode 100644 index 0000000000..6bebe6af02 --- /dev/null +++ b/docs/libs/svg.rst @@ -0,0 +1,49 @@ +.. _svg: + +============== +SVG support +============== + +Scalable Vector Graphics (SVG) Tiny 1.2 support in LVGL. + +Detailed introduction: https://www.w3.org/TR/SVGTiny12/ + +Usage +----- + +Enable :c:macro:`LV_USE_SVG` in ``lv_conf.h``. + +See the examples below. + +If you need support SVG animation attribute parsing, +you can enable :c:macro:`LV_USE_SVG_ANIMATION` in ``lv_conf.h``. + +.. _svg_example: + +Example +------- + +.. include:: ../examples/libs/svg/index.rst + +.. code:: c + + lv_svg_node_t * svg_doc; + const char* svg_data = ""; + + /* Create an SVG DOM tree*/ + svg_doc = lv_svg_load_data(svg_data, svg_len); + ... + + /* Draw SVG image*/ + lv_draw_svg(layer, svg_doc); + ... + + /* Release the DOM tree*/ + lv_svg_node_delete(svg_doc); + +.. _svg_api: + +API +--- + + diff --git a/examples/libs/lv_example_libs.h b/examples/libs/lv_example_libs.h index ae8dcb9218..8eae76e8c4 100644 --- a/examples/libs/lv_example_libs.h +++ b/examples/libs/lv_example_libs.h @@ -25,6 +25,7 @@ extern "C" { #include "tjpgd/lv_example_tjpgd.h" #include "libjpeg_turbo/lv_example_libjpeg_turbo.h" #include "tiny_ttf/lv_example_tiny_ttf.h" +#include "svg/lv_example_svg.h" /********************* * DEFINES diff --git a/examples/libs/svg/index.rst b/examples/libs/svg/index.rst new file mode 100644 index 0000000000..8b361cb92a --- /dev/null +++ b/examples/libs/svg/index.rst @@ -0,0 +1,6 @@ +Load and render SVG data +--------------------------------------- + +.. lv_example:: libs/svg/lv_example_svg_1 + :language: c + diff --git a/examples/libs/svg/lv_example_svg.h b/examples/libs/svg/lv_example_svg.h new file mode 100644 index 0000000000..1af7d0552a --- /dev/null +++ b/examples/libs/svg/lv_example_svg.h @@ -0,0 +1,38 @@ +/** + * @file lv_example_svg.h + * + */ + +#ifndef LV_EXAMPLE_SVG_H +#define LV_EXAMPLE_SVG_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ +void lv_example_svg_1(void); + +/********************** + * MACROS + **********************/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_EXAMPLE_SVG_H*/ diff --git a/examples/libs/svg/lv_example_svg_1.c b/examples/libs/svg/lv_example_svg_1.c new file mode 100644 index 0000000000..c9dcf2e9a7 --- /dev/null +++ b/examples/libs/svg/lv_example_svg_1.c @@ -0,0 +1,36 @@ +#include "../../lv_examples.h" +#if LV_BUILD_EXAMPLES +#if LV_USE_SVG && LV_USE_VECTOR_GRAPHIC + +/** + * Load an SVG data + */ +static void event_cb(lv_event_t * e) +{ + static char svg_data[] = "" + ""; + + lv_layer_t * layer = lv_event_get_layer(e); + lv_svg_node_t * svg = lv_svg_load_data(svg_data, sizeof(svg_data) / sizeof(char)); + lv_draw_svg(layer, svg); + lv_svg_node_delete(svg); +} + +void lv_example_svg_1(void) +{ + lv_obj_add_event_cb(lv_screen_active(), event_cb, LV_EVENT_DRAW_MAIN, NULL); +} +#else + +void lv_example_svg_1(void) +{ + /*TODO + *fallback for online examples*/ + + lv_obj_t * label = lv_label_create(lv_screen_active()); + lv_label_set_text(label, "SVG is not enabled"); + lv_obj_center(label); +} + +#endif +#endif diff --git a/lv_conf_template.h b/lv_conf_template.h index ae3c533d5e..d647c37ec8 100644 --- a/lv_conf_template.h +++ b/lv_conf_template.h @@ -907,6 +907,11 @@ /** Use external LZ4 library */ #define LV_USE_LZ4_EXTERNAL 0 +/*SVG library*/ +#define LV_USE_SVG 0 +#define LV_USE_SVG_ANIMATION 0 +#define LV_USE_SVG_DEBUG 0 + /** FFmpeg library for image decoding and playing videos. * Supports all major image formats so do not enable other image decoder with it. */ #define LV_USE_FFMPEG 0 diff --git a/lvgl.h b/lvgl.h index dbc7570465..5b62c3f52a 100644 --- a/lvgl.h +++ b/lvgl.h @@ -35,7 +35,8 @@ extern "C" { #include "src/misc/lv_utils.h" #include "src/misc/lv_iter.h" #include "src/misc/lv_circle_buf.h" - +#include "src/misc/lv_tree.h" + #include "src/tick/lv_tick.h" #include "src/core/lv_obj.h" @@ -106,6 +107,8 @@ extern "C" { #include "src/libs/rlottie/lv_rlottie.h" #include "src/libs/ffmpeg/lv_ffmpeg.h" #include "src/libs/tiny_ttf/lv_tiny_ttf.h" +#include "src/libs/svg/lv_svg.h" +#include "src/libs/svg/lv_svg_render.h" #include "src/layouts/lv_layout.h" diff --git a/src/libs/svg/lv_svg.c b/src/libs/svg/lv_svg.c new file mode 100644 index 0000000000..726fadfeac --- /dev/null +++ b/src/libs/svg/lv_svg.c @@ -0,0 +1,125 @@ +/** + * @file lv_svg.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "lv_svg.h" +#if LV_USE_SVG + +#include "../../misc/lv_assert.h" +#include "../../misc/lv_log.h" +#include "../../stdlib/lv_mem.h" + +#include "lv_svg_token.h" +#include "lv_svg_parser.h" + +/********************* +* DEFINES +*********************/ + +/********************** +* TYPEDEFS +**********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ +static void lv_svg_node_constructor(const lv_tree_class_t * class_p, lv_tree_node_t * node) +{ + LV_UNUSED(class_p); + lv_svg_node_t * t = (lv_svg_node_t *)node; + t->xml_id = NULL; + t->type = LV_SVG_TAG_INVALID; + lv_array_init(&t->attrs, 4, sizeof(lv_svg_attr_t)); + t->render_obj = NULL; +} + +static void lv_svg_node_destructor(const lv_tree_class_t * class_p, lv_tree_node_t * node) +{ + LV_UNUSED(class_p); + lv_svg_node_t * t = (lv_svg_node_t *)node; + if(t->xml_id) { + lv_free(t->xml_id); + } + for(uint32_t i = 0; i < lv_array_size(&t->attrs); i++) { + lv_svg_attr_t * attr = lv_array_at(&t->attrs, i); + if(attr->val_type == LV_SVG_ATTR_VALUE_PTR) { + lv_free(attr->value.val); + } + } + lv_array_deinit(&t->attrs); +} + +static bool svg_token_process_cb(_lv_svg_token_t * token, void * data) +{ + _lv_svg_parser_t * parser = (_lv_svg_parser_t *)data; + return _lv_svg_parser_token(parser, token); +} + +/********************** + * STATIC VARIABLES + **********************/ +const lv_tree_class_t lv_svg_node_class = { + .base_class = &lv_tree_node_class, + .instance_size = sizeof(lv_svg_node_t), + .constructor_cb = lv_svg_node_constructor, + .destructor_cb = lv_svg_node_destructor, +}; + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ +lv_svg_node_t * lv_svg_load_data(const char * svg_data, uint32_t data_len) +{ + LV_ASSERT_NULL(svg_data); + LV_ASSERT(data_len > 0); + + _lv_svg_parser_t parser; + _lv_svg_parser_init(&parser); + + if(_lv_svg_tokenizer(svg_data, data_len, svg_token_process_cb, &parser)) { + if(_lv_svg_parser_is_finish(&parser)) { + lv_svg_node_t * doc = parser.doc_root; + parser.doc_root = NULL; + _lv_svg_parser_deinit(&parser); +#if LV_USE_SVG_DEBUG + _lv_svg_dump_tree(doc, 0); +#endif + return doc; + } + else { + _lv_svg_parser_deinit(&parser); + LV_LOG_ERROR("svg document parser raise errors!"); + return NULL; + } + } + else { + _lv_svg_parser_deinit(&parser); + LV_LOG_ERROR("svg document tokenizer raise errors!"); + return NULL; + } +} + +lv_svg_node_t * lv_svg_node_create(lv_svg_node_t * parent) +{ + lv_tree_node_t * node = lv_tree_node_create(&lv_svg_node_class, (lv_tree_node_t *)parent); + return (lv_svg_node_t *)node; +} + +void lv_svg_node_delete(lv_svg_node_t * node) +{ + LV_ASSERT_NULL(node); + lv_tree_node_delete((lv_tree_node_t *)node); +} + +/********************** + * STATIC FUNCTIONS + **********************/ +#endif /*LV_USE_SVG*/ diff --git a/src/libs/svg/lv_svg.h b/src/libs/svg/lv_svg.h new file mode 100644 index 0000000000..b5a8965cb5 --- /dev/null +++ b/src/libs/svg/lv_svg.h @@ -0,0 +1,334 @@ +/** + * @file lv_svg.h + * + */ + +#ifndef LV_SVG_H +#define LV_SVG_H + +/********************* + * INCLUDES + *********************/ +#include "../../lv_conf_internal.h" +#if LV_USE_SVG + +#include "../../misc/lv_array.h" +#include "../../misc/lv_tree.h" +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ +enum { + LV_SVG_TAG_INVALID = -1, + LV_SVG_TAG_CONTENT, + LV_SVG_TAG_SVG, + LV_SVG_TAG_USE, + LV_SVG_TAG_G, + LV_SVG_TAG_PATH, + LV_SVG_TAG_RECT, + LV_SVG_TAG_CIRCLE, + LV_SVG_TAG_ELLIPSE, + LV_SVG_TAG_LINE, + LV_SVG_TAG_POLYLINE, + LV_SVG_TAG_POLYGON, + LV_SVG_TAG_SOLID_COLOR, + LV_SVG_TAG_LINEAR_GRADIENT, + LV_SVG_TAG_RADIAL_GRADIENT, + LV_SVG_TAG_STOP, + LV_SVG_TAG_DEFS, + LV_SVG_TAG_IMAGE, +#if LV_USE_SVG_ANIMATION + LV_SVG_TAG_MPATH, + LV_SVG_TAG_SET, + LV_SVG_TAG_ANIMATE, + LV_SVG_TAG_ANIMATE_COLOR, + LV_SVG_TAG_ANIMATE_TRANSFORM, + LV_SVG_TAG_ANIMATE_MOTION, +#endif + LV_SVG_TAG_TEXT, + LV_SVG_TAG_TSPAN, + LV_SVG_TAG_TEXT_AREA, +}; +typedef int8_t lv_svg_tag_t; + +enum { + LV_SVG_ATTR_INVALID = 0, + LV_SVG_ATTR_ID, + LV_SVG_ATTR_XML_ID, + LV_SVG_ATTR_VERSION, + LV_SVG_ATTR_BASE_PROFILE, + LV_SVG_ATTR_VIEWBOX, + LV_SVG_ATTR_PRESERVE_ASPECT_RATIO, + LV_SVG_ATTR_VIEWPORT_FILL, + LV_SVG_ATTR_VIEWPORT_FILL_OPACITY, + LV_SVG_ATTR_DISPLAY, + LV_SVG_ATTR_VISIBILITY, + LV_SVG_ATTR_X, + LV_SVG_ATTR_Y, + LV_SVG_ATTR_WIDTH, + LV_SVG_ATTR_HEIGHT, + LV_SVG_ATTR_RX, + LV_SVG_ATTR_RY, + LV_SVG_ATTR_CX, + LV_SVG_ATTR_CY, + LV_SVG_ATTR_R, + LV_SVG_ATTR_X1, + LV_SVG_ATTR_Y1, + LV_SVG_ATTR_X2, + LV_SVG_ATTR_Y2, + LV_SVG_ATTR_POINTS, + LV_SVG_ATTR_D, + LV_SVG_ATTR_PATH_LENGTH, + LV_SVG_ATTR_XLINK_HREF, + LV_SVG_ATTR_FILL, + LV_SVG_ATTR_FILL_RULE, + LV_SVG_ATTR_FILL_OPACITY, + LV_SVG_ATTR_STROKE, + LV_SVG_ATTR_STROKE_WIDTH, + LV_SVG_ATTR_STROKE_LINECAP, + LV_SVG_ATTR_STROKE_LINEJOIN, + LV_SVG_ATTR_STROKE_MITER_LIMIT, + LV_SVG_ATTR_STROKE_DASH_ARRAY, + LV_SVG_ATTR_STROKE_DASH_OFFSET, + LV_SVG_ATTR_STROKE_OPACITY, + LV_SVG_ATTR_OPACITY, + LV_SVG_ATTR_SOLID_COLOR, + LV_SVG_ATTR_SOLID_OPACITY, + LV_SVG_ATTR_GRADIENT_UNITS, + LV_SVG_ATTR_GRADIENT_STOP_OFFSET, + LV_SVG_ATTR_GRADIENT_STOP_COLOR, + LV_SVG_ATTR_GRADIENT_STOP_OPACITY, + LV_SVG_ATTR_FONT_FAMILY, + LV_SVG_ATTR_FONT_STYLE, + LV_SVG_ATTR_FONT_VARIANT, + LV_SVG_ATTR_FONT_WEIGHT, + LV_SVG_ATTR_FONT_SIZE, + LV_SVG_ATTR_TRANSFORM, + LV_SVG_ATTR_TEXT_ANCHOR, +#if LV_USE_SVG_ANIMATION + LV_SVG_ATTR_ATTRIBUTE_NAME, + LV_SVG_ATTR_ATTRIBUTE_TYPE, + LV_SVG_ATTR_BEGIN, + LV_SVG_ATTR_END, + LV_SVG_ATTR_DUR, + LV_SVG_ATTR_MIN, + LV_SVG_ATTR_MAX, + LV_SVG_ATTR_RESTART, + LV_SVG_ATTR_REPEAT_COUNT, + LV_SVG_ATTR_REPEAT_DUR, + LV_SVG_ATTR_CALC_MODE, + LV_SVG_ATTR_VALUES, + LV_SVG_ATTR_KEY_TIMES, + LV_SVG_ATTR_KEY_SPLINES, + LV_SVG_ATTR_KEY_POINTS, + LV_SVG_ATTR_FROM, + LV_SVG_ATTR_TO, + LV_SVG_ATTR_BY, + LV_SVG_ATTR_ADDITIVE, + LV_SVG_ATTR_ACCUMULATE, + LV_SVG_ATTR_PATH, + LV_SVG_ATTR_ROTATE, + LV_SVG_ATTR_TRANSFORM_TYPE, +#endif +}; +typedef uint8_t lv_svg_attr_type_t; + +enum { + LV_SVG_TRANSFORM_TYPE_MATRIX = 1, + LV_SVG_TRANSFORM_TYPE_TRANSLATE, + LV_SVG_TRANSFORM_TYPE_ROTATE, + LV_SVG_TRANSFORM_TYPE_SCALE, + LV_SVG_TRANSFORM_TYPE_SKEW_X, + LV_SVG_TRANSFORM_TYPE_SKEW_Y, +}; +typedef uint8_t lv_svg_transform_type_t; + +#if LV_USE_SVG_ANIMATION +enum { + LV_SVG_ANIM_REMOVE = 0, + LV_SVG_ANIM_FREEZE, +}; + +enum { + LV_SVG_ANIM_RESTART_ALWAYS = 0, + LV_SVG_ANIM_RESTART_WHEN_NOT_ACTIVE, + LV_SVG_ANIM_RESTART_NEVER, +}; + +enum { + LV_SVG_ANIM_CALC_MODE_LINEAR = 0, + LV_SVG_ANIM_CALC_MODE_PACED, + LV_SVG_ANIM_CALC_MODE_SPLINE, + LV_SVG_ANIM_CALC_MODE_DISCRETE, +}; + +enum { + LV_SVG_ANIM_ADDITIVE_REPLACE = 0, + LV_SVG_ANIM_ADDITIVE_SUM, +}; + +enum { + LV_SVG_ANIM_ACCUMULATE_NONE = 0, + LV_SVG_ANIM_ACCUMULATE_SUM, +}; +#endif + +enum { + LV_SVG_ASPECT_RATIO_NONE = 0, + LV_SVG_ASPECT_RATIO_XMIN_YMIN = (1 << 1), + LV_SVG_ASPECT_RATIO_XMID_YMIN = (2 << 1), + LV_SVG_ASPECT_RATIO_XMAX_YMIN = (3 << 1), + LV_SVG_ASPECT_RATIO_XMIN_YMID = (4 << 1), + LV_SVG_ASPECT_RATIO_XMID_YMID = (5 << 1), + LV_SVG_ASPECT_RATIO_XMAX_YMID = (6 << 1), + LV_SVG_ASPECT_RATIO_XMIN_YMAX = (7 << 1), + LV_SVG_ASPECT_RATIO_XMID_YMAX = (8 << 1), + LV_SVG_ASPECT_RATIO_XMAX_YMAX = (9 << 1), +}; + +enum { + LV_SVG_ASPECT_RATIO_OPT_MEET = 0, + LV_SVG_ASPECT_RATIO_OPT_SLICE, +}; +typedef uint32_t lv_svg_aspect_ratio_t; + +typedef struct { + float x; + float y; +} lv_svg_point_t; + +typedef struct { + float m[3][3]; +} lv_svg_matrix_t; + +typedef uint32_t lv_svg_color_t; + +enum { + LV_SVG_FILL_NONZERO = 0, + LV_SVG_FILL_EVENODD, +}; +typedef uint8_t lv_svg_fill_rule_t; + +enum { + LV_SVG_LINE_CAP_BUTT = 0, + LV_SVG_LINE_CAP_SQUARE, + LV_SVG_LINE_CAP_ROUND, +}; +typedef uint8_t lv_svg_line_cap_t; + +enum { + LV_SVG_LINE_JOIN_MITER = 0, + LV_SVG_LINE_JOIN_BEVEL, + LV_SVG_LINE_JOIN_ROUND, +}; +typedef uint8_t lv_svg_line_join_t; + +enum { + LV_SVG_GRADIENT_UNITS_OBJECT = 0, + LV_SVG_GRADIENT_UNITS_USER_SPACE, +}; +typedef uint8_t lv_svg_gradient_units_t; + +typedef union { + int32_t ival; + uint32_t uval; + float fval; + char * sval; + void * val; +} lv_svg_attr_value_t; + +/* + * to simplify list buffer management, allocate enough memory for all data and length. + * | size | data[0] | data[1] | data[2] | ... | + */ +typedef struct { + uint32_t length; + uint8_t data[1]; +} lv_svg_attr_values_list_t; + +/* https://www.w3.org/TR/SVGTiny12/svgudomidl.html */ +enum { + LV_SVG_PATH_CMD_MOVE_TO = 77, + LV_SVG_PATH_CMD_LINE_TO = 76, + LV_SVG_PATH_CMD_CURVE_TO = 67, + LV_SVG_PATH_CMD_QUAD_TO = 81, + LV_SVG_PATH_CMD_CLOSE = 90, +}; + +/* + * to simplify list buffer management, allocate enough memory for all path data and cmd. + * | cmd | data[0] | data[1] | data[2] | ... | + */ +typedef struct { + uint32_t cmd; + uint8_t data[1]; +} lv_svg_attr_path_value_t; + +enum { + LV_SVG_ATTR_VALUE_DATA = 0, + LV_SVG_ATTR_VALUE_PTR, +}; +typedef uint8_t lv_svg_attr_value_type_t; + +enum { + LV_SVG_ATTR_VALUE_NONE = 0, + LV_SVG_ATTR_VALUE_INITIAL, + LV_SVG_ATTR_VALUE_INHERIT, +}; +typedef uint8_t lv_svg_attr_value_class_t; + +typedef struct { + lv_svg_attr_type_t id; + lv_svg_attr_value_type_t val_type; + lv_svg_attr_value_class_t class_type; + lv_svg_attr_value_t value; +} lv_svg_attr_t; + +struct _lv_svg_render_obj; + +typedef struct { + lv_tree_node_t base; + char * xml_id; /* xml_id or content */ + lv_svg_tag_t type; + lv_array_t attrs; + struct _lv_svg_render_obj * render_obj; +} lv_svg_node_t; + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * @brief Loading SVG data and creating the DOM tree + * @param svg_data pointer to the SVG data + * @param data_len the SVG data length + */ +lv_svg_node_t * lv_svg_load_data(const char * svg_data, uint32_t data_len); + +/** + * @brief Create an SVG DOM node + * @param parent pointer to the parent node + * @return true: an new SVG DOM node, false: NULL + */ +lv_svg_node_t * lv_svg_node_create(lv_svg_node_t * parent); + +/** + * @brief Delete an SVG DOM subtree + * @param node pointer to an SVG DOM subtree + */ +void lv_svg_node_delete(lv_svg_node_t * node); + +/********************** + * MACROS + **********************/ +#define LV_SVG_NODE_CHILD(n, i) \ + ((lv_svg_node_t *)(LV_TREE_NODE((n))->children[i])) + +#define LV_SVG_NODE(n) ((lv_svg_node_t*)(n)) + +#endif /*LV_USE_SVG*/ + +#endif /*LV_SVG_H*/ diff --git a/src/libs/svg/lv_svg_parser.c b/src/libs/svg/lv_svg_parser.c new file mode 100644 index 0000000000..107d599381 --- /dev/null +++ b/src/libs/svg/lv_svg_parser.c @@ -0,0 +1,2309 @@ +/** + * @file lv_svg_parser.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "lv_svg_parser.h" +#if LV_USE_SVG + +#include "../../../lvgl.h" +#include +#include +#include +#include + +/********************* +* DEFINES +*********************/ +#ifndef M_PI + #define M_PI 3.1415926f +#endif + +#define MAP_LEN(m) sizeof((m)) / sizeof((m[0])) + +#define CHECK_AND_RESIZE_ATTRS(a) \ + do { \ + if((lv_array_size(&(a)) + 1) > lv_array_capacity(&(a))) { \ + lv_array_resize(&(a), (a).capacity << 1); \ + } \ + } while(0) + +/********************** +* TYPEDEFS +**********************/ +static const struct _lv_svg_tag_map { + const char * name; + uint32_t name_len; + lv_svg_tag_t tag; +} _svg_tag_map[] = { + {"svg", 3, LV_SVG_TAG_SVG}, + {"use", 3, LV_SVG_TAG_USE}, + {"g", 1, LV_SVG_TAG_G}, + {"path", 4, LV_SVG_TAG_PATH}, + {"rect", 4, LV_SVG_TAG_RECT}, + {"circle", 6, LV_SVG_TAG_CIRCLE}, + {"ellipse", 7, LV_SVG_TAG_ELLIPSE}, + {"line", 4, LV_SVG_TAG_LINE}, + {"polyline", 8, LV_SVG_TAG_POLYLINE}, + {"polygon", 7, LV_SVG_TAG_POLYGON}, + {"solidColor", 10, LV_SVG_TAG_SOLID_COLOR}, + {"linearGradient", 14, LV_SVG_TAG_LINEAR_GRADIENT}, + {"radialGradient", 14, LV_SVG_TAG_RADIAL_GRADIENT}, + {"stop", 4, LV_SVG_TAG_STOP}, + {"defs", 4, LV_SVG_TAG_DEFS}, + {"image", 5, LV_SVG_TAG_IMAGE}, +#if LV_USE_SVG_ANIMATION + {"mpath", 5, LV_SVG_TAG_MPATH}, + {"set", 3, LV_SVG_TAG_SET}, + {"animate", 7, LV_SVG_TAG_ANIMATE}, + {"animateColor", 12, LV_SVG_TAG_ANIMATE_COLOR}, + {"animateTransform", 16, LV_SVG_TAG_ANIMATE_TRANSFORM}, + {"animateMotion", 13, LV_SVG_TAG_ANIMATE_MOTION}, +#endif + {"text", 4, LV_SVG_TAG_TEXT}, + {"tspan", 5, LV_SVG_TAG_TSPAN}, + {"textArea", 8, LV_SVG_TAG_TEXT_AREA}, +}; + +static const struct _lv_svg_attr_map { + const char * name; + uint32_t name_len; + lv_svg_attr_type_t attr; +} _svg_attr_map[] = { + {"id", 2, LV_SVG_ATTR_ID}, + {"xml:id", 6, LV_SVG_ATTR_XML_ID}, + {"version", 7, LV_SVG_ATTR_VERSION}, + {"baseProfile", 11, LV_SVG_ATTR_BASE_PROFILE}, + {"viewBox", 7, LV_SVG_ATTR_VIEWBOX}, + {"preserveAspectRatio", 19, LV_SVG_ATTR_PRESERVE_ASPECT_RATIO}, + {"viewport-fill", 13, LV_SVG_ATTR_VIEWPORT_FILL}, + {"viewport-fill-opacity", 21, LV_SVG_ATTR_VIEWPORT_FILL_OPACITY}, + {"display", 7, LV_SVG_ATTR_DISPLAY}, + {"visibility", 10, LV_SVG_ATTR_VISIBILITY}, + {"x", 1, LV_SVG_ATTR_X}, + {"y", 1, LV_SVG_ATTR_Y}, + {"width", 5, LV_SVG_ATTR_WIDTH}, + {"height", 6, LV_SVG_ATTR_HEIGHT}, + {"rx", 2, LV_SVG_ATTR_RX}, + {"ry", 2, LV_SVG_ATTR_RY}, + {"cx", 2, LV_SVG_ATTR_CX}, + {"cy", 2, LV_SVG_ATTR_CY}, + {"r", 1, LV_SVG_ATTR_R}, + {"x1", 2, LV_SVG_ATTR_X1}, + {"y1", 2, LV_SVG_ATTR_Y1}, + {"x2", 2, LV_SVG_ATTR_X2}, + {"y2", 2, LV_SVG_ATTR_Y2}, + {"points", 6, LV_SVG_ATTR_POINTS}, + {"d", 1, LV_SVG_ATTR_D}, + {"pathLength", 10, LV_SVG_ATTR_PATH_LENGTH}, + {"xlink:href", 10, LV_SVG_ATTR_XLINK_HREF}, + {"fill", 4, LV_SVG_ATTR_FILL}, + {"fill-rule", 9, LV_SVG_ATTR_FILL_RULE}, + {"fill-opacity", 12, LV_SVG_ATTR_FILL_OPACITY}, + {"stroke", 6, LV_SVG_ATTR_STROKE}, + {"stroke-width", 12, LV_SVG_ATTR_STROKE_WIDTH}, + {"stroke-linecap", 14, LV_SVG_ATTR_STROKE_LINECAP}, + {"stroke-linejoin", 15, LV_SVG_ATTR_STROKE_LINEJOIN}, + {"stroke-miterlimit", 17, LV_SVG_ATTR_STROKE_MITER_LIMIT}, + {"stroke-dasharray", 16, LV_SVG_ATTR_STROKE_DASH_ARRAY}, + {"stroke-dashoffset", 17, LV_SVG_ATTR_STROKE_DASH_OFFSET}, + {"stroke-opacity", 14, LV_SVG_ATTR_STROKE_OPACITY}, + {"opacity", 7, LV_SVG_ATTR_OPACITY}, + {"solid-color", 11, LV_SVG_ATTR_SOLID_COLOR}, + {"solid-opacity", 13, LV_SVG_ATTR_SOLID_OPACITY}, + {"gradientUnits", 13, LV_SVG_ATTR_GRADIENT_UNITS}, + {"offset", 6, LV_SVG_ATTR_GRADIENT_STOP_OFFSET}, + {"stop-color", 10, LV_SVG_ATTR_GRADIENT_STOP_COLOR}, + {"stop-opacity", 12, LV_SVG_ATTR_GRADIENT_STOP_OPACITY}, + {"font-family", 11, LV_SVG_ATTR_FONT_FAMILY}, + {"font-style", 10, LV_SVG_ATTR_FONT_STYLE}, + {"font-variant", 12, LV_SVG_ATTR_FONT_VARIANT}, + {"font-weight", 11, LV_SVG_ATTR_FONT_WEIGHT}, + {"font-size", 9, LV_SVG_ATTR_FONT_SIZE}, + {"transform", 9, LV_SVG_ATTR_TRANSFORM}, + {"text-anchor", 11, LV_SVG_ATTR_TEXT_ANCHOR}, +#if LV_USE_SVG_ANIMATION + {"attributeName", 13, LV_SVG_ATTR_ATTRIBUTE_NAME}, + {"attributeType", 13, LV_SVG_ATTR_ATTRIBUTE_TYPE}, + {"begin", 5, LV_SVG_ATTR_BEGIN}, + {"end", 3, LV_SVG_ATTR_END}, + {"dur", 3, LV_SVG_ATTR_DUR}, + {"min", 3, LV_SVG_ATTR_MIN}, + {"max", 3, LV_SVG_ATTR_MAX}, + {"restart", 7, LV_SVG_ATTR_RESTART}, + {"repeatCount", 11, LV_SVG_ATTR_REPEAT_COUNT}, + {"repeatDur", 9, LV_SVG_ATTR_REPEAT_DUR}, + {"calcMode", 8, LV_SVG_ATTR_CALC_MODE}, + {"values", 6, LV_SVG_ATTR_VALUES}, + {"keyTimes", 8, LV_SVG_ATTR_KEY_TIMES}, + {"keySplines", 10, LV_SVG_ATTR_KEY_SPLINES}, + {"keyPoints", 9, LV_SVG_ATTR_KEY_POINTS}, + {"from", 4, LV_SVG_ATTR_FROM}, + {"to", 2, LV_SVG_ATTR_TO}, + {"by", 2, LV_SVG_ATTR_BY}, + {"additive", 8, LV_SVG_ATTR_ADDITIVE}, + {"accumulate", 10, LV_SVG_ATTR_ACCUMULATE}, + {"path", 4, LV_SVG_ATTR_PATH}, + {"rotate", 6, LV_SVG_ATTR_ROTATE}, + {"type", 4, LV_SVG_ATTR_TRANSFORM_TYPE}, +#endif +}; + +static const struct _lv_svg_attr_aspect_ratio_map { + const char * name; + uint32_t align; +} _svg_attr_aspect_ratio_map[] = { + {"xMinYMin", LV_SVG_ASPECT_RATIO_XMIN_YMIN}, + {"xMidYMin", LV_SVG_ASPECT_RATIO_XMID_YMIN}, + {"xMaxYMin", LV_SVG_ASPECT_RATIO_XMAX_YMIN}, + {"xMinYMid", LV_SVG_ASPECT_RATIO_XMIN_YMID}, + {"xMidYMid", LV_SVG_ASPECT_RATIO_XMID_YMID}, + {"xMaxYMid", LV_SVG_ASPECT_RATIO_XMAX_YMID}, + {"xMinYMax", LV_SVG_ASPECT_RATIO_XMIN_YMAX}, + {"xMidYMax", LV_SVG_ASPECT_RATIO_XMID_YMAX}, + {"xMaxYMax", LV_SVG_ASPECT_RATIO_XMAX_YMAX}, +}; + +static const struct _lv_svg_color_map { + const char * name; + uint32_t name_len; + uint32_t color; +} _svg_color_map[] = { + {"aliceblue", 9, 0xf0f8ff}, + {"antiquewhite", 12, 0xfaebd7}, + {"aqua", 4, 0x00ffff}, + {"aquamarine", 10, 0x7fffd4}, + {"azure", 5, 0xf0ffff}, + {"beige", 5, 0xf5f5dc}, + {"bisque", 6, 0xffe4c4}, + {"black", 5, 0x000000}, + {"blanchedalmond", 14, 0xffebcd}, + {"blue", 4, 0x0000ff}, + {"blueviolet", 10, 0x8a2be2}, + {"brown", 5, 0xa52a2a}, + {"burlywood", 9, 0xdeb887}, + {"cadetblue", 9, 0x5f9ea0}, + {"chartreuse", 10, 0x7fff00}, + {"chocolate", 9, 0xd2691e}, + {"coral", 5, 0xff7f50}, + {"cornflowerblue", 14, 0x6495ed}, + {"cornsilk", 8, 0xfff8dc}, + {"crimson", 7, 0xdc143c}, + {"cyan", 4, 0x00ffff}, + {"darkblue", 8, 0x00008b}, + {"darkcyan", 8, 0x008b8b}, + {"darkgoldenrod", 13, 0xb8860b}, + {"darkgray", 8, 0xa9a9a9}, + {"darkgrey", 8, 0xa9a9a9}, + {"darkgreen", 9, 0x006400}, + {"darkkhaki", 9, 0xbdb76b}, + {"darkmagenta", 11, 0x8b008b}, + {"darkolivegreen", 14, 0x556b2f}, + {"darkorange", 10, 0xff8c00}, + {"darkorchid", 10, 0x9932cc}, + {"darkred", 7, 0x8b0000}, + {"darksalmon", 10, 0xe9967a}, + {"darkseagreen", 12, 0x8fbc8f}, + {"darkslateblue", 13, 0x483d8b}, + {"darkslategray", 13, 0x2f4f4f}, + {"darkslategrey", 13, 0x2f4f4f}, + {"darkturquoise", 13, 0x00ced1}, + {"darkviolet", 10, 0x9400d3}, + {"deeppink", 8, 0xff1493}, + {"deepskyblue", 11, 0x00bfff}, + {"dimgray", 7, 0x696969}, + {"dimgrey", 7, 0x696969}, + {"dodgerblue", 10, 0x1e90ff}, + {"firebrick", 9, 0xb22222}, + {"floralwhite", 11, 0xfffaf0}, + {"forestgreen", 11, 0x228b22}, + {"fuchsia", 7, 0xff00ff}, + {"gainsboro", 9, 0xdcdcdc}, + {"ghostwhite", 10, 0xf8f8ff}, + {"gold", 4, 0xffd700}, + {"goldenrod", 9, 0xdaa520}, + {"gray", 4, 0x808080}, + {"grey", 4, 0x808080}, + {"green", 5, 0x008000}, + {"greenyellow", 11, 0xadff2f}, + {"honeydew", 8, 0xf0fff0}, + {"hotpink", 7, 0xff69b4}, + {"indianred", 9, 0xcd5c5c}, + {"indigo", 6, 0x4b0082}, + {"ivory", 5, 0xfffff0}, + {"khaki", 5, 0xf0e68c}, + {"lavender", 8, 0xe6e6fa}, + {"lavenderblush", 13, 0xfff0f5}, + {"lawngreen", 9, 0x7cfc00}, + {"lemonchiffon", 12, 0xfffacd}, + {"lightblue", 9, 0xadd8e6}, + {"lightcoral", 10, 0xf08080}, + {"lightcyan", 9, 0xe0ffff}, + {"lightgoldenrodyellow", 20, 0xfafad2}, + {"lightgray", 9, 0xd3d3d3}, + {"lightgrey", 9, 0xd3d3d3}, + {"lightgreen", 10, 0x90ee90}, + {"lightpink", 9, 0xffb6c1}, + {"lightsalmon", 11, 0xffa07a}, + {"lightseagreen", 13, 0x20b2aa}, + {"lightskyblue", 12, 0x87cefa}, + {"lightslategray", 14, 0x778899}, + {"lightslategrey", 14, 0x778899}, + {"lightsteelblue", 14, 0xb0c4de}, + {"lightyellow", 11, 0xffffe0}, + {"lime", 4, 0x00ff00}, + {"limegreen", 9, 0x32cd32}, + {"linen", 5, 0xfaf0e6}, + {"magenta", 7, 0xff00ff}, + {"maroon", 6, 0x800000}, + {"mediumaquamarine", 16, 0x66cdaa}, + {"mediumblue", 10, 0x0000cd}, + {"mediumorchid", 12, 0xba55d3}, + {"mediumpurple", 12, 0x9370d8}, + {"mediumseagreen", 14, 0x3cb371}, + {"mediumslateblue", 15, 0x7b68ee}, + {"mediumspringgreen", 17, 0x00fa9a}, + {"mediumturquoise", 15, 0x48d1cc}, + {"mediumvioletred", 15, 0xc71585}, + {"midnightblue", 12, 0x191970}, + {"mintcream", 9, 0xf5fffa}, + {"mistyrose", 9, 0xffe4e1}, + {"moccasin", 8, 0xffe4b5}, + {"navajowhite", 11, 0xffdead}, + {"navy", 4, 0x000080}, + {"oldlace", 7, 0xfdf5e6}, + {"olive", 5, 0x808000}, + {"olivedrab", 9, 0x6b8e23}, + {"orange", 6, 0xffa500}, + {"orangered", 9, 0xff4500}, + {"orchid", 6, 0xda70d6}, + {"palegoldenrod", 13, 0xeee8aa}, + {"palegreen", 9, 0x98fb98}, + {"paleturquoise", 13, 0xafeeee}, + {"palevioletred", 13, 0xd87093}, + {"papayawhip", 10, 0xffefd5}, + {"peachpuff", 9, 0xffdab9}, + {"peru", 4, 0xcd853f}, + {"pink", 4, 0xffc0cb}, + {"plum", 4, 0xdda0dd}, + {"powderblue", 10, 0xb0e0e6}, + {"purple", 6, 0x800080}, + {"red", 3, 0xff0000}, + {"rosybrown", 9, 0xbc8f8f}, + {"royalblue", 9, 0x4169e1}, + {"saddlebrown", 11, 0x8b4513}, + {"salmon", 6, 0xfa8072}, + {"sandybrown", 10, 0xf4a460}, + {"seagreen", 8, 0x2e8b57}, + {"seashell", 8, 0xfff5ee}, + {"sienna", 6, 0xa0522d}, + {"silver", 6, 0xc0c0c0}, + {"skyblue", 7, 0x87ceeb}, + {"slateblue", 9, 0x6a5acd}, + {"slategray", 9, 0x708090}, + {"slategrey", 9, 0x708090}, + {"snow", 4, 0xfffafa}, + {"springgreen", 11, 0x00ff7f}, + {"steelblue", 9, 0x4682b4}, + {"tan", 3, 0xd2b48c}, + {"teal", 4, 0x008080}, + {"thistle", 7, 0xd8bfd8}, + {"tomato", 6, 0xff6347}, + {"turquoise", 9, 0x40e0d0}, + {"violet", 6, 0xee82ee}, + {"wheat", 5, 0xf5deb3}, + {"white", 5, 0xffffff}, + {"whitesmoke", 10, 0xf5f5f5}, + {"yellow", 6, 0xffff00}, + {"yellowgreen", 11, 0x9acd32}, +}; + +/********************** + * STATIC PROTOTYPES + **********************/ + +static lv_svg_tag_t _get_svg_tag_type(const _lv_svg_token_t * token) +{ + uint32_t len = MAP_LEN(_svg_tag_map); + uint32_t token_len = SVG_TOKEN_LEN(token); + + for(uint32_t i = 0; i < len; i++) { + if(token_len == _svg_tag_map[i].name_len && strncmp(_svg_tag_map[i].name, token->start, token_len) == 0) { + return _svg_tag_map[i].tag; + } + } + return LV_SVG_TAG_INVALID; +} + +static bool _process_end_tag(_lv_svg_parser_t * parser, lv_svg_tag_t tag, const _lv_svg_token_t * token) +{ + if(parser->state == LV_SVG_PARSER_IGNORE) { + uint32_t len = SVG_TOKEN_LEN(token); + if((parser->ignore_len == len) && strncmp(parser->ignore_name, token->start, len) == 0) { + parser->state = LV_SVG_PARSER_PROCESS; + lv_free(parser->ignore_name); + parser->ignore_name = NULL; + parser->ignore_len = 0; + } + return true; + } + + if(parser->cur_node->type != tag) { + LV_LOG_ERROR("svg tag does not match in pairs!"); + return false; + } + + if(parser->cur_node != parser->doc_root) { + parser->cur_node = (lv_svg_node_t *)LV_TREE_NODE(parser->cur_node)->parent; + } + return true; +} + +static lv_svg_attr_type_t _get_svg_attr_type(const char * attr_start, const char * attr_end) +{ + uint32_t len = MAP_LEN(_svg_attr_map); + uint32_t attr_len = attr_end - attr_start; + + for(uint32_t i = 0; i < len; i++) { + if(attr_len == _svg_attr_map[i].name_len && strncmp(_svg_attr_map[i].name, attr_start, attr_len) == 0) { + return _svg_attr_map[i].attr; + } + } + return LV_SVG_ATTR_INVALID; +} + +static void _process_string(lv_svg_node_t * node, lv_svg_attr_type_t type, const char * val_start, const char * val_end) +{ + CHECK_AND_RESIZE_ATTRS(node->attrs); + + node->attrs.size++; + lv_svg_attr_t * attr = lv_array_at(&node->attrs, node->attrs.size - 1); + attr->id = type; + attr->val_type = LV_SVG_ATTR_VALUE_PTR; + attr->class_type = LV_SVG_ATTR_VALUE_INITIAL; + + uint32_t len = val_end - val_start; + char * str = lv_malloc(len + 1); + LV_ASSERT_MALLOC(str); + lv_memcpy(str, val_start, len); + str[len] = '\0'; + attr->value.sval = str; +} + +static void _process_xlink(lv_svg_node_t * node, lv_svg_attr_type_t type, const char * val_start, const char * val_end) +{ + CHECK_AND_RESIZE_ATTRS(node->attrs); + + node->attrs.size++; + lv_svg_attr_t * attr = lv_array_at(&node->attrs, node->attrs.size - 1); + attr->id = type; + attr->val_type = LV_SVG_ATTR_VALUE_PTR; + attr->class_type = LV_SVG_ATTR_VALUE_INITIAL; + + if(*val_start == '#') { + val_start++; + } + + uint32_t len = val_end - val_start; + char * str = lv_malloc(len + 1); + LV_ASSERT_MALLOC(str); + lv_memcpy(str, val_start, len); + str[len] = '\0'; + attr->value.sval = str; +} + +static bool _is_number_begin(char ch) +{ + return ch != 0 && strchr("0123456789+-.", ch) != NULL; +} + +static const char * _skip_space(const char * str, const char * str_end) +{ + while((str < str_end) && isspace(*str)) { + ++str; + } + return str; +} + +static bool _is_separators(char c) +{ + return c == ',' || c == '\t' || c == '\n' || c == '\r'; +} + +static const char * _skip_space_and_separators(const char * str, const char * str_end) +{ + while((str < str_end) && (isspace(*str) || _is_separators(*str))) { + ++str; + } + return str; +} + +static const char * _parse_number(const char * str, const char * str_end, float * val) +{ + if(!str) { + return NULL; + } + // skip loading + while((str < str_end) && !_is_number_begin(*str)) { + ++str; + } + + if(str == str_end) { // parse fail + return NULL; + } + + char * end = NULL; + *val = strtof(str, &end); + return end; +} + +static const char * _parse_length(const char * str, const char * str_end, int32_t dpi, float * val) +{ + str = _parse_number(str, str_end, val); + if(str) { + uint32_t len = str_end - str; + if(len > 0) { + if(len == 1 && (*str == '%')) { + // percentage + *val *= 0.01f; + } + else if(len == 2) { + if(str[0] == 'p' && str[1] == 't') { // pt + *val = *val / 72.0f * (float)dpi; + } + else if(str[0] == 'p' && str[1] == 'c') { // pc + *val = *val / 6.0f * (float)dpi; + } + else if(str[0] == 'i' && str[1] == 'n') { // in + *val = *val * (float)dpi; + } + else if(str[0] == 'm' && str[1] == 'm') { // mm + *val = *val / 25.4f * (float)dpi; + } + else if(str[0] == 'c' && str[1] == 'm') { // cm + *val = *val / 2.54f * (float)dpi; + } + else if(str[0] == 'e' && str[1] == 'm') { // em + *val = *val * 16.0f; // FIXME: browser default font size + } + else if(str[0] == 'e' && str[1] == 'x') { // ex + *val = *val * 16.0f * 0.52f; + } + } + } + str += len; + } + return str; +} + +static const char * _parse_color(const char * str, const char * str_end, uint32_t * val) +{ + if(!str) { + return NULL; + } + + const char * ptr = str; + while((ptr < str_end) && (*ptr != ')')) { // calc letters end + ++ptr; + } + + uint32_t len = ptr - str; + uint8_t r = 0, g = 0, b = 0; + + if(*str == '#') { + if(len == 4) { // three digit hex format '#rgb' + if(isxdigit(str[1]) && isxdigit(str[2]) && isxdigit(str[3])) { + char st[3] = {0}; + st[0] = st[1] = str[1]; + r = (uint8_t)strtol(st, NULL, 16); + st[0] = st[1] = str[2]; + g = (uint8_t)strtol(st, NULL, 16); + st[0] = st[1] = str[3]; + b = (uint8_t)strtol(st, NULL, 16); + } + } + else if(len == 7) { // six digit hex format '#rrggbb' + if(isxdigit(str[1]) && isxdigit(str[2]) && isxdigit(str[3]) + && isxdigit(str[4]) && isxdigit(str[5]) && isxdigit(str[6])) { + char st[3] = {0}; + st[0] = str[1]; + st[1] = str[2]; + r = (uint8_t)strtol(st, NULL, 16); + st[0] = str[3]; + st[1] = str[4]; + g = (uint8_t)strtol(st, NULL, 16); + st[0] = str[5]; + st[1] = str[6]; + b = (uint8_t)strtol(st, NULL, 16); + } + } + // make color + *val = (r << 16) + (g << 8) + b; + } + else if(len > 4 && strncmp(str, "rgb(", 4) == 0) { + str += 4; + bool valid_color = true; + float vals[3] = {0}; + + for(int i = 0; i < 3; i++) { + str = _parse_number(str, ptr, &vals[i]); + if(!str) valid_color = false; + + if(*str == '%') { + vals[i] *= 2.56f; + } + } + + if(valid_color) { + r = (uint8_t)vals[0]; + g = (uint8_t)vals[1]; + b = (uint8_t)vals[2]; + } + // make color + *val = (r << 16) + (g << 8) + b; + } + else { // color keyword + uint32_t map_len = MAP_LEN(_svg_color_map); + for(uint32_t i = 0; i < map_len; i++) { + if(len == _svg_color_map[i].name_len && strncmp(_svg_color_map[i].name, str, len) == 0) { + *val = _svg_color_map[i].color; + } + } + } + return ++ptr; +} + +static void _multiply_matrix(lv_svg_matrix_t * matrix, const lv_svg_matrix_t * mul) +{ + // TODO: use NEON to optimize this function on ARM architecture. + lv_svg_matrix_t tmp; + + for(int y = 0; y < 3; y++) { + for(int x = 0; x < 3; x++) { + tmp.m[y][x] = (matrix->m[y][0] * mul->m[0][x]) + + (matrix->m[y][1] * mul->m[1][x]) + + (matrix->m[y][2] * mul->m[2][x]); + } + } + + lv_memcpy(matrix, &tmp, sizeof(lv_svg_matrix_t)); +} + +static const char * _parse_matrix(const char * str, const char * str_end, lv_svg_transform_type_t type, + lv_svg_matrix_t * matrix) +{ + // skip loading + while((str < str_end) && *str != '(') { + ++str; + } + + if(str == str_end) { // parse fail + return str; + } + + const char * ptr = str; + switch(type) { + case LV_SVG_TRANSFORM_TYPE_MATRIX: { + float vals[6] = {0}; + for(int i = 0; i < 6; i++) { + ptr = _parse_number(ptr, str_end, &vals[i]); + if(!ptr) return str; + str = ptr; + } + + lv_svg_matrix_t mt = {{ + {vals[0], vals[2], vals[4]}, + {vals[1], vals[3], vals[5]}, + {0.0f, 0.0f, 1.0f}, + } + }; + + _multiply_matrix(matrix, &mt); + } + break; + case LV_SVG_TRANSFORM_TYPE_TRANSLATE: { + float tx = 0.0f, ty = 0.0f; + ptr = _parse_number(ptr, str_end, &tx); + if(!ptr) return str; + str = ptr; + + ptr = _skip_space(ptr, str_end); + if(*ptr != ')') { + ptr = _parse_number(ptr, str_end, &ty); + if(ptr) str = ptr; + } + + lv_svg_matrix_t tlm = {{ + {1.0f, 0.0f, tx}, + {0.0f, 1.0f, ty}, + {0.0f, 0.0f, 1.0f}, + } + }; + + _multiply_matrix(matrix, &tlm); + } + break; + case LV_SVG_TRANSFORM_TYPE_ROTATE: { + float degree = 0.0f, cx = 0.0f, cy = 0.0f; + bool trans = false; + + ptr = _parse_number(ptr, str_end, °ree); + if(!ptr) return str; + str = ptr; + + ptr = _skip_space(ptr, str_end); + if(*ptr != ')') { + ptr = _parse_number(ptr, str_end, &cx); + ptr = _parse_number(ptr, str_end, &cy); + if(ptr) { + trans = true; + str = ptr; + } + } + + float radian = degree / 180.0f * (float)M_PI; + float cos_r = cosf(radian); + float sin_r = sinf(radian); + + lv_svg_matrix_t rtm = {{ + {cos_r, -sin_r, 0.0f}, + {sin_r, cos_r, 0.0f}, + {0.0f, 0.0f, 1.0f}, + } + }; + + if(!trans) { + _multiply_matrix(matrix, &rtm); + } + else { + lv_svg_matrix_t tlm = {{ + {1.0f, 0.0f, cx}, + {0.0f, 1.0f, cy}, + {0.0f, 0.0f, 1.0f}, + } + }; + + _multiply_matrix(matrix, &tlm); + _multiply_matrix(matrix, &rtm); + + tlm.m[0][2] = -cx; + tlm.m[1][2] = -cy; + _multiply_matrix(matrix, &tlm); + } + } + break; + case LV_SVG_TRANSFORM_TYPE_SCALE: { + float sx = 0.0f, sy = 0.0f; + ptr = _parse_number(ptr, str_end, &sx); + if(!ptr) return str; + str = ptr; + + sy = sx; + + ptr = _skip_space(ptr, str_end); + if(*ptr != ')') { + ptr = _parse_number(ptr, str_end, &sy); + if(ptr) str = ptr; + } + + lv_svg_matrix_t scm = {{ + {sx, 0.0f, 0.0f}, + {0.0f, sy, 0.0f}, + {0.0f, 0.0f, 1.0f}, + } + }; + + _multiply_matrix(matrix, &scm); + } + break; + case LV_SVG_TRANSFORM_TYPE_SKEW_X: { + float degree = 0.0f; + ptr = _parse_number(ptr, str_end, °ree); + if(!ptr) return str; + str = ptr; + + float radian = degree / 180.0f * (float)M_PI; + float tan = tanf(radian); + + lv_svg_matrix_t skm = {{ + {1.0f, tan, 0.0f}, + {0.0f, 1.0f, 0.0f}, + {0.0f, 0.0f, 1.0f}, + } + }; + + _multiply_matrix(matrix, &skm); + } + break; + case LV_SVG_TRANSFORM_TYPE_SKEW_Y: { + float degree = 0.0f; + ptr = _parse_number(ptr, str_end, °ree); + if(!ptr) return str; + str = ptr; + + float radian = degree / 180.0f * (float)M_PI; + float tan = tanf(radian); + + lv_svg_matrix_t skm = {{ + {1.0f, 0.0f, 0.0f}, + {tan, 1.0f, 0.0f}, + {0.0f, 0.0f, 1.0f}, + } + }; + + _multiply_matrix(matrix, &skm); + } + break; + } + return str; +} + +static void _process_view_box(lv_svg_node_t * node, lv_svg_attr_type_t type, const char * val_start, + const char * val_end) +{ + CHECK_AND_RESIZE_ATTRS(node->attrs); + + node->attrs.size++; + lv_svg_attr_t * attr = lv_array_at(&node->attrs, node->attrs.size - 1); + attr->id = type; + attr->val_type = LV_SVG_ATTR_VALUE_PTR; + attr->class_type = LV_SVG_ATTR_VALUE_INITIAL; + + uint32_t len = val_end - val_start; + if(len >= 4 && strncmp(val_start, "none", 4) == 0) { + attr->val_type = LV_SVG_ATTR_VALUE_DATA; + attr->class_type = LV_SVG_ATTR_VALUE_NONE; + return; + } + + float * vals = lv_malloc_zeroed(sizeof(float) * 4); + LV_ASSERT_MALLOC(vals); + const char * ptr = val_start; + for(int i = 0; i < 4; i++) { + ptr = _parse_number(ptr, val_end, &vals[i]); + if(!ptr) { + attr->val_type = LV_SVG_ATTR_VALUE_DATA; + attr->class_type = LV_SVG_ATTR_VALUE_NONE; + lv_free(vals); + return; + } + } + attr->value.val = vals; +} + +static void _process_points_value(lv_svg_node_t * node, lv_svg_attr_type_t type, const char * val_start, + const char * val_end) +{ + CHECK_AND_RESIZE_ATTRS(node->attrs); + + node->attrs.size++; + lv_svg_attr_t * attr = lv_array_at(&node->attrs, node->attrs.size - 1); + attr->id = type; + attr->val_type = LV_SVG_ATTR_VALUE_PTR; + attr->class_type = LV_SVG_ATTR_VALUE_INITIAL; + + uint32_t list_cap = 4; + lv_svg_attr_values_list_t * list = lv_malloc(sizeof(lv_svg_point_t) * list_cap + sizeof(uint32_t)); + LV_ASSERT_MALLOC(list); + + float val_number = 0.0f; + const char * ptr = val_start; + uint32_t point_cnt = 0; + + while(ptr < val_end) { + if(point_cnt == list_cap) { + list_cap = list_cap << 1; + list = (lv_svg_attr_values_list_t *)lv_realloc(list, sizeof(lv_svg_point_t) * list_cap + sizeof(uint32_t)); + LV_ASSERT_MALLOC(list); + } + lv_svg_point_t * pt = (lv_svg_point_t *)(&list->data) + point_cnt; + val_number = 0.0f; + ptr = _parse_number(ptr, val_end, &val_number); + pt->x = val_number; + val_number = 0.0f; + ptr = _parse_number(ptr, val_end, &val_number); + pt->y = val_number; + if(!ptr) break; + ++point_cnt; + } + + list->length = point_cnt; + attr->value.val = list; +} + +static int _get_path_point_count(char cmd) +{ + switch(cmd) { + case 'M': + case 'm': + case 'L': + case 'l': + case 'H': + case 'h': + case 'V': + case 'v': + case 'Z': + case 'z': + return 1; + case 'C': + case 'c': + case 'S': + case 's': + return 3; + case 'Q': + case 'q': + case 'T': + case 't': + return 2; + default: + return 0; + } +} + +static bool _is_relative_cmd(char cmd) +{ + switch(cmd) { + case 'm': + case 'l': + case 'h': + case 'v': + case 'c': + case 's': + case 'q': + case 't': + case 'z': + return true; + case 'M': + case 'L': + case 'H': + case 'V': + case 'C': + case 'S': + case 'Q': + case 'T': + case 'Z': + default: + return false; + } +} + +static bool _is_path_cmd(char ch) +{ + return ch != 0 && strchr("MLHVCSQTZmlhvcsqtz", ch) != NULL; +} + +static void _process_path_value(lv_svg_node_t * node, lv_svg_attr_type_t type, const char * val_start, + const char * val_end) +{ + CHECK_AND_RESIZE_ATTRS(node->attrs); + + node->attrs.size++; + lv_svg_attr_t * attr = lv_array_at(&node->attrs, node->attrs.size - 1); + attr->id = type; + attr->val_type = LV_SVG_ATTR_VALUE_PTR; + attr->class_type = LV_SVG_ATTR_VALUE_INITIAL; + + uint32_t list_cap = 4; + uint32_t list_size = sizeof(lv_svg_point_t) * list_cap + sizeof(uint32_t) * list_cap + sizeof(uint32_t); + lv_svg_attr_values_list_t * list = lv_malloc(list_size); + LV_ASSERT_MALLOC(list); + + uint32_t cmd_cnt = 0; + uint32_t cur_size = 0; + char cur_cmd = 0; + lv_svg_point_t cur_point = {0, 0}; + lv_svg_point_t cur_ctrlPoint = {0, 0}; + lv_svg_point_t first_point = {0, 0}; + + const char * ptr = val_start; + uint8_t * data_ptr = (uint8_t *)(&list->data); + + while(ptr < val_end) { + ptr = _skip_space_and_separators(ptr, val_end); + if(ptr == val_end) break; + + char ch = *ptr; + if(_is_number_begin(ch)) { + if(cur_cmd != 0) { + if(cur_cmd == 'M') { + ch = 'L'; + } + else if(cur_cmd == 'm') { + ch = 'l'; + } + else { + ch = cur_cmd; + } + } + else { + break; + } + } + else if(_is_path_cmd(ch)) { + ++ptr; + } + else { + break; + } + + int point_count = _get_path_point_count(ch); + uint32_t mem_inc = sizeof(lv_svg_point_t) * point_count + sizeof(uint32_t); + + if((cur_size + mem_inc) > (list_size - sizeof(uint32_t))) { + list_cap = list_cap << 1; + list_size = sizeof(lv_svg_point_t) * list_cap + sizeof(uint32_t) * list_cap + sizeof(uint32_t); + list = (lv_svg_attr_values_list_t *)lv_realloc(list, list_size); + LV_ASSERT_MALLOC(list); + } + + data_ptr = (uint8_t *)(&list->data) + cur_size; + lv_svg_attr_path_value_t * path_seg = (lv_svg_attr_path_value_t *)data_ptr; + + bool relative = _is_relative_cmd(ch); + + switch(ch) { + case 'm': + case 'M': { + lv_svg_point_t * point = (lv_svg_point_t *)(&path_seg->data); + float xval = 0.0f; + ptr = _parse_number(ptr, val_end, &xval); + float yval = 0.0f; + ptr = _parse_number(ptr, val_end, &yval); + if(relative) { + xval += cur_point.x; + yval += cur_point.y; + } + path_seg->cmd = LV_SVG_PATH_CMD_MOVE_TO; + point->x = xval; + point->y = yval; + cur_point.x = xval; + cur_point.y = yval; + first_point.x = xval; + first_point.y = yval; + } + break; + case 'L': + case 'l': { + lv_svg_point_t * point = (lv_svg_point_t *)(&path_seg->data); + float xval = 0.0f; + ptr = _parse_number(ptr, val_end, &xval); + float yval = 0.0f; + ptr = _parse_number(ptr, val_end, &yval); + if(relative) { + xval += cur_point.x; + yval += cur_point.y; + } + path_seg->cmd = LV_SVG_PATH_CMD_LINE_TO; + point->x = xval; + point->y = yval; + cur_point.x = xval; + cur_point.y = yval; + } + break; + case 'H': + case 'h': { + lv_svg_point_t * point = (lv_svg_point_t *)(&path_seg->data); + float xval = 0.0f; + ptr = _parse_number(ptr, val_end, &xval); + if(relative) { + xval += cur_point.x; + } + path_seg->cmd = LV_SVG_PATH_CMD_LINE_TO; + point->x = xval; + point->y = cur_point.y; + cur_point.x = xval; + } + break; + case 'V': + case 'v': { + lv_svg_point_t * point = (lv_svg_point_t *)(&path_seg->data); + float yval = 0.0f; + ptr = _parse_number(ptr, val_end, &yval); + if(relative) { + yval += cur_point.y; + } + path_seg->cmd = LV_SVG_PATH_CMD_LINE_TO; + point->x = cur_point.x; + point->y = yval; + cur_point.y = yval; + } + break; + case 'C': + case 'c': { + lv_svg_point_t * point = (lv_svg_point_t *)(&path_seg->data); + for(int i = 0; i < 3; i++) { + float xval = 0.0f; + ptr = _parse_number(ptr, val_end, &xval); + float yval = 0.0f; + ptr = _parse_number(ptr, val_end, &yval); + if(relative) { + xval += cur_point.x; + yval += cur_point.y; + } + path_seg->cmd = LV_SVG_PATH_CMD_CURVE_TO; + point[i].x = xval; + point[i].y = yval; + } + + cur_ctrlPoint.x = point[1].x; + cur_ctrlPoint.y = point[1].y; + cur_point.x = point[2].x; + cur_point.y = point[2].y; + } + break; + case 'S': + case 's': { + lv_svg_point_t * point = (lv_svg_point_t *)(&path_seg->data); + + if(cur_cmd == 'C' || cur_cmd == 'c' || cur_cmd == 'S' || cur_cmd == 's') { + point[0].x = cur_point.x * 2 - cur_ctrlPoint.x; + point[0].y = cur_point.y * 2 - cur_ctrlPoint.y; + } + else { + point[0].x = cur_point.x; + point[0].y = cur_point.y; + } + + for(int i = 1; i < 3; i++) { + float xval = 0.0f; + ptr = _parse_number(ptr, val_end, &xval); + float yval = 0.0f; + ptr = _parse_number(ptr, val_end, &yval); + if(relative) { + xval += cur_point.x; + yval += cur_point.y; + } + path_seg->cmd = LV_SVG_PATH_CMD_CURVE_TO; + point[i].x = xval; + point[i].y = yval; + } + + cur_ctrlPoint.x = point[1].x; + cur_ctrlPoint.y = point[1].y; + cur_point.x = point[2].x; + cur_point.y = point[2].y; + } + break; + case 'Q': + case 'q': { + lv_svg_point_t * point = (lv_svg_point_t *)(&path_seg->data); + for(int i = 0; i < 2; i++) { + float xval = 0.0f; + ptr = _parse_number(ptr, val_end, &xval); + float yval = 0.0f; + ptr = _parse_number(ptr, val_end, &yval); + if(relative) { + xval += cur_point.x; + yval += cur_point.y; + } + path_seg->cmd = LV_SVG_PATH_CMD_QUAD_TO; + point[i].x = xval; + point[i].y = yval; + } + + cur_ctrlPoint.x = point[0].x; + cur_ctrlPoint.y = point[0].y; + cur_point.x = point[1].x; + cur_point.y = point[1].y; + } + break; + case 'T': + case 't': { + lv_svg_point_t * point = (lv_svg_point_t *)(&path_seg->data); + if(cur_cmd == 'Q' || cur_cmd == 'q' || cur_cmd == 'T' || cur_cmd == 't') { + point[0].x = cur_point.x * 2 - cur_ctrlPoint.x; + point[0].y = cur_point.y * 2 - cur_ctrlPoint.y; + } + else { + point[0].x = cur_point.x; + point[0].y = cur_point.y; + } + + for(int i = 1; i < 2; i++) { + float xval = 0.0f; + ptr = _parse_number(ptr, val_end, &xval); + float yval = 0.0f; + ptr = _parse_number(ptr, val_end, &yval); + if(relative) { + xval += cur_point.x; + yval += cur_point.y; + } + path_seg->cmd = LV_SVG_PATH_CMD_QUAD_TO; + point[i].x = xval; + point[i].y = yval; + } + + cur_ctrlPoint.x = point[0].x; + cur_ctrlPoint.y = point[0].y; + cur_point.x = point[1].x; + cur_point.y = point[1].y; + } + break; + case 'Z': + case 'z': { + path_seg->cmd = LV_SVG_PATH_CMD_CLOSE; + cur_point.x = first_point.x; + cur_point.y = first_point.y; + } + break; + } + + if(!ptr) break; + cur_size += mem_inc; + cur_cmd = ch; + ++cmd_cnt; + } + + list->length = cmd_cnt; + attr->value.val = list; +} + +static void _process_gradient_units(lv_svg_node_t * node, lv_svg_attr_type_t type, const char * val_start, + const char * val_end) +{ + CHECK_AND_RESIZE_ATTRS(node->attrs); + + node->attrs.size++; + lv_svg_attr_t * attr = lv_array_at(&node->attrs, node->attrs.size - 1); + attr->id = type; + attr->val_type = LV_SVG_ATTR_VALUE_DATA; + attr->class_type = LV_SVG_ATTR_VALUE_INITIAL; + + uint32_t len = val_end - val_start; + int32_t val = 0; + + if(len == 14 && strncmp(val_start, "userSpaceOnUse", 14) == 0) { + val = LV_SVG_GRADIENT_UNITS_USER_SPACE; + } + else { + val = LV_SVG_GRADIENT_UNITS_OBJECT; + } + attr->value.ival = val; +} + +static void _process_paint_dasharray(lv_svg_node_t * node, lv_svg_attr_type_t type, const char * val_start, + const char * val_end) +{ + CHECK_AND_RESIZE_ATTRS(node->attrs); + + node->attrs.size++; + lv_svg_attr_t * attr = lv_array_at(&node->attrs, node->attrs.size - 1); + attr->id = type; + attr->val_type = LV_SVG_ATTR_VALUE_DATA; + attr->class_type = LV_SVG_ATTR_VALUE_INITIAL; + + uint32_t len = val_end - val_start; + + if(len >= 4 && strncmp(val_start, "none", 4) == 0) { + attr->class_type = LV_SVG_ATTR_VALUE_NONE; + return; + } + else if(len >= 7 && strncmp(val_start, "inherit", 7) == 0) { + attr->class_type = LV_SVG_ATTR_VALUE_INHERIT; + return; + } + else { + attr->val_type = LV_SVG_ATTR_VALUE_PTR; + + uint32_t list_cap = 4; + lv_svg_attr_values_list_t * list = lv_malloc(sizeof(float) * list_cap + sizeof(uint32_t)); + LV_ASSERT_MALLOC(list); + + uint32_t count = 0; + const char * ptr = val_start; + + while(ptr < val_end) { + if(count == list_cap) { + list_cap = list_cap << 1; + list = lv_realloc(list, sizeof(float) * list_cap + sizeof(uint32_t)); + LV_ASSERT_MALLOC(list); + } + float * val = (float *)(&list->data) + count; + ptr = _parse_number(ptr, val_end, val); + if(!ptr) break; + ++count; + } + + list->length = count; + attr->value.val = list; + } +} + +static void _process_font_attrs(lv_svg_node_t * node, lv_svg_attr_type_t type, const char * val_start, + const char * val_end, int32_t dpi) +{ + CHECK_AND_RESIZE_ATTRS(node->attrs); + + node->attrs.size++; + lv_svg_attr_t * attr = lv_array_at(&node->attrs, node->attrs.size - 1); + attr->id = type; + attr->val_type = LV_SVG_ATTR_VALUE_DATA; + attr->class_type = LV_SVG_ATTR_VALUE_INITIAL; + + uint32_t len = val_end - val_start; + + if(len >= 7 && strncmp(val_start, "inherit", 7) == 0) { + attr->class_type = LV_SVG_ATTR_VALUE_INHERIT; + return; + } + + if(type == LV_SVG_ATTR_FONT_SIZE && _is_number_begin(*val_start)) { + float val_number = 0.0f; + val_start = _parse_length(val_start, val_end, dpi, &val_number); + attr->value.fval = val_number; + } + else { + attr->val_type = LV_SVG_ATTR_VALUE_PTR; + + char * str = lv_malloc(len + 1); + LV_ASSERT_MALLOC(str); + lv_memcpy(str, val_start, len); + str[len] = '\0'; + attr->value.sval = str; + } +} + +static void _process_paint_attrs(lv_svg_node_t * node, lv_svg_attr_type_t type, const char * val_start, + const char * val_end) +{ + CHECK_AND_RESIZE_ATTRS(node->attrs); + + node->attrs.size++; + lv_svg_attr_t * attr = lv_array_at(&node->attrs, node->attrs.size - 1); + attr->id = type; + attr->val_type = LV_SVG_ATTR_VALUE_DATA; + attr->class_type = LV_SVG_ATTR_VALUE_INITIAL; + + uint32_t len = val_end - val_start; + + if(len >= 7 && strncmp(val_start, "inherit", 7) == 0) { + attr->class_type = LV_SVG_ATTR_VALUE_INHERIT; + return; + } + + if(type == LV_SVG_ATTR_FILL_RULE) { + int32_t val = 0; + if(strncmp(val_start, "evenodd", 7) == 0) { + val = LV_SVG_FILL_EVENODD; + } + else { + val = LV_SVG_FILL_NONZERO; + } + attr->value.ival = val; + } + else if(type == LV_SVG_ATTR_STROKE_LINECAP) { + int32_t val = 0; + if(strncmp(val_start, "round", 5) == 0) { + val = LV_SVG_LINE_CAP_ROUND; + } + else if(strncmp(val_start, "square", 6) == 0) { + val = LV_SVG_LINE_CAP_SQUARE; + } + else { + val = LV_SVG_LINE_CAP_BUTT; + } + attr->value.ival = val; + } + else if(type == LV_SVG_ATTR_STROKE_LINEJOIN) { + int32_t val = 0; + if(strncmp(val_start, "round", 5) == 0) { + val = LV_SVG_LINE_JOIN_ROUND; + } + else if(strncmp(val_start, "bevel", 5) == 0) { + val = LV_SVG_LINE_JOIN_BEVEL; + } + else { + val = LV_SVG_LINE_JOIN_MITER; + } + attr->value.ival = val; + } + else if(type == LV_SVG_ATTR_STROKE_WIDTH) { + float val = 1.0f; + val_start = _parse_number(val_start, val_end, &val); + if(val < 0.0f) { + val = 0.0f; + } + attr->value.fval = val; + } + else if(type == LV_SVG_ATTR_STROKE_MITER_LIMIT) { + float val = 4.0f; + val_start = _parse_number(val_start, val_end, &val); + if(val < 1.0f) { + val = 1.0f; + } + attr->value.ival = (int32_t)val; + } + else if(type == LV_SVG_ATTR_STROKE_DASH_OFFSET) { + float val = 0.0f; + val_start = _parse_number(val_start, val_end, &val); + attr->value.fval = val; + } + else if(type == LV_SVG_ATTR_GRADIENT_STOP_OFFSET) { + float val = 0.0f; + val_start = _parse_number(val_start, val_end, &val); + attr->value.fval = val; + } +} + +static void _process_paint(lv_svg_node_t * node, lv_svg_attr_type_t type, const char * val_start, + const char * val_end) +{ + CHECK_AND_RESIZE_ATTRS(node->attrs); + + node->attrs.size++; + lv_svg_attr_t * attr = lv_array_at(&node->attrs, node->attrs.size - 1); + attr->id = type; + attr->val_type = LV_SVG_ATTR_VALUE_DATA; + attr->class_type = LV_SVG_ATTR_VALUE_INITIAL; + + uint32_t len = val_end - val_start; + if(len >= 4 && strncmp(val_start, "none", 4) == 0) { + attr->class_type = LV_SVG_ATTR_VALUE_NONE; + return; + } + else if(len >= 7 && strncmp(val_start, "inherit", 7) == 0) { + attr->class_type = LV_SVG_ATTR_VALUE_INHERIT; + return; + } + else if(len > 4 && strncmp(val_start, "url(", 4) == 0) { + // parse url + const char * ptr = val_start + 4; + const char * url_start = NULL; + const char * url_end = NULL; + + ptr = _skip_space(ptr, val_end); + if(ptr == val_end) { + attr->class_type = LV_SVG_ATTR_VALUE_NONE; + return; + } + + if(*ptr == '#') { + url_start = ptr + 1; + } + + while((ptr < val_end) && !isspace(*ptr) && *ptr != ')') { + ++ptr; + } + + url_end = ptr; + if(url_start && url_end) { + attr->val_type = LV_SVG_ATTR_VALUE_PTR; + len = url_end - url_start; + char * node_id = lv_malloc(len + 1); + LV_ASSERT_MALLOC(node_id); + lv_memcpy(node_id, url_start, len); + node_id[len] = '\0'; + attr->value.sval = node_id; + } + return; + } + else { +#if LV_USE_SVG_ANIMATION + if(len == 6) { + if(strncmp(val_start, "freeze", 6) == 0) { + attr->value.ival = LV_SVG_ANIM_FREEZE; + return; + } + else if(strncmp(val_start, "remove", 6) == 0) { + attr->value.ival = LV_SVG_ANIM_REMOVE; + return; + } + } +#endif + // parse color + uint32_t color = 0; + _parse_color(val_start, val_end, &color); + attr->value.uval = color; + return; + } +} + +static void _process_opacity_value(lv_svg_node_t * node, lv_svg_attr_type_t type, const char * val_start, + const char * val_end) +{ + CHECK_AND_RESIZE_ATTRS(node->attrs); + + node->attrs.size++; + lv_svg_attr_t * attr = lv_array_at(&node->attrs, node->attrs.size - 1); + attr->id = type; + attr->val_type = LV_SVG_ATTR_VALUE_DATA; + attr->class_type = LV_SVG_ATTR_VALUE_INITIAL; + + uint32_t len = val_end - val_start; + + if(len >= 7 && strncmp(val_start, "inherit", 7) == 0) { + attr->class_type = LV_SVG_ATTR_VALUE_INHERIT; + return; + } + + float val_number = 1.0f; + val_start = _parse_number(val_start, val_end, &val_number); + + if(val_number < 0.0f) val_number = 0.0f; + else if(val_number > 1.0f) val_number = 1.0f; + + attr->value.fval = val_number; +} + +static void _process_length_value(lv_svg_node_t * node, lv_svg_attr_type_t type, const char * val_start, + const char * val_end, int32_t dpi) +{ + CHECK_AND_RESIZE_ATTRS(node->attrs); + + node->attrs.size++; + lv_svg_attr_t * attr = lv_array_at(&node->attrs, node->attrs.size - 1); + attr->id = type; + attr->val_type = LV_SVG_ATTR_VALUE_DATA; + attr->class_type = LV_SVG_ATTR_VALUE_INITIAL; + + float val_number = 0.0f; + val_start = _parse_length(val_start, val_end, dpi, &val_number); + attr->value.fval = val_number; +} + +static void _process_transform(lv_svg_node_t * node, lv_svg_attr_type_t type, const char * val_start, + const char * val_end) +{ + CHECK_AND_RESIZE_ATTRS(node->attrs); + + node->attrs.size++; + lv_svg_attr_t * attr = lv_array_at(&node->attrs, node->attrs.size - 1); + attr->id = type; + attr->val_type = LV_SVG_ATTR_VALUE_PTR; + attr->class_type = LV_SVG_ATTR_VALUE_INITIAL; + + uint32_t len = val_end - val_start; + if(len >= 4 && strncmp(val_start, "none", 4) == 0) { + attr->val_type = LV_SVG_ATTR_VALUE_DATA; + attr->class_type = LV_SVG_ATTR_VALUE_NONE; + return; + } + + lv_svg_matrix_t * matrix = lv_malloc_zeroed(sizeof(lv_svg_matrix_t)); + LV_ASSERT_MALLOC(matrix); + matrix->m[0][0] = matrix->m[1][1] = matrix->m[2][2] = 1.0f; // identity + + const char * ptr = val_start; + while(ptr < val_end) { + ptr = _skip_space(ptr, val_end); + if(ptr == val_end) break; + + len = val_end - ptr; + + if(len >= 9 && strncmp(ptr, "translate", 9) == 0) { + ptr = _parse_matrix(ptr, val_end, LV_SVG_TRANSFORM_TYPE_TRANSLATE, matrix); + } + else if(len >= 6 && strncmp(ptr, "matrix", 6) == 0) { + ptr = _parse_matrix(ptr, val_end, LV_SVG_TRANSFORM_TYPE_MATRIX, matrix); + } + else if(len >= 6 && strncmp(ptr, "rotate", 6) == 0) { + ptr = _parse_matrix(ptr, val_end, LV_SVG_TRANSFORM_TYPE_ROTATE, matrix); + } + else if(len >= 5 && strncmp(ptr, "scale", 5) == 0) { + ptr = _parse_matrix(ptr, val_end, LV_SVG_TRANSFORM_TYPE_SCALE, matrix); + } + else if(len >= 5 && strncmp(ptr, "skewX", 5) == 0) { + ptr = _parse_matrix(ptr, val_end, LV_SVG_TRANSFORM_TYPE_SKEW_X, matrix); + } + else if(len >= 5 && strncmp(ptr, "skewY", 5) == 0) { + ptr = _parse_matrix(ptr, val_end, LV_SVG_TRANSFORM_TYPE_SKEW_Y, matrix); + } + + ++ptr; + } + attr->value.val = matrix; +} + +static void _process_preserve_aspect_ratio(lv_svg_node_t * node, lv_svg_attr_type_t type, const char * val_start, + const char * val_end) +{ + CHECK_AND_RESIZE_ATTRS(node->attrs); + + node->attrs.size++; + lv_svg_attr_t * attr = lv_array_at(&node->attrs, node->attrs.size - 1); + attr->id = type; + attr->val_type = LV_SVG_ATTR_VALUE_DATA; + attr->class_type = LV_SVG_ATTR_VALUE_INITIAL; + + lv_svg_aspect_ratio_t ratio = LV_SVG_ASPECT_RATIO_XMID_YMID; + uint32_t len = MAP_LEN(_svg_attr_aspect_ratio_map); + + for(uint32_t i = 0; i < len; i++) { + if(strncmp(_svg_attr_aspect_ratio_map[i].name, val_start, 8) == 0) { + ratio = _svg_attr_aspect_ratio_map[i].align; + val_start += 8; + break; + } + else if(strncmp("none", val_start, 4) == 0) { + ratio = LV_SVG_ASPECT_RATIO_NONE; + val_start += 4; + break; + } + } + + if(ratio != LV_SVG_ASPECT_RATIO_NONE) { + len = val_end - val_start; + if(len > 4) { + val_start = _skip_space(val_start, val_end); + if(strncmp(val_start, "meet", 4) == 0) { + ratio |= LV_SVG_ASPECT_RATIO_OPT_MEET; + } + else if(strncmp(val_start, "slice", 5) == 0) { + ratio |= LV_SVG_ASPECT_RATIO_OPT_SLICE; + } + } + } + attr->value.uval = ratio; +} + +#if LV_USE_SVG_ANIMATION +typedef void(*_parse_list_cb)(lv_svg_node_t * node, lv_svg_attr_t * attr, const char * val_start, const char * val_end, + int32_t dpi, void * data); + +static uint32_t _parse_anim_value_list(lv_svg_node_t * node, lv_svg_attr_t * attr, const char * val_start, + const char * val_end, int32_t dpi, _parse_list_cb cb, void * data) +{ + uint32_t count = 0; + val_start = _skip_space(val_start, val_end); + const char * ptr = val_start; + + while(ptr != val_end) { + if(*ptr == ';') { + cb(node, attr, val_start, ptr, dpi, data); + val_start = ++ptr; + val_start = _skip_space(val_start, val_end); + count++; + } + else { + ++ptr; + } + } + if(val_start < val_end) { + cb(node, attr, val_start, ptr, dpi, data); + count++; + } + return count; +} + +static const char * _parse_clock_time(const char * str, const char * str_end, float * val) +{ + str = _parse_number(str, str_end, val); + if(str) { + uint32_t len = str_end - str; + if(len > 0) { + if(len >= 2 && str[0] == 'm' && str[1] == 's') { + *val = roundf(*val); + } + else { + *val = roundf(*val * 1000.0f); + } + } + else { + *val = roundf(*val * 1000.0f); + } + str += len; + return str; + } + *val = roundf(*val * 1000.0f); + return str; +} + +static void _process_clock_time(lv_svg_node_t * node, lv_svg_attr_type_t type, const char * val_start, + const char * val_end) +{ + CHECK_AND_RESIZE_ATTRS(node->attrs); + + node->attrs.size++; + lv_svg_attr_t * attr = lv_array_at(&node->attrs, node->attrs.size - 1); + attr->id = type; + attr->val_type = LV_SVG_ATTR_VALUE_DATA; + attr->class_type = LV_SVG_ATTR_VALUE_INITIAL; + + uint32_t len = val_end - val_start; + if(len == 10 && strncmp(val_start, "indefinite", 10) == 0) { + attr->value.fval = 0.0f; + return; + } + + float val_number = 0.0f; + val_start = _parse_clock_time(val_start, val_end, &val_number); + attr->value.fval = val_number; // ms +} + +static void _process_anim_attr_number(lv_svg_node_t * node, lv_svg_attr_type_t type, const char * val_start, + const char * val_end) +{ + CHECK_AND_RESIZE_ATTRS(node->attrs); + + node->attrs.size++; + lv_svg_attr_t * attr = lv_array_at(&node->attrs, node->attrs.size - 1); + attr->id = type; + attr->val_type = LV_SVG_ATTR_VALUE_DATA; + attr->class_type = LV_SVG_ATTR_VALUE_INITIAL; + + if(type == LV_SVG_ATTR_REPEAT_COUNT) { + uint32_t len = val_end - val_start; + if(len == 10 && strncmp(val_start, "indefinite", 10) == 0) { + attr->value.uval = 0; + return; + } + + float val_number = 0.0f; + val_start = _parse_number(val_start, val_end, &val_number); + attr->value.uval = (uint32_t)val_number; + } + else { // LV_SVG_ATTR_ROTATE + uint32_t len = val_end - val_start; + if(len == 4 && strncmp(val_start, "auto", 4) == 0) { + attr->class_type = + LV_SVG_ATTR_VALUE_INHERIT; // rotated over time by the angle of the direction (i.e., directional tangent vector) of the motion path + attr->value.fval = 0.0f; + return; + } + else if(len == 12 && strncmp(val_start, "auto-reverse", 12) == 0) { + attr->class_type = + LV_SVG_ATTR_VALUE_INHERIT; // rotated over time by the angle of the direction (i.e., directional tangent vector) of the motion path plus 180 degrees. + attr->value.fval = 180.0f; + return; + } + + float val_number = 0.0f; + val_start = _parse_number(val_start, val_end, &val_number); + attr->value.fval = val_number; + } +} + +static void _process_anim_attr_names(lv_svg_node_t * node, lv_svg_attr_type_t type, const char * val_start, + const char * val_end) +{ + CHECK_AND_RESIZE_ATTRS(node->attrs); + + node->attrs.size++; + lv_svg_attr_t * attr = lv_array_at(&node->attrs, node->attrs.size - 1); + attr->id = type; + attr->val_type = LV_SVG_ATTR_VALUE_DATA; + attr->class_type = LV_SVG_ATTR_VALUE_INITIAL; + attr->value.ival = _get_svg_attr_type(val_start, val_end); +} + +static void _process_anim_attr_options(lv_svg_node_t * node, lv_svg_attr_type_t type, const char * val_start, + const char * val_end) +{ + CHECK_AND_RESIZE_ATTRS(node->attrs); + + node->attrs.size++; + lv_svg_attr_t * attr = lv_array_at(&node->attrs, node->attrs.size - 1); + attr->id = type; + attr->val_type = LV_SVG_ATTR_VALUE_DATA; + attr->class_type = LV_SVG_ATTR_VALUE_INITIAL; + + uint32_t len = val_end - val_start; + switch(type) { + case LV_SVG_ATTR_RESTART: { + if(len == 6 && strncmp(val_start, "always", 6) == 0) { + attr->value.ival = LV_SVG_ANIM_RESTART_ALWAYS; + return; + } + else if(len == 13 && strncmp(val_start, "whenNotActive", 13) == 0) { + attr->value.ival = LV_SVG_ANIM_RESTART_WHEN_NOT_ACTIVE; + return; + } + else if(len == 5 && strncmp(val_start, "never", 5) == 0) { + attr->value.ival = LV_SVG_ANIM_RESTART_NEVER; + return; + } + } + break; + case LV_SVG_ATTR_CALC_MODE: { + if(len == 6 && strncmp(val_start, "linear", 6) == 0) { + attr->value.ival = LV_SVG_ANIM_CALC_MODE_LINEAR; + return; + } + else if(len == 5 && strncmp(val_start, "paced", 5) == 0) { + attr->value.ival = LV_SVG_ANIM_CALC_MODE_PACED; + return; + } + else if(len == 6 && strncmp(val_start, "spline", 6) == 0) { + attr->value.ival = LV_SVG_ANIM_CALC_MODE_SPLINE; + return; + } + else if(len == 8 && strncmp(val_start, "discrete", 8) == 0) { + attr->value.ival = LV_SVG_ANIM_CALC_MODE_DISCRETE; + return; + } + } + break; + case LV_SVG_ATTR_ADDITIVE: { + if(len == 7 && strncmp(val_start, "replace", 7) == 0) { + attr->value.ival = LV_SVG_ANIM_ADDITIVE_REPLACE; + return; + } + else if(len == 3 && strncmp(val_start, "sum", 3) == 0) { + attr->value.ival = LV_SVG_ANIM_ADDITIVE_SUM; + return; + } + } + break; + case LV_SVG_ATTR_ACCUMULATE: { + if(len == 4 && strncmp(val_start, "none", 4) == 0) { + attr->value.ival = LV_SVG_ANIM_ACCUMULATE_NONE; + return; + } + else if(len == 3 && strncmp(val_start, "sum", 3) == 0) { + attr->value.ival = LV_SVG_ANIM_ACCUMULATE_SUM; + return; + } + } + break; + case LV_SVG_ATTR_TRANSFORM_TYPE: { + if(len == 9 && strncmp(val_start, "translate", 9) == 0) { + attr->value.ival = LV_SVG_TRANSFORM_TYPE_TRANSLATE; + return; + } + else if(len == 5 && strncmp(val_start, "scale", 5) == 0) { + attr->value.ival = LV_SVG_TRANSFORM_TYPE_SCALE; + return; + } + else if(len == 6 && strncmp(val_start, "rotate", 6) == 0) { + attr->value.ival = LV_SVG_TRANSFORM_TYPE_ROTATE; + return; + } + else if(len == 5 && strncmp(val_start, "skewX", 5) == 0) { + attr->value.ival = LV_SVG_TRANSFORM_TYPE_SKEW_X; + return; + } + else if(len == 5 && strncmp(val_start, "skewY", 5) == 0) { + attr->value.ival = LV_SVG_TRANSFORM_TYPE_SKEW_Y; + return; + } + } + break; + } + attr->value.ival = 0; +} + +static void _parse_anim_value(lv_svg_node_t * node, lv_svg_attr_t * attr, const char * val_start, const char * val_end, + int32_t dpi) +{ + if(node->type == LV_SVG_TAG_ANIMATE || node->type == LV_SVG_TAG_SET) { + float val_number = 0.0f; + val_start = _parse_length(val_start, val_end, dpi, &val_number); + attr->value.fval = val_number; + } + else if(node->type == LV_SVG_TAG_ANIMATE_COLOR) { + uint32_t color = 0; + val_start = _parse_color(val_start, val_end, &color); + attr->value.uval = color; + } + else if(node->type == LV_SVG_TAG_ANIMATE_TRANSFORM) { + attr->val_type = LV_SVG_ATTR_VALUE_PTR; + lv_svg_attr_values_list_t * list = lv_malloc(sizeof(float) * 4 + sizeof(uint32_t)); + LV_ASSERT_MALLOC(list); + + float val_number = 0.0f; + uint32_t cnt = 0; + const char * ptr = val_start; + + while((ptr < val_end) && (cnt < 3)) { + float * val = (float *)(&list->data) + cnt; + + val_number = 0.0f; + ptr = _parse_number(ptr, val_end, &val_number); + *val = val_number; + + if(!ptr) break; + ++cnt; + } + + list->length = cnt; + attr->value.val = list; + } + else if(node->type == LV_SVG_TAG_ANIMATE_MOTION) { + attr->val_type = LV_SVG_ATTR_VALUE_PTR; + lv_svg_attr_values_list_t * list = lv_malloc(sizeof(lv_svg_point_t) + sizeof(uint32_t)); + LV_ASSERT_MALLOC(list); + + lv_svg_point_t * pt = (lv_svg_point_t *)(&list->data); + + float val_number = 0.0f; + val_start = _parse_number(val_start, val_end, &val_number); + pt->x = val_number; + val_number = 0.0f; + val_start = _parse_number(val_start, val_end, &val_number); + pt->y = val_number; + + list->length = 1; + attr->value.val = list; + } +} + +struct _parse_value_list_context { + uint32_t mem_size; + uint32_t list_count; + lv_svg_attr_values_list_t * list; +}; + +struct _transform_values_list { + uint32_t length; + float data[4]; +}; + +#define GET_NEXT_VALUE_PTR(ptr, ctx, type) \ + do { \ + lv_svg_attr_values_list_t * list = ctx->list; \ + if(!list) { \ + ctx->mem_size = sizeof(type) * 4 + sizeof(uint32_t);\ + ctx->list = lv_malloc_zeroed(ctx->mem_size); \ + LV_ASSERT_MALLOC(ctx->list); \ + ptr = (type *)(&(ctx->list->data)); \ + ctx->list_count = 1; \ + } else { \ + uint32_t mem = sizeof(type) * (ctx->list_count + 1) + sizeof(uint32_t); \ + if(ctx->mem_size < mem) { \ + ctx->mem_size = (ctx->list_count << 1) * sizeof(type) + sizeof(uint32_t); \ + ctx->list = (lv_svg_attr_values_list_t *)lv_realloc(ctx->list, ctx->mem_size); \ + LV_ASSERT_MALLOC(ctx->list); \ + } \ + ptr = (type *)(&(ctx->list->data)) + ctx->list_count; \ + ctx->list_count++; \ + } \ + } while(0) + +static void _anim_values_cb(lv_svg_node_t * node, lv_svg_attr_t * attr, const char * val_start, const char * val_end, + int32_t dpi, void * data) +{ + LV_UNUSED(attr); + struct _parse_value_list_context * ctx = (struct _parse_value_list_context *)data; + + if(node->type == LV_SVG_TAG_ANIMATE || node->type == LV_SVG_TAG_SET) { + float * val_number = NULL; + GET_NEXT_VALUE_PTR(val_number, ctx, float); + val_start = _parse_length(val_start, val_end, dpi, val_number); + } + else if(node->type == LV_SVG_TAG_ANIMATE_COLOR) { + uint32_t * color = NULL; + GET_NEXT_VALUE_PTR(color, ctx, uint32_t); + val_start = _parse_color(val_start, val_end, color); + } + else if(node->type == LV_SVG_TAG_ANIMATE_TRANSFORM) { + struct _transform_values_list * trans_vals = NULL; + GET_NEXT_VALUE_PTR(trans_vals, ctx, struct _transform_values_list); + + uint32_t cnt = 0; + const char * ptr = val_start; + + while((ptr < val_end) && (cnt < 3)) { + float * val = &(trans_vals->data[cnt]); + ptr = _parse_number(ptr, val_end, val); + if(!ptr) break; + ++cnt; + } + + trans_vals->length = cnt; + } + else if(node->type == LV_SVG_TAG_ANIMATE_MOTION) { + lv_svg_point_t * point = NULL; + GET_NEXT_VALUE_PTR(point, ctx, lv_svg_point_t); + val_start = _parse_number(val_start, val_end, &point->x); + val_start = _parse_number(val_start, val_end, &point->y); + } + ctx->list->length = ctx->list_count; +} + +static void _anim_keys_cb(lv_svg_node_t * node, lv_svg_attr_t * attr, const char * val_start, const char * val_end, + int32_t dpi, void * data) +{ + LV_UNUSED(node); + LV_UNUSED(attr); + LV_UNUSED(dpi); + struct _parse_value_list_context * ctx = (struct _parse_value_list_context *)data; + + float * val_number = NULL; + GET_NEXT_VALUE_PTR(val_number, ctx, float); + val_start = _parse_number(val_start, val_end, val_number); + + ctx->list->length = ctx->list_count; +} + +static void _anim_key_splines_cb(lv_svg_node_t * node, lv_svg_attr_t * attr, const char * val_start, + const char * val_end, int32_t dpi, void * data) +{ + LV_UNUSED(node); + LV_UNUSED(attr); + LV_UNUSED(dpi); + struct _parse_value_list_context * ctx = (struct _parse_value_list_context *)data; + + lv_svg_point_t * point = NULL; + GET_NEXT_VALUE_PTR(point, ctx, lv_svg_point_t); + val_start = _parse_number(val_start, val_end, &point->x); + val_start = _parse_number(val_start, val_end, &point->y); + + GET_NEXT_VALUE_PTR(point, ctx, lv_svg_point_t); + val_start = _parse_number(val_start, val_end, &point->x); + val_start = _parse_number(val_start, val_end, &point->y); + + ctx->list->length = ctx->list_count; +} + +static void _anim_begin_end_cb(lv_svg_node_t * node, lv_svg_attr_t * attr, const char * val_start, + const char * val_end, int32_t dpi, void * data) +{ + LV_UNUSED(node); + LV_UNUSED(attr); + LV_UNUSED(dpi); + struct _parse_value_list_context * ctx = (struct _parse_value_list_context *)data; + + // offset-value + float * val_number = NULL; + GET_NEXT_VALUE_PTR(val_number, ctx, float); + val_start = _parse_clock_time(val_start, val_end, val_number); + + //FIXME: not support begin-end type + // syncbase-value + // event-value + // repeat-value + // accessKey-value + // indefinite + + ctx->list->length = ctx->list_count; +} + +static void _process_anim_attr_values(lv_svg_node_t * node, lv_svg_attr_type_t type, const char * val_start, + const char * val_end, int32_t dpi) +{ + CHECK_AND_RESIZE_ATTRS(node->attrs); + + node->attrs.size++; + lv_svg_attr_t * attr = lv_array_at(&node->attrs, node->attrs.size - 1); + attr->id = type; + attr->val_type = LV_SVG_ATTR_VALUE_DATA; + attr->class_type = LV_SVG_ATTR_VALUE_INITIAL; + + if(type == LV_SVG_ATTR_VALUES) { + attr->val_type = LV_SVG_ATTR_VALUE_PTR; + struct _parse_value_list_context ctx = {.mem_size = 0, .list_count = 0, .list = NULL}; + _parse_anim_value_list(node, attr, val_start, val_end, dpi, _anim_values_cb, &ctx); + attr->value.val = ctx.list; + } + else if(type == LV_SVG_ATTR_KEY_TIMES || type == LV_SVG_ATTR_KEY_POINTS) { + attr->val_type = LV_SVG_ATTR_VALUE_PTR; + struct _parse_value_list_context ctx = {.mem_size = 0, .list_count = 0, .list = NULL}; + _parse_anim_value_list(node, attr, val_start, val_end, dpi, _anim_keys_cb, &ctx); + attr->value.val = ctx.list; + } + else if(type == LV_SVG_ATTR_KEY_SPLINES) { + attr->val_type = LV_SVG_ATTR_VALUE_PTR; + struct _parse_value_list_context ctx = {.mem_size = 0, .list_count = 0, .list = NULL}; + _parse_anim_value_list(node, attr, val_start, val_end, dpi, _anim_key_splines_cb, &ctx); + attr->value.val = ctx.list; + } + else if(type == LV_SVG_ATTR_BEGIN || type == LV_SVG_ATTR_END) { + attr->val_type = LV_SVG_ATTR_VALUE_PTR; + struct _parse_value_list_context ctx = {.mem_size = 0, .list_count = 0, .list = NULL}; + _parse_anim_value_list(node, attr, val_start, val_end, dpi, _anim_begin_end_cb, &ctx); + attr->value.val = ctx.list; + } + else { + _parse_anim_value(node, attr, val_start, val_end, dpi); + } +} + +#endif + +static void _process_attrs_tag(_lv_svg_parser_t * parser, lv_svg_node_t * node, const _lv_svg_token_t * token) +{ + uint32_t len = lv_array_size(&token->attrs); + for(uint32_t i = 0; i < len; i++) { + _lv_svg_token_attr_t * tok_attr = lv_array_at(&token->attrs, i); + lv_svg_attr_type_t type = _get_svg_attr_type(tok_attr->name_start, tok_attr->name_end); + + tok_attr->value_start = _skip_space(tok_attr->value_start, tok_attr->value_end); + uint32_t value_len = tok_attr->value_end - tok_attr->value_start; + if(value_len == 0) { + continue; // skip empty value attribute + } + + if(type == LV_SVG_ATTR_XML_ID || type == LV_SVG_ATTR_ID) { // get xml:id + char * str = lv_malloc(value_len + 1); + LV_ASSERT_MALLOC(str); + lv_memcpy(str, tok_attr->value_start, value_len); + str[value_len] = '\0'; + node->xml_id = str; + continue; + } + + switch(type) { + case LV_SVG_ATTR_VERSION: + case LV_SVG_ATTR_BASE_PROFILE: + _process_string(node, type, tok_attr->value_start, tok_attr->value_end); + break; + case LV_SVG_ATTR_VIEWBOX: + _process_view_box(node, type, tok_attr->value_start, tok_attr->value_end); + break; + case LV_SVG_ATTR_PRESERVE_ASPECT_RATIO: + _process_preserve_aspect_ratio(node, type, tok_attr->value_start, tok_attr->value_end); + break; + case LV_SVG_ATTR_X: + case LV_SVG_ATTR_Y: + case LV_SVG_ATTR_WIDTH: + case LV_SVG_ATTR_HEIGHT: + case LV_SVG_ATTR_RX: + case LV_SVG_ATTR_RY: + case LV_SVG_ATTR_CX: + case LV_SVG_ATTR_CY: + case LV_SVG_ATTR_R: + case LV_SVG_ATTR_X1: + case LV_SVG_ATTR_Y1: + case LV_SVG_ATTR_X2: + case LV_SVG_ATTR_Y2: + case LV_SVG_ATTR_PATH_LENGTH: + _process_length_value(node, type, tok_attr->value_start, tok_attr->value_end, parser->dpi); + break; + case LV_SVG_ATTR_OPACITY: + case LV_SVG_ATTR_FILL_OPACITY: + case LV_SVG_ATTR_STROKE_OPACITY: + case LV_SVG_ATTR_SOLID_OPACITY: + case LV_SVG_ATTR_VIEWPORT_FILL_OPACITY: + case LV_SVG_ATTR_GRADIENT_STOP_OPACITY: + _process_opacity_value(node, type, tok_attr->value_start, tok_attr->value_end); + break; + case LV_SVG_ATTR_POINTS: + _process_points_value(node, type, tok_attr->value_start, tok_attr->value_end); + break; + case LV_SVG_ATTR_D: +#if LV_USE_SVG_ANIMATION + case LV_SVG_ATTR_PATH: +#endif + _process_path_value(node, type, tok_attr->value_start, tok_attr->value_end); + break; + case LV_SVG_ATTR_TRANSFORM: + _process_transform(node, type, tok_attr->value_start, tok_attr->value_end); + break; + case LV_SVG_ATTR_FILL: + case LV_SVG_ATTR_STROKE: + case LV_SVG_ATTR_VIEWPORT_FILL: + case LV_SVG_ATTR_SOLID_COLOR: + case LV_SVG_ATTR_GRADIENT_STOP_COLOR: + _process_paint(node, type, tok_attr->value_start, tok_attr->value_end); + break; + case LV_SVG_ATTR_FILL_RULE: + case LV_SVG_ATTR_STROKE_LINECAP: + case LV_SVG_ATTR_STROKE_LINEJOIN: + case LV_SVG_ATTR_STROKE_WIDTH: + case LV_SVG_ATTR_STROKE_MITER_LIMIT: + case LV_SVG_ATTR_STROKE_DASH_OFFSET: + case LV_SVG_ATTR_GRADIENT_STOP_OFFSET: + _process_paint_attrs(node, type, tok_attr->value_start, tok_attr->value_end); + break; + case LV_SVG_ATTR_STROKE_DASH_ARRAY: + _process_paint_dasharray(node, type, tok_attr->value_start, tok_attr->value_end); + break; + case LV_SVG_ATTR_GRADIENT_UNITS: + _process_gradient_units(node, type, tok_attr->value_start, tok_attr->value_end); + break; + case LV_SVG_ATTR_FONT_FAMILY: + case LV_SVG_ATTR_FONT_STYLE: + case LV_SVG_ATTR_FONT_VARIANT: + case LV_SVG_ATTR_FONT_WEIGHT: + case LV_SVG_ATTR_FONT_SIZE: + _process_font_attrs(node, type, tok_attr->value_start, tok_attr->value_end, parser->dpi); + break; + case LV_SVG_ATTR_XLINK_HREF: + _process_xlink(node, type, tok_attr->value_start, tok_attr->value_end); + break; +#if LV_USE_SVG_ANIMATION + case LV_SVG_ATTR_DUR: + case LV_SVG_ATTR_MIN: + case LV_SVG_ATTR_MAX: + case LV_SVG_ATTR_REPEAT_DUR: + _process_clock_time(node, type, tok_attr->value_start, tok_attr->value_end); + break; + case LV_SVG_ATTR_ATTRIBUTE_NAME: + _process_anim_attr_names(node, type, tok_attr->value_start, tok_attr->value_end); + break; + case LV_SVG_ATTR_FROM: + case LV_SVG_ATTR_TO: + case LV_SVG_ATTR_BY: + case LV_SVG_ATTR_VALUES: + case LV_SVG_ATTR_KEY_TIMES: + case LV_SVG_ATTR_KEY_POINTS: + case LV_SVG_ATTR_KEY_SPLINES: + case LV_SVG_ATTR_BEGIN: + case LV_SVG_ATTR_END: + _process_anim_attr_values(node, type, tok_attr->value_start, tok_attr->value_end, parser->dpi); + break; + case LV_SVG_ATTR_ROTATE: + case LV_SVG_ATTR_REPEAT_COUNT: + _process_anim_attr_number(node, type, tok_attr->value_start, tok_attr->value_end); + break; + case LV_SVG_ATTR_RESTART: + case LV_SVG_ATTR_CALC_MODE: + case LV_SVG_ATTR_ADDITIVE: + case LV_SVG_ATTR_ACCUMULATE: + case LV_SVG_ATTR_TRANSFORM_TYPE: + _process_anim_attr_options(node, type, tok_attr->value_start, tok_attr->value_end); + break; + case LV_SVG_ATTR_ATTRIBUTE_TYPE: +#endif + case LV_SVG_ATTR_DISPLAY: + case LV_SVG_ATTR_VISIBILITY: + case LV_SVG_ATTR_TEXT_ANCHOR: + // not support yet + break; + } + } +} + +static bool _process_begin_tag(_lv_svg_parser_t * parser, lv_svg_tag_t tag, const _lv_svg_token_t * token) +{ + if(parser->state == LV_SVG_PARSER_IGNORE) { + // ignore ignored tokens + return true; + } + + if(token->type == LV_SVG_TOKEN_CONTENT) { + uint32_t len = SVG_TOKEN_LEN(token); + char * content = lv_malloc(len + 1); + LV_ASSERT_MALLOC(content); + lv_memcpy(content, token->start, len); + content[len] = '\0'; + lv_svg_node_t * node = lv_svg_node_create(parser->cur_node); + node->xml_id = content; + node->type = LV_SVG_TAG_CONTENT; + return true; + } + + // begin invalid tag + if(tag == LV_SVG_TAG_INVALID) { + if(!token->flat) { + parser->state = LV_SVG_PARSER_IGNORE; + uint32_t len = SVG_TOKEN_LEN(token); + parser->ignore_name = lv_malloc(len + 1); + LV_ASSERT_MALLOC(parser->ignore_name); + parser->ignore_len = len; + lv_memcpy(parser->ignore_name, token->start, len); + parser->ignore_name[len] = '\0'; + } + return true; + } + + // create new node + lv_svg_node_t * node = lv_svg_node_create(parser->cur_node); + node->type = tag; + _process_attrs_tag(parser, node, token); + + if(!parser->doc_root) { // root node + parser->doc_root = node; + } + if(!token->flat) { // FIXME: not leaf node + parser->cur_node = node; + } + return true; +} + +/********************** + * GLOBAL FUNCTIONS + **********************/ +void _lv_svg_parser_init(_lv_svg_parser_t * parser) +{ + LV_ASSERT_NULL(parser); + lv_memzero(parser, sizeof(_lv_svg_parser_t)); + parser->state = LV_SVG_PARSER_PROCESS; + parser->ignore_name = NULL; + parser->ignore_len = 0; + parser->dpi = 96; // FIXME: Is it right ? + parser->doc_root = NULL; + parser->cur_node = NULL; +} + +void _lv_svg_parser_deinit(_lv_svg_parser_t * parser) +{ + LV_ASSERT_NULL(parser); + if(parser->ignore_name) { + lv_free(parser->ignore_name); + parser->ignore_name = NULL; + parser->ignore_len = 0; + } + + if(parser->doc_root) { + lv_svg_node_delete(parser->doc_root); + } + parser->doc_root = parser->cur_node = NULL; +} + +bool _lv_svg_parser_is_finish(_lv_svg_parser_t * parser) +{ + LV_ASSERT_NULL(parser); + return (parser->doc_root != NULL) + && (parser->cur_node == parser->doc_root) + && (parser->state != LV_SVG_PARSER_IGNORE); +} + +bool _lv_svg_parser_token(_lv_svg_parser_t * parser, const _lv_svg_token_t * token) +{ + LV_ASSERT_NULL(parser); + LV_ASSERT_NULL(token); + lv_svg_tag_t tag = _get_svg_tag_type(token); + + if(parser->doc_root == NULL) { + if(!(tag == LV_SVG_TAG_SVG && token->type == LV_SVG_TOKEN_BEGIN)) { + LV_LOG_ERROR("root element in svg document must be !"); + return false; + } + } + + if(token->type == LV_SVG_TOKEN_END) { + return _process_end_tag(parser, tag, token); + } + + return _process_begin_tag(parser, tag, token); +} + +#if LV_USE_SVG_DEBUG +#include +void _lv_svg_dump_tree(lv_svg_node_t * root, int depth) +{ + if(!root) { + return; + } + + for(int i = 0; i < depth; i++) { + printf(" "); + } + if(root->type == LV_SVG_TAG_CONTENT) { + printf("content: [%s]\n", root->xml_id); + } + else { + printf("tag <%s>", _svg_tag_map[root->type - 1].name); + if(root->xml_id) { + printf(" - id [%s]", root->xml_id); + } + printf("\n"); + } + + uint32_t len = lv_array_size(&root->attrs); + for(uint32_t i = 0; i < len; i++) { + for(int j = 0; j < depth; j++) { + printf(" "); + } + lv_svg_attr_t * attr = lv_array_at(&root->attrs, i); + printf(" attr <%s>\n", _svg_attr_map[attr->id - 1].name); + } + + lv_tree_node_t * tree_root = (lv_tree_node_t *)root; + + for(uint32_t i = 0; i < tree_root->child_cnt; i++) { + ++depth; + _lv_svg_dump_tree((lv_svg_node_t *)tree_root->children[i], depth); + --depth; + } +} +#endif /*LV_USE_SVG_DEBUG*/ + +/********************** + * STATIC FUNCTIONS + **********************/ +#endif /*LV_USE_SVG*/ diff --git a/src/libs/svg/lv_svg_parser.h b/src/libs/svg/lv_svg_parser.h new file mode 100644 index 0000000000..fc99fe8bd2 --- /dev/null +++ b/src/libs/svg/lv_svg_parser.h @@ -0,0 +1,84 @@ +/** + * @file lv_svg_parser.h + * + */ + +#ifndef LV_SVG_PARSER_H +#define LV_SVG_PARSER_H + +/********************* + * INCLUDES + *********************/ +#include "../../lv_conf_internal.h" +#if LV_USE_SVG + +#include "lv_svg.h" +#include "lv_svg_token.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +typedef enum { + LV_SVG_PARSER_PROCESS = 0, + LV_SVG_PARSER_IGNORE, +} _lv_svg_parser_state_t; + +typedef struct { + uint16_t state; + char * ignore_name; + uint32_t ignore_len; + int32_t dpi; + lv_svg_node_t * doc_root; + lv_svg_node_t * cur_node; +} _lv_svg_parser_t; + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * @brief Initialize the SVG parser + * @param parser pointer to a parser object + */ +void _lv_svg_parser_init(_lv_svg_parser_t * parser); + +/** + * @brief Deinitialize the SVG parser + * @param parser pointer to a parser object + */ +void _lv_svg_parser_deinit(_lv_svg_parser_t * parser); + +/** + * @brief Parse an SVG document + * @param parser pointer to a parser object + * @param token pointer to a token object + * @return true: the parsing is finished, false: the parsing is not finished yet. + */ +bool _lv_svg_parser_token(_lv_svg_parser_t * parser, const _lv_svg_token_t * token); + +/** + * @brief Check if the parsing is finished + * @param parser pointer to a parser object + * @return true: the parsing is finished, false: the parsing is not finished yet. + */ +bool _lv_svg_parser_is_finish(_lv_svg_parser_t * parser); + +/** + * @brief Dump the SVG tree + * @param root pointer to the root of the SVG tree + * @param depth the depth of the current node in the tree + */ +void _lv_svg_dump_tree(lv_svg_node_t * root, int depth); + +/********************** + * MACROS + **********************/ + +#endif /*LV_USE_SVG*/ + +#endif /*LV_SVG_PARSER_H*/ diff --git a/src/libs/svg/lv_svg_render.c b/src/libs/svg/lv_svg_render.c new file mode 100644 index 0000000000..9980a89d79 --- /dev/null +++ b/src/libs/svg/lv_svg_render.c @@ -0,0 +1,2204 @@ +/** + * @file lv_svg_render.c + * + */ + +/********************* + * INCLUDES + *********************/ + +#include "../../../lvgl.h" +#include "lv_svg_render.h" +#if LV_USE_SVG + +#include "../../misc/lv_text_private.h" +#include +#include + +#if LV_USE_FREETYPE + #include "../../libs/freetype/lv_freetype_private.h" +#endif + +/********************* +* DEFINES +*********************/ +#ifndef M_PI + #define M_PI 3.1415926f +#endif + +#define MIN(a,b) (((a)<(b))?(a):(b)) +#define MAX(a,b) (((a)>(b))?(a):(b)) +#define ABS(a) fabsf(a) + +#define CALC_BOUNDS(p, bounds) \ + do { \ + if((p).x < (bounds).x1) (bounds).x1 = (int32_t)(p).x; \ + if((p).y < (bounds).y1) (bounds).y1 = (int32_t)(p).y; \ + if((p).x > (bounds).x2) (bounds).x2 = (int32_t)(p).x; \ + if((p).y > (bounds).y2) (bounds).y2 = (int32_t)(p).y; \ + } while(0) + +#define PCT_TO_PX(v, base) ((v) > 1 ? (v) : ((v) * (base))) + +enum { + _RENDER_NORMAL = 0, + _RENDER_IN_DEFS = 1, + _RENDER_IN_GROUP = 2, +}; + +enum { + _RENDER_ATTR_FILL = (4 << 1), + _RENDER_ATTR_FILL_RULE = (4 << 2), + _RENDER_ATTR_FILL_OPACITY = (4 << 3), + _RENDER_ATTR_STROKE = (4 << 4), + _RENDER_ATTR_STROKE_OPACITY = (4 << 5), + _RENDER_ATTR_STROKE_WIDTH = (4 << 6), + _RENDER_ATTR_STROKE_LINECAP = (4 << 7), + _RENDER_ATTR_STROKE_LINEJOIN = (4 << 8), + _RENDER_ATTR_STROKE_MITER_LIMIT = (4 << 9), + _RENDER_ATTR_STROKE_DASH_ARRAY = (4 << 10), +}; + +/********************** +* TYPEDEFS +**********************/ +static void _set_attr(lv_svg_render_obj_t * obj, lv_vector_draw_dsc_t * dsc, const lv_svg_attr_t * attr); +static void _init_draw_dsc(lv_vector_draw_dsc_t * dsc); +static void _deinit_draw_dsc(lv_vector_draw_dsc_t * dsc); +static void _copy_draw_dsc(lv_vector_draw_dsc_t * dst, const lv_vector_draw_dsc_t * src); +static void _prepare_render(const lv_svg_render_obj_t * obj, lv_vector_dsc_t * dsc); +static void _special_render(const lv_svg_render_obj_t * obj, lv_vector_dsc_t * dsc); +#if LV_USE_FREETYPE + static void _freetype_outline_cb(lv_event_t * e); +#endif + +typedef struct { + lv_svg_render_obj_t base; + float width; + float height; + bool viewport_fill; +} lv_svg_render_viewport_t; + +typedef struct { + lv_svg_render_obj_t base; + lv_array_t items; +} lv_svg_render_group_t; + +typedef struct { + lv_svg_render_obj_t base; + float x; + float y; + char * xlink; +} lv_svg_render_use_t; + +typedef struct { + lv_svg_render_obj_t base; + lv_color_t color; + float opacity; +} lv_svg_render_solid_t; + +typedef struct { + lv_svg_render_obj_t base; + lv_vector_gradient_t dsc; + lv_svg_gradient_units_t units; +} lv_svg_render_gradient_t; + +typedef struct { + lv_svg_render_obj_t base; + float x; + float y; + float width; + float height; + float rx; + float ry; +} lv_svg_render_rect_t; + +typedef struct { + lv_svg_render_obj_t base; + float cx; + float cy; + float r; +} lv_svg_render_circle_t; + +typedef struct { + lv_svg_render_obj_t base; + float cx; + float cy; + float rx; + float ry; +} lv_svg_render_ellipse_t; + +typedef struct { + lv_svg_render_obj_t base; + float x1; + float y1; + float x2; + float y2; +} lv_svg_render_line_t; + +typedef struct { + lv_svg_render_obj_t base; + lv_vector_path_t * path; + lv_area_t bounds; +} lv_svg_render_poly_t; + +typedef struct { + lv_svg_render_obj_t base; + float x; + float y; + float width; + float height; + lv_draw_image_dsc_t img_dsc; + lv_svg_aspect_ratio_t ratio; +} lv_svg_render_image_t; + +#if LV_USE_FREETYPE +typedef struct { + lv_svg_render_obj_t base; + lv_array_t contents; + char * family; + float size; + lv_freetype_font_style_t style; + lv_font_t * font; + float x; + float y; + lv_vector_path_t * path; + lv_area_t bounds; +} lv_svg_render_text_t; + +typedef struct _lv_svg_render_content { + lv_svg_render_obj_t base; + void (*render_content)(const struct _lv_svg_render_content * content, + lv_vector_dsc_t * dsc, lv_matrix_t * matrix); + uint32_t * letters; + uint32_t count; +} lv_svg_render_content_t; + +typedef struct { + lv_svg_render_content_t base; + char * family; + float size; + lv_freetype_font_style_t style; + lv_font_t * font; + lv_vector_path_t * path; + lv_area_t bounds; +} lv_svg_render_tspan_t; +#endif + +struct _lv_svg_draw_dsc { + struct _lv_svg_draw_dsc * next; + lv_vector_draw_dsc_t dsc; + const char * fill_ref; + const char * stroke_ref; +}; + +struct _lv_svg_drawing_builder_state { + const lv_svg_node_t * doc; + struct _lv_svg_draw_dsc * draw_dsc; + int in_group_deps; + bool in_defs; +#if LV_USE_FREETYPE + bool in_text; + lv_svg_node_t * cur_text; +#endif + lv_svg_render_obj_t * list; + lv_svg_render_obj_t * tail; +}; + +/********************** + * STATIC VARIABLES + **********************/ +static lv_svg_render_hal_t hal_funcs = {NULL}; + +/********************** + * STATIC PROTOTYPES + **********************/ +void lv_svg_render_init(const lv_svg_render_hal_t * hal) +{ + if(hal) { + hal_funcs = *hal; +#if LV_USE_FREETYPE + lv_freetype_outline_add_event(_freetype_outline_cb, LV_EVENT_ALL, NULL); +#endif + } +} + +static struct _lv_svg_draw_dsc * _lv_svg_draw_dsc_create(void) +{ + struct _lv_svg_draw_dsc * dsc = lv_malloc_zeroed(sizeof(struct _lv_svg_draw_dsc)); + LV_ASSERT_MALLOC(dsc); + _init_draw_dsc(&(dsc->dsc)); + return dsc; +} + +static void _lv_svg_draw_dsc_delete(struct _lv_svg_draw_dsc * dsc) +{ + while(dsc) { + struct _lv_svg_draw_dsc * cur = dsc; + dsc = dsc->next; + _deinit_draw_dsc(&(cur->dsc)); + lv_free(cur); + } +} + +static struct _lv_svg_draw_dsc * _lv_svg_draw_dsc_push(struct _lv_svg_draw_dsc * dsc) +{ + if(!dsc) return NULL; + struct _lv_svg_draw_dsc * cur = lv_malloc_zeroed(sizeof(struct _lv_svg_draw_dsc)); + LV_ASSERT_MALLOC(cur); + _copy_draw_dsc(&(cur->dsc), &(dsc->dsc)); + cur->fill_ref = dsc->fill_ref; + cur->stroke_ref = dsc->stroke_ref; + cur->next = dsc; + return cur; +} + +static struct _lv_svg_draw_dsc * _lv_svg_draw_dsc_pop(struct _lv_svg_draw_dsc * dsc) +{ + if(!dsc) return NULL; + struct _lv_svg_draw_dsc * cur = dsc->next; + lv_free(dsc); + return cur; +} + +static void _set_viewport_attr(lv_svg_render_obj_t * obj, lv_vector_draw_dsc_t * dsc, const lv_svg_attr_t * attr) +{ + lv_svg_render_viewport_t * view = (lv_svg_render_viewport_t *)obj; + switch(attr->id) { + case LV_SVG_ATTR_WIDTH: + view->width = attr->value.fval; + break; + case LV_SVG_ATTR_HEIGHT: + view->height = attr->value.fval; + break; + case LV_SVG_ATTR_VIEWBOX: { + if(attr->class_type == LV_SVG_ATTR_VALUE_INITIAL) { + float * vals = attr->value.val; + float scale_x = 1.0f; + float scale_y = 1.0f; + float trans_x = vals[0]; + float trans_y = vals[1]; + + if(view->width > 0 && vals[2] > 0) { + scale_x = view->width / vals[2]; + } + if(view->height > 0 && vals[3] > 0) { + scale_y = view->height / vals[3]; + } + + lv_matrix_scale(&obj->matrix, scale_x, scale_y); + lv_matrix_translate(&obj->matrix, -trans_x, -trans_y); + } + } + break; + case LV_SVG_ATTR_VIEWPORT_FILL: { + if(attr->class_type == LV_SVG_ATTR_VALUE_INITIAL + && attr->val_type == LV_SVG_ATTR_VALUE_DATA) { + dsc->fill_dsc.color = lv_color_to_32(lv_color_hex(attr->value.uval), 0xFF); + view->viewport_fill = true; + } + else if(attr->class_type == LV_SVG_ATTR_VALUE_NONE) { + view->viewport_fill = false; + } + } + break; + case LV_SVG_ATTR_VIEWPORT_FILL_OPACITY: { + if(attr->class_type == LV_SVG_ATTR_VALUE_INITIAL) { + dsc->fill_dsc.opa = (lv_opa_t)(attr->value.fval * 255.0f); + } + } + break; + } +} + +static void _set_use_attr(lv_svg_render_obj_t * obj, lv_vector_draw_dsc_t * dsc, const lv_svg_attr_t * attr) +{ + _set_attr(obj, dsc, attr); + lv_svg_render_use_t * use = (lv_svg_render_use_t *)obj; + switch(attr->id) { + case LV_SVG_ATTR_X: + use->x = attr->value.fval; + break; + case LV_SVG_ATTR_Y: + use->y = attr->value.fval; + break; + case LV_SVG_ATTR_XLINK_HREF: { + if(use->xlink) lv_free(use->xlink); + use->xlink = lv_strdup(attr->value.sval); + } + break; + } +} + +static void _set_solid_attr(lv_svg_render_obj_t * obj, lv_vector_draw_dsc_t * dsc, const lv_svg_attr_t * attr) +{ + LV_UNUSED(dsc); + lv_svg_render_solid_t * solid = (lv_svg_render_solid_t *)obj; + switch(attr->id) { + case LV_SVG_ATTR_SOLID_COLOR: + solid->color = lv_color_hex(attr->value.uval); + break; + case LV_SVG_ATTR_SOLID_OPACITY: + solid->opacity = attr->value.fval; + break; + } +} + +static void _set_gradient_attr(lv_svg_render_obj_t * obj, lv_vector_draw_dsc_t * dsc, const lv_svg_attr_t * attr) +{ + LV_UNUSED(dsc); + lv_svg_render_gradient_t * grad = (lv_svg_render_gradient_t *)obj; + switch(attr->id) { + case LV_SVG_ATTR_CX: + grad->dsc.cx = attr->value.fval; + break; + case LV_SVG_ATTR_CY: + grad->dsc.cy = attr->value.fval; + break; + case LV_SVG_ATTR_R: + grad->dsc.cr = attr->value.fval; + break; + case LV_SVG_ATTR_X1: + grad->dsc.x1 = attr->value.fval; + break; + case LV_SVG_ATTR_Y1: + grad->dsc.y1 = attr->value.fval; + break; + case LV_SVG_ATTR_X2: + grad->dsc.x2 = attr->value.fval; + break; + case LV_SVG_ATTR_Y2: + grad->dsc.y2 = attr->value.fval; + break; + case LV_SVG_ATTR_GRADIENT_UNITS: + grad->units = attr->value.ival; + break; + } +} + +static void _set_rect_attr(lv_svg_render_obj_t * obj, lv_vector_draw_dsc_t * dsc, const lv_svg_attr_t * attr) +{ + _set_attr(obj, dsc, attr); + lv_svg_render_rect_t * rect = (lv_svg_render_rect_t *)obj; + switch(attr->id) { + case LV_SVG_ATTR_X: + rect->x = attr->value.fval; + break; + case LV_SVG_ATTR_Y: + rect->y = attr->value.fval; + break; + case LV_SVG_ATTR_WIDTH: + rect->width = attr->value.fval; + break; + case LV_SVG_ATTR_HEIGHT: + rect->height = attr->value.fval; + break; + case LV_SVG_ATTR_RX: + rect->rx = attr->value.fval; + break; + case LV_SVG_ATTR_RY: + rect->ry = attr->value.fval; + break; + } +} + +static void _set_circle_attr(lv_svg_render_obj_t * obj, lv_vector_draw_dsc_t * dsc, const lv_svg_attr_t * attr) +{ + _set_attr(obj, dsc, attr); + lv_svg_render_circle_t * circle = (lv_svg_render_circle_t *)obj; + switch(attr->id) { + case LV_SVG_ATTR_CX: + circle->cx = attr->value.fval; + break; + case LV_SVG_ATTR_CY: + circle->cy = attr->value.fval; + break; + case LV_SVG_ATTR_R: + circle->r = attr->value.fval; + break; + } +} + +static void _set_ellipse_attr(lv_svg_render_obj_t * obj, lv_vector_draw_dsc_t * dsc, const lv_svg_attr_t * attr) +{ + _set_attr(obj, dsc, attr); + lv_svg_render_ellipse_t * ellipse = (lv_svg_render_ellipse_t *)obj; + switch(attr->id) { + case LV_SVG_ATTR_CX: + ellipse->cx = attr->value.fval; + break; + case LV_SVG_ATTR_CY: + ellipse->cy = attr->value.fval; + break; + case LV_SVG_ATTR_RX: + ellipse->rx = attr->value.fval; + break; + case LV_SVG_ATTR_RY: + ellipse->ry = attr->value.fval; + break; + } +} + +static void _set_line_attr(lv_svg_render_obj_t * obj, lv_vector_draw_dsc_t * dsc, const lv_svg_attr_t * attr) +{ + _set_attr(obj, dsc, attr); + lv_svg_render_line_t * line = (lv_svg_render_line_t *)obj; + switch(attr->id) { + case LV_SVG_ATTR_X1: + line->x1 = attr->value.fval; + break; + case LV_SVG_ATTR_Y1: + line->y1 = attr->value.fval; + break; + case LV_SVG_ATTR_X2: + line->x2 = attr->value.fval; + break; + case LV_SVG_ATTR_Y2: + line->y2 = attr->value.fval; + break; + } +} + +static void _set_polyline_attr(lv_svg_render_obj_t * obj, lv_vector_draw_dsc_t * dsc, const lv_svg_attr_t * attr) +{ + _set_attr(obj, dsc, attr); + lv_svg_render_poly_t * poly = (lv_svg_render_poly_t *)obj; + if(attr->id == LV_SVG_ATTR_POINTS) { + lv_vector_path_clear(poly->path); + lv_svg_attr_values_list_t * vals = (lv_svg_attr_values_list_t *)(attr->value.val); + uint32_t len = vals->length; + lv_svg_point_t * points = (lv_svg_point_t *)(&vals->data); + + CALC_BOUNDS(points[0], poly->bounds); + lv_fpoint_t pt = {points[0].x, points[0].y}; + lv_vector_path_move_to(poly->path, &pt); + for(uint32_t i = 1; i < len; i++) { + pt.x = points[i].x; + pt.y = points[i].y; + lv_vector_path_line_to(poly->path, &pt); + CALC_BOUNDS(pt, poly->bounds); + } + } +} + +static void _set_polygen_attr(lv_svg_render_obj_t * obj, lv_vector_draw_dsc_t * dsc, const lv_svg_attr_t * attr) +{ + _set_polyline_attr(obj, dsc, attr); + lv_svg_render_poly_t * poly = (lv_svg_render_poly_t *)obj; + if(attr->id == LV_SVG_ATTR_POINTS) { + lv_vector_path_close(poly->path); + } +} + +static size_t _get_path_seg_size(uint32_t cmd) +{ + switch(cmd) { + case LV_SVG_PATH_CMD_MOVE_TO: + case LV_SVG_PATH_CMD_LINE_TO: + case LV_SVG_PATH_CMD_CLOSE: + return sizeof(lv_svg_point_t) + sizeof(uint32_t); + case LV_SVG_PATH_CMD_QUAD_TO: + return sizeof(lv_svg_point_t) * 2 + sizeof(uint32_t); + case LV_SVG_PATH_CMD_CURVE_TO: + return sizeof(lv_svg_point_t) * 3 + sizeof(uint32_t); + default: + return 0; + } +} + +static void _set_path_attr(lv_svg_render_obj_t * obj, lv_vector_draw_dsc_t * dsc, const lv_svg_attr_t * attr) +{ + _set_attr(obj, dsc, attr); + lv_svg_render_poly_t * poly = (lv_svg_render_poly_t *)obj; + if(attr->id == LV_SVG_ATTR_D) { + lv_vector_path_clear(poly->path); + lv_svg_attr_values_list_t * vals = (lv_svg_attr_values_list_t *)(attr->value.val); + uint32_t len = vals->length; + uint8_t * data_ptr = (uint8_t *)(&vals->data); + + for(uint32_t i = 0; i < len; i++) { + lv_svg_attr_path_value_t * path_seg = (lv_svg_attr_path_value_t *)data_ptr; + uint32_t cmd = path_seg->cmd; + lv_svg_point_t * points = (lv_svg_point_t *)(&path_seg->data); + switch(cmd) { + case LV_SVG_PATH_CMD_MOVE_TO: { + lv_fpoint_t pt = {points[0].x, points[0].y}; + lv_vector_path_move_to(poly->path, &pt); + CALC_BOUNDS(pt, poly->bounds); + } + break; + case LV_SVG_PATH_CMD_LINE_TO: { + lv_fpoint_t pt = {points[0].x, points[0].y}; + lv_vector_path_line_to(poly->path, &pt); + CALC_BOUNDS(pt, poly->bounds); + } + break; + case LV_SVG_PATH_CMD_QUAD_TO: { + lv_fpoint_t pt[2] = { + {points[0].x, points[0].y}, + {points[1].x, points[1].y} + }; + lv_vector_path_quad_to(poly->path, &pt[0], &pt[1]); + CALC_BOUNDS(pt[0], poly->bounds); + CALC_BOUNDS(pt[1], poly->bounds); + } + break; + case LV_SVG_PATH_CMD_CURVE_TO: { + lv_fpoint_t pt[3] = { + {points[0].x, points[0].y}, + {points[1].x, points[1].y}, + {points[2].x, points[2].y} + }; + lv_vector_path_cubic_to(poly->path, &pt[0], &pt[1], &pt[2]); + CALC_BOUNDS(pt[0], poly->bounds); + CALC_BOUNDS(pt[1], poly->bounds); + CALC_BOUNDS(pt[2], poly->bounds); + } + break; + case LV_SVG_PATH_CMD_CLOSE: { + lv_vector_path_close(poly->path); + } + break; + } + size_t mem_inc = _get_path_seg_size(cmd); + data_ptr += mem_inc; + } + } +} + +#if LV_USE_FREETYPE +#define SET_FONT_ATTRS(obj, attr) \ + do { \ + switch(attr->id) { \ + case LV_SVG_ATTR_FONT_FAMILY: { \ + if(attr->val_type == LV_SVG_ATTR_VALUE_PTR) { \ + if((obj)->font) { \ + lv_freetype_font_delete((obj)->font); \ + (obj)->font = NULL; \ + } \ + lv_vector_path_clear((obj)->path); \ + if((obj)->family) lv_free((obj)->family); \ + (obj)->family = lv_strdup(attr->value.sval); \ + } \ + } \ + break; \ + case LV_SVG_ATTR_FONT_SIZE: \ + if(attr->class_type == LV_SVG_ATTR_VALUE_INITIAL) { \ + if(attr->val_type == LV_SVG_ATTR_VALUE_DATA) { \ + if((obj)->font) { \ + lv_freetype_font_delete((obj)->font); \ + (obj)->font = NULL; \ + } \ + (obj)->size = attr->value.fval; \ + lv_vector_path_clear((obj)->path); \ + } \ + } \ + break; \ + case LV_SVG_ATTR_FONT_STYLE: \ + if(attr->class_type == LV_SVG_ATTR_VALUE_INITIAL) { \ + if(attr->val_type == LV_SVG_ATTR_VALUE_PTR) { \ + if((obj)->font) { \ + lv_freetype_font_delete((obj)->font); \ + (obj)->font = NULL; \ + } \ + lv_vector_path_clear((obj)->path); \ + if(strncmp(attr->value.sval, "italic", 6) == 0) { \ + (obj)->style = LV_FREETYPE_FONT_STYLE_ITALIC; \ + } \ + } \ + } \ + break; \ + case LV_SVG_ATTR_FONT_WEIGHT: \ + if(attr->class_type == LV_SVG_ATTR_VALUE_INITIAL) { \ + if(attr->val_type == LV_SVG_ATTR_VALUE_PTR) { \ + if((obj)->font) { \ + lv_freetype_font_delete((obj)->font); \ + (obj)->font = NULL; \ + } \ + lv_vector_path_clear((obj)->path); \ + if(strncmp(attr->value.sval, "bold", 4) == 0) { \ + (obj)->style = LV_FREETYPE_FONT_STYLE_BOLD; \ + } \ + } \ + } \ + break; \ + case LV_SVG_ATTR_FONT_VARIANT: \ + if(attr->class_type == LV_SVG_ATTR_VALUE_INITIAL) { \ + if(attr->val_type == LV_SVG_ATTR_VALUE_PTR) { \ + if((obj)->font) { \ + lv_freetype_font_delete((obj)->font); \ + (obj)->font = NULL; \ + } \ + lv_vector_path_clear((obj)->path); \ + if(strncmp(attr->value.sval, "small-caps", 10) == 0) { \ + (obj)->size /= 2; \ + } \ + } \ + } \ + break; \ + } \ + } while(0) + +static void _set_tspan_attr(lv_svg_render_obj_t * obj, lv_vector_draw_dsc_t * dsc, const lv_svg_attr_t * attr) +{ + _set_attr(obj, dsc, attr); + lv_svg_render_tspan_t * tspan = (lv_svg_render_tspan_t *)obj; + + SET_FONT_ATTRS(tspan, attr); +} + +static void _set_text_attr(lv_svg_render_obj_t * obj, lv_vector_draw_dsc_t * dsc, const lv_svg_attr_t * attr) +{ + _set_attr(obj, dsc, attr); + lv_svg_render_text_t * text = (lv_svg_render_text_t *)obj; + + SET_FONT_ATTRS(text, attr); + + switch(attr->id) { + case LV_SVG_ATTR_X: + text->x = attr->value.fval; + break; + case LV_SVG_ATTR_Y: + text->y = attr->value.fval; + break; + } +} +#endif + +static void _set_image_attr(lv_svg_render_obj_t * obj, lv_vector_draw_dsc_t * dsc, const lv_svg_attr_t * attr) +{ + _set_attr(obj, dsc, attr); + lv_svg_render_image_t * image = (lv_svg_render_image_t *)obj; + switch(attr->id) { + case LV_SVG_ATTR_X: + image->x = attr->value.fval; + break; + case LV_SVG_ATTR_Y: + image->y = attr->value.fval; + break; + case LV_SVG_ATTR_HEIGHT: + image->height = attr->value.fval; + break; + case LV_SVG_ATTR_WIDTH: + image->width = attr->value.fval; + break; + case LV_SVG_ATTR_OPACITY: + image->img_dsc.opa = (lv_opa_t)(attr->value.fval * 255.0f); + break; + case LV_SVG_ATTR_XLINK_HREF: { + const char * xlink = attr->value.sval; + if(hal_funcs.load_image) { + hal_funcs.load_image(xlink, &image->img_dsc); + } + } + break; + case LV_SVG_ATTR_PRESERVE_ASPECT_RATIO: { + if(attr->class_type == LV_SVG_ATTR_VALUE_INITIAL) { + image->ratio = attr->value.uval; + } + } + break; + } +} + +static void _set_attr(lv_svg_render_obj_t * obj, lv_vector_draw_dsc_t * dsc, const lv_svg_attr_t * attr) +{ + LV_UNUSED(obj); + switch(attr->id) { + case LV_SVG_ATTR_FILL: { + if(attr->class_type == LV_SVG_ATTR_VALUE_NONE) { + dsc->fill_dsc.opa = LV_OPA_0; + obj->flags |= _RENDER_ATTR_FILL_OPACITY; + obj->flags |= _RENDER_ATTR_FILL; + return; + } + else if(attr->class_type == LV_SVG_ATTR_VALUE_INHERIT) { + obj->flags &= ~_RENDER_ATTR_FILL; + return; + } + else { + if(obj->fill_ref) { + lv_free(obj->fill_ref); + obj->fill_ref = NULL; + } + if(attr->val_type == LV_SVG_ATTR_VALUE_PTR) { + obj->fill_ref = lv_strdup(attr->value.sval); + } + else { // color + dsc->fill_dsc.style = LV_VECTOR_DRAW_STYLE_SOLID; + dsc->fill_dsc.color = lv_color_to_32(lv_color_hex(attr->value.uval), 0xFF); + } + obj->flags |= _RENDER_ATTR_FILL; + if(obj->dsc.fill_dsc.opa == LV_OPA_0) { + dsc->fill_dsc.opa = LV_OPA_COVER; + obj->flags |= _RENDER_ATTR_FILL_OPACITY; + } + } + } + break; + case LV_SVG_ATTR_STROKE: { + if(attr->class_type == LV_SVG_ATTR_VALUE_NONE) { + dsc->stroke_dsc.opa = LV_OPA_0; + obj->flags |= _RENDER_ATTR_STROKE_OPACITY; + obj->flags |= _RENDER_ATTR_STROKE; + return; + } + else if(attr->class_type == LV_SVG_ATTR_VALUE_INHERIT) { + obj->flags &= ~_RENDER_ATTR_STROKE; + return; + } + else { + if(obj->stroke_ref) { + lv_free(obj->stroke_ref); + obj->stroke_ref = NULL; + } + if(attr->val_type == LV_SVG_ATTR_VALUE_PTR) { + obj->stroke_ref = lv_strdup(attr->value.sval); + } + else { // color + dsc->stroke_dsc.style = LV_VECTOR_DRAW_STYLE_SOLID; + dsc->stroke_dsc.color = lv_color_to_32(lv_color_hex(attr->value.uval), 0xFF); + } + } + + obj->flags |= _RENDER_ATTR_STROKE; + if(obj->dsc.stroke_dsc.opa == LV_OPA_0) { + dsc->stroke_dsc.opa = LV_OPA_COVER; + obj->flags |= _RENDER_ATTR_STROKE_OPACITY; + } + } + break; + case LV_SVG_ATTR_FILL_OPACITY: { + if(attr->class_type == LV_SVG_ATTR_VALUE_INHERIT) { + obj->flags &= ~_RENDER_ATTR_FILL_OPACITY; + return; + } + dsc->fill_dsc.opa = (lv_opa_t)(attr->value.fval * 255.0f); + obj->flags |= _RENDER_ATTR_FILL_OPACITY; + } + break; + case LV_SVG_ATTR_STROKE_OPACITY: { + if(attr->class_type == LV_SVG_ATTR_VALUE_INHERIT) { + obj->flags &= ~_RENDER_ATTR_STROKE_OPACITY; + return; + } + dsc->stroke_dsc.opa = (lv_opa_t)(attr->value.fval * 255.0f); + obj->flags |= _RENDER_ATTR_STROKE_OPACITY; + } + break; + case LV_SVG_ATTR_FILL_RULE: { + if(attr->class_type == LV_SVG_ATTR_VALUE_INHERIT) { + obj->flags &= ~_RENDER_ATTR_FILL_RULE; + return; + } + dsc->fill_dsc.fill_rule = attr->value.ival; + obj->flags |= _RENDER_ATTR_FILL_RULE; + } + break; + case LV_SVG_ATTR_STROKE_WIDTH: { + if(attr->class_type == LV_SVG_ATTR_VALUE_INHERIT) { + obj->flags &= ~_RENDER_ATTR_STROKE_WIDTH; + return; + } + dsc->stroke_dsc.width = attr->value.fval; + obj->flags |= _RENDER_ATTR_STROKE_WIDTH; + } + break; + case LV_SVG_ATTR_STROKE_LINECAP: { + if(attr->class_type == LV_SVG_ATTR_VALUE_INHERIT) { + obj->flags &= ~_RENDER_ATTR_STROKE_LINECAP; + return; + } + dsc->stroke_dsc.cap = attr->value.ival; + obj->flags |= _RENDER_ATTR_STROKE_LINECAP; + } + break; + case LV_SVG_ATTR_STROKE_LINEJOIN: { + if(attr->class_type == LV_SVG_ATTR_VALUE_INHERIT) { + obj->flags &= ~_RENDER_ATTR_STROKE_LINEJOIN; + return; + } + dsc->stroke_dsc.join = attr->value.ival; + obj->flags |= _RENDER_ATTR_STROKE_LINEJOIN; + } + break; + case LV_SVG_ATTR_STROKE_MITER_LIMIT: { + if(attr->class_type == LV_SVG_ATTR_VALUE_INHERIT) { + obj->flags &= ~_RENDER_ATTR_STROKE_MITER_LIMIT; + return; + } + dsc->stroke_dsc.miter_limit = attr->value.ival; + obj->flags |= _RENDER_ATTR_STROKE_MITER_LIMIT; + } + break; + case LV_SVG_ATTR_STROKE_DASH_ARRAY: { + if(attr->class_type == LV_SVG_ATTR_VALUE_NONE) { + lv_array_clear(&(dsc->stroke_dsc.dash_pattern)); + obj->flags |= _RENDER_ATTR_STROKE_DASH_ARRAY; + return; + } + else if(attr->class_type == LV_SVG_ATTR_VALUE_INHERIT) { + obj->flags &= ~_RENDER_ATTR_STROKE_DASH_ARRAY; + return; + } + else { + lv_array_t * dash_array = &(dsc->stroke_dsc.dash_pattern); + + lv_svg_attr_values_list_t * vals = (lv_svg_attr_values_list_t *)(attr->value.val); + uint32_t len = vals->length; + float * dashs = (float *)(&vals->data); + lv_array_clear(dash_array); + + obj->flags |= _RENDER_ATTR_STROKE_DASH_ARRAY; + if(len) { + if(lv_array_capacity(dash_array) == 0) { + lv_array_init(dash_array, len, sizeof(float)); + } + else { + lv_array_resize(dash_array, len); + } + for(uint32_t i = 0; i < len; i++) { + lv_array_push_back(dash_array, (uint8_t *)(&dashs[i])); + } + } + } + } + break; + case LV_SVG_ATTR_TRANSFORM: { + if(attr->class_type == LV_SVG_ATTR_VALUE_NONE) { + return; + } + lv_memcpy(&(obj->matrix), attr->value.val, sizeof(lv_matrix_t)); + } + break; + case LV_SVG_ATTR_STROKE_DASH_OFFSET: + // not support yet + break; + } +} + +static void _set_solid_ref(lv_svg_render_obj_t * obj, lv_vector_draw_dsc_t * dsc, + const lv_svg_render_obj_t * target_obj, bool fill) +{ + LV_UNUSED(target_obj); + lv_svg_render_solid_t * solid = (lv_svg_render_solid_t *)obj; + if(fill) { + dsc->fill_dsc.style = LV_VECTOR_DRAW_STYLE_SOLID; + dsc->fill_dsc.color = lv_color_to_32(solid->color, 0xFF); + dsc->fill_dsc.opa = (lv_opa_t)(solid->opacity * 255.0f); + } + else { + dsc->stroke_dsc.style = LV_VECTOR_DRAW_STYLE_SOLID; + dsc->stroke_dsc.color = lv_color_to_32(solid->color, 0xFF); + dsc->stroke_dsc.opa = (lv_opa_t)(solid->opacity * 255.0f); + } +} + +static void _set_gradient_ref(lv_svg_render_obj_t * obj, lv_vector_draw_dsc_t * dsc, + const lv_svg_render_obj_t * target_obj, bool fill) +{ + if(!target_obj->get_bounds) { + return; + } + + lv_svg_render_gradient_t * grad = (lv_svg_render_gradient_t *)obj; + lv_vector_gradient_t * grad_dsc = NULL; + lv_matrix_t * mtx = NULL; + + if(fill) { + dsc->fill_dsc.style = LV_VECTOR_DRAW_STYLE_GRADIENT; + grad_dsc = &dsc->fill_dsc.gradient; + mtx = &dsc->fill_dsc.matrix; + } + else { + dsc->stroke_dsc.style = LV_VECTOR_DRAW_STYLE_GRADIENT; + grad_dsc = &dsc->stroke_dsc.gradient; + mtx = &dsc->stroke_dsc.matrix; + } + + lv_memcpy(grad_dsc, &grad->dsc, sizeof(lv_vector_gradient_t)); + + lv_area_t bounds = {0, 0, 0, 0}; + target_obj->get_bounds(target_obj, &bounds); + + int32_t w = bounds.x2 - bounds.x1; + int32_t h = bounds.y2 - bounds.y1; + if(grad->dsc.style == LV_VECTOR_GRADIENT_STYLE_RADIAL) { + if(grad->units == LV_SVG_GRADIENT_UNITS_OBJECT) { + grad_dsc->cx = PCT_TO_PX(grad_dsc->cx, w); + grad_dsc->cy = PCT_TO_PX(grad_dsc->cy, h); + grad_dsc->cr = PCT_TO_PX(grad_dsc->cr, MAX(w, h)); + lv_matrix_translate(mtx, bounds.x1, bounds.y1); + } + } + else { // LV_VECTOR_GRADIENT_STYLE_LINEAR + if(grad->units == LV_SVG_GRADIENT_UNITS_OBJECT) { + grad_dsc->x1 = PCT_TO_PX(grad_dsc->x1, w); + grad_dsc->y1 = PCT_TO_PX(grad_dsc->y1, h); + grad_dsc->x2 = PCT_TO_PX(grad_dsc->x2, w); + grad_dsc->y2 = PCT_TO_PX(grad_dsc->y2, h); + lv_matrix_translate(mtx, bounds.x1, bounds.y1); + } + } +} + +static void _init_draw_dsc(lv_vector_draw_dsc_t * dsc) +{ + lv_vector_fill_dsc_t * fill_dsc = &(dsc->fill_dsc); + fill_dsc->style = LV_VECTOR_DRAW_STYLE_SOLID; + fill_dsc->color = lv_color_to_32(lv_color_black(), 0xFF); + fill_dsc->opa = LV_OPA_COVER; + fill_dsc->fill_rule = LV_VECTOR_FILL_NONZERO; + lv_matrix_identity(&(fill_dsc->matrix)); // identity matrix + + lv_vector_stroke_dsc_t * stroke_dsc = &(dsc->stroke_dsc); + stroke_dsc->style = LV_VECTOR_DRAW_STYLE_SOLID; + stroke_dsc->color = lv_color_to_32(lv_color_black(), 0xFF); + stroke_dsc->opa = LV_OPA_0; // default no stroke + stroke_dsc->width = 1.0f; + stroke_dsc->cap = LV_VECTOR_STROKE_CAP_BUTT; + stroke_dsc->join = LV_VECTOR_STROKE_JOIN_MITER; + stroke_dsc->miter_limit = 4.0f; + lv_matrix_identity(&(stroke_dsc->matrix)); // identity matrix + + dsc->blend_mode = LV_VECTOR_BLEND_SRC_OVER; + lv_matrix_identity(&(dsc->matrix)); // identity matrix +} + +static void _deinit_draw_dsc(lv_vector_draw_dsc_t * dsc) +{ + lv_array_deinit(&(dsc->stroke_dsc.dash_pattern)); +} + +static void _copy_draw_dsc(lv_vector_draw_dsc_t * dst, const lv_vector_draw_dsc_t * src) +{ + lv_memcpy(&dst->fill_dsc, &src->fill_dsc, sizeof(lv_vector_fill_dsc_t)); + + dst->stroke_dsc.style = src->stroke_dsc.style; + dst->stroke_dsc.color = src->stroke_dsc.color; + dst->stroke_dsc.opa = src->stroke_dsc.opa; + dst->stroke_dsc.width = src->stroke_dsc.width; + dst->stroke_dsc.cap = src->stroke_dsc.cap; + dst->stroke_dsc.join = src->stroke_dsc.join; + dst->stroke_dsc.miter_limit = src->stroke_dsc.miter_limit; + lv_array_copy(&(dst->stroke_dsc.dash_pattern), &(src->stroke_dsc.dash_pattern)); + lv_memcpy(&(dst->stroke_dsc.gradient), &(src->stroke_dsc.gradient), sizeof(lv_vector_gradient_t)); + lv_memcpy(&(dst->stroke_dsc.matrix), &(src->stroke_dsc.matrix), sizeof(lv_matrix_t)); + + dst->blend_mode = src->blend_mode; +} + +static void _copy_draw_dsc_from_ref(lv_vector_dsc_t * dsc, const lv_svg_render_obj_t * obj) +{ + lv_vector_draw_dsc_t * dst = &(dsc->current_dsc); + if(obj->fill_ref) { + lv_svg_render_obj_t * list = obj->head; + while(list) { + if(list->id) { + if(strcmp(obj->fill_ref, list->id) == 0) { + list->set_paint_ref(list, dst, obj, true); + break; + } + } + list = list->next; + } + } + + if(obj->stroke_ref) { + lv_svg_render_obj_t * list = obj->head; + while(list) { + if(list->id) { + if(strcmp(obj->stroke_ref, list->id) == 0) { + list->set_paint_ref(list, dst, obj, false); + break; + } + } + list = list->next; + } + } +} + +static void _set_render_attrs(lv_svg_render_obj_t * obj, const lv_svg_node_t * node, + struct _lv_svg_drawing_builder_state * state) +{ + if((node->type != LV_SVG_TAG_CONTENT) && node->xml_id) { + obj->id = lv_strdup(node->xml_id); + } + if(obj->init) { + obj->init(obj, node); + } + if(state->draw_dsc->fill_ref) { + obj->fill_ref = lv_strdup(state->draw_dsc->fill_ref); + } + if(state->draw_dsc->stroke_ref) { + obj->stroke_ref = lv_strdup(state->draw_dsc->stroke_ref); + } + + uint32_t len = lv_array_size(&node->attrs); + for(uint32_t i = 0; i < len; i++) { + lv_svg_attr_t * attr = lv_array_at(&node->attrs, i); + obj->set_attr(obj, &(state->draw_dsc->dsc), attr); + } + if(node->type == LV_SVG_TAG_G) { // only need store it + state->draw_dsc->fill_ref = obj->fill_ref; + state->draw_dsc->stroke_ref = obj->stroke_ref; + } + obj->head = state->list; +} + +// init functions + +static void _init_obj(lv_svg_render_obj_t * obj, const lv_svg_node_t * node) +{ + LV_UNUSED(node); + lv_matrix_identity(&obj->matrix); +} + +static void _init_viewport(lv_svg_render_obj_t * obj, const lv_svg_node_t * node) +{ + _init_obj(obj, node); + lv_svg_render_viewport_t * view = (lv_svg_render_viewport_t *)obj; + view->viewport_fill = false; +} + +static void _init_group(lv_svg_render_obj_t * obj, const lv_svg_node_t * node) +{ + _init_obj(obj, node); + lv_svg_render_group_t * group = (lv_svg_render_group_t *)obj; + lv_array_init(&group->items, LV_TREE_NODE(node)->child_cnt, sizeof(lv_svg_render_obj_t *)); +} + +static void _init_image(lv_svg_render_obj_t * obj, const lv_svg_node_t * node) +{ + _init_obj(obj, node); + lv_svg_render_image_t * image = (lv_svg_render_image_t *)obj; + lv_draw_image_dsc_init(&image->img_dsc); + image->ratio = LV_SVG_ASPECT_RATIO_XMID_YMID | LV_SVG_ASPECT_RATIO_OPT_MEET; +} + +static void _init_poly(lv_svg_render_obj_t * obj, const lv_svg_node_t * node) +{ + _init_obj(obj, node); + lv_svg_render_poly_t * poly = (lv_svg_render_poly_t *)obj; + poly->path = lv_vector_path_create(LV_VECTOR_PATH_QUALITY_MEDIUM); + lv_area_set(&poly->bounds, 0, 0, 0, 0); +} + +#if LV_USE_FREETYPE +static void _init_text(lv_svg_render_obj_t * obj, const lv_svg_node_t * node) +{ + _init_obj(obj, node); + lv_svg_render_text_t * text = (lv_svg_render_text_t *)obj; + text->family = lv_strdup("sans-serif"); + text->size = 16.0f; + text->style = LV_FREETYPE_FONT_STYLE_NORMAL; + text->font = NULL; + text->x = text->y = 0.0f; + lv_array_init(&text->contents, LV_TREE_NODE(node)->child_cnt, sizeof(lv_svg_render_obj_t *)); + text->path = lv_vector_path_create(LV_VECTOR_PATH_QUALITY_MEDIUM); +} + +static void _init_content(lv_svg_render_obj_t * obj, const lv_svg_node_t * node) +{ + _init_obj(obj, node); + lv_svg_render_content_t * content = (lv_svg_render_content_t *)obj; + const char * str = node->xml_id; + content->count = lv_text_get_encoded_length(str); + content->letters = lv_malloc(sizeof(uint32_t) * content->count); + LV_ASSERT_MALLOC(content->letters); + uint32_t offset = 0; + for(uint32_t i = 0; i < content->count; i++) { + content->letters[i] = lv_text_encoded_next(str, &offset); + } +} + +static void _init_tspan(lv_svg_render_obj_t * obj, const lv_svg_node_t * node) +{ + _init_obj(obj, node); + lv_svg_render_tspan_t * span = (lv_svg_render_tspan_t *)obj; + lv_svg_node_t * parent = LV_SVG_NODE(LV_TREE_NODE(node)->parent); + if(parent->type != LV_SVG_TAG_TEXT) { + return; + } + + lv_svg_render_text_t * text = (lv_svg_render_text_t *)parent->render_obj; + span->family = lv_strdup(text->family); + span->size = text->size; + span->style = text->style; + span->path = lv_vector_path_create(LV_VECTOR_PATH_QUALITY_MEDIUM); + + lv_svg_node_t * content_node = LV_SVG_NODE(LV_TREE_NODE(node)->children[0]); + _init_content(obj, content_node); +} +#endif + +static void _init_gradient(lv_svg_render_obj_t * obj, const lv_svg_node_t * node) +{ + _init_obj(obj, node); + lv_svg_render_gradient_t * grad = (lv_svg_render_gradient_t *)obj; + grad->units = LV_SVG_GRADIENT_UNITS_OBJECT; + grad->dsc.cx = 0.5f; + grad->dsc.cy = 0.5f; + grad->dsc.cr = 0.5f; + grad->dsc.x1 = 0.0f; + grad->dsc.y1 = 0.0f; + grad->dsc.x2 = 1.0f; + grad->dsc.y2 = 0.0f; + grad->dsc.spread = LV_VECTOR_GRADIENT_SPREAD_PAD; + + uint32_t count = LV_TREE_NODE(node)->child_cnt; + uint32_t stop_count = 0; + + for(uint32_t i = 0; i < count; i++) { + lv_svg_node_t * child_node = LV_SVG_NODE_CHILD(node, i); + uint32_t len = lv_array_size(&child_node->attrs); + + bool is_stop = false; + lv_color_t stop_color = lv_color_black(); + lv_opa_t stop_opa = LV_OPA_COVER; + uint8_t stop_frac = 0; + + for(uint32_t j = 0; j < len; j++) { + lv_svg_attr_t * attr = lv_array_at(&child_node->attrs, j); + switch(attr->id) { + case LV_SVG_ATTR_GRADIENT_STOP_COLOR: { + stop_color = lv_color_hex(attr->value.uval); + is_stop = true; + } + break; + case LV_SVG_ATTR_GRADIENT_STOP_OPACITY: { + stop_opa = (lv_opa_t)(attr->value.fval * 255.0f); + is_stop = true; + } + break; + case LV_SVG_ATTR_GRADIENT_STOP_OFFSET: { + stop_frac = (uint8_t)(attr->value.fval * 255.0f); + is_stop = true; + } + break; + } + } + + if(is_stop) { + grad->dsc.stops[stop_count].opa = stop_opa; + grad->dsc.stops[stop_count].frac = stop_frac; + grad->dsc.stops[stop_count].color = stop_color; + stop_count++; + } + + if(stop_count == LV_GRADIENT_MAX_STOPS) { + break; + } + } + grad->dsc.stops_count = stop_count; +} + +static void _setup_matrix(lv_matrix_t * matrix, lv_vector_dsc_t * dsc, const lv_svg_render_obj_t * obj) +{ + lv_memcpy(matrix, &dsc->current_dsc.matrix, sizeof(lv_matrix_t)); + lv_matrix_multiply(&dsc->current_dsc.matrix, &obj->matrix); +} + +static void _restore_matrix(lv_matrix_t * matrix, lv_vector_dsc_t * dsc) +{ + lv_memcpy(&dsc->current_dsc.matrix, matrix, sizeof(lv_matrix_t)); +} + +static void _prepare_render(const lv_svg_render_obj_t * obj, lv_vector_dsc_t * dsc) +{ + _copy_draw_dsc(&(dsc->current_dsc), &(obj->dsc)); +} + +static void _special_render(const lv_svg_render_obj_t * obj, lv_vector_dsc_t * dsc) +{ + const lv_vector_draw_dsc_t * src = &(obj->dsc); + lv_vector_draw_dsc_t * dst = &(dsc->current_dsc); + + if(obj->flags & _RENDER_ATTR_FILL) { + lv_memcpy(&(dst->fill_dsc), &(src->fill_dsc), sizeof(lv_vector_fill_dsc_t)); + dst->blend_mode = src->blend_mode; + } + + if(obj->flags & _RENDER_ATTR_FILL_OPACITY) { + dst->fill_dsc.opa = src->fill_dsc.opa; + } + + if(obj->flags & _RENDER_ATTR_FILL_RULE) { + dst->fill_dsc.fill_rule = src->fill_dsc.fill_rule; + } + + if(obj->flags & _RENDER_ATTR_STROKE) { + dst->stroke_dsc.style = src->stroke_dsc.style; + dst->stroke_dsc.color = src->stroke_dsc.color; + lv_memcpy(&(dst->stroke_dsc.gradient), &(src->stroke_dsc.gradient), sizeof(lv_vector_gradient_t)); + lv_memcpy(&(dst->stroke_dsc.matrix), &(src->stroke_dsc.matrix), sizeof(lv_matrix_t)); + dst->blend_mode = src->blend_mode; + } + + if(obj->flags & _RENDER_ATTR_STROKE_OPACITY) { + dst->stroke_dsc.opa = src->stroke_dsc.opa; + } + + if(obj->flags & _RENDER_ATTR_STROKE_WIDTH) { + dst->stroke_dsc.width = src->stroke_dsc.width; + } + if(obj->flags & _RENDER_ATTR_STROKE_LINECAP) { + dst->stroke_dsc.cap = src->stroke_dsc.cap; + } + if(obj->flags & _RENDER_ATTR_STROKE_LINEJOIN) { + dst->stroke_dsc.join = src->stroke_dsc.join; + } + if(obj->flags & _RENDER_ATTR_STROKE_MITER_LIMIT) { + dst->stroke_dsc.miter_limit = src->stroke_dsc.miter_limit; + } + if(obj->flags & _RENDER_ATTR_STROKE_DASH_ARRAY) { + lv_array_copy(&(dst->stroke_dsc.dash_pattern), &(src->stroke_dsc.dash_pattern)); + } +} + +// render functions +static void _render_viewport(const lv_svg_render_obj_t * obj, lv_vector_dsc_t * dsc, const lv_matrix_t * matrix) +{ + LV_UNUSED(matrix); + + lv_svg_render_viewport_t * view = (lv_svg_render_viewport_t *)obj; + lv_matrix_multiply(&dsc->current_dsc.matrix, &obj->matrix); + if(view->viewport_fill) { + lv_area_t rc = {0, 0, (int32_t)view->width, (int32_t)view->height}; + lv_vector_clear_area(dsc, &rc); + } +} + +static void _render_rect(const lv_svg_render_obj_t * obj, lv_vector_dsc_t * dsc, const lv_matrix_t * matrix) +{ + lv_matrix_t mtx; + _setup_matrix(&mtx, dsc, obj); + + if(matrix) { + lv_matrix_multiply(&dsc->current_dsc.matrix, matrix); + } + + lv_svg_render_rect_t * rect = (lv_svg_render_rect_t *)obj; + + if(rect->rx > 0 && rect->ry == 0) rect->ry = rect->rx; + else if(rect->ry > 0 && rect->rx == 0) rect->rx = rect->ry; + + lv_vector_path_t * path = lv_vector_path_create(LV_VECTOR_PATH_QUALITY_MEDIUM); + lv_area_t rc = {(int32_t)rect->x, (int32_t)rect->y, (int32_t)(rect->x + rect->width), (int32_t)(rect->y + rect->height)}; + lv_vector_path_append_rect(path, &rc, rect->rx, rect->ry); + + _copy_draw_dsc_from_ref(dsc, obj); + lv_vector_dsc_add_path(dsc, path); + lv_vector_path_delete(path); + + _restore_matrix(&mtx, dsc); +} + +static void _render_circle(const lv_svg_render_obj_t * obj, lv_vector_dsc_t * dsc, const lv_matrix_t * matrix) +{ + lv_matrix_t mtx; + _setup_matrix(&mtx, dsc, obj); + + if(matrix) { + lv_matrix_multiply(&dsc->current_dsc.matrix, matrix); + } + + lv_svg_render_circle_t * circle = (lv_svg_render_circle_t *)obj; + lv_vector_path_t * path = lv_vector_path_create(LV_VECTOR_PATH_QUALITY_MEDIUM); + lv_fpoint_t cp = {circle->cx, circle->cy}; + lv_vector_path_append_circle(path, &cp, circle->r, circle->r); + + _copy_draw_dsc_from_ref(dsc, obj); + lv_vector_dsc_add_path(dsc, path); + lv_vector_path_delete(path); + + _restore_matrix(&mtx, dsc); +} + +static void _render_ellipse(const lv_svg_render_obj_t * obj, lv_vector_dsc_t * dsc, const lv_matrix_t * matrix) +{ + lv_matrix_t mtx; + _setup_matrix(&mtx, dsc, obj); + + if(matrix) { + lv_matrix_multiply(&dsc->current_dsc.matrix, matrix); + } + + lv_svg_render_ellipse_t * ellipse = (lv_svg_render_ellipse_t *)obj; + lv_vector_path_t * path = lv_vector_path_create(LV_VECTOR_PATH_QUALITY_MEDIUM); + lv_fpoint_t cp = {ellipse->cx, ellipse->cy}; + lv_vector_path_append_circle(path, &cp, ellipse->rx, ellipse->ry); + + _copy_draw_dsc_from_ref(dsc, obj); + lv_vector_dsc_add_path(dsc, path); + lv_vector_path_delete(path); + + _restore_matrix(&mtx, dsc); +} + +static void _render_line(const lv_svg_render_obj_t * obj, lv_vector_dsc_t * dsc, const lv_matrix_t * matrix) +{ + lv_matrix_t mtx; + _setup_matrix(&mtx, dsc, obj); + + if(matrix) { + lv_matrix_multiply(&dsc->current_dsc.matrix, matrix); + } + + lv_svg_render_line_t * line = (lv_svg_render_line_t *)obj; + lv_vector_path_t * path = lv_vector_path_create(LV_VECTOR_PATH_QUALITY_MEDIUM); + lv_fpoint_t sp = {line->x1, line->y1}; + lv_vector_path_move_to(path, &sp); + lv_fpoint_t ep = {line->x2, line->y2}; + lv_vector_path_line_to(path, &ep); + + _copy_draw_dsc_from_ref(dsc, obj); + lv_vector_dsc_add_path(dsc, path); + lv_vector_path_delete(path); + + _restore_matrix(&mtx, dsc); +} + +static void _render_poly(const lv_svg_render_obj_t * obj, lv_vector_dsc_t * dsc, const lv_matrix_t * matrix) +{ + lv_matrix_t mtx; + _setup_matrix(&mtx, dsc, obj); + + if(matrix) { + lv_matrix_multiply(&dsc->current_dsc.matrix, matrix); + } + + lv_svg_render_poly_t * poly = (lv_svg_render_poly_t *)obj; + + _copy_draw_dsc_from_ref(dsc, obj); + lv_vector_dsc_add_path(dsc, poly->path); + + _restore_matrix(&mtx, dsc); +} + +static void _render_group(const lv_svg_render_obj_t * obj, lv_vector_dsc_t * dsc, const lv_matrix_t * matrix) +{ + lv_svg_render_group_t * group = (lv_svg_render_group_t *)obj; + + lv_matrix_t mtx; + _setup_matrix(&mtx, dsc, obj); + + struct _lv_svg_draw_dsc save_dsc; + lv_memzero(&save_dsc, sizeof(struct _lv_svg_draw_dsc)); + + for(uint32_t i = 0; i < group->items.size; i++) { + lv_svg_render_obj_t * list = *((lv_svg_render_obj_t **)lv_array_at(&group->items, i)); + + if(list->render && (list->flags & _RENDER_IN_GROUP)) { + _copy_draw_dsc(&(save_dsc.dsc), &(dsc->current_dsc)); + _special_render(list, dsc); + list->render(list, dsc, matrix); + _copy_draw_dsc(&(dsc->current_dsc), &(save_dsc.dsc)); + } + } + + _restore_matrix(&mtx, dsc); +} + +static void _render_image(const lv_svg_render_obj_t * obj, lv_vector_dsc_t * dsc, const lv_matrix_t * matrix) +{ + lv_matrix_t imtx; + _setup_matrix(&imtx, dsc, obj); + + if(matrix) { + lv_matrix_multiply(&dsc->current_dsc.matrix, matrix); + } + + lv_svg_render_image_t * image = (lv_svg_render_image_t *)obj; + if(!image->img_dsc.header.w || !image->img_dsc.header.h || !image->img_dsc.src) { + return; + } + + lv_vector_path_t * path = lv_vector_path_create(LV_VECTOR_PATH_QUALITY_MEDIUM); + lv_area_t rc = {(int32_t)image->x, (int32_t)image->y, (int32_t)(image->x + image->width), (int32_t)(image->y + image->height)}; + lv_vector_path_append_rect(path, &rc, 0, 0); + + lv_matrix_t mtx; + lv_matrix_identity(&mtx); + + float img_w = (float)image->img_dsc.header.w; + float img_h = (float)image->img_dsc.header.h; + float scale_x = image->width / img_w; + float scale_y = image->height / img_h; + float scale = 1.0f; + + if((image->ratio & 0x1) == LV_SVG_ASPECT_RATIO_OPT_SLICE) { + scale = MAX(scale_x, scale_y); + } + else if((image->ratio & 0x1) == LV_SVG_ASPECT_RATIO_OPT_MEET) { + scale = MIN(scale_x, scale_y); + } + + uint32_t align = image->ratio & ~0x1; + + switch(align) { + case LV_SVG_ASPECT_RATIO_NONE: + lv_matrix_scale(&mtx, scale_x, scale_y); + break; + case LV_SVG_ASPECT_RATIO_XMIN_YMIN: + lv_matrix_scale(&mtx, scale, scale); + break; + case LV_SVG_ASPECT_RATIO_XMID_YMIN: { + float tx = (image->width - img_w * scale) / 2; + lv_matrix_translate(&mtx, tx, 0); + lv_matrix_scale(&mtx, scale, scale); + } + break; + case LV_SVG_ASPECT_RATIO_XMAX_YMIN: { + float tx = image->width - img_w * scale; + lv_matrix_translate(&mtx, tx, 0); + lv_matrix_scale(&mtx, scale, scale); + } + break; + case LV_SVG_ASPECT_RATIO_XMIN_YMID: { + float ty = (image->height - img_h * scale) / 2; + lv_matrix_translate(&mtx, 0, ty); + lv_matrix_scale(&mtx, scale, scale); + } + break; + case LV_SVG_ASPECT_RATIO_XMID_YMID: { + float tx = (image->width - img_w * scale) / 2; + float ty = (image->height - img_h * scale) / 2; + lv_matrix_translate(&mtx, tx, ty); + lv_matrix_scale(&mtx, scale, scale); + } + break; + case LV_SVG_ASPECT_RATIO_XMAX_YMID: { + float tx = image->width - img_w * scale; + float ty = (image->height - img_h * scale) / 2; + lv_matrix_translate(&mtx, tx, ty); + lv_matrix_scale(&mtx, scale, scale); + } + break; + case LV_SVG_ASPECT_RATIO_XMIN_YMAX: { + float ty = image->height - img_h * scale; + lv_matrix_translate(&mtx, 0, ty); + lv_matrix_scale(&mtx, scale, scale); + } + break; + case LV_SVG_ASPECT_RATIO_XMID_YMAX: { + float tx = (image->width - img_w * scale) / 2; + float ty = image->height - img_h * scale; + lv_matrix_translate(&mtx, tx, ty); + lv_matrix_scale(&mtx, scale, scale); + } + break; + case LV_SVG_ASPECT_RATIO_XMAX_YMAX: { + float tx = image->width - img_w * scale; + float ty = image->height - img_h * scale; + lv_matrix_translate(&mtx, tx, ty); + lv_matrix_scale(&mtx, scale, scale); + } + break; + } + + lv_vector_dsc_set_fill_transform(dsc, &mtx); + lv_vector_dsc_set_fill_image(dsc, &image->img_dsc); + + _copy_draw_dsc_from_ref(dsc, obj); + lv_vector_dsc_add_path(dsc, path); + lv_vector_path_delete(path); + + _restore_matrix(&imtx, dsc); +} + +static void _render_use(const lv_svg_render_obj_t * obj, lv_vector_dsc_t * dsc, const lv_matrix_t * matrix) +{ + LV_UNUSED(matrix); + lv_matrix_t imtx; + _setup_matrix(&imtx, dsc, obj); + + lv_svg_render_use_t * use = (lv_svg_render_use_t *)obj; + + lv_matrix_t mtx; + lv_matrix_identity(&mtx); + lv_matrix_translate(&mtx, use->x, use->y); + + lv_svg_render_obj_t * list = obj->head; + while(list) { + if(list->id) { + if(strcmp(use->xlink, list->id) == 0) { + if(list->render) { + _prepare_render(list, dsc); + _special_render(obj, dsc); + list->render(list, dsc, &mtx); + } + break; + } + } + list = list->next; + } + + _restore_matrix(&imtx, dsc); +} + +#if LV_USE_FREETYPE +static void _render_text(const lv_svg_render_obj_t * obj, lv_vector_dsc_t * dsc, const lv_matrix_t * matrix) +{ + lv_svg_render_text_t * text = (lv_svg_render_text_t *)obj; + if(!text->font) { + if(!hal_funcs.get_font_path) { + return; + } + const char * font_path = hal_funcs.get_font_path(text->family); + if(!font_path) { + return; + } + text->font = lv_freetype_font_create(font_path, LV_FREETYPE_FONT_RENDER_MODE_OUTLINE, (uint32_t)text->size, + text->style); + } + + if(!text->font || !lv_freetype_is_outline_font(text->font)) { + LV_LOG_ERROR("svg current font is not outline font!"); + return; + } + + lv_matrix_t tmtx; + _setup_matrix(&tmtx, dsc, obj); + + if(matrix) { + lv_matrix_multiply(&dsc->current_dsc.matrix, matrix); + } + + if(lv_array_size(&text->path->ops) == 0) { /* empty path */ + lv_vector_path_t * glyph_path = lv_vector_path_create(LV_VECTOR_PATH_QUALITY_MEDIUM); + // draw text contents and spans + lv_matrix_t mtx; + lv_matrix_identity(&mtx); + lv_matrix_translate(&mtx, text->x, text->y); + for(uint32_t i = 0; i < lv_array_size(&text->contents); i++) { + lv_svg_render_obj_t * ptext = *((lv_svg_render_obj_t **)lv_array_at(&text->contents, i)); + lv_svg_render_content_t * content = (lv_svg_render_content_t *)ptext; + + if(content->render_content) { + content->render_content(content, dsc, &mtx); + } + else { + float scale = text->size / 128.0f; + for(uint32_t j = 0; j < content->count; j++) { + uint32_t letter = content->letters[j]; + lv_font_glyph_dsc_t g; + lv_font_get_glyph_dsc(text->font, &g, letter, '\0'); + lv_vector_path_t * p = (lv_vector_path_t *)lv_font_get_glyph_bitmap(&g, NULL); + lv_vector_path_clear(glyph_path); + lv_vector_path_copy(glyph_path, p); + uint32_t letter_w = g.box_w > 0 ? g.box_w : g.adv_w; + + lv_matrix_t scale_matrix = mtx; + lv_matrix_translate(&mtx, g.ofs_x, 0); + lv_matrix_scale(&scale_matrix, scale, scale); + lv_matrix_transform_path(&scale_matrix, glyph_path); + + lv_vector_path_append_path(text->path, glyph_path); + text->font->release_glyph(text->font, &g); + lv_matrix_translate(&mtx, letter_w, 0); + } + } + } + lv_vector_path_delete(glyph_path); + lv_vector_path_get_bounding(text->path, &text->bounds); + } + + _copy_draw_dsc_from_ref(dsc, obj); + lv_vector_dsc_add_path(dsc, text->path); + + _restore_matrix(&tmtx, dsc); +} + +static void _render_span(const lv_svg_render_content_t * content, lv_vector_dsc_t * dsc, lv_matrix_t * matrix) +{ + lv_svg_render_obj_t * obj = (lv_svg_render_obj_t *)content; + + lv_svg_render_tspan_t * span = (lv_svg_render_tspan_t *)content; + if(!span->font) { + if(!hal_funcs.get_font_path) { + return; + } + const char * font_path = hal_funcs.get_font_path(span->family); + if(!font_path) { + return; + } + span->font = lv_freetype_font_create(font_path, LV_FREETYPE_FONT_RENDER_MODE_OUTLINE, (uint32_t)span->size, + span->style); + } + + if(!span->font || !lv_freetype_is_outline_font(span->font)) { + LV_LOG_ERROR("svg current font is not outline font!"); + return; + } + + struct _lv_svg_draw_dsc save_dsc; + lv_memzero(&save_dsc, sizeof(struct _lv_svg_draw_dsc)); + _copy_draw_dsc(&(save_dsc.dsc), &(dsc->current_dsc)); + + _copy_draw_dsc(&(dsc->current_dsc), &(obj->dsc)); + + if(lv_array_size(&span->path->ops) == 0) { /* empty path */ + lv_vector_path_t * glyph_path = lv_vector_path_create(LV_VECTOR_PATH_QUALITY_MEDIUM); + // draw text contents and spans + lv_matrix_t * mtx = matrix; + + float scale = span->size / 128.0f; + for(uint32_t j = 0; j < content->count; j++) { + uint32_t letter = content->letters[j]; + lv_font_glyph_dsc_t g; + lv_font_get_glyph_dsc(span->font, &g, letter, '\0'); + lv_vector_path_t * p = (lv_vector_path_t *)lv_font_get_glyph_bitmap(&g, NULL); + lv_vector_path_clear(glyph_path); + lv_vector_path_copy(glyph_path, p); + uint32_t letter_w = g.box_w > 0 ? g.box_w : g.adv_w; + + lv_matrix_t scale_matrix = *mtx; + lv_matrix_translate(mtx, g.ofs_x, 0); + lv_matrix_scale(&scale_matrix, scale, scale); + lv_matrix_transform_path(&scale_matrix, glyph_path); + + lv_vector_path_append_path(span->path, glyph_path); + span->font->release_glyph(span->font, &g); + lv_matrix_translate(mtx, letter_w, 0); + } + lv_vector_path_delete(glyph_path); + lv_vector_path_get_bounding(span->path, &span->bounds); + } + _copy_draw_dsc_from_ref(dsc, obj); + lv_vector_dsc_add_path(dsc, span->path); + + _copy_draw_dsc(&(dsc->current_dsc), &(save_dsc.dsc)); +} +#endif + +// get bounds functions + +static void _get_rect_bounds(const lv_svg_render_obj_t * obj, lv_area_t * area) +{ + lv_svg_render_rect_t * rect = (lv_svg_render_rect_t *)obj; + area->x1 = (int32_t)rect->x; + area->y1 = (int32_t)rect->y; + area->x2 = (int32_t)(rect->x + rect->width); + area->y2 = (int32_t)(rect->y + rect->height); +} + +static void _get_circle_bounds(const lv_svg_render_obj_t * obj, lv_area_t * area) +{ + lv_svg_render_circle_t * circle = (lv_svg_render_circle_t *)obj; + area->x1 = (int32_t)(circle->cx - circle->r); + area->y1 = (int32_t)(circle->cy - circle->r); + area->x2 = (int32_t)(circle->cx + circle->r); + area->y2 = (int32_t)(circle->cy + circle->r); +} + +static void _get_ellipse_bounds(const lv_svg_render_obj_t * obj, lv_area_t * area) +{ + lv_svg_render_ellipse_t * ellipse = (lv_svg_render_ellipse_t *)obj; + area->x1 = (int32_t)(ellipse->cx - ellipse->rx); + area->y1 = (int32_t)(ellipse->cy - ellipse->ry); + area->x2 = (int32_t)(ellipse->cx + ellipse->rx); + area->y2 = (int32_t)(ellipse->cy + ellipse->ry); +} + +static void _get_line_bounds(const lv_svg_render_obj_t * obj, lv_area_t * area) +{ + lv_svg_render_line_t * line = (lv_svg_render_line_t *)obj; + area->x1 = (int32_t)(line->x1); + area->y1 = (int32_t)(line->y1); + area->x2 = (int32_t)(line->x2); + area->y2 = (int32_t)(line->y2); +} + +static void _get_poly_bounds(const lv_svg_render_obj_t * obj, lv_area_t * area) +{ + lv_svg_render_poly_t * poly = (lv_svg_render_poly_t *)obj; + lv_area_copy(area, &poly->bounds); +} + +#if LV_USE_FREETYPE +static void _get_text_bounds(const lv_svg_render_obj_t * obj, lv_area_t * area) +{ + lv_svg_render_text_t * text = (lv_svg_render_text_t *)obj; + lv_area_copy(area, &text->bounds); +} + +static void _get_tspan_bounds(const lv_svg_render_obj_t * obj, lv_area_t * area) +{ + lv_svg_render_tspan_t * tspan = (lv_svg_render_tspan_t *)obj; + lv_area_copy(area, &tspan->bounds); +} +#endif + +// destroy functions +static void _destroy_poly(lv_svg_render_obj_t * obj) +{ + lv_svg_render_poly_t * poly = (lv_svg_render_poly_t *)obj; + lv_vector_path_delete(poly->path); +} + +static void _destroy_use(lv_svg_render_obj_t * obj) +{ + lv_svg_render_use_t * use = (lv_svg_render_use_t *)obj; + if(use->xlink) { + lv_free(use->xlink); + } +} + +static void _destroy_group(lv_svg_render_obj_t * obj) +{ + lv_svg_render_group_t * group = (lv_svg_render_group_t *)obj; + lv_array_deinit(&group->items); +} + +#if LV_USE_FREETYPE +static void _destroy_text(lv_svg_render_obj_t * obj) +{ + lv_svg_render_text_t * text = (lv_svg_render_text_t *)obj; + if(text->font) { + lv_freetype_font_delete(text->font); + } + if(text->family) { + lv_free(text->family); + } + lv_array_deinit(&text->contents); + lv_vector_path_delete(text->path); +} + +static void _destroy_content(lv_svg_render_obj_t * obj) +{ + lv_svg_render_content_t * content = (lv_svg_render_content_t *)obj; + if(content->letters) { + lv_free(content->letters); + } +} + +static void _destroy_tspan(lv_svg_render_obj_t * obj) +{ + lv_svg_render_tspan_t * span = (lv_svg_render_tspan_t *)obj; + if(span->font) { + lv_freetype_font_delete(span->font); + } + + if(span->family) { + lv_free(span->family); + } + + _destroy_content(obj); + lv_vector_path_delete(span->path); +} + +#endif + +static lv_svg_render_obj_t * _lv_svg_render_create(const lv_svg_node_t * node, + struct _lv_svg_drawing_builder_state * state) +{ + switch(node->type) { + case LV_SVG_TAG_SVG: { + lv_svg_render_viewport_t * view = lv_malloc_zeroed(sizeof(lv_svg_render_viewport_t)); + LV_ASSERT_MALLOC(view); + view->base.init = _init_viewport; + view->base.render = _render_viewport; + view->base.set_attr = _set_viewport_attr; + _set_render_attrs(LV_SVG_RENDER_OBJ(view), node, state); + return LV_SVG_RENDER_OBJ(view); + } + case LV_SVG_TAG_RECT: { + lv_svg_render_rect_t * rect = lv_malloc_zeroed(sizeof(lv_svg_render_rect_t)); + LV_ASSERT_MALLOC(rect); + rect->base.init = _init_obj; + rect->base.render = _render_rect; + rect->base.set_attr = _set_rect_attr; + rect->base.get_bounds = _get_rect_bounds; + _set_render_attrs(LV_SVG_RENDER_OBJ(rect), node, state); + return LV_SVG_RENDER_OBJ(rect); + } + case LV_SVG_TAG_CIRCLE: { + lv_svg_render_circle_t * circle = lv_malloc_zeroed(sizeof(lv_svg_render_circle_t)); + LV_ASSERT_MALLOC(circle); + circle->base.init = _init_obj; + circle->base.render = _render_circle; + circle->base.set_attr = _set_circle_attr; + circle->base.get_bounds = _get_circle_bounds; + _set_render_attrs(LV_SVG_RENDER_OBJ(circle), node, state); + return LV_SVG_RENDER_OBJ(circle); + } + case LV_SVG_TAG_ELLIPSE: { + lv_svg_render_ellipse_t * ellipse = lv_malloc_zeroed(sizeof(lv_svg_render_ellipse_t)); + LV_ASSERT_MALLOC(ellipse); + ellipse->base.init = _init_obj; + ellipse->base.render = _render_ellipse; + ellipse->base.set_attr = _set_ellipse_attr; + ellipse->base.get_bounds = _get_ellipse_bounds; + _set_render_attrs(LV_SVG_RENDER_OBJ(ellipse), node, state); + return LV_SVG_RENDER_OBJ(ellipse); + } + case LV_SVG_TAG_LINE: { + lv_svg_render_line_t * line = lv_malloc_zeroed(sizeof(lv_svg_render_line_t)); + LV_ASSERT_MALLOC(line); + line->base.init = _init_obj; + line->base.render = _render_line; + line->base.set_attr = _set_line_attr; + line->base.get_bounds = _get_line_bounds; + _set_render_attrs(LV_SVG_RENDER_OBJ(line), node, state); + return LV_SVG_RENDER_OBJ(line); + } + case LV_SVG_TAG_POLYLINE: { + lv_svg_render_poly_t * poly = lv_malloc_zeroed(sizeof(lv_svg_render_poly_t)); + LV_ASSERT_MALLOC(poly); + poly->base.init = _init_poly; + poly->base.render = _render_poly; + poly->base.set_attr = _set_polyline_attr; + poly->base.get_bounds = _get_poly_bounds; + poly->base.destroy = _destroy_poly; + _set_render_attrs(LV_SVG_RENDER_OBJ(poly), node, state); + return LV_SVG_RENDER_OBJ(poly); + } + case LV_SVG_TAG_POLYGON: { + lv_svg_render_poly_t * poly = lv_malloc_zeroed(sizeof(lv_svg_render_poly_t)); + LV_ASSERT_MALLOC(poly); + poly->base.init = _init_poly; + poly->base.render = _render_poly; + poly->base.set_attr = _set_polygen_attr; + poly->base.get_bounds = _get_poly_bounds; + poly->base.destroy = _destroy_poly; + _set_render_attrs(LV_SVG_RENDER_OBJ(poly), node, state); + return LV_SVG_RENDER_OBJ(poly); + } + case LV_SVG_TAG_PATH: { + lv_svg_render_poly_t * poly = lv_malloc_zeroed(sizeof(lv_svg_render_poly_t)); + LV_ASSERT_MALLOC(poly); + poly->base.init = _init_poly; + poly->base.render = _render_poly; + poly->base.set_attr = _set_path_attr; + poly->base.get_bounds = _get_poly_bounds; + poly->base.destroy = _destroy_poly; + _set_render_attrs(LV_SVG_RENDER_OBJ(poly), node, state); + return LV_SVG_RENDER_OBJ(poly); + } +#if LV_USE_FREETYPE + case LV_SVG_TAG_TEXT: { + lv_svg_render_text_t * txt = lv_malloc_zeroed(sizeof(lv_svg_render_text_t)); + LV_ASSERT_MALLOC(txt); + txt->base.init = _init_text; + txt->base.set_attr = _set_text_attr; + txt->base.render = _render_text; + txt->base.get_bounds = _get_text_bounds; + txt->base.destroy = _destroy_text; + _set_render_attrs(LV_SVG_RENDER_OBJ(txt), node, state); + return LV_SVG_RENDER_OBJ(txt); + } + case LV_SVG_TAG_TSPAN: { + lv_svg_render_tspan_t * span = lv_malloc_zeroed(sizeof(lv_svg_render_tspan_t)); + LV_ASSERT_MALLOC(span); + lv_svg_render_content_t * content = (lv_svg_render_content_t *)span; + content->render_content = _render_span; + content->base.init = _init_tspan; + content->base.set_attr = _set_tspan_attr; + content->base.get_bounds = _get_tspan_bounds; + content->base.destroy = _destroy_tspan; + _set_render_attrs(LV_SVG_RENDER_OBJ(span), node, state); + return LV_SVG_RENDER_OBJ(span); + } + case LV_SVG_TAG_CONTENT: { + lv_svg_render_content_t * content = lv_malloc_zeroed(sizeof(lv_svg_render_content_t)); + LV_ASSERT_MALLOC(content); + content->base.init = _init_content; + content->base.destroy = _destroy_content; + _set_render_attrs(LV_SVG_RENDER_OBJ(content), node, state); + return LV_SVG_RENDER_OBJ(content); + } +#endif + case LV_SVG_TAG_IMAGE: { + lv_svg_render_image_t * image = lv_malloc_zeroed(sizeof(lv_svg_render_image_t)); + LV_ASSERT_MALLOC(image); + image->base.init = _init_image; + image->base.render = _render_image; + image->base.set_attr = _set_image_attr; + _set_render_attrs(LV_SVG_RENDER_OBJ(image), node, state); + return LV_SVG_RENDER_OBJ(image); + } + case LV_SVG_TAG_USE: { + lv_svg_render_use_t * use = lv_malloc_zeroed(sizeof(lv_svg_render_use_t)); + LV_ASSERT_MALLOC(use); + use->base.init = _init_obj; + use->base.set_attr = _set_use_attr; + use->base.render = _render_use; + use->base.destroy = _destroy_use; + _set_render_attrs(LV_SVG_RENDER_OBJ(use), node, state); + return LV_SVG_RENDER_OBJ(use); + } + case LV_SVG_TAG_SOLID_COLOR: { + lv_svg_render_solid_t * solid = lv_malloc_zeroed(sizeof(lv_svg_render_solid_t)); + LV_ASSERT_MALLOC(solid); + solid->base.init = _init_obj; + solid->base.set_attr = _set_solid_attr; + solid->base.set_paint_ref = _set_solid_ref; + _set_render_attrs(LV_SVG_RENDER_OBJ(solid), node, state); + return LV_SVG_RENDER_OBJ(solid); + } + case LV_SVG_TAG_RADIAL_GRADIENT: + case LV_SVG_TAG_LINEAR_GRADIENT: { + lv_svg_render_gradient_t * grad = lv_malloc_zeroed(sizeof(lv_svg_render_gradient_t)); + LV_ASSERT_MALLOC(grad); + grad->base.init = _init_gradient; + grad->base.set_attr = _set_gradient_attr; + grad->base.set_paint_ref = _set_gradient_ref; + if(node->type == LV_SVG_TAG_LINEAR_GRADIENT) { + grad->dsc.style = LV_VECTOR_GRADIENT_STYLE_LINEAR; + } + else { // radial gradient + grad->dsc.style = LV_VECTOR_GRADIENT_STYLE_RADIAL; + } + _set_render_attrs(LV_SVG_RENDER_OBJ(grad), node, state); + return LV_SVG_RENDER_OBJ(grad); + } + case LV_SVG_TAG_G: { + lv_svg_render_group_t * group = lv_malloc_zeroed(sizeof(lv_svg_render_group_t)); + LV_ASSERT_MALLOC(group); + group->base.init = _init_group; + group->base.set_attr = _set_attr; + group->base.render = _render_group; + group->base.destroy = _destroy_group; + _set_render_attrs(LV_SVG_RENDER_OBJ(group), node, state); + return LV_SVG_RENDER_OBJ(group); + } + default: + return NULL; + } +} + +static bool _lv_svg_doc_walk_cb(const lv_tree_node_t * node, void * data) +{ + struct _lv_svg_drawing_builder_state * state = (struct _lv_svg_drawing_builder_state *)data; + lv_svg_render_obj_t * obj = _lv_svg_render_create(LV_SVG_NODE(node), state); + if(!obj) { + return true; + } + + if(state->in_defs) { + obj->flags |= _RENDER_IN_DEFS; + } + if(state->in_group_deps > 0) { + obj->flags |= _RENDER_IN_GROUP; + } + + if(state->list == NULL) { + state->list = obj; + state->tail = obj; + } + else { + state->tail->next = obj; + state->tail = obj; + } + LV_SVG_NODE(node)->render_obj = obj; + return true; +} + +static bool _lv_svg_doc_walk_before_cb(const lv_tree_node_t * node, void * data) +{ + struct _lv_svg_drawing_builder_state * state = (struct _lv_svg_drawing_builder_state *)data; + lv_svg_node_t * svg_node = LV_SVG_NODE(node); +#if LV_USE_FREETYPE + if(svg_node->type == LV_SVG_TAG_TEXT) { + state->in_text = true; + state->cur_text = svg_node; + } +#endif + if(svg_node->type == LV_SVG_TAG_DEFS) { + state->in_defs = true; + } + + if(svg_node->type == LV_SVG_TAG_G) { + state->in_group_deps++; + } + state->draw_dsc = _lv_svg_draw_dsc_push(state->draw_dsc); + return true; +} + +static void _lv_svg_doc_walk_after_cb(const lv_tree_node_t * node, void * data) +{ + struct _lv_svg_drawing_builder_state * state = (struct _lv_svg_drawing_builder_state *)data; + lv_svg_node_t * svg_node = LV_SVG_NODE(node); + if(svg_node->render_obj) { + _copy_draw_dsc(&(LV_SVG_NODE(node)->render_obj->dsc), &(state->draw_dsc->dsc)); + } +#if LV_USE_FREETYPE + if(state->in_text) { + if(svg_node->type == LV_SVG_TAG_TSPAN || svg_node->type == LV_SVG_TAG_CONTENT) { + if(LV_TREE_NODE(svg_node)->parent == LV_TREE_NODE(state->cur_text)) { + lv_svg_render_text_t * text = (lv_svg_render_text_t *)state->cur_text->render_obj; + if((lv_array_size(&text->contents) + 1) > lv_array_capacity(&text->contents)) { + lv_array_resize(&text->contents, text->contents.capacity << 1); + } + lv_array_push_back(&text->contents, (uint8_t *)(&svg_node->render_obj)); + } + } + } + if(svg_node->type == LV_SVG_TAG_TEXT) { + state->in_text = false; + state->cur_text = NULL; + } +#endif + if(svg_node->type == LV_SVG_TAG_G) { + lv_svg_render_group_t * group = (lv_svg_render_group_t *)svg_node->render_obj; + uint32_t count = LV_TREE_NODE(node)->child_cnt; + for(uint32_t i = 0; i < count; i++) { + lv_svg_node_t * child = LV_SVG_NODE_CHILD(node, i); + if(child->render_obj) { // not defs + lv_array_push_back(&group->items, (uint8_t *)(&child->render_obj)); + } + } + + state->in_group_deps--; + if(state->in_group_deps == 0) { + group->base.flags &= ~_RENDER_IN_GROUP; + } + } + if(svg_node->type == LV_SVG_TAG_DEFS) { + state->in_defs = false; + } + state->draw_dsc = _lv_svg_draw_dsc_pop(state->draw_dsc); +} + +#if LV_USE_FREETYPE +static void _freetype_outline_cb(lv_event_t * e) +{ + lv_event_code_t code = lv_event_get_code(e); + lv_freetype_outline_event_param_t * param = lv_event_get_param(e); + switch(code) { + case LV_EVENT_CREATE: + param->outline = lv_vector_path_create(LV_VECTOR_PATH_QUALITY_MEDIUM); + break; + case LV_EVENT_DELETE: + lv_vector_path_delete(param->outline); + break; + case LV_EVENT_INSERT: { + if(param->type == LV_FREETYPE_OUTLINE_MOVE_TO) { + lv_fpoint_t pt = {0}; + pt.x = LV_FREETYPE_F26DOT6_TO_FLOAT(param->to.x); + pt.y = -LV_FREETYPE_F26DOT6_TO_FLOAT(param->to.y); + lv_vector_path_move_to(param->outline, &pt); + } + else if(param->type == LV_FREETYPE_OUTLINE_LINE_TO) { + lv_fpoint_t pt = {0}; + pt.x = LV_FREETYPE_F26DOT6_TO_FLOAT(param->to.x); + pt.y = -LV_FREETYPE_F26DOT6_TO_FLOAT(param->to.y); + lv_vector_path_line_to(param->outline, &pt); + } + else if(param->type == LV_FREETYPE_OUTLINE_CUBIC_TO) { + lv_fpoint_t pt[3] = {0}; + pt[0].x = LV_FREETYPE_F26DOT6_TO_FLOAT(param->control1.x); + pt[0].y = -LV_FREETYPE_F26DOT6_TO_FLOAT(param->control1.y); + pt[1].x = LV_FREETYPE_F26DOT6_TO_FLOAT(param->control2.x); + pt[1].y = -LV_FREETYPE_F26DOT6_TO_FLOAT(param->control2.y); + pt[2].x = LV_FREETYPE_F26DOT6_TO_FLOAT(param->to.x); + pt[2].y = -LV_FREETYPE_F26DOT6_TO_FLOAT(param->to.y); + lv_vector_path_cubic_to(param->outline, &pt[0], &pt[1], &pt[2]); + } + else if(param->type == LV_FREETYPE_OUTLINE_CONIC_TO) { + lv_fpoint_t pt[2] = {0}; + pt[0].x = LV_FREETYPE_F26DOT6_TO_FLOAT(param->control1.x); + pt[0].y = -LV_FREETYPE_F26DOT6_TO_FLOAT(param->control1.y); + pt[1].x = LV_FREETYPE_F26DOT6_TO_FLOAT(param->to.x); + pt[1].y = -LV_FREETYPE_F26DOT6_TO_FLOAT(param->to.y); + lv_vector_path_quad_to(param->outline, &pt[0], &pt[1]); + } + else if(param->type == LV_FREETYPE_OUTLINE_END) { + lv_vector_path_close(param->outline); + } + } + break; + default: + LV_LOG_WARN("unknown event code: %d", code); + break; + } +} +#endif +/********************** + * GLOBAL FUNCTIONS + **********************/ + +lv_svg_render_obj_t * lv_svg_render_create(const lv_svg_node_t * svg_doc) +{ + if(!svg_doc) { + return NULL; + } + + struct _lv_svg_draw_dsc * dsc = _lv_svg_draw_dsc_create(); + struct _lv_svg_drawing_builder_state state = { + .doc = svg_doc, + .draw_dsc = dsc, + .in_group_deps = 0, + .in_defs = false, +#if LV_USE_FREETYPE + .in_text = false, + .cur_text = NULL, +#endif + .list = NULL, .tail = NULL + }; + + lv_tree_walk(LV_TREE_NODE(svg_doc), LV_TREE_WALK_PRE_ORDER, _lv_svg_doc_walk_cb, _lv_svg_doc_walk_before_cb, + _lv_svg_doc_walk_after_cb, &state); + _lv_svg_draw_dsc_delete(dsc); + return state.list; +} + +void lv_svg_render_delete(lv_svg_render_obj_t * list) +{ + while(list) { + lv_svg_render_obj_t * obj = list; + list = list->next; + + _deinit_draw_dsc(&(obj->dsc)); + + if(obj->destroy) { + obj->destroy(obj); + } + if(obj->id) { + lv_free(obj->id); + } + if(obj->fill_ref) { + lv_free(obj->fill_ref); + } + if(obj->stroke_ref) { + lv_free(obj->stroke_ref); + } + lv_free(obj); + } +} + +void lv_draw_svg_render(lv_vector_dsc_t * dsc, const lv_svg_render_obj_t * render) +{ + if(!render || !dsc) { + return; + } + + const lv_svg_render_obj_t * cur = render; + while(cur) { + if(cur->render && ((cur->flags & 3) == _RENDER_NORMAL)) { + _prepare_render(cur, dsc); + cur->render(cur, dsc, NULL); + } + cur = cur->next; + } +} + +void lv_draw_svg(lv_layer_t * layer, const lv_svg_node_t * svg_doc) +{ + if(!svg_doc) { + return; + } + + lv_vector_dsc_t * dsc = lv_vector_dsc_create(layer); + lv_svg_render_obj_t * list = lv_svg_render_create(svg_doc); + lv_draw_svg_render(dsc, list); + lv_draw_vector(dsc); + lv_svg_render_delete(list); + lv_vector_dsc_delete(dsc); +} + +/********************** + * STATIC FUNCTIONS + **********************/ +#endif /*LV_USE_SVG*/ diff --git a/src/libs/svg/lv_svg_render.h b/src/libs/svg/lv_svg_render.h new file mode 100644 index 0000000000..1353054880 --- /dev/null +++ b/src/libs/svg/lv_svg_render.h @@ -0,0 +1,98 @@ +/** + * @file lv_svg_render.h + * + */ + +#ifndef LV_SVG_RENDER_H +#define LV_SVG_RENDER_H + +/********************* + * INCLUDES + *********************/ +#include "../../lv_conf_internal.h" + +#if LV_USE_SVG && LV_USE_VECTOR_GRAPHIC +#include "lv_svg.h" +#include "../../misc/lv_types.h" +#include "../../draw/lv_draw_vector_private.h" + +/********************* + * DEFINES + *********************/ + +#define LV_SVG_RENDER_OBJ(n) ((lv_svg_render_obj_t*)(n)) + +/********************** + * TYPEDEFS + **********************/ + +typedef struct _lv_svg_render_obj { + struct _lv_svg_render_obj * next; + uint32_t flags; + char * id; + lv_vector_draw_dsc_t dsc; + lv_matrix_t matrix; + + /* for url(XXX) reference */ + struct _lv_svg_render_obj * head; + char * fill_ref; + char * stroke_ref; + void (*set_paint_ref)(struct _lv_svg_render_obj * obj, lv_vector_draw_dsc_t * dsc, + const struct _lv_svg_render_obj * target_obj, bool fill); + + void (*init)(struct _lv_svg_render_obj * obj, const lv_svg_node_t * node); + void (*render)(const struct _lv_svg_render_obj * obj, lv_vector_dsc_t * dsc, const lv_matrix_t * matrix); + void (*set_attr)(struct _lv_svg_render_obj * obj, lv_vector_draw_dsc_t * dsc, const lv_svg_attr_t * attr); + void (*get_bounds)(const struct _lv_svg_render_obj * obj, lv_area_t * area); + void (*destroy)(struct _lv_svg_render_obj * obj); +} lv_svg_render_obj_t; + +typedef struct _lv_svg_render_hal { + void (*load_image)(const char * image_url, lv_draw_image_dsc_t * img_dsc); + const char * (*get_font_path)(const char * font_family); +} lv_svg_render_hal_t; + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * @brief Initialize the SVG render + * @param hal pointer to a structure with rendering functions + */ +void lv_svg_render_init(const lv_svg_render_hal_t * hal); + +/** + * @brief Create a new SVG render from an SVG document + * @param svg_doc pointer to the SVG document + * @return pointer to the new SVG render object + */ +lv_svg_render_obj_t * lv_svg_render_create(const lv_svg_node_t * svg_doc); + +/** + * @brief Delete an SVG render object + * @param render pointer to the SVG render object to delete + */ +void lv_svg_render_delete(lv_svg_render_obj_t * render); + +/** + * @brief Render an SVG object to a vector graphics + * @param dsc pointer to the vector graphics descriptor + * @param render pointer to the SVG render object to render + */ +void lv_draw_svg_render(lv_vector_dsc_t * dsc, const lv_svg_render_obj_t * render); + +/** + * @brief Draw an SVG document to a layer + * @param layer pointer to the target layer + * @param svg_doc pointer to the SVG document to draw + */ +void lv_draw_svg(lv_layer_t * layer, const lv_svg_node_t * svg_doc); + +/********************** + * MACROS + **********************/ + +#endif /*LV_USE_SVG*/ + +#endif /*LV_SVG_RENDER_H*/ diff --git a/src/libs/svg/lv_svg_token.c b/src/libs/svg/lv_svg_token.c new file mode 100644 index 0000000000..6ba54ca434 --- /dev/null +++ b/src/libs/svg/lv_svg_token.c @@ -0,0 +1,483 @@ +/** + * @file lv_svg_token.c + * + */ + +/********************* + * INCLUDES + *********************/ + +#include "lv_svg_token.h" +#if LV_USE_SVG + +#include "../../../lvgl.h" +#include +#include + +/********************* +* DEFINES +*********************/ + +/********************** +* TYPEDEFS +**********************/ + +/* + * tag mask quote mask tag search comment doc type xml inst + * | 0 0 0 | 0 0 | 0 | 0 | 0 | 0 | 0 | + */ +enum { + SVG_TAG_MASK = (1 << 3) - 1, + SVG_QUOTE_MASK = (1 << 5) - (1 << 3), + SVG_TAG = 1 << 5, + SVG_SEARCH = 1 << 6, + SVG_COMMENT = 1 << 7, + SVG_DOCTYPE = 1 << 8, + SVG_XMLINST = 1 << 9, +}; +typedef uint32_t _lv_svg_parser_bits_t; + +enum { + SVG_NO_QUOTE = 0, + SVG_SINGLE_QUOTE = 1, + SVG_DOUBLE_QUOTE = 2, +}; +typedef uint32_t _lv_svg_parser_quote_t; + +enum { + SVG_NO_TAG = 0, + SVG_TAG_NAME = 1, + SVG_ATTR_START = 2, + SVG_ATTR_NAME = 3, + SVG_SEARCH_EQUAL = 4, + SVG_SEARCH_VALUE = 5, + SVG_QUOTE_VALUE = 6, + SVG_VALUE = 7, +}; +typedef uint32_t _lv_svg_parser_tag_state_t; + +typedef struct { + uint32_t flags; + const char * cur; + const char * end; +} _lv_svg_parser_state_t; + +/********************** + * STATIC PROTOTYPES + **********************/ +static void _set_state(_lv_svg_parser_state_t * state, uint32_t bit) +{ + state->flags |= bit; +} + +static void _clear_state(_lv_svg_parser_state_t * state, uint32_t bit) +{ + state->flags &= ~bit; +} + +static bool _is_state(_lv_svg_parser_state_t * state, uint32_t bit) +{ + return state->flags & bit; +} + +static void _set_tag_state(_lv_svg_parser_state_t * state, uint32_t bit) +{ + state->flags = (state->flags & ~SVG_TAG_MASK) | bit; +} + +static void _set_quote_state(_lv_svg_parser_state_t * state, uint32_t bit) +{ + state->flags = (state->flags & ~SVG_QUOTE_MASK) | (bit << 3); +} + +static bool _special_handle(_lv_svg_parser_state_t * state) +{ + return state->flags & (SVG_TAG | SVG_SEARCH | SVG_TAG_MASK | SVG_COMMENT | SVG_DOCTYPE | SVG_XMLINST); +} + +static void _lv_svg_token_init(_lv_svg_token_t * token) +{ + token->start = NULL; + token->end = NULL; + token->type = LV_SVG_TOKEN_CONTENT; + token->flat = false; + token->cur_attr = NULL; + lv_array_init(&token->attrs, LV_ARRAY_DEFAULT_CAPACITY, sizeof(_lv_svg_token_attr_t)); +} + +static void _lv_svg_token_reset(_lv_svg_token_t * token) +{ + token->start = NULL; + token->end = NULL; + token->type = LV_SVG_TOKEN_CONTENT; + token->flat = false; + token->cur_attr = NULL; + lv_array_clear(&token->attrs); +} + +static bool _lv_svg_token_process(_lv_svg_token_t * token, svg_token_process cb, void * data) +{ + if(!token->start || SVG_TOKEN_LEN(token) == 0) + return true; + + bool ret = cb(token, data); + _lv_svg_token_reset(token); + return ret; +} + +static _lv_svg_token_attr_t * _new_svg_attr(_lv_svg_token_t * token) +{ + if((lv_array_size(&token->attrs) + 1) > lv_array_capacity(&token->attrs)) { + lv_array_resize(&token->attrs, token->attrs.capacity << 1); + } + + token->attrs.size++; + _lv_svg_token_attr_t * attr = lv_array_at(&token->attrs, token->attrs.size - 1); + lv_memset(attr, 0, sizeof(_lv_svg_token_attr_t)); + return attr; +} + +static void _svg_parser_xml_inst(_lv_svg_parser_state_t * state, _lv_svg_token_t * token) +{ + LV_UNUSED(token); + + while(state->cur <= state->end) { + char ch = *(state->cur); + if(ch == '>' && (*(state->cur - 1)) == '?') { + _clear_state(state, SVG_XMLINST); + state->cur++; + break; + } + state->cur++; + } +} + +static void _svg_parser_comment(_lv_svg_parser_state_t * state, _lv_svg_token_t * token) +{ + LV_UNUSED(token); + + while(state->cur <= state->end) { + char ch = *(state->cur); + if(ch == '>' && (*(state->cur - 1)) == '-' && (*(state->cur - 2)) == '-') { + _clear_state(state, SVG_COMMENT); + state->cur++; + break; + } + state->cur++; + } +} + +static void _svg_parser_doctype(_lv_svg_parser_state_t * state, _lv_svg_token_t * token) +{ + LV_UNUSED(token); + + //TODO: processing DTD type + while(state->cur <= state->end) { + char ch = *(state->cur); + if(ch == '>') { + _clear_state(state, SVG_DOCTYPE); + state->cur++; + break; + } + state->cur++; + } +} + +static bool _svg_parser_tag(_lv_svg_parser_state_t * state, _lv_svg_token_t * token, svg_token_process cb, void * data) +{ + while(state->cur <= state->end) { + switch(state->flags & SVG_TAG_MASK) { + case SVG_NO_TAG: { + if(!_lv_svg_token_process(token, cb, data)) { + return false; + } + state->cur++; + } + return true; + case SVG_TAG_NAME: { + char ch = *(state->cur); + if(ch == '/') { + token->type = LV_SVG_TOKEN_END; + state->cur++; + if(!token->start) { + token->start = state->cur; + } + continue; + } + else if(ch == '>' || isspace(ch)) { + token->end = state->cur; + _set_tag_state(state, SVG_ATTR_START); + continue; + } + else { + if(!token->start) { + token->type = LV_SVG_TOKEN_BEGIN; + token->start = state->cur; + } + state->cur++; + continue; + } + } + break; + case SVG_ATTR_START: { + char ch = *(state->cur); + if(!isspace(ch) && ch != '\'' && ch != '\"') { + if(ch == '/') { + token->flat = true; + state->cur++; + continue; + } + if(ch == '>') { + _set_tag_state(state, SVG_NO_TAG); + } + else { + token->cur_attr = NULL; + _set_tag_state(state, SVG_ATTR_NAME); + } + continue; + } + } + break; + case SVG_ATTR_NAME: { + if(!token->cur_attr) { + token->cur_attr = _new_svg_attr(token); + } + char ch = *(state->cur); + if(isspace(ch) || ch == '=' || ch == '/' || ch == '>') { + token->cur_attr->name_end = state->cur; + _set_tag_state(state, SVG_SEARCH_EQUAL); + continue; + } + else { + if(!token->cur_attr->name_start) { + token->cur_attr->name_start = state->cur; + } + state->cur++; + continue; + } + } + break; + case SVG_SEARCH_EQUAL: { + char ch = *(state->cur); + if(!isspace(ch) && ch != '/' && ch != '\'' && ch != '\"') { + if(ch == '=') { + _set_tag_state(state, SVG_SEARCH_VALUE); + } + else { + // attr name has empty value + token->cur_attr = NULL; + _set_tag_state(state, SVG_ATTR_START); + continue; + } + } + } + break; + case SVG_SEARCH_VALUE: { + char ch = *(state->cur); + if(!isspace(ch)) { + if(ch == '\'' || ch == '\"') { + if(ch == '\'') { + _set_quote_state(state, SVG_SINGLE_QUOTE); + } + else { + _set_quote_state(state, SVG_DOUBLE_QUOTE); + } + _set_tag_state(state, SVG_QUOTE_VALUE); + } + else { + _set_tag_state(state, SVG_VALUE); + continue; + } + } + } + break; + case SVG_QUOTE_VALUE: { + char ch = *(state->cur); + if((ch == '\'' && ((state->flags & SVG_QUOTE_MASK) >> 3) == SVG_SINGLE_QUOTE) + || (ch == '\"' && ((state->flags & SVG_QUOTE_MASK) >> 3) == SVG_DOUBLE_QUOTE)) { + if(!token->cur_attr->value_start) { + token->cur_attr->value_start = state->cur; + } + token->cur_attr->value_end = state->cur; + _set_quote_state(state, SVG_NO_QUOTE); + _set_tag_state(state, SVG_ATTR_START); + continue; + } + else { + if(!token->cur_attr->value_start) { + token->cur_attr->value_start = state->cur; + } + state->cur++; + continue; + } + } + break; + case SVG_VALUE: { + char ch = *(state->cur); + if(isspace(ch) || ch == '>' || ch == '/') { + if(!token->cur_attr->value_start) { + token->cur_attr->value_start = state->cur; + } + token->cur_attr->value_end = state->cur; + _set_quote_state(state, SVG_NO_QUOTE); + _set_tag_state(state, SVG_ATTR_START); + continue; + } + else { + if(!token->cur_attr->value_start) { + token->cur_attr->value_start = state->cur; + } + state->cur++; + continue; + } + } + break; + } + state->cur++; + } + return true; +} + +/********************** + * STATIC VARIABLES + **********************/ + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +bool _lv_svg_tokenizer(const char * svg_data, uint32_t data_len, svg_token_process cb, void * data) +{ + LV_ASSERT_NULL(svg_data); + LV_ASSERT(data_len > 0); + LV_ASSERT_NULL(cb); + LV_ASSERT_NULL(data); + + _lv_svg_token_t token; + _lv_svg_token_init(&token); + _lv_svg_parser_state_t state = { + .flags = 0, + .cur = svg_data, + .end = svg_data + data_len, + }; + + while(state.cur <= state.end) { + char ch = *(state.cur); + if(ch == '\r' || ch == '\n') { // skip LR character + state.cur++; + continue; + } + else if(_special_handle(&state)) { + if(_is_state(&state, SVG_TAG)) { + _clear_state(&state, SVG_TAG); + switch(ch) { + case '/': // end tag + _set_tag_state(&state, SVG_TAG_NAME); + break; + case '!': { + // " + "" + "" + "" + "" + ""; + + svg = lv_svg_load_data(svg_com_4, lv_strlen(svg_com_4)); + TEST_ASSERT_NOT_EQUAL(NULL, svg); + draw_svg(svg); + draw_snapshot(SNAPSHOT_NAME(svg_com_4)); + lv_svg_node_delete(svg); + + const char * svg_com_5 = \ + "" + "" + "" + "" + "" + ""; + + svg = lv_svg_load_data(svg_com_5, lv_strlen(svg_com_5)); + TEST_ASSERT_NOT_EQUAL(NULL, svg); + draw_svg(svg); + draw_snapshot(SNAPSHOT_NAME(svg_com_5)); + lv_svg_node_delete(svg); + + const char * svg_com_6 = \ + "" + "" + "" + "" + "" + "" + "" + "" + ""; + + svg = lv_svg_load_data(svg_com_6, lv_strlen(svg_com_6)); + TEST_ASSERT_NOT_EQUAL(NULL, svg); + draw_svg(svg); + draw_snapshot(SNAPSHOT_NAME(svg_com_6)); + lv_svg_node_delete(svg); + + const char * svg_com_7 = \ + "" + "" + "" + ""; + + svg = lv_svg_load_data(svg_com_7, lv_strlen(svg_com_7)); + TEST_ASSERT_NOT_EQUAL(NULL, svg); + draw_svg(svg); + draw_snapshot(SNAPSHOT_NAME(svg_com_7)); + lv_svg_node_delete(svg); + + const char * svg_com_8 = \ + "" + "داستان SVG Tiny 1.2 طولا ني است." + ""; + + svg = lv_svg_load_data(svg_com_8, lv_strlen(svg_com_8)); + TEST_ASSERT_NOT_EQUAL(NULL, svg); + draw_svg(svg); + draw_snapshot(SNAPSHOT_NAME(svg_com_8)); + lv_svg_node_delete(svg); + +} + +void test_draw_svg(void) +{ + const char * svg_viewport_1 = \ + ""; + + lv_svg_node_t * svg = lv_svg_load_data(svg_viewport_1, lv_strlen(svg_viewport_1)); + TEST_ASSERT_NOT_EQUAL(NULL, svg); + draw_svg(svg); + draw_snapshot(SNAPSHOT_NAME(svg_viewport_1)); + lv_svg_node_delete(svg); + + const char * svg_viewport_2 = \ + ""; + + svg = lv_svg_load_data(svg_viewport_2, lv_strlen(svg_viewport_2)); + TEST_ASSERT_NOT_EQUAL(NULL, svg); + draw_svg(svg); + draw_snapshot(SNAPSHOT_NAME(svg_viewport_2)); + lv_svg_node_delete(svg); + + const char * svg_viewport_3 = \ + "" + "" + "" + "" + "Stretch to fit" + ""; + + svg = lv_svg_load_data(svg_viewport_3, lv_strlen(svg_viewport_3)); + TEST_ASSERT_NOT_EQUAL(NULL, svg); + draw_svg(svg); + draw_snapshot(SNAPSHOT_NAME(svg_viewport_3)); + lv_svg_node_delete(svg); +} +#else + +void test_draw_svg(void) +{ + ; +} +#endif +#endif diff --git a/tests/src/test_cases/test_svg.c b/tests/src/test_cases/test_svg.c new file mode 100644 index 0000000000..aec799548b --- /dev/null +++ b/tests/src/test_cases/test_svg.c @@ -0,0 +1,853 @@ +#if LV_BUILD_TEST +#include "../lvgl.h" + +#include "unity/unity.h" + +#define LV_ARRAY_GET(array, index, type) ((type*)lv_array_at((array), (index))) + +static const char * svg_str_1 = \ + "" + "" + "" + "" + "" + " SVG Test " + ""; + +void setUp(void) +{ +} + +void tearDown(void) +{ +} + +void testSvgParser(void) +{ + lv_svg_node_t * svg = lv_svg_load_data(svg_str_1, lv_strlen(svg_str_1)); + TEST_ASSERT_NOT_EQUAL(NULL, svg); + lv_svg_node_delete(svg); +} + +void testNode(void) +{ + lv_svg_node_t * svg = lv_svg_node_create(NULL); + + TEST_ASSERT_NOT_EQUAL(NULL, svg); + lv_svg_node_delete(svg); +} + +void testNodeTree(void) +{ + lv_svg_node_t * root = lv_svg_node_create(NULL); + + lv_svg_node_t * node1 = lv_svg_node_create(root); + lv_svg_node_t * node2 = lv_svg_node_create(node1); + lv_svg_node_t * node3 = lv_svg_node_create(root); + + lv_svg_attr_t attr1; + attr1.id = LV_SVG_ATTR_X; + attr1.val_type = LV_SVG_ATTR_VALUE_DATA; + attr1.value.fval = 10.0f; + + lv_array_push_back(&node3->attrs, &attr1); + + TEST_ASSERT_EQUAL(lv_array_size(&node3->attrs), 1); + TEST_ASSERT_EQUAL_FLOAT((LV_ARRAY_GET(&node3->attrs, 0, lv_svg_attr_t))->value.fval, 10.0f); + + lv_svg_node_delete(node2); + lv_svg_node_delete(root); +} + +void testSvgElement(void) +{ + const char * svg_1 = \ + ""; + lv_svg_node_t * svg_node1 = lv_svg_load_data(svg_1, lv_strlen(svg_1)); + TEST_ASSERT_EQUAL(lv_array_size(&svg_node1->attrs), 2); + TEST_ASSERT_EQUAL_STRING((LV_ARRAY_GET(&svg_node1->attrs, 0, lv_svg_attr_t))->value.sval, "1.2"); + TEST_ASSERT_EQUAL_STRING((LV_ARRAY_GET(&svg_node1->attrs, 1, lv_svg_attr_t))->value.sval, "tiny"); + lv_svg_node_delete(svg_node1); + + /* test viewBox */ + const char * svg_viewbox0 = \ + ""; + lv_svg_node_t * svg_node_viewbox = lv_svg_load_data(svg_viewbox0, lv_strlen(svg_viewbox0)); + TEST_ASSERT_EQUAL(0, LV_ARRAY_GET(&svg_node_viewbox->attrs, 0, lv_svg_attr_t)->class_type); + lv_svg_node_delete(svg_node_viewbox); + + const char * svg_viewbox1 = \ + ""; + lv_svg_node_t * svg_node_viewbox1 = lv_svg_load_data(svg_viewbox1, lv_strlen(svg_viewbox1)); + float ret1[4] = {0.0f, 0.0f, 10.0f, 10.0f}; + TEST_ASSERT_EQUAL_FLOAT_ARRAY(ret1, (float *)(LV_ARRAY_GET(&svg_node_viewbox1->attrs, 0, lv_svg_attr_t))->value.val, 4); + lv_svg_node_delete(svg_node_viewbox1); + + const char * svg_viewbox2 = \ + ""; + lv_svg_node_t * svg_node_viewbox2 = lv_svg_load_data(svg_viewbox2, lv_strlen(svg_viewbox2)); + float ret2[4] = {-5.0f, 10.0f, 10.0f, -10.0f}; + TEST_ASSERT_EQUAL_FLOAT_ARRAY(ret2, (float *)(LV_ARRAY_GET(&svg_node_viewbox2->attrs, 0, lv_svg_attr_t))->value.val, 4); + lv_svg_node_delete(svg_node_viewbox2); + + const char * svg_viewbox3 = \ + ""; + lv_svg_node_t * svg_node_viewbox3 = lv_svg_load_data(svg_viewbox3, lv_strlen(svg_viewbox3)); + TEST_ASSERT_EQUAL(0, LV_ARRAY_GET(&svg_node_viewbox3->attrs, 0, lv_svg_attr_t)->class_type); + lv_svg_node_delete(svg_node_viewbox3); + + const char * svg_viewbox4 = \ + ""; + lv_svg_node_t * svg_node_viewbox4 = lv_svg_load_data(svg_viewbox4, lv_strlen(svg_viewbox4)); + float ret4[4] = {-5.0f, -5.0f, 0.2f, 15.0f}; + TEST_ASSERT_EQUAL_FLOAT_ARRAY(ret4, (float *)(LV_ARRAY_GET(&svg_node_viewbox4->attrs, 0, lv_svg_attr_t))->value.val, 4); + lv_svg_node_delete(svg_node_viewbox4); + + /* width and height */ + const char * svg_wh = \ + ""; + lv_svg_node_t * svg_node_wh = lv_svg_load_data(svg_wh, lv_strlen(svg_wh)); + TEST_ASSERT_EQUAL_FLOAT((LV_ARRAY_GET(&svg_node_wh->attrs, 0, lv_svg_attr_t))->value.fval, 100.0f); + TEST_ASSERT_EQUAL_FLOAT((LV_ARRAY_GET(&svg_node_wh->attrs, 1, lv_svg_attr_t))->value.fval, 100.0f); + lv_svg_node_delete(svg_node_wh); + + const char * svg_wh2 = \ + ""; + lv_svg_node_t * svg_node_wh2 = lv_svg_load_data(svg_wh2, lv_strlen(svg_wh2)); + TEST_ASSERT_EQUAL_FLOAT((LV_ARRAY_GET(&svg_node_wh2->attrs, 0, lv_svg_attr_t))->value.fval, 377.9528f); + TEST_ASSERT_EQUAL_FLOAT((LV_ARRAY_GET(&svg_node_wh2->attrs, 1, lv_svg_attr_t))->value.fval, 377.9528f); + lv_svg_node_delete(svg_node_wh2); + + const char * svg_wh3 = \ + ""; + lv_svg_node_t * svg_node_wh3 = lv_svg_load_data(svg_wh3, lv_strlen(svg_wh3)); + TEST_ASSERT_EQUAL_FLOAT((LV_ARRAY_GET(&svg_node_wh3->attrs, 0, lv_svg_attr_t))->value.fval, 960.0f); + TEST_ASSERT_EQUAL_FLOAT((LV_ARRAY_GET(&svg_node_wh3->attrs, 1, lv_svg_attr_t))->value.fval, 160.0f); + lv_svg_node_delete(svg_node_wh3); + + const char * svg_wh4 = \ + ""; + lv_svg_node_t * svg_node_wh4 = lv_svg_load_data(svg_wh4, lv_strlen(svg_wh4)); + TEST_ASSERT_EQUAL_FLOAT((LV_ARRAY_GET(&svg_node_wh4->attrs, 0, lv_svg_attr_t))->value.fval, 160.0f); + TEST_ASSERT_EQUAL_FLOAT((LV_ARRAY_GET(&svg_node_wh4->attrs, 1, lv_svg_attr_t))->value.fval, 83.2f); + lv_svg_node_delete(svg_node_wh4); + + const char * svg_wh5 = \ + ""; + lv_svg_node_t * svg_node_wh5 = lv_svg_load_data(svg_wh5, lv_strlen(svg_wh5)); + TEST_ASSERT_EQUAL_FLOAT((LV_ARRAY_GET(&svg_node_wh5->attrs, 0, lv_svg_attr_t))->value.fval, 13.3333f); + TEST_ASSERT_EQUAL_FLOAT((LV_ARRAY_GET(&svg_node_wh5->attrs, 1, lv_svg_attr_t))->value.fval, 1.0f); + lv_svg_node_delete(svg_node_wh5); + + /* preserveAspectRatio */ + + const char * svg_ar0 = \ + ""; + lv_svg_node_t * svg_node_ar = lv_svg_load_data(svg_ar0, lv_strlen(svg_ar0)); + TEST_ASSERT_EQUAL(lv_array_size(&svg_node_ar->attrs), 1); + lv_svg_node_delete(svg_node_ar); + + const char * svg_ar1 = \ + ""; + svg_node_ar = lv_svg_load_data(svg_ar1, lv_strlen(svg_ar1)); + TEST_ASSERT_EQUAL((LV_ARRAY_GET(&svg_node_ar->attrs, 0, lv_svg_attr_t))->value.uval, 2); + lv_svg_node_delete(svg_node_ar); + + const char * svg_ar2 = \ + ""; + svg_node_ar = lv_svg_load_data(svg_ar2, lv_strlen(svg_ar2)); + TEST_ASSERT_EQUAL((LV_ARRAY_GET(&svg_node_ar->attrs, 0, lv_svg_attr_t))->value.uval, 5); + lv_svg_node_delete(svg_node_ar); + + const char * svg_ar3 = \ + ""; + svg_node_ar = lv_svg_load_data(svg_ar3, lv_strlen(svg_ar3)); + TEST_ASSERT_EQUAL((LV_ARRAY_GET(&svg_node_ar->attrs, 0, lv_svg_attr_t))->value.uval, 6); + lv_svg_node_delete(svg_node_ar); + + const char * svg_ar4 = \ + ""; + svg_node_ar = lv_svg_load_data(svg_ar4, lv_strlen(svg_ar4)); + TEST_ASSERT_EQUAL((LV_ARRAY_GET(&svg_node_ar->attrs, 0, lv_svg_attr_t))->value.uval, 8); + lv_svg_node_delete(svg_node_ar); + + const char * svg_ar5 = \ + ""; + svg_node_ar = lv_svg_load_data(svg_ar5, lv_strlen(svg_ar5)); + TEST_ASSERT_EQUAL((LV_ARRAY_GET(&svg_node_ar->attrs, 0, lv_svg_attr_t))->value.uval, 10); + lv_svg_node_delete(svg_node_ar); + + const char * svg_ar6 = \ + ""; + svg_node_ar = lv_svg_load_data(svg_ar6, lv_strlen(svg_ar6)); + TEST_ASSERT_EQUAL((LV_ARRAY_GET(&svg_node_ar->attrs, 0, lv_svg_attr_t))->value.uval, 13); + lv_svg_node_delete(svg_node_ar); + + const char * svg_ar7 = \ + ""; + svg_node_ar = lv_svg_load_data(svg_ar7, lv_strlen(svg_ar7)); + TEST_ASSERT_EQUAL((LV_ARRAY_GET(&svg_node_ar->attrs, 0, lv_svg_attr_t))->value.uval, 15); + lv_svg_node_delete(svg_node_ar); + + const char * svg_ar8 = \ + ""; + svg_node_ar = lv_svg_load_data(svg_ar8, lv_strlen(svg_ar8)); + TEST_ASSERT_EQUAL((LV_ARRAY_GET(&svg_node_ar->attrs, 0, lv_svg_attr_t))->value.uval, 16); + lv_svg_node_delete(svg_node_ar); + + const char * svg_ar9 = \ + ""; + svg_node_ar = lv_svg_load_data(svg_ar9, lv_strlen(svg_ar9)); + TEST_ASSERT_EQUAL((LV_ARRAY_GET(&svg_node_ar->attrs, 0, lv_svg_attr_t))->value.uval, 18); + lv_svg_node_delete(svg_node_ar); + + const char * svg_ar10 = \ + ""; + svg_node_ar = lv_svg_load_data(svg_ar10, lv_strlen(svg_ar10)); + TEST_ASSERT_EQUAL(lv_array_size(&svg_node_ar->attrs), 1); + lv_svg_node_delete(svg_node_ar); +} + +void testPolylineElement(void) +{ + const char * svg_poly1 = \ + ""; + lv_svg_node_t * svg_node_root = lv_svg_load_data(svg_poly1, lv_strlen(svg_poly1)); + lv_svg_node_t * svg_node = LV_SVG_NODE_CHILD(svg_node_root, 0); + TEST_ASSERT_EQUAL(((lv_svg_attr_values_list_t *)(LV_ARRAY_GET(&svg_node->attrs, 0, lv_svg_attr_t))->value.val)->length, + 5); + lv_svg_node_delete(svg_node_root); + + const char * svg_poly2 = \ + ""; + svg_node_root = lv_svg_load_data(svg_poly2, lv_strlen(svg_poly2)); + svg_node = LV_SVG_NODE_CHILD(svg_node_root, 0); + TEST_ASSERT_EQUAL(((lv_svg_attr_values_list_t *)(LV_ARRAY_GET(&svg_node->attrs, 0, lv_svg_attr_t))->value.val)->length, + 3); + lv_svg_node_delete(svg_node_root); + + const char * svg_poly3 = \ + ""; + svg_node_root = lv_svg_load_data(svg_poly3, lv_strlen(svg_poly3)); + svg_node = LV_SVG_NODE_CHILD(svg_node_root, 0); + TEST_ASSERT_EQUAL(((lv_svg_attr_values_list_t *)(LV_ARRAY_GET(&svg_node->attrs, 0, lv_svg_attr_t))->value.val)->length, + 1); + lv_svg_node_delete(svg_node_root); + + const char * svg_poly4 = \ + ""; + svg_node_root = lv_svg_load_data(svg_poly4, lv_strlen(svg_poly4)); + svg_node = LV_SVG_NODE_CHILD(svg_node_root, 0); + TEST_ASSERT_EQUAL(((lv_svg_attr_values_list_t *)(LV_ARRAY_GET(&svg_node->attrs, 0, lv_svg_attr_t))->value.val)->length, + 0); + lv_svg_node_delete(svg_node_root); + + const char * svg_poly5 = \ + ""; + svg_node_root = lv_svg_load_data(svg_poly5, lv_strlen(svg_poly5)); + svg_node = LV_SVG_NODE_CHILD(svg_node_root, 0); + TEST_ASSERT_EQUAL(lv_array_size(&svg_node->attrs), 0); + lv_svg_node_delete(svg_node_root); + +} + +void testPathElement(void) +{ + const char * svg_path1 = \ + ""; + lv_svg_node_t * svg_node_root = lv_svg_load_data(svg_path1, lv_strlen(svg_path1)); + lv_svg_node_t * svg_node = LV_SVG_NODE_CHILD(svg_node_root, 0); + TEST_ASSERT_EQUAL(lv_array_size(&svg_node->attrs), 0); + lv_svg_node_delete(svg_node_root); + + const char * svg_path2 = \ + ""; + svg_node_root = lv_svg_load_data(svg_path2, lv_strlen(svg_path2)); + svg_node = LV_SVG_NODE_CHILD(svg_node_root, 0); + TEST_ASSERT_EQUAL(((lv_svg_attr_values_list_t *)(LV_ARRAY_GET(&svg_node->attrs, 0, lv_svg_attr_t))->value.val)->length, + 0); + lv_svg_node_delete(svg_node_root); + + const char * svg_path3 = \ + ""; + svg_node_root = lv_svg_load_data(svg_path3, lv_strlen(svg_path3)); + svg_node = LV_SVG_NODE_CHILD(svg_node_root, 0); + TEST_ASSERT_EQUAL(((lv_svg_attr_values_list_t *)(LV_ARRAY_GET(&svg_node->attrs, 0, lv_svg_attr_t))->value.val)->length, + 0); + lv_svg_node_delete(svg_node_root); + + const char * svg_path4 = \ + ""; + svg_node_root = lv_svg_load_data(svg_path4, lv_strlen(svg_path4)); + svg_node = LV_SVG_NODE_CHILD(svg_node_root, 0); + TEST_ASSERT_EQUAL(((lv_svg_attr_values_list_t *)(LV_ARRAY_GET(&svg_node->attrs, 0, lv_svg_attr_t))->value.val)->length, + 5); + lv_svg_node_delete(svg_node_root); + + const char * svg_path5 = \ + ""; + svg_node_root = lv_svg_load_data(svg_path5, lv_strlen(svg_path5)); + svg_node = LV_SVG_NODE_CHILD(svg_node_root, 0); + TEST_ASSERT_EQUAL(((lv_svg_attr_values_list_t *)(LV_ARRAY_GET(&svg_node->attrs, 0, lv_svg_attr_t))->value.val)->length, + 9); + uint32_t seg_size = sizeof(uint32_t) + sizeof(lv_svg_point_t); + lv_svg_attr_values_list_t * list = (lv_svg_attr_values_list_t *)(LV_ARRAY_GET(&svg_node->attrs, 0, + lv_svg_attr_t))->value.val; + + lv_svg_attr_path_value_t * path = (lv_svg_attr_path_value_t *)(&list->data); + TEST_ASSERT_EQUAL(path->cmd, LV_SVG_PATH_CMD_MOVE_TO); + + path = (lv_svg_attr_path_value_t *)((uint8_t *)&list->data + seg_size); + TEST_ASSERT_EQUAL(path->cmd, LV_SVG_PATH_CMD_LINE_TO); + + path = (lv_svg_attr_path_value_t *)((uint8_t *)&list->data + seg_size * 2); + TEST_ASSERT_EQUAL(path->cmd, LV_SVG_PATH_CMD_LINE_TO); + + TEST_ASSERT_EQUAL_FLOAT(((lv_svg_point_t *)(&path->data))->x, 400.0f); + TEST_ASSERT_EQUAL_FLOAT(((lv_svg_point_t *)(&path->data))->y, 450.0f); + + path = (lv_svg_attr_path_value_t *)((uint8_t *)&list->data + seg_size * 7); + TEST_ASSERT_EQUAL(path->cmd, LV_SVG_PATH_CMD_LINE_TO); + + TEST_ASSERT_EQUAL_FLOAT(((lv_svg_point_t *)(&path->data))->x, 600.0f); + TEST_ASSERT_EQUAL_FLOAT(((lv_svg_point_t *)(&path->data))->y, 650.0f); + lv_svg_node_delete(svg_node_root); + + const char * svg_path6 = \ + ""; + svg_node_root = lv_svg_load_data(svg_path6, lv_strlen(svg_path6)); + svg_node = LV_SVG_NODE_CHILD(svg_node_root, 0); + TEST_ASSERT_EQUAL(((lv_svg_attr_values_list_t *)(LV_ARRAY_GET(&svg_node->attrs, 0, lv_svg_attr_t))->value.val)->length, + 10); + lv_svg_node_delete(svg_node_root); +} + +void testTransform(void) +{ + const char * svg_tr1 = \ + ""; + lv_svg_node_t * svg_node_root = lv_svg_load_data(svg_tr1, lv_strlen(svg_tr1)); + lv_svg_node_t * svg_node = LV_SVG_NODE_CHILD(svg_node_root, 0); + TEST_ASSERT_EQUAL(lv_array_size(&svg_node->attrs), 0); + lv_svg_node_delete(svg_node_root); + + const char * svg_tr2 = \ + ""; + svg_node_root = lv_svg_load_data(svg_tr2, lv_strlen(svg_tr2)); + svg_node = LV_SVG_NODE_CHILD(svg_node_root, 0); + TEST_ASSERT_EQUAL(lv_array_size(&svg_node->attrs), 1); + lv_svg_node_delete(svg_node_root); + + const char * svg_tr3 = \ + ""; + svg_node_root = lv_svg_load_data(svg_tr3, lv_strlen(svg_tr3)); + svg_node = LV_SVG_NODE_CHILD(svg_node_root, 0); + lv_svg_matrix_t * matrix = (lv_svg_matrix_t *)(LV_ARRAY_GET(&svg_node->attrs, 0, lv_svg_attr_t))->value.val; + + TEST_ASSERT_EQUAL_FLOAT(matrix->m[0][0], 1.0f); + TEST_ASSERT_EQUAL_FLOAT(matrix->m[1][1], 1.0f); + TEST_ASSERT_EQUAL_FLOAT(matrix->m[2][2], 1.0f); + + lv_svg_node_delete(svg_node_root); + + const char * svg_tr4 = \ + ""; + svg_node_root = lv_svg_load_data(svg_tr4, lv_strlen(svg_tr4)); + svg_node = LV_SVG_NODE_CHILD(svg_node_root, 0); + matrix = (lv_svg_matrix_t *)(LV_ARRAY_GET(&svg_node->attrs, 0, lv_svg_attr_t))->value.val; + + TEST_ASSERT_EQUAL_FLOAT(matrix->m[0][0], 1.5f); + TEST_ASSERT_EQUAL_FLOAT(matrix->m[1][0], 0.0f); + TEST_ASSERT_EQUAL_FLOAT(matrix->m[0][1], 2.0f); + TEST_ASSERT_EQUAL_FLOAT(matrix->m[1][1], 2.0f); + TEST_ASSERT_EQUAL_FLOAT(matrix->m[0][2], 10.0f); + TEST_ASSERT_EQUAL_FLOAT(matrix->m[1][2], 20.0f); + + lv_svg_node_delete(svg_node_root); + + const char * svg_tr5 = \ + ""; + svg_node_root = lv_svg_load_data(svg_tr5, lv_strlen(svg_tr5)); + svg_node = LV_SVG_NODE_CHILD(svg_node_root, 0); + matrix = (lv_svg_matrix_t *)(LV_ARRAY_GET(&svg_node->attrs, 0, lv_svg_attr_t))->value.val; + + TEST_ASSERT_EQUAL_FLOAT(matrix->m[0][2], 3.0f); + TEST_ASSERT_EQUAL_FLOAT(matrix->m[1][2], 4.0f); + + lv_svg_node_delete(svg_node_root); + + const char * svg_tr6 = \ + ""; + svg_node_root = lv_svg_load_data(svg_tr6, lv_strlen(svg_tr6)); + svg_node = LV_SVG_NODE_CHILD(svg_node_root, 0); + matrix = (lv_svg_matrix_t *)(LV_ARRAY_GET(&svg_node->attrs, 0, lv_svg_attr_t))->value.val; + + TEST_ASSERT_EQUAL_FLOAT(matrix->m[0][0], 0.25f); + TEST_ASSERT_EQUAL_FLOAT(matrix->m[1][1], 0.25f); + + lv_svg_node_delete(svg_node_root); + + const char * svg_tr7 = \ + ""; + svg_node_root = lv_svg_load_data(svg_tr7, lv_strlen(svg_tr7)); + svg_node = LV_SVG_NODE_CHILD(svg_node_root, 0); + matrix = (lv_svg_matrix_t *)(LV_ARRAY_GET(&svg_node->attrs, 0, lv_svg_attr_t))->value.val; + + TEST_ASSERT_EQUAL_FLOAT(matrix->m[0][2], 10.0f); + TEST_ASSERT_EQUAL_FLOAT(matrix->m[1][2], 0.0f); + TEST_ASSERT_EQUAL_FLOAT(matrix->m[0][0], 0.25f); + TEST_ASSERT_EQUAL_FLOAT(matrix->m[1][1], 0.25f); + + lv_svg_node_delete(svg_node_root); + + const char * svg_tr8 = \ + ""; + svg_node_root = lv_svg_load_data(svg_tr8, lv_strlen(svg_tr8)); + svg_node = LV_SVG_NODE_CHILD(svg_node_root, 0); + matrix = (lv_svg_matrix_t *)(LV_ARRAY_GET(&svg_node->attrs, 0, lv_svg_attr_t))->value.val; + + TEST_ASSERT_EQUAL_FLOAT(matrix->m[0][2], 20.0f); + TEST_ASSERT_EQUAL_FLOAT(matrix->m[1][2], 0.0f); + + lv_svg_node_delete(svg_node_root); + + const char * svg_tr9 = \ + ""; + svg_node_root = lv_svg_load_data(svg_tr9, lv_strlen(svg_tr9)); + svg_node = LV_SVG_NODE_CHILD(svg_node_root, 0); + matrix = (lv_svg_matrix_t *)(LV_ARRAY_GET(&svg_node->attrs, 0, lv_svg_attr_t))->value.val; + + TEST_ASSERT_EQUAL_FLOAT(matrix->m[0][2], 0.0f); + TEST_ASSERT_EQUAL_FLOAT(matrix->m[1][2], 0.0f); + + lv_svg_node_delete(svg_node_root); + + const char * svg_tr10 = \ + ""; + svg_node_root = lv_svg_load_data(svg_tr10, lv_strlen(svg_tr10)); + svg_node = LV_SVG_NODE_CHILD(svg_node_root, 0); + matrix = (lv_svg_matrix_t *)(LV_ARRAY_GET(&svg_node->attrs, 0, lv_svg_attr_t))->value.val; + + TEST_ASSERT_EQUAL_FLOAT(matrix->m[0][1], 0.176327f); + TEST_ASSERT_EQUAL_FLOAT(matrix->m[1][0], 0.176327f); + + lv_svg_node_delete(svg_node_root); +} + +void testStrokeFill(void) +{ + const char * svg_sf1 = \ + ""; + lv_svg_node_t * svg_node_root = lv_svg_load_data(svg_sf1, lv_strlen(svg_sf1)); + lv_svg_node_t * svg_node = LV_SVG_NODE_CHILD(svg_node_root, 0); + TEST_ASSERT_EQUAL(lv_array_size(&svg_node->attrs), 0); + lv_svg_node_delete(svg_node_root); + + const char * svg_sf2 = \ + ""; + svg_node_root = lv_svg_load_data(svg_sf2, lv_strlen(svg_sf2)); + svg_node = LV_SVG_NODE_CHILD(svg_node_root, 0); + TEST_ASSERT_EQUAL(lv_array_size(&svg_node->attrs), 2); + lv_svg_node_delete(svg_node_root); + + const char * svg_sf3 = \ + ""; + svg_node_root = lv_svg_load_data(svg_sf3, lv_strlen(svg_sf3)); + svg_node = LV_SVG_NODE_CHILD(svg_node_root, 0); + const char * str = (const char *)(LV_ARRAY_GET(&svg_node->attrs, 0, lv_svg_attr_t))->value.sval; + TEST_ASSERT_EQUAL_STRING(str, "grad1"); + lv_svg_node_delete(svg_node_root); + + const char * svg_sf4 = \ + ""; + svg_node_root = lv_svg_load_data(svg_sf4, lv_strlen(svg_sf4)); + svg_node = LV_SVG_NODE_CHILD(svg_node_root, 0); + str = (const char *)(LV_ARRAY_GET(&svg_node->attrs, 0, lv_svg_attr_t))->value.sval; + TEST_ASSERT_EQUAL_STRING(str, ""); + lv_svg_node_delete(svg_node_root); + + const char * svg_sf5 = \ + ""; + svg_node_root = lv_svg_load_data(svg_sf5, lv_strlen(svg_sf5)); + svg_node = LV_SVG_NODE_CHILD(svg_node_root, 0); + str = (const char *)(LV_ARRAY_GET(&svg_node->attrs, 0, lv_svg_attr_t))->value.sval; + TEST_ASSERT_EQUAL_STRING(str, "grad2"); + lv_svg_node_delete(svg_node_root); + + const char * svg_sf6 = \ + ""; + svg_node_root = lv_svg_load_data(svg_sf6, lv_strlen(svg_sf6)); + svg_node = LV_SVG_NODE_CHILD(svg_node_root, 0); + uint32_t c = (LV_ARRAY_GET(&svg_node->attrs, 0, lv_svg_attr_t))->value.uval; + TEST_ASSERT_EQUAL(c, 0); + lv_svg_node_delete(svg_node_root); + + const char * svg_sf7 = \ + ""; + svg_node_root = lv_svg_load_data(svg_sf7, lv_strlen(svg_sf7)); + svg_node = LV_SVG_NODE_CHILD(svg_node_root, 0); + c = (LV_ARRAY_GET(&svg_node->attrs, 0, lv_svg_attr_t))->value.uval; + TEST_ASSERT_EQUAL(c, 0xffffff); + lv_svg_node_delete(svg_node_root); + + const char * svg_sf8 = \ + ""; + svg_node_root = lv_svg_load_data(svg_sf8, lv_strlen(svg_sf8)); + svg_node = LV_SVG_NODE_CHILD(svg_node_root, 0); + c = (LV_ARRAY_GET(&svg_node->attrs, 0, lv_svg_attr_t))->value.uval; + TEST_ASSERT_EQUAL(c, 0x808080); + lv_svg_node_delete(svg_node_root); + + const char * svg_sf9 = \ + ""; + svg_node_root = lv_svg_load_data(svg_sf9, lv_strlen(svg_sf9)); + svg_node = LV_SVG_NODE_CHILD(svg_node_root, 0); + c = (LV_ARRAY_GET(&svg_node->attrs, 0, lv_svg_attr_t))->value.uval; + TEST_ASSERT_EQUAL(c, 0xff0000); + lv_svg_node_delete(svg_node_root); + + const char * svg_sf10 = \ + ""; + svg_node_root = lv_svg_load_data(svg_sf10, lv_strlen(svg_sf10)); + svg_node = LV_SVG_NODE_CHILD(svg_node_root, 0); + c = (LV_ARRAY_GET(&svg_node->attrs, 0, lv_svg_attr_t))->value.uval; + TEST_ASSERT_EQUAL(c, 0xff8000); + lv_svg_node_delete(svg_node_root); + + const char * svg_sf11 = \ + ""; + svg_node_root = lv_svg_load_data(svg_sf11, lv_strlen(svg_sf11)); + svg_node = LV_SVG_NODE_CHILD(svg_node_root, 0); + c = (LV_ARRAY_GET(&svg_node->attrs, 0, lv_svg_attr_t))->value.uval; + TEST_ASSERT_EQUAL(c, 0xff0000); + lv_svg_node_delete(svg_node_root); +} + +void testStrokeFillAttrs(void) +{ + const char * svg_sf0 = \ + ""; + lv_svg_node_t * svg_node_root = lv_svg_load_data(svg_sf0, lv_strlen(svg_sf0)); + lv_svg_node_t * svg_node = LV_SVG_NODE_CHILD(svg_node_root, 0); + TEST_ASSERT_EQUAL(lv_array_size(&svg_node->attrs), 0); + lv_svg_node_delete(svg_node_root); + + const char * svg_sf1 = \ + ""; + svg_node_root = lv_svg_load_data(svg_sf1, lv_strlen(svg_sf1)); + svg_node = LV_SVG_NODE_CHILD(svg_node_root, 0); + float f1 = (LV_ARRAY_GET(&svg_node->attrs, 0, lv_svg_attr_t))->value.fval; + TEST_ASSERT_EQUAL_FLOAT(f1, 1.0f); + uint32_t f2 = (LV_ARRAY_GET(&svg_node->attrs, 1, lv_svg_attr_t))->value.ival; + TEST_ASSERT_EQUAL(f2, 3); + lv_svg_node_delete(svg_node_root); + + const char * svg_sf2 = \ + ""; + svg_node_root = lv_svg_load_data(svg_sf2, lv_strlen(svg_sf2)); + svg_node = LV_SVG_NODE_CHILD(svg_node_root, 0); + uint32_t r1 = (LV_ARRAY_GET(&svg_node->attrs, 0, lv_svg_attr_t))->value.ival; + TEST_ASSERT_EQUAL(r1, LV_SVG_FILL_EVENODD); + float w1 = (LV_ARRAY_GET(&svg_node->attrs, 1, lv_svg_attr_t))->value.fval; + TEST_ASSERT_EQUAL_FLOAT(w1, 0.0f); + uint32_t l1 = (LV_ARRAY_GET(&svg_node->attrs, 2, lv_svg_attr_t))->value.uval; + TEST_ASSERT_EQUAL(l1, 1); + lv_svg_node_delete(svg_node_root); + + const char * svg_sf3 = \ + ""; + svg_node_root = lv_svg_load_data(svg_sf3, lv_strlen(svg_sf3)); + svg_node = LV_SVG_NODE_CHILD(svg_node_root, 0); + uint32_t c1 = (LV_ARRAY_GET(&svg_node->attrs, 0, lv_svg_attr_t))->value.ival; + TEST_ASSERT_EQUAL(c1, LV_SVG_LINE_CAP_ROUND); + uint32_t c2 = (LV_ARRAY_GET(&svg_node->attrs, 1, lv_svg_attr_t))->value.ival; + TEST_ASSERT_EQUAL(c2, LV_SVG_LINE_JOIN_BEVEL); + uint32_t r2 = (LV_ARRAY_GET(&svg_node->attrs, 2, lv_svg_attr_t))->value.ival; + TEST_ASSERT_EQUAL(r2, LV_SVG_FILL_NONZERO); + lv_svg_node_delete(svg_node_root); + + const char * svg_sf4 = \ + ""; + svg_node_root = lv_svg_load_data(svg_sf4, lv_strlen(svg_sf4)); + svg_node = LV_SVG_NODE_CHILD(svg_node_root, 0); + uint32_t c3 = (LV_ARRAY_GET(&svg_node->attrs, 0, lv_svg_attr_t))->value.ival; + TEST_ASSERT_EQUAL(c3, LV_SVG_LINE_CAP_SQUARE); + uint32_t c4 = (LV_ARRAY_GET(&svg_node->attrs, 1, lv_svg_attr_t))->value.ival; + TEST_ASSERT_EQUAL(c4, LV_SVG_LINE_JOIN_ROUND); + lv_svg_node_delete(svg_node_root); + + const char * svg_sf5 = \ + ""; + svg_node_root = lv_svg_load_data(svg_sf5, lv_strlen(svg_sf5)); + svg_node = LV_SVG_NODE_CHILD(svg_node_root, 0); + uint32_t c5 = (LV_ARRAY_GET(&svg_node->attrs, 0, lv_svg_attr_t))->value.ival; + TEST_ASSERT_EQUAL(c5, LV_SVG_LINE_CAP_BUTT); + uint32_t c6 = (LV_ARRAY_GET(&svg_node->attrs, 1, lv_svg_attr_t))->value.ival; + TEST_ASSERT_EQUAL(c6, LV_SVG_LINE_JOIN_MITER); + lv_svg_node_delete(svg_node_root); + + const char * svg_sf6 = \ + ""; + svg_node_root = lv_svg_load_data(svg_sf6, lv_strlen(svg_sf6)); + svg_node = LV_SVG_NODE_CHILD(svg_node_root, 0); + lv_svg_attr_values_list_t * list = (lv_svg_attr_values_list_t *)(LV_ARRAY_GET(&svg_node->attrs, 0, + lv_svg_attr_t))->value.val; + TEST_ASSERT_EQUAL(list->length, 5); + float * arr = (float *)(&list->data); + float ret[5] = {1.0f, 2.0f, 3.0f, 2.5f, 3.0f}; + TEST_ASSERT_EQUAL_FLOAT_ARRAY(ret, arr, 5); + + float c8 = (LV_ARRAY_GET(&svg_node->attrs, 1, lv_svg_attr_t))->value.fval; + TEST_ASSERT_EQUAL_FLOAT(c8, 1.2f); + float c9 = (LV_ARRAY_GET(&svg_node->attrs, 2, lv_svg_attr_t))->value.fval; + TEST_ASSERT_EQUAL_FLOAT(c9, 1.0f); + lv_svg_node_delete(svg_node_root); +} + +void testTextAttrs(void) +{ + const char * svg_sf0 = \ + "hello world!"; + lv_svg_node_t * svg_node_root = lv_svg_load_data(svg_sf0, lv_strlen(svg_sf0)); + lv_svg_node_t * svg_node = LV_SVG_NODE_CHILD(svg_node_root, 0); + + float font_size = (LV_ARRAY_GET(&svg_node->attrs, 0, lv_svg_attr_t))->value.fval; + TEST_ASSERT_EQUAL_FLOAT(font_size, 16.0f); + const char * font_family = (LV_ARRAY_GET(&svg_node->attrs, 1, lv_svg_attr_t))->value.sval; + TEST_ASSERT_EQUAL_STRING(font_family, "arial"); + lv_svg_node_t * svg_node1 = LV_SVG_NODE_CHILD(svg_node, 0); + const char * content = svg_node1->xml_id; + TEST_ASSERT_EQUAL_STRING(content, "hello world!"); + lv_svg_node_delete(svg_node_root); + + const char * svg_sf1 = \ + "hellomy\nworld!"; + svg_node_root = lv_svg_load_data(svg_sf1, lv_strlen(svg_sf1)); + svg_node = LV_SVG_NODE_CHILD(svg_node_root, 0); + + font_size = (LV_ARRAY_GET(&svg_node->attrs, 0, lv_svg_attr_t))->value.fval; + TEST_ASSERT_EQUAL_FLOAT(font_size, 256.0f); + const char * font_style = (LV_ARRAY_GET(&svg_node->attrs, 1, lv_svg_attr_t))->value.sval; + TEST_ASSERT_EQUAL_STRING(font_style, "italic "); + svg_node1 = LV_SVG_NODE_CHILD(svg_node, 0); + const char * content1 = svg_node1->xml_id; + TEST_ASSERT_EQUAL_STRING(content1, "hello"); + svg_node1 = LV_SVG_NODE_CHILD(svg_node, 2); + const char * content2 = svg_node1->xml_id; + TEST_ASSERT_EQUAL_STRING(content2, "world!"); + svg_node1 = LV_SVG_NODE_CHILD(svg_node, 1); + lv_svg_node_t * svg_node2 = LV_SVG_NODE_CHILD(svg_node1, 0); + const char * content3 = svg_node2->xml_id; + TEST_ASSERT_EQUAL_STRING(content3, "my"); + lv_svg_node_delete(svg_node_root); +} + +void testGradient(void) +{ + const char * svg_gt1 = \ + "" + "" + "" + ""; + lv_svg_node_t * svg_node_root = lv_svg_load_data(svg_gt1, lv_strlen(svg_gt1)); + lv_svg_node_t * svg_node = LV_SVG_NODE_CHILD(svg_node_root, 0); + TEST_ASSERT_EQUAL_STRING(svg_node->xml_id, "gt1"); + lv_svg_node_t * svg_node1 = LV_SVG_NODE_CHILD(svg_node, 1); + uint32_t c1 = (LV_ARRAY_GET(&svg_node1->attrs, 0, lv_svg_attr_t))->value.uval; + TEST_ASSERT_EQUAL(c1, 0); + float o1 = (LV_ARRAY_GET(&svg_node1->attrs, 1, lv_svg_attr_t))->value.fval; + TEST_ASSERT_EQUAL_FLOAT(o1, 0.5f); + float o2 = (LV_ARRAY_GET(&svg_node1->attrs, 2, lv_svg_attr_t))->value.fval; + TEST_ASSERT_EQUAL_FLOAT(o2, 1.0f); + lv_svg_node_delete(svg_node_root); + + const char * svg_gt2 = \ + "" + "" + "" + "" + ""; + svg_node_root = lv_svg_load_data(svg_gt2, lv_strlen(svg_gt2)); + svg_node = LV_SVG_NODE_CHILD(svg_node_root, 0); //defs + svg_node1 = LV_SVG_NODE_CHILD(svg_node, 0); + TEST_ASSERT_EQUAL_STRING(svg_node1->xml_id, "gt2"); + uint32_t g = (LV_ARRAY_GET(&svg_node1->attrs, 0, lv_svg_attr_t))->value.ival; + TEST_ASSERT_EQUAL(g, LV_SVG_GRADIENT_UNITS_USER_SPACE); + float cx = (LV_ARRAY_GET(&svg_node1->attrs, 1, lv_svg_attr_t))->value.fval; + TEST_ASSERT_EQUAL_FLOAT(cx, 400.0f); + float cy = (LV_ARRAY_GET(&svg_node1->attrs, 2, lv_svg_attr_t))->value.fval; + TEST_ASSERT_EQUAL_FLOAT(cy, 200.0f); + lv_svg_node_delete(svg_node_root); +} + +void testBadCase(void) +{ + const char * svg_b1 = \ + ""; + lv_svg_node_t * svg = lv_svg_load_data(svg_b1, lv_strlen(svg_b1)); + TEST_ASSERT_EQUAL(NULL, svg); + lv_svg_node_delete(svg); + + const char * svg_b2 = \ + ""; + svg = lv_svg_load_data(svg_b2, lv_strlen(svg_b2)); + TEST_ASSERT_EQUAL(NULL, svg); + lv_svg_node_delete(svg); + + const char * svg_b3 = \ + ""; + svg = lv_svg_load_data(svg_b3, lv_strlen(svg_b3)); + TEST_ASSERT_EQUAL(NULL, svg); + lv_svg_node_delete(svg); + + const char * svg_b4 = \ + ""; + svg = lv_svg_load_data(svg_b4, lv_strlen(svg_b4)); + lv_svg_node_t * svg_node = LV_SVG_NODE_CHILD(svg, 0); + TEST_ASSERT_EQUAL(0, LV_ARRAY_GET(&svg_node->attrs, 0, lv_svg_attr_t)->class_type); + lv_svg_node_delete(svg); + + const char * svg_b5 = \ + ""; + svg = lv_svg_load_data(svg_b5, lv_strlen(svg_b5)); + svg_node = LV_SVG_NODE_CHILD(svg, 0); + lv_svg_matrix_t * matrix = (lv_svg_matrix_t *)(LV_ARRAY_GET(&svg_node->attrs, 0, lv_svg_attr_t))->value.val; + + TEST_ASSERT_EQUAL_FLOAT(matrix->m[0][0], 1.0f); + TEST_ASSERT_EQUAL_FLOAT(matrix->m[1][1], 1.0f); + TEST_ASSERT_EQUAL_FLOAT(matrix->m[2][2], 1.0f); + lv_svg_node_delete(svg); + + const char * svg_b6 = \ + "<123><123>"; + svg = lv_svg_load_data(svg_b6, lv_strlen(svg_b6)); + TEST_ASSERT_EQUAL(NULL, svg); + lv_svg_node_delete(svg); + + const char * svg_b7 = \ + " bad case "; + svg = lv_svg_load_data(svg_b7, lv_strlen(svg_b7)); + TEST_ASSERT_EQUAL(NULL, svg); + lv_svg_node_delete(svg); + + const char * svg_b8 = \ + ""; + svg = lv_svg_load_data(svg_b8, lv_strlen(svg_b8)); + svg_node = LV_SVG_NODE_CHILD(svg, 0); + TEST_ASSERT_EQUAL(((lv_svg_attr_values_list_t *)(LV_ARRAY_GET(&svg_node->attrs, 0, lv_svg_attr_t))->value.val)->length, + 3); + uint32_t seg_size = sizeof(uint32_t) + sizeof(lv_svg_point_t); + lv_svg_attr_values_list_t * list = (lv_svg_attr_values_list_t *)(LV_ARRAY_GET(&svg_node->attrs, 0, + lv_svg_attr_t))->value.val; + + lv_svg_attr_path_value_t * path = (lv_svg_attr_path_value_t *)(&list->data); + TEST_ASSERT_EQUAL(path->cmd, LV_SVG_PATH_CMD_MOVE_TO); + + TEST_ASSERT_EQUAL_FLOAT(((lv_svg_point_t *)(&path->data))->x, 100.0f); + TEST_ASSERT_EQUAL_FLOAT(((lv_svg_point_t *)(&path->data))->y, 150.0f); + + path = (lv_svg_attr_path_value_t *)((uint8_t *)&list->data + seg_size); + TEST_ASSERT_EQUAL(path->cmd, LV_SVG_PATH_CMD_LINE_TO); + + TEST_ASSERT_EQUAL_FLOAT(((lv_svg_point_t *)(&path->data))->x, 180.0f); + TEST_ASSERT_EQUAL_FLOAT(((lv_svg_point_t *)(&path->data))->y, 150.0f); + lv_svg_node_delete(svg); + + const char * svg_b9 = \ + ""; + svg = lv_svg_load_data(svg_b9, lv_strlen(svg_b9)); + svg_node = LV_SVG_NODE_CHILD(svg, 0); + TEST_ASSERT_EQUAL(((lv_svg_attr_values_list_t *)(LV_ARRAY_GET(&svg_node->attrs, 0, lv_svg_attr_t))->value.val)->length, + 1); + seg_size = sizeof(uint32_t) + sizeof(lv_svg_point_t); + list = (lv_svg_attr_values_list_t *)(LV_ARRAY_GET(&svg_node->attrs, 0, lv_svg_attr_t))->value.val; + + path = (lv_svg_attr_path_value_t *)(&list->data); + TEST_ASSERT_EQUAL(path->cmd, LV_SVG_PATH_CMD_MOVE_TO); + + TEST_ASSERT_EQUAL_FLOAT(((lv_svg_point_t *)(&path->data))->x, 100.0f); + TEST_ASSERT_EQUAL_FLOAT(((lv_svg_point_t *)(&path->data))->y, 200.0f); + lv_svg_node_delete(svg); + + const char * svg_b10 = \ + "bad case"; + svg = lv_svg_load_data(svg_b10, lv_strlen(svg_b10)); + TEST_ASSERT_EQUAL(NULL, svg); + lv_svg_node_delete(svg); + + const char * svg_b11 = \ + ""; + svg = lv_svg_load_data(svg_b11, lv_strlen(svg_b11)); + svg_node = LV_SVG_NODE_CHILD(svg, 0); + TEST_ASSERT_EQUAL(lv_array_size(&svg_node->attrs), 0); + TEST_ASSERT_EQUAL(LV_TREE_NODE(svg_node)->child_cnt, 0); + lv_svg_node_delete(svg); + + const char * svg_b12 = \ + ""; + svg = lv_svg_load_data(svg_b12, lv_strlen(svg_b12)); + svg_node = LV_SVG_NODE_CHILD(svg, 0); + TEST_ASSERT_EQUAL(lv_array_size(&svg_node->attrs), 0); + TEST_ASSERT_EQUAL(LV_TREE_NODE(svg_node)->child_cnt, 0); + lv_svg_node_delete(svg); + + const char * svg_b13 = \ + ""; + svg = lv_svg_load_data(svg_b13, lv_strlen(svg_b13)); + TEST_ASSERT_NOT_EQUAL(NULL, svg); + lv_svg_node_delete(svg); + + const char * svg_b14 = \ + ""; + svg = lv_svg_load_data(svg_b14, lv_strlen(svg_b14)); + TEST_ASSERT_NOT_EQUAL(NULL, svg); + lv_svg_node_delete(svg); + + const char * svg_b15 = \ + "" + "" + ""; + svg = lv_svg_load_data(svg_b15, lv_strlen(svg_b15)); + svg_node = LV_SVG_NODE_CHILD(svg, 0); + + TEST_ASSERT_EQUAL(((lv_svg_attr_values_list_t *)(LV_ARRAY_GET(&svg_node->attrs, 0, lv_svg_attr_t))->value.val)->length, + 5); + seg_size = sizeof(uint32_t) + sizeof(lv_svg_point_t); + list = (lv_svg_attr_values_list_t *)(LV_ARRAY_GET(&svg_node->attrs, 0, lv_svg_attr_t))->value.val; + + path = (lv_svg_attr_path_value_t *)(&list->data); + TEST_ASSERT_EQUAL(path->cmd, LV_SVG_PATH_CMD_MOVE_TO); + + TEST_ASSERT_EQUAL_FLOAT(((lv_svg_point_t *)(&path->data))->x, -122.3f); + TEST_ASSERT_EQUAL_FLOAT(((lv_svg_point_t *)(&path->data))->y, 84.285f); + + lv_svg_node_delete(svg); + + const char * svg_b16 = \ + "" + "" + "" + ""; + svg = lv_svg_load_data(svg_b16, lv_strlen(svg_b16)); + svg_node = LV_SVG_NODE_CHILD(svg, 0); + matrix = (lv_svg_matrix_t *)(LV_ARRAY_GET(&svg_node->attrs, 0, lv_svg_attr_t))->value.val; + + TEST_ASSERT_EQUAL_FLOAT(matrix->m[0][0], 1.7656463f); + TEST_ASSERT_EQUAL_FLOAT(matrix->m[1][0], 0.0f); + TEST_ASSERT_EQUAL_FLOAT(matrix->m[0][1], 0.0f); + TEST_ASSERT_EQUAL_FLOAT(matrix->m[1][1], 1.7656463f); + TEST_ASSERT_EQUAL_FLOAT(matrix->m[0][2], 324.90716f); + TEST_ASSERT_EQUAL_FLOAT(matrix->m[1][2], 255.00942f); + + lv_svg_node_delete(svg); +} + +#endif diff --git a/tests/src/test_cases/test_svg_anim.c b/tests/src/test_cases/test_svg_anim.c new file mode 100644 index 0000000000..305b00d497 --- /dev/null +++ b/tests/src/test_cases/test_svg_anim.c @@ -0,0 +1,267 @@ +#if LV_BUILD_TEST +#include "../lvgl.h" + +#include "unity/unity.h" + +#define LV_ARRAY_GET(array, index, type) ((type*)lv_array_at((array), (index))) + +void setUp(void) +{ +} + +void tearDown(void) +{ +} + +void testAnimate(void) +{ + const char * svg_anim0 = \ + "" + "" + ""; + + lv_svg_node_t * svg_node_root = lv_svg_load_data(svg_anim0, lv_strlen(svg_anim0)); + lv_svg_node_t * svg_node = LV_SVG_NODE_CHILD(svg_node_root, 0); + lv_svg_node_t * anim_node = LV_SVG_NODE_CHILD(svg_node, 0); + TEST_ASSERT_NOT_EQUAL(NULL, anim_node); + TEST_ASSERT_EQUAL(LV_SVG_TAG_ANIMATE, anim_node->type); + lv_svg_attr_type_t at = (LV_ARRAY_GET(&anim_node->attrs, 0, lv_svg_attr_t))->value.ival; + TEST_ASSERT_EQUAL(LV_SVG_ATTR_X, at); + float dur = (LV_ARRAY_GET(&anim_node->attrs, 1, lv_svg_attr_t))->value.fval; + TEST_ASSERT_EQUAL_FLOAT(dur, 9000.0f); + + int ft = (LV_ARRAY_GET(&anim_node->attrs, 2, lv_svg_attr_t))->value.ival; + TEST_ASSERT_EQUAL(ft, LV_SVG_ANIM_FREEZE); + + float fr = (LV_ARRAY_GET(&anim_node->attrs, 3, lv_svg_attr_t))->value.fval; + TEST_ASSERT_EQUAL_FLOAT(fr, 300.0f); + + float to = (LV_ARRAY_GET(&anim_node->attrs, 4, lv_svg_attr_t))->value.fval; + TEST_ASSERT_EQUAL_FLOAT(to, 0.0f); + + lv_svg_node_delete(svg_node_root); +} + +void testSet(void) +{ + const char * svg_anim0 = \ + "" + "" + ""; + + lv_svg_node_t * svg_node_root = lv_svg_load_data(svg_anim0, lv_strlen(svg_anim0)); + lv_svg_node_t * svg_node = LV_SVG_NODE_CHILD(svg_node_root, 0); + lv_svg_node_t * anim_node = LV_SVG_NODE_CHILD(svg_node, 0); + TEST_ASSERT_NOT_EQUAL(NULL, anim_node); + TEST_ASSERT_EQUAL(LV_SVG_TAG_SET, anim_node->type); + lv_svg_attr_type_t at = (LV_ARRAY_GET(&anim_node->attrs, 0, lv_svg_attr_t))->value.ival; + TEST_ASSERT_EQUAL(LV_SVG_ATTR_X, at); + float to = (LV_ARRAY_GET(&anim_node->attrs, 1, lv_svg_attr_t))->value.fval; + TEST_ASSERT_EQUAL_FLOAT(to, 500.0f); + + lv_svg_node_delete(svg_node_root); +} + +void testAnimateMotion(void) +{ + const char * svg_anim0 = \ + "" + "" + "" + "" + ""; + + lv_svg_node_t * svg_node_root = lv_svg_load_data(svg_anim0, lv_strlen(svg_anim0)); + lv_svg_node_t * path_node = LV_SVG_NODE_CHILD(svg_node_root, 0); + TEST_ASSERT_NOT_EQUAL(NULL, path_node); + lv_svg_node_t * anim_node = LV_SVG_NODE_CHILD(svg_node_root, 1); + TEST_ASSERT_NOT_EQUAL(NULL, anim_node); + TEST_ASSERT_EQUAL(LV_SVG_TAG_ANIMATE_MOTION, anim_node->type); + + lv_svg_node_t * mpath_node = LV_SVG_NODE_CHILD(anim_node, 0); + TEST_ASSERT_NOT_EQUAL(NULL, mpath_node); + TEST_ASSERT_EQUAL(LV_SVG_TAG_MPATH, mpath_node->type); + + const char * xlink = (LV_ARRAY_GET(&mpath_node->attrs, 0, lv_svg_attr_t))->value.sval; + TEST_ASSERT_EQUAL_STRING(xlink, path_node->xml_id); + + uint32_t rp = (LV_ARRAY_GET(&anim_node->attrs, 1, lv_svg_attr_t))->value.uval; + TEST_ASSERT_EQUAL(rp, 0); + + float rt = (LV_ARRAY_GET(&anim_node->attrs, 2, lv_svg_attr_t))->value.fval; + TEST_ASSERT_EQUAL_FLOAT(rt, 0.0f); + lv_svg_node_delete(svg_node_root); + + const char * svg_anim1 = \ + "" + "" + "" + ""; + + svg_node_root = lv_svg_load_data(svg_anim1, lv_strlen(svg_anim1)); + path_node = LV_SVG_NODE_CHILD(svg_node_root, 0); + anim_node = LV_SVG_NODE_CHILD(path_node, 0); + TEST_ASSERT_NOT_EQUAL(NULL, anim_node); + TEST_ASSERT_EQUAL(LV_SVG_TAG_ANIMATE_MOTION, anim_node->type); + + lv_svg_attr_values_list_t * lb = (LV_ARRAY_GET(&anim_node->attrs, 0, lv_svg_attr_t))->value.val; + TEST_ASSERT_EQUAL(lb->length, 1); + float * fb = (float *)(&lb->data); + TEST_ASSERT_EQUAL_FLOAT(*fb, 500.0f); + + float dr = (LV_ARRAY_GET(&anim_node->attrs, 1, lv_svg_attr_t))->value.fval; + TEST_ASSERT_EQUAL(dr, 3100.0f); + + int cm = (LV_ARRAY_GET(&anim_node->attrs, 2, lv_svg_attr_t))->value.ival; + TEST_ASSERT_EQUAL(cm, LV_SVG_ANIM_CALC_MODE_LINEAR); + + lv_svg_attr_values_list_t * l = (LV_ARRAY_GET(&anim_node->attrs, 3, lv_svg_attr_t))->value.val; + TEST_ASSERT_EQUAL(l->length, 3); + + float * pt = (float *)(&l->data); + TEST_ASSERT_EQUAL_FLOAT(pt[0], 0.5f); + TEST_ASSERT_EQUAL_FLOAT(pt[1], 0.8f); + TEST_ASSERT_EQUAL_FLOAT(pt[2], 1.0f); + + lv_svg_attr_values_list_t * lp = (LV_ARRAY_GET(&anim_node->attrs, 4, lv_svg_attr_t))->value.val; + TEST_ASSERT_EQUAL(lp->length, 2); + lv_svg_node_delete(svg_node_root); + + const char * svg_anim2 = \ + "" + "" + "" + ""; + + svg_node_root = lv_svg_load_data(svg_anim2, lv_strlen(svg_anim2)); + path_node = LV_SVG_NODE_CHILD(svg_node_root, 0); + anim_node = LV_SVG_NODE_CHILD(path_node, 0); + TEST_ASSERT_NOT_EQUAL(NULL, anim_node); + TEST_ASSERT_EQUAL(LV_SVG_TAG_ANIMATE_MOTION, anim_node->type); + + lb = (LV_ARRAY_GET(&anim_node->attrs, 0, lv_svg_attr_t))->value.val; + TEST_ASSERT_EQUAL(lb->length, 2); + fb = (float *)(&lb->data); + TEST_ASSERT_EQUAL_FLOAT(fb[0], 5000.0f); + TEST_ASSERT_EQUAL_FLOAT(fb[1], 2000.0f); + + lb = (LV_ARRAY_GET(&anim_node->attrs, 1, lv_svg_attr_t))->value.val; + TEST_ASSERT_EQUAL(lb->length, 2); + fb = (float *)(&lb->data); + TEST_ASSERT_EQUAL_FLOAT(fb[0], 8000.0f); + TEST_ASSERT_EQUAL_FLOAT(fb[1], 10000.0f); + + lb = (LV_ARRAY_GET(&anim_node->attrs, 2, lv_svg_attr_t))->value.val; + TEST_ASSERT_EQUAL(lb->length, 2); + lv_svg_point_t * ps = (lv_svg_point_t *)(&lb->data); + TEST_ASSERT_EQUAL_FLOAT(ps[0].x, 100.0f); + TEST_ASSERT_EQUAL_FLOAT(ps[0].y, 50.0f); + TEST_ASSERT_EQUAL_FLOAT(ps[1].x, 200.0f); + TEST_ASSERT_EQUAL_FLOAT(ps[1].y, 200.0f); + + lb = (LV_ARRAY_GET(&anim_node->attrs, 3, lv_svg_attr_t))->value.val; + TEST_ASSERT_EQUAL(lb->length, 2); + fb = (float *)(&lb->data); + TEST_ASSERT_EQUAL_FLOAT(fb[0], 100.0f); + TEST_ASSERT_EQUAL_FLOAT(fb[1], 200.0f); + + l = (LV_ARRAY_GET(&anim_node->attrs, 4, lv_svg_attr_t))->value.val; + TEST_ASSERT_EQUAL(l->length, 4); + + ps = (lv_svg_point_t *)(&l->data); + TEST_ASSERT_EQUAL_FLOAT(ps[0].x, 0.0f); + TEST_ASSERT_EQUAL_FLOAT(ps[0].y, 0.0f); + TEST_ASSERT_EQUAL_FLOAT(ps[1].x, 1.5f); + TEST_ASSERT_EQUAL_FLOAT(ps[1].y, 1.0f); + TEST_ASSERT_EQUAL_FLOAT(ps[2].x, 0.5f); + TEST_ASSERT_EQUAL_FLOAT(ps[2].y, 0.5f); + TEST_ASSERT_EQUAL_FLOAT(ps[3].x, 2.0f); + TEST_ASSERT_EQUAL_FLOAT(ps[3].y, 1.5f); + + lv_svg_node_delete(svg_node_root); +} + +void testAnimateTransform(void) +{ + const char * svg_anim0 = \ + "" + "" + "" + ""; + + lv_svg_node_t * svg_node_root = lv_svg_load_data(svg_anim0, lv_strlen(svg_anim0)); + lv_svg_node_t * path_node = LV_SVG_NODE_CHILD(svg_node_root, 0); + TEST_ASSERT_NOT_EQUAL(NULL, path_node); + lv_svg_node_t * anim_node1 = LV_SVG_NODE_CHILD(path_node, 0); + TEST_ASSERT_NOT_EQUAL(NULL, anim_node1); + TEST_ASSERT_EQUAL(LV_SVG_TAG_ANIMATE_TRANSFORM, anim_node1->type); + lv_svg_node_t * anim_node2 = LV_SVG_NODE_CHILD(path_node, 1); + TEST_ASSERT_NOT_EQUAL(NULL, anim_node2); + TEST_ASSERT_EQUAL(LV_SVG_TAG_ANIMATE_TRANSFORM, anim_node2->type); + + lv_svg_attr_type_t at = (LV_ARRAY_GET(&anim_node1->attrs, 0, lv_svg_attr_t))->value.ival; + TEST_ASSERT_EQUAL(LV_SVG_ATTR_TRANSFORM, at); + + lv_svg_transform_type_t tt = (LV_ARRAY_GET(&anim_node1->attrs, 1, lv_svg_attr_t))->value.ival; + TEST_ASSERT_EQUAL(LV_SVG_TRANSFORM_TYPE_ROTATE, tt); + + lv_svg_attr_values_list_t * l = (LV_ARRAY_GET(&anim_node1->attrs, 2, lv_svg_attr_t))->value.val; + TEST_ASSERT_EQUAL(l->length, 1); + float * pt = (float *)(&l->data); + TEST_ASSERT_EQUAL_FLOAT(pt[0], 0.0f); + + l = (LV_ARRAY_GET(&anim_node2->attrs, 5, lv_svg_attr_t))->value.val; + TEST_ASSERT_EQUAL(l->length, 2); + lv_svg_attr_values_list_t * ll = (lv_svg_attr_values_list_t *)(&l->data); + TEST_ASSERT_EQUAL(ll->length, 1); + pt = (float *)(&ll->data); + TEST_ASSERT_EQUAL_FLOAT(pt[0], 0.5f); + + ll = (lv_svg_attr_values_list_t *)((uint8_t *)(&l->data) + sizeof(uint32_t) + sizeof(float) * 4); + TEST_ASSERT_EQUAL(ll->length, 2); + pt = (float *)(&ll->data); + TEST_ASSERT_EQUAL_FLOAT(pt[0], 0.2f); + TEST_ASSERT_EQUAL_FLOAT(pt[1], 0.2f); + + lv_svg_node_delete(svg_node_root); +} + +void testAnimateColor(void) +{ + const char * svg_anim0 = \ + "" + "" + ""; + + lv_svg_node_t * svg_node_root = lv_svg_load_data(svg_anim0, lv_strlen(svg_anim0)); + lv_svg_node_t * path_node = LV_SVG_NODE_CHILD(svg_node_root, 0); + TEST_ASSERT_NOT_EQUAL(NULL, path_node); + lv_svg_node_t * anim_node = LV_SVG_NODE_CHILD(path_node, 0); + TEST_ASSERT_NOT_EQUAL(NULL, anim_node); + TEST_ASSERT_EQUAL(LV_SVG_TAG_ANIMATE_COLOR, anim_node->type); + + lv_svg_attr_type_t at = (LV_ARRAY_GET(&anim_node->attrs, 0, lv_svg_attr_t))->value.ival; + TEST_ASSERT_EQUAL(LV_SVG_ATTR_FILL, at); + + uint32_t c = (LV_ARRAY_GET(&anim_node->attrs, 1, lv_svg_attr_t))->value.uval; + TEST_ASSERT_EQUAL(c, 0xFF0000); + + c = (LV_ARRAY_GET(&anim_node->attrs, 2, lv_svg_attr_t))->value.uval; + TEST_ASSERT_EQUAL(c, 0xDDDDFF); + + lv_svg_attr_values_list_t * l = (LV_ARRAY_GET(&anim_node->attrs, 9, lv_svg_attr_t))->value.val; + TEST_ASSERT_EQUAL(l->length, 2); + uint32_t * pc = (uint32_t *)(&l->data); + TEST_ASSERT_EQUAL(pc[0], 0x00FF00); + TEST_ASSERT_EQUAL(pc[1], 0x000000); + + lv_svg_node_delete(svg_node_root); +} +#endif diff --git a/tests/src/test_cases/test_tree.c b/tests/src/test_cases/test_tree.c new file mode 100644 index 0000000000..c6f61c8eb8 --- /dev/null +++ b/tests/src/test_cases/test_tree.c @@ -0,0 +1,213 @@ +#if LV_BUILD_TEST +#include "../lvgl.h" + +#include "unity/unity.h" + +#define MY_CLASS &lv_test_tree_class + +typedef struct { + lv_tree_node_t base; + int data1; + float data2; +} lv_test_node_t; + +static void lv_test_constructor(const lv_tree_class_t * class_p, lv_tree_node_t * node) +{ + LV_UNUSED(class_p); + lv_test_node_t * t = (lv_test_node_t *)node; + t->data1 = 100; + t->data2 = 100.0f; +} + +static void lv_test_destructor(const lv_tree_class_t * class_p, lv_tree_node_t * node) +{ + LV_UNUSED(class_p); + lv_test_node_t * t = (lv_test_node_t *)node; + t->data1 = 0; + t->data2 = 0.0f; +} + +const lv_tree_class_t lv_test_tree_class = { + .base_class = &lv_tree_node_class, + .instance_size = sizeof(lv_test_node_t), + .constructor_cb = lv_test_constructor, + .destructor_cb = lv_test_destructor, +}; + +static lv_test_node_t * lv_test_create(lv_test_node_t * parent) +{ + lv_tree_node_t * node = lv_tree_node_create(MY_CLASS, (lv_tree_node_t *)parent); + return (lv_test_node_t *)node; +} + +static void lv_test_delete(lv_test_node_t * node) +{ + lv_tree_node_delete((lv_tree_node_t *)node); +} + +static lv_test_node_t * test; + +void setUp(void) +{ + test = lv_test_create(NULL); +} + +void tearDown(void) +{ + lv_test_delete(test); +} + +void testCreateAndDelete(void) +{ + lv_test_node_t * node = lv_test_create(test); + TEST_ASSERT_EQUAL_UINT32(1, test->base.child_cnt); + TEST_ASSERT_EQUAL_UINT32(0, node->base.child_cnt); + TEST_ASSERT_EQUAL(test, node->base.parent); + TEST_ASSERT_EQUAL(NULL, test->base.parent); + + TEST_ASSERT_EQUAL_INT32(100, node->data1); + TEST_ASSERT_EQUAL_FLOAT(100.0f, node->data2); + + lv_test_delete(node); + TEST_ASSERT_EQUAL(NULL, test->base.children[0]); +} + +struct _result { + int32_t result[8]; + int32_t num; +}; + +static bool test_walk_cb(const lv_tree_node_t * node, void * data) +{ + lv_test_node_t * n = (lv_test_node_t *)node; + struct _result * ret = (struct _result *)data; + + ret->result[ret->num++] = n->data1; + return true; +} + +void testBuildTree(void) +{ + lv_test_node_t * node = lv_test_create(test); + node->data1 = 0; + + for(int i = 0; i < 16; i++) { + lv_test_node_t * snode = lv_test_create(node); + snode->data1 = i; + TEST_ASSERT_NOT_EQUAL(NULL, snode); + } + TEST_ASSERT_EQUAL(16, node->base.child_cnt); + lv_test_delete(node); +} + +void testWalkTree(void) +{ + lv_test_node_t * node = lv_test_create(test); + node->data1 = 1; + + lv_test_node_t * node1 = lv_test_create(node); + node1->data1 = 2; + lv_test_node_t * node2 = lv_test_create(node); + node2->data1 = 3; + + lv_test_node_t * node11 = lv_test_create(node1); + node11->data1 = 4; + + lv_test_node_t * node12 = lv_test_create(node1); + node12->data1 = 5; + + lv_test_node_t * node21 = lv_test_create(node2); + node21->data1 = 6; + + lv_test_node_t * node111 = lv_test_create(node11); + node111->data1 = 7; + + struct _result result = {.num = 0}; + + lv_tree_walk((lv_tree_node_t *)node, LV_TREE_WALK_POST_ORDER, test_walk_cb, NULL, NULL, &result); + + int32_t ret[8] = {7, 4, 5, 2, 6, 3, 1}; + + TEST_ASSERT_EQUAL_INT32_ARRAY(ret, result.result, 8); + + result.num = 0; + lv_memset(&result, 0, sizeof(struct _result)); + lv_tree_walk((lv_tree_node_t *)node, LV_TREE_WALK_PRE_ORDER, test_walk_cb, NULL, NULL, &result); + + int32_t ret2[8] = {1, 2, 4, 7, 5, 3, 6}; + + TEST_ASSERT_EQUAL_INT32_ARRAY(ret2, result.result, 8); + + lv_test_delete(node111); + + result.num = 0; + lv_memset(&result, 0, sizeof(struct _result)); + lv_tree_walk((lv_tree_node_t *)node, LV_TREE_WALK_PRE_ORDER, test_walk_cb, NULL, NULL, &result); + + int32_t ret3[8] = {1, 2, 4, 5, 3, 6}; + + TEST_ASSERT_EQUAL_INT32_ARRAY(ret3, result.result, 8); + + lv_test_delete(node); +} + +static bool test_search_cb(const lv_tree_node_t * node, void * data) +{ + lv_test_node_t * n = (lv_test_node_t *)node; + struct _result * ret = (struct _result *)data; + + if(n->data1 == 3) { + return false; + } + + ret->result[ret->num++] = n->data1; + return true; +} + +static bool test_before_search_cb(const lv_tree_node_t * node, void * data) +{ + LV_UNUSED(node); + LV_UNUSED(data); + return true; +} + +static void test_after_search_cb(const lv_tree_node_t * node, void * data) +{ + LV_UNUSED(node); + LV_UNUSED(data); +} + +void testTreeSearch(void) +{ + lv_test_node_t * node = lv_test_create(test); + node->data1 = 1; + + lv_test_node_t * node1 = lv_test_create(node); + node1->data1 = 2; + lv_test_node_t * node2 = lv_test_create(node); + node2->data1 = 3; + + lv_test_node_t * node11 = lv_test_create(node1); + node11->data1 = 4; + + struct _result result = {.num = 0}; + + lv_tree_walk((lv_tree_node_t *)node, LV_TREE_WALK_PRE_ORDER, test_search_cb, test_before_search_cb, + test_after_search_cb, &result); + + int32_t ret2[4] = {1, 2}; + + TEST_ASSERT_EQUAL_INT32_ARRAY(ret2, result.result, 2); + + result.num = 0; + lv_memset(&result, 0, sizeof(struct _result)); + lv_tree_walk((lv_tree_node_t *)node, LV_TREE_WALK_POST_ORDER, test_search_cb, test_before_search_cb, + test_after_search_cb, &result); + + int32_t ret3[4] = {4, 2}; + + TEST_ASSERT_EQUAL_INT32_ARRAY(ret3, result.result, 2); + lv_test_delete(node); +} + +#endif