fix(distort): validate the full corner quad, source bounds and targets
This commit is contained in:
parent
57050b0987
commit
0d697ed79d
|
|
@ -132,30 +132,48 @@ fn warp_node(n: &mut Node, pr: &Projective) {
|
|||
}
|
||||
|
||||
fn distort(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let corners: Vec<Point> = p
|
||||
.get("corners")
|
||||
.and_then(Value::as_array)
|
||||
.map(|a| a.iter().filter_map(|c| Some(Point::new(c.get(0)?.as_f64()?, c.get(1)?.as_f64()?))).collect())
|
||||
.unwrap_or_default();
|
||||
if corners.len() != 4 {
|
||||
return Err(bad("object.distort", "corners must be 4 [x,y] points (TL, TR, BR, BL)"));
|
||||
}
|
||||
let ids = match ids_param(p, "ids") {
|
||||
Some(v) => v,
|
||||
None => selected_roots(s)?,
|
||||
};
|
||||
let src = match p.get("from").and_then(Value::as_array) {
|
||||
Some(a) if a.len() == 4 => {
|
||||
let f: Vec<f64> = a.iter().filter_map(Value::as_f64).collect();
|
||||
if f.len() != 4 {
|
||||
return Err(bad("object.distort", "from must be [x0,y0,x1,y1]"));
|
||||
const C: &str = "object.distort";
|
||||
let invalid_corners = || bad(C, "corners must be exactly 4 finite [x,y] points (TL, TR, BR, BL)");
|
||||
let points = p.get("corners").and_then(Value::as_array).filter(|a| a.len() == 4).ok_or_else(invalid_corners)?;
|
||||
let corners: Vec<Point> = points
|
||||
.iter()
|
||||
.map(|v| {
|
||||
let Some([x, y]) = v.as_array().map(Vec::as_slice) else {
|
||||
return Err(invalid_corners());
|
||||
};
|
||||
let (Some(x), Some(y)) = (x.as_f64(), y.as_f64()) else {
|
||||
return Err(invalid_corners());
|
||||
};
|
||||
if !x.is_finite() || !y.is_finite() {
|
||||
return Err(invalid_corners());
|
||||
}
|
||||
Rect::new(f[0], f[1], f[2], f[3])
|
||||
}
|
||||
_ => s.doc()?.doc.bounds_of(&ids, false).ok_or_else(|| EngineError::Other("nothing to distort".into()))?,
|
||||
Ok(Point::new(x, y))
|
||||
})
|
||||
.collect::<Result<_>>()?;
|
||||
let ids = if p.get("ids").is_some_and(|v| !v.is_null()) {
|
||||
checked_ids_param(s, p, "ids", C)?
|
||||
} else {
|
||||
selected_roots(s)?
|
||||
};
|
||||
if src.width().abs() < 1e-9 || src.height().abs() < 1e-9 {
|
||||
return Err(EngineError::Other("cannot distort a zero-size bounding box".into()));
|
||||
if ids.is_empty() {
|
||||
return Err(bad(C, "nothing to distort"));
|
||||
}
|
||||
let src = match p.get("from") {
|
||||
None | Some(Value::Null) => s.doc()?.doc.bounds_of(&ids, false).ok_or_else(|| bad(C, "nothing to distort"))?,
|
||||
Some(v) => {
|
||||
let Some([x0, y0, x1, y1]) = v.as_array().map(Vec::as_slice) else {
|
||||
return Err(bad(C, "from must be exactly 4 finite numbers [x0,y0,x1,y1]"));
|
||||
};
|
||||
let coordinate = |v: &Value| {
|
||||
v.as_f64()
|
||||
.filter(|n| n.is_finite())
|
||||
.ok_or_else(|| bad(C, "from must be exactly 4 finite numbers [x0,y0,x1,y1]"))
|
||||
};
|
||||
Rect::new(coordinate(x0)?, coordinate(y0)?, coordinate(x1)?, coordinate(y1)?)
|
||||
}
|
||||
};
|
||||
if !src.width().is_finite() || !src.height().is_finite() || src.width().abs() < 1e-9 || src.height().abs() < 1e-9 {
|
||||
return Err(bad(C, "cannot distort a zero-size or non-finite bounding box"));
|
||||
}
|
||||
let pr = Projective::from_rect(src, [corners[0], corners[1], corners[2], corners[3]]);
|
||||
s.edit("Free Distort", |d, _| {
|
||||
|
|
|
|||
|
|
@ -572,3 +572,27 @@ fn copy_from_requires_source() {
|
|||
assert!(s.execute("appearance.copyFrom", &json!({})).is_err());
|
||||
assert!(s.execute("appearance.copyFrom", &json!({"source": 9999})).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn distort_rejects_malformed_coordinates_and_ids_without_editing_art() {
|
||||
let mut s = session();
|
||||
let a = rect(&mut s, 0.0, 0.0, 100.0, 100.0);
|
||||
let original = bounds(&s, a);
|
||||
let history = undo_len(&s);
|
||||
let corners = json!([[10, 0], [90, 0], [100, 100], [0, 100]]);
|
||||
for p in [
|
||||
json!({"corners": [[10, 0, 50], [90, 0], [100, 100], [0, 100]]}),
|
||||
json!({"corners": [[10, 0], [90, 0], ["bad", 100], [0, 100]]}),
|
||||
json!({"corners": [[10, 0], [90, 0], [100, 100], [0, 100], ["bad"]]}),
|
||||
json!({"corners": corners, "from": [0, 0, 100]}),
|
||||
json!({"corners": corners, "from": "not bounds"}),
|
||||
json!({"corners": corners, "ids": [a.0, "bad"]}),
|
||||
json!({"corners": corners, "ids": [a.0, u64::MAX]}),
|
||||
] {
|
||||
assert!(s.execute("object.distort", &p).is_err(), "invalid input: {p}");
|
||||
assert!(close(bounds(&s, a), original));
|
||||
assert_eq!(undo_len(&s), history);
|
||||
}
|
||||
s.execute("object.distort", &json!({"corners": corners, "from": [0, 0, 100, 100], "ids": [a.0]})).unwrap();
|
||||
assert_eq!(undo_len(&s), history + 1);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue