M29 — Print production
State: to do — Depends on: M19, M20, M22, M23, M25 — Conformance preserved or its loss reported, per ADR 17; profiles in the conformance satellite and findings apart from diagnostics, per ADR 36; a dependency-free core and satellites, per ADR 9; output versions per ADR 40, which PDF/X-4 may ask to amend; Skia only in
.Htmland.Rendering, per ADR 43; profiles a stylesheet names fetched only through the resolver, per ADR 38; a lossy change to an image only by name, per ADR 42
Goal
Hand a print shop what it expects: invoices, statements and reports generated as PDF/X-4 with their bleed, marks and output intent; whole batch runs as PDF/VT with one document part per record and the shared artwork written once; colors converted through a managed, deterministic color-management engine; CMYK and spot colors and overprint written from CSS; and a verdict on the PDF/X and PDF/VT files the library receives, checked against an independent referee chosen before anything else is built.
The business case is the outsourced print run: thousands of invoices, statements or notices sent to an éditique provider who prints, folds, inserts and posts them. Such providers ask for PDF/X, and for large runs PDF/VT, so that their raster image processor can color-manage every page against the press and cache what repeats. M12's batch generation already produces the records and keeps their boundaries; M20 made the conformance engine public; M22 evaluates every color space; M25 renders through a color-conversion seam. This milestone makes the file one they accept, and gives the rest of the library the color management each of those milestones deferred here.
The failures it exists to prevent are the ones a print provider's preflight reports on the morning of the run. A page
without a /TrimBox, or with a /TrimBox and an /ArtBox together. An RGB logo under a CMYK output intent, with no
default color space to say what the RGB meant. A /Trapped left Unknown. A creation date read from the server's
clock, so that a re-run is never byte-identical. A spot color written as Pantone 186 C where the press expects
PANTONE 186 C, which prints as a plate nobody loaded. A logo embedded ten thousand times in a ten-thousand-record
run. A color engine whose last bit differs between the Windows workstation that proofed the job and the Linux
server that produced it. A supplier's CMYK leaflet merged into an RGB volume and converted by nobody. A batch whose
record boundaries the inserter cannot find, so that one customer's statement is folded into the next one's envelope.
Scope
In:
- the referee and the version, first: the maintainer decides at the start of the milestone, and nothing before, what independently checks a PDF/X-4 and a PDF/VT file — veraPDF validates PDF/A, PDF/UA and WTPDF only, and no open-source PDF/X or PDF/VT validator is known —, with what runs in CI and what the maintainer runs, and whether ISO 15930-7 caps PDF/X-4 at PDF 1.6, and so whether ADR 40 needs a 1.6 output for generated documents; slice 1 records both as ADRs at the next free numbers;
- page geometry from CSS Paged Media 3:
@pagebleedandmarkswritten as/MediaBox,/BleedBoxand/TrimBox; crop and registration marks drawn outside the bleed box in the registration color; the page's rendering clipped at the bleed edge rather than the page box; - output intents for print: one
GTS_PDFXintent with its embedded output profile (PDF/X-4) or a reference to it (PDF/X-4p), and, in a file that also claims PDF/A, the same profile object under both intents; - PDF/X-4 and PDF/X-4p generation in the core, from M08's builder and from M12's engine: a constraint table as data, consulted by the writer, the fonts, the content builder, the annotations and the assembly, as M20's part-1 table is; the dual claims PDF/A-2b, 2u, 3b and 3u with PDF/X-4, Factur-X included;
- a managed color-management engine in a satellite of its own: ICC v2 and v4 profiles read under hostile-input bounds; transforms between them at the four rendering intents, with black-point compensation; device links precomputed and interpolated in integer arithmetic, so that the same profile and input give the same bytes on every platform; a transform cache; the implementation of M25's color-conversion seam and of the core's transform seam;
- color from CSS:
device-cmyk()written asDeviceCMYK; CSS Color 5's@color-profileandcolor()in a named profile's space; spot colors and overprint through-adc-vendor extensions, written asSeparationandDeviceNspaces with their alternates and as graphics-state overprint; sRGB content under a CMYK intent either kept managed through a default color space or converted early, by option; - document color conversion in the core, through the transform seam: device and ICC-based color in content, images, shadings, patterns and appearances converted to a target profile, spot colorants kept unless the caller asks, JPEG and JPEG 2000 images converted only under M23's named lossy step; used to reconcile output intents on merge (M06, M20), by M09's stamps and by M21's remedies, all of which deferred color conversion here;
- PDF/VT-1 on M12.6's batch generation — the document part hierarchy, one leaf per record, record metadata, shared XObjects with their reuse hints —, and PDF/VT-2 over PDF/X-4p (the output profile referenced, one file); a read model of a received document's parts;
- the PDF/X and PDF/VT profiles in
AdCodicem.Pdf.Conformance: PDF/X-4 and 4p, PDF/X-1a and PDF/X-3 (their 2001 to 2003 editions) validated, never generated; PDF/VT-1 and 2 validated; the claim reader extended to PDF/X and PDF/VT identifications; - color-managed rendering through M25's seam: ICC-based color by its profile, soft proofing against the output
intent, and — through a plate device in
AdCodicem.Pdf.Rendering— separation previews and overprint simulation, which M25 left here; - the command-line tool's print options and verbs.
Out, explicitly:
- mixed raster content compression — an open question of the roadmap, reopened by color scans too large for a portal cap after M23's recompression; nothing here needs it;
- trapping, OPI and halftone authoring —
TrapNetannotations, OPI dictionaries,HTandHTO— read, preserved through every operation, reported by the profile where a part forbids them, never written; - transparency flattening — it needs rasterization into the document, which nothing plans; PDF/X-4 keeps transparency live and does not ask for it, and so PDF/X-1a and PDF/X-3 generation are not planned either (they forbid transparency); the library validates them;
- PDF/X-5 (g, pg and n: external graphical content, n-colorant intents), PDF/VT-2 over PDF/X-5, PDF/VT-2s (streamed MIME packages) — until a print provider asks;
- PDF/X-6 and PDF/VT-3, the PDF 2.0 parts — until a print provider asks, as M28 left PDF/X-6 here;
- imposition beyond M09's N-up, JDF job tickets, color bars and press control strips — not planned;
- converting a received document to PDF/X — not planned: the profile reports, and color conversion repairs color only; M21 converts to PDF/A;
- color libraries (Pantone and its kin) — never shipped, their data being licensed: the caller names each spot and gives its alternate; ICC named color profiles are read as unsupported and reported;
- iccMAX (ICC version 5) profiles, spectral processing, black-preserving and ink-limiting transforms, and profile creation — not planned; a v5 profile is refused with a diagnostic;
- converting a JPEG's color without re-encoding it — impossible; only M23's named
ReencodeJpegstep does it, reported; - heuristics on received files — guessing a missing output intent, renaming a spot a caller misspelled — never.
Design
Where it lives
| Part | Where | Why |
|---|---|---|
PdfXOptions, PdfVtOptions, page geometry, output intents, the PDF/X constraint table, the document-part writer, PdfDocumentParts | Core, beside M14's and M20's conformance targets | The writer, the builder, the fonts, the stamps and the assembly enforce them; no dependency is needed |
| The ICC header and tag-table reader | Core, internal (M20's) | Already bounded and total; the conformance rules and the writer read headers, never transform |
IPdfColorTransformProvider, PdfColorConversion | Core, Graphics/ | Conversion edits content through M19's pipeline, which is internal to the core; the color science arrives through the seam, so the core still converts no color of its own (M10's rule) |
The color-management engine — IccProfile, IccTransform, intents, black-point compensation, the device-link tables, the cache, the implementations of both seams | New satellite, AdCodicem.Pdf.ColorManagement — the AdCodicem. prefix is reserved on nuget.org (ADR 24), and the spelling follows the project's convention for identifiers | ADR 9: a large engine and its reference data in a package a caller adds knowingly; docs/architecture.md already names it without an identifier |
device-cmyk(), @color-profile, color(), the -adc- spot and overprint extensions | AdCodicem.Pdf.Html | CSS is the engine's; the property table gains the rows |
| The PDF/X and PDF/VT profiles and their rule catalog | AdCodicem.Pdf.Conformance | ADR 36, on M20's public rule API |
| The plate device and soft proofing | AdCodicem.Pdf.Rendering | ADR 43: Skia draws there and nowhere else |
| The referee harness | tests/AdCodicem.Pdf.TestSupport | Shared by this milestone's integration tests and any later one that claims PDF/X |
| Print options and verbs | AdCodicem.Pdf.Tool | The tool ships every satellite |
The color satellite depends on the core alone, has no native code and declares IsAotCompatible from its first
commit; it runs in M23's WebAssembly host, where its determinism is tested. It is a new package, so #42 (an API
baseline per package) applies to it.
M25's seam is in the core. M25 declares IPdfColorConverter in the core's Graphics/, beside M22's evaluation,
so that the color satellite implements it without depending on SkiaSharp.
Page geometry
bleedandmarks(CSS Paged Media 3):marks: none | [crop || cross];bleed: auto | <length>, whereautocomputes to 6pt whenmarkshascropand to zero otherwise. M12 parsed both and reported them; M29 writes them.- The boxes. The page box is the
/TrimBox; the/BleedBoxextends it by the bleed on every side; the/MediaBoxextends the bleed box by a slug wide enough for the marks when there are marks, and equals the bleed box otherwise. No/ArtBoxis written beside a/TrimBox, and no/CropBoxis written, so that viewers show the media.page-orientationrotates all of them with/Rotate, as M12 writes it. Under PDF/X, a page without bleed still gets a/TrimBoxequal to its/MediaBox. - Painting into the bleed. The page's background covers the bleed area, and the page's rendering is clipped at the bleed edge, not the page box: a band that touches the page edge in the source bleeds off it on paper. Layout is unchanged; only the clip moves.
- The marks. Crop marks at each corner of the trim, offset outside the bleed so that no mark enters the printed
area; registration crosses centered on each side in the slug. Drawn in the registration color — a
Separationspace whose colorant is/All, so that they print on every plate —, as content marked as a page artifact — the kind ISO 32000 gives to production aids extraneous to the document, cut marks and color bars among them —, so that M13's tagging, M15's extraction and PDF/UA leave them aside. Their lengths, offsets and line width are constants recorded by slice 2. - The trim, bleed and art boxes are not inherited: ISO 32000 defaults each to the crop box, which is inherited and defaults to the media box (CLAUDE.md, Known traps). The writer puts every box on each page dictionary, and the profile resolves a default through the crop box's inherited resolution — but a default is not a box, and PDF/X asks for the trim or art box on the page (to verify the wording).
Output intents and identification
PdfXOptions Part (X4, X4p); Condition — identifier (e.g. "FOGRA39", "CGATS TR 001"), registry name
(http://www.color.org when the condition is registered), info; Profile — the output profile's
bytes (X-4) or a reference to it (X-4p); Trapped (False by default, or True); RgbContent
(ManagedByDefaultSpace, the default; ConvertEarly, which needs a transform provider); Policy
(Refuse, the default; Convert, with a provider; RemoveClaim)
PdfXProfileReference for X-4p: the profile's name, URLs, checksum, ICC version and colorant table, as ISO 15930-7
names them (the keys to verify against the text)
- One
GTS_PDFXintent, with/OutputConditionIdentifier,/RegistryNameand/Infofrom the options, and/DestOutputProfile(X-4) or the profile reference (X-4p). The profile must be an output profile (prtr) in gray, RGB or CMYK; anything else is refused when the options are built, by the core's ICC header reader. - With a PDF/A claim — PDF/A-2b, 2u, 3b or 3u, and so Factur-X — the
GTS_PDFA1intent is written beside it, both pointing at one profile object, as PDF/A-2 requires of every intent's profile (M20); the XMP carries both identifications, andpdfxidis declared as an extension schema where PDF/A does not predefine it (to verify against ISO 19005-2's list of predefined schemas). - Identification:
pdfxid:GTS_PDFXVersionin the XMP (M14's model), which is how ISO 15930-7 identifies conformance;pdf:Trappedand the document information dictionary's/Trappedagree,TrueorFalse, neverUnknown;/Title,/CreationDateand/ModDatepresent, with their XMP counterparts; the trailer's/ID(M03). - Dates are the caller's. PDF/X requires a creation and a modification date; the library never reads the clock (invariant 6), so a PDF/X target without both is refused when the options are built.
- The version. ISO 15930-7 specifies the use of PDF version 1.6, as ISO's abstract says, and published summaries
read that as a file of version 1.6 at most — "PDF/X-4 files are regular PDF 1.6 files" (prepressure.com) — (to
verify against the text). ADR 40's writer writes 1.7 or 2.0 for a document it generates and never lowers a received
document's version (M03). The maintainer decides against the text at the start of the milestone; if the cap holds,
slice 1 records an amendment of ADR 40 at the next free number — a
Pdf16output for generated documents only, whose features the writer knows, the version table's rows for PDF 1.7 features refusing under it — and slice 5 implements it. A received document keeps its version, and a PDF/X-4 target on one that declares 1.7 is refused.
The PDF/X-4 constraints
PdfXConstraintSet (internal), one per part, is data, like M20's PdfAConstraintSet, and each writer consults the set
of the target or of the document's claim. The rows below are the ones this specification can state from published
summaries; slice 5 writes each from ISO 15930-7:2010 with its clause, and a row marked to verify is kept, amended or
dropped by that reading.
| Area | Requirement | Who keeps it | When it cannot be met |
|---|---|---|---|
| Identification, version | pdfxid:GTS_PDFXVersion; the version cap above (to verify) | M14's XMP model, M03's writer | Refused when the options are built |
| Output intent | One GTS_PDFX intent, an embedded output profile, or a reference under X-4p | Above | Refused |
| Metadata | Title, creation and modification dates, Trapped true or false, /ID | Above | Refused |
| Page boxes | A /MediaBox, and a /TrimBox or an /ArtBox but never both; bleed box within the media box and containing the trim box | Page geometry; M06's page copy, M09's N-up | A copied page with both boxes: the /ArtBox dropped and reported, or refused under Refuse |
| Encryption | None | M16's writer | Refused |
| Fonts | Every font embedded (to verify: whether a font used only in render mode 3 is exempt, as in PDF/A) | M08, M12 | A received part with a font not embedded: a conflict |
| Device color | Device color only in the output intent's family, unless a default color space says what it means | The content builder, M12's color, M09's stamps, M10's codes | Under Refuse, a conflict; under Convert, converted through the provider; RGB content under a CMYK intent follows RgbContent |
| Other color | ICCBased, Lab, Separation and DeviceN with their alternates | Color from CSS, below | — |
| Transparency | Allowed, and kept live; the page group's blending space in the intent's family (to verify: what the part requires of /CS) | M12's page groups, M09's opacity | — |
| Optional content | Allowed, under constraints on its configurations (to verify) | M11 | A conflict |
| Interactivity | No JavaScript, no actions other than navigation, no interactive form fields (to verify: the exact list), no multimedia | M11, M16, M17 | A conflict: M17's fields are refused under a PDF/X target |
| Annotations | Within the bleed box, only printer's marks and trap networks may print (to verify) | M11, M12's links (no appearance, not printed) | A conflict |
| Images | 16-bit samples and JPEG 2000 allowed; no /Alternates, no OPI (to verify) | M07, M12.5, M22 | A conflict |
| Graphics state | No transfer function; halftones and PostScript XObjects restricted (to verify) | The content builder | A conflict |
A conflict follows PdfXOptions.Policy, as PdfConformancePolicy does for PDF/A (M09): Refuse throws
PdfConformanceException naming the part and the clause, before a byte is written; Convert converts device
color through the provider and reports each conversion; RemoveClaim writes without the identification and
reports pdfx.claim-removed at ConformanceLoss (invariant 7).
The color-management satellite
IccProfile Parse(ReadOnlySpan<byte>, IccParseOptions) -> an immutable profile, or a report of why not:
header (version, class, color space, PCS, rendering intent, illuminant, profile ID), tags by
signature, each read on demand and bounded
IccRenderingIntent Perceptual, RelativeColorimetric, Saturation, AbsoluteColorimetric
IccTransform Create(source, destination, intent, blackPointCompensation) -> immutable, thread-safe;
Convert rows of 8- or 16-bit samples, or components in [0, 1]
IccTransformCache bounded by count; keyed by both profiles' SHA-256, the intent and the compensation
ColorManagement the entry point: Transforms(options) -> IPdfColorTransformProvider (core's seam), and
Converter(options) -> IPdfColorConverter (M25's seam)
- Profiles: versions 2 (ICC.1:2001-04) and 4 (ICC.1:2010, which ISO 15076-1:2010 adopts); the input, display,
output, color-space and abstract classes, and device links. Tag types:
curv,para,XYZ,sf32,mft1andmft2(the v2 LUTs),mABandmBA(the v4 LUTs, with their A, M and B curves, matrix and CLUT),chad,mluc,descandtext. A named-color profile or an iccMAX profile is refused withcolor.profile-unsupported. - Pipelines are built from a profile's tags for the intent: matrix and TRC for display profiles; A-to-B and B-to-A
tables for the rest, falling back to the perceptual table (
A2B0,B2A0) when the intent's is absent, as the ICC specification says (to verify per profile class); the PCS joined in XYZ or Lab; v2's and v4's 16-bit Lab encodings differ (L* = 100 isFF00hin v2's legacy encoding andFFFFhin v4's), and a transform between a v2 and a v4 profile converts between them; absolute colorimetric through the media white point, and v4's chromatic adaptation throughchad. - Black-point compensation as ISO 18619:2015 specifies it: for the relative colorimetric intent, and for the others only where the standard extends it (to verify — v2 perceptual tables, whose black point is not defined, are where implementations differ).
- Device links, precomputed. A transform is evaluated once on a grid — 33 points per input for three inputs, 17 for four, constants recorded by slice 3 — through the floating-point pipeline in a fixed order of operations, and stored as 16-bit values; conversion interpolates the grid in integer arithmetic, tetrahedrally for three inputs and tetrahedrally in C, M and Y then linearly in K for four. The hot loop is a table read and integer arithmetic, 0 B allocated per row.
- Deterministic everywhere. The floating-point pipeline uses IEEE basic operations only, in a fixed order, with no
fused multiply-add unless written, and M22's own power and cube-root routines rather than
Math.Pow, whose last bit the platform's C library chooses (M22's trap). The same profile and input give the same bytes on x64 and ARM64, on Linux, Windows and macOS, and in M23's WebAssembly host — tested on each. - Accuracy is measured against two independent implementations: LittleCMS (MIT;
transiccfor colors,tificcfor images) and the ICC's own reference implementation, DemoIccMAX, renamed iccDEV in 2025 (iccApplyNamedCmm,iccDumpProfile; its license read before use). The tolerance, in ΔE2000, is fixed by slice 3 per intent and recorded; where the two referees disagree — black-point compensation and v2 Lab encoding are known places to look —, the manifest of profiles records which reading is ours and why. - The profile is hostile input (invariant 4): the tag count is at most what the profile's length can hold
(twelve bytes a tag after the header); each tag's offset and size lie within the profile; tags may share data, as
the ICC specification allows, but none is followed twice; a CLUT's size — grid points to the power of inputs, times
outputs, times the precision — is computed in 64 bits and checked against the tag's size before anything is
allocated; a curve of 0 entries is the identity and of 1 entry a gamma; a
parafunction outside types 0 to 4 is refused. A profile that fails iscolor.profile-invalid, and the color it describes goes through its/Alternate, as M22 does.
Color from CSS
device-cmyk()under a target whose intent is CMYK is written asDeviceCMYK, exactly; otherwise it is converted through the profile an@color-profile device-cmykrule names, when there is one and a provider is given, and by CSS Color 5's naive formula otherwise, as M12 does, reported.@color-profile(CSS Color 5):srcloaded through ADR 38's resolver — deny-by-default, bounded, cached per document —,rendering-intenthonored;color(--name c1 c2 …)written in anICCBasedspace on that profile, one object per profile per document. A profile the resolver refuses or the parser rejects iscss.color-profile-unavailable, and the color falls back as CSS Color 5 says.- sRGB content under a CMYK intent — every color M12 resolves, every RGB image — follows
RgbContent:ManagedByDefaultSpacewrites/DefaultRGBasICCBasedon M14's sRGB profile in each page's resources, so thatrgstill means sRGB and the press converts it (late binding);ConvertEarlyconverts every color and RGB image to the intent through the provider at the options' intent, so that the file holds CMYK only. RGB images already tagged with a profile (a PNG'siCCP, a JPEG's APP2, which M07 and M12 read) keep it. - Spot colors: a vendor at-rule declares a colorant and its alternate, and a vendor function uses it wherever a
color is allowed — text, borders, backgrounds, SVG's
fillandstroke, gradient stops —, with a tint. The syntax is fixed by slice 4 and entered in the property table under M12's-adc-prefix and its ADR; Prince's@prince-colorrule andprince-color()function are the prior art it is measured against. Written as aSeparationspace: the colorant's name exactly as given, encoded as a name (a space as#20), never normalized; the alternate inDeviceCMYKorLab; a type 2 tint transform from zero to the alternate. One space object per colorant per document. Two declarations of one name with different alternates keep the first (print.spot-conflict). - Mixtures — a gradient between two spots, or between a spot and a process color — are written as
DeviceNwith theNChannelsubtype, its/Colorantsand/Processattributes, and a tint transform sampled as a type 0 function. - Overprint: a vendor wrapper around a color sets
/OPand/opin the graphics state for the fill or the stroke, with/OPM 1under a CMYK intent, so that a zero component leaves the plate beneath untouched. Syntax fixed by slice 4; Prince'soverprintkeyword is the prior art. Knockout groups are not written. - Transparency under a CMYK intent: page and form groups blend in the intent's family (
/CS /DeviceCMYK), so that amultiplyhappens where the press will see it, not in RGB (to verify against the part's clause on groups). - Versions:
Separationneeds PDF 1.2,DeviceN1.3, theNChannelattributes 1.6 — rows in M03's version table, under the cap above when it is confirmed.
Document color conversion
IPdfColorTransformProvider (core, public) Create(source space, target profile, intent, compensation) ->
IPdfColorTransform, which converts rows of components; implemented by the satellite
PdfColorConversion (core) Convert(document, PdfColorConversionOptions, provider, progress, token)
-> the report: what was converted, left, approximated or refused, object by object
PdfColorConversionOptions target profile and family; intent; compensation; Scope (DeviceOnly — the default —,
DeviceAndIcc, AllButSpots); Spots (Keep — the default — or ConvertToProcess);
ImageSteps (M23's lossy steps named for JPEG and JPEG 2000 images)
- Content through M19's content-editing pipeline:
g,G,rg,RG,k,K,sc,SC,scn,SCN,cs,CSand inline images, in page content, form XObjects (each converted once, however many pages draw it), colored tiling patterns, Type 3 glyph procedures that set color (d0; ad1glyph takes the current color), and annotation appearance streams. - Images through M22's rows: Flate, LZW, RunLength, CCITT and uncompressed images converted from their samples and
re-encoded losslessly (Flate with a predictor); an
Indexedimage by converting its palette, exactly; JPEG and JPEG 2000 only when M23'sReencodeJpegis named — a lossy step, reported per image —, and otherwise left and covered by a default color space where one can say what they mean, or reported (color.image-left). - Shadings: a type 2 function between two device colors has its end points converted; any other function is
resampled into a type 0 function of a fixed size, and the largest error of the resampling, in ΔE2000, is reported
(
color.approximated). - Spots stay spots. A
SeparationorDeviceNcolorant prints on its own plate; conversion re-expresses its alternate in the target family only when the alternate's family differs, and converts the colorant itself only underConvertToProcess./Alland/Noneare never converted. - Group blending spaces: a page or form group's
/CSin a device family other than the target's becomes the target's family, so that blending happens where the press will see it (the trap below); anICCBased/CSfollows the scope, as content does. - Not converted: soft-mask groups — a luminosity mask is a mask, not a color on paper —; the page group's
backdrop; color in
/Metadata. Default color spaces that no longer apply are removed. - Consumers: M20's merge (
PdfAssemblyOptions.Conformance) gainsConvertParts, which converts a part whose profile's family differs from the target's rather than covering it with a default space; M09's stamps gainPdfConformancePolicy.Convertfor "an RGB color on a CMYK intent", which M09 refuses today; M21's remedy table gains the row "device color converted to the output intent's family, with a provider, when the caller prefers it to a default color space"; the tool'scolor convert.
PDF/VT
PdfVtOptions Part (VT1, VT2 over X-4p); the level names (NodeNameList, e.g. Job, Record); the record level;
a record's metadata builder — strings, numbers, booleans, dates and nested dictionaries, no
streams —; the XObject reuse hints; the PDF/X options it builds on
PdfDocumentParts (core, read model) Open(document) -> the part tree: levels, records, each record's page range
and metadata; iterative, each node read once, a cycle cut and reported
- On M12.6's batch, in the one-document mode: each record starts a page group of its own already (its counters,
a bookmark, a named destination); M29 adds the document part hierarchy: the catalog's
/DPartRoot, its/DPartRootNode,/RecordLeveland/NodeNameList;DPartnodes with/Parentand/DParts— an array of arrays of children, as ISO 32000-2 defines it —; one leaf per record with/Startpointing at its first page,/Endat its last when the record has more than one, and/DPMholding its metadata; and each page's/DPartpointing at its leaf. ISO 32000-2 §14.12, Document parts, defines the structure and these keys (read in its text), and ISO 16612-2 brought it into PDF 1.6-based files. - Forward-only: a leaf's number is reserved when its record starts, its pages point at it, and it is written when
the record ends; leaves are grouped under intermediate nodes of at most a constant number of children, so that no
/DPartsarray grows with the run; the root is written last. Memory: one reference per child of the open node at each level. - Record metadata: the caller's values per record — a customer number, a postal code for sorting, a page count for
the inserter — written in
/DPMunder the caller's names; ISO 16612-2 recommends a vocabulary derived from CIP4's (to verify), which the documentation shows and the options do not impose. A value outside the closed vocabulary is dropped and reported (vt.dpm-value-dropped). - What repeats is written once. M12.6's caches already make a letterhead or a logo one XObject for the whole run.
PDF/VT adds hints a raster image processor uses to cache it rendered: a scope hint and an encapsulation hint (to
verify: the key names,
GTS_ScopeandGTS_Encapsulated, and their values), written only for XObjects that the layout proves independent of the state they are drawn in — no pattern, no inherited soft mask, no blending with the backdrop. - Identification:
pdfvtid:GTS_PDFVTVersionandpdfvtid:GTS_PDFVTModDatein the XMP, beside PDF/X-4's (PDF/VT-1 is a PDF/X-4 file); PDF/VT-2 over X-4p references the output profile instead of embedding it, in one file. - Budget: the batch keeps M12's throughput and allocation budget within an overhead this slice records.
The conformance profiles
- Levels:
PdfConformanceLevelgainsPdfX1a2001,PdfX1a2003,PdfX32002,PdfX32003,PdfX4,PdfX4p,PdfVT1andPdfVT2. The claim reader (core) readspdfxid:GTS_PDFXVersionfrom the XMP, the information dictionary's/GTS_PDFXVersionand/GTS_PDFXConformancethat the older parts use, andpdfvtid:GTS_PDFVTVersion, reporting what the parts do not define asclaim.malformed, as M20 does. - Profiles:
pdf-x-4,pdf-x-4p,pdf-x-1a,pdf-x-3,pdf-vt-1,pdf-vt-2, on M20's public API. Families:pdfx-file,pdfx-metadata,pdfx-page-box,pdfx-color,pdfx-font,pdfx-graphics,pdfx-image,pdfx-annotation,pdfx-action,pdfx-optional-content,pdfvt-part,pdfvt-metadata,pdfvt-xobject. One identifier per requirement whatever the part, as M20's catalog does; each rule's references give the part and the clause. - Sourcing. The ISO 15930 and 16612 texts are paid publications: each rule is written in our own words from the text the maintainer reads, with its clause; the referee is a checklist of what it checks, never the source.
- Verdicts: PDF/X has no human condition, so a verdict is
Conforms,DoesNotConformorIndeterminate(a rule that could not run, as M20 defines it).
Color-managed rendering (M25's seam)
- The converter:
ICCBasedcolor by its profile;Labexactly; device color through the document's output intent profile when it has one, so that a CMYK file is shown as the press will print it — soft proofing —, at relative colorimetric with compensation by default, absolute on request (paper white simulated); through M22's device formulas otherwise, as M25 does. Called per color set and per image row, never per pixel through an interface (M25's rule). - The plate device, in
AdCodicem.Pdf.Rendering: M15's interpreter drives a device that keeps one gray raster per colorant the page uses — the process colorants of the intent and each spot —, a band at a time as M25 renders. Each paint operation writes its tint into the plates its color space reaches, knocking out the others unless the graphics state overprints, where/OPM 1leaves a plate untouched for a zero component. Skia draws coverage masks; plates are combined in integer arithmetic. - Outputs: separation previews — the plates, as grayscale images named by colorant —, and overprint simulation — the plates composed through the intent's profile to sRGB, spots through their alternates.
- Referees: Ghostscript's
tiffsepdevice, which writes one grayscale plate per colorant with the composite; MuPDF with color management on (mutool drawwithout-N) for soft proofs; Ghostscript's and MuPDF's overprint simulation options (to verify their names and behavior in the pinned versions).
Bounds, classified (invariant 12, ADR 34)
| Bound | Class | Why |
|---|---|---|
| An embedded ICC profile's length | The existing guard MaxDecodedStreamLength | A profile is a stream's decoded data |
| A profile a stylesheet names | The resolver's resource-size bound (ADR 38, M12) | The template's input, not the document's |
| Tag count, tag offsets and sizes, CLUT sizes | Sized by the profile's bytes, computed in 64 bits | A profile cannot describe more data than it holds |
| CLUT inputs and grid points | Internal constants: the ranges the ICC's field widths allow — grid points in one byte, the input count per tag type (to verify) | Field widths the specification defines; no valid profile exceeds them |
| Our device-link grids | Internal constants (33 and 17 points per input) | Independent of the file |
| The transform cache | An option (count), not a guard | Reaching it costs a rebuild of a transform, never data |
DeviceN colorants | Sized by the array the file holds, under MaxObjectLength | Each colorant is a name the parser read |
| The document part tree | Walked iteratively, each node once, cycles cut | A tree is no larger than the objects in the file |
| Plates per page | One per colorant the page's color spaces name, a band at a time | Bounded by the color spaces the file holds and M25's band budget |
Diagnostics
In PdfDiagnosticCodes, disjoint from rule identifiers (ADR 36):
| Code | Severity | Meaning |
|---|---|---|
color.profile-invalid | Warning | An ICC profile the parser could not use; the space falls back to its alternate |
color.profile-unsupported | Warning | A named-color or iccMAX profile |
color.approximated | Information | A shading's function resampled, or a color converted without a profile; the largest error |
color.image-left | Information | An image whose conversion needs a named lossy step; left, and covered by a default space where possible |
css.color-profile-unavailable | Warning | An @color-profile the resolver refused or the parser rejected |
print.spot-conflict | Warning | One colorant name declared with two alternates; the first kept |
print.artbox-dropped | Warning | A copied page carried both a trim box and an art box; the art box dropped under PDF/X |
pdfx.content-converted | Information | Device color converted to the intent under Policy.Convert |
pdfx.claim-removed | ConformanceLoss | A conflict written under RemoveClaim; the identification is gone |
vt.dpm-value-dropped | Warning | A record's metadata value outside the closed vocabulary |
vt.part-tree-cycle | Warning | A received document part tree that reaches itself; cut where it does |
Referees
All in containers (ADR 27), pinned by digest, their versions recorded in docs/status.md:
| Referee | Confirms |
|---|---|
| The referee chosen in slice 1 | That every PDF/X-4, 4p and PDF/VT file generated conforms, and the verdict on every received print file |
| veraPDF — the PDF/A flavors; and its feature report with a PDF/X policy of our own (partial) | The PDF/A claim of every dual file; the facts of PDF/X files as an independent parser extracts them, judged by our policy — a regression check, not validation |
| qpdf, pikepdf | Every output sound; boxes and their nesting, output intents and their profile object, color spaces, the part tree walked independently, XObjects counted |
| ExifTool | The XMP identifications, pdf:Trapped, the extension schemas |
| Ghostscript | tiffsep plates for spot colors and overprint; its overprint simulation |
| MuPDF | Color-managed renderings for soft proofs; -N for M22's device formulas |
LittleCMS (transicc, tificc) and iccDEV (iccApplyNamedCmm, iccDumpProfile) | Color values per intent and compensation; image conversions; parsed tags |
| M14's Factur-X referee | That a Factur-X invoice made PDF/X-4 is still a valid Factur-X |
The command-line tool
adpdf html2pdf IN.html -o OUT.pdf --pdf-x 4|4p --output-profile P.icc --condition ID [--registry URL] [--bleed 3mm] [--marks crop,cross] [--trapped true|false] [--rgb managed|convert]; html2pdf --template T.html --records R.jsonl --one-document --pdf-vt 1|2 [--record-level NAME]; validate FILE --profile pdf-x-4|pdf-x-1a|pdf-x-3|pdf-vt-1
(M20's verb); color convert FILE --profile P.icc [--intent relative] [--bpc] [--scope device|icc] [--spots keep|process] -o OUT; color inspect P.icc; parts FILE [--json], the records of a PDF/VT file;
render FILE --soft-proof | --separations (M25's verb). Exit codes are M06's; the AOT binary produces what the API
produces.
Slices
Each slice ends on a green commit, with its codes and rules documented, its benchmark, if it has one, in
benchmarks/budgets.json (M23), and its measurements in docs/status.md.
- The referee and the version. Records the maintainer's two decisions, taken at the start of the milestone and
not before: the referee's ADR, and whether ISO 15930-7 caps PDF/X-4 at 1.6 — if it does, the ADR amending ADR 40
for a
Pdf16output of generated documents. The referee's ADR weighs: a commercial preflight — callas pdfToolbox, sold as a command-line and server product (its Linux edition and its license terms for CI to verify), or Enfocus PitStop — under a license the project can use in CI; the same, or Acrobat's Preflight, run by the maintainer, its reports committed; the published test suites of the Ghent Workgroup and of the Altona suite as a corpus for our profile, their terms read; and the partial checks open tools allow (the rows of Referees above). The expected outcome, to be confirmed or overturned by the ADR: the partial checks run on every output in CI; the full verdict comes from a commercial preflight, run in CI if a license allows it, and otherwise by the maintainer with its reports committed undertests/corpus/referee/, each naming the SHA-256 of the file it judged — invariant 6 makes that binding exact, and a CI test fails when a generated output's hash has no current report, so that a changed output cannot pass on an old verdict. Delivers the harness for whatever is chosen, and the first reports on the corpus's print files. The manifest'sprintuse case joins the schema's enum here, and the corpus's print files take it. Proved by the harness on two hand-made fixtures, one sound and one breaking five requirements, each verdict as expected; the binding test failing when one byte of an output changes; the version decision cited with its clause of ISO 15930-7. Leaves geometry. - Page geometry, output intents and claims. Delivers
bleedandmarkswritten, the boxes, the bleed clip, the marks in the registration color as page artifacts,PdfXOptionswith the intent, identification,Trappedand the dates, the shared profile under dual claims, the claim reader extended. Proved by unit tests (box nesting at everypage-orientation;bleed: autowith and without crop marks; marks never inside the bleed box; a target without dates refused); integration: pikepdf's boxes and intents, ExifTool's identifications, MuPDF's rendering of the media box showing the marks outside the trim. Leaves color. - The color-management satellite. Delivers the package,
IccProfile, the pipelines, the four intents, compensation, the device-link tables, the cache, both seams,ColorTransformBenchmarks. Proved by unit tests per tag type, per encoding and per hostile case; FsCheck — any input converted through a profile and its inverse at relative colorimetric returns within the recorded tolerance on matrix profiles —; integration: ΔE2000 against LittleCMS and iccDEV on the reference profiles, per intent, with and without compensation;iccDumpProfile's tag lists equal ours on every profile the corpus embeds; the determinism test on x64 and ARM64, Linux, Windows and macOS, and in M23's WebAssembly host. Leaves CSS. - Color from CSS. Delivers
device-cmyk()written as such,@color-profilethrough the resolver,color(), the spot and overprint extensions with their property-table rows,Separation,DeviceNand/All,RgbContent, the blending space. Proved by unit tests (a name with a space, a tint of zero, a gradient between two spots, an overprinted black text on a spot background); integration: Ghostscript'stiffsepon the print source gives one plate per process colorant and per spot, named exactly, with the overprinted text present on the plate beneath; pikepdf's color spaces. Leaves PDF/X. - PDF/X-4 and PDF/X-4p generation. Delivers
PdfXConstraintSetread from ISO 15930-7, the policy, the dual claims, X-4p's reference, and — if slice 1 confirmed the version cap — thePdf16output its ADR decided. Proved by unit tests per constraint alone, each refused, converted and removed as the policy says; integration: the referee on the reference documents and the print source; veraPDF on the dual files; M14's Factur-X referee on the invoice. Leaves the profile. - The PDF/X profiles. Delivers the levels, the four PDF/X profiles and their catalog with clauses. Proved by a triggering, a silent and a borderline fixture per rule; the corpus's print files against the referee's committed or live verdicts, every disagreement fixed or recorded with its reason; our generated files reported conforming. Leaves conversion.
- Document color conversion. Delivers
IPdfColorTransformProvider,PdfColorConversion, its report, the merge'sConvertParts, M09'sConvert, M21's remedy row,color convert. Proved by unit tests per operator family, per image filter, per shading type, for a colored and an uncolored pattern, ad0and ad1glyph, a page group's blending space, a luminosity mask left alone, a spot kept and a spot converted; integration:tificcon each converted image within the tolerance; Ghostscript's plates before and after (spots unchanged); veraPDF on the converted EU regulation; MuPDF's soft proofs before and after within the recorded ΔE. Leaves PDF/VT. - PDF/VT-1 on the batch. Delivers
PdfVtOptions, the part tree written forward-only, metadata, the reuse hints, the identification,PdfDocumentParts,parts. Proved by unit tests (one record; a record of one page; ten thousand records under intermediate nodes; a metadata value refused); integration: pikepdf's walk of the tree gives our records and page ranges; one logo object referenced by every page; the referee on the batch; the batch within its budget. Leaves the PDF/VT profile. - PDF/VT-2 and the PDF/VT profiles. Delivers VT-2 over X-4p,
pdf-vt-1andpdf-vt-2. Proved by fixtures per rule; received PDF/VT files, not in the corpus (below), against the referee; our batches conforming. Leaves rendering. - Color-managed rendering. Delivers the converter behind M25's seam, soft proofing,
render --soft-proof. Proved by the reference documents, the EU regulation and the print source rendered within the threshold of MuPDF's color-managed rendering;RenderBenchmarksrows with and without management. Leaves plates. - Separations and overprint simulation. Delivers the plate device, previews, simulation,
render --separations. Proved by unit tests (knockout, overprint with/OPM0 and 1,/All, aDeviceNof three spots); integration: plates equal Ghostscript'stiffsepwithin the recorded threshold on every print file, the composites equal its and MuPDF's overprint simulation within it. Leaves the whole. - The whole. Delivers the tool's options and verbs,
PrintBatchBenchmarks, the fuzz targets for the ICC parser and the part-tree reader in M23's campaign, the documentation. Proved by every acceptance row, andCorpusToolTests.
Tests required
Unit — tests/AdCodicem.Pdf.Tests, a folder per satellite as M10 placed its satellite's — the HTML and
rendering satellites' in the test project M12.1's first slice gives the satellites that carry native assets, if it
gives them one:
- Geometry: every combination of
bleed,marksandpage-orientation; boxes on pages, never on nodes; a copied page with both trim and art boxes under each policy. - Intents and claims: X-4 and X-4p objects; the shared profile under dual claims; each identification read and written; a malformed claim; dates required.
- Constraints: each row alone, under
Refuse,ConvertandRemoveClaim; the version cap if confirmed. - ICC: each tag type and pipeline; v2 and v4 Lab encodings; each intent; compensation on and off; absolute colorimetric with a non-D50 media white; a device link; a named-color profile refused.
- Hostile ICC: a tag count of 2³² − 1, a tag past the profile's end, two tags overlapping, a CLUT declaring 15 inputs
of 255 points over 200 bytes, a
paraof type 9, a curve of 2³¹ entries, a profile whose declared size disagrees with its stream, a truncated header — each rejected withcolor.profile-invalid, within its time and allocation budget, nothing allocated from a declared size unchecked. - Determinism: every transform's output bytes identical across two runs, two cultures, and the platforms CI runs.
- CSS:
device-cmyk()under each intent family;@color-profilerefused by the resolver; each spot and overprint form; a name with non-ASCII letters; two declarations of one name. - Conversion: each operator family; each image filter and bit depth; each shading type; colored and uncolored
patterns;
d0andd1; annotation appearances; soft masks untouched; spots kept and converted; JPEG left without the named step and converted with it. - PDF/VT: tree shapes; forward-only reservation with a non-seekable output; metadata vocabulary; reuse hints only on proven-independent XObjects; the read model on a tree with a cycle, a leaf without pages, pages without a leaf.
- Profiles: a triggering, a silent and a borderline fixture per rule; every catalog entry with its references.
- Plates: knockout; overprint under both modes;
/Alland/None; bands of one row. - Fuzzing: the ICC parser and the part-tree reader join M23's campaign, seeded with every profile the corpus embeds and the hostile set; a finding becomes a regression test first.
Integration — tests/AdCodicem.Pdf.IntegrationTests, every referee in a container (ADR 27), as Referees lists
them: the chosen referee on every PDF/X and PDF/VT file generated and every received print file; veraPDF on every dual
claim; qpdf --check on every output; pikepdf, ExifTool, Ghostscript, MuPDF, LittleCMS and iccDEV on what each
confirms; M14's Factur-X referee on the PDF/X-4 invoice.
Acceptance conditions
"The reference documents" are M12's renderings of tests/corpus/sources/invoice-fr.html, report-fr.html and
contract-fr.html. "The print source" is a statement with a full-bleed band, a spot-color logo, CMYK brand colors
and crop marks — not in the corpus (below). "The batch" is M12's thousand invoices from invoice-fr.html and a
seeded record set. "The test output profile" is Adobe's U.S. Web Coated (SWOP) v2, a version 2 CMYK output profile,
read at test time from the output intent of the committed vendor/eu-publications/distiller10-eu-consolidated-regulation-2015.pdf,
never copied out of it; a version 4 profile and a FOGRA-characterized one are not in the corpus (below). "The
referee" is the one slice 1's ADR chooses; where it runs outside CI, a row passes on its committed report bound to the
output's hash. Remote rows close only on a green Remote corpus run, recorded in status.md.
| Documents | Behavior | Verified by |
|---|---|---|
| The reference documents and the print source, generated as PDF/X-4 and as PDF/X-4p on the test output profile | The referee reports no error; pikepdf reads nested boxes and one GTS_PDFX intent with its profile or reference; ExifTool reads the identification and Trapped; qpdf is silent | CorpusPrintTests.Generated_pdfx_documents_pass_the_referee (new) |
| M14's Factur-X invoice, generated as PDF/A-3b and PDF/X-4 together | veraPDF reports no error under 3b; the referee none under PDF/X-4; both intents point at one profile object; M14's Factur-X referee accepts the invoice | CorpusPrintTests.A_facturx_invoice_is_also_pdfx_4 (new) |
| The batch, generated as PDF/VT-1 and as PDF/VT-2 over PDF/X-4p | The referee reports no error; pikepdf's walk of the part tree finds one leaf per record with its pages and metadata; the logo is one object referenced by every page; throughput and allocation within M12's batch budget plus the recorded overhead | CorpusPrintTests.Generated_pdfvt_batches_pass_the_referee (new) |
remote/ocrmypdf/photoshop-cc2015-pdfx3-cmyk.pdf (a PDF/X-3:2002 claim); vendor/eu-publications/distiller10-eu-consolidated-regulation-2015.pdf (a SWOP intent under a PDF/A-1a claim); vendor/us-federal/illustrator-irs-pub1-english.pdf (a trim box and an art box together, PANTONE 301 C); third-party PDF/X-4 and PDF/VT files — not in the corpus (below) | Our profiles give the referee's verdict on the level each claims or is asked about; every check the referee fails maps to one of our rules or a recorded reason | CorpusPrintTests.The_pdfx_profiles_agree_with_the_referee (new) |
vendor/us-federal/illustrator-irs-pub1-english.pdf (a Separation and an overprinting graphics state); remote, remote/pdf-association/indesign-cs6-pdfua1-brochure.pdf (overprint, multiply) and remote/opf-format-corpus/acrobat9-portfolio-signed-3d.pdf (DeviceN with a type 4 tint transform); the print source | Through M03's rewrite, M06's merge, M09's stamp, M23's lossless optimization and M29's conversion with spots kept: every colorant name byte-identical, and Ghostscript's tiffsep plates before and after within the recorded threshold | CorpusPrintTests.Spot_colors_and_overprint_survive_every_operation (new) |
| The test output profile; the sRGB profile M14 embeds; the ICC's published sRGB profiles and the iccDEV test profiles — not in the corpus (below) | Every transform within the recorded ΔE2000 of LittleCMS and of iccDEV, per intent, with and without compensation; the same bytes on every platform CI runs, WebAssembly included | CorpusColorTests.Transforms_match_the_reference_implementations (new) |
Every ICC profile embedded in a committed document — output intents, ICCBased spaces, DefaultCMYK —, and in the remote ones nightly | Parsed with the tag list iccDumpProfile reads, or rejected with color.profile-invalid; never an untyped exception | CorpusColorTests.Every_embedded_profile_parses_or_is_reported (new) |
vendor/eu-publications/distiller10-eu-consolidated-regulation-2015.pdf merged with documents/archival/libreoffice-report-pdfa2b.pdf (an sRGB intent under PDF/A-2b) and documents/invoice/chromium-invoice-fr.pdf, under a PDF/X-4 target on the test output profile, with and without ConvertParts | One intent in the output; the RGB parts converted, or covered by a default space; the referee accepts; pdftotext's text unchanged; without a provider, the loss reported rather than a claim written | CorpusPrintTests.Merged_output_intents_are_reconciled_or_the_loss_reported (new) |
vendor/eu-publications/distiller10-eu-consolidated-regulation-2015.pdf (sRGB ICCBased content in page groups that blend in DeviceRGB, under a CMYK intent: veraPDF 1.30 fails its PDF/A-1a claim on 6.2.3.3 test 1, three checks, and on 6.4 test 3, a transparency group on each page) | Converted to its own intent under the default DeviceOnly scope: the page groups blend in DeviceCMYK, veraPDF's 6.2.3.3 failure disappears and its 6.4 failure stays — conversion does not remove transparency groups, which part 1 forbids —, nothing else in its report changing; the sRGB content, which part 1 admits under any intent, converted only under DeviceAndIcc; MuPDF's soft proofs before and after within the recorded ΔE2000 | CorpusPrintTests.Device_color_is_converted_to_the_output_intent (new) |
| The reference documents, the print source, the EU regulation; remote, the Photoshop PDF/X-3 page | Our color-managed rendering within the recorded threshold of MuPDF's with color management on | CorpusRenderingTests.Color_managed_renderings_agree_with_mupdf (new) |
| The Illustrator IRS publication, the print source; remote, the InDesign brochure and the Acrobat 9 portfolio | Our plates equal Ghostscript's tiffsep plates by name and within the recorded threshold; overprint simulation within it of Ghostscript's and MuPDF's | CorpusRenderingTests.Separations_and_overprint_agree_with_ghostscript (new) |
| Every profile the corpus embeds, and the hostile profiles — not in the corpus (below) — as seeds | The nightly campaign over the ICC parser and the part-tree reader finds no untyped exception, hang or unbounded allocation over fourteen consecutive nights before the milestone closes | FuzzingTests.Parsing_a_mutated_icc_profile_either_works_or_reports (new), the Fuzzing workflow's record |
| Every print output above | Byte-identical across two runs, two cultures, and the platforms CI runs | CorpusPrintTests.Print_output_is_deterministic_everywhere (new) |
| The same operations through the tool | The AOT binary's print options and verbs produce the API's bytes | CorpusToolTests.Print_verbs_match_the_api (new) |
Corpus
What the corpus holds
- PDF/X: one claim, remote — Photoshop CC 2015's PDF/X-3:2002 page with a SWOP CMYK intent, a
DefaultCMYK, one profile embedded twice, CMYK JPEGs with/ColorTransform, an unused overprinting graphics state and an art box (pdfx-3-claim,cmyk-output-intent,default-cmyk-iccbased,duplicate-icc-profile,artbox). - CMYK output intents: Distiller 10's EU regulation, committed, a PDF/A-1a claim veraPDF rejects, blending its
sRGB content in DeviceRGB page groups under a SWOP v2 intent (
cmyk-output-intent) — which also gives the test output profile. - Other intents: sRGB under many PDF/A and Factur-X files; AdobeRGB (Ghostscript's PDF/A-1b, remote); PDF/E intents (the BOE gazette and the AbleDocs chapter, remote); a custom intent (iText 9, remote).
- Spots and overprint: Illustrator CS6's IRS publication, committed, with
PANTONE 301 C, overprint, and trim, bleed and art boxes together (separation-color-space,overprint-extgstate,trimbox-bleedbox-artbox); remote, InDesign CS6's brochure (overprint-extgstate,blend-mode-multiply) and the Acrobat 9 portfolio'sDeviceN(devicen-nchannel,type4-function). - Page boxes: DILA's FOP notice (
bleedbox-trimbox), the IRS publication, the Photoshop page (artbox). - ICC-based color: ICCBased images in 16 committed documents, CalRGB and CalGray, CMYK JPEGs (remote).
- Batch material: M12's template and seeded record set, generated at test time.
What it lacks
| Need | Why | Priority | Likely source |
|---|---|---|---|
| PDF/X-4 files from InDesign, Acrobat or a RIP vendor, with a preflight report | The PDF/X-4 profile needs third-party files to agree with; the corpus's only claim is PDF/X-3 | 1 | A public source: the Ghent Workgroup's and the Altona suites, terms read first, remote unless redistributable; a contribution from a print provider (W21) |
| PDF/VT files from variable-data tools | The PDF/VT profile and PdfDocumentParts need real part trees, not only our own | 1 | A public source (vendors' published samples, the Ghent Workgroup's PDF/VT material if any, terms read); a contribution from an éditique provider (W21) |
| The referee's verdict on every print file of the corpus, recorded in the manifest | Expectations come from the independent tool, as conformanceValid does for veraPDF; claimsConformance admits PDF/A and PDF/UA only | 1 | Generated here by the referee's harness, or by the maintainer's committed reports, into M20's list of claims — whose pattern admits PDF/X and PDF/VT — with this referee's verdict beside veraPDF's; slice 1 records it with its first reports |
| The print source: a statement or invoice with a full-bleed band, a spot-color logo, CMYK brand colors, crop marks, and its approved rendering | Every generated-print row needs one document that exercises bleed, spots and marks together | 1 | Generated here: tests/corpus/sources/statement-fr.html, with its Chromium rendering beside it for the visual referee as M12's sources have |
| Reference ICC profiles we may commit: the ICC's sRGB v2 and v4, a v4 CMYK output profile, a FOGRA-characterized one, a gray and a Lab profile, a device link, a named-color profile | Transforms must be measured on more than one v2 CMYK profile read out of a document; the refused classes need a file | 1 | A public source (W19): the ICC's published profiles and iccDEV's test profiles, terms read; ECI's profiles (terms to verify); generated here: ArgyllCMS's colprof over published characterization data (terms to verify); each under tests/corpus/sources/third-party/ with its license and SOURCE |
| Hostile ICC profiles, one per bound above | The parser's tests and the fuzzing seeds | 1 | Generated here: hand-written in the test support, recorded |
Documents with several spots, a spot DeviceN, overprint with /OPM 0 and 1, a CMYK-blended group | The plates and the profiles meet each case in one file at most | 2 | Generated here: ReportLab's separation colors and overprint (to verify in the pinned version), Ghostscript's pdfwrite |
| PDF/X-1a and PDF/X-3 files from other producers | Two older profiles judged on one Photoshop page | 2 | Generated here: Ghostscript's pdfwrite with its PDF/X option (to verify which parts it writes); a public source |
| An invoice or statement run as a print provider receives it | The use case the milestone exists for | 2 | A contribution, anonymized (W21) |
Traps
- The referee may not be free, and may not run in CI. A milestone whose acceptance cannot run is not specified; the maintainer settles it at the start, slice 1 records it before anything is built, and a committed report is bound to the bytes it judged, or it proves nothing about the next build.
- A committed report goes stale with the runtime: Flate is deterministic per runtime (M03's trap), so a servicing release that changes the deflater changes every output's hash and asks for new reports.
- The parts differ more than their names suggest. PDF/X-4 keeps transparency live, which X-1a and X-3 forbid; X-4 allows RGB and Lab when managed, X-1a allows CMYK and spots only; X-1a and X-3 identify themselves in the information dictionary, X-4 in the XMP.
/TrimBoxand/ArtBoxtogether are forbidden, and the corpus's Illustrator file has both, each equal to or inside its media box: a copy under PDF/X must choose.- A default is not a box. A trim box absent from a page defaults to its crop box, itself inherited from the page tree: every reader sees one, and a preflight still reports it missing.
- Device color means nothing without an intent, and an RGB
rgunder a CMYK intent means nothing at all unless a default space says what it meant. - Blending happens in the group's space. A
multiplycomputed in RGB and printed in CMYK is not themultiplythe designer saw. - Spot names are exact. Case, spaces and their encoding in a name decide which plate a color lands on; a name is
never normalized, and
/Alland/Noneare not spots. - Overprint changes meaning with the family: it has no effect in RGB, and
/OPM 1makes a zero CMYK component transparent. Converting color can change what overprints. - A luminosity soft mask is not color on paper: converting its group changes the mask.
- v2 and v4 encode Lab differently, and a transform between the two versions that ignores it shifts every lightness by a fraction.
- Black-point compensation is where implementations disagree; say which reading is ours.
- Floating point is deterministic only if written to be: a fixed order, no implicit fused multiply-add, no
Math.Pow(M22's trap). - An ICC profile is hostile input: tag tables, counts, offsets and CLUT sizes are bounded by the bytes present (invariant 4).
- A JPEG cannot change color without a generation; conversion leaves it unless a lossy step is named.
- PDF/X needs dates, and the library has no clock: a target without them is refused, never completed with "now".
- The version cap: if ISO 15930-7 limits the header to 1.6, a writer that knows only 1.7 and 2.0 writes files every preflight rejects.
- A dual PDF/A and PDF/X file needs one profile object under both intents and both identifications in one XMP packet, with the extension schemas PDF/A requires for what it does not predefine.
- A ten-thousand-record run must not hold ten thousand of anything: part-tree arrays are bounded by intermediate nodes, and the shared artwork is one object.
- Reuse hints are promises to the RIP: an XObject marked independent of its context that is not will print wrongly on every record after the first.
Documentation
docs/website/docs/guides/print-production.md(new) — PDF/X-4 and 4p, PDF/VT, output intents, bleed and marks, dual claims with PDF/A and Factur-X, what the referee is and how a report is bound to a file.docs/website/docs/concepts/color.md(new) — color spaces, output intents, the color-management satellite, rendering intents and compensation, spots and overprint, conversion and what it leaves, soft proofs and separations.docs/website/docs/reference/css-support.md—bleed,marks,device-cmyk(),@color-profile,color(), the spot and overprint extensions.docs/website/docs/concepts/validation.md,docs/website/docs/reference/validation.mdand the generated rule reference — the PDF/X and PDF/VT profiles and their families.docs/website/docs/reference/diagnostics.md— the codes above;docs/website/docs/reference/tool/— the options and verbs.docs/website/docs/introduction.mdanddocs/features/features.json— theprintentry brought to its state.docs/architecture.md— the color satellite's identifier, contents and dependencies; the core's transform seam; the plate device in.Rendering.docs/corpus.md, the manifest schema andtests/corpus/README.md— PDF/X and PDF/VT claims and the referee's verdicts;docs/corpus-sources.md— the profiles' and suites' terms.- The ADRs: the referee, and the amendment of ADR 40 if the version cap holds — both slice 1's.
docs/status.md— the tolerances, the thresholds, the referee's versions, the benchmarks.
Exit criteria
- The referee is chosen and the version cap settled by the maintainer at the start, both recorded as ADRs by slice 1, and the referee's harness runs as its ADR decides.
- PDF/X-4 and 4p generation, the dual claims with PDF/A and Factur-X, and PDF/VT-1 and 2 on the batch exist, each accepted by the referee.
- The color-management satellite converts at every intent within the recorded tolerance of LittleCMS and iccDEV, byte-identical on every platform CI runs; M25's seam and the core's are implemented.
- Spot colors, overprint and CMYK are written from CSS, and survive every operation of the library.
- Document color conversion works, and M06, M09, M20 and M21 use it where they deferred to this milestone.
- The PDF/X and PDF/VT profiles agree with the referee on every print file of the corpus, or each disagreement is recorded with its reason.
- Soft proofs, separation previews and overprint simulation agree with MuPDF and Ghostscript within the recorded thresholds.
- The priority-1 gaps above are filled; each remaining gap is recorded in
docs/corpus-contributions.md. - The acceptance conditions above pass on the corpus, in CI — or through the referee's committed reports bound to
the outputs' hashes, as the ADR decides —, with no document skipped, and the remote rows on a green
Remote corpusrun recorded instatus.md. - Unit tests cover each behavior, its degenerate cases and its hostile ones; the FsCheck property holds; the ICC parser and the part-tree reader have run fourteen consecutive nights in the fuzzing campaign with no open finding.
- Integration tests run the referee, veraPDF, qpdf, pikepdf, ExifTool, Ghostscript, MuPDF, LittleCMS, iccDEV and M14's Factur-X referee, each in a container.
-
ColorTransformBenchmarksandPrintBatchBenchmarksrun withMemoryDiagnoser, their rows inbenchmarks/budgets.json;docs/status.mdrecords the measurements. - The print options and verbs ship in the dotnet tool and the AOT binary, documented.
- The documentation site publishes the pages listed above, and
features.jsonmatches what exists. - Every page of Documentation is written in its Diátaxis section, one mode per page (ADR 47).