From c2fbf1b0f6e8a4b6c1f9be66068209979b8c847b Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Fri, 2 Oct 2026 23:28:43 -0400 Subject: [PATCH] C: Transparency Blend Space setting (+ IDML) Co-Authored-By: Claude Opus 5.5 --- ROADMAP.md | 1 + crates/doc/src/build.rs | 1 + crates/doc/src/lib.rs | 13 ++++++++++++ crates/engine/src/cmd/prefs.rs | 36 ++++++++++++++++++++++++++++++++++ crates/idml/src/export.rs | 7 +++++++ crates/idml/src/import.rs | 5 +++++ crates/ui-egui/src/menus.rs | 4 ++++ docs/parity.md | 6 +++--- 8 files changed, 70 insertions(+), 3 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 8db5b0a6d..58e5c1fb4 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -49,6 +49,7 @@ DesignCraft aims at full Adobe InDesign parity — and to be better: faster, ope - Endnotes (`endnote.insert/edit/delete/list/options`, Type ▸ Insert Endnote): references number through the document, the endnote frame on a new last page lists them under a heading; IDML keeps the listed text only. - Editorial notes (`note.new/edit/delete/convertToText/list`, Notes panel): anchored in text, flagged on screen, never printed or exported. - File ▸ Print Booklet (`file.printBooklet`): saddle-stitch or 2-up consecutive printer spreads as PDF, padded with blanks, with a gap between pages. +- Edit ▸ Transparency Blend Space (`edit.transparencyBlendSpace`; RGB for web/mobile documents; IDML TransparencyPreference) — stored; compositing is still RGB on screen and in PDF. - Drag and drop text editing: drag selected text to move it (Alt copies) with its formatting (`text.release`). - Image Import Options: place any page of a multi-page PDF (`file.place {pdfPage}`; the app asks which page), shown on screen and embedded as that page in PDF export. - Define Lists: named numbered lists that continue across stories in page order (`list.define`, paragraph `listName` / `startAt`), round-tripped through IDML; `null` in `type.para` / `type.char` now removes an override. diff --git a/crates/doc/src/build.rs b/crates/doc/src/build.rs index acd544fcb..6923868ca 100644 --- a/crates/doc/src/build.rs +++ b/crates/doc/src/build.rs @@ -115,6 +115,7 @@ impl Document { slug: nd.slug, horizontal_units: nd.units, vertical_units: nd.units, + blend_space: if nd.intent == Intent::Print { crate::BlendSpace::Cmyk } else { crate::BlendSpace::Rgb }, ..Default::default() }; let mut d = Document { diff --git a/crates/doc/src/lib.rs b/crates/doc/src/lib.rs index 4397d9649..fc5d9c95f 100644 --- a/crates/doc/src/lib.rs +++ b/crates/doc/src/lib.rs @@ -68,6 +68,16 @@ pub enum DocError { pub type Result = std::result::Result; +/// Edit › Transparency Blend Space: the colour space transparency is flattened in (CMYK for +/// print documents, RGB for screen ones, as InDesign defaults). +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum BlendSpace { + #[default] + Cmyk, + Rgb, +} + #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub enum Intent { @@ -210,6 +220,8 @@ pub struct DocSettings { /// Preferences › Advanced Type: superscript and subscript size and position (percent of the /// font size). pub advanced_type: AdvancedType, + /// Edit › Transparency Blend Space. + pub blend_space: BlendSpace, } impl Default for DocSettings { @@ -237,6 +249,7 @@ impl Default for DocSettings { chapter_number: 1, global_light: 120.0, advanced_type: AdvancedType::default(), + blend_space: BlendSpace::Cmyk, } } } diff --git a/crates/engine/src/cmd/prefs.rs b/crates/engine/src/cmd/prefs.rs index d41c57585..50f46a788 100644 --- a/crates/engine/src/cmd/prefs.rs +++ b/crates/engine/src/cmd/prefs.rs @@ -23,6 +23,24 @@ const DOC_KEYS: &[&str] = &[ pub fn specs() -> Vec { vec![ + cmd!( + "edit.transparencyBlendSpace", + "Transparency Blend Space", + ["Edit", "Transparency Blend Space"], + None, + "{space: cmyk|rgb} — the colour space transparency is composited in for output", + has_doc, + |s, p| { + let space: designcraft_doc::BlendSpace = p + .get("space") + .and_then(|v| serde_json::from_value(v.clone()).ok()) + .ok_or_else(|| bad("edit.transparencyBlendSpace", "`space`: cmyk or rgb"))?; + s.edit(|d, _| { + d.settings.blend_space = space; + Ok(serde_json::json!({"space": space})) + }) + } + ), cmd!( noundo "prefs.set", "Preferences", @@ -227,3 +245,21 @@ mod keep_highlight_tests { assert!(flagged(&s), "too long to keep together in one column"); } } + +#[cfg(test)] +mod blend_space_tests { + use serde_json::json; + + use crate::Session; + + #[test] + fn blend_space_is_set_and_kept_in_idml() { + let mut s = Session::new(); + s.execute("file.new", &json!({})).unwrap(); + assert_eq!(s.doc().unwrap().doc.settings.blend_space, designcraft_doc::BlendSpace::Cmyk); + s.execute("edit.transparencyBlendSpace", &json!({"space": "rgb"})).unwrap(); + let back = designcraft_idml::import_idml(&designcraft_idml::export_idml(&s.doc().unwrap().doc)).unwrap(); + assert_eq!(back.settings.blend_space, designcraft_doc::BlendSpace::Rgb); + assert!(s.execute("edit.transparencyBlendSpace", &json!({"space": "lab"})).is_err()); + } +} diff --git a/crates/idml/src/export.rs b/crates/idml/src/export.rs index 674ca3073..82d9a5a0a 100644 --- a/crates/idml/src/export.rs +++ b/crates/idml/src/export.rs @@ -704,6 +704,13 @@ impl<'a> Ex<'a> { ); root.push(El::new("PasteboardPreference").attr("PasteboardMargins", pt(s.pasteboard.0, s.pasteboard.1))); root.push(self.footnote_option_el()); + root.push(El::new("TransparencyPreference").attr( + "BlendingSpace", + match s.blend_space { + designcraft_doc::BlendSpace::Cmyk => "CMYK", + designcraft_doc::BlendSpace::Rgb => "RGB", + }, + )); let a = &s.advanced_type; root.push( El::new("TextPreference") diff --git a/crates/idml/src/import.rs b/crates/idml/src/import.rs index 176ec97b8..0bed29b1f 100644 --- a/crates/idml/src/import.rs +++ b/crates/idml/src/import.rs @@ -219,6 +219,11 @@ impl<'r> Importer<'r> { for e in top { match e.local() { "DocumentPreference" => self.doc_prefs(e), + "TransparencyPreference" => { + if let Some(v) = e.get("BlendingSpace") { + self.settings.blend_space = if v == "RGB" { designcraft_doc::BlendSpace::Rgb } else { designcraft_doc::BlendSpace::Cmyk }; + } + } "TextPreference" => { let a = &mut self.settings.advanced_type; for (k, v) in [ diff --git a/crates/ui-egui/src/menus.rs b/crates/ui-egui/src/menus.rs index 4b4be09ac..6a0c4ed57 100644 --- a/crates/ui-egui/src/menus.rs +++ b/crates/ui-egui/src/menus.rs @@ -146,6 +146,10 @@ pub const MENUS: &[(&str, &[&str])] = &[ "cmd:spelling.check", "cmd:spelling.addWord", "<", + ">Transparency Blend Space", + "cmd:edit.transparencyBlendSpace|Document RGB|{\"space\": \"rgb\"}", + "cmd:edit.transparencyBlendSpace|Document CMYK|{\"space\": \"cmyk\"}", + "<", "-", "ui:app.palette", "ui:app.keyboardShortcuts", diff --git a/docs/parity.md b/docs/parity.md index 4253ed101..1f21c6333 100644 --- a/docs/parity.md +++ b/docs/parity.md @@ -10,7 +10,7 @@ Each row of the InDesign feature catalogue is scored **D** (done), **P** (partia | Priority | Features | Done | Partial | Missing | Parity | |---|---|---|---|---|---| | P0 | 102 | 99 | 3 | 0 | 99% | -| P1 | 97 | 75 | 20 | 2 | 88% | +| P1 | 97 | 75 | 21 | 1 | 88% | | P2 | 76 | 19 | 13 | 44 | 34% | | Area | Parity | @@ -21,7 +21,7 @@ Each row of the InDesign feature catalogue is scored **D** (done), **P** (partia | Frames, shapes & paths | 93% | | Transform | 100% | | Fill, stroke, colour | 88% | -| Effects & transparency | 62% | +| Effects & transparency | 71% | | Placing & links | 82% | | Type & text frames | 92% | | Typography | 88% | @@ -164,7 +164,7 @@ Open P0 items: Preferences, Vector/page formats (PDF/AI/SVG/EPS place), Export t | Effects & transparency | Inner Shadow, Outer Glow, Inner Glow, Bevel, Satin | P2 | P | | Effects & transparency | Basic/Directional/Gradient Feather | P2 | P | | Effects & transparency | Global Light | P2 | D | -| Effects & transparency | Transparency blend space | P1 | M | +| Effects & transparency | Transparency blend space | P1 | P | | Effects & transparency | Transparency flattener | P2 | M | | Placing & links | Place | P0 | D | | Placing & links | Place into frame / by click or drag | P0 | D |