diff --git a/crates/ui-egui/src/place.rs b/crates/ui-egui/src/place.rs index 70bd807c3..e14d987ab 100644 --- a/crates/ui-egui/src/place.rs +++ b/crates/ui-egui/src/place.rs @@ -155,6 +155,7 @@ fn open_dialog(app: &mut VectorcraftApp, files: Vec) { let units = app.session.active().map(|d| d.doc.units).unwrap_or_default(); let info: Vec = files.iter().map(|f| summary(app, f, units)).collect(); let one_object = app.session.active().is_some_and(|d| d.selection.objects.len() == 1); + let linked: Vec = files.iter().map(|f| is_vectorcraft_file(app, f)).collect(); app.ui.dialog = Some(crate::state::Dialog::new( crate::dialogs::place::KIND, json!({ @@ -163,18 +164,24 @@ fn open_dialog(app: &mut VectorcraftApp, files: Vec) { "template": false, "replace": false, "__replace": files.len() == 1 && one_object, - "__documents": files.iter().any(is_vectorcraft_file), - "__others": !files.iter().all(is_vectorcraft_file), + "__documents": linked.contains(&true), + "__others": linked.contains(&false), "__info": info, }), )); } /// Is dialog file entry `file` a VectorCraft document on disk (what Link places as one locked -/// object), or a PDF or `.ai` file on disk? -fn is_vectorcraft_file(file: &Value) -> bool { - let linked = ["vectorcraft", vectorcraft_format::LEGACY_EXTENSION, "vctemplate", "pdf", "ai", "ait"]; - file.get("path").and_then(Value::as_str).is_some_and(|p| linked.contains(&fileio::extension(p).as_str())) +/// object), or a PDF or `.ai` file on disk? A `.ai` saved without PDF compatibility is PostScript, +/// which `file.place` embeds whether Link is on or not. +fn is_vectorcraft_file(app: &VectorcraftApp, file: &Value) -> bool { + let Some(path) = file.get("path").and_then(Value::as_str) else { return false }; + let native = ["vectorcraft", vectorcraft_format::LEGACY_EXTENSION, "vctemplate"]; + match fileio::extension(path).as_str() { + e if native.contains(&e) => true, + "pdf" | "ai" | "ait" => app.services.read.as_ref().and_then(|r| r(path).ok()).is_none_or(|b| !vectorcraft_pdf::is_postscript(&b)), + _ => false, + } } /// The engine params naming `file` (a dialog file entry). diff --git a/crates/ui-egui/src/tests_place.rs b/crates/ui-egui/src/tests_place.rs index ff11ba58e..2ecb1c812 100644 --- a/crates/ui-egui/src/tests_place.rs +++ b/crates/ui-egui/src/tests_place.rs @@ -659,6 +659,23 @@ fn the_place_dialog_links_a_pdf_page_and_embeds_it_with_link_off() { assert_eq!(st.doc.node(st.selection.objects[0]).unwrap().kind_label(), "Clip Group"); } +#[test] +fn the_place_dialog_does_not_offer_a_postscript_ai_as_a_locked_object() { + let mut app = app(); + let ps = b"%!PS-Adobe-3.0\n%%BoundingBox: 0 0 100 100\n%%EndComments\n1 0 0 setrgbcolor 10 10 50 50 rectfill\nshowpage\n%%EOF\n"; + let path = temp_file("legacy.ai", ps); + // file.place embeds a .ai saved without PDF compatibility, even with Link on. + let r = app.session.execute("file.place", &json!({"path": path, "link": true})).unwrap(); + assert_eq!(r["linked"], json!(false), "{r}"); + let st = app.session.active().unwrap(); + assert!(!matches!(st.doc.node(st.selection.objects[0]).unwrap().kind, NodeKind::PlacedDocument(_))); + // So Link's tooltip does not promise one locked object for it. + place_picked(&mut app, &path); + let d = app.ui.dialog.as_ref().expect("the Place dialog"); + assert!(!d.bool("__documents") && d.bool("__others"), "{:?}", d.fields); + assert!(!crate::dialogs::place::link_tip(d).contains("locked object")); +} + /// Tool key `key` as the Control bar's Apply (Enter) and Cancel (Escape) send it. fn tool_key(app: &mut VectorcraftApp, key: vectorcraft_tools::ToolKey) { let view = app.view_info();