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 4cf920e6a197df5c645a95b9c0b9fd35211b2603
Author: [email protected] <[email protected]>
AuthorDate: Thu Apr 30 16:53:51 2026 -0600

    edje: emit vector trees and overrides in edje_decc (Phase 8.1)
    
    Implement source code generation for named vector trees and per-shape
    overrides via both edje_edit and edje_decc source-generation pipelines.
    
    This unblocks edje_decc decompilation: without these emitters, edje_decc
    would silently produce incomplete EDC files for any theme using named
    vector trees (Mode A trees referenced via vector.use:), and the result
    would not recompile. The feature was fully functional at runtime but
    broken for round-trip workflows.
    
    Changes:
      - Add Edje_Vg_Tree.name field (stringshare) to track toplevel tree
        names across serialization. Eet-descriptor updated for backward
        compatibility: loading older .edj files reads it as NULL.
    
      - Emit vectors {} block in both _edje_edit_source_generate (edje_edit
        API) and _edje_generate_source (edje_decc/edje_edit_source_save).
        Named trees are emitted at top level; anonymous trees stay inline.
    
      - Per-description vector support: emit vector.use: "name" for Mode A
        references, vector { } inline blocks for Mode B anonymous trees,
        and sparse per-node overrides with color_class bindings.
    
      - VG emission helpers for shapes, gradients, transforms, and fill/
        stroke properties using EDC syntax (cap/join/fill-rule enums).
    
    The test suite adds edje_vg_source_gen_round_trip to verify both
    pipelines emit structural substrings (vectors {, vector.use:, vector.shape,
    color_class:), catching regressions in either path. This caught a real
    bug during development where vectors {}} was only hooked into one pipeline.
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
---
 src/bin/edje/edje_cc_handlers.c          |   3 +
 src/lib/edje/edje_data.c                 |   1 +
 src/lib/edje/edje_edit.c                 | 476 +++++++++++++++++++++++++++++++
 src/lib/edje/edje_private.h              |   1 +
 src/lib/edje/edje_vg_tree.c              |   2 +
 src/tests/edje/edje_test_vector_states.c | 109 ++++++-
 6 files changed, 591 insertions(+), 1 deletion(-)

diff --git a/src/bin/edje/edje_cc_handlers.c b/src/bin/edje/edje_cc_handlers.c
index 79df0860d3..91379ed2e8 100644
--- a/src/bin/edje/edje_cc_handlers.c
+++ b/src/bin/edje/edje_cc_handlers.c
@@ -1846,6 +1846,9 @@ st_vectors_vector_name(void)
    if (eina_hash_find(toplevel_vg_trees, name))
      error_and_abort(NULL, "Duplicate vector tree name '%s' (VEC001)", name);
    eina_hash_add(toplevel_vg_trees, name, current_vg_tree);
+   /* Store the name inside the tree so the decompiler can recover it. */
+   if (current_vg_tree->name) eina_stringshare_del(current_vg_tree->name);
+   current_vg_tree->name = eina_stringshare_add(name);
    free(name);
 }
 
diff --git a/src/lib/edje/edje_data.c b/src/lib/edje/edje_data.c
index c0208e494d..5f7426b13a 100644
--- a/src/lib/edje/edje_data.c
+++ b/src/lib/edje/edje_data.c
@@ -570,6 +570,7 @@ _edje_edd_init(void)
    /* Edje_Vg_Tree */
    EET_EINA_FILE_DATA_DESCRIPTOR_CLASS_SET(&eddc, Edje_Vg_Tree);
    _edje_edd_edje_vg_tree = eet_data_descriptor_file_new(&eddc);
+   EET_DATA_DESCRIPTOR_ADD_BASIC(_edje_edd_edje_vg_tree, Edje_Vg_Tree, "name", name, EET_T_STRING);
    EET_DATA_DESCRIPTOR_ADD_BASIC(_edje_edd_edje_vg_tree, Edje_Vg_Tree, "vbx", vbx, EET_T_DOUBLE);
    EET_DATA_DESCRIPTOR_ADD_BASIC(_edje_edd_edje_vg_tree, Edje_Vg_Tree, "vby", vby, EET_T_DOUBLE);
    EET_DATA_DESCRIPTOR_ADD_BASIC(_edje_edd_edje_vg_tree, Edje_Vg_Tree, "vbw", vbw, EET_T_DOUBLE);
diff --git a/src/lib/edje/edje_edit.c b/src/lib/edje/edje_edit.c
index e4a5b4a1da..d54671d657 100644
--- a/src/lib/edje/edje_edit.c
+++ b/src/lib/edje/edje_edit.c
@@ -11,6 +11,7 @@
 #define EFL_CANVAS_GROUP_PROTECTED
 
 #include "edje_private.h"
+#include "edje_vg_tree.h"
 
 #include "canvas/evas_canvas_eo.h"
 
@@ -12683,6 +12684,9 @@ static const char *aspect_mode[] = {"NONE", "NEITHER", "HORIZONTAL", "VERTICAL",
 static Eina_Bool
  _edje_generate_source_of_group(Edje *ed, Edje_Part_Collection_Directory_Entry *pce, Eina_Strbuf *buf);
 
+static void
+ _edje_generate_source_of_vectors_block(const Edje_File *ef, Eina_Strbuf *buf);
+
 static Eina_Bool
  _edje_generate_source_of_style(Edje *ed, const char *name, Eina_Strbuf *buf);
 
@@ -13051,6 +13055,10 @@ _edje_edit_source_generate(Evas_Object *obj, Eina_Bool without_global_data)
      }
 
 
+   /* Emit toplevel named vector trees (Mode A) */
+   if (!without_global_data)
+     _edje_generate_source_of_vectors_block(ed->file, buf);
+
    /* print the main code of group collections */
    if (!without_global_data) BUF_APPEND(I0 "collections {\n");
    /* if sounds were found, print them */
@@ -13999,6 +14007,467 @@ _edje_generate_source_state_relative(Edje *ed,
      }
 }
 
+/* =========================================================================
+ * VG source generation — Phase 8.1
+ * ========================================================================= */
+
+static void _edje_vg_emit_node(const Edje_Vg_Node *n, Eina_Strbuf *buf, int depth);
+
+static const char *
+_vg_cap_str(Efl_Gfx_Cap c)
+{
+   switch (c)
+     {
+      case EFL_GFX_CAP_ROUND:  return "ROUND";
+      case EFL_GFX_CAP_SQUARE: return "SQUARE";
+      default:                 return "BUTT";
+     }
+}
+
+static const char *
+_vg_join_str(Efl_Gfx_Join j)
+{
+   switch (j)
+     {
+      case EFL_GFX_JOIN_ROUND: return "ROUND";
+      case EFL_GFX_JOIN_BEVEL: return "BEVEL";
+      default:                 return "MITER";
+     }
+}
+
+static const char *
+_vg_fill_rule_str(Efl_Gfx_Fill_Rule r)
+{
+   return (r == EFL_GFX_FILL_RULE_ODD_EVEN) ? "EVEN_ODD" : "WINDING";
+}
+
+static const char *
+_vg_spread_str(Efl_Gfx_Gradient_Spread s)
+{
+   switch (s)
+     {
+      case EFL_GFX_GRADIENT_SPREAD_REFLECT: return "REFLECT";
+      case EFL_GFX_GRADIENT_SPREAD_REPEAT:  return "REPEAT";
+      default:                              return "PAD";
+     }
+}
+
+/* Build indentation string for a given tree depth.  The toplevel vectors {}
+ * block uses I1 base; each nesting level inside a node block adds one more
+ * I1.  Maximum of eight levels should cover any realistic tree. */
+static const char *
+_vg_node_indent(int depth)
+{
+   /* depth 0 = inside top-level vector{}: I2; depth 1 = I3, etc. */
+   static const char *table[] = { I2, I3, I4, I5, I6, I7, I7, I7 };
+   if (depth < 0) depth = 0;
+   if (depth > 7) depth = 7;
+   return table[depth];
+}
+
+static void
+_edje_vg_emit_xform(const Edje_Vg_Transform *x, Eina_Strbuf *buf, const char *indent)
+{
+   Eina_Bool ret = EINA_TRUE;
+   if (x->has_matrix)
+     {
+        BUF_APPENDF("%stransform.matrix: %.6g %.6g %.6g %.6g %.6g %.6g %.6g %.6g %.6g;\n",
+                    indent,
+                    x->m[0], x->m[1], x->m[2],
+                    x->m[3], x->m[4], x->m[5],
+                    x->m[6], x->m[7], x->m[8]);
+     }
+   else
+     {
+        if (x->tx != 0.0 || x->ty != 0.0)
+          BUF_APPENDF("%stransform.translate: %.6g %.6g;\n", indent, x->tx, x->ty);
+        if (x->angle != 0.0)
+          BUF_APPENDF("%stransform.rotate: %.6g;\n", indent, x->angle);
+        if (x->sx != 1.0 || x->sy != 1.0)
+          BUF_APPENDF("%stransform.scale: %.6g %.6g;\n", indent, x->sx, x->sy);
+     }
+   (void)ret;
+}
+
+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);
+
+   /* fill */
+   if (n->shape.fill.color_class || n->shape.fill.r || n->shape.fill.g ||
+       n->shape.fill.b || n->shape.fill.a)
+     {
+        if (n->shape.fill.color_class)
+          {
+             BUF_APPENDF("%sfill {\n", indent);
+             if (n->shape.fill.r || n->shape.fill.g || n->shape.fill.b || n->shape.fill.a)
+               BUF_APPENDF("%scolor: %d %d %d %d;\n", inner, n->shape.fill.r,
+                           n->shape.fill.g, n->shape.fill.b, n->shape.fill.a);
+             BUF_APPENDF("%scolor_class: \"%s\";\n", inner, n->shape.fill.color_class);
+             BUF_APPENDF("%s}\n", indent);
+          }
+        else
+          BUF_APPENDF("%sfill.color: %d %d %d %d;\n", indent,
+                      n->shape.fill.r, n->shape.fill.g, n->shape.fill.b, n->shape.fill.a);
+     }
+   if (n->shape.gradient_ref)
+     BUF_APPENDF("%sfill.gradient: \"%s\";\n", indent, n->shape.gradient_ref);
+
+   /* stroke */
+   {
+      Eina_Bool has_stroke = (n->shape.stroke_color.r || n->shape.stroke_color.g ||
+                              n->shape.stroke_color.b || n->shape.stroke_color.a ||
+                              n->shape.stroke_color.color_class ||
+                              n->shape.stroke_width != 0.0 ||
+                              n->shape.stroke_dash_count > 0 ||
+                              n->shape.stroke_cap != EFL_GFX_CAP_BUTT ||
+                              n->shape.stroke_join != EFL_GFX_JOIN_MITER);
+      if (has_stroke)
+        {
+           BUF_APPENDF("%sstroke {\n", indent);
+           if (n->shape.stroke_color.color_class)
+             {
+                if (n->shape.stroke_color.r || n->shape.stroke_color.g ||
+                    n->shape.stroke_color.b || n->shape.stroke_color.a)
+                  BUF_APPENDF("%scolor: %d %d %d %d;\n", inner,
+                              n->shape.stroke_color.r, n->shape.stroke_color.g,
+                              n->shape.stroke_color.b, n->shape.stroke_color.a);
+                BUF_APPENDF("%scolor_class: \"%s\";\n", inner, n->shape.stroke_color.color_class);
+             }
+           else if (n->shape.stroke_color.r || n->shape.stroke_color.g ||
+                    n->shape.stroke_color.b || n->shape.stroke_color.a)
+             BUF_APPENDF("%scolor: %d %d %d %d;\n", inner,
+                         n->shape.stroke_color.r, n->shape.stroke_color.g,
+                         n->shape.stroke_color.b, n->shape.stroke_color.a);
+           if (n->shape.stroke_width != 0.0)
+             BUF_APPENDF("%swidth: %.6g;\n", inner, n->shape.stroke_width);
+           if (n->shape.stroke_cap != EFL_GFX_CAP_BUTT)
+             BUF_APPENDF("%scap: %s;\n", inner, _vg_cap_str(n->shape.stroke_cap));
+           if (n->shape.stroke_join != EFL_GFX_JOIN_MITER)
+             BUF_APPENDF("%sjoin: %s;\n", inner, _vg_join_str(n->shape.stroke_join));
+           if (n->shape.stroke_dash_count > 0)
+             {
+                unsigned int di;
+                BUF_APPENDF("%sdash:", inner);
+                for (di = 0; di < n->shape.stroke_dash_count; di++)
+                  BUF_APPENDF(" %.6g", n->shape.stroke_dash[di]);
+                BUF_APPEND(";\n");
+             }
+           BUF_APPENDF("%s}\n", indent);
+        }
+   }
+
+   if (n->shape.fill_rule != EFL_GFX_FILL_RULE_WINDING)
+     BUF_APPENDF("%sfill_rule: %s;\n", indent, _vg_fill_rule_str(n->shape.fill_rule));
+   (void)ret;
+}
+
+static void
+_edje_vg_emit_gradient_node(const Edje_Vg_Node *n, Eina_Strbuf *buf,
+                             const char *indent, const char *inner)
+{
+   Eina_Bool ret = EINA_TRUE;
+   if (n->gradient.spread != EFL_GFX_GRADIENT_SPREAD_PAD)
+     BUF_APPENDF("%sspread: %s;\n", indent, _vg_spread_str(n->gradient.spread));
+   if (n->gradient.is_radial)
+     {
+        BUF_APPENDF("%scenter: %.6g %.6g;\n", indent, n->gradient.x0, n->gradient.y0);
+        BUF_APPENDF("%sfocal: %.6g %.6g;\n", indent, n->gradient.x1, n->gradient.y1);
+        BUF_APPENDF("%sradius: %.6g;\n", indent, n->gradient.radius);
+     }
+   else
+     {
+        BUF_APPENDF("%sstart: %.6g %.6g;\n", indent, n->gradient.x0, n->gradient.y0);
+        BUF_APPENDF("%send: %.6g %.6g;\n", indent, n->gradient.x1, n->gradient.y1);
+     }
+   if (n->gradient.stops && n->gradient.stops_count > 0)
+     {
+        unsigned int si;
+        for (si = 0; si < n->gradient.stops_count; si++)
+          {
+             const Edje_Vg_Stop *st = &n->gradient.stops[si];
+             BUF_APPENDF("%sstop {\n", indent);
+             BUF_APPENDF("%soffset: %.6g;\n", inner, st->offset);
+             if (st->color.color_class)
+               {
+                  BUF_APPENDF("%scolor: %d %d %d %d;\n", inner,
+                              st->color.r, st->color.g, st->color.b, st->color.a);
+                  BUF_APPENDF("%scolor_class: \"%s\";\n", inner, st->color.color_class);
+               }
+             else
+               BUF_APPENDF("%scolor: %d %d %d %d;\n", inner,
+                           st->color.r, st->color.g, st->color.b, st->color.a);
+             BUF_APPENDF("%s}\n", indent);
+          }
+     }
+   (void)ret;
+}
+
+/* Emit one node and, for containers, recurse into children.
+ * depth 0 = directly inside a vector{} root; depth 1 = inside a container. */
+static void
+_edje_vg_emit_node(const Edje_Vg_Node *n, Eina_Strbuf *buf, int depth)
+{
+   Eina_Bool ret = EINA_TRUE;
+   const char *indent = _vg_node_indent(depth);
+   const char *inner  = _vg_node_indent(depth + 1);
+   const char *kw;
+
+   switch (n->type)
+     {
+      case EDJE_VG_NODE_SHAPE:           kw = "shape";           break;
+      case EDJE_VG_NODE_CONTAINER:       kw = "container";       break;
+      case EDJE_VG_NODE_GRADIENT_LINEAR: kw = "gradient_linear"; break;
+      case EDJE_VG_NODE_GRADIENT_RADIAL: kw = "gradient_radial"; break;
+      default:                           kw = "shape";           break;
+     }
+
+   BUF_APPENDF("%s%s {\n", indent, kw);
+   if (n->name)
+     BUF_APPENDF("%sname: \"%s\";\n", inner, n->name);
+   if (!n->visible)
+     BUF_APPENDF("%svisible: 0;\n", inner);
+   if (n->color.r != 255 || n->color.g != 255 || n->color.b != 255 || n->color.a != 255 ||
+       n->color.color_class)
+     {
+        if (n->color.color_class)
+          {
+             BUF_APPENDF("%scolor: %d %d %d %d;\n", inner,
+                         n->color.r, n->color.g, n->color.b, n->color.a);
+             BUF_APPENDF("%scolor_class: \"%s\";\n", inner, n->color.color_class);
+          }
+        else
+          BUF_APPENDF("%scolor: %d %d %d %d;\n", inner,
+                      n->color.r, n->color.g, n->color.b, n->color.a);
+     }
+   _edje_vg_emit_xform(&n->xform, buf, inner);
+
+   if (n->type == EDJE_VG_NODE_SHAPE)
+     _edje_vg_emit_shape_node(n, buf, indent, inner);
+   else if (n->type == EDJE_VG_NODE_GRADIENT_LINEAR ||
+            n->type == EDJE_VG_NODE_GRADIENT_RADIAL)
+     _edje_vg_emit_gradient_node(n, buf, inner, _vg_node_indent(depth + 2));
+   else if (n->type == EDJE_VG_NODE_CONTAINER)
+     {
+        Eina_List *l;
+        Edje_Vg_Node *child;
+        EINA_LIST_FOREACH(n->container.children, l, child)
+          _edje_vg_emit_node(child, buf, depth + 1);
+     }
+
+   BUF_APPENDF("%s}\n", indent);
+   (void)ret;
+}
+
+/* Emit the full inline vector{} body for a tree (viewbox + root children). */
+static void
+_edje_vg_emit_tree_body(const Edje_Vg_Tree *t, Eina_Strbuf *buf, int child_depth)
+{
+   Eina_Bool ret = EINA_TRUE;
+   BUF_APPENDF("%sviewbox: %.6g %.6g %.6g %.6g;\n",
+               _vg_node_indent(child_depth - 1),
+               t->vbx, t->vby, t->vbw, t->vbh);
+   if (t->root)
+     {
+        Eina_List *l;
+        Edje_Vg_Node *child;
+        EINA_LIST_FOREACH(t->root->container.children, l, child)
+          _edje_vg_emit_node(child, buf, child_depth);
+     }
+   (void)ret;
+}
+
+/* Emit a single override block.  Sparse emit — only fields in field_mask. */
+static void
+_edje_vg_emit_override(const Edje_Vg_Override *ovr, Eina_Strbuf *buf,
+                        const char *indent, const char *inner)
+{
+   Eina_Bool ret = EINA_TRUE;
+   const char *kw;
+   switch (ovr->expected_type)
+     {
+      case EDJE_VG_NODE_SHAPE:           kw = "vector.shape";     break;
+      case EDJE_VG_NODE_CONTAINER:       kw = "vector.container"; break;
+      case EDJE_VG_NODE_GRADIENT_LINEAR:
+      case EDJE_VG_NODE_GRADIENT_RADIAL: kw = "vector.gradient";  break;
+      default:                           kw = "vector.shape";     break;
+     }
+
+   BUF_APPENDF("%s%s \"%s\" {\n", indent, kw, ovr->target_name);
+
+   const Edje_Vg_Node *p = &ovr->payload;
+
+   if (ovr->field_mask & EDJE_VG_OVR_VISIBLE)
+     BUF_APPENDF("%svisible: %d;\n", inner, p->visible);
+   if (ovr->field_mask & EDJE_VG_OVR_COLOR_RAW)
+     BUF_APPENDF("%scolor: %d %d %d %d;\n", inner,
+                 p->color.r, p->color.g, p->color.b, p->color.a);
+   if (ovr->field_mask & EDJE_VG_OVR_COLOR_CLASS)
+     BUF_APPENDF("%scolor_class: \"%s\";\n", inner,
+                 p->color.color_class ? p->color.color_class : "");
+   if (ovr->field_mask & EDJE_VG_OVR_TRANSFORM)
+     _edje_vg_emit_xform(&p->xform, buf, inner);
+
+   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 : "");
+        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);
+        if (ovr->field_mask & EDJE_VG_OVR_FILL_CLASS)
+          BUF_APPENDF("%sfill.color_class: \"%s\";\n", inner,
+                      p->shape.fill.color_class ? p->shape.fill.color_class : "");
+        if (ovr->field_mask & EDJE_VG_OVR_STROKE_RAW)
+          BUF_APPENDF("%sstroke.color: %d %d %d %d;\n", inner,
+                      p->shape.stroke_color.r, p->shape.stroke_color.g,
+                      p->shape.stroke_color.b, p->shape.stroke_color.a);
+        if (ovr->field_mask & EDJE_VG_OVR_STROKE_CLASS)
+          BUF_APPENDF("%sstroke.color_class: \"%s\";\n", inner,
+                      p->shape.stroke_color.color_class ? p->shape.stroke_color.color_class : "");
+        if (ovr->field_mask & EDJE_VG_OVR_STROKE_WIDTH)
+          BUF_APPENDF("%sstroke.width: %.6g;\n", inner, p->shape.stroke_width);
+        if (ovr->field_mask & EDJE_VG_OVR_STROKE_CAP)
+          BUF_APPENDF("%sstroke.cap: %s;\n", inner, _vg_cap_str(p->shape.stroke_cap));
+        if (ovr->field_mask & EDJE_VG_OVR_STROKE_JOIN)
+          BUF_APPENDF("%sstroke.join: %s;\n", inner, _vg_join_str(p->shape.stroke_join));
+        if ((ovr->field_mask & EDJE_VG_OVR_STROKE_DASH) &&
+            p->shape.stroke_dash && p->shape.stroke_dash_count > 0)
+          {
+             unsigned int di;
+             BUF_APPENDF("%sstroke.dash:", inner);
+             for (di = 0; di < p->shape.stroke_dash_count; di++)
+               BUF_APPENDF(" %.6g", p->shape.stroke_dash[di]);
+             BUF_APPEND(";\n");
+          }
+        if (ovr->field_mask & EDJE_VG_OVR_FILL_RULE)
+          BUF_APPENDF("%sfill_rule: %s;\n", inner, _vg_fill_rule_str(p->shape.fill_rule));
+     }
+   else if (ovr->expected_type == EDJE_VG_NODE_GRADIENT_LINEAR ||
+            ovr->expected_type == EDJE_VG_NODE_GRADIENT_RADIAL)
+     {
+        if (ovr->field_mask & EDJE_VG_OVR_GRAD_SPREAD)
+          BUF_APPENDF("%sspread: %s;\n", inner, _vg_spread_str(p->gradient.spread));
+        if (ovr->field_mask & EDJE_VG_OVR_GRAD_GEOM)
+          {
+             if (p->gradient.is_radial)
+               {
+                  BUF_APPENDF("%scenter: %.6g %.6g;\n", inner, p->gradient.x0, p->gradient.y0);
+                  BUF_APPENDF("%sfocal: %.6g %.6g;\n", inner, p->gradient.x1, p->gradient.y1);
+                  BUF_APPENDF("%sradius: %.6g;\n", inner, p->gradient.radius);
+               }
+             else
+               {
+                  BUF_APPENDF("%sstart: %.6g %.6g;\n", inner, p->gradient.x0, p->gradient.y0);
+                  BUF_APPENDF("%send: %.6g %.6g;\n", inner, p->gradient.x1, p->gradient.y1);
+               }
+          }
+        if ((ovr->field_mask & EDJE_VG_OVR_GRAD_STOPS) &&
+            p->gradient.stops && p->gradient.stops_count > 0)
+          {
+             unsigned int si;
+             /* Derive stop-content indent one level deeper than the stop{} opener
+              * (inner), matching the pattern in _edje_vg_emit_gradient_node.
+              * Each level is 3 spaces; I2 (6 chars) is depth 0. */
+             const char *stop_inner =
+                _vg_node_indent((int)(strlen(inner) - strlen(I2)) / 3 + 1);
+             for (si = 0; si < p->gradient.stops_count; si++)
+               {
+                  const Edje_Vg_Stop *st = &p->gradient.stops[si];
+                  BUF_APPENDF("%sstop {\n", inner);
+                  BUF_APPENDF("%soffset: %.6g;\n", stop_inner, st->offset);
+                  if (st->color.color_class)
+                    {
+                       BUF_APPENDF("%scolor: %d %d %d %d;\n", stop_inner,
+                                   st->color.r, st->color.g, st->color.b, st->color.a);
+                       BUF_APPENDF("%scolor_class: \"%s\";\n", stop_inner, st->color.color_class);
+                    }
+                  else
+                    BUF_APPENDF("%scolor: %d %d %d %d;\n", stop_inner,
+                                st->color.r, st->color.g, st->color.b, st->color.a);
+                  BUF_APPENDF("%s}\n", inner);
+               }
+          }
+     }
+
+   BUF_APPENDF("%s}\n", indent);
+   (void)ret;
+}
+
+/* Emit the top-level vectors {} block for all named trees in the file. */
+static void
+_edje_generate_source_of_vectors_block(const Edje_File *ef, Eina_Strbuf *buf)
+{
+   Eina_Bool ret = EINA_TRUE;
+
+   if (!ef->vector_dir || ef->vector_dir->trees_count == 0) return;
+
+   /* Check whether any tree has a name (only named trees go in vectors {}).
+    * Anonymous trees that are only referenced inline have no name. */
+   Eina_Bool any_named = EINA_FALSE;
+   unsigned int i;
+   for (i = 0; i < ef->vector_dir->trees_count; i++)
+     if (ef->vector_dir->trees[i].name) { any_named = EINA_TRUE; break; }
+   if (!any_named) return;
+
+   BUF_APPEND(I0 "vectors {\n");
+   for (i = 0; i < ef->vector_dir->trees_count; i++)
+     {
+        const Edje_Vg_Tree *t = &ef->vector_dir->trees[i];
+        /* sparse emit — unnamed trees belong to inline descriptions, not here */
+        if (!t->name) continue;
+        BUF_APPENDF(I1 "vector { name: \"%s\";\n", t->name);
+        /* child_depth = 1 → uses I2 for viewbox line, I2/I3 for nodes */
+        _edje_vg_emit_tree_body(t, buf, 1);
+        BUF_APPEND(I1 "}\n");
+     }
+   BUF_APPEND(I0 "}\n\n");
+   (void)ret;
+}
+
+/* Emit vector-specific content for a VECTOR part's description. */
+static void
+_edje_generate_source_state_vector(Edje *ed,
+                                   Edje_Part_Description_Common *pd,
+                                   Eina_Strbuf *buf)
+{
+   Eina_Bool ret = EINA_TRUE;
+   Edje_Part_Description_Vector *vd = (Edje_Part_Description_Vector *)pd;
+   const Eina_List *l;
+   const Edje_Vg_Override *ovr;
+
+   if (vd->vg.tree_id >= 0 &&
+       ed->file->vector_dir &&
+       (unsigned int)vd->vg.tree_id < ed->file->vector_dir->trees_count)
+     {
+        const Edje_Vg_Tree *t = &ed->file->vector_dir->trees[vd->vg.tree_id];
+        if (t->name)
+          {
+             /* Mode A: reference a named toplevel tree */
+             BUF_APPENDF(I5 "vector.use: \"%s\";\n", t->name);
+          }
+        else
+          {
+             /* Mode B anonymous: emit inline vector{} block */
+             BUF_APPEND(I5 "vector {\n");
+             _edje_vg_emit_tree_body(t, buf, 5);
+             BUF_APPEND(I5 "}\n");
+          }
+     }
+
+   /* Emit per-node overrides — sparse emit, only fields in field_mask. */
+   EINA_LIST_FOREACH(vd->vg.overrides, l, ovr)
+     _edje_vg_emit_override(ovr, buf, I5, I6);
+
+   (void)ret;
+}
+
+/* end VG source generation — Phase 8.1 */
+
 static void
 _edje_generate_source_state_image(Edje_Edit *eed, Evas_Object *obj,
                                   const char *part, const char *state, double value,
@@ -15475,6 +15944,10 @@ _edje_generate_source_of_state(Evas_Object *obj, const char *part, const char *s
    if (rp->part->type == EDJE_PART_TYPE_IMAGE)
      _edje_generate_source_state_image(eed, obj, part, state, value, pd, inherit_pd, buf) ;
 
+   //Vector
+   if (rp->part->type == EDJE_PART_TYPE_VECTOR)
+     _edje_generate_source_state_vector(ed, pd, buf);
+
    //Proxy
    if (rp->part->type == EDJE_PART_TYPE_PROXY)
      _edje_generate_source_state_proxy(ed, pd, inherit_pd, buf);
@@ -16215,6 +16688,9 @@ _edje_generate_source(Evas_Object *obj)
           }
      }
 
+   /* Vectors — emit named trees for edje_decc and full-source paths */
+   _edje_generate_source_of_vectors_block(ed->file, buf);
+
    /* Styles */
    if ((ll = edje_edit_styles_list_get(obj)))
      {
diff --git a/src/lib/edje/edje_private.h b/src/lib/edje/edje_private.h
index 8ffeffe941..f678bbd498 100644
--- a/src/lib/edje/edje_private.h
+++ b/src/lib/edje/edje_private.h
@@ -774,6 +774,7 @@ struct _Edje_Vg_Node {
 };
 
 struct _Edje_Vg_Tree {
+   const char   *name;                /* tree name in the toplevel vectors {} block; NULL for inline/anonymous */
    double        vbx, vby, vbw, vbh;  /* viewbox */
    Edje_Vg_Node *root;                /* always a CONTAINER */
 };
diff --git a/src/lib/edje/edje_vg_tree.c b/src/lib/edje/edje_vg_tree.c
index 370e0ea41b..26f7a09848 100644
--- a/src/lib/edje/edje_vg_tree.c
+++ b/src/lib/edje/edje_vg_tree.c
@@ -201,6 +201,7 @@ EAPI void
 _edje_vg_tree_free_fields(Edje_Vg_Tree *t)
 {
    if (!t) return;
+   if (t->name) { eina_stringshare_del(t->name); t->name = NULL; }
    _edje_vg_node_free(t->root);
    t->root = NULL;
 }
@@ -587,6 +588,7 @@ _edje_vg_tree_dup(const Edje_Vg_Tree *t)
    Edje_Vg_Tree *dst = calloc(1, sizeof(Edje_Vg_Tree));
    if (!dst) return NULL;
 
+   dst->name = eina_stringshare_ref(t->name);
    dst->vbx  = t->vbx;
    dst->vby  = t->vby;
    dst->vbw  = t->vbw;
diff --git a/src/tests/edje/edje_test_vector_states.c b/src/tests/edje/edje_test_vector_states.c
index 16696b2c84..2aa247f1f2 100644
--- a/src/tests/edje/edje_test_vector_states.c
+++ b/src/tests/edje/edje_test_vector_states.c
@@ -13,6 +13,9 @@
 #include <Ecore_Eo.h>
 #include <Ecore_Evas.h>
 #include "edje_suite.h"
+/* Phase 8.1: source-generation round-trip test needs the Edje_Edit API. */
+#define EDJE_EDIT_IS_UNSTABLE_AND_I_KNOW_ABOUT_IT
+#include <Edje_Edit.h>
 
 /*
  * Forward declarations for internal VG-tree helpers defined in
@@ -38,7 +41,7 @@ typedef enum {
  * A partial view struct is safe for accessing only those fields.
  * DO NOT access any other fields through this view. */
 struct _Edje_Vg_Node { const char *name; Edje_Vg_Node_Type type; /* opaque tail */ };
-struct _Edje_Vg_Tree { double vbx, vby, vbw, vbh; Edje_Vg_Node *root; };
+struct _Edje_Vg_Tree { const char *name; double vbx, vby, vbw, vbh; Edje_Vg_Node *root; };
 
 /* Phase 1 helpers */
 Edje_Vg_Tree        *_edje_vg_tree_new_for_test(void);
@@ -2416,6 +2419,108 @@ EFL_START_TEST(edje_vg_diag_vec004_werror_escalation)
 }
 EFL_END_TEST
 
+/*
+ * edje_vg_source_gen_round_trip  (Phase 8.1)
+ *
+ * Loads test_vector_states.edj via the Edje_Edit API, calls
+ * edje_edit_source_generate() on a VECTOR part group and verifies
+ * the emitted source contains:
+ *   - "vectors {"          — the top-level vectors {} block
+ *   - "vector.use:"        — Mode A reference in the default state
+ *   - "vector.shape"       — an override block in the hover state
+ *   - "color_class:"       — the vg_accent binding from tile_classed
+ *
+ * The stronger recompile-and-compare check is skipped here because
+ * we have no reliable in-tree edje_cc path from within the test
+ * binary (spawning an external process would require knowing the
+ * install prefix).  The structural string checks are sufficient to
+ * prove the emitter fires the correct code paths.
+ */
+EFL_START_TEST(edje_vg_source_gen_round_trip)
+{
+   const char *edj = TESTS_BUILD_DIR "/data/test_vector_states.edj";
+   Ecore_Evas *ee;
+   Evas *evas;
+   Evas_Object *edit_obj;
+   const char *src;
+
+   ee = ecore_evas_buffer_new(100, 100);
+   fail_if(!ee);
+   evas = ecore_evas_get(ee);
+   fail_if(!evas);
+
+   /* Load the hover group (uses tile_a + override on "body") via Edje_Edit.
+    * Any vector group works; this one exercises both vector.use and override. */
+   edit_obj = edje_edit_object_add(evas);
+   fail_if(!edit_obj);
+   fail_unless(edje_object_file_set(edit_obj, edj, "ui/box_a_hover"));
+
+   src = ""
+   fail_if(!src);
+
+   ck_assert_msg(strstr(src, "vectors {") != NULL,
+                 "Source must contain top-level 'vectors {' block");
+   ck_assert_msg(strstr(src, "vector.use:") != NULL,
+                 "Source must contain 'vector.use:' in default description");
+   ck_assert_msg(strstr(src, "vector.shape") != NULL,
+                 "Source must contain 'vector.shape' override block in hover description");
+
+   edje_edit_string_free(src);
+   evas_object_del(edit_obj);
+   ecore_evas_free(ee);
+
+   /* Second check: load the classed group and confirm color_class: is emitted. */
+   ee = ecore_evas_buffer_new(100, 100);
+   fail_if(!ee);
+   evas = ecore_evas_get(ee);
+   fail_if(!evas);
+
+   edit_obj = edje_edit_object_add(evas);
+   fail_if(!edit_obj);
+   fail_unless(edje_object_file_set(edit_obj, edj, "ui/box_classed"));
+
+   src = ""
+   fail_if(!src);
+
+   ck_assert_msg(strstr(src, "color_class:") != NULL,
+                 "Source for box_classed must contain 'color_class:' binding");
+
+   edje_edit_string_free(src);
+   evas_object_del(edit_obj);
+   ecore_evas_free(ee);
+
+   /* Third check: edje_edit_full_source_generate exercises _edje_generate_source
+    * (the path used by edje_decc and edje_edit_source_save).  Verify the same
+    * structural substrings are present — this would fail if the vectors block
+    * hook were missing from that pipeline. */
+   ee = ecore_evas_buffer_new(100, 100);
+   fail_if(!ee);
+   evas = ecore_evas_get(ee);
+   fail_if(!evas);
+
+   edit_obj = edje_edit_object_add(evas);
+   fail_if(!edit_obj);
+   fail_unless(edje_object_file_set(edit_obj, edj, "ui/box_a_hover"));
+
+   {
+      char *full_src = edje_edit_full_source_generate(edit_obj);
+      fail_if(!full_src);
+
+      ck_assert_msg(strstr(full_src, "vectors {") != NULL,
+                    "full_source_generate must contain top-level 'vectors {' block");
+      ck_assert_msg(strstr(full_src, "vector.use:") != NULL,
+                    "full_source_generate must contain 'vector.use:' in default description");
+      ck_assert_msg(strstr(full_src, "vector.shape") != NULL,
+                    "full_source_generate must contain 'vector.shape' override block");
+
+      free(full_src);
+   }
+
+   evas_object_del(edit_obj);
+   ecore_evas_free(ee);
+}
+EFL_END_TEST
+
 void
 edje_test_vector_states(TCase *tc)
 {
@@ -2469,4 +2574,6 @@ edje_test_vector_states(TCase *tc)
    tcase_add_test(tc, edje_vg_diag_vec004_topology_mismatch);
    tcase_add_test(tc, edje_vg_diag_vec004_no_false_positive);
    tcase_add_test(tc, edje_vg_diag_vec004_werror_escalation);
+   /* Phase 8.1 source-generation round-trip */
+   tcase_add_test(tc, edje_vg_source_gen_round_trip);
 }

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.

Reply via email to