diff --git a/crates/engine/src/script/mod.rs b/crates/engine/src/script/mod.rs
index 4b5380319..ee351d298 100644
--- a/crates/engine/src/script/mod.rs
+++ b/crates/engine/src/script/mod.rs
@@ -520,6 +520,14 @@ fn raise_ids(s: &mut Session) {
s.script_high_water = marks;
}
+/// Every object in `d`: the art of its layers, its symbols and its patterns, opacity masks included.
+fn all_objects(d: &crate::Document) -> u64 {
+ let layers = d.layers.iter().map(|n| n.count_with_masks());
+ let symbols = d.symbols.iter().map(|s| s.art.count_with_masks());
+ let patterns = d.patterns.iter().flat_map(|p| &p.art).map(|n| n.count_with_masks());
+ layers.chain(symbols).chain(patterns).map(|n| n as u64).sum()
+}
+
fn note_ids(s: &mut Session) {
for st in &s.docs {
let mark = s.script_high_water.entry(st.uid).or_insert(0);
@@ -551,11 +559,18 @@ fn step(s: &mut Session, host: &Host, id: &str, params: &Value) -> Result<(Value
note_ids(s);
let (mut objects, mut bytes) = (0, 0);
for st in &s.docs {
- if let Some((_, d, ids)) = before.iter().find(|(uid, ..)| *uid == st.uid)
- && !Arc::ptr_eq(d, &st.doc)
- {
- objects += st.doc.peek_next_id().saturating_sub(*ids);
- bytes += d.retained_bytes_each(&st.doc).1 as u64;
+ match before.iter().find(|(uid, ..)| *uid == st.uid) {
+ Some((_, d, ids)) if !Arc::ptr_eq(d, &st.doc) => {
+ objects += st.doc.peek_next_id().saturating_sub(*ids);
+ bytes += d.retained_bytes_each(&st.doc).1 as u64;
+ }
+ Some(_) => {}
+ // A document the command opened or made counts whole.
+ None if mutating => {
+ objects += all_objects(&st.doc);
+ bytes += crate::Document::new(1.0, 1.0).retained_bytes_each(&st.doc).1 as u64;
+ }
+ None => {}
}
}
Ok((v, objects, bytes, save))
diff --git a/crates/engine/src/tests/script_run.rs b/crates/engine/src/tests/script_run.rs
index 7912c7b1d..566e36145 100644
--- a/crates/engine/src/tests/script_run.rs
+++ b/crates/engine/src/tests/script_run.rs
@@ -601,6 +601,37 @@ fn a_run_with_nobody_to_ask_ends_at_its_growth_allowance() {
assert_eq!(state(&s), before);
}
+#[test]
+fn a_document_a_run_opens_counts_toward_its_growth_allowance() {
+ TEST_GROWTH.with(|g| g.set(Some(20)));
+ let rects: String = (0..30).map(|i| format!(r#""#)).collect();
+ let svg = format!(r#""#);
+ let data = vectorcraft_format::base64_encode(svg.as_bytes());
+ let mut s = two_docs();
+ let before = state(&s);
+ let source = format!("try {{ vc.run('document.open', {{name: 'a.svg', dataBase64: '{data}'}}); }} catch (e) {{}}");
+ let e = run(&mut s, &source).unwrap_err().to_string();
+ assert!(e.contains("objects and data"), "{e}");
+ assert_eq!(state(&s), before);
+
+ // A document whose art is all in a symbol counts it too.
+ let mut maker = Session::new();
+ maker.execute("file.new", &json!({})).unwrap();
+ for x in 0..30 {
+ maker.execute("shape.rectangle", &rect(x)).unwrap();
+ }
+ maker.execute("select.all", &json!({})).unwrap();
+ let instance = maker.execute("symbol.new", &json!({"name": "Many"})).unwrap()["id"].as_u64().unwrap();
+ maker.execute("edit.clear", &json!({"ids": [instance]})).unwrap();
+ assert_eq!(objects(&maker, 0), 0);
+ let data = maker.execute("document.serialize", &json!({})).unwrap()["dataBase64"].as_str().unwrap().to_string();
+ let source = format!("vc.run('document.open', {{name: 'b.vectorcraft', dataBase64: '{data}'}});");
+ let e = run(&mut s, &source).unwrap_err().to_string();
+ TEST_GROWTH.with(|g| g.set(None));
+ assert!(e.contains("objects and data"), "{e}");
+ assert_eq!(state(&s), before);
+}
+
#[test]
fn a_person_can_let_a_growing_run_go_on_or_stop_it() {
TEST_GROWTH.with(|g| g.set(Some(20)));