mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-11 07:13:46 +00:00
Type: use pathops' number and length helpers; refuse a number with trailing text
text.setStyle now reads `size` and `tracking` with `number_given` and `length_given` from pathops.rs instead of its own copies. Both helpers read a string only when all of it is a number, or for `length_given` a length with a unit (`"5 mm"`). Before, `"20abc"` read as 20, so `text.setStyle` applied a 20 pt size and Offset Path a 10 pt offset for `"10abc"`. Such a string is now refused with "`size` must be a number, not \"20abc\"" and the document is left as it was.
This commit is contained in:
@@ -221,25 +221,41 @@ pub(crate) fn len_param(p: &Value, key: &str) -> Option<f64> {
|
||||
}
|
||||
}
|
||||
|
||||
/// The number `p` gives `cmd` for `key`, as [`num_param`] reads it, or `None` when `p` leaves it out
|
||||
/// or gives null. Any other value, such as `true` or `"wide"`, is an error: "`{key}` must be a
|
||||
/// number, not {v}".
|
||||
/// The number `p` gives `cmd` for `key`, or `None` when `p` leaves it out or gives null. A string
|
||||
/// counts only when all of it is a number (`"20"`). Any other value, such as `true`, `"wide"` or
|
||||
/// `"20abc"`, is an error: "`{key}` must be a number, not {v}".
|
||||
pub(crate) fn number_given(p: &Value, key: &str, cmd: &str) -> Result<Option<f64>> {
|
||||
match p.get(key) {
|
||||
None | Some(Value::Null) => Ok(None),
|
||||
Some(v) => num_param(p, key).map(Some).ok_or_else(|| bad(cmd, format!("`{key}` must be a number, not {v}"))),
|
||||
Some(v) => match v {
|
||||
Value::String(s) => whole_number(s),
|
||||
_ => num_param(p, key),
|
||||
}
|
||||
.map(Some)
|
||||
.ok_or_else(|| bad(cmd, format!("`{key}` must be a number, not {v}"))),
|
||||
}
|
||||
}
|
||||
|
||||
/// The distance `p` gives `cmd` for `key`, as [`len_param`] reads it (`"5 mm"` included), or `None`
|
||||
/// when `p` leaves it out or gives null. Any other value is an error, as [`number_given`] gives it.
|
||||
/// The distance `p` gives `cmd` for `key`, or `None` when `p` leaves it out or gives null. A string
|
||||
/// counts only when all of it is a length that [`vectorcraft_doc::Unit::parse`] reads (`"5 mm"`) or a
|
||||
/// number. Any other value, such as `"20abc"`, is an error, as [`number_given`] gives it.
|
||||
pub(crate) fn length_given(p: &Value, key: &str, cmd: &str) -> Result<Option<f64>> {
|
||||
match p.get(key) {
|
||||
None | Some(Value::Null) => Ok(None),
|
||||
Some(v) => len_param(p, key).map(Some).ok_or_else(|| bad(cmd, format!("`{key}` must be a number, not {v}"))),
|
||||
Some(v) => match v {
|
||||
Value::String(s) => vectorcraft_doc::Unit::Points.parse(s).filter(|v| v.is_finite()).or_else(|| whole_number(s)),
|
||||
_ => num_param(p, key),
|
||||
}
|
||||
.map(Some)
|
||||
.ok_or_else(|| bad(cmd, format!("`{key}` must be a number, not {v}"))),
|
||||
}
|
||||
}
|
||||
|
||||
/// `s` as a finite number when all of it, spaces around it aside, is one.
|
||||
fn whole_number(s: &str) -> Option<f64> {
|
||||
s.trim().parse::<f64>().ok().filter(|v| v.is_finite())
|
||||
}
|
||||
|
||||
fn ids_json(ids: &[NodeId]) -> Value {
|
||||
json!({ "ids": ids.iter().map(|i| i.0).collect::<Vec<_>>() })
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ use vectorcraft_doc::{AreaFit, Document, Justify, Node, NodeId, NodeKind, RubyAl
|
||||
use vectorcraft_geom::{Affine, PathData, Vec2};
|
||||
|
||||
use super::edit::selected_roots;
|
||||
use super::pathops::{len_param, num_param, shape_node};
|
||||
use super::pathops::{len_param, length_given, number_given, shape_node};
|
||||
use super::{
|
||||
CommandSpec, EngineError, Result, Session, bad, check_numbers, cmd, color_value, f64_req, has_doc, has_selection, id_param, index, number_or, ok,
|
||||
str_param, switch_or, targets, text_param,
|
||||
@@ -810,25 +810,6 @@ pub(crate) fn justify_param(v: &str) -> Option<Justify> {
|
||||
})
|
||||
}
|
||||
|
||||
/// The number `p` gives `cmd` for `key`, as [`num_param`] reads it (`"20"` included), or `None` when
|
||||
/// `p` leaves it out or gives null. Any other value, such as `true` or `"wide"`, is an error:
|
||||
/// "`{key}` must be a number, not {v}".
|
||||
fn number_given(p: &Value, key: &str, cmd: &str) -> Result<Option<f64>> {
|
||||
match p.get(key) {
|
||||
None | Some(Value::Null) => Ok(None),
|
||||
Some(v) => num_param(p, key).map(Some).ok_or_else(|| bad(cmd, format!("`{key}` must be a number, not {v}"))),
|
||||
}
|
||||
}
|
||||
|
||||
/// The distance `p` gives `cmd` for `key`, as [`len_param`] reads it (`"5 mm"` included), or `None`
|
||||
/// when `p` leaves it out or gives null. Any other value is an error, as [`number_given`] gives it.
|
||||
fn length_given(p: &Value, key: &str, cmd: &str) -> Result<Option<f64>> {
|
||||
match p.get(key) {
|
||||
None | Some(Value::Null) => Ok(None),
|
||||
Some(v) => len_param(p, key).map(Some).ok_or_else(|| bad(cmd, format!("`{key}` must be a number, not {v}"))),
|
||||
}
|
||||
}
|
||||
|
||||
fn set_style(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
const C: &str = "text.setStyle";
|
||||
let font = text_param(p, "font", C)?.map(str::to_string);
|
||||
|
||||
@@ -863,6 +863,8 @@ fn path_commands_refuse_a_param_of_the_wrong_type() {
|
||||
for (cmd, p, want) in [
|
||||
("object.path.offsetPath", json!({"offset": true}), number("offset", "true")),
|
||||
("object.path.offsetPath", json!({"offset": "wide"}), number("offset", "\"wide\"")),
|
||||
("object.path.offsetPath", json!({"offset": "10abc"}), number("offset", "\"10abc\"")),
|
||||
("object.path.offsetPath", json!({"offset": 5, "miterLimit": "4x"}), number("miterLimit", "\"4x\"")),
|
||||
("object.path.offsetPath", json!({"offset": 5, "joins": 3}), "`joins` must be a string".to_string()),
|
||||
("object.path.offsetPath", json!({"offset": 5, "joins": "pointy"}), "`joins` must be one of miter, round, bevel".to_string()),
|
||||
("object.path.offsetPath", json!({"offset": 5, "miterLimit": [4]}), number("miterLimit", "[4]")),
|
||||
|
||||
@@ -505,6 +505,8 @@ fn character_ruby_and_path_handle_refuse_a_param_of_the_wrong_type() {
|
||||
[
|
||||
("text.setStyle", json!({"size": true}), "size"),
|
||||
("text.setStyle", json!({"size": "big"}), "size"),
|
||||
("text.setStyle", json!({"size": "20abc"}), "size"),
|
||||
("text.setStyle", json!({"tracking": "20abc"}), "tracking"),
|
||||
("text.setStyle", json!({"font": "Inter", "size": [12]}), "size"),
|
||||
("text.setStyle", json!({"tracking": "wide"}), "tracking"),
|
||||
("text.setStyle", json!({"size": 30, "tracking": false}), "tracking"),
|
||||
|
||||
Reference in New Issue
Block a user