mirror of
https://github.com/storytold/designcraft.git
synced 2026-10-30 05:06:48 -06:00
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:
@@ -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 {
|
||||
|
||||
@@ -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"] },
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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]
|
||||
|
||||
Reference in New Issue
Block a user