craft-separations
Print separations for the Craft apps:
ink: the ink model the writers and previews share.InkSetholds the four process inks (always first, in plate order) and the spot inks of a job, and is the job's ink manager (aliases, spots to process);InkColoris a color as ink tints (Separation,DeviceN, registration or none);Overprintholdsop,OPandOPM. Spot names are compared as PDF names:/Gold#20FoilandGold Foilare one ink.plates: one 8-bit plane per ink (255 = 100% ink) rendered from a PDF page.coverage: ink coverage at a pixel and over a page, total area coverage (TAC) per pixel, and the pixels and regions over a TAC limit.composite: an sRGB preview of chosen plates.cms: process color conversion into a CMYK printing condition through craft-cms.
Plates from an exported PDF
An app exports its PDF as usual, then separates a page of it:
use craft_separations::{plates, InkId, Options, Process};
let p = plates(&pdf, 0, 150.0, &Options::default())?;
for (id, ink) in p.inks.iter() {
let plane = p.plane(id); // p.width × p.height tints, row-major
println!("{}: {} pixels inked", ink.name(), plane.map_or(0, |t| t.iter().filter(|v| **v > 0).count()));
}
let black = p.tint(InkId::process(Process::Black), 10, 10);
let gold = p.inks.lookup("Gold").and_then(|id| p.tint(id, 10, 10));
The plates are the PDF as printed, so a preview built on them shows what the writer produced, not what the document model intended.
How a plate is made: the page renders once per ink through craft-pdf's raster with
hayro-interpret's plate mode (InterpreterSettings::plate). Every paint draws its tint of
that ink as gray film. A paint that overprints (op/OP, and OPM 1 for zero DeviceCMYK
components) without naming the ink leaves the film as it is; with Options::overprint off,
every paint knocks out. Clipping, anti-aliasing, soft masks, transparency and knockout groups
stay the renderer's own. Separation and DeviceN colorants paint their own plates (All
every plate, None none), DeviceCMYK its components, DeviceGray black only.
Inks are found by a low-resolution pass that records each spot ink a paint uses, in the order first painted. Spot inks used only inside soft masks are left out. Each spot gets a Lab alternate measured from its sRGB appearance at 100%, so out-of-sRGB spots are clipped.
Process color
RGB, CIE-based and ICCBased colors become CMYK through, in order:
- the CMYK profile of the page's or the document's output intent (a
GTS_PDFXintent first), when craft-cms can convert into it; Options::fallback: craft-cms's Coated FOGRA39 profile (the default), a profile the caller gives, or the ISO 32000-2 §10.4.2.4 device formulas.
Plates::conversion says which was used. Conversions are relative colorimetric without black
point compensation. DeviceCMYK, and ICCBased CMYK in the printing condition's own profile,
separate as they are.
Caps
| Constant | Value | Why |
|---|---|---|
MAX_SPOT_INKS |
32 spot inks per page | A DeviceN space has at most 32 colorants, and each plate costs a render and a byte per pixel. Further spots are listed in Plates::unseparated. |
MAX_PLATE_PIXELS |
4 MP per plate (2000 × 2000) | A page with many inks stays within a few hundred megabytes. The resolution is lowered to fit; Plates::dpi reports the one used. |
The renderer's side limit (craft_pdf::raster::MAX_SIDE) also lowers the resolution.
Approximations
- Non-separable blend modes and groups that blend in RGB are approximated per ink.
- The graphics state's rendering intent is not read; transfer functions and halftones are not applied.
Coverage and TAC
Every reading takes enabled, indexed by InkId::index like a Separations Preview's
toggles: inks past its end count, so &[] counts every ink. Values are percent of one ink at
100%, so TAC runs to 100 × the number of inks.
| Call | Gives |
|---|---|
coverage_at(x, y, enabled) |
Coverage: each ink's tint and the TAC of the enabled inks at a pixel |
pixel_at(u, v) |
The pixel under a point given as fractions of the page, for a pointer readout |
tac(enabled) |
TAC per pixel, row-major |
stats(enabled) |
Stats: per ink InkStats { max, mean, area }; the page's highest TAC, where, and its mean |
above(limit, enabled) |
Above: a mask of the pixels over limit and their count; regions() lists connected regions with bounding boxes, largest first (at most MAX_REGIONS); paint(rgb, color) marks them on a preview |
Composite preview
Plates::composite(enabled, &cms) returns sRGB8 (three bytes per pixel, row-major) of the
enabled inks:
- the process plates convert through
cms(aCmsConversionfor the plates' printing condition,Plates::conversionnames it), relative colorimetric with black point compensation, or through the bundled CMYK profile whencmscan't convert; - each enabled spot plate multiplies over them by the spot's appearance at its tint: a Lab alternate interpolated from paper white to the solid, or a CMYK alternate scaled by the tint.
Overprint is already in the plates (the renderer decides per plate what overprints and what knocks out), so the preview shows it without rewriting any paint as a blend mode.
use craft_separations::{CmsConversion, InkId};
let cms = CmsConversion::fogra39();
let mut enabled = vec![true; p.inks.len()];
enabled[InkId::process(craft_separations::Process::Black).index()] = false; // hide Black
let mut rgb = p.composite(&enabled, &cms);
let over = p.above(300.0, &enabled);
over.paint(&mut rgb, [255, 0, 255]); // TAC warning
let worst = over.regions().first().copied();
Ink manager
InkSet carries the job's ink settings next to its inks:
| Call | Effect |
|---|---|
alias(from, to) |
Spot from prints on to's plate (a process ink or another spot, chains followed); alias(from, from) removes it. Loops and process from are refused. |
set_to_process(ink, on) |
Spot ink prints as process color, from its alternate (spot_cmyk). |
set_all_to_process(on) |
All Spots to Process; aliases still apply first. |
plate_of(ink) |
The plate a tint of ink lands on, None when it prints as process. |
resolve(&color, &cms) |
color with both applied. Tints landing on one plate combine as 1 − (1 − a)(1 − b); a converted color lists the four process inks first, and InkColor::as_cmyk gives the CMYK when no spot is left. |
resolve takes a CmykConversion (such as CmsConversion) because a spot with a Lab
alternate needs the printing condition to become CMYK.
Apply it in two places:
- Export. The writer calls
resolveon every color it emits, so the PDF carries the result: DeviceCMYK whenas_cmykisSome, else aSeparationorDeviceNof the resolved inks (the aliased spot's name, not the original). An overprinting color that became CMYK is written withOPM 1, so its zero components leave the plates under it as they are. - Preview. The preview separates that exported PDF with
platesand showscompositeand the coverage readings. Plates are the truth: an ink manager change shows after the next export, and the preview never recomputes overprint from the document.
In DesignCraft and VectorCraft
- Separations Preview: export the spread to PDF
in memory,
plates(pdf, 0, dpi, &Options::default())at the zoom's resolution, thencomposite(enabled, &cms)for the canvas. The panel listsp.inkswith a toggle per row feedingenabled, each row's percentage fromcoverage_atunder the pointer (pixel_at), and the TAC mode fromabove(limit, enabled)withpaint. Single-plate view showsp.plane(id)as gray film. It would replace VectorCraft'sproof.rs, which recolors the document and draws overprint as Darken. - Ink Manager (DesignCraft's
InkManageraliases and convert-to-process, VectorCraft'sinks.rsplates): keep oneInkSetper document, add each spot swatch with its Lab or CMYK alternate, and map the dialog's rows ontoalias,set_to_processandset_all_to_process. The PDF writer resolves swatch colors through it; the Separations Preview then lists the plates the export produced. - Preflight:
stats(&[])gives per-ink max and mean coverage and the page's highest TAC and its position for a report line such as "TAC 340% at …, limit 300%".
Credits
- Plate mode, ink discovery, the output-intent handling and the coverage readings and
composite (
Separations::above,stats,composite) come from storytold/pdfcraft#2770 by Guillem Hermida (guillehr2), with craft-cms in place of moxcms. - Knockout groups in the renderer come from storytold/pdfcraft#2974 by mtlcl410.
Both MIT OR Apache-2.0, as this crate.