mirror of
https://github.com/storytold/designcraft.git
synced 2026-10-28 20:26:57 -06:00
Print color: plain print exports blend in CMYK without an output intent
Repin craft-pdf, craft-prepress and krilla to craft-libs#190 at 8fbfea8. Plain exports with a CMYK blend space use print::cmyk_blending (CMYK page and transparency groups, no output intent, no /Trapped); PDF/X-4 keeps print::settings with the working space as GTS_PDFX output intent, as InDesign writes an output intent only for PDF/X.
This commit is contained in:
Generated
+2
-2
@@ -832,7 +832,7 @@ source = "git+https://github.com/storytold/craft-libs?rev=82a24b006eefa036b43c09
|
||||
[[package]]
|
||||
name = "craft-pdf"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/aiongg/craft-libs?rev=0fe6156614de2e8780f34612fa6a577c745f35bc#0fe6156614de2e8780f34612fa6a577c745f35bc"
|
||||
source = "git+https://github.com/aiongg/craft-libs?rev=8fbfea8840f0a0f5a22df18901e9db324894b2ce#8fbfea8840f0a0f5a22df18901e9db324894b2ce"
|
||||
dependencies = [
|
||||
"krilla",
|
||||
"thiserror 2.0.21",
|
||||
@@ -2542,7 +2542,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "krilla"
|
||||
version = "0.8.2"
|
||||
source = "git+https://github.com/aiongg/craft-libs?rev=0fe6156614de2e8780f34612fa6a577c745f35bc#0fe6156614de2e8780f34612fa6a577c745f35bc"
|
||||
source = "git+https://github.com/aiongg/craft-libs?rev=8fbfea8840f0a0f5a22df18901e9db324894b2ce#8fbfea8840f0a0f5a22df18901e9db324894b2ce"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"bumpalo",
|
||||
|
||||
+3
-3
@@ -12,9 +12,9 @@ repository = "https://github.com/storytold/designcraft"
|
||||
|
||||
[workspace.dependencies]
|
||||
# Shared writer; the immutable revision must be published before this consumer.
|
||||
craft-pdf = { git = "https://github.com/aiongg/craft-libs", rev = "0fe6156614de2e8780f34612fa6a577c745f35bc", version = "=0.1.0", default-features = false }
|
||||
craft-pdf = { git = "https://github.com/aiongg/craft-libs", rev = "8fbfea8840f0a0f5a22df18901e9db324894b2ce", version = "=0.1.0", default-features = false }
|
||||
# Separations, ink coverage and the ink manager; same revision as craft-pdf and the krilla patch.
|
||||
craft-prepress = { git = "https://github.com/aiongg/craft-libs", rev = "0fe6156614de2e8780f34612fa6a577c745f35bc", version = "=0.1.0" }
|
||||
craft-prepress = { git = "https://github.com/aiongg/craft-libs", rev = "8fbfea8840f0a0f5a22df18901e9db324894b2ce", version = "=0.1.0" }
|
||||
# internal
|
||||
designcraft-geom = { path = "crates/geom" }
|
||||
designcraft-color = { path = "crates/color" }
|
||||
@@ -119,7 +119,7 @@ debug = 0
|
||||
# Opt-in cancelable macOS termination on the pinned winit 0.30 line.
|
||||
[patch.crates-io]
|
||||
winit = { git = "https://github.com/skylartaylor/winit.git", rev = "3d51077fd1a966f84df9ef287839dc84fd5b4ca7" }
|
||||
krilla = { git = "https://github.com/aiongg/craft-libs", rev = "0fe6156614de2e8780f34612fa6a577c745f35bc", version = "=0.8.2" }
|
||||
krilla = { git = "https://github.com/aiongg/craft-libs", rev = "8fbfea8840f0a0f5a22df18901e9db324894b2ce", version = "=0.8.2" }
|
||||
# The hayro crates from pdfcraft's vendor/ directory, with the patches its README lists.
|
||||
hayro = { git = "https://github.com/storytold/pdfcraft", rev = "b5467d93a0ffaa5e5e6658bccdf8655a3ba689c9" }
|
||||
hayro-interpret = { git = "https://github.com/storytold/pdfcraft", rev = "b5467d93a0ffaa5e5e6658bccdf8655a3ba689c9" }
|
||||
|
||||
+2
-2
@@ -63,7 +63,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) — exported PDFs blend in that space: CMYK print exports write their transparency groups and page groups in DeviceCMYK and carry the CMYK working space as a `GTS_PDFX` output intent with `/Trapped /False`; RGB exports give pages with transparency a DeviceRGB page group. On screen, spreads with transparency in a CMYK blend space are shown through the working CMYK profile (their colours keep to its gamut, as when composited in CMYK).
|
||||
- Edit ▸ Transparency Blend Space (`edit.transparencyBlendSpace`; RGB for web/mobile documents; IDML TransparencyPreference) — exported PDFs blend in that space: CMYK print exports write their transparency groups and page groups in DeviceCMYK (PDF/X-4 also carries the CMYK working space as its `GTS_PDFX` output intent); RGB exports give pages with transparency a DeviceRGB page group. On screen, spreads with transparency in a CMYK blend space are shown through the working CMYK profile (their colours keep to its gamut, as when composited in CMYK).
|
||||
- High Contrast interface theme (fifth Interface Color Theme: black chrome, white text, yellow selection).
|
||||
- Place Excel workbooks (.xlsx): the first worksheet's used range becomes a table (shared/inline strings, numbers, booleans).
|
||||
- Object Library (`library.new/open/add/place/remove/list/json/close`, Library panel): `.dclib` files of snippets that keep stories, styles, swatches and images.
|
||||
@@ -348,7 +348,7 @@ list) · [roadmap.md](docs/roadmap.md) (milestones, plan) · [parity-checklist.m
|
||||
|
||||
Newest first.
|
||||
|
||||
- **2026-10-20** · Print color (craft-libs#190): CMYK print PDFs blend in DeviceCMYK and carry the CMYK working space as their output intent through craft-pdf's print settings instead of byte rewrites; overprint (Overprint Fill, Stroke and Gap, overprinting text, 100% [Black]) is written as `/op` `/OP` `/OPM 1`; Knockout Group writes `/K true` instead of becoming an image; File › Print prints the PDF Export PDF writes. No readiness percentage change.
|
||||
- **2026-10-20** · Print color (craft-libs#190): CMYK print PDFs blend in DeviceCMYK through craft-pdf's print settings instead of byte rewrites, with the CMYK working space as the output intent in PDF/X-4 only; overprint (Overprint Fill, Stroke and Gap, overprinting text, 100% [Black]) is written as `/op` `/OP` `/OPM 1`; Knockout Group writes `/K true` instead of becoming an image; File › Print prints the PDF Export PDF writes. No readiness percentage change.
|
||||
- **2026-10-20** · Spot gradients print on their plates. A gradient stop keeps the swatch it was given (Gradient panel drop, `swatch.create {gradient}`, `object.gradient`, IDML `StopColor`), following its renames and edits. PDF export shades one spot with its tints or [Paper] as a Separation, and a spot with process inks or a second spot as DeviceN; a process gradient to [Paper] prints in DeviceCMYK. Documents with plain-colour stops export as before. No readiness percentage change.
|
||||
- **2026-10-16** · Type › Reload Fonts (`font.reload`): fonts installed, edited or removed while DesignCraft runs are read again without a restart, and the open documents recompose with them (#328, #547). An edited font file is noticed by its length and modification time; bundled, added and document fonts stay as they are. InDesign does this by itself, so noticing changes automatically is still open (gap 6). No readiness percentage change.
|
||||
- **2026-10-16** · Parents can be deleted from their Pages-panel context menu or `layout.parents.delete {id}`. Applied-page and based-on references are cleared, local override copies survive, and Undo restores the parent. Addresses the deletion portion of #956; no readiness percentage change.
|
||||
|
||||
@@ -129,9 +129,9 @@ pub(crate) fn mark_start(opts: &PdfOptions, bleed: [f64; 4]) -> f64 {
|
||||
bleed.iter().copied().fold(opts.marks.offset.max(0.0), f64::max)
|
||||
}
|
||||
|
||||
/// Whether an export is CMYK print output: transparency blends in CMYK and the document's CMYK
|
||||
/// working space is the output intent. True when the document blends in CMYK, and always in
|
||||
/// PDF/X; PDF/A exports are RGB.
|
||||
/// Whether an export is CMYK print output: transparency blends in CMYK, in the document's CMYK
|
||||
/// working space (which PDF/X also carries as its output intent). True when the document blends
|
||||
/// in CMYK, and always in PDF/X; PDF/A exports are RGB.
|
||||
pub fn print_color(doc: &Document, opts: &PdfOptions) -> bool {
|
||||
opts.standard != Standard::PdfA2b && (doc.settings.blend_space == designcraft_doc::BlendSpace::Cmyk || opts.standard == Standard::PdfX4)
|
||||
}
|
||||
@@ -151,7 +151,8 @@ fn export_pdf_with_report_inner(doc: &Document, cache: &Cache, opts: &PdfOptions
|
||||
let configuration = write::configuration(version, archival)?;
|
||||
let mut warnings = Vec::new();
|
||||
let base = if print_color(doc, opts) {
|
||||
let (settings, warning) = crate::pdfx::print_settings()?;
|
||||
// An output intent only for PDF/X, as in InDesign.
|
||||
let (settings, warning) = crate::pdfx::print_settings(opts.standard == Standard::PdfX4)?;
|
||||
warnings.extend(warning);
|
||||
let pdfx_version = (opts.standard == Standard::PdfX4).then(|| "PDF/X-4".to_string());
|
||||
krilla::SerializeSettings { pdfx_version, ..settings }
|
||||
@@ -350,7 +351,7 @@ fn export_booklet_inner(doc: &Document, cache: &Cache, opts: &BookletOptions) ->
|
||||
let pdf_opts = PdfOptions { title: opts.title.clone(), ..PdfOptions::default() };
|
||||
let mut warnings = Vec::new();
|
||||
let base = if print_color(doc, &pdf_opts) {
|
||||
let (settings, warning) = crate::pdfx::print_settings()?;
|
||||
let (settings, warning) = crate::pdfx::print_settings(false)?;
|
||||
warnings.extend(warning);
|
||||
settings
|
||||
} else {
|
||||
|
||||
+20
-21
@@ -15,32 +15,32 @@ use craft_pdf::write::print::{self, PrintColor};
|
||||
|
||||
use crate::transitions::{find, int_after, object, rfind};
|
||||
|
||||
/// The serialization settings for CMYK print output: CMYK blending groups, the document's CMYK
|
||||
/// working space as the `GTS_PDFX` output intent (Coated FOGRA39 when it has none or its profile
|
||||
/// can't be written) and `/Trapped /False`. The second value is a warning when the working
|
||||
/// space's profile was replaced.
|
||||
pub(crate) fn print_settings() -> crate::Result<(krilla::SerializeSettings, Option<String>)> {
|
||||
print_settings_with(&designcraft_color::cms::active())
|
||||
/// The serialization settings for CMYK print output: DeviceCMYK and CMYK blending groups in the
|
||||
/// document's CMYK working space (Coated FOGRA39 when it has none or its profile can't be
|
||||
/// written). With `intent` (PDF/X), the working space is also the `GTS_PDFX` output intent, with
|
||||
/// `/Trapped /False`. The second value is a warning when the working space's profile was replaced.
|
||||
pub(crate) fn print_settings(intent: bool) -> crate::Result<(krilla::SerializeSettings, Option<String>)> {
|
||||
print_settings_with(&designcraft_color::cms::active(), intent)
|
||||
}
|
||||
|
||||
/// [`print_settings`] for the working spaces of `cms`.
|
||||
pub(crate) fn print_settings_with(cms: &designcraft_color::cms::Cms) -> crate::Result<(krilla::SerializeSettings, Option<String>)> {
|
||||
let fogra39 = || print::settings(PrintColor::fogra39(craft_cms::builtin::COATED_FOGRA39_ICC));
|
||||
let Ok(intent) = cms.output_intent() else { return Ok((fogra39()?, None)) };
|
||||
let registered = intent.identifier != "Custom";
|
||||
pub(crate) fn print_settings_with(cms: &designcraft_color::cms::Cms, intent: bool) -> crate::Result<(krilla::SerializeSettings, Option<String>)> {
|
||||
let write = |color: PrintColor<'_>| if intent { print::settings(color) } else { print::cmyk_blending(color.profile) };
|
||||
let fogra39 = || write(PrintColor::fogra39(craft_cms::builtin::COATED_FOGRA39_ICC));
|
||||
let Ok(space) = cms.output_intent() else { return Ok((fogra39()?, None)) };
|
||||
let registered = space.identifier != "Custom";
|
||||
let color = PrintColor {
|
||||
profile: &intent.icc,
|
||||
condition_identifier: intent.identifier,
|
||||
condition: Some(&intent.condition),
|
||||
profile: &space.icc,
|
||||
condition_identifier: space.identifier,
|
||||
condition: Some(&space.condition),
|
||||
registry_name: registered.then_some("http://www.color.org"),
|
||||
info: Some(&intent.condition),
|
||||
info: Some(&space.condition),
|
||||
};
|
||||
match print::settings(color) {
|
||||
match write(color) {
|
||||
Ok(settings) => Ok((settings, None)),
|
||||
Err(WriteError::InvalidProfile) => Ok((
|
||||
fogra39()?,
|
||||
Some(format!("the CMYK working space `{}` couldn't be written as the output intent; Coated FOGRA39 was used", intent.condition)),
|
||||
)),
|
||||
Err(WriteError::InvalidProfile) => {
|
||||
Ok((fogra39()?, Some(format!("the CMYK working space `{}` couldn't be written to the PDF; Coated FOGRA39 was used", space.condition))))
|
||||
}
|
||||
Err(e) => Err(e.into()),
|
||||
}
|
||||
}
|
||||
@@ -516,8 +516,7 @@ mod tests {
|
||||
let rgb: Vec<&String> = r.warnings.iter().filter(|w| w.contains("RGB")).collect();
|
||||
assert_eq!(rgb.len(), 1, "{:?}", r.warnings);
|
||||
assert!(rgb[0].starts_with("PDF/X-4: an RGB image (object ") && rgb[0].contains("4 × 2 px) on page 1:"), "{}", rgb[0]);
|
||||
// A screen export (RGB blend space) has no output intent to check RGB against.
|
||||
d.settings.blend_space = designcraft_doc::BlendSpace::Rgb;
|
||||
// Without a PDF/X output intent there is nothing to check RGB against.
|
||||
let plain = export_pdf_with_report(&d, &Cache::new(), &PdfOptions::default()).unwrap();
|
||||
assert!(!check_pdfx4(&plain.bytes).iter().any(|i| i.contains("RGB")));
|
||||
}
|
||||
|
||||
@@ -87,23 +87,32 @@ fn form_groups(bytes: &[u8]) -> Vec<(Option<String>, bool, bool)> {
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Print output (a CMYK blend space) blends in DeviceCMYK and carries the CMYK working space
|
||||
/// as its output intent; a screen document (RGB blend space) is written as before.
|
||||
/// Print output (a CMYK blend space) blends in DeviceCMYK. A plain export carries no output
|
||||
/// intent and no `/Trapped`, as in InDesign; PDF/X-4 carries the CMYK working space as its output
|
||||
/// intent with `/Trapped /False`. A screen document (RGB blend space) is written as before.
|
||||
#[test]
|
||||
fn print_output_blends_in_cmyk_with_the_working_space_as_output_intent() {
|
||||
fn print_output_blends_in_cmyk_with_an_output_intent_only_in_pdfx() {
|
||||
let mut d = transparent_doc();
|
||||
let bytes = export_pdf(&d, &Cache::new(), &PdfOptions::default()).unwrap();
|
||||
assert_eq!(page_group_spaces(&bytes), [Some("DeviceCMYK".to_string())]);
|
||||
assert!(!form_groups(&bytes).is_empty());
|
||||
assert!(form_groups(&bytes).iter().all(|(cs, ..)| cs.is_none()), "content groups inherit the page group: {:?}", form_groups(&bytes));
|
||||
assert!(!crate::has_rgb_groups(&bytes));
|
||||
let intent = output_intent(&bytes).expect("output intent");
|
||||
assert!(output_intent(&bytes).is_none(), "no output intent outside PDF/X");
|
||||
assert!(!String::from_utf8_lossy(&bytes).contains("/OutputIntents"));
|
||||
assert!(!String::from_utf8_lossy(&bytes).contains("/Trapped"));
|
||||
|
||||
let x4 = export_pdf(&d, &Cache::new(), &PdfOptions { standard: crate::Standard::PdfX4, ..PdfOptions::default() }).unwrap();
|
||||
assert_eq!(page_group_spaces(&x4), [Some("DeviceCMYK".to_string())]);
|
||||
let intent = output_intent(&x4).expect("output intent");
|
||||
assert_eq!(intent.identifier, "FOGRA39");
|
||||
assert_eq!(intent.profile, craft_cms::builtin::COATED_FOGRA39_ICC);
|
||||
assert!(String::from_utf8_lossy(&bytes).contains("/Trapped/False"));
|
||||
// A booklet of a print document too.
|
||||
assert!(String::from_utf8_lossy(&x4).contains("/Trapped/False"));
|
||||
|
||||
// A booklet of a print document blends in CMYK too, without an output intent.
|
||||
let booklet = crate::export_booklet(&d, &Cache::new(), &crate::BookletOptions::default()).unwrap();
|
||||
assert!(page_group_spaces(&booklet.bytes).contains(&Some("DeviceCMYK".to_string())));
|
||||
assert!(output_intent(&booklet.bytes).is_some());
|
||||
assert!(output_intent(&booklet.bytes).is_none());
|
||||
|
||||
d.settings.blend_space = BlendSpace::Rgb;
|
||||
let screen = export_pdf(&d, &Cache::new(), &PdfOptions::default()).unwrap();
|
||||
@@ -117,7 +126,10 @@ fn print_output_blends_in_cmyk_with_the_working_space_as_output_intent() {
|
||||
fn output_intent_is_the_cmyk_working_space() {
|
||||
use craft_pdf::write::backend as krilla;
|
||||
let generic = Cms::new(&ColorSettings { cmyk: cms::GENERIC_CMYK.into(), ..ColorSettings::default() }).unwrap();
|
||||
let (settings, warning) = crate::pdfx::print_settings_with(&generic).unwrap();
|
||||
let (plain, warning) = crate::pdfx::print_settings_with(&generic, false).unwrap();
|
||||
assert_eq!(warning, None);
|
||||
assert!(plain.output_intent.is_none() && plain.trapped.is_none() && plain.cmyk_profile.is_some());
|
||||
let (settings, warning) = crate::pdfx::print_settings_with(&generic, true).unwrap();
|
||||
assert_eq!(warning, None);
|
||||
let mut pdf = krilla::Document::new_with(settings);
|
||||
let mut page = pdf.start_page_with(krilla::page::PageSettings::new(krilla::geom::Size::from_wh(100.0, 100.0).unwrap()));
|
||||
|
||||
@@ -318,7 +318,7 @@ It is evidence of breadth: each item exists. It is not a claim of InDesign-level
|
||||
- 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) — exported PDFs blend in that space: CMYK print exports write their transparency groups and page groups in DeviceCMYK and carry the CMYK working space as a `GTS_PDFX` output intent with `/Trapped /False`; RGB exports give pages with transparency a DeviceRGB page group. On screen, CMYK spreads with separable blend modes use two ink-channel passes through the working colour settings. Parent-page items, nested groups, effects and pasteboard share that path; 100% K in Color Dodge whitens coloured areas while paper stays outside the blend backdrop. Effect blend layers sit outside their clips and offscreen filters, preserving their backdrop in RGB too. The pass count is independent of blended-object count, and unchanged viewports reuse one cached raster (64 MiB cap). Spreads containing Hue, Saturation, Color or Luminosity still use the existing RGB blend followed by a CMYK proof approximation; full nonseparable CMYK blending remains open.
|
||||
- Edit ▸ Transparency Blend Space (`edit.transparencyBlendSpace`; RGB for web/mobile documents; IDML TransparencyPreference) — exported PDFs blend in that space: CMYK print exports write their transparency groups and page groups in DeviceCMYK (PDF/X-4 also carries the CMYK working space as its `GTS_PDFX` output intent); RGB exports give pages with transparency a DeviceRGB page group. On screen, CMYK spreads with separable blend modes use two ink-channel passes through the working colour settings. Parent-page items, nested groups, effects and pasteboard share that path; 100% K in Color Dodge whitens coloured areas while paper stays outside the blend backdrop. Effect blend layers sit outside their clips and offscreen filters, preserving their backdrop in RGB too. The pass count is independent of blended-object count, and unchanged viewports reuse one cached raster (64 MiB cap). Spreads containing Hue, Saturation, Color or Luminosity still use the existing RGB blend followed by a CMYK proof approximation; full nonseparable CMYK blending remains open.
|
||||
- High Contrast interface theme (fifth Interface Color Theme: black chrome, white text, yellow selection).
|
||||
- Place Excel workbooks (.xlsx): the first worksheet's used range becomes a table (shared/inline strings, numbers, booleans).
|
||||
- Object Library (`library.new/open/add/place/remove/list/json/close`, Library panel): `.dclib` files of snippets that keep stories, styles, swatches and images.
|
||||
|
||||
Reference in New Issue
Block a user