mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-10 06:31:19 +00:00
Layers: a layer or gap color with an alpha part is refused instead of losing its alpha
layer.setProps, layer.new, layer.newSublayer and livePaint.gapOptions read an 8-digit hex color such as #ff000080 as #ff0000 and set it without its alpha. They now refuse it with the error a bad color already gets, and change nothing. #rgba was already refused. #rrggbb, #rgb, a preset index and a preset name are read as before.
This commit is contained in:
@@ -108,7 +108,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"Gap Options",
|
||||
[],
|
||||
None,
|
||||
"{id?, ids?: [id…] (instead of id; default: the selected Live Paint groups), detect?: bool (Gap Detection: paint stops at gaps up to size wide), size?: \"small\" (3 pt) | \"medium\" (6 pt) | \"large\" (12 pt) | pt (custom, over 0 up to 72) (Paint Stops At), color?: #rrggbb or a layer color's name such as \"Red\" (Gap Preview Color), closeGaps?: bool (add an unpainted path across each gap up to size wide to the group's paths)} set the Gap Options of Live Paint groups (what is not given stays as it was; a group starts with detect false, small and Red) and make their faces again, keeping their paint → {ids, faces, gaps: [[x0,y0,x1,y1]…] (the gaps up to size wide that no path closes, from a loose end of a path to where it closes to; detect closes them for paint), closed (paths added)}",
|
||||
"{id?, ids?: [id…] (instead of id; default: the selected Live Paint groups), detect?: bool (Gap Detection: paint stops at gaps up to size wide), size?: \"small\" (3 pt) | \"medium\" (6 pt) | \"large\" (12 pt) | pt (custom, over 0 up to 72) (Paint Stops At), color?: #rrggbb or a layer color's name such as \"Red\" (Gap Preview Color; a hex color with an alpha part, #rrggbbaa or #rgba, is an error), closeGaps?: bool (add an unpainted path across each gap up to size wide to the group's paths)} set the Gap Options of Live Paint groups (what is not given stays as it was; a group starts with detect false, small and Red) and make their faces again, keeping their paint → {ids, faces, gaps: [[x0,y0,x1,y1]…] (the gaps up to size wide that no path closes, from a loose end of a path to where it closes to; detect closes them for paint), closed (paths added)}",
|
||||
has_selection_or_ids,
|
||||
lp_gap_options
|
||||
),
|
||||
|
||||
@@ -83,7 +83,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"Layer Options…",
|
||||
["Window", "Layers"],
|
||||
None,
|
||||
"{ids?|id? (default: the highlighted rows, else the current layer), name? (with prefs.identifyObjectsBy xmlId: a valid XML ID, stored as the object name), visible?, locked?, template?, printable?, preview?, dimImages?: 0..100|false, color?: index 0..26|\"#rrggbb\"|preset name} Layer Options; rows that are not layers take name, visible and locked. Template on also locks the layer and dims its images to 50% (unless given); off unlocks it and stops dimming. One undo step",
|
||||
"{ids?|id? (default: the highlighted rows, else the current layer), name? (with prefs.identifyObjectsBy xmlId: a valid XML ID, stored as the object name), visible?, locked?, template?, printable?, preview?, dimImages?: 0..100|false, color?: index 0..26|\"#rrggbb\"|preset name (a hex color with an alpha part, #rrggbbaa or #rgba, is an error)} Layer Options; rows that are not layers take name, visible and locked. Template on also locks the layer and dims its images to 50% (unless given); off unlocks it and stops dimming. One undo step",
|
||||
has_doc,
|
||||
set_props
|
||||
),
|
||||
@@ -297,7 +297,8 @@ pub(crate) fn make_layer(d: &mut Document, name: Option<&str>) -> Node {
|
||||
NewLayers::of(d).make(d, name)
|
||||
}
|
||||
|
||||
/// A layer colour: a preset index (0–26), `#rrggbb` or a preset's name (any case).
|
||||
/// A layer colour: a preset index (0–26), `#rrggbb` or a preset's name (any case). A hex color
|
||||
/// with an alpha part (`#rrggbbaa` or `#rgba`) is an error.
|
||||
pub(super) fn layer_color_param(v: &Value) -> std::result::Result<LayerColor, String> {
|
||||
match v {
|
||||
Value::Number(n) => match n.as_u64() {
|
||||
@@ -308,7 +309,13 @@ pub(super) fn layer_color_param(v: &Value) -> std::result::Result<LayerColor, St
|
||||
if let Some(i) = LAYER_COLORS.iter().position(|(name, _)| name.eq_ignore_ascii_case(s.trim())) {
|
||||
return Ok(LayerColor::Preset(i as u8));
|
||||
}
|
||||
let c = vectorcraft_color::Color::from_hex(s).ok_or_else(|| format!("`{s}` is not a colour (#rrggbb or a preset name)"))?;
|
||||
let refused = || format!("`{s}` is not a colour (#rrggbb or a preset name)");
|
||||
// `Color::from_hex` reads `#rrggbbaa` as `#rrggbb`, dropping the alpha.
|
||||
let digits = s.trim();
|
||||
if digits.strip_prefix('#').unwrap_or(digits).len() == 8 {
|
||||
return Err(refused());
|
||||
}
|
||||
let c = vectorcraft_color::Color::from_hex(s).ok_or_else(refused)?;
|
||||
let [r, g, b, _] = c.to_rgba8(1.0);
|
||||
Ok(LayerColor::Custom([r, g, b]))
|
||||
}
|
||||
|
||||
@@ -2244,6 +2244,25 @@ fn live_paint_gap_detection_stops_paint_at_a_gap() {
|
||||
assert!(node(&s, g).extras().live_paint_gaps.is_none());
|
||||
}
|
||||
|
||||
/// A Gap Preview Color with an alpha part (`#rrggbbaa` or `#rgba`) is refused and changes nothing;
|
||||
/// `#rrggbb` and a layer color's name still set it.
|
||||
#[test]
|
||||
fn a_gap_color_with_an_alpha_part_is_refused() {
|
||||
let mut s = session();
|
||||
let g = live_square_with_a_gap(&mut s);
|
||||
let depth = s.doc().unwrap().history.undo.len();
|
||||
for bad in ["#00ff0080", "00ff0080", "#0f08"] {
|
||||
let err = s.execute("livePaint.gapOptions", &json!({"id": g.0, "color": bad})).unwrap_err().to_string();
|
||||
assert!(err.contains(&format!("`{bad}` is not a colour (#rrggbb or a preset name)")), "{bad}: {err}");
|
||||
}
|
||||
assert_eq!(s.doc().unwrap().history.undo.len(), depth);
|
||||
assert_eq!(gap_options(&s, g).color, [0xff, 0x4f, 0x4f]);
|
||||
s.execute("livePaint.gapOptions", &json!({"id": g.0, "color": "#00ff00"})).unwrap();
|
||||
assert_eq!(gap_options(&s, g).color, [0, 0xff, 0]);
|
||||
s.execute("livePaint.gapOptions", &json!({"id": g.0, "color": "Green"})).unwrap();
|
||||
assert_eq!(gap_options(&s, g).color, [0x4f, 0xff, 0x4f]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn medium_and_large_gaps_stop_paint_at_wider_gaps() {
|
||||
// Squares whose ends stop 5 pt and 9 pt short of their starts: each size closes the gaps up
|
||||
|
||||
@@ -375,6 +375,32 @@ fn layer_options_validate_and_template_locks_and_dims() {
|
||||
assert_eq!((summary["preview"].clone(), summary["dimImages"].clone()), (json!(false), json!(30)));
|
||||
}
|
||||
|
||||
/// A layer color with an alpha part (`#rrggbbaa` or `#rgba`) is refused by Layer Options, New
|
||||
/// Layer and New Sublayer and changes nothing; `#rrggbb` and a preset name still set it.
|
||||
#[test]
|
||||
fn a_layer_color_with_an_alpha_part_is_refused() {
|
||||
let mut s = session();
|
||||
let (layer, ..) = tree(&mut s);
|
||||
let color = |s: &Session| match node(s, layer).kind {
|
||||
NodeKind::Layer { color, .. } => color,
|
||||
_ => panic!("not a layer"),
|
||||
};
|
||||
let before = (color(&s), undos(&s));
|
||||
for bad in ["#ff000080", "ff000080", " #FF000080 ", "#f008"] {
|
||||
let err = s.execute("layer.setProps", &json!({"id": layer.0, "color": bad})).unwrap_err().to_string();
|
||||
assert!(err.contains(&format!("`{bad}` is not a colour (#rrggbb or a preset name)")), "{bad}: {err}");
|
||||
assert!(s.execute("layer.new", &json!({"color": bad})).is_err(), "{bad}");
|
||||
assert!(s.execute("layer.newSublayer", &json!({"parent": layer.0, "color": bad})).is_err(), "{bad}");
|
||||
}
|
||||
assert_eq!((color(&s), undos(&s)), before);
|
||||
run(&mut s, "layer.setProps", json!({"id": layer.0, "color": "#ff0000"}));
|
||||
assert_eq!(color(&s), LayerColor::Custom([0xff, 0, 0]));
|
||||
run(&mut s, "layer.setProps", json!({"id": layer.0, "color": "Green"}));
|
||||
assert_eq!(color(&s), LayerColor::Preset(2));
|
||||
let new = id(&run(&mut s, "layer.new", json!({"color": "#00ff00"})));
|
||||
assert!(matches!(node(&s, new).kind, NodeKind::Layer { color: LayerColor::Custom([0, 0xff, 0]), .. }));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_layers_go_above_the_current_one_at_its_level() {
|
||||
let mut s = session();
|
||||
|
||||
Reference in New Issue
Block a user