Transform commands: a string for an optional number is an error (#1952)

object.setBounds read x, y, width and height with f64_or, which falls back
to the current value for anything that isn't a JSON number, so
{"width": "60 mm"} returned ok and changed nothing. object.move,
object.nudge, object.rotate, object.scale (sy) and object.shear did the
same with their optional numbers. A value that is neither a number nor
null is now a BadParams error naming the key, as a missing required
number already is.
This commit is contained in:
Gert Pellin
2026-10-16 02:56:35 +07:00
committed by bflatastic
parent a0f8a88e82
commit e6170de1a2
3 changed files with 53 additions and 0 deletions
+11
View File
@@ -308,6 +308,17 @@ pub(crate) fn f64_or(p: &Value, key: &str, default: f64) -> f64 {
pub(crate) fn f64_req(p: &Value, key: &str, cmd: &str) -> Result<f64> {
p.get(key).and_then(Value::as_f64).ok_or_else(|| bad(cmd, format!("missing number `{key}`")))
}
/// Error unless each of `keys` that `p` gives is a number (or null): an optional number read with
/// [`f64_or`] would otherwise fall back to its default for a string such as `"60 mm"` (#1952).
pub(crate) fn check_numbers(p: &Value, keys: &[&str], cmd: &str) -> Result<()> {
for key in keys {
match p.get(*key) {
None | Some(Value::Null) | Some(Value::Number(_)) => {}
Some(v) => return Err(bad(cmd, format!("`{key}` must be a number, not {v}"))),
}
}
Ok(())
}
pub(crate) fn bool_or(p: &Value, key: &str, default: bool) -> bool {
p.get(key).and_then(Value::as_bool).unwrap_or(default)
}
+6
View File
@@ -396,11 +396,13 @@ fn transform(s: &mut Session, p: &Value) -> Result<Value> {
}
fn move_cmd(s: &mut Session, p: &Value) -> Result<Value> {
check_numbers(p, &["dx", "dy"], "object.move")?;
let ids = selected_roots(s)?;
apply_transform(s, "Move", ids, Affine::translate((f64_or(p, "dx", 0.0), f64_or(p, "dy", 0.0))), p, true)
}
fn nudge(s: &mut Session, p: &Value) -> Result<Value> {
check_numbers(p, &["dx", "dy"], "object.nudge")?;
let k = s.prefs.keyboard_increment * if bool_or(p, "big", false) { 10.0 } else { 1.0 };
let dx = f64_or(p, "dx", 0.0) * k;
let dy = f64_or(p, "dy", 0.0) * k;
@@ -428,6 +430,7 @@ fn about(o: Point, a: Affine) -> Affine {
}
fn rotate(s: &mut Session, p: &Value) -> Result<Value> {
check_numbers(p, &["angle"], "object.rotate")?;
let ids = selected_roots(s)?;
let o = origin_of(s, p, &ids)?;
let mut angle = f64_or(p, "angle", 0.0);
@@ -440,6 +443,7 @@ fn rotate(s: &mut Session, p: &Value) -> Result<Value> {
}
fn scale(s: &mut Session, p: &Value) -> Result<Value> {
check_numbers(p, &["sy"], "object.scale")?;
let ids = selected_roots(s)?;
let o = origin_of(s, p, &ids)?;
let sx = f64_req(p, "sx", "object.scale")? / 100.0;
@@ -470,6 +474,7 @@ fn reflect(s: &mut Session, p: &Value) -> Result<Value> {
}
fn shear(s: &mut Session, p: &Value) -> Result<Value> {
check_numbers(p, &["angle"], "object.shear")?;
let ids = selected_roots(s)?;
let o = origin_of(s, p, &ids)?;
let t = f64_or(p, "angle", 0.0).clamp(-89.0, 89.0).to_radians().tan();
@@ -1436,6 +1441,7 @@ fn distribute_spacing(s: &mut Session, p: &Value) -> Result<Value> {
}
fn set_bounds(s: &mut Session, p: &Value) -> Result<Value> {
check_numbers(p, &["x", "y", "width", "height"], "object.setBounds")?;
let ids = selected_roots(s)?;
let st = s.doc()?;
if transforms_objects(p)