diff --git a/3rd/svgtiny/src/svgtiny_arc.h b/3rd/svgtiny/include/svgtiny_arc.h similarity index 95% rename from 3rd/svgtiny/src/svgtiny_arc.h rename to 3rd/svgtiny/include/svgtiny_arc.h index acbc7cf65..6eb05fd61 100644 --- a/3rd/svgtiny/src/svgtiny_arc.h +++ b/3rd/svgtiny/include/svgtiny_arc.h @@ -99,7 +99,7 @@ static ret_t arc_info_init(arc_info_t* info, pointf_t from, pointf_t to, pointf_ return RET_OK; } -bool_t arc_info_next(arc_info_t* info, pointf_t* cp1, pointf_t* cp2, pointf_t* to) { +static bool_t arc_info_next(arc_info_t* info, pointf_t* cp1, pointf_t* cp2, pointf_t* to) { if (info->seg_index == info->num_segs) { return FALSE; } diff --git a/docs/changes.md b/docs/changes.md index 24b1da403..c2131405a 100644 --- a/docs/changes.md +++ b/docs/changes.md @@ -1,5 +1,8 @@ # 最新动态 +2024/03/06 + * 完善svg支持awtk-widget-shape(感谢陈聪提供补丁) + 2024/03/04 * 增加zip_file。 * 修改edit控件支持只读下数据可以拷贝到剪切板上面(感谢智明提供补丁) diff --git a/src/svg/svg_path_parser.c b/src/svg/svg_path_parser.c index 380acf71c..1a00de73b 100644 --- a/src/svg/svg_path_parser.c +++ b/src/svg/svg_path_parser.c @@ -20,9 +20,294 @@ */ #include "tkc/utils.h" +#include "tkc/mem.h" #include "svg/svg_path.h" #include "svg/svg_path_parser.h" +#include "svgtiny/include/svgtiny_arc.h" -/* - * 由于svg改为用svgtiny解析,所以去掉svg/svg_path_parser.c|.h中的代码,但为了兼容保留文件。 -*/ +typedef struct _svg_path_parser_t { + char* path; + void* ctx; + tk_visit_t on_path; +} svg_path_parser_t; + +static ret_t svg_path_parser_parse(svg_path_parser_t* parser) { + char* s = parser->path; + float first_x = 0, first_y = 0; + float last_x = 0, last_y = 0; + float last_cubic_x = 0, last_cubic_y = 0; + float last_quad_x = 0, last_quad_y = 0; + + /* parse d and build path */ + s = parser->path; + while (*s) { + if (*s == ',') { + *s = ' '; + } + s++; + } + + s = parser->path; + while (*s) { + int n; + char command[2]; + float x, y, x1, y1, x2, y2, rx, ry, rotation, large_arc, sweep; + + /* moveto (M, m) (2 arguments) */ + if (sscanf(s, " %1[Mm] %f %f %n", command, &x, &y, &n) == 3) { + svg_path_move_t path; + + do { + if (*command == 'm') { + x += last_x; + y += last_y; + } + first_x = last_cubic_x = last_quad_x = last_x = x; + first_y = last_cubic_y = last_quad_y = last_y = y; + s += n; + + memset(&path, 0, sizeof(path)); + svg_path_move_init(&path, x, y); + parser->on_path(parser->ctx, (svg_path_t*)&path); + + } while (sscanf(s, "%f %f %n", &x, &y, &n) == 2); + + /* lineto (L, l) (2 arguments) */ + } else if (sscanf(s, " %1[Ll] %f %f %n", command, &x, &y, &n) == 3) { + svg_path_line_t path; + + do { + if (*command == 'l') { + x += last_x; + y += last_y; + } + last_cubic_x = last_quad_x = last_x = x; + last_cubic_y = last_quad_y = last_y = y; + s += n; + + memset(&path, 0, sizeof(path)); + svg_path_line_init(&path, x, y); + parser->on_path(parser->ctx, (svg_path_t*)&path); + + } while (sscanf(s, "%f %f %n", &x, &y, &n) == 2); + + /* closepath (Z, z) (no arguments) */ + } else if (sscanf(s, " %1[Zz] %n", command, &n) == 1) { + svg_path_t path; + + last_x = first_x; + last_y = first_y; + s += n; + + memset(&path, 0, sizeof(path)); + path.type = SVG_PATH_Z; + parser->on_path(parser->ctx, (svg_path_t*)&path); + + /* horizontal lineto (H, h) (1 argument) */ + } else if (sscanf(s, " %1[Hh] %f %n", command, &x, &n) == 2) { + svg_path_line_t path; + + do { + if (*command == 'h') { + x += last_x; + } + last_cubic_x = last_quad_x = last_x = x; + last_cubic_y = last_quad_y = last_y; + s += n; + + memset(&path, 0, sizeof(path)); + svg_path_line_init(&path, x, last_y); + parser->on_path(parser->ctx, (svg_path_t*)&path); + + } while (sscanf(s, "%f %n", &x, &n) == 1); + + /* vertical lineto (V, v) (1 argument) */ + } else if (sscanf(s, " %1[Vv] %f %n", command, &y, &n) == 2) { + svg_path_line_t path; + + do { + if (*command == 'v') { + y += last_y; + } + last_cubic_x = last_quad_x = last_x; + last_cubic_y = last_quad_y = last_y = y; + s += n; + + memset(&path, 0, sizeof(path)); + svg_path_line_init(&path, last_x, y); + parser->on_path(parser->ctx, (svg_path_t*)&path); + + } while (sscanf(s, "%f %n", &y, &n) == 1); + + /* curveto (C, c) (6 arguments) */ + } else if (sscanf(s, " %1[Cc] %f %f %f %f %f %f %n", command, &x1, &y1, &x2, &y2, &x, &y, &n) == + 7) { + svg_path_curve_to_t path; + + do { + if (*command == 'c') { + x1 += last_x; + y1 += last_y; + x2 += last_x; + y2 += last_y; + x += last_x; + y += last_y; + } + last_cubic_x = x2; + last_cubic_y = y2; + last_quad_x = last_x = x; + last_quad_y = last_y = y; + s += n; + + memset(&path, 0, sizeof(path)); + svg_path_curve_to_init(&path, x1, y1, x2, y2, x, y); + parser->on_path(parser->ctx, (svg_path_t*)&path); + + } while (sscanf(s, "%f %f %f %f %f %f %n", &x1, &y1, &x2, &y2, &x, &y, &n) == 6); + + /* shorthand/smooth curveto (S, s) (4 arguments) */ + } else if (sscanf(s, " %1[Ss] %f %f %f %f %n", command, &x2, &y2, &x, &y, &n) == 5) { + svg_path_curve_to_t path; + + do { + x1 = last_x + (last_x - last_cubic_x); + y1 = last_y + (last_y - last_cubic_y); + if (*command == 's') { + x2 += last_x; + y2 += last_y; + x += last_x; + y += last_y; + } + last_cubic_x = x2; + last_cubic_y = y2; + last_quad_x = last_x = x; + last_quad_y = last_y = y; + s += n; + + memset(&path, 0, sizeof(path)); + svg_path_curve_to_init(&path, x1, y1, x2, y2, x, y); + parser->on_path(parser->ctx, (svg_path_t*)&path); + + } while (sscanf(s, "%f %f %f %f %n", &x2, &y2, &x, &y, &n) == 4); + + /* quadratic Bezier curveto (Q, q) (4 arguments) */ + } else if (sscanf(s, " %1[Qq] %f %f %f %f %n", command, &x1, &y1, &x, &y, &n) == 5) { + svg_path_curve_to_t path; + float p[4]; + + do { + if (*command == 'q') { + x1 += last_x; + y1 += last_y; + x += last_x; + y += last_y; + } + last_quad_x = x1; + last_quad_y = y1; + p[0] = 1. / 3 * last_x + 2. / 3 * x1; + p[1] = 1. / 3 * last_y + 2. / 3 * y1; + p[2] = 2. / 3 * x1 + 1. / 3 * x; + p[3] = 2. / 3 * y1 + 1. / 3 * y; + last_cubic_x = last_x = x; + last_cubic_y = last_y = y; + s += n; + + memset(&path, 0, sizeof(path)); + svg_path_curve_to_init(&path, p[0], p[1], p[2], p[3], x, y); + parser->on_path(parser->ctx, (svg_path_t*)&path); + + } while (sscanf(s, "%f %f %f %f %n", &x1, &y1, &x, &y, &n) == 4); + + /* shorthand/smooth quadratic Bezier curveto (T, t) + (2 arguments) */ + } else if (sscanf(s, " %1[Tt] %f %f %n", command, &x, &y, &n) == 3) { + svg_path_curve_to_t path; + float p[4]; + + do { + x1 = last_x + (last_x - last_quad_x); + y1 = last_y + (last_y - last_quad_y); + last_quad_x = x1; + last_quad_y = y1; + if (*command == 't') { + x += last_x; + y += last_y; + } + p[0] = 1. / 3 * last_x + 2. / 3 * x1; + p[1] = 1. / 3 * last_y + 2. / 3 * y1; + p[2] = 2. / 3 * x1 + 1. / 3 * x; + p[3] = 2. / 3 * y1 + 1. / 3 * y; + last_cubic_x = last_x = x; + last_cubic_y = last_y = y; + s += n; + + memset(&path, 0, sizeof(path)); + svg_path_curve_to_init(&path, p[0], p[1], p[2], p[3], x, y); + parser->on_path(parser->ctx, (svg_path_t*)&path); + + } while (sscanf(s, "%f %f %n", &x, &y, &n) == 2); + + /* elliptical arc (A, a) (7 arguments) */ + } else if (sscanf(s, " %1[Aa] %f %f %f %f %f %f %f %n", command, &rx, &ry, &rotation, + &large_arc, &sweep, &x, &y, &n) == 8) { + svg_path_curve_to_t path; + + do { + arc_info_t info; + pointf_t cp1 = {0, 0}; + pointf_t cp2 = {0, 0}; + pointf_t end = {0, 0}; + pointf_t r = {rx, ry}; + pointf_t from = {last_x, last_y}; + pointf_t to = {x, y}; + if (*command == 'a') { + to.x += last_x; + to.y += last_y; + } + arc_info_init(&info, from, to, r, rotation, large_arc, sweep); + while (arc_info_next(&info, &cp1, &cp2, &end)) { + memset(&path, 0, sizeof(path)); + svg_path_curve_to_init(&path, cp1.x, cp1.y, cp2.x, cp2.y, end.x, end.y); + parser->on_path(parser->ctx, (svg_path_t*)&path); + } + + last_x = to.x; + last_y = to.y; + s += n; + } while (sscanf(s, "%f %f %f %f %f %f %f %n", &rx, &ry, &rotation, &large_arc, &sweep, &x, &y, + &n) == 7); + + } else { + log_error("parse failed at \"%s\"\n", s); + return RET_FAIL; + } + } + + return RET_OK; +} + +static ret_t svg_path_parser_init(svg_path_parser_t* parser, const char* path, void* ctx, + tk_visit_t on_path) { + memset(parser, 0x00, sizeof(*parser)); + + parser->ctx = ctx; + parser->path = (char*)path; + parser->on_path = on_path; + + return RET_OK; +} + +ret_t svg_path_parse(const char* path, void* ctx, tk_visit_t on_path) { + ret_t ret; + char* p = NULL; + svg_path_parser_t parser; + return_value_if_fail(path != NULL && on_path != NULL, RET_BAD_PARAMS); + + p = tk_strdup(path); + return_value_if_fail(p != NULL, RET_OOM); + + svg_path_parser_init(&parser, p, ctx, on_path); + ret = svg_path_parser_parse(&parser); + TKMEM_FREE(p); + return ret; +} diff --git a/src/svg/svg_path_parser.h b/src/svg/svg_path_parser.h index 6311e1042..d9f7517fe 100644 --- a/src/svg/svg_path_parser.h +++ b/src/svg/svg_path_parser.h @@ -24,8 +24,26 @@ #include "tkc/types_def.h" -/* - * 由于svg改为用svgtiny解析,所以去掉svg/svg_path_parser.c|.h中的代码,但为了兼容保留文件。 -*/ +BEGIN_C_DECLS + +/** + * @class svg_path_t + * @annotation ["fake"] + */ + +/** + * @method svg_path_parse + * + * 解析路径。 + * + * @param {const char*} path 路径数据。 + * @param {void*} ctx 回调函数上下文。 + * @param {tk_visit_t} on_path 路径处理回调函数。 + * + * @return {ret_t} 返回RET_OK表示成功,否则表示失败。 + */ +ret_t svg_path_parse(const char* path, void* ctx, tk_visit_t on_path); + +END_C_DECLS #endif /*TK_SVG_PATH_BUILDER_H*/ diff --git a/tests/svg_test.cc b/tests/svg_test.cc index 31aec44a9..8d7f68f3f 100644 --- a/tests/svg_test.cc +++ b/tests/svg_test.cc @@ -1,5 +1,6 @@ #include "svg/bsvg.h" #include "svg/bsvg_builder.h" +#include "svg/svg_path_parser.h" #include "tkc/utils.h" #include "gtest/gtest.h" #include "svg/bsvg_to_svg.h" @@ -84,6 +85,13 @@ static ret_t on_path(void* ctx, const void* data) { return RET_OK; } +static ret_t on_path_parse(void* ctx, const void* data) { + cmp_path(((const svg_path_t**)ctx)[s_on_path_count], (const svg_path_t*)data); + s_on_path_count++; + + return RET_OK; +} + static ret_t on_shape_null(void* ctx, const void* data) { (void)ctx; (void)data; @@ -135,3 +143,89 @@ TEST(SvgPath, curve) { svg_path_t* p = svg_path_curve_to_init(&path, 1, 2, 3, 4, 5, 6); test_one_path(p, " C1.0 2.0 3.0 4.0 5.0 6.0"); } + +static void test_one_path_parse(const char* str, svg_path_t** ctx, uint32_t c) { + s_on_path_count = 0; + svg_path_parse(str, ctx, on_path_parse); + ASSERT_EQ(s_on_path_count, c); +} + +TEST(SVGPathParser, move) { + svg_path_t* ctx[2]; + svg_path_move_t path[2]; + memset(&path, 0, sizeof(path)); + + ctx[0] = svg_path_move_init(&path[0], 409.6, 281.6); + ctx[1] = svg_path_move_init(&path[1], 409.6, 281.6); + test_one_path_parse("M409.6 281.6", (svg_path_t**)ctx, 1); + test_one_path_parse("M409.6 281.6 409.6 281.6", (svg_path_t**)ctx, 2); + + ctx[0] = svg_path_move_init(&path[0], 409.6, 281.6); + ctx[1] = svg_path_move_init(&path[1], 409.6 * 2, 281.6 * 2); + test_one_path_parse("m409.6 281.6", (svg_path_t**)ctx, 1); + test_one_path_parse("m409.6 281.6 409.6 281.6", (svg_path_t**)ctx, 2); +} + +TEST(SVGPathParser, line) { + svg_path_t* ctx[2]; + svg_path_line_t path[2] = {0}; + memset(&path, 0, sizeof(path)); + + ctx[0] = svg_path_line_init(&path[0], 128, -128.5); + ctx[1] = svg_path_line_init(&path[1], 128, -128.5); + test_one_path_parse("L128-128.5", (svg_path_t**)ctx, 1); + test_one_path_parse("L128-128.5 128-128.5", (svg_path_t**)ctx, 2); + + ctx[0] = svg_path_line_init(&path[0], 128, -128.5); + ctx[1] = svg_path_line_init(&path[1], 128 * 2, -128.5 * 2); + test_one_path_parse("l128-128.5", (svg_path_t**)ctx, 1); + test_one_path_parse("l128-128.5 128-128.5", (svg_path_t**)ctx, 2); +} + +TEST(SVGPathParser, hline) { + svg_path_t* ctx[2]; + svg_path_line_t path[2] = {0}; + memset(&path, 0, sizeof(path)); + + ctx[0] = svg_path_line_init(&path[0], 179.2, 0); + ctx[1] = svg_path_line_init(&path[1], 179.2, 0); + test_one_path_parse("H179.2", (svg_path_t**)ctx, 1); + test_one_path_parse("H179.2 179.2", (svg_path_t**)ctx, 2); + + ctx[0] = svg_path_line_init(&path[0], 179.2, 0); + ctx[1] = svg_path_line_init(&path[1], 179.2 * 2, 0); + test_one_path_parse("h179.2", (svg_path_t**)ctx, 1); + test_one_path_parse("h179.2 179.2", (svg_path_t**)ctx, 2); +} + +TEST(SVGPathParser, vline) { + svg_path_t* ctx[2]; + svg_path_line_t path[2]; + memset(&path, 0, sizeof(path)); + + ctx[0] = svg_path_line_init(&path[0], 0, -179.2); + ctx[1] = svg_path_line_init(&path[1], 0, -179.2); + test_one_path_parse("V-179.2", (svg_path_t**)ctx, 1); + test_one_path_parse("V-179.2-179.2", (svg_path_t**)ctx, 2); + + ctx[0] = svg_path_line_init(&path[0], 0, -179.2); + ctx[1] = svg_path_line_init(&path[1], 0, -179.2 * 2); + test_one_path_parse("v-179.2", (svg_path_t**)ctx, 1); + test_one_path_parse("v-179.2-179.2", (svg_path_t**)ctx, 2); +} + +TEST(SVGPathParser, curve) { + svg_path_t* ctx[2]; + svg_path_curve_to_t path[2]; + memset(&path, 0, sizeof(path)); + + ctx[0] = svg_path_curve_to_init(&path[0], 1, 2, 3, 4, 5, 6); + ctx[1] = svg_path_curve_to_init(&path[1], 1, 2, 3, 4, 5, 6); + test_one_path_parse("C1 2 3 4 5 6", (svg_path_t**)ctx, 1); + test_one_path_parse("C1 2 3 4 5 6 1 2 3 4 5 6 ", (svg_path_t**)ctx, 2); + + ctx[0] = svg_path_curve_to_init(&path[0], 1, 2, 3, 4, 5, 6); + ctx[1] = svg_path_curve_to_init(&path[1], 1 + 5, 2 + 6, 3 + 5, 4 + 6, 5 * 2, 6 * 2); + test_one_path_parse("c1 2 3 4 5 6", (svg_path_t**)ctx, 1); + test_one_path_parse("c1 2 3 4 5 6 1 2 3 4 5 6 ", (svg_path_t**)ctx, 2); +}