mirror of
https://github.com/storytold/figcraft.git
synced 2026-10-29 12:56:47 -06:00
Merge fix/625
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
//! Commands never leave a layer with a non-finite position or size: such a file saves but can't be
|
||||
//! reopened (JSON writes NaN and infinity as null).
|
||||
|
||||
use serde_json::json;
|
||||
|
||||
use crate::Session;
|
||||
|
||||
fn rect(s: &mut Session) -> crate::NodeId {
|
||||
let r = s.execute("node.create", &json!({"type": "rectangle", "x": 0, "y": 10, "width": 100, "height": 50})).unwrap();
|
||||
crate::params::id_of(&r["id"]).unwrap()
|
||||
}
|
||||
|
||||
fn assert_roundtrips(s: &Session) {
|
||||
let bytes = figcraft_format::save_bytes(s.doc().unwrap(), None).unwrap();
|
||||
assert!(figcraft_format::load_bytes(&bytes).is_ok(), "the saved file reopens");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn huge_moves_are_refused_and_the_file_still_reopens() {
|
||||
let mut s = Session::new();
|
||||
let r = rect(&mut s);
|
||||
s.execute("transform.move", &json!({"ids": [r], "dx": 1e308, "dy": 0})).ok();
|
||||
let second = s.execute("transform.move", &json!({"ids": [r], "dx": 1e308, "dy": 1e308}));
|
||||
assert!(second.is_err(), "a move past the f64 range is refused");
|
||||
let n = s.doc().unwrap().node(r).unwrap();
|
||||
assert!(n.transform.as_coeffs().iter().all(|c| c.is_finite()), "{:?}", n.transform);
|
||||
assert_roundtrips(&s);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn huge_scale_is_refused_and_the_file_still_reopens() {
|
||||
let mut s = Session::new();
|
||||
let r = rect(&mut s);
|
||||
s.execute("transform.scale", &json!({"ids": [r], "factor": 1e300})).ok();
|
||||
let second = s.execute("transform.scale", &json!({"ids": [r], "factor": 1e300}));
|
||||
assert!(second.is_err(), "a size past the f64 range is refused");
|
||||
let n = s.doc().unwrap().node(r).unwrap();
|
||||
assert!(n.width.is_finite() && n.height.is_finite(), "{} x {}", n.width, n.height);
|
||||
assert_roundtrips(&s);
|
||||
}
|
||||
@@ -880,7 +880,8 @@ impl Session {
|
||||
let result = guard::run(id, || (spec.run)(self, params))
|
||||
.and_then(|v| self.check_instance_structure(id, &before).map(|()| v))
|
||||
.and_then(|v| self.check_preview(id, &before).map(|()| v))
|
||||
.and_then(|v| self.check_live_access(id, &before).map(|()| v));
|
||||
.and_then(|v| self.check_live_access(id, &before).map(|()| v))
|
||||
.and_then(|v| self.check_finite_geometry(id, &before).map(|()| v));
|
||||
match &result {
|
||||
Ok(_) => {
|
||||
self.after_command(spec, before);
|
||||
@@ -918,6 +919,22 @@ impl Session {
|
||||
Err(EngineError::Disabled(id.to_string(), PREVIEW_READ_ONLY.into()))
|
||||
}
|
||||
|
||||
/// A layer's position and size stay finite: infinity and NaN are written as `null`, so a file
|
||||
/// holding them saves but can't be reopened. Refuse such an edit (it is rolled back).
|
||||
fn check_finite_geometry(&self, id: &str, before: &Option<(Arc<Document>, Vec<NodeId>, NodeId, u64)>) -> Result<()> {
|
||||
let Some((bdoc, _, _, uid)) = before else { return Ok(()) };
|
||||
let Some(ds) = self.docs.iter().find(|d| d.uid == *uid) else { return Ok(()) };
|
||||
if Arc::ptr_eq(&ds.doc, bdoc) {
|
||||
return Ok(());
|
||||
}
|
||||
let broken = |n: &Node| !(n.transform.as_coeffs().iter().all(|c| c.is_finite()) && n.width.is_finite() && n.height.is_finite());
|
||||
let untouched = |n: &Node| bdoc.node_arc(n.id).zip(ds.doc.node_arc(n.id)).is_some_and(|(a, b)| Arc::ptr_eq(a, b));
|
||||
match ds.doc.nodes().find(|n| broken(n) && !untouched(n)) {
|
||||
Some(n) => Err(bad(id, format!("\"{}\" would get a position or size beyond the number range", n.name))),
|
||||
None => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
/// A live session's document you may only view (or that is on its way to a server) is
|
||||
/// read-only: refuse an edit (it is rolled back).
|
||||
fn check_live_access(&self, id: &str, before: &Option<(Arc<Document>, Vec<NodeId>, NodeId, u64)>) -> Result<()> {
|
||||
@@ -1530,6 +1547,8 @@ mod fig_export_tests;
|
||||
#[cfg(test)]
|
||||
mod fig_import_tests;
|
||||
#[cfg(test)]
|
||||
mod finite_geometry_tests;
|
||||
#[cfg(test)]
|
||||
mod frame_guides_tests;
|
||||
#[cfg(test)]
|
||||
mod frame_label_tests;
|
||||
|
||||
Reference in New Issue
Block a user