From 22b6f2a89c090ee6e40ca3afff7084f766765451 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Thu, 22 Oct 2026 03:48:18 -0700 Subject: [PATCH] Place: Link's tooltip treats a PostScript .ai as a file it embeds The Place dialog classed every .pdf, .ai and .ait path as a document that Link places as one locked object. A .ai saved without PDF compatibility is PostScript, and file.place embeds it as editable art with Link on or off, so the tooltip was wrong for it. The dialog now reads the file's header with the same PostScript check file.place uses, and describes such a file as it does other files Link does not keep linked. --- crates/ui-egui/src/place.rs | 19 +++++++++++++------ crates/ui-egui/src/tests_place.rs | 17 +++++++++++++++++ 2 files changed, 30 insertions(+), 6 deletions(-) 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();