This is an automated email from the git hooks/post-receive script.
git pushed a commit to branch edje-vector-intergration
in repository efl.
View the commit online.
commit c64fc555840ab1d24a68df8562cd0d5ffbb98ab0
Author: [email protected] <[email protected]>
AuthorDate: Sat May 2 14:29:44 2026 -0600
edje: pre-parse SVG path strings to Efl_Gfx_Path arrays at compile time
SVG path 'd' attributes are now consumed by edje_cc and converted to
Efl_Gfx_Path_Command and double arrays at compile time, storing only
the binary data in .edj files. No SVG strings are carried on disk.
## Performance improvement
The runtime hot path no longer re-parses SVG path strings on every cache
rebuild (which occurs once per transition and once per class change).
Previously, materializer called efl_gfx_path_append_svg_path repeatedly;
now it calls efl_gfx_path_set with pre-baked arrays. This aligns with
Edje's philosophy of pushing cost to compile time, not runtime.
## Validation at compile time
Path syntax errors are now caught by edje_cc with a clear VEC003 diagnostic,
preventing broken themes from shipping. Arc commands (a/A/e/E) are explicitly
rejected with VEC003 because their interpolation requires the original SVG
string; themes must convert arcs to cubic Bezier curves using an authoring
tool before import.
## Simpler on-disk format
.edj files contain only command/point arrays, reducing file size and decoder
complexity. No ambiguity from locale-dependent float parsing or differing
SVG implementations.
## VEC004 topology check simplified
Path topology verification is now a single memcmp(commands_a, commands_b)
instead of re-parsing both SVG strings and extracting their command
sequences. Removed ~45 lines of parsing logic.
## Implementation details
- edje_private.h: Edje_Vg_Node.shape changes from 'const char *path' to
four fields: commands (Efl_Gfx_Path_Command*), points (double*),
commands_count, points_count.
- edje_data.c: Eet descriptor updated to use VAR_ARRAY for both fields.
Added static_assert to catch -fshort-enums builds that would corrupt
the on-disk enum size.
- edje_cc_handlers.c: New _svg_path_parse_to_arrays helper using a buffer
canvas + EFL shape + efl_gfx_path_append_svg_path, then memcpy before
object deletion. Arc detector added. Buffer canvas promoted to file scope
so _edje_cc_vg_cleanup can free it on exit.
- edje_cc_out.c: _paths_topology_compatible now takes Edje_Vg_Node pointers
and compares commands arrays directly via memcmp.
- edje_vg_tree.c: Duplication, override merging, and free-path logic
updated to malloc/memcpy/free arrays instead of stringshare. Test tree
updated to use pre-parsed shape data.
- edje_edit.c: New _edje_vg_path_to_svg_string helper reconstructs SVG
strings from arrays for edje_decc round-trip. Locale-safe: forces
LC_NUMERIC="C" internally.
## Verification
- ninja -C build: clean
- EFL_RUN_IN_TREE=1 CK_FORK=no ./build/src/tests/edje/edje_suite: 91/91 pass
- vector_states.edc example recompiles cleanly
- edje_decc round-trip verified (emitted SVG → recompile → identical arrays)
- Arc rejection verified with synthetic EDC (VEC003 abort as expected)
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
---
src/bin/edje/edje_cc_handlers.c | 203 +++++++++++++++++++++++++++++++++++++---
src/bin/edje/edje_cc_out.c | 54 +++--------
src/lib/edje/edje_data.c | 8 +-
src/lib/edje/edje_edit.c | 88 ++++++++++++++++-
src/lib/edje/edje_private.h | 5 +-
src/lib/edje/edje_vg_tree.c | 171 +++++++++++++++++++++++++++------
6 files changed, 444 insertions(+), 85 deletions(-)
diff --git a/src/bin/edje/edje_cc_handlers.c b/src/bin/edje/edje_cc_handlers.c
index bf6e64f7e2..1695060133 100644
--- a/src/bin/edje/edje_cc_handlers.c
+++ b/src/bin/edje/edje_cc_handlers.c
@@ -182,6 +182,12 @@ static Eina_Array *current_vg_node_stack = NULL;
static Eina_Hash *toplevel_vg_trees = NULL;
static Eina_List *all_vg_trees = NULL;
+/* Buffer canvas used by _svg_path_parse_to_arrays — file-scope so that
+ * _edje_cc_vg_cleanup can free it and avoid valgrind definite-loss reports. */
+static Ecore_Evas *_cc_ee = NULL;
+static Evas *_cc_evas = NULL;
+static Evas_Object *_cc_vg = NULL;
+
/* Task 2.8: maps Edje_Part_Description_Vector* (key, by pointer) to
* Edje_Vg_Tree* (value, borrowed). Populated by Mode A (vector.use:) and
* Mode B (inline vector{}) handlers; read by Task 2.9's serializer to patch
@@ -297,6 +303,9 @@ _edje_cc_vg_cleanup(void)
toplevel_vg_trees = NULL;
_vg_desc_to_tree = NULL;
_vg_xform_seen = NULL;
+ /* Free the buffer canvas used by _svg_path_parse_to_arrays.
+ * _cc_evas and _cc_vg are owned by _cc_ee and freed transitively. */
+ if (_cc_ee) { ecore_evas_free(_cc_ee); _cc_ee = NULL; _cc_evas = NULL; _cc_vg = NULL; }
}
Eina_List *po_files;
@@ -2147,8 +2156,39 @@ _merge_override_fields(Edje_Vg_Override *dst, const Edje_Vg_Override *src)
if (src->expected_type == EDJE_VG_NODE_SHAPE)
{
if (m & EDJE_VG_OVR_PATH)
- eina_stringshare_replace(&dst->payload.shape.path,
- src->payload.shape.path);
+ {
+ free(dst->payload.shape.commands);
+ dst->payload.shape.commands = NULL;
+ dst->payload.shape.commands_count = 0;
+ free(dst->payload.shape.points);
+ dst->payload.shape.points = NULL;
+ dst->payload.shape.points_count = 0;
+ if (src->payload.shape.commands_count > 0 && src->payload.shape.commands)
+ {
+ size_t sz = src->payload.shape.commands_count
+ * sizeof(Efl_Gfx_Path_Command);
+ dst->payload.shape.commands = malloc(sz);
+ if (dst->payload.shape.commands)
+ {
+ memcpy(dst->payload.shape.commands,
+ src->payload.shape.commands, sz);
+ dst->payload.shape.commands_count =
+ src->payload.shape.commands_count;
+ }
+ }
+ if (src->payload.shape.points_count > 0 && src->payload.shape.points)
+ {
+ size_t sz = src->payload.shape.points_count * sizeof(double);
+ dst->payload.shape.points = malloc(sz);
+ if (dst->payload.shape.points)
+ {
+ memcpy(dst->payload.shape.points,
+ src->payload.shape.points, sz);
+ dst->payload.shape.points_count =
+ src->payload.shape.points_count;
+ }
+ }
+ }
if (m & EDJE_VG_OVR_FILL_RAW)
{
@@ -2533,6 +2573,110 @@ st_vg_node_transform_matrix(void)
error_and_abort(NULL, "shape-only property on non-shape node"); \
} while (0)
+/* ---------------------------------------------------------------------------
+ * _svg_path_has_arcs — scan the SVG path string for arc commands (a/A).
+ * The efl_gfx_path.c parser also recognises 'e'/'E' as arc-like commands;
+ * we reject those too for simplicity.
+ * ---------------------------------------------------------------------------*/
+static Eina_Bool
+_svg_path_has_arcs(const char *path)
+{
+ if (!path) return EINA_FALSE;
+ const char *p = path;
+ while (*p)
+ {
+ /* skip whitespace and commas */
+ while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r' || *p == ',')
+ p++;
+ if (!*p) break;
+ if (strchr("MmLlHhVvCcSsQqTtZz", *p))
+ {
+ p++;
+ continue;
+ }
+ if (*p == 'a' || *p == 'A' || *p == 'e' || *p == 'E')
+ return EINA_TRUE;
+ p++;
+ }
+ return EINA_FALSE;
+}
+
+/* ---------------------------------------------------------------------------
+ * _svg_path_parse_to_arrays — parse an SVG path string into freshly-allocated
+ * Efl_Gfx_Path_Command and double arrays using a temporary buffer canvas.
+ * Returns EINA_TRUE and populates *out_cmds / *out_pts / *out_nc / *out_np
+ * on success. The caller owns both arrays and must free() them.
+ * ---------------------------------------------------------------------------*/
+static Eina_Bool
+_svg_path_parse_to_arrays(const char *svg,
+ Efl_Gfx_Path_Command **out_cmds,
+ double **out_pts,
+ unsigned int *out_nc,
+ unsigned int *out_np)
+{
+ if (!_cc_ee)
+ {
+ _cc_ee = ecore_evas_buffer_new(1, 1);
+ if (!_cc_ee) return EINA_FALSE;
+ _cc_evas = ecore_evas_get(_cc_ee);
+ _cc_vg = efl_add(EFL_CANVAS_VG_OBJECT_CLASS, _cc_evas,
+ efl_gfx_entity_geometry_set(efl_added,
+ EINA_RECT(0, 0, 1, 1)));
+ }
+
+ if (!_cc_vg) return EINA_FALSE;
+
+ /* Obtain or create the VG root and a scratch shape child. */
+ Efl_VG *root = efl_canvas_vg_object_root_node_get(_cc_vg);
+ if (!root)
+ {
+ root = efl_add(EFL_CANVAS_VG_CONTAINER_CLASS, _cc_vg);
+ efl_canvas_vg_object_root_node_set(_cc_vg, root);
+ }
+
+ Efl_VG *shape = efl_add(EFL_CANVAS_VG_SHAPE_CLASS, root);
+ if (!shape) return EINA_FALSE;
+
+ efl_gfx_path_reset(shape);
+ efl_gfx_path_append_svg_path(shape, svg);
+
+ const Efl_Gfx_Path_Command *cmds = NULL;
+ const double *pts = NULL;
+ unsigned int nc = 0, np = 0;
+
+ efl_gfx_path_get(shape, &cmds, &pts);
+ efl_gfx_path_length_get(shape, &nc, &np);
+
+ /* Copy BEFORE efl_del — the pointers are borrowed refs into shape's data. */
+ if (!cmds || nc == 0)
+ {
+ efl_del(shape);
+ return EINA_FALSE;
+ }
+
+ Efl_Gfx_Path_Command *cc = malloc(nc * sizeof(Efl_Gfx_Path_Command));
+ double *pp = np > 0 ? malloc(np * sizeof(double)) : NULL;
+
+ if (!cc || (np > 0 && !pp))
+ {
+ free(cc);
+ free(pp);
+ efl_del(shape);
+ return EINA_FALSE;
+ }
+
+ memcpy(cc, cmds, nc * sizeof(Efl_Gfx_Path_Command));
+ if (np > 0) memcpy(pp, pts, np * sizeof(double));
+
+ efl_del(shape);
+
+ *out_cmds = cc;
+ *out_pts = pp;
+ *out_nc = nc;
+ *out_np = np;
+ return EINA_TRUE;
+}
+
/**
* @page edcref
* @property
@@ -2541,8 +2685,8 @@ st_vg_node_transform_matrix(void)
* [string]
* @effect
* SVG 'd' attribute command string describing the shape's geometry.
- * The string is stored verbatim; no validation is performed at parse
- * time (VEC003 validation is Phase 7).
+ * The string is pre-parsed at compile time into Efl_Gfx_Path_Command and
+ * coordinate arrays. Arc commands (a/A/e/E) are rejected with VEC003.
* @endproperty
*/
static void
@@ -2550,13 +2694,50 @@ st_vg_shape_path(void)
{
check_arg_count(1);
EXPECT_SHAPE();
- char *p = parse_str(0);
- /* VEC003: validate the SVG path command sequence. */
- if (!_edje_vg_path_is_valid(p))
- edje_diag_emit("VEC003", EDJE_DIAG_ERR, file_in, line, NULL, NULL,
- "path: '%s' is not a valid SVG path command sequence", p);
- eina_stringshare_replace(¤t_vg_node->shape.path, p);
- free(p);
+ char *svg = parse_str(0);
+
+ /* VEC003a: basic grammar check. */
+ if (!_edje_vg_path_is_valid(svg))
+ {
+ edje_diag_emit("VEC003", EDJE_DIAG_ERR, file_in, line, NULL, NULL,
+ "path: '%s' is not a valid SVG path command sequence", svg);
+ free(svg);
+ return;
+ }
+
+ /* VEC003b: arc commands are not supported — themes must convert to cubics. */
+ if (_svg_path_has_arcs(svg))
+ {
+ edje_diag_emit("VEC003", EDJE_DIAG_ERR, file_in, line, NULL, NULL,
+ "path: '%s' contains arc commands (a/A/e/E) which are "
+ "not supported. Convert arcs to cubic beziers in your "
+ "authoring tool before importing.", svg);
+ free(svg);
+ return;
+ }
+
+ Efl_Gfx_Path_Command *cmds = NULL;
+ double *pts = NULL;
+ unsigned int nc = 0, np = 0;
+
+ if (!_svg_path_parse_to_arrays(svg, &cmds, &pts, &nc, &np))
+ {
+ edje_diag_emit("VEC003", EDJE_DIAG_ERR, file_in, line, NULL, NULL,
+ "path: '%s' could not be parsed", svg);
+ free(svg);
+ return;
+ }
+
+ free(svg);
+
+ /* Install arrays into the current node, replacing any previous value. */
+ free(current_vg_node->shape.commands);
+ free(current_vg_node->shape.points);
+ current_vg_node->shape.commands = cmds;
+ current_vg_node->shape.points = pts;
+ current_vg_node->shape.commands_count = nc;
+ current_vg_node->shape.points_count = np;
+
VG_FLAG(PATH);
}
diff --git a/src/bin/edje/edje_cc_out.c b/src/bin/edje/edje_cc_out.c
index 31134180aa..7d5a4250d1 100644
--- a/src/bin/edje/edje_cc_out.c
+++ b/src/bin/edje/edje_cc_out.c
@@ -3040,49 +3040,19 @@ data_write_color_class_register(Eet_File *ef)
/* --- Task 7.5: path topology helpers -------------------------------------- */
-/* Extract the ordered command-letter sequence from an SVG path string into
- * cmds[0..max-1]. Returns the number of commands written. Coordinate
- * digits, signs, dots, and exponent letters are NOT counted — only the
- * single-letter command tokens from the SVG path grammar are recorded.
- *
- * 'e'/'E' are skipped because they appear inside floating-point exponents
- * (e.g. "1.5e-3") as well as at word boundaries where they would be
- * ambiguous. No legitimate SVG path uses the hypothetical 'e' command, so
- * skipping them causes no false positives. */
-static unsigned int
-_path_command_count(const char *p, char *cmds, unsigned int max)
-{
- if (!p) return 0;
- unsigned int n = 0;
- for (; *p && n < max; p++)
- {
- /* Skip whitespace and comma separators. */
- if (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r' || *p == ',')
- continue;
- /* Record recognised SVG command letters. */
- if (strchr("MmLlHhVvCcSsQqTtAaZz", *p))
- cmds[n++] = *p;
- /* Everything else (digits, '.', '+', '-', exponents) is coordinate
- * data — skip silently. */
- }
- return n;
-}
-
-/* Return EINA_TRUE if path_a and path_b have the same command-letter
- * sequence (same length, same per-position command). Coordinate values
- * are intentionally ignored — they are expected to differ across states
- * that are meant to morph. */
+/* Return EINA_TRUE if node_a and node_b have topology-compatible paths.
+ * With pre-parsed arrays, topology-compatibility is simply: same
+ * commands_count and identical per-element Efl_Gfx_Path_Command sequence.
+ * Coordinate values (points) are intentionally not compared — they are
+ * expected to differ across states that are meant to morph. */
static Eina_Bool
-_paths_topology_compatible(const char *path_a, const char *path_b)
+_paths_topology_compatible(const Edje_Vg_Node *na, const Edje_Vg_Node *nb)
{
- /* 2048 commands is an ample upper bound; real paths rarely exceed ~100. */
- char cmds_a[2048], cmds_b[2048];
- unsigned int n_a = _path_command_count(path_a, cmds_a, sizeof(cmds_a));
- unsigned int n_b = _path_command_count(path_b, cmds_b, sizeof(cmds_b));
- if (n_a != n_b) return EINA_FALSE;
- for (unsigned int i = 0; i < n_a; i++)
- if (cmds_a[i] != cmds_b[i]) return EINA_FALSE;
- return EINA_TRUE;
+ if (na->shape.commands_count != nb->shape.commands_count) return EINA_FALSE;
+ if (na->shape.commands_count == 0) return EINA_TRUE;
+ return memcmp(na->shape.commands, nb->shape.commands,
+ na->shape.commands_count * sizeof(Efl_Gfx_Path_Command)) == 0
+ ? EINA_TRUE : EINA_FALSE;
}
/* Recursive walk over tree A's nodes; for each named node also present in
@@ -3119,7 +3089,7 @@ _vec_walk_pair(const Edje_Vg_Node *na, const Edje_Vg_Tree *tb,
{
/* VEC004: same name, same SHAPE type, but incompatible path
* topology — runtime will step instead of morphing. */
- if (!_paths_topology_compatible(na->shape.path, nb->shape.path))
+ if (!_paths_topology_compatible(na, nb))
{
edje_diag_emit("VEC004", EDJE_DIAG_WARN, NULL, 0,
part_name, NULL,
diff --git a/src/lib/edje/edje_data.c b/src/lib/edje/edje_data.c
index 5f7426b13a..8948ef628d 100644
--- a/src/lib/edje/edje_data.c
+++ b/src/lib/edje/edje_data.c
@@ -546,7 +546,13 @@ _edje_edd_init(void)
EET_DATA_DESCRIPTOR_ADD_SUB_NESTED(_edje_edd_edje_vg_node, Edje_Vg_Node, "color", color, _edje_edd_edje_vg_color_binding);
EET_DATA_DESCRIPTOR_ADD_SUB_NESTED(_edje_edd_edje_vg_node, Edje_Vg_Node, "xform", xform, _edje_edd_edje_vg_transform);
/* shape payload */
- EET_DATA_DESCRIPTOR_ADD_BASIC(_edje_edd_edje_vg_node, Edje_Vg_Node, "shape.path", shape.path, EET_T_STRING);
+ /* EET_T_UINT reads 4 bytes per element. Guard against -fshort-enums
+ * shrinking Efl_Gfx_Path_Command below uint size, which would corrupt
+ * the on-disk representation silently. */
+ _Static_assert(sizeof(Efl_Gfx_Path_Command) == sizeof(unsigned int),
+ "Edje VG Eet descriptor assumes Efl_Gfx_Path_Command is uint-sized");
+ EET_DATA_DESCRIPTOR_ADD_BASIC_VAR_ARRAY(_edje_edd_edje_vg_node, Edje_Vg_Node, "shape.commands", shape.commands, EET_T_UINT);
+ EET_DATA_DESCRIPTOR_ADD_BASIC_VAR_ARRAY(_edje_edd_edje_vg_node, Edje_Vg_Node, "shape.points", shape.points, EET_T_DOUBLE);
EET_DATA_DESCRIPTOR_ADD_SUB_NESTED(_edje_edd_edje_vg_node, Edje_Vg_Node, "shape.fill", shape.fill, _edje_edd_edje_vg_color_binding);
EET_DATA_DESCRIPTOR_ADD_SUB_NESTED(_edje_edd_edje_vg_node, Edje_Vg_Node, "shape.stroke_color", shape.stroke_color, _edje_edd_edje_vg_color_binding);
EET_DATA_DESCRIPTOR_ADD_BASIC(_edje_edd_edje_vg_node, Edje_Vg_Node, "shape.stroke_width", shape.stroke_width, EET_T_DOUBLE);
diff --git a/src/lib/edje/edje_edit.c b/src/lib/edje/edje_edit.c
index 49ff67b1b2..865af8e378 100644
--- a/src/lib/edje/edje_edit.c
+++ b/src/lib/edje/edje_edit.c
@@ -14089,13 +14089,85 @@ _edje_vg_emit_xform(const Edje_Vg_Transform *x, Eina_Strbuf *buf, const char *in
(void)ret;
}
+/* -------------------------------------------------------------------------
+ * _edje_vg_path_to_svg_string
+ *
+ * Converts pre-parsed Efl_Gfx_Path_Command / double arrays back to an SVG
+ * path 'd' string and appends it to buf. Absolute uppercase commands are
+ * always emitted (M, L, C, Z). LC_NUMERIC is forced to "C" internally so
+ * that %.10g always uses '.' as the decimal separator regardless of the
+ * process locale.
+ * -------------------------------------------------------------------------*/
+static void
+_edje_vg_path_to_svg_string(const Efl_Gfx_Path_Command *commands,
+ const double *points,
+ unsigned int commands_count,
+ Eina_Strbuf *buf)
+{
+ if (!commands || commands_count == 0) return;
+
+ char *_prev_locale = setlocale(LC_NUMERIC, NULL);
+ if (_prev_locale) _prev_locale = strdup(_prev_locale);
+ setlocale(LC_NUMERIC, "C");
+
+ const double *p = points;
+ unsigned int i;
+ Eina_Bool first = EINA_TRUE;
+
+ for (i = 0; i < commands_count; i++)
+ {
+ switch (commands[i])
+ {
+ case EFL_GFX_PATH_COMMAND_TYPE_END:
+ goto done;
+ case EFL_GFX_PATH_COMMAND_TYPE_MOVE_TO:
+ eina_strbuf_append_printf(buf, "%sM%.10g,%.10g",
+ first ? "" : " ", p[0], p[1]);
+ p += 2;
+ break;
+ case EFL_GFX_PATH_COMMAND_TYPE_LINE_TO:
+ eina_strbuf_append_printf(buf, " L%.10g,%.10g", p[0], p[1]);
+ p += 2;
+ break;
+ case EFL_GFX_PATH_COMMAND_TYPE_CUBIC_TO:
+ /* EFL CUBIC_TO order: ctrl1(x,y) ctrl2(x,y) end(x,y) */
+ eina_strbuf_append_printf(buf,
+ " C%.10g,%.10g %.10g,%.10g %.10g,%.10g",
+ p[0], p[1], p[2], p[3], p[4], p[5]);
+ p += 6;
+ break;
+ case EFL_GFX_PATH_COMMAND_TYPE_CLOSE:
+ eina_strbuf_append(buf, " Z");
+ break;
+ case EFL_GFX_PATH_COMMAND_TYPE_LAST:
+ default:
+ goto done;
+ }
+ first = EINA_FALSE;
+ }
+done:
+ setlocale(LC_NUMERIC, _prev_locale ? _prev_locale : "");
+ free(_prev_locale);
+}
+
static void
_edje_vg_emit_shape_node(const Edje_Vg_Node *n, Eina_Strbuf *buf,
const char *indent, const char *inner)
{
Eina_Bool ret = EINA_TRUE;
- if (n->shape.path)
- BUF_APPENDF("%spath: \"%s\";\n", inner, n->shape.path);
+ if (n->shape.commands_count > 0 && n->shape.commands)
+ {
+ /* Reconstruct the SVG string from the pre-parsed arrays for EDC output. */
+ Eina_Strbuf *path_buf = eina_strbuf_new();
+ if (path_buf)
+ {
+ _edje_vg_path_to_svg_string(n->shape.commands, n->shape.points,
+ n->shape.commands_count, path_buf);
+ BUF_APPENDF("%spath: \"%s\";\n", inner,
+ eina_strbuf_string_get(path_buf));
+ eina_strbuf_free(path_buf);
+ }
+ }
/* fill */
if (n->shape.fill.color_class || n->shape.fill.r || n->shape.fill.g ||
@@ -14315,7 +14387,17 @@ _edje_vg_emit_override(const Edje_Vg_Override *ovr, Eina_Strbuf *buf,
if (ovr->expected_type == EDJE_VG_NODE_SHAPE)
{
if (ovr->field_mask & EDJE_VG_OVR_PATH)
- BUF_APPENDF("%spath: \"%s\";\n", inner, p->shape.path ? p->shape.path : "");
+ {
+ Eina_Strbuf *path_buf = eina_strbuf_new();
+ if (path_buf)
+ {
+ _edje_vg_path_to_svg_string(p->shape.commands, p->shape.points,
+ p->shape.commands_count, path_buf);
+ BUF_APPENDF("%spath: \"%s\";\n", inner,
+ eina_strbuf_string_get(path_buf));
+ eina_strbuf_free(path_buf);
+ }
+ }
if (ovr->field_mask & EDJE_VG_OVR_FILL_RAW)
BUF_APPENDF("%sfill.color: %d %d %d %d;\n", inner,
p->shape.fill.r, p->shape.fill.g, p->shape.fill.b, p->shape.fill.a);
diff --git a/src/lib/edje/edje_private.h b/src/lib/edje/edje_private.h
index ee7567956c..ace2146024 100644
--- a/src/lib/edje/edje_private.h
+++ b/src/lib/edje/edje_private.h
@@ -747,7 +747,10 @@ struct _Edje_Vg_Node {
/* Discriminated payload — only one is valid per `type`. */
struct {
- const char *path; /* SVG d-string */
+ Efl_Gfx_Path_Command *commands; /* END-terminated; heap-allocated */
+ double *points; /* coordinate pairs; heap-allocated */
+ unsigned int commands_count;
+ unsigned int points_count;
Edje_Vg_Color_Binding fill;
Edje_Vg_Color_Binding stroke_color;
double stroke_width;
diff --git a/src/lib/edje/edje_vg_tree.c b/src/lib/edje/edje_vg_tree.c
index cff0088879..8dce3c1070 100644
--- a/src/lib/edje/edje_vg_tree.c
+++ b/src/lib/edje/edje_vg_tree.c
@@ -52,7 +52,6 @@ _node_new_container(const char *name)
static Edje_Vg_Node *
_node_new_shape(const char *name,
- const char *path,
unsigned char fr, unsigned char fg,
unsigned char fb, unsigned char fa,
unsigned char sr, unsigned char sg,
@@ -65,7 +64,6 @@ _node_new_shape(const char *name,
n->visible = 1;
n->name = name ? eina_stringshare_add(name) : NULL;
- n->shape.path = eina_stringshare_add(path);
n->shape.fill.r = fr;
n->shape.fill.g = fg;
n->shape.fill.b = fb;
@@ -109,10 +107,13 @@ _edje_vg_tree_new_for_test(void)
if (!root) { free(t); return NULL; }
/* body shape — use neutral colours to avoid risk of rgba bytes being
- * misread as pointer values during Eet encoding diagnostics. */
+ * misread as pointer values during Eet encoding diagnostics.
+ * Path "M0,0 L100,0 L100,100 Z" pre-parsed:
+ * commands: MOVE_TO, LINE_TO, LINE_TO, CLOSE, END (5 entries)
+ * points: 0,0, 100,0, 100,100 (6 entries)
+ */
Edje_Vg_Node *body = _node_new_shape(
"body",
- "M0,0 L100,0 L100,100 Z",
64, 128, 32, 255, /* fill rgba — non-trivial but not pointer-like */
0, 0, 0, 255, /* stroke rgba */
2.0 /* stroke_width */
@@ -123,6 +124,36 @@ _edje_vg_tree_new_for_test(void)
free(t);
return NULL;
}
+ {
+ static const Efl_Gfx_Path_Command cmds[] = {
+ EFL_GFX_PATH_COMMAND_TYPE_MOVE_TO,
+ EFL_GFX_PATH_COMMAND_TYPE_LINE_TO,
+ EFL_GFX_PATH_COMMAND_TYPE_LINE_TO,
+ EFL_GFX_PATH_COMMAND_TYPE_CLOSE,
+ EFL_GFX_PATH_COMMAND_TYPE_END
+ };
+ static const double pts[] = { 0.0, 0.0, 100.0, 0.0, 100.0, 100.0 };
+ body->shape.commands =
+ malloc(sizeof(cmds));
+ body->shape.points =
+ malloc(sizeof(pts));
+ if (body->shape.commands && body->shape.points)
+ {
+ memcpy(body->shape.commands, cmds, sizeof(cmds));
+ memcpy(body->shape.points, pts, sizeof(pts));
+ body->shape.commands_count = 5;
+ body->shape.points_count = 6;
+ }
+ else
+ {
+ free(body->shape.commands);
+ free(body->shape.points);
+ body->shape.commands = NULL;
+ body->shape.points = NULL;
+ body->shape.commands_count = 0;
+ body->shape.points_count = 0;
+ }
+ }
root->container.children =
eina_list_append(root->container.children, body);
@@ -159,8 +190,15 @@ _edje_vg_node_free(Edje_Vg_Node *n, Eina_Bool free_strings)
switch (n->type)
{
case EDJE_VG_NODE_SHAPE:
- if (free_strings && n->shape.path)
- { eina_stringshare_del(n->shape.path); n->shape.path = NULL; }
+ /* commands/points are always heap-allocated (VAR_ARRAY in Eet decodes
+ * them unconditionally regardless of edf->free_strings), so free them
+ * unconditionally — never stringshare. */
+ free(n->shape.commands);
+ n->shape.commands = NULL;
+ n->shape.commands_count = 0;
+ free(n->shape.points);
+ n->shape.points = NULL;
+ n->shape.points_count = 0;
if (free_strings && n->shape.fill.color_class)
{ eina_stringshare_del(n->shape.fill.color_class); n->shape.fill.color_class = NULL; }
if (free_strings && n->shape.stroke_color.color_class)
@@ -387,7 +425,16 @@ _node_equal(const Edje_Vg_Node *a, const Edje_Vg_Node *b)
switch (a->type)
{
case EDJE_VG_NODE_SHAPE:
- if (!_streq(a->shape.path, b->shape.path)) return EINA_FALSE;
+ if (a->shape.commands_count != b->shape.commands_count) return EINA_FALSE;
+ if (a->shape.points_count != b->shape.points_count) return EINA_FALSE;
+ if (a->shape.commands_count > 0 &&
+ memcmp(a->shape.commands, b->shape.commands,
+ a->shape.commands_count * sizeof(Efl_Gfx_Path_Command)) != 0)
+ return EINA_FALSE;
+ if (a->shape.points_count > 0 &&
+ memcmp(a->shape.points, b->shape.points,
+ a->shape.points_count * sizeof(double)) != 0)
+ return EINA_FALSE;
if (!_color_binding_equal(&a->shape.fill, &b->shape.fill))
return EINA_FALSE;
if (!_color_binding_equal(&a->shape.stroke_color, &b->shape.stroke_color))
@@ -511,8 +558,31 @@ _node_dup(const Edje_Vg_Node *src)
switch (src->type)
{
case EDJE_VG_NODE_SHAPE:
- dst->shape.path =
- src->shape.path ? eina_stringshare_add(src->shape.path) : NULL;
+ dst->shape.commands_count = src->shape.commands_count;
+ dst->shape.points_count = src->shape.points_count;
+ if (src->shape.commands_count > 0 && src->shape.commands)
+ {
+ size_t sz =
+ src->shape.commands_count * sizeof(Efl_Gfx_Path_Command);
+ dst->shape.commands = malloc(sz);
+ if (dst->shape.commands)
+ memcpy(dst->shape.commands, src->shape.commands, sz);
+ else
+ dst->shape.commands_count = 0;
+ }
+ else
+ dst->shape.commands_count = 0;
+ if (src->shape.points_count > 0 && src->shape.points)
+ {
+ size_t sz = src->shape.points_count * sizeof(double);
+ dst->shape.points = malloc(sz);
+ if (dst->shape.points)
+ memcpy(dst->shape.points, src->shape.points, sz);
+ else
+ dst->shape.points_count = 0;
+ }
+ else
+ dst->shape.points_count = 0;
_color_binding_dup(&dst->shape.fill, &src->shape.fill);
_color_binding_dup(&dst->shape.stroke_color, &src->shape.stroke_color);
dst->shape.stroke_width = src->shape.stroke_width;
@@ -640,8 +710,35 @@ _edje_vg_tree_apply_override(Edje_Vg_Tree *working,
node->xform = ovr->payload.xform;
if (m & EDJE_VG_OVR_PATH)
- eina_stringshare_replace(&node->shape.path,
- ovr->payload.shape.path);
+ {
+ free(node->shape.commands);
+ node->shape.commands = NULL;
+ node->shape.commands_count = 0;
+ free(node->shape.points);
+ node->shape.points = NULL;
+ node->shape.points_count = 0;
+ if (ovr->payload.shape.commands_count > 0 && ovr->payload.shape.commands)
+ {
+ size_t sz =
+ ovr->payload.shape.commands_count * sizeof(Efl_Gfx_Path_Command);
+ node->shape.commands = malloc(sz);
+ if (node->shape.commands)
+ {
+ memcpy(node->shape.commands, ovr->payload.shape.commands, sz);
+ node->shape.commands_count = ovr->payload.shape.commands_count;
+ }
+ }
+ if (ovr->payload.shape.points_count > 0 && ovr->payload.shape.points)
+ {
+ size_t sz = ovr->payload.shape.points_count * sizeof(double);
+ node->shape.points = malloc(sz);
+ if (node->shape.points)
+ {
+ memcpy(node->shape.points, ovr->payload.shape.points, sz);
+ node->shape.points_count = ovr->payload.shape.points_count;
+ }
+ }
+ }
if (m & EDJE_VG_OVR_FILL_RAW)
{
@@ -801,9 +898,31 @@ _edje_vg_override_dup(const Edje_Vg_Override *src)
if (src->expected_type == EDJE_VG_NODE_SHAPE)
{
if (src->field_mask & EDJE_VG_OVR_PATH)
- dup->payload.shape.path = src->payload.shape.path
- ? eina_stringshare_add(src->payload.shape.path)
- : NULL;
+ {
+ dup->payload.shape.commands_count = src->payload.shape.commands_count;
+ dup->payload.shape.points_count = src->payload.shape.points_count;
+ if (src->payload.shape.commands_count > 0 && src->payload.shape.commands)
+ {
+ size_t sz =
+ src->payload.shape.commands_count * sizeof(Efl_Gfx_Path_Command);
+ dup->payload.shape.commands = malloc(sz);
+ if (dup->payload.shape.commands)
+ memcpy(dup->payload.shape.commands,
+ src->payload.shape.commands, sz);
+ else
+ dup->payload.shape.commands_count = 0;
+ }
+ if (src->payload.shape.points_count > 0 && src->payload.shape.points)
+ {
+ size_t sz = src->payload.shape.points_count * sizeof(double);
+ dup->payload.shape.points = malloc(sz);
+ if (dup->payload.shape.points)
+ memcpy(dup->payload.shape.points,
+ src->payload.shape.points, sz);
+ else
+ dup->payload.shape.points_count = 0;
+ }
+ }
if (src->field_mask & EDJE_VG_OVR_FILL_RAW)
{
@@ -932,11 +1051,12 @@ _edje_vg_override_free(Edje_Vg_Override *ovr)
/* Shape-specific */
if (ovr->expected_type == EDJE_VG_NODE_SHAPE)
{
- if (ovr->payload.shape.path)
- {
- eina_stringshare_del(ovr->payload.shape.path);
- ovr->payload.shape.path = NULL;
- }
+ free(ovr->payload.shape.commands);
+ ovr->payload.shape.commands = NULL;
+ ovr->payload.shape.commands_count = 0;
+ free(ovr->payload.shape.points);
+ ovr->payload.shape.points = NULL;
+ ovr->payload.shape.points_count = 0;
if (ovr->payload.shape.fill.color_class)
{
eina_stringshare_del(ovr->payload.shape.fill.color_class);
@@ -1169,7 +1289,7 @@ static void
*
* Path geometry is NOT touched here — it is interpolated on the live
* Efl_VG via efl_gfx_path_interpolate() at materialize time (edje_calc.c).
- * out_node already carries B's path string as the step fallback.
+ * out_node already carries B's commands+points arrays as the step fallback.
*/
_node_lerp_recursive(Edje_Vg_Node *out_node,
const Edje_Vg_Tree *a, const Edje_Vg_Tree *b,
@@ -1256,8 +1376,8 @@ _node_lerp_recursive(Edje_Vg_Node *out_node,
/* Cap, join, fill_rule: step — out already holds B's values */
/* Path: NOT lerped here — efl_gfx_path_interpolate at
- materialize-time (Task 5.3). out already holds B's path
- string from the dup; this is the fallback step value. */
+ materialize-time (Task 5.3). out already holds B's
+ commands+points from the dup; this is the step fallback. */
break;
case EDJE_VG_NODE_GRADIENT_LINEAR:
@@ -1581,11 +1701,8 @@ _shape_to_efl_vg(const Edje_Vg_Node *n, Efl_VG *parent,
if (!s) return NULL;
/* Path */
- if (n->shape.path && *n->shape.path)
- {
- efl_gfx_path_reset(s);
- efl_gfx_path_append_svg_path(s, n->shape.path);
- }
+ if (n->shape.commands_count > 0 && n->shape.commands)
+ efl_gfx_path_set(s, n->shape.commands, n->shape.points);
/* Fill: gradient or flat color */
if (n->shape.gradient_ref && *n->shape.gradient_ref)
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.