A prepress house sends the job back: the same spot ink separated onto two plates. HotPDF prevents that at authoring time by writing NChannel as the ISO 32000-2 five-element DeviceN form and by keeping one canonical alternate space and tint transform per spot name for the whole document, rejecting the second, conflicting definition instead of emitting it
NChannel is not a colour space family name
The first thing to unlearn is the name itself. NChannel is not a colour space family the way Separation and DeviceN are families. ISO 32000-2 §8.6.6.5 describes it as a subtype of DeviceN, so a conforming NChannel space is written as the five-element array [/DeviceN names alternateSpace tintTransform attributes], and the attributes dictionary carries /Subtype /NChannel. There is no [/NChannel ...] array in the specification. If you have ever hand-built one and watched a RIP shrug at it, that is why
HotPDF got this wrong once and then fixed it, which is worth saying plainly because it shapes how the component behaves today. Older HotPDF releases emitted the family-name form. THotPDF.RegisterNChannelColorSpace now emits only the standard DeviceN-plus-attributes form, and because the NChannel subtype arrived in PDF 1.6, the producer entry point gates on RequirePDFVersion(pdf16, ...) and simply declines on older targets. The render side is deliberately more forgiving than the writer: HPDFResolveColorSpace still accepts the legacy /NChannel token as a DeviceN family so files from the old writer keep rendering, but anything HotPDF writes back out uses the standard encoding. Lenient on input, strict on output is the right asymmetry here, because your reader has to cope with files it did not create while your writer has no such excuse
Why does one spot name end up on two plates?
Because a spot colourant name is a document-wide plate identity, not a local argument. Two calls that both name Orange but hand over a different alternate space, or the same alternate space with a different tint transform, describe two different inks that happen to share a label. A RIP building separations has no way to reconcile that, so it does the only honest thing and gives you two plates. HotPDF therefore maintains a per-document canonical signature per colourant name. RegisterSpotColorantDefinition composes that signature from the alternate colour space and the shape of the tint function, and every RegisterSeparation, RegisterSeparationFunc, RegisterSeparationLUT, and NChannel spot definition passes through it. When a second definition disagrees, the call raises rather than silently registering a second variant, and the message is deliberately specific about the failure mode, because the alternative is discovering it on a plate proof three weeks later
// Orange was already registered against DeviceCMYK with the
// tint transform 0 / 0.55 / 1 / 0 at full tint.
Conflicting := Pdf.RegisterExponentialFunction(
Domain1, NoInk, OtherOrangeCMYK, 1, []);
try
Pdf.RegisterSeparationFunc('Orange', 'DeviceCMYK', Conflicting);
except
on E: Exception do
// 'Spot colourant "Orange" has inconsistent alternate colour
// space or tint definition in this document'
LogPrepressWarning(E.Message);
end;
Splitting the master names into process and spot
A complete NChannel has to account for every one of its master colourant names exactly once, as either a process component or a spot colourant. The advanced RegisterNChannelColorSpace overload takes the master ColorantNames, the ProcessColorantNames, the alternate space, the overall tint transform, an array of THPDFNChannelSpotColorant records, and an optional printing order. Each spot record carries its own name, its own single-input Separation tint transform, an optional solidity, and an optional dot-gain function. The overall tint transform must map N inputs to the alternate space component count; each spot tint must map one input to that same count
const
Colorants: array[0..4] of AnsiString =
('Cyan', 'Magenta', 'Yellow', 'Black', 'Orange');
ProcessNames: array[0..3] of AnsiString =
('Cyan', 'Magenta', 'Yellow', 'Black');
Order: array[0..4] of AnsiString =
('Yellow', 'Magenta', 'Cyan', 'Orange', 'Black');
Domain5: array[0..9] of Single = (0, 1, 0, 1, 0, 1, 0, 1, 0, 1);
Range4: array[0..7] of Single = (0, 1, 0, 1, 0, 1, 0, 1);
Domain1: array[0..1] of Single = (0, 1);
NoInk: array[0..3] of Single = (0, 0, 0, 0);
OrangeCMYK: array[0..3] of Single = (0, 0.55, 1, 0);
GainC0: array[0..0] of Single = (0);
GainC1: array[0..0] of Single = (1);
var
Pdf: THotPDF;
Spots: array[0..0] of THPDFNChannelSpotColorant;
CSName: AnsiString;
begin
Pdf.Version := pdf20;
Pdf.BeginDoc;
Spots[0].Name := 'Orange';
Spots[0].TintTransform := Pdf.RegisterExponentialFunction(
Domain1, NoInk, OrangeCMYK, 1, []);
Spots[0].HasSolidity := True;
Spots[0].Solidity := 0.82;
Spots[0].DotGainFunction := Pdf.RegisterExponentialFunction(
Domain1, GainC0, GainC1, 1, []);
CSName := Pdf.RegisterNChannelColorSpace(Colorants, ProcessNames,
'DeviceCMYK',
Pdf.RegisterPostScriptFunction(Domain5, Range4,
'{ pop pop pop pop pop 0 0 0 0 }'),
Spots, Order);
From that call HotPDF emits the attributes dictionary the spec asks for: /Subtype /NChannel, a /Process dictionary whose /ColorSpace is the process space and whose /Components lists the process names in that space component order, a /Colorants dictionary holding a real [/Separation name alternate tintfn] array for each spot, and a /MixingHints dictionary carrying /Solidities, /PrintingOrder, and /DotGain when you supplied them. The returned resource name goes to SetFillColorSpace or SetStrokeColorSpace exactly like the simpler spaces covered in the article on Separation and DeviceN spot colour rendering
What does the consistency check actually reject?
It rejects structural incoherence within the space, and it does so before a single object is written. Colourant names must be unique and must not be empty, All, or None. Process and spot definitions together must cover the master names exactly, with no colourant appearing in both roles and none left undefined. When the alternate space is DeviceCMYK, the process components must be Cyan, Magenta, Yellow, Black in that order, and the process name count must match the alternate component count. Every tint transform and dot-gain function must be an indirect function object with the right input and output arity. Solidity must be a finite value inside 0..1. Printing order must be empty or a full permutation of the master names, never a partial list. What it does not do is judge colour: nothing here checks that your Orange tint transform actually resembles the ink in the can, that its CMYK build is a sane proxy, or that the solidity you supplied matches measured behaviour on the substrate. Those are press and measurement questions, and the component has no standing to answer them. The simpler process-only overload is stricter still by design: it produces a process-only NChannel and deliberately refuses to accept spot names, because writing a spot into the names array without a matching /Colorants entry would produce a structurally non-conforming file, and fabricating a default definition would be worse than failing
PDF/X-6n output intents must cover every registered spot
An N-colourant PDF/X-6n file declares its colourants twice, and the two declarations must agree. AddPDFX6ExternalOutputIntent writes the external ICC profile reference with its ColorantTable, and before it does, ValidateRegisteredSpotOutputColorants walks every spot the document has registered and raises if one is missing from the table. The check runs in both directions: once an output intent has published its colourant list, a later spot registration for a name outside that list is refused as well. AddPDFX6ExternalOutputIntentSpotData adds the per-ink metadata on top of that, and it enforces its own rules, notably that a colourant carries either a solidity value or CxF/X-4 spectral data, never both. This is the same conformance surface discussed in the piece on PDF/A, PDF/X, and PDF/UA validation
Spectral := TMemoryStream.Create;
try
LoadCxFForInk('Orange', Spectral); // ISO 17972-4 payload
Pdf.AddPDFX6ExternalOutputIntentSpotData(
'ECG-5', 'Five-colour output condition',
'https://profiles.example.com/ecg-5.icc', '5CLR',
Colorants,
'00112233445566778899AABBCCDDEEFF', #4#3#0#0,
['Cyan'], [0.70], // solidity for one colourant
Order,
['Orange'], [Spectral]); // spectral data for another
finally
Spectral.Free;
end;
Two operational details are easy to miss. The registry that backs all of this is per document and is cleared at document boundaries, so loading a new file into the same THotPDF instance does not inherit the previous document spot identities; that isolation is the point, since a leaked signature would reject perfectly valid work in the next job. And the profile side has hard limits of its own: an external output intent requires a PDF 2.0 target, an absolute HTTP or HTTPS profile URL, a four-byte ICC colour space signature, and for PDF/X-6n between 2 and 15 colourants with a matching 2CLR to FCLR signature
Where the checking stops
The CxF/X-4 handling is the part to be honest about. HotPDF applies bounded structural safety checks to the spectral stream and confirms that the ink identity inside it matches the colourant you named. It caps the payload size, rejects embedded null bytes, refuses any stream containing a DOCTYPE or ENTITY declaration, requires a recognisable CxF root, and requires exactly one SpotInkCharacterisation element carrying exactly one SpotInkName that equals your colourant name. That is a gate against malformed and hostile input, not a schema validator. It is not a complete ISO 17972-4 implementation, it does not verify your spectral measurements, and it does not reverse-audit arbitrary pre-existing third-party object graphs or the internal colourant tables inside an embedded ICC profile. If your workflow depends on full CxF conformance, validate the file with a dedicated tool before you hand it to the component
One adjacent constraint bites people who never expected to meet it. A luminosity soft mask cannot use Separation, DeviceN, or NChannel as its transparency group /CS; the group blending space has to be a device or CIE-based space, so spot paint inside the group is resolved through its tint transform into the alternate space before luminosity is computed. RegisterLuminositySoftMaskState builds a DeviceGray group precisely so this cannot go wrong by accident. The practical consequence is that a spot-heavy design masked this way is being evaluated through its CMYK proxy, not its ink, which matters when you compare it against a separated proof as described in the notes on overprint proofing and render devices
None of this removes the need for a plate proof, but it moves an entire class of prepress rejections from the press room back to the build. The NChannel, Separation, and PDF/X-6n output intent APIs described here ship in the standard HotPDF Delphi Component for Delphi and C++Builder, where the reference documents the full record layouts and the exact conditions under which each call fails closed