Technischer Artikel

NChannel-Spot-Farben konsistent in Delphi mit HotPDF

Eine Druckvorstufe schickt den Job zurück: dieselbe Spot-Farbe auf zwei Druckplatten separiert. HotPDF verhindert das zur Erzeugungszeit, indem es NChannel als die ISO-32000-2-Fünfelement-DeviceN-Form schreibt und pro Spot-Namen für das ganze Dokument einen kanonischen Alternativfarbraum und eine Tint-Transformation hält, die zweite, widersprüchliche Definition zurückweist, statt sie auszugeben

NChannel ist kein Familienname eines Farbraums

Das Erste, was man verlernen muss, ist der Name selbst. NChannel ist keine Farbraumfamilie so wie Separation und DeviceN Familien sind. ISO 32000-2 §8.6.6.5 beschreibt es als einen Subtyp von DeviceN, also wird ein konformer NChannel-Farbraum als das Fünfelement-Array [/DeviceN names alternateSpace tintTransform attributes] geschrieben, und das Attributes-Dictionary trägt /Subtype /NChannel. Ein [/NChannel ...]-Array gibt es in der Spezifikation nicht. Wer je eines von Hand gebaut hat und zusehen durfte, wie ein RIP mit den Achseln zuckt, kennt den Grund

HotPDF schreibt einen NChannel-Farbraum als das Fünfelement-DeviceN-Array, dessen Attributes-Dictionary den Subtyp NChannel zusammen mit den Einträgen Process, Colorants und MixingHints trägt, denn ein NChannel-Familiennamen-Array existiert in der Spezifikation überhaupt nicht
Ein konformer NChannel-Farbraum ist ein DeviceN-Array mit einem Attributes-Dictionary, deshalb gibt der Writer nur diese Form aus, während der Reader das Legacy-Token weiter akzeptiert

HotPDF hat das einmal falsch gemacht und es dann repariert, was Klartext verdient, weil es das heutige Verhalten der Komponente prägt. Ältere HotPDF-Versionen gaben die Familiennamen-Form aus. THotPDF.RegisterNChannelColorSpace gibt jetzt nur die Standardform DeviceN-plus-Attributes aus, und weil der NChannel-Subtyp mit PDF 1.6 kam, gate der Producer-Einsprungpunkt auf RequirePDFVersion(pdf16, ...) und lehnt bei älteren Zielen schlicht ab. Die Render-Seite ist absichtlich nachsichtiger als der Writer: HPDFResolveColorSpace akzeptiert das Legacy-Token /NChannel weiterhin als DeviceN-Familie, damit Dateien des alten Writers weiter rendern, aber alles, was HotPDF zurückschreibt, nutzt die Standardkodierung. Nachsichtig bei der Eingabe, streng bei der Ausgabe ist hier die richtige Asymmetrie, denn Ihr Reader muss mit Dateien klarkommen, die er nicht erzeugt hat, während Ihr Writer eine solche Entschuldigung nicht hat

Warum landet ein Spot-Name auf zwei Druckplatten?

Weil ein Spot-Farbstoffname eine dokumentweite Platten-Identität ist, kein lokales Argument. Zwei Aufrufe, die beide Orange nennen, aber einen anderen Alternativfarbraum übergeben, oder denselben Alternativfarbraum mit einer anderen Tint-Transformation, beschreiben zwei verschiedene Farben, die zufällig ein Etikett teilen. Ein RIP, der Separationen baut, hat keine Möglichkeit, das in Einklang zu bringen, also tut er das einzige Ehrliche und gibt Ihnen zwei Platten. HotPDF führt deshalb eine kanonische Signatur pro Farbstoffname und Dokument. RegisterSpotColorantDefinition komponiert diese Signatur aus dem Alternativfarbraum und der Gestalt der Tint-Funktion, und jeder Aufruf von RegisterSeparation, RegisterSeparationFunc, RegisterSeparationLUT und jede NChannel-Spot-Definition läuft hindurch. Widerspricht eine zweite Definition, wirft der Aufruf eine Exception, statt stillschweigend eine zweite Variante zu registrieren, und die Meldung ist absichtlich spezifisch über die Fehlerart, denn die Alternative ist, es drei Wochen später auf dem Plattenproof zu entdecken

Jeder HotPDF-Spot-Registrierungsaufruf läuft durch RegisterSpotColorantDefinition, das pro Farbstoffname eine kanonische Alternativfarbraum- und Tint-Signatur für das Dokument aufzeichnet und eine Exception wirft, wenn eine zweite, widersprechende Definition desselben Namens ankommt
Ein Name, eine Signatur: Die zweite, widersprüchliche Definition einer Spot-Farbe wird zur Erzeugungszeit abgewiesen, statt auf einem Plattenproof entdeckt zu werden
// Orange war bereits gegen DeviceCMYK registriert mit der
// Tint-Transformation 0 / 0.55 / 1 / 0 bei voller Tönung.
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;

Aufteilung der Master-Namen in Process und Spot

Ein vollständiger NChannel muss jeden seiner Master-Farbstoffnamen genau einmal erfassen, entweder als Process-Komponente oder als Spot-Farbstoff. Die fortgeschrittene Überladung RegisterNChannelColorSpace nimmt die Master-ColorantNames, die ProcessColorantNames, den Alternativfarbraum, die Gesamttint-Transformation, ein Array von THPDFNChannelSpotColorant-Records und eine optionale Druckreihenfolge. Jeder Spot-Record trägt seinen eigenen Namen, seine eigene Single-Input-Separation-Tint-Transformation, eine optionale Solidität und eine optionale Dot-Gain-Funktion. Die Gesamttint-Transformation muss N Eingaben auf die Komponentenzahl des Alternativfarbraums abbilden; jeder Spot-Tint muss eine Eingabe auf ebendiese Zahl abbilden

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);

Aus diesem Aufruf gibt HotPDF das Attributes-Dictionary aus, das die Spezifikation verlangt: /Subtype /NChannel, ein /Process-Dictionary, dessen /ColorSpace der Process-Farbraum ist und dessen /Components die Process-Namen in der Komponentenreihenfolge dieses Farbraums listet, ein /Colorants-Dictionary, das für jeden Spot ein echtes [/Separation name alternate tintfn]-Array hält, und ein /MixingHints-Dictionary, das /Solidities, /PrintingOrder und /DotGain trägt, sofern Sie sie geliefert haben. Der zurückgegebene Ressourcenname geht an SetFillColorSpace oder SetStrokeColorSpace, genau wie bei den einfacheren Farbräumen aus dem Artikel zum Rendern von Separation- und DeviceN-Spot-Farben

HotPDF teilt die Master-Farbstoffnamen eines NChannel-Farbraums in ein Process-Dictionary für die vier CMYK-Komponenten und ein Colorants-Dictionary, das pro Spot ein Separation-Array hält, wobei jede Stelligkeit vor dem Schreiben geprüft wird
Jeder Master-Name wird genau einmal belegt, und die Gesamttint-Transformation, die Spot-Tints und die Druckreihenfolge werden alle validiert, bevor irgendein Objekt ausgegeben wird

Was weist die Konsistenzprüfung tatsächlich zurück?

Sie weist strukturelle Inkohärenz innerhalb des Farbraums zurück, und zwar bevor ein einziges Objekt geschrieben wird. Farbstoffnamen müssen eindeutig sein und dürfen nicht leer, All oder None sein. Process- und Spot-Definitionen müssen die Master-Namen zusammen exakt abdecken, ohne dass ein Farbstoff in beiden Rollen auftaucht und ohne dass einer undefiniert bleibt. Ist der Alternativfarbraum DeviceCMYK, müssen die Process-Komponenten Cyan, Magenta, Yellow, Black in dieser Reihenfolge sein, und die Process-Namenzahl muss zur Komponentenzahl des Alternativfarbraums passen. Jede Tint-Transformation und jede Dot-Gain-Funktion muss ein indirektes Funktionsobjekt mit richtiger Ein- und Ausgabestelligkeit sein. Solidität muss ein endlicher Wert innerhalb 0..1 sein. Die Druckreihenfolge muss leer oder eine volle Permutation der Master-Namen sein, nie eine Teilliste. Was sie nicht tut, ist Farbe beurteilen: Nichts hier prüft, ob Ihre Orange-Tint-Transformation tatsächlich der Farbe in der Dose ähnelt, ob ihr CMYK-Aufbau ein vernünftiger Proxy ist oder ob die gelieferte Solidität zu gemessenem Verhalten auf dem Substrat passt. Das sind Presse- und Messfragen, und die Komponente ist nicht befugt, sie zu beantworten. Die einfachere Process-only-Überladung ist dem Entwurf nach noch strenger: Sie erzeugt einen Process-only-NChannel und verweigert sich bewusst Spot-Namen, denn ein Spot in das Namen-Array ohne passenden /Colorants-Eintrag erzeugte eine strukturell nicht konforme Datei, und eine erdachte Standarddefinition wäre schlimmer als ein Scheitern

PDF/X-6n-Output-Intents müssen jeden registrierten Spot abdecken

Eine N-Farbstoff-PDF/X-6n-Datei deklariert ihre Farbstoffe zweimal, und beide Deklarationen müssen übereinstimmen. AddPDFX6ExternalOutputIntent schreibt die externe ICC-Profil-Referenz samt ihrer ColorantTable, und bevor es das tut, geht ValidateRegisteredSpotOutputColorants jeden vom Dokument registrierten Spot durch und wirft, wenn einer in der Tabelle fehlt. Die Prüfung läuft in beide Richtungen: Hat ein Output Intent seine Farbstoffliste einmal veröffentlicht, wird auch eine spätere Spot-Registrierung für einen Namen außerhalb der Liste abgewiesen. AddPDFX6ExternalOutputIntentSpotData setzt die Metadaten je Farbe obendrauf und erzwingt eigene Regeln, insbesondere, dass ein Farbstoff entweder einen Soliditätswert oder CxF/X-4-Spektraldaten trägt, nie beides. Das ist dieselbe Konformitätsfläche, die im Artikel zur Validierung von PDF/A, PDF/X und PDF/UA besprochen wird

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],                  // Solidität für einen Farbstoff
    Order,
    ['Orange'], [Spectral]);           // Spektraldaten für einen anderen
finally
  Spectral.Free;
end;

Zwei Betriebsdetails sind leicht zu übersehen. Die Registry, die all das trägt, gilt pro Dokument und wird an Dokumentgrenzen geleert, sodass das Laden einer neuen Datei in dieselbe THotPDF-Instanz die Spot-Identitäten des vorherigen Dokuments nicht erbt; diese Isolation ist der Punkt, denn eine durchgeleckte Signatur würde im nächsten Job völlig korrekte Arbeit zurückweisen. Und die Profilseite hat harte Grenzen eigener Art: Ein externer Output Intent verlangt ein PDF-2.0-Ziel, eine absolute HTTP- oder HTTPS-Profil-URL, eine vier Byte lange ICC-Farbraumsignatur und für PDF/X-6n zwischen 2 und 15 Farbstoffe mit passender 2CLR- bis FCLR-Signatur

Wo die Prüfung endet

Die CxF/X-4-Behandlung ist der Teil, bei dem Ehrlichkeit sich lohnt. HotPDF wendet begrenzte strukturelle Sicherheitsprüfungen auf den Spektralstream an und bestätigt, dass die Farb-Identität darin zum von Ihnen genannten Farbstoff passt. Es begrenzt die Payload-Größe, weist eingebettete Null-Bytes zurück, verweigert jeden Stream mit einer DOCTYPE- oder ENTITY-Deklaration, verlangt eine erkennbare CxF-Wurzel und verlangt genau ein SpotInkCharacterisation-Element, das genau einen SpotInkName trägt, der Ihrem Farbstoffnamen entspricht. Das ist ein Tor gegen fehlerhafte und feindliche Eingaben, kein Schema-Validator. Es ist keine vollständige ISO-17972-4-Implementierung, es verifiziert Ihre Spektralmessungen nicht, und es auditiert weder beliebige bereits existierende Drittanbieter-Objektgraphen rückwärts noch die internen Farbstofftabellen in einem eingebetteten ICC-Profil. Hängt Ihr Workflow an voller CxF-Konformität, validieren Sie die Datei mit einem dedizierten Werkzeug, bevor Sie sie der Komponente übergeben

Eine benachbarte Einschränkung beißt Leute, die nie damit gerechnet hatten. Eine Luminosity-Softmask kann Separation, DeviceN oder NChannel nicht als /CS ihrer Transparenzgruppe verwenden; der Gruppenblending-Farbraum muss ein Geräte- oder CIE-basierter Farbraum sein, also wird Spot-Farbe in der Gruppe über ihre Tint-Transformation in den Alternativfarbraum aufgelöst, bevor die Luminosität berechnet wird. RegisterLuminositySoftMaskState baut eine DeviceGray-Gruppe, gerade damit das nicht versehentlich schiefgehen kann. Die praktische Folge: Ein spotreiches Entwurfsdokument, das so maskiert ist, wird über seinen CMYK-Proxy bewertet, nicht über seine Farbe, was zählt, wenn Sie es gegen einen separierten Proof vergleichen, wie in den Notizen zu Overprint-Proofing und Render-Geräten beschrieben

Nichts davon ersetzt den Plattenproof, aber es verlagert eine ganze Klasse von Vorstufen-Rückweisungen aus der Druckhalle zurück in den Build. Die hier beschriebenen NChannel-, Separation- und PDF/X-6n-Output-Intent-APIs kommen in der Standard-HotPDF Delphi Component für Delphi und C++Builder, deren Referenz die vollständigen Record-Layouts und die genauen Bedingungen dokumentiert, unter denen jeder Aufruf scheitert