diff --git a/Cargo.lock b/Cargo.lock index b46aa6550..92227fc93 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1144,7 +1144,7 @@ dependencies = [ [[package]] name = "craft-eps" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=6ba1ca33ca21c33de55ef155141db08ca6048e8d#6ba1ca33ca21c33de55ef155141db08ca6048e8d" +source = "git+https://github.com/storytold/craft-libs?rev=b4ada28f1#b4ada28f19067d4693e902e0e5f32eb292241e3a" [[package]] name = "craft-eps" @@ -1164,10 +1164,10 @@ dependencies = [ [[package]] name = "craft-file-formats" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=6ba1ca33ca21c33de55ef155141db08ca6048e8d#6ba1ca33ca21c33de55ef155141db08ca6048e8d" +source = "git+https://github.com/storytold/craft-libs?rev=b4ada28f1#b4ada28f19067d4693e902e0e5f32eb292241e3a" dependencies = [ - "craft-eps 0.1.0 (git+https://github.com/storytold/craft-libs?rev=6ba1ca33ca21c33de55ef155141db08ca6048e8d)", - "craft-svg 0.1.0 (git+https://github.com/storytold/craft-libs?rev=6ba1ca33ca21c33de55ef155141db08ca6048e8d)", + "craft-eps 0.1.0 (git+https://github.com/storytold/craft-libs?rev=b4ada28f1)", + "craft-svg 0.1.0 (git+https://github.com/storytold/craft-libs?rev=b4ada28f1)", ] [[package]] @@ -1362,9 +1362,9 @@ source = "git+https://github.com/storytold/craft-libs?rev=ba24fe2435adb3c81ad605 [[package]] name = "craft-svg" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=6ba1ca33ca21c33de55ef155141db08ca6048e8d#6ba1ca33ca21c33de55ef155141db08ca6048e8d" +source = "git+https://github.com/storytold/craft-libs?rev=b4ada28f1#b4ada28f19067d4693e902e0e5f32eb292241e3a" dependencies = [ - "craft-xml 0.1.0 (git+https://github.com/storytold/craft-libs?rev=6ba1ca33ca21c33de55ef155141db08ca6048e8d)", + "craft-xml 0.1.0 (git+https://github.com/storytold/craft-libs?rev=b4ada28f1)", "flate2", "kurbo", "roxmltree 0.21.2", @@ -1479,7 +1479,7 @@ dependencies = [ [[package]] name = "craft-xml" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=6ba1ca33ca21c33de55ef155141db08ca6048e8d#6ba1ca33ca21c33de55ef155141db08ca6048e8d" +source = "git+https://github.com/storytold/craft-libs?rev=b4ada28f1#b4ada28f19067d4693e902e0e5f32eb292241e3a" dependencies = [ "quick-xml 0.41.0", "thiserror 2.0.21", @@ -1967,6 +1967,7 @@ name = "designcraft-ui-egui" version = "0.20.1" dependencies = [ "craft-dialogs", + "craft-file-formats", "craft-keymap", "craft-prepress", "craft-ui", diff --git a/Cargo.toml b/Cargo.toml index 2b2f09020..74359a740 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,7 +26,7 @@ craft-docx = { git = "https://github.com/storytold/craft-libs", rev = "ba24fe243 craft-wordmodel = { git = "https://github.com/storytold/craft-libs", rev = "ba24fe2435adb3c81ad6057faf36365fbba736cf", version = "=0.1.0" } craft-image-codecs = { git = "https://github.com/storytold/craft-libs", rev = "a47b78256c6acf3d7fdc4bc7cfbc25c373eac522", version = "=0.1.0" } craft-psd = { git = "https://github.com/storytold/craft-libs", rev = "a47b78256c6acf3d7fdc4bc7cfbc25c373eac522", version = "=0.1.0" } -craft-file-formats = { git = "https://github.com/storytold/craft-libs", rev = "6ba1ca33ca21c33de55ef155141db08ca6048e8d", version = "=0.1.0" } +craft-file-formats = { git = "https://github.com/storytold/craft-libs", rev = "b4ada28f1", version = "=0.1.0" } # Shared keyboard bindings: chords, overrides, conflicts, egui dispatch, the Keyboard Shortcuts dialog. craft-keymap = { git = "https://github.com/storytold/craft-libs", rev = "92d392cff132eaf53e13afff77f38b5c11b73aa0", version = "=0.1.0", features = ["egui", "ui"] } # Separations, ink coverage and the ink manager; same revision as craft-pdf and the krilla patch. diff --git a/crates/engine/src/cmd/export/formats.rs b/crates/engine/src/cmd/export/formats.rs index a25553d69..845b9b1d7 100644 --- a/crates/engine/src/cmd/export/formats.rs +++ b/crates/engine/src/cmd/export/formats.rs @@ -136,7 +136,7 @@ pub(super) fn export_text(s: &mut Session, p: &Value) -> Result { Some(f @ ("rtf" | "tagged")) => f, Some("txt") | Some("text") => "txt", Some(f) => return Err(bad(ID, format!("unknown format `{f}` (txt, rtf, tagged)"))), - None if path.is_some_and(|x| x.to_ascii_lowercase().ends_with(".rtf")) => "rtf", + None if path.is_some_and(|x| craft_file_formats::detect(x, &[]) == Some(craft_file_formats::Format::Rtf)) => "rtf", None => "txt", }; let text = match format { diff --git a/crates/engine/src/cmd/file/bytes.rs b/crates/engine/src/cmd/file/bytes.rs index 5fd6d4e0b..f169b74e4 100644 --- a/crates/engine/src/cmd/file/bytes.rs +++ b/crates/engine/src/cmd/file/bytes.rs @@ -21,7 +21,7 @@ fn save_bytes(d: &Document, preview: Option<&[u8]>) -> Result> { pub fn with_document_extension(path: &str) -> String { let p = std::path::Path::new(path); let ext = p.extension().and_then(std::ffi::OsStr::to_str); - if p.file_name().is_none() || ext.is_some_and(|e| e.eq_ignore_ascii_case(designcraft_format::EXTENSION)) { + if p.file_name().is_none() || ext.is_some_and(|e| craft_file_formats::from_extension(e) == Some(craft_file_formats::Format::DesignCraft)) { return path.to_string(); } format!("{path}.{}", designcraft_format::EXTENSION) @@ -32,7 +32,7 @@ pub fn with_document_extension(path: &str) -> String { /// that kept it would name the file `Brochure.designcraft.designcraft`. A bare `.designcraft` stays. pub(super) fn without_native_extension(name: &str) -> &str { match name.rsplit_once('.') { - Some((stem, ext)) if !stem.is_empty() && ext.eq_ignore_ascii_case(designcraft_format::EXTENSION) => stem, + Some((stem, ext)) if !stem.is_empty() && craft_file_formats::from_extension(ext) == Some(craft_file_formats::Format::DesignCraft) => stem, _ => name, } } diff --git a/crates/ui-egui/Cargo.toml b/crates/ui-egui/Cargo.toml index 6b08420a9..143dfebfd 100644 --- a/crates/ui-egui/Cargo.toml +++ b/crates/ui-egui/Cargo.toml @@ -13,6 +13,7 @@ workspace = true craft-ui = { workspace = true, features = ["native-windows"] } craft-window = { workspace = true } craft-keymap = { workspace = true } +craft-file-formats = { workspace = true } designcraft-geom = { workspace = true } designcraft-color = { workspace = true } designcraft-doc = { workspace = true } diff --git a/crates/ui-egui/src/app/files.rs b/crates/ui-egui/src/app/files.rs index 069515394..61f0f9bbe 100644 --- a/crates/ui-egui/src/app/files.rs +++ b/crates/ui-egui/src/app/files.rs @@ -24,7 +24,7 @@ impl DesignApp { /// Open (`open`) or place (`place`) the file at `path`, chosen in a dialog or passed by a script. pub(crate) fn open_picked(&mut self, purpose: &str, path: String) -> Result { // Image Import Options: a multi-page PDF asks which page. - if purpose == "place" && path.to_ascii_lowercase().ends_with(".pdf") { + if purpose == "place" && craft_file_formats::detect(&path, &[]) == Some(craft_file_formats::Format::Pdf) { let target = self.session.active().map(|d| d.uid); let pages = self.services.read.as_mut().and_then(|r| r(&path).ok()).and_then(|b| designcraft_render::pdf_page_count(&b)).unwrap_or(1); if pages > 1 { @@ -126,7 +126,7 @@ impl DesignApp { } fn import_file(&mut self, file: ImportedFile) -> Result { - let lower = file.name.to_ascii_lowercase(); + let format = craft_file_formats::detect(&file.name, &file.bytes); if let Some(kind) = file.request.purpose.strip_prefix("styles") { self.activate_import_target(&file.request)?; let mut params = json!({"name": file.name, "base64": designcraft_engine::cmd::base64_encode(&file.bytes)}); @@ -140,11 +140,11 @@ impl DesignApp { let layout = opens_as_document(&file.name) || (std::path::Path::new(&file.name).extension().is_none() && designcraft_engine::cmd::is_document(&file.bytes)); let place = - file.request.purpose == "place" || (matches!(file.request.purpose.as_str(), "drop" | "open") && !layout && !lower.ends_with(".ase")); - if place || lower.ends_with(".ase") || file.request.purpose == "swatches" { + file.request.purpose == "place" || (matches!(file.request.purpose.as_str(), "drop" | "open") && !layout && format != Some(craft_file_formats::Format::Ase)); + if place || format == Some(craft_file_formats::Format::Ase) || file.request.purpose == "swatches" { self.activate_import_target(&file.request)?; } - if place && lower.ends_with(".pdf") { + if place && craft_file_formats::detect(&file.name, &file.bytes) == Some(craft_file_formats::Format::Pdf) { let pages = designcraft_render::pdf_page_count(&file.bytes).unwrap_or(1); if pages > 1 { self.ui.dialog = @@ -154,7 +154,7 @@ impl DesignApp { } } let b64 = designcraft_engine::cmd::base64_encode(&file.bytes); - if lower.ends_with(".ase") || file.request.purpose == "swatches" { + if format == Some(craft_file_formats::Format::Ase) || file.request.purpose == "swatches" { self.run("swatch.load", json!({"base64": b64})) } else if file.request.purpose == "place" { self.place_file(json!({"name": file.name, "base64": b64})) @@ -198,7 +198,7 @@ impl DesignApp { /// the pointer when there is none, and clears the flag, so the files after it land at the /// pointer instead of replacing that graphic. pub(crate) fn place_dropped(&mut self, path: &str, pointer: Option, frame_free: &mut bool) -> Result { - if path.to_ascii_lowercase().ends_with(".ase") { + if craft_file_formats::detect(path, &[]) == Some(craft_file_formats::Format::Ase) { return self.run("swatch.load", json!({"path": path})); } let document = opens_as_document(path) @@ -318,8 +318,10 @@ impl DesignApp { /// Files that open as documents (when dropped or picked) rather than being placed: DesignCraft, /// IDML, and InDesign documents and templates (converted to IDML on open). 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)) + matches!( + craft_file_formats::detect(name, &[]), + Some(craft_file_formats::Format::DesignCraft | craft_file_formats::Format::Idml | craft_file_formats::Format::Indd) + ) } #[cfg(test)] diff --git a/crates/ui-egui/src/menus/export.rs b/crates/ui-egui/src/menus/export.rs index 2c1946d4f..a80a55fc0 100644 --- a/crates/ui-egui/src/menus/export.rs +++ b/crates/ui-egui/src/menus/export.rs @@ -92,7 +92,7 @@ pub(super) fn export_bytes(app: &mut DesignApp, p: &Value, ext: &str, cmd: &str) _ => json!({}), }; if cmd == "file.exportText" { - let txt = path.to_ascii_lowercase().ends_with(".txt"); + let txt = craft_file_formats::detect(&path, &[]) == Some(craft_file_formats::Format::PlainText); params["format"] = json!(if txt { "txt" } else { "rtf" }); } let r = app.run(cmd, params)?; diff --git a/crates/ui-egui/src/menus/run.rs b/crates/ui-egui/src/menus/run.rs index 731efb570..054bd8060 100644 --- a/crates/ui-egui/src/menus/run.rs +++ b/crates/ui-egui/src/menus/run.rs @@ -142,8 +142,7 @@ fn run_ui_part1(app: &mut DesignApp, id: &str, p: &Value) -> Option { // Word/RTF Import Options: style mapping and conflicts before the text lands. let Some(path) = p.get("path").and_then(Value::as_str) else { return Some(rerun_with_path(app, "place", id, p, "path")) }; - let l = path.to_lowercase(); - if !(l.ends_with(".docx") || l.ends_with(".rtf")) { + if !matches!(craft_file_formats::detect(path, &[]), Some(craft_file_formats::Format::Docx | craft_file_formats::Format::Rtf)) { return Some(app.place_file(json!({"path": path}))); } let info = match app.run("place.styles", json!({"path": path})) {