Articolo tecnico

Coerenza dei colori spot NChannel in Delphi con HotPDF

Una tipografia prepress ti rimanda il lavoro: lo stesso inchiostro spot separato su due lastre. HotPDF lo previene in fase di scrittura scrivendo NChannel come forma DeviceN a cinque elementi di ISO 32000-2 e mantenendo un unico spazio alternativo canonico e una tint transform per nome spot per l'intero documento, respingendo la seconda definizione in conflitto invece di emetterla

NChannel non è un nome di famiglia di spazi colore

La prima cosa da disimparare è il nome stesso. NChannel non è una famiglia di spazi colore come lo sono Separation e DeviceN. ISO 32000-2 §8.6.6.5 lo descrive come un sottotipo di DeviceN, quindi uno spazio NChannel conforme si scrive come l'array a cinque elementi [/DeviceN names alternateSpace tintTransform attributes], e il dizionario attributes porta /Subtype /NChannel. Nella specifica non esiste alcun array [/NChannel ...]. Se ne hai mai costruito uno a mano e hai visto un RIP alzare le spalle, ecco perché

HotPDF scrive uno spazio NChannel come array DeviceN a cinque elementi il cui dizionario attributes porta Subtype NChannel insieme alle voci Process, Colorants e MixingHints, perché nella specifica non esiste affatto un array con nome famiglia NChannel
Uno spazio NChannel conforme è un array DeviceN con un dizionario attributes, ecco perché il writer emette solo questa forma mentre il reader accetta ancora il token legacy

HotPDF una volta si è sbagliato su questo e poi l'ha corretto, cosa che vale la pena dire chiaramente perché modella come il componente si comporta oggi. Le versioni più vecchie di HotPDF emettevano la forma col nome famiglia. THotPDF.RegisterNChannelColorSpace ora emette solo la forma standard DeviceN più attributes, e siccome il sottotipo NChannel è arrivato in PDF 1.6, il punto di ingresso del producer fa gate su RequirePDFVersion(pdf16, ...) e semplicemente declina su target più vecchi. Il lato rendering è deliberatamente più indulgente del writer: HPDFResolveColorSpace accetta ancora il token legacy /NChannel come famiglia DeviceN così i file del vecchio writer continuano a renderizzarsi, ma qualsiasi cosa HotPDF riscrive usa la codifica standard. Indulgente in input, severo in output è la giusta asimmetria qui, perché il tuo reader deve far fronte a file che non ha creato mentre il tuo writer non ha una simile scusa

Perché un nome spot finisce su due lastre?

Perché un nome di colorante spot è un'identità di lastra a livello di documento, non un argomento locale. Due chiamate che nominano entrambe Orange ma consegnano uno spazio alternativo diverso, o lo stesso spazio alternativo con una tint transform diversa, descrivono due inchiostri diversi che per caso condividono un'etichetta. Un RIP che costruisce separazioni non ha modo di riconciliare la cosa, quindi fa l'unica cosa onesta e ti dà due lastre. HotPDF mantiene quindi una firma canonica per nome di colorante a livello di documento. RegisterSpotColorantDefinition compone quella firma dallo spazio colore alternativo e dalla forma della funzione tint, e ogni RegisterSeparation, RegisterSeparationFunc, RegisterSeparationLUT e definizione spot NChannel passa attraverso di essa. Quando una seconda definizione è in disaccordo, la chiamata solleva un'eccezione invece di registrare silenziosamente una seconda variante, e il messaggio è deliberatamente specifico sulla modalità di fallimento, perché l'alternativa è scoprirlo su una prova di lastra tre settimane dopo

Ogni chiamata di registrazione spot di HotPDF passa per RegisterSpotColorantDefinition, che registra un unico spazio alternativo canonico e firma tint per nome di colorante per il documento e solleva un'eccezione quando arriva una seconda definizione in disaccordo dello stesso nome
Un nome, una firma: la seconda definizione in conflitto di uno spot viene rifiutata in fase di scrittura anziché scoperta su una prova di lastra
// Orange era già registrato contro DeviceCMYK con la
// tint transform 0 / 0.55 / 1 / 0 a tinta piena.
Conflicting := Pdf.RegisterExponentialFunction(
  Domain1, NoInk, OtherOrangeCMYK, 1, []);
try
  Pdf.RegisterSeparationFunc('Orange', 'DeviceCMYK', Conflicting);
except
  on E: Exception do
    // 'Spot colourant "Orange" ha uno spazio colore alternativo
    //  o una definizione tint incoerenti in questo documento'
    LogPrepressWarning(E.Message);
end;

Suddividere i nomi master in process e spot

Un NChannel completo deve dare conto di ognuno dei suoi nomi di colorante master esattamente una volta, come componente process o come colorante spot. L'overload avanzato di RegisterNChannelColorSpace prende i ColorantNames master, i ProcessColorantNames, lo spazio alternativo, la tint transform complessiva, un array di record THPDFNChannelSpotColorant, e un ordine di stampa opzionale. Ogni record spot porta il proprio nome, la propria tint transform Separation a un solo input, una solidity opzionale, e una funzione dot-gain opzionale. La tint transform complessiva deve mappare N input sul numero di componenti dello spazio alternativo; ogni tint spot deve mappare un input sullo stesso conteggio

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

Da quella chiamata HotPDF emette il dizionario attributes che la specifica chiede: /Subtype /NChannel, un dizionario /Process il cui /ColorSpace è lo spazio process e il cui /Components elenca i nomi process nell'ordine dei componenti di quello spazio, un dizionario /Colorants che contiene un vero array [/Separation name alternate tintfn] per ogni spot, e un dizionario /MixingHints che porta /Solidities, /PrintingOrder e /DotGain quando li hai forniti. Il nome della risorsa restituito va a SetFillColorSpace o SetStrokeColorSpace esattamente come gli spazi più semplici trattati nell'articolo sul rendering dei colori spot Separation e DeviceN

HotPDF suddivide i nomi di colorante master di uno spazio NChannel in un dizionario Process per i quattro componenti CMYK e un dizionario Colorants che contiene un array Separation per spot, con ogni arità controllata prima della scrittura
Ogni nome master viene rivendicato esattamente una volta, e la tint transform complessiva, le tinte per spot e l'ordine di stampa sono tutti validati prima che un oggetto qualunque venga emesso

Cosa respinge davvero il controllo di coerenza?

Respinge l'incoerenza strutturale dentro lo spazio, e lo fa prima che un singolo oggetto venga scritto. I nomi dei coloranti devono essere univoci e non devono essere vuoti, All o None. Le definizioni process e spot insieme devono coprire esattamente i nomi master, senza che un colorante compaia in entrambi i ruoli e senza che nessuno resti indefinito. Quando lo spazio alternativo è DeviceCMYK, i componenti process devono essere Cyan, Magenta, Yellow, Black in quest'ordine, e il conteggio dei nomi process deve corrispondere al conteggio dei componenti alternativi. Ogni tint transform e funzione dot-gain deve essere un oggetto funzione indiretto con la giusta arità di input e output. La solidity deve essere un valore finito dentro 0..1. L'ordine di stampa deve essere vuoto o una permutazione completa dei nomi master, mai un elenco parziale. Ciò che non fa è giudicare il colore: niente qui controlla che la tua tint transform Orange assomigli davvero all'inchiostro nel barattolo, che la sua composizione CMYK sia un proxy ragionevole, o che la solidity che hai fornito corrisponda al comportamento misurato sul supporto. Quelle sono domande di stampa e di misura, e il componente non ha competenza per rispondervi. L'overload più semplice solo process è più severo ancora per progetto: produce un NChannel solo process e rifiuta deliberatamente di accettare nomi spot, perché scrivere uno spot nell'array dei nomi senza una voce /Colorants corrispondente produrrebbe un file strutturalmente non conforme, e fabbricare una definizione predefinita sarebbe peggio del fallire

Gli output intent PDF/X-6n devono coprire ogni spot registrato

Un file PDF/X-6n a N coloranti dichiara i suoi coloranti due volte, e le due dichiarazioni devono essere d'accordo. AddPDFX6ExternalOutputIntent scrive il riferimento al profilo ICC esterno con il suo ColorantTable, e prima di farlo, ValidateRegisteredSpotOutputColorants percorre ogni spot che il documento ha registrato e solleva un'eccezione se uno manca dalla tabella. Il controllo gira in entrambe le direzioni: una volta che un output intent ha pubblicato il suo elenco di coloranti, anche una registrazione spot successiva per un nome fuori da quell'elenco viene rifiutata. AddPDFX6ExternalOutputIntentSpotData aggiunge i metadati per inchiostro sopra a questo, e applica le sue regole, in particolare che un colorante porta o un valore di solidity o dati spettrali CxF/X-4, mai entrambi. È la stessa superficie di conformità discussa nel pezzo sulla validazione PDF/A, PDF/X e PDF/UA

Spectral := TMemoryStream.Create;
try
  LoadCxFForInk('Orange', Spectral);   // payload ISO 17972-4
  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 per un colorante
    Order,
    ['Orange'], [Spectral]);           // dati spettrali per un altro
finally
  Spectral.Free;
end;

Due dettagli operativi sono facili da perdere. Il registro che sostiene tutto questo è per documento e viene azzerato ai confini del documento, quindi caricare un nuovo file nella stessa istanza THotPDF non eredita le identità spot del documento precedente; quell'isolamento è il punto, dato che una firma fuoriuscita respingerebbe lavoro perfettamente valido nel lavoro successivo. E il lato profilo ha limiti rigidi suoi: un output intent esterno richiede un target PDF 2.0, un URL profilo HTTP o HTTPS assoluto, una firma di spazio colore ICC a quattro byte, e per PDF/X-6n tra 2 e 15 coloranti con una firma corrispondente da 2CLR a FCLR

Dove il controllo si ferma

La gestione CxF/X-4 è la parte su cui essere onesti. HotPDF applica controlli di sicurezza strutturali limitati allo stream spettrale e conferma che l'identità dell'inchiostro dentro corrisponda al colorante che hai nominato. Mette un tetto alla dimensione del payload, respinge byte nulli incorporati, rifiuta qualsiasi stream contenente una dichiarazione DOCTYPE o ENTITY, richiede una radice CxF riconoscibile, e richiede esattamente un elemento SpotInkCharacterisation che porti esattamente un SpotInkName uguale al tuo nome di colorante. È un gate contro input malformato e ostile, non un validatore di schema. Non è un'implementazione completa di ISO 17972-4, non verifica le tue misure spettrali, e non fa audit inverso di grafi di oggetti terzi preesistenti arbitrari o delle tabelle di coloranti interne dentro un profilo ICC incorporato. Se il tuo flusso di lavoro dipende dalla piena conformità CxF, valida il file con uno strumento dedicato prima di consegnarlo al componente

Un vincolo adiacente morde chi non se lo aspettava mai. Una soft mask di luminosità non può usare Separation, DeviceN o NChannel come /CS del suo transparency group; lo spazio di blending del gruppo deve essere uno spazio device o basato su CIE, quindi la vernice spot dentro il gruppo si risolve attraverso la sua tint transform nello spazio alternativo prima che la luminosità sia calcolata. RegisterLuminositySoftMaskState costruisce un gruppo DeviceGray proprio perché questo non possa andare storto per incidente. La conseguenza pratica è che un design ricco di spot mascherato così viene valutato attraverso il suo proxy CMYK, non il suo inchiostro, cosa che conta quando lo confronti con una prova separata come descritto nelle note su prove di overprint e dispositivi di rendering

Niente di tutto ciò elimina il bisogno di una prova di lastra, ma sposta un'intera classe di respinti prepress dalla sala macchine al build. Le API NChannel, Separation e output intent PDF/X-6n descritte qui vengono spedite nella standard HotPDF Delphi Component per Delphi e C++Builder, dove il riferimento documenta i layout completi dei record e le condizioni esatte sotto cui ogni chiamata fallisce in modo sicuro