From 1ccf4fbfbe719bdd52b656284e9a83f617dc28c3 Mon Sep 17 00:00:00 2001 From: aiongg Date: Thu, 8 Oct 2026 00:51:47 -0400 Subject: [PATCH] File open: an .indd file is an unreadable file like any other Remove the INDD signature check and message, and .indd/.indt from the Open filters and drop routing. The alert tests use a generic unreadable file. --- apps/designcraft-web/src/web.rs | 4 +-- apps/designcraft/src/main.rs | 3 +- crates/engine/src/cmd/file.rs | 52 ------------------------------ crates/ui-egui/src/lib.rs | 57 +++++++++++++++++---------------- 4 files changed, 32 insertions(+), 84 deletions(-) diff --git a/apps/designcraft-web/src/web.rs b/apps/designcraft-web/src/web.rs index c1c48a9b8..c0466b691 100644 --- a/apps/designcraft-web/src/web.rs +++ b/apps/designcraft-web/src/web.rs @@ -4,7 +4,7 @@ use designcraft_engine::Session; use designcraft_ui_egui::{DesignApp, Inbox, Services}; use wasm_bindgen::JsCast as _; -const DOC_EXTS: &[&str] = &["designcraft", "idml", "indd", "indt"]; +const DOC_EXTS: &[&str] = &["designcraft", "idml"]; const IMAGE_EXTS: &[&str] = &[ "png", "jpg", @@ -123,7 +123,7 @@ fn services(inbox: Inbox, ctx: egui::Context) -> Services { } else if purpose == "place" { rfd::AsyncFileDialog::new().add_filter("Graphics", IMAGE_EXTS) } else { - rfd::AsyncFileDialog::new().add_filter("DesignCraft, IDML or InDesign", DOC_EXTS) + rfd::AsyncFileDialog::new().add_filter("DesignCraft", DOC_EXTS) }; wasm_bindgen_futures::spawn_local(async move { let Some(file) = dialog.pick_file().await else { diff --git a/apps/designcraft/src/main.rs b/apps/designcraft/src/main.rs index 7a6115046..307bfe1a0 100644 --- a/apps/designcraft/src/main.rs +++ b/apps/designcraft/src/main.rs @@ -146,10 +146,9 @@ fn open_filters(purpose: &str) -> &'static [OpenFilter] { OpenFilter { name: "Video and sound", extensions: &["mp4", "m4v", "mov", "webm", "mp3", "m4a", "wav", "ogg"] }, ], _ => &[ - OpenFilter { name: "DesignCraft, IDML or InDesign", extensions: &["designcraft", "idml", "indd", "indt"] }, + OpenFilter { name: "DesignCraft or IDML", extensions: &["designcraft", "idml"] }, OpenFilter { name: "DesignCraft", extensions: &["designcraft"] }, OpenFilter { name: "InDesign Markup (IDML)", extensions: &["idml"] }, - OpenFilter { name: "InDesign Document or Template (INDD, INDT)", extensions: &["indd", "indt"] }, ], } } diff --git a/crates/engine/src/cmd/file.rs b/crates/engine/src/cmd/file.rs index bfbdd20a7..e40000095 100644 --- a/crates/engine/src/cmd/file.rs +++ b/crates/engine/src/cmd/file.rs @@ -112,23 +112,9 @@ pub fn to_bytes(d: &Document) -> Vec { } pub fn from_bytes(b: &[u8]) -> Result { - if is_indd(b) { - return Err(EngineError::Other(INDD_UNREADABLE.into())); - } designcraft_format::load(b).map_err(|e| EngineError::Other(e.to_string())) } -/// InDesign documents and templates (.indd, .indt) begin with this 16-byte class id. -const INDD_SIGNATURE: [u8; 16] = [0x06, 0x06, 0xED, 0xF5, 0xD8, 0x1D, 0x46, 0xE5, 0xBD, 0x31, 0xEF, 0xE7, 0xFE, 0x74, 0xB7, 0x1D]; - -const INDD_UNREADABLE: &str = "This is an InDesign document (.indd). Its format is closed and can't be read: in InDesign, \ - choose File › Export… › InDesign Markup (IDML), then open the .idml file here."; - -/// An InDesign document or template, recognised by its signature. -pub fn is_indd(b: &[u8]) -> bool { - b.starts_with(&INDD_SIGNATURE) -} - fn file_new(s: &mut Session, p: &Value) -> Result { let mut nd = match str_param(p, "preset") { Some(name) => NewDocument::from_preset(name).ok_or_else(|| bad("file.new", format!("unknown preset `{name}`")))?, @@ -269,9 +255,6 @@ fn file_place(s: &mut Session, p: &Value) -> Result { s.ui_requests.push(crate::UiRequest::Pick { purpose: "place".into(), params: json!({}) }); return ok(); }; - if is_indd(&bytes) { - return Err(EngineError::Other(INDD_UNREADABLE.into())); - } // A page of another layout (IDML or DesignCraft): its objects, as one group. let lname = name.to_lowercase(); if lname.ends_with(".idml") || lname.ends_with(".designcraft") { @@ -715,41 +698,6 @@ mod layout_place_tests { } } -#[cfg(test)] -mod indd_tests { - use serde_json::json; - - use crate::Session; - - /// The start of an InDesign document: its signature, then the `DOCUMENT` class name. - fn indd() -> Vec { - let mut b = super::INDD_SIGNATURE.to_vec(); - b.extend_from_slice(b"DOCUMENT"); - b.resize(4096, 0); - b - } - - #[test] - fn indd_files_say_to_export_idml() { - let b64 = super::base64_encode(&indd()); - let mut s = Session::new(); - let open = s.execute("file.openBytes", &json!({"base64": b64, "name": "Brochure"})).unwrap_err().to_string(); - assert!(open.contains("InDesign document") && open.contains("IDML"), "{open}"); - s.execute("file.new", &json!({})).unwrap(); - let place = s.execute("file.place", &json!({"base64": b64, "name": "Brochure.indd"})).unwrap_err().to_string(); - assert!(place.contains("InDesign document") && place.contains("IDML"), "{place}"); - #[cfg(not(target_arch = "wasm32"))] - { - let path = std::env::temp_dir().join(format!("designcraft-indd-{}.indd", std::process::id())); - std::fs::write(&path, indd()).unwrap(); - let open = s.execute("file.open", &json!({"path": path.to_string_lossy()})).unwrap_err().to_string(); - let _ = std::fs::remove_file(&path); - assert!(open.contains("InDesign document"), "{open}"); - } - assert_eq!(s.documents().len(), 1, "nothing was opened"); - } -} - #[cfg(test)] mod document_fonts_tests { use std::path::{Path, PathBuf}; diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index 79c838f15..3126d53cc 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -872,11 +872,11 @@ impl DesignApp { } } -/// Files that open as documents (when dropped or picked) rather than being placed: DesignCraft, -/// IDML, and InDesign documents and templates (which explain how to export IDML). +/// Files that open as documents (when dropped or picked) rather than being placed: DesignCraft +/// and IDML. pub fn opens_as_document(name: &str) -> bool { let n = name.to_ascii_lowercase(); - [".designcraft", ".idml", ".indd", ".indt"].iter().any(|ext| n.ends_with(ext)) + [".designcraft", ".idml"].iter().any(|ext| n.ends_with(ext)) } pub fn now_ms() -> f64 { @@ -907,12 +907,10 @@ mod tests { assert_eq!(ui.snap_view().zone_px, 0.0); } - /// A temporary file that starts like an InDesign document (its 16-byte signature). - fn indd_file(tag: &str) -> std::path::PathBuf { - let path = std::env::temp_dir().join(format!("designcraft-ui-{tag}-{}.indd", std::process::id())); - let mut b = vec![0x06, 0x06, 0xED, 0xF5, 0xD8, 0x1D, 0x46, 0xE5, 0xBD, 0x31, 0xEF, 0xE7, 0xFE, 0x74, 0xB7, 0x1D]; - b.resize(4096, 0); - std::fs::write(&path, b).unwrap(); + /// A temporary file named `name` whose bytes aren't a document or a graphic. + fn unreadable_file(name: &str) -> std::path::PathBuf { + let path = std::env::temp_dir().join(format!("designcraft-ui-{}-{name}", std::process::id())); + std::fs::write(&path, b"neither a document nor a graphic").unwrap(); path } @@ -925,49 +923,52 @@ mod tests { #[test] fn dropped_documents_open_and_the_rest_is_placed() { - for name in ["a.designcraft", "B.IDML", "c.indd", "D.INDT"] { + for name in ["a.designcraft", "B.IDML"] { assert!(opens_as_document(name), "{name}"); } - for name in ["a.png", "b.pdf", "indd.txt", "c.indd.zip"] { + for name in ["a.png", "b.pdf", "idml.txt", "c.idml.zip"] { assert!(!opens_as_document(name), "{name}"); } } #[test] - fn dropping_an_indesign_document_opens_it_and_alerts() { - let path = indd_file("drop"); + fn dropping_an_unreadable_file_alerts() { + // A document opens, so it says it can't be opened. + let path = unreadable_file("broken.designcraft"); let mut app = DesignApp::new(Session::new(), Services::default()); let r = app.open_dropped(&path.to_string_lossy()); let _ = std::fs::remove_file(&path); - assert!(r.is_err()); + let e = r.unwrap_err(); let (title, message) = alert(&app); - assert_eq!(title, "Can't Open the File", "an .indd opens, it isn't placed"); - assert!(message.contains("InDesign document") && message.contains("IDML"), "{message}"); - assert!(app.ui.status.contains("InDesign document"), "the status line says it too"); + assert_eq!(title, "Can't Open the File", "a document opens, it isn't placed"); + assert_eq!(message, e); + assert!(app.ui.status.contains(&e), "the status line says it too"); assert!(app.session.documents().is_empty()); dialogs::confirm(&mut app).unwrap(); assert!(app.ui.dialog.is_none(), "OK closes the alert"); + // Anything else is placed, so it says it can't be placed. + app.run("file.new", json!({})).unwrap(); + let path = unreadable_file("notes.xyz"); + let r = app.open_dropped(&path.to_string_lossy()); + let _ = std::fs::remove_file(&path); + let e = r.unwrap_err(); + assert_eq!(alert(&app), ("Can't Place the File".to_string(), e)); } #[test] fn file_open_failures_show_an_alert() { - let path = indd_file("open"); + let path = unreadable_file("open.designcraft"); let picked = path.to_string_lossy().to_string(); let services = Services { pick_open: Some(Box::new(move |_| Some(picked.clone()))), ..Default::default() }; let mut app = DesignApp::new(Session::new(), services); - let r = app.run("app.openDialog", json!({})); - assert!(r.is_err()); - let (title, message) = alert(&app); - assert_eq!(title, "Can't Open the File"); - assert!(message.contains("InDesign document"), "{message}"); - // Placing one says so too. + let e = app.run("app.openDialog", json!({})).unwrap_err(); + assert_eq!(alert(&app), ("Can't Open the File".to_string(), e)); + // Placing it says so too. app.ui.dialog = None; app.run("file.new", json!({})).unwrap(); - assert!(app.run("app.placeDialog", json!({})).is_err()); + let e = app.run("app.placeDialog", json!({})).unwrap_err(); let _ = std::fs::remove_file(&path); - let (title, message) = alert(&app); - assert_eq!(title, "Can't Place the File"); - assert!(message.contains("InDesign document"), "{message}"); + assert_eq!(alert(&app), ("Can't Place the File".to_string(), e)); } #[test]