Produce print-ready CMYK and spot color
At a glance
Section titled “At a glance”Screen color is RGB. Print color is not. When a press or a proofing RIP consumes your PDF, it wants process inks (CMYK), the occasional named ink (a spot or Separation colour), and a declaration of the production condition it should assume. This recipe shows the three colour-emission paths NextPDF gives you for that audience:
- Device CMYK fills and strokes —
setFillColorCmyk()/setDrawColorCmyk(). - Spot / Separation inks at a tint —
registerSpotColor()thensetSpotFillColor()/setSpotDrawColor(). - An ICC output intent that declares an RGB working/output condition for the whole document —
Config::withOutputColorProfile(). The bundled profiles are RGB working spaces; this does not declare a true CMYK press / PDF/X print condition.
Scope note up front: this is colour emission. NextPDF writes the CMYK numbers, the Separation space, and the output-intent declaration you ask for. It does not run a colour-management transform — it will not re-render your RGB artwork into a destination CMYK gamut, soft-proof it, or guarantee a visual match. Supply colours already specified for your condition.
Install
Section titled “Install”composer require nextpdf/core:^3No optional extension is required. The colour API has been stable since 1.2.0 and runs on the 8.1–8.4 backport matrix.
Device CMYK fills and strokes
Section titled “Device CMYK fills and strokes”CMYK is the default for offset and digital process printing: four channels, cyan / magenta / yellow / black (the “key”). Set the fill or stroke colour, then draw as usual.
<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();$doc->addPage();
// Components are percentages, 0–100 (cyan, magenta, yellow, key).$doc->setFillColorCmyk(0, 95, 100, 0); // a process "red"$doc->rect(20, 30, 60, 40, 'F');
$doc->setDrawColorCmyk(100, 0, 0, 0); // pure cyan rule$doc->setLineWidth(1.0);$doc->rect(20, 30, 60, 40, 'S');
$doc->setTextColorCmyk(0, 0, 0, 100); // rich-free 100% K text$doc->setFont('helvetica', '', 12);$doc->text(20, 85, 'Process CMYK');
$doc->save(getenv('NEXTPDF_COOKBOOK_OUTPUT') ?: __DIR__ . '/cmyk.pdf');Each call emits a DeviceCMYK colour operator into the content stream: k for the non-stroking (fill/text) colour and K for the stroking colour, with the four components scaled to the 0.0–1.0 range PDF uses internally. ISO 32000-2 §8.6.8 (Colour Operators, Table 73) defines k and K as the DeviceCMYK colour-setting operators; §8.6.4.4 defines the DeviceCMYK colour space itself.
The underlying value object is NextPDF\Graphics\Color. If you build colours directly, Color::cmyk($c, $m, $y, $k) takes the same 0–100 percentages and is what every …Cmyk document method calls.
Spot / Separation inks at a tint
Section titled “Spot / Separation inks at a tint”A spot colour is a single named ink — a Pantone, an HKS, a varnish, a die-cut “cut” plate — that the press loads as its own plate rather than building from process inks. In PDF it is a Separation colour space: a colourant name plus an alternate space (here DeviceCMYK) and a tint-transform that maps a tint value in [0, 1] to the alternate, so non-spot renderers still show an approximation.
Register the ink once with its CMYK fallback, then set it as the fill or stroke colour at a tint percentage:
<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();$doc->addPage();
// Register the ink and its CMYK fallback (percentages, 0–100).$doc->registerSpotColor('PANTONE 485 C', 0, 95, 100, 0);
// Full-strength fill.$doc->setSpotFillColor('PANTONE 485 C'); // tint defaults to 100$doc->rect(20, 30, 50, 30, 'F');
// 40% tint of the same ink.$doc->setSpotFillColor('PANTONE 485 C', 40);$doc->rect(80, 30, 50, 30, 'F');
// Same ink as a stroke at 75%.$doc->setSpotDrawColor('PANTONE 485 C', 75);$doc->setLineWidth(1.5);$doc->line(20, 75, 130, 75);
$doc->save(getenv('NEXTPDF_COOKBOOK_OUTPUT') ?: __DIR__ . '/spot.pdf');You must registerSpotColor() before you select it; an unregistered name throws a PageLayoutException. Registration writes the Separation resource and tint transform; setSpotFillColor() / setSpotDrawColor() then emit /CS_<token> cs <tint> scn for a fill (or the upper-case /CS_<token> CS <tint> SCN stroking form) against that registered Separation resource. Two distinct names are at play here, and the engine handles them separately:
- The resource KEY — the
/CS_…token that the content-stream operator references and that the page’s/Resources /ColorSpacedictionary is keyed under — is derived from the ink name by replacing each non-word character with_, so'PANTONE 485 C'selects the resource/CS_PANTONE_485_C. (For ink names that contain a literal_, a hyphen, or other punctuation, a short deterministic suffix is appended to keep the key collision-free, so two near-identical names never share one colour space.) - The Separation colourant name itself — the name carried inside the
[/Separation /<name> …]colour-space array — is kept as-is and encoded by the writer as a PDF name object, hex-escaping (#XX) any delimiter or non-printing byte. It is not flattened to theCS_…token; the resource key and the colourant name are computed independently.
The tint argument you pass is a percentage, 0 (paper) to 100 (full ink), but the operand written into the content stream is the normalised 0.0–1.0 value, so a 40 tint emits /CS_PANTONE_485_C cs 0.400000 scn. ISO 32000-2 §8.6.6.4 covers Separation colour spaces and their tint transform.
The default tint transform is a linear interpolation from [0 0 0 0] (paper at tint 0) to the registered CMYK fallback (full ink at tint 1). That is correct for ordinary spot inks; a non-linear ink or a measured profile needs a custom transform, which is beyond this recipe.
Declare an ICC output intent
Section titled “Declare an ICC output intent”PDF can carry a document-level declaration of the colour condition its colours were prepared for — an output intent: a dictionary naming the production condition, with the destination ICC profile embedded as an ICCBased stream. In a true print/PDF-X workflow this is a CMYK press condition (a coated FOGRA or GRACoL profile, for example). The profiles NextPDF bundles, however, are RGB working spaces — so what Config::withOutputColorProfile() declares is an RGB viewing / working-space output condition, not a CMYK destination. See the note below the example before relying on it for press output.
NextPDF exposes this through the document Config. Pick one of the bundled working-space profiles, build the config, and create the document from it:
<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Config;use NextPDF\Core\Document;use NextPDF\Core\OutputColorProfile;
// Declare an explicit, embedded output intent for the document.$config = (new Config()) ->withOutputColorProfile(OutputColorProfile::Srgb);
$doc = Document::createStandalone($config);$doc->addPage();
$doc->setFillColorCmyk(0, 95, 100, 0);$doc->rect(20, 30, 60, 40, 'F');
$doc->save(getenv('NEXTPDF_COOKBOOK_OUTPUT') ?: __DIR__ . '/with-intent.pdf');OutputColorProfile is an enum of working spaces the engine bundles a profile for. All profile-backed cases are three-component RGB working spaces; DeviceRGB is the default sentinel and emits no output intent. There is no bundled CMYK destination profile:
DeviceRGB— the default. No output intent is emitted: no PDF-level output condition is declared, and the PDF/A pipeline (if active) retains exclusive control of the/OutputIntentsarray. Use this when you do not want to declare one. (PDFDeviceRGBcontent with no output intent is device-dependent — it is not a declared sRGB.)Srgb— emits an output-intent dictionary embedding the bundledsRGB.icc, with the standard condition identifiersRGB IEC61966-2.1.DisplayP3,Rec2020,A98RGB,ProphotoRGB— wide-gamut RGB working spaces, each embedding its bundled ICC profile under a custom (NextPDF wide-gamut …) condition identifier.
Any case other than DeviceRGB makes the writer emit an output-intent dictionary referencing the embedded ICCBased stream (with its /N 3 component count). ISO 32000-2 §14.11.5 defines the output-intent dictionary; §8.6.5.5 defines the ICCBased colour space that carries the embedded profile. In the current engine this dictionary is written with the PDF/X output-intent subtype /GTS_PDFX regardless of which bundled profile you pick (the PDF/A archival path uses its own /GTS_PDFA1 subtype and is emitted separately).
The important caveat: because all the bundled profiles are RGB, this declares the document’s RGB working space and viewing intent — it is not a CMYK destination profile, and it does not transform anything into CMYK. The engine does not let you supply a custom CMYK destination ICC through withOutputColorProfile(). A true print/PDF-X CMYK output intent requires a workflow that embeds your own CMYK ICC profile as the destination; for an archival output intent wired into the same /OutputIntents array, see PDF/A-4 output.
When to reach for each
Section titled “When to reach for each”- Device CMYK — the default for any artwork bound for a process press. Specify your colours in CMYK from the start so no RGB→CMYK guesswork happens downstream.
- Spot / Separation — brand colours that must hit an exact ink, plus non-process plates (varnish, white, metallic, die-cut, foil). Register once, paint at any tint.
- Output intent — set it when you want the file to be self-describing about its RGB working/output condition, or when a consumer (a viewer, a colour-managed RIP, a validator) expects a declared output condition. The bundled profiles declare an RGB working/output condition only; they do not declare a true CMYK press / PDF/X print condition.
Edge cases & gotchas
Section titled “Edge cases & gotchas”- CMYK components are percentages (0–100), not 0–1. The
…Cmykdocument methods andColor::cmyk()take each channel as a0–100percentage and divide by 100 when writing the operator; the content-stream operand is the normalised0.0–1.0value. Pass100for full ink, not1.0(which the engine would treat as 1% ink). - Register before you select a spot.
setSpotFillColor()/setSpotDrawColor()throw if the name was never registered. Names are matched exactly, including case and spaces. - Colour state persists. A CMYK or spot colour stays in effect until you change it. Reset before an unrelated block.
- The CMYK fallback is an approximation. The Separation tint transform exists so non-spot renderers can show something; it is not the press output. The real ink is what the named plate produces.
- An output intent is a declaration, not a conversion. Declaring
OutputColorProfile::Srgbdoes not transform your colours into sRGB; it states the intended condition. Colour accuracy is your responsibility upstream.
Performance
Section titled “Performance”Each colour selection is a single content-stream operator; a spot also writes one Separation resource per registered ink. An embedded output-intent profile is written once per document. All paths stay well inside the 2000 ms / 64 MB budget.
Security notes
Section titled “Security notes”This recipe emits only the colours and the profile your code specifies. It parses no untrusted input and makes no network access. If colourant names or CMYK components come from external data, validate them: clamp components to 0–100 and reject control characters in spot names (a NUL byte in a Separation name is rejected by the engine).
Conformance
Section titled “Conformance”| Statement | Spec | Clause |
|---|---|---|
k and K are the colour operators that set the DeviceCMYK non-stroking / stroking colour. | ISO 32000-2 | §8.6.8 |
| DeviceCMYK is the subtractive four-component process colour space. | ISO 32000-2 | §8.6.4.4 |
| A Separation colour space names one colourant whose tint maps to an alternate space via a tint transform. | ISO 32000-2 | §8.6.6.4 |
An ICCBased colour space embeds an ICC profile stream with an /N component count. | ISO 32000-2 | §8.6.5.5 |
An output intent declares the target production condition; GTS_PDFX names the print subtype. | ISO 32000-2 | §14.11.5 |
Reproducibility profile — structural. Colour emission has no entropy of its own, but every saved document carries a trailer /ID and date atoms, and an embedded ICC stream is byte-stable only for a fixed bundled profile. The supported claim is structural equality after qpdf normalisation. This recipe describes how NextPDF produces the structure; it does not assert blanket ISO 32000-2 conformance.
Commercial context
Section titled “Commercial context”Not applicable. Device CMYK, spot/Separation colour, and the output-intent declaration are Core capabilities with no Premium gate. Core supports the bundled RGB output-intent declaration shown here; custom CMYK print-condition ICC workflows are not claimed by this recipe.