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.
This commit is contained in:
aiongg
2026-10-08 00:51:47 -04:00
parent b757687a41
commit 1ccf4fbfbe
4 changed files with 32 additions and 84 deletions
+2 -2
View File
@@ -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 {
+1 -2
View File
@@ -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"] },
],
}
}
-52
View File
@@ -112,23 +112,9 @@ pub fn to_bytes(d: &Document) -> Vec<u8> {
}
pub fn from_bytes(b: &[u8]) -> Result<Document> {
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<Value> {
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<Value> {
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<u8> {
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};
+29 -28
View File
@@ -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]