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(&current_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.

Reply via email to