From 5d0b966548fa8f4bb72ad7480429abd1165c6f4e Mon Sep 17 00:00:00 2001 From: 00xJ0 <56056764+00xJ0@users.noreply.github.com> Date: Sun, 18 Oct 2026 18:06:24 -0700 Subject: [PATCH 1/3] PDF presets: Press Quality converts to the document's profile, keeping its numbers (#2950) Press Quality left Output at No conversion, so in a CMYK document RGB images went to the press as DeviceRGB. It now converts to the destination with the numbers kept, the destination blank (the document's profile): RGB comes out CMYK in a CMYK document, CMYK values stay exactly, RGB documents are unchanged, and no profiles are included, as a printer's published [Press Quality] settings list. --- crates/pdf/src/presets.rs | 16 ++++++++++++---- crates/pdf/src/tests_cmykimages.rs | 18 ++++++++++++++++++ 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/crates/pdf/src/presets.rs b/crates/pdf/src/presets.rs index cf7ae0fe3..a386e7493 100644 --- a/crates/pdf/src/presets.rs +++ b/crates/pdf/src/presets.rs @@ -5,8 +5,8 @@ use serde::{Deserialize, Serialize}; use crate::{ - BleedSettings, Compatibility, CompressionSettings, Downsample, ImageCodec, ImageCompression, JpegQuality, MonoCodec, MonoCompression, - PdfSettings, Standard, + BleedSettings, ColorConversion, Compatibility, CompressionSettings, Downsample, ImageCodec, ImageCompression, JpegQuality, MonoCodec, + MonoCompression, OutputSettings, PdfSettings, Standard, }; /// The built-in preset every PDF export starts from. @@ -62,10 +62,18 @@ pub fn builtin_presets() -> Vec { PdfSettings { preserve_editing: true, ..Default::default() }, ), preset("High Quality Print", "For desktop printers and proofs: images stay at print resolution.", print(ImageCodec::Auto, false)), + // Converted to the document's profile, its own numbers kept: in a CMYK document RGB colours + // and images come out CMYK, as a press needs (#2950). A printer's published settings for + // the [Press Quality] preset list the same Output: Convert to Destination (Preserve + // Numbers), Document CMYK, no profiles included + // (https://postmediasolutions.com/hubfs/Website/Client%20Resources/PostmediaPrintPDFSpecSheet.pdf). preset( "Press Quality", - "For commercial printing: high-resolution images compressed losslessly, the document's bleed.", - print(ImageCodec::Zip, true), + "For commercial printing: high-resolution images compressed losslessly, the document's bleed, colours converted to the document's profile with its own numbers kept (RGB comes out CMYK in a CMYK document).", + PdfSettings { + output: OutputSettings { conversion: ColorConversion::PreserveNumbers, ..Default::default() }, + ..print(ImageCodec::Zip, true) + }, ), preset( "Smallest File Size", diff --git a/crates/pdf/src/tests_cmykimages.rs b/crates/pdf/src/tests_cmykimages.rs index 65f93d3dc..afa71f44d 100644 --- a/crates/pdf/src/tests_cmykimages.rs +++ b/crates/pdf/src/tests_cmykimages.rs @@ -429,3 +429,21 @@ fn a_cmyk_image_masked_by_a_grey_image_of_its_grid_writes_it_as_its_smask() { assert_eq!(smasks, [None]); assert!(String::from_utf8_lossy(&bytes).contains("/S/Luminosity"), "a luminosity soft mask"); } + +/// #2950: Press Quality converts to the document's profile with its numbers kept, so in a CMYK +/// document an RGB image comes out CMYK, as the press needs; with no conversion (the default +/// preset) it stays RGB, as before. +#[test] +fn press_quality_writes_an_rgb_image_in_a_cmyk_document_as_cmyk() { + let rgb = image::RgbImage::from_fn(4, 4, |x, y| image::Rgb([(x * 60) as u8, (y * 60) as u8, 128])); + let blob = ImageBlob { mime: "image/png".into(), bytes: Arc::new(png(rgb)), proxy: None }; + let d = image_doc(blob, 4, 4); + let press = builtin_presets().into_iter().find(|p| p.name == "Press Quality").unwrap().settings; + assert_eq!(press.output.conversion, ColorConversion::PreserveNumbers); + let options = PdfOptions { settings: press, created: Some(1_700_000_000), ..Default::default() }; + let w = written(&export_with_report(&d, &options).unwrap().bytes); + assert_eq!(w.len(), 1, "{w:?}"); + assert_eq!((w[0].space.as_str(), w[0].icc_n), ("DeviceCMYK", None), "Press Quality"); + let w = written(&export_with_report(&d, &opts(json!({}))).unwrap().bytes); + assert_eq!(w[0].space, "DeviceRGB", "no conversion"); +} From 91107c89d2d7211a0d5ff88aed758a618a77305e Mon Sep 17 00:00:00 2001 From: 00xJ0 <56056764+00xJ0@users.noreply.github.com> Date: Mon, 19 Oct 2026 12:29:20 -0700 Subject: [PATCH 2/3] PDF: a Document CMYK destination; Press Quality converts to it in RGB documents too (#3012 review) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A blank destination is the document's own profile, so in an RGB document Press Quality (Preserve Numbers, blank) converted CMYK colours and images to RGB. Output › Destination can now be DOCUMENT_CMYK, the CMYK profile of the colour settings in effect whatever the document's colour mode (Illustrator's "Document CMYK"); Press Quality uses it, and the Save PDF dialog lists it after Document profile. Test: in an RGB document a CMYK image keeps its inks and an RGB image comes out CMYK. --- crates/pdf/src/output.rs | 2 ++ crates/pdf/src/presets.rs | 12 ++++++------ crates/pdf/src/settings.rs | 7 ++++++- crates/pdf/src/tests_cmykimages.rs | 20 ++++++++++++++++++++ crates/ui-egui/src/dialogs/save_pdf.rs | 7 ++++++- 5 files changed, 40 insertions(+), 8 deletions(-) diff --git a/crates/pdf/src/output.rs b/crates/pdf/src/output.rs index 710bbe82f..1fb13c8ec 100644 --- a/crates/pdf/src/output.rs +++ b/crates/pdf/src/output.rs @@ -160,6 +160,8 @@ fn destination(set: &PdfSettings, mode: ColorMode, source: &Cms) -> Result source.settings().cmyk.as_str(), (false, _) => name, (true, _) if cmyk_only => fallback.as_str(), (true, ColorMode::Cmyk) => source.settings().cmyk.as_str(), diff --git a/crates/pdf/src/presets.rs b/crates/pdf/src/presets.rs index a386e7493..df592804b 100644 --- a/crates/pdf/src/presets.rs +++ b/crates/pdf/src/presets.rs @@ -5,8 +5,8 @@ use serde::{Deserialize, Serialize}; use crate::{ - BleedSettings, ColorConversion, Compatibility, CompressionSettings, Downsample, ImageCodec, ImageCompression, JpegQuality, MonoCodec, - MonoCompression, OutputSettings, PdfSettings, Standard, + BleedSettings, ColorConversion, Compatibility, CompressionSettings, DOCUMENT_CMYK, Downsample, ImageCodec, ImageCompression, JpegQuality, + MonoCodec, MonoCompression, OutputSettings, PdfSettings, Standard, }; /// The built-in preset every PDF export starts from. @@ -62,16 +62,16 @@ pub fn builtin_presets() -> Vec { PdfSettings { preserve_editing: true, ..Default::default() }, ), preset("High Quality Print", "For desktop printers and proofs: images stay at print resolution.", print(ImageCodec::Auto, false)), - // Converted to the document's profile, its own numbers kept: in a CMYK document RGB colours - // and images come out CMYK, as a press needs (#2950). A printer's published settings for + // Converted to Document CMYK, the numbers of colours already in it kept: RGB colours and + // images come out CMYK, in an RGB document too, as a press needs (#2950). A printer's published settings for // the [Press Quality] preset list the same Output: Convert to Destination (Preserve // Numbers), Document CMYK, no profiles included // (https://postmediasolutions.com/hubfs/Website/Client%20Resources/PostmediaPrintPDFSpecSheet.pdf). preset( "Press Quality", - "For commercial printing: high-resolution images compressed losslessly, the document's bleed, colours converted to the document's profile with its own numbers kept (RGB comes out CMYK in a CMYK document).", + "For commercial printing: high-resolution images compressed losslessly, the document's bleed, and colours converted to the CMYK profile in effect (Document CMYK), keeping the numbers of colours already in it.", PdfSettings { - output: OutputSettings { conversion: ColorConversion::PreserveNumbers, ..Default::default() }, + output: OutputSettings { conversion: ColorConversion::PreserveNumbers, destination: DOCUMENT_CMYK.into(), ..Default::default() }, ..print(ImageCodec::Zip, true) }, ), diff --git a/crates/pdf/src/settings.rs b/crates/pdf/src/settings.rs index 329e8c7fd..17f37f90b 100644 --- a/crates/pdf/src/settings.rs +++ b/crates/pdf/src/settings.rs @@ -347,12 +347,17 @@ impl BleedSettings { } } +/// The Output destination that names the CMYK profile of the colour settings in effect, in an RGB +/// document as in a CMYK one (Illustrator's "Document CMYK"; #2950). +pub const DOCUMENT_CMYK: &str = "Document CMYK"; + /// The Output section. #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] #[serde(default, rename_all = "camelCase")] pub struct OutputSettings { pub conversion: ColorConversion, - /// Destination profile name (empty: the document's profile). + /// Destination profile name (empty: the document's profile; [`DOCUMENT_CMYK`]: the CMYK + /// profile in effect, whatever the document's colour mode). pub destination: String, pub profiles: ProfileInclusion, /// PDF/X output intent profile name. diff --git a/crates/pdf/src/tests_cmykimages.rs b/crates/pdf/src/tests_cmykimages.rs index afa71f44d..b9fdc26dd 100644 --- a/crates/pdf/src/tests_cmykimages.rs +++ b/crates/pdf/src/tests_cmykimages.rs @@ -447,3 +447,23 @@ fn press_quality_writes_an_rgb_image_in_a_cmyk_document_as_cmyk() { let w = written(&export_with_report(&d, &opts(json!({}))).unwrap().bytes); assert_eq!(w[0].space, "DeviceRGB", "no conversion"); } + +/// #3012 review: in an RGB document Press Quality converts to Document CMYK too, the CMYK +/// profile in effect: a CMYK image keeps its inks (not converted to RGB), and an RGB image comes +/// out CMYK. +#[test] +fn press_quality_writes_cmyk_in_an_rgb_document_too() { + let press = || PdfOptions { settings: builtin_preset("Press Quality").unwrap().settings, created: Some(1_700_000_000), ..Default::default() }; + assert_eq!(press().settings.output.destination, DOCUMENT_CMYK); + let ink = [0u8, 102, 255, 0]; + let tiff = ImageBlob::cmyk_tiff(&Inks::new(4, 4, flat(ink, 16)).unwrap()).unwrap(); + let rgb = image::RgbImage::from_fn(4, 4, |x, y| image::Rgb([(x * 60) as u8, (y * 60) as u8, 128])); + let png_blob = ImageBlob { mime: "image/png".into(), bytes: Arc::new(png(rgb)), proxy: None }; + for (what, blob) in [("CMYK image", tiff), ("RGB image", png_blob)] { + let mut d = image_doc(blob, 4, 4); + d.color_mode = ColorMode::Rgb; + let w = written(&export_with_report(&d, &press()).unwrap().bytes); + assert_eq!(w.len(), 1, "{what}: {w:?}"); + assert_eq!(w[0].space, "DeviceCMYK", "{what} in an RGB document"); + } +} diff --git a/crates/ui-egui/src/dialogs/save_pdf.rs b/crates/ui-egui/src/dialogs/save_pdf.rs index 5b2734a23..5b4f99188 100644 --- a/crates/ui-egui/src/dialogs/save_pdf.rs +++ b/crates/ui-egui/src/dialogs/save_pdf.rs @@ -599,7 +599,12 @@ fn output(ui: &mut egui::Ui, d: &mut Dialog) { }); // PDF/X-1a converts to CMYK even without a conversion. let converting = get(d, "output.conversion") != ColorConversion::None.id() || standard.cmyk_only(); - row(ui, tl!("Destination:"), |ui| profile_pick(ui, d, "output.destination", &names(DOCUMENT_PROFILE), 1, converting)); + // The document's own profile (blank), the CMYK profile in effect, then every profile. + let destinations: Vec<&str> = std::iter::once(DOCUMENT_PROFILE) + .chain(std::iter::once(vectorcraft_pdf::DOCUMENT_CMYK)) + .chain(profiles.iter().map(|p| p.name.as_str())) + .collect(); + row(ui, tl!("Destination:"), |ui| profile_pick(ui, d, "output.destination", &destinations, 2, converting)); row(ui, tl!("Profile inclusion:"), |ui| { // A standard decides whether colours are tagged. ui.add_enabled_ui(standard == Standard::None, |ui| pick::(ui, d, "output.profiles", 300.0, |_| true)); From 096544db40827cdee928e1a5603bc7ce30847605 Mon Sep 17 00:00:00 2001 From: 00xJ0 <56056764+00xJ0@users.noreply.github.com> Date: Mon, 19 Oct 2026 13:03:25 -0700 Subject: [PATCH 3/3] Test: Press Quality keeps a CMYK image's inks in an RGB document (#3012 review) The colour space alone could pass a conversion that changed the inks: the exported image is opened again and its inks compared with the ones placed. --- crates/pdf/src/tests_cmykimages.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/crates/pdf/src/tests_cmykimages.rs b/crates/pdf/src/tests_cmykimages.rs index b9fdc26dd..67e67f26c 100644 --- a/crates/pdf/src/tests_cmykimages.rs +++ b/crates/pdf/src/tests_cmykimages.rs @@ -462,8 +462,14 @@ fn press_quality_writes_cmyk_in_an_rgb_document_too() { for (what, blob) in [("CMYK image", tiff), ("RGB image", png_blob)] { let mut d = image_doc(blob, 4, 4); d.color_mode = ColorMode::Rgb; - let w = written(&export_with_report(&d, &press()).unwrap().bytes); + let out = export_with_report(&d, &press()).unwrap(); + let w = written(&out.bytes); assert_eq!(w.len(), 1, "{what}: {w:?}"); assert_eq!(w[0].space, "DeviceCMYK", "{what} in an RGB document"); + // Preserve Numbers: the CMYK image comes back with the inks it had. + if what == "CMYK image" { + let back = import(&out.bytes).unwrap(); + assert_eq!(all_images(&back)[0].cmyk().unwrap().data, flat(ink, 16), "{what} inks"); + } } }