mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-14 19:26:56 -04:00
Select: saved selections checked when a file opens, names capped, docs (review of #318)
A file's saved selections are untrusted: on load at most 25 are kept, names are trimmed, capped at 255 characters and unique, and ids the document doesn't have are dropped (after fix_next_id such an id would select whatever object is drawn next). Save and Edit Selection cap names the same way. The limit, the name cleanup and the default "Selection N" live on SavedSelection, shared by the engine, the Save Selection dialog and the Select menu's slot table. docs/mcp.md documents the select.save / savedList / recall / editSaved commands; ROADMAP counts 13 stubbed menu items and names what's still missing (Update Selection, the Layers panel menu). Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
87d1acd589
commit
249b34fc7c
10 files changed
+118
-23
No files matched your search
@@ -380,6 +380,8 @@ pub fn load_file(bytes: &[u8]) -> Result<NativeFile, FormatError> {
|
||||
// Saved before v3: the assigned profiles move out of `unknown`.
|
||||
doc.migrate_color_profiles();
|
||||
doc.fix_next_id();
|
||||
// Untrusted: capped, named and pointing at objects the file has.
|
||||
doc.tidy_saved_selections();
|
||||
let profiles = f.profiles.into_iter().filter_map(|(name, icc)| Some((name, base64_decode(&icc.data)?))).collect();
|
||||
Ok(NativeFile { doc, info: FileInfo { version: f.version, legacy: f.format == LEGACY_EXTENSION }, profiles })
|
||||
}
|
||||
|
||||
@@ -203,3 +203,35 @@ fn bug_f64_bit_exact_roundtrip() {
|
||||
d.raster_effects_ppi = 0.1 + 0.2;
|
||||
check_native_roundtrip_exact(&d).unwrap();
|
||||
}
|
||||
|
||||
/// Saved selections come from the file: older files have none, and a hostile one is cut down to
|
||||
/// what the Select menu can list, with names that fit and ids the document has.
|
||||
#[test]
|
||||
fn saved_selections_from_a_file_are_tidied() {
|
||||
let d = rich_doc();
|
||||
let mut v: Value = serde_json::from_slice(&save(&d, false)).unwrap();
|
||||
assert!(v["document"].get("saved_selections").is_none(), "nothing written when there are none");
|
||||
assert!(load(&serde_json::to_vec(&v).unwrap()).unwrap().saved_selections.is_empty());
|
||||
let id = fixtures::all_ids(&d).into_iter().next().unwrap();
|
||||
let missing = fixtures::all_ids(&d).into_iter().map(|NodeId(i)| i).max().unwrap() + 1;
|
||||
let mut list = vec![
|
||||
json!({"name": format!(" {} ", "x".repeat(1000)), "objects": [id.0, missing, id.0]}),
|
||||
json!({"name": " ", "objects": [id.0]}),
|
||||
json!({"name": "Dup", "objects": []}),
|
||||
json!({"name": "Dup", "objects": [id.0]}),
|
||||
];
|
||||
list.extend((0..100).map(|i| json!({"name": format!("S{i}"), "objects": [id.0]})));
|
||||
v["document"]["saved_selections"] = json!(list);
|
||||
let back = load(&serde_json::to_vec(&v).unwrap()).unwrap();
|
||||
let saved = &back.saved_selections;
|
||||
assert_eq!(saved.len(), vectorcraft_doc::SavedSelection::MAX);
|
||||
assert_eq!(saved[0].name, "x".repeat(vectorcraft_doc::SavedSelection::MAX_NAME));
|
||||
assert_eq!(saved[0].objects, vec![id], "a missing id is dropped, a repeated one kept once");
|
||||
assert_eq!((saved[1].name.as_str(), saved[1].objects.len()), ("Dup", 0), "blank names go, the first of a name stays");
|
||||
assert_eq!(saved[2].name, "S0");
|
||||
// What loads saves and loads again unchanged.
|
||||
assert_eq!(&load(&save(&back, false)).unwrap().saved_selections, saved);
|
||||
// Junk in the member is a format error, not a crash.
|
||||
v["document"]["saved_selections"] = json!([{"name": 5, "objects": "x"}]);
|
||||
assert!(load(&serde_json::to_vec(&v).unwrap()).is_err());
|
||||
}
|
||||
Reference in new issue
Block a user