Layers: a .ai another app wrote opens with its top-level layers closed, as its groups (fixes #1475)

This commit is contained in:
Marco Arnulfo
2026-10-17 12:26:14 +07:00
committed by bflatastic
parent 52771905e6
commit f31d91bf1a
3 changed files with 46 additions and 1 deletions
+5
View File
@@ -377,6 +377,11 @@ fn open_loaded(s: &mut Session, loaded: Loaded, links: Resolved, path: Option<St
doc.template = false;
doc.title = s.next_untitled();
}
// A .ai (or .ait) another app wrote carries no open Layers rows we read: all of them start
// closed, its top-level layers as its groups (#1475). Ours carry their own.
if matches!(format.id, "ai" | "ait") && !restored && doc.layers_open.is_none() {
doc.layers_open = Some(vec![]);
}
// Save writes the file back only in a format it writes, only a whole PDF (see
// [`LoadOptions::is_partial`]), and a .ai file only when it carried the native document (Save
// writes .ai that way).
@@ -374,3 +374,43 @@ fn bled_file_opens_on_its_artboard(name: &str, key: &str) {
open(&mut s, name, &file, json!({})).unwrap();
assert_eq!(names(&s.doc().unwrap().doc.layers), ["Art"], "{name}: the editing data's layers are read");
}
/// The rows that open in the Layers panel (layers, sublayers and groups), and which of them are
/// open.
fn open_rows(s: &Session) -> (Vec<String>, Vec<String>) {
let st = s.doc().unwrap();
let (mut rows, mut open) = (vec![], vec![]);
st.doc.walk(|n| {
if n.children().is_some() {
rows.push(n.name.clone().unwrap_or_default());
if st.layers_open.contains(n.id) {
open.push(n.name.clone().unwrap_or_default());
}
}
});
(rows, open)
}
/// #1475: a `.ai` another app wrote keeps no open rows VectorCraft reads, so it opens with every
/// row closed, its top-level layers as its groups; a `.ai` VectorCraft wrote, and other formats,
/// open as before (with their top-level layers open unless the file says otherwise).
#[test]
fn an_illustrator_ai_file_opens_with_its_layers_closed() {
let mut s = Session::new();
for file in [ai::ai(&compressed(&ai::sample_data()), ai::page_pdf()), ai::eps(&ai::sample_data(), ai::page_ps())] {
let r = open(&mut s, "art.ai", &file, json!({})).unwrap();
assert_eq!((&r["format"], &r["restored"]), (&json!("ai"), &json!(false)), "{r}");
let (rows, open) = open_rows(&s);
assert!(rows.contains(&"Art".into()) && rows.contains(&"Pair".into()) && rows.contains(&"Sub".into()), "{rows:?}");
assert_eq!(open, Vec::<String>::new(), "{r}");
}
// The same art as an EPS: its top-level layers open.
open(&mut s, "art.eps", &ai::eps(&ai::sample_data(), ai::page_ps()), json!({})).unwrap();
assert_eq!(open_rows(&s).1, ["Art", "Hidden"]);
// Saved as .ai by VectorCraft, it opens with the rows it was saved with.
let saved = s.execute("document.save", &json!({"format": "ai"})).unwrap();
let bytes = vectorcraft_format::base64_decode(saved["dataBase64"].as_str().unwrap()).unwrap();
let r = open(&mut s, "again.ai", &bytes, json!({})).unwrap();
assert_eq!(r["restored"], json!(true), "{r}");
assert_eq!(open_rows(&s).1, ["Art", "Hidden"]);
}
+1 -1
View File
@@ -345,7 +345,7 @@ static NEXT_DOC_UID: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64
/// The rows open in the Layers panel: the layers, sublayers and groups that show what they hold.
/// A document opens with the ones it was saved with ([`Document::layers_open`]), else with only
/// its top-level layers open.
/// its top-level layers open (a `.ai` another app wrote opens with none open).
#[derive(Clone, Debug, Default)]
pub struct OpenRows {
ids: std::collections::HashSet<NodeId>,