mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-02 22:20:50 -06:00
M4.73: PDF colour conversion, ICC profiles and output intent
Save PDF > Output now applies: colours convert to a destination profile (destination, or preserveNumbers keeping the numbers of colours already in its model; grey stays grey; images and freeform gradient images convert too; printer's marks don't). Profile inclusion writes ICC-based colour spaces (CMYK with the destination's or the document's profile, RGB as sRGB, grey), and transparency in CMYK output blends in the CMYK profile's space. The output intent profile is embedded as the catalog's /GTS_PDFX output intent with its condition, identifier and registry, and Trapped goes in the document information (PDF/A keeps its own). A shared patch module edits written PDFs (Lab spot alternates use it too). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
51f6817a0b
commit
5b69862a08
@@ -893,6 +893,8 @@ mod tests_dxfimport;
|
||||
#[cfg(test)]
|
||||
mod tests_metafile;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests_pdfoutput;
|
||||
#[cfg(test)]
|
||||
mod tests_tiffbmp;
|
||||
|
||||
|
||||
@@ -46,7 +46,12 @@ pub(super) const OPTIONS: &[FormatOption] = &[
|
||||
default: "null",
|
||||
description: "{useDocument, top, bottom, left, right} in points: the page grows by the bleed (BleedBox) around its artboard (TrimBox)",
|
||||
},
|
||||
FormatOption { name: "output", ty: "object", default: "null", description: "colour conversion and output intent (see document.exportPdf)" },
|
||||
FormatOption {
|
||||
name: "output",
|
||||
ty: "object",
|
||||
default: "null",
|
||||
description: "colour conversion to a destination profile, ICC profiles, output intent and Trapped (see document.exportPdf)",
|
||||
},
|
||||
FormatOption { name: "advanced", ty: "object", default: "null", description: "{fontSubsetPercent, outlineText, overprint}" },
|
||||
FormatOption {
|
||||
name: "security",
|
||||
@@ -69,7 +74,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"Export PDF",
|
||||
[],
|
||||
None,
|
||||
"{path?, preset?: \"VectorCraft Default\" (built-in or saved: pdf.preset.list), artboard? | artboards?: [i…] | range?: \"1-3, 5\" (1-based; default all, one page each), standard?: none|pdfA2b|pdfX1a|pdfX3|pdfX4, compatibility?: 1.4|1.5|1.6|1.7 (default)|2.0, preserveEditing? (on in VectorCraft Default: the native document as an embedded file, which document.open restores; choosing a standard turns it off, PDF/A refuses it), thumbnails?, fastWebView?, viewAfterSaving? (the app opens the written file), createLayers?, includeNonPrinting? (keep layers whose Print option is off; left out by default unless createLayers), compression?: {color?, gray?: {downsample: none|average|subsample|bicubic, ppi: 300, abovePpi: 450, compression: none|zip|jpeg|jpeg2000|auto, quality: minimum|low|medium|high|maximum}, mono?: {downsample, ppi: 1200, abovePpi: 1800, compression: none|ccittG3|ccittG4|zip|runLength} (black-and-white images), compressText?: true} (images above abovePpi are resampled to ppi; auto keeps JPEGs JPEG, the others lossless; JPEG needs opaque images; none, jpeg2000, CCITT and runLength are written as ZIP with a warning), marks?: {trim, registration, colorBars, pageInfo, kind: roman|japanese, weight: 0.25 (trim marks and targets), offset: 6 (pt from the artboard, at least the bleed)} (printer's marks in [Registration], every plate: trim marks, registration targets mid-side, CMYK/spot/black-tint colour bars on top, page information (title, artboard, date UTC) below), bleed?: {useDocument (the document's bleed, document.setup), top, bottom, left, right} (pt; art in the bleed is kept). Each page: TrimBox = artboard, BleedBox = artboard + bleed, MediaBox = that + the marks' room (art clipped to the BleedBox), output?: {conversion: none|destination|preserveNumbers, destination, profiles: none|all|destination|taggedSource, outputIntent, outputCondition, outputConditionId, registry, trapped}, advanced?: {fontSubsetPercent: 100, outlineText: true, overprint: preserve|discard}, security?: {openPassword, permissionsPassword, printing: none|low|high, changes: none|pages|forms|comments|any, copy, screenReader, plaintextMetadata}} → {path, bytes, warnings}; no path → {dataBase64, bytes, warnings}. The options apply over the preset (null keeps its value); options accepted but not applied yet come back as warnings; PDF/X, PDF/A-2b at 2.0 and a password with a PDF/A or PDF/X standard are refused. document.export {format: pdf} takes the same options",
|
||||
"{path?, preset?: \"VectorCraft Default\" (built-in or saved: pdf.preset.list), artboard? | artboards?: [i…] | range?: \"1-3, 5\" (1-based; default all, one page each), standard?: none|pdfA2b|pdfX1a|pdfX3|pdfX4, compatibility?: 1.4|1.5|1.6|1.7 (default)|2.0, preserveEditing? (on in VectorCraft Default: the native document as an embedded file, which document.open restores; choosing a standard turns it off, PDF/A refuses it), thumbnails?, fastWebView?, viewAfterSaving? (the app opens the written file), createLayers?, includeNonPrinting? (keep layers whose Print option is off; left out by default unless createLayers), compression?: {color?, gray?: {downsample: none|average|subsample|bicubic, ppi: 300, abovePpi: 450, compression: none|zip|jpeg|jpeg2000|auto, quality: minimum|low|medium|high|maximum}, mono?: {downsample, ppi: 1200, abovePpi: 1800, compression: none|ccittG3|ccittG4|zip|runLength} (black-and-white images), compressText?: true} (images above abovePpi are resampled to ppi; auto keeps JPEGs JPEG, the others lossless; JPEG needs opaque images; none, jpeg2000, CCITT and runLength are written as ZIP with a warning), marks?: {trim, registration, colorBars, pageInfo, kind: roman|japanese, weight: 0.25 (trim marks and targets), offset: 6 (pt from the artboard, at least the bleed)} (printer's marks in [Registration], every plate: trim marks, registration targets mid-side, CMYK/spot/black-tint colour bars on top, page information (title, artboard, date UTC) below), bleed?: {useDocument (the document's bleed, document.setup), top, bottom, left, right} (pt; art in the bleed is kept). Each page: TrimBox = artboard, BleedBox = artboard + bleed, MediaBox = that + the marks' room (art clipped to the BleedBox), output?: {conversion: none (default)|destination (every colour into the destination profile's model, CMYK or RGB, also colours of that model in another profile)|preserveNumbers (only colours of the other model and Lab; those in the destination's model keep their numbers) (grey stays grey; images are converted too, printer's marks are not), destination: profile name (edit.colorSettings lists them; default: the document's profile for its colour mode), profiles: none (device colours)|all|destination|taggedSource (documents assigned a profile: edit.assignProfile) (ICC-based colours: CMYK with the destination or document CMYK profile, RGB as sRGB, grey with the sRGB tone curve; PDF/A always), outputIntent: profile name (embedded as the catalog's /GTS_PDFX output intent; a name that isn't a profile is only named), outputCondition, outputConditionId (default: outputIntent), registry, trapped (/Trapped /True; /False when an output intent is written)} (PDF/A files keep their own output intent), advanced?: {fontSubsetPercent: 100, outlineText: true, overprint: preserve|discard}, security?: {openPassword, permissionsPassword, printing: none|low|high, changes: none|pages|forms|comments|any, copy, screenReader, plaintextMetadata}} → {path, bytes, warnings}; no path → {dataBase64, bytes, warnings}. The options apply over the preset (null keeps its value); options accepted but not applied yet come back as warnings; PDF/X, PDF/A-2b at 2.0 and a password with a PDF/A or PDF/X standard are refused. document.export {format: pdf} takes the same options",
|
||||
has_doc,
|
||||
export_pdf
|
||||
),
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
//! `document.exportPdf` output options reach the writer: colours converted and tagged with
|
||||
//! profiles, and the output intent written.
|
||||
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use super::*;
|
||||
|
||||
fn pdf(s: &mut Session, p: Value) -> (String, Value) {
|
||||
let v = s.execute("document.exportPdf", &p).unwrap();
|
||||
let bytes = vectorcraft_format::base64_decode(v["dataBase64"].as_str().unwrap()).unwrap();
|
||||
(String::from_utf8_lossy(&bytes).into_owned(), v["warnings"].clone())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn export_pdf_converts_colours_and_writes_the_output_intent() {
|
||||
let mut s = Session::new();
|
||||
s.execute("file.new", &json!({"width": 200, "height": 100})).unwrap();
|
||||
s.execute("shape.rectangle", &json!({"x": 10, "y": 10, "width": 40, "height": 40})).unwrap();
|
||||
s.execute("paint.setFill", &json!({"color": {"model": "rgb", "r": 1.0, "g": 0.0, "b": 0.0}})).unwrap();
|
||||
s.execute("text.create", &json!({"x": 80, "y": 60, "text": "Press ready"})).unwrap();
|
||||
let generic = vectorcraft_color::cms::GENERIC_CMYK;
|
||||
let p = json!({
|
||||
"preset": "VectorCraft Default",
|
||||
"compression": {"compressText": false},
|
||||
"output": {"conversion": "destination", "destination": generic, "profiles": "destination", "outputIntent": generic, "outputConditionId": "Generic press", "trapped": true},
|
||||
});
|
||||
let (text, warnings) = pdf(&mut s, p.clone());
|
||||
assert_eq!(warnings, json!([]), "everything asked for is applied");
|
||||
assert!(text.contains("/ICCBased") && text.contains("/N 4"), "CMYK colours tagged with the destination");
|
||||
assert!(text.contains("/S/GTS_PDFX") && text.contains("/Trapped/True"));
|
||||
// The Summary has nothing to report either.
|
||||
let mut q = p;
|
||||
q["includeDocument"] = json!(true);
|
||||
assert_eq!(s.execute("document.pdfSettings", &q).unwrap()["warnings"], json!([]));
|
||||
// Converting to a profile that isn't there is a bad parameter.
|
||||
let bad = s.execute("document.exportPdf", &json!({"output": {"conversion": "destination", "destination": "Missing"}}));
|
||||
assert!(matches!(bad, Err(crate::EngineError::BadParams { .. })), "{bad:?}");
|
||||
}
|
||||
Reference in New Issue
Block a user