Merge pull request #1731 from zral/docs/vector-path-xy-offset

M0.9: document that x/y offset a vector path by its own box
This commit is contained in:
BFlat authored and GitHub committed 2026-10-31 09:14:25 +07:00
commit 3b77c236af
3 files changed
+30 -5

No files matched your search

+2 -2
View File
@@ -59,7 +59,7 @@ pub fn specs() -> Vec<CommandSpec> {
spec!(
"node.create",
"Create layer",
"{type: rectangle|ellipse|polygon|star|line|arrow|vector|text|frame|section|component|slice, x?, y? (relative to the parent frame; groups don't count), width?, height?, parent? (id; default: the page), index?, name?, text? | characters? (for text), path? | points? (vector: SVG path data | [[x,y],..]), closed? (vector), props? {node.set keys}, select? (default true)}",
"{type: rectangle|ellipse|polygon|star|line|arrow|vector|text|frame|section|component|slice, x?, y? (relative to the parent frame; groups don't count), width?, height?, parent? (id; default: the page), index?, name?, text? | characters? (for text), path? | points? (vector: SVG path data | [[x,y],..]; x/y move their own origin, so the layer's left/top is x/y plus their box's unscaled offset), closed? (vector), props? {node.set keys}, select? (default true)}",
true,
create
),
@@ -106,7 +106,7 @@ pub fn specs() -> Vec<CommandSpec> {
spec!(
"vector.create",
"Vector",
"{path (SVG path data) | points [[x,y],..], closed?, x?, y?, width?, height? (scale the path; one keeps its proportion), parent?, index?, name?, props?, select? (default true)}",
"{path (SVG path data) | points [[x,y],..], closed?, x?, y? (move the path's own origin: the layer's left/top is x/y plus the box's unscaled offset), width?, height? (scale the path; one keeps its proportion), parent?, index?, name?, props?, select? (default true)}",
true,
|s, p| create_kind(s, p, "vector")
),
+3 -3
View File
@@ -187,15 +187,15 @@ fn tools() -> Vec<Value> {
obj(
json!({
"type": {"type": "string", "enum": ["frame", "rectangle", "ellipse", "polygon", "star", "line", "arrow", "text", "vector", "section", "component", "slice"], "description": "Layer type"},
"x": num("Left, relative to the parent frame (default 0)"),
"y": num("Top, relative to the parent frame (default 0)"),
"x": num("Left, relative to the parent frame (default 0). For a vector with `path` or `points`, x/y move their own origin instead: data whose box starts at (30, 0) makes a layer whose left is x + 30. Start the path at 0,0 to place its left edge at x, or set x afterwards with set_properties (which moves the left edge)"),
"y": num("Top, relative to the parent frame (default 0). For a vector with `path` or `points`, their own top is added, as for x"),
"width": num("Width (default 100; text hugs its content unless width is set, which makes it wrap; for a vector with path, width/height scale the path, one alone keeping its proportion, and without them the path's own box is used)"),
"height": num("Height (default 100; ignored for lines/arrows and auto-height text)"),
"parent": id_schema("Parent frame/group/component id (default: the current page)"),
"index": int("Z-index among the parent's children (default: on top)"),
"name": string("Layer name"),
"text": string("Characters, for type text"),
"path": string("SVG path data, for type vector (e.g. \"M0 0 L100 0 L50 80 Z\")"),
"path": string("SVG path data, for type vector (e.g. \"M0 0 L100 0 L50 80 Z\"); its coordinates are kept relative to x/y, so a path that doesn't start at 0,0 is offset by its own box"),
"points": {"type": "array", "items": {"type": "array", "items": {"type": "number"}}, "description": "For type vector, instead of path: the points [[x, y], ...]"},
"closed": boolean("For type vector: close the path"),
"select": boolean("Select the new layer (default true)"),
+25
View File
@@ -325,3 +325,28 @@ fn set_properties_passes_on_what_it_skipped() {
assert!(c.ok("set_properties", json!({"ids": [f], "props": {"gap": 4}})).get("warnings").is_none());
assert!(c.err("set_properties", json!({"ids": [t], "props": {"gap": 4}})).contains("auto layout"));
}
#[test]
fn create_node_x_y_offset_a_vector_path_by_its_own_box_as_documented() {
// An agent placed `M95 0 L225 0 ...` (box starting at x 30) with x 830 expecting its left at
// 830: x/y move the path's own origin, so the layer lands at 860. The schema has to say so.
let mut c = Client::new();
let lid = "M95 0 L225 0 L290 130 L232 470 L88 470 L30 130 Z";
let offset = c.create(json!({"type": "vector", "path": lid, "x": 830, "y": 445, "width": 260, "height": 470}));
let n = c.node(&offset);
assert_eq!((n["x"].as_f64(), n["y"].as_f64(), n["width"].as_f64()), (Some(860.0), Some(445.0), Some(260.0)), "{n}");
// The same shape drawn from 0,0 lands at x/y.
let at_origin = c.create(json!({"type": "vector", "path": "M65 0 L195 0 L260 130 L202 470 L58 470 L0 130 Z", "x": 830, "y": 445}));
assert_eq!(c.node(&at_origin)["x"].as_f64(), Some(830.0));
// Points are placed the same way.
let points = c.create(json!({"type": "vector", "points": [[30, 0], [290, 130], [88, 470]], "closed": true, "x": 830, "y": 445}));
assert_eq!(c.node(&points)["x"].as_f64(), Some(860.0));
// set_properties x moves the left edge.
c.ok("set_properties", json!({"ids": [offset], "props": {"x": 830}}));
assert_eq!(c.node(&offset)["x"].as_f64(), Some(830.0));
let r = c.rpc("tools/list", json!({}));
let tool = r["result"]["tools"].as_array().unwrap().iter().find(|t| t["name"] == "create_node").unwrap().clone();
let x_doc = tool["inputSchema"]["properties"]["x"]["description"].as_str().unwrap();
assert!(x_doc.contains("path") && x_doc.contains("x + 30"), "{x_doc}");
assert!(tool["inputSchema"]["properties"]["path"]["description"].as_str().unwrap().contains("offset by its own box"));
}