mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-28 03:37:00 -06:00
Commands: path.create with path data refuses closed given as text, and the grids refuse a count
path.create read closed only when it drew from anchors. Given path
data, {"d": "M0 0 L10 0 L10 10", "closed": "true"} drew the path and
recorded an undo step, while the same closed with anchors fails with
"`closed` must be true or false". shape.rectangularGrid and
shape.polarGrid read rows, columns, concentric and radial given as text
as the count left out, so {"rows": "3"} drew the default 5 horizontal
dividers instead of 3.
path.create now checks closed before it reads d or anchors, and the
grid commands refuse such a count with "`rows` must be a number, not
"3"". Neither command changes the document or its undo history then.
With d, a closed of true, false or null still leaves the path as its
path data draws it.
Docs: docs/mcp.md, docs/target-app-parity.md.
This commit is contained in:
@@ -428,9 +428,11 @@ fn grid_group(s: &mut Session, label: &str, paths: Vec<PathData>, fill: bool) ->
|
||||
Ok(json!({ "id": id.0 }))
|
||||
}
|
||||
|
||||
/// A grid's divider count `count` with its skew `key` in percent (0 when absent, clamped to
|
||||
/// ±[`shapes::GRID_SKEW_MAX`]).
|
||||
/// A grid's divider count `count` (an error unless it is a number or null; 5 unless it is a JSON
|
||||
/// integer of 0 or more, so 4.0 and 2.5 give 5; at most 999) with its skew `key` in percent (0 when
|
||||
/// absent, clamped to ±[`shapes::GRID_SKEW_MAX`]).
|
||||
fn dividers(p: &Value, count: &str, key: &str, cmd: &str) -> Result<(u32, f64)> {
|
||||
check_numbers(p, &[count], cmd)?;
|
||||
let n = p.get(count).and_then(Value::as_u64).unwrap_or(5).min(999) as u32;
|
||||
let skew = match p.get(key) {
|
||||
None | Some(Value::Null) => 0.0,
|
||||
@@ -488,6 +490,8 @@ pub(crate) fn anchor_from_json(v: &Value, cmd: &str) -> Result<Anchor> {
|
||||
}
|
||||
|
||||
fn path_create(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
// Checked with `d` too, where the path data alone decides whether the path is closed.
|
||||
let closed = switch_or(p, "closed", false, "path.create")?;
|
||||
let path = if let Some(d) = str_param(p, "d") {
|
||||
let bp = vectorcraft_geom::BezPath::from_svg(d).map_err(|e| bad("path.create", format!("bad path data: {e}")))?;
|
||||
PathData::from_bezpath(&bp)
|
||||
@@ -502,7 +506,7 @@ fn path_create(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
if anchors.is_empty() {
|
||||
return Err(bad("path.create", "need at least one anchor"));
|
||||
}
|
||||
PathData::single(SubPath::new(anchors, switch_or(p, "closed", false, "path.create")?))
|
||||
PathData::single(SubPath::new(anchors, closed))
|
||||
};
|
||||
let mut look = s.new_art_look(s.paint.fill.clone(), s.paint.stroke.clone(), s.paint.stroke_width);
|
||||
// Open paths drawn with a stroke of None would be invisible: mimic Illustrator and keep what the user set.
|
||||
|
||||
@@ -1257,6 +1257,23 @@ fn shape_commands_refuse_a_number_or_boolean_given_as_text() {
|
||||
s.execute("shape.arc", &json!({"x1": 0, "y1": 0, "x2": 50, "y2": 50, "closed": false})).unwrap();
|
||||
}
|
||||
|
||||
/// `path.create` given path data (`d`) refuses a `closed` other than true, false or null, as it does
|
||||
/// with `anchors`, and draws nothing; with true, false or null it draws the path data, which alone
|
||||
/// decides whether the path is closed.
|
||||
#[test]
|
||||
fn path_create_with_path_data_refuses_closed_given_as_text() {
|
||||
let mut s = session();
|
||||
let d = "M0 0 L10 0 L10 10";
|
||||
let calls = [json!("true"), json!(1), json!([true])].map(|v| not_a_switch("path.create", json!({"d": d, "closed": v}), "closed"));
|
||||
let wrong = not_refused(&mut s, &calls);
|
||||
assert!(wrong.is_empty(), "not refused: {wrong:#?}");
|
||||
for closed in [json!(true), json!(false), json!(null)] {
|
||||
let id = NodeId(s.execute("path.create", &json!({"d": d, "closed": closed})).unwrap()["id"].as_u64().unwrap());
|
||||
let NodeKind::Path { path, .. } = &s.doc().unwrap().doc.node(id).unwrap().kind else { panic!("a path") };
|
||||
assert_eq!((path.subpaths.len(), path.anchor_count(), path.is_closed()), (1, 3, false), "closed: {closed}");
|
||||
}
|
||||
}
|
||||
|
||||
/// #1824: with anchors selected with Direct Selection, the Transform panel's X/Y/W/H
|
||||
/// (`object.setBounds`) are the box around those anchors, and only they move or scale.
|
||||
#[test]
|
||||
|
||||
@@ -818,13 +818,17 @@ fn polar_grid_options_compound_fill_and_skew() {
|
||||
assert!(tips(&inward)[1..].iter().all(|t| t.x < 50.0), "counterclockwise of the top: {:?}", tips(&inward));
|
||||
}
|
||||
|
||||
/// The grid options are checked: a skew is a number and a switch true or false, or the command
|
||||
/// fails without an undo step.
|
||||
/// The grid options are checked: a divider count or a skew is a number and a switch true or false,
|
||||
/// or the command fails without an undo step.
|
||||
#[test]
|
||||
fn grid_commands_reject_options_of_the_wrong_type() {
|
||||
let mut s = session();
|
||||
let size = json!({"x": 0, "y": 0, "width": 10, "height": 10});
|
||||
for (cmd, key, value) in [
|
||||
("shape.rectangularGrid", "rows", json!("3")),
|
||||
("shape.rectangularGrid", "columns", json!([4])),
|
||||
("shape.polarGrid", "concentric", json!(true)),
|
||||
("shape.polarGrid", "radial", json!("6")),
|
||||
("shape.rectangularGrid", "rowSkew", json!("50")),
|
||||
("shape.rectangularGrid", "columnSkew", json!(true)),
|
||||
("shape.rectangularGrid", "frame", json!(1)),
|
||||
|
||||
Reference in New Issue
Block a user