Uma gráfica de pré-impressão devolve o job: a mesma tinta spot separada em duas chapas. O HotPDF evita isso na hora da autoria escrevendo o NChannel como a forma DeviceN de cinco elementos do ISO 32000-2 e mantendo um espaço alternativo canônico e um tint transform por nome de spot para o documento inteiro, rejeitando a segunda definição conflitante em vez de emiti-la
NChannel não é um nome de família de espaço de cor
A primeira coisa a desaprender é o próprio nome. O NChannel não é uma família de espaços de cor como Separation e DeviceN são famílias. O ISO 32000-2 §8.6.6.5 o descreve como um subtipo de DeviceN, então um espaço NChannel conforme é escrito como o array de cinco elementos [/DeviceN names alternateSpace tintTransform attributes], e o dicionário de atributos carrega /Subtype /NChannel. Não existe um array [/NChannel ...] na especificação. Se você já montou um à mão e viu um RIP fazer que não viu, é por isso
O HotPDF já errou nisso uma vez e depois consertou, o que vale dizer com franqueza porque molda como o componente se comporta hoje. Versões antigas do HotPDF emitiam a forma de nome de família. O THotPDF.RegisterNChannelColorSpace agora emite apenas a forma padrão DeviceN-com-atributos, e como o subtipo NChannel chegou no PDF 1.6, o ponto de entrada do produtor filtra com RequirePDFVersion(pdf16, ...) e simplesmente recusa em alvos mais antigos. O lado de renderização é deliberadamente mais tolerante que o writer: o HPDFResolveColorSpace ainda aceita o token legado /NChannel como família DeviceN para que arquivos do writer antigo continuem renderizando, mas qualquer coisa que o HotPDF escreve de volta usa a codificação padrão. Tolerante na entrada, rigoroso na saída é a assimetria certa aqui, porque seu reader precisa lidar com arquivos que não criou, enquanto seu writer não tem essa desculpa
Por que um nome de spot acaba em duas chapas?
Porque um nome de colorant spot é uma identidade de chapa para o documento inteiro, não um argumento local. Duas chamadas que ambas nomeiam Orange mas entregam um espaço alternativo diferente, ou o mesmo espaço alternativo com um tint transform diferente, descrevem duas tintas diferentes que por acaso compartilham um rótulo. Um RIP construindo separações não tem como reconciliar isso, então faz a única coisa honesta e te dá duas chapas. O HotPDF por isso mantém uma assinatura canônica por nome de colorant para cada documento. O RegisterSpotColorantDefinition compõe essa assinatura a partir do espaço de cor alternativo e da forma da função tint, e todo RegisterSeparation, RegisterSeparationFunc, RegisterSeparationLUT e definição de spot NChannel passa por ele. Quando uma segunda definição discorda, a chamada levanta exceção em vez de registrar silenciosamente uma segunda variante, e a mensagem é deliberadamente específica sobre o modo de falha, porque a alternativa é descobrir isso numa prova de chapa três semanas depois
// Orange já estava registrado contra DeviceCMYK com o
// tint transform 0 / 0.55 / 1 / 0 em tint total.
Conflicting := Pdf.RegisterExponentialFunction(
Domain1, NoInk, OtherOrangeCMYK, 1, []);
try
Pdf.RegisterSeparationFunc('Orange', 'DeviceCMYK', Conflicting);
except
on E: Exception do
// 'Spot colourant "Orange" tem espaço alternativo de cor
// ou definição tint inconsistentes neste documento'
LogPrepressWarning(E.Message);
end;
Dividindo os nomes master em process e spot
Um NChannel completo precisa dar conta de cada um de seus nomes de colorant master exatamente uma vez, seja como componente process, seja como colorant spot. A sobrecarga avançada de RegisterNChannelColorSpace recebe os ColorantNames master, os ProcessColorantNames, o espaço alternativo, o tint transform geral, um array de records THPDFNChannelSpotColorant e uma ordem de impressão opcional. Cada record de spot carrega seu próprio nome, seu próprio tint transform Separation de entrada única, uma solidity opcional e uma função dot-gain opcional. O tint transform geral precisa mapear N entradas para a contagem de componentes do espaço alternativo; cada tint de spot precisa mapear uma entrada para essa mesma contagem
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);
Dessa chamada o HotPDF emite o dicionário de atributos que a spec pede: /Subtype /NChannel, um dicionário /Process cujo /ColorSpace é o espaço process e cujo /Components lista os nomes process na ordem de componentes desse espaço, um dicionário /Colorants guardando um array real [/Separation name alternate tintfn] para cada spot, e um dicionário /MixingHints carregando /Solidities, /PrintingOrder e /DotGain quando você os forneceu. O nome de recurso retornado vai para SetFillColorSpace ou SetStrokeColorSpace exatamente como os espaços mais simples cobertos no artigo sobre renderização de cores spot Separation e DeviceN
O que a checagem de consistência realmente rejeita?
Ela rejeita incoerência estrutural dentro do espaço, e o faz antes que um único objeto seja escrito. Nomes de colorant precisam ser únicos e não podem ser vazios, All ou None. Definições process e spot juntas precisam cobrir os nomes master exatamente, sem colorant aparecendo nos dois papéis e nenhum ficando indefinido. Quando o espaço alternativo é DeviceCMYK, os componentes process precisam ser Cyan, Magenta, Yellow, Black nessa ordem, e a contagem de nomes process precisa bater com a contagem de componentes do alternativo. Todo tint transform e função dot-gain precisa ser um objeto função indireto com a aridade certa de entrada e saída. Solidity precisa ser um valor finito dentro de 0..1. A ordem de impressão precisa ser vazia ou uma permutação completa dos nomes master, nunca uma lista parcial. O que ela não faz é julgar cor: nada aqui verifica se seu tint transform de Orange realmente lembra a tinta do lata, se sua composição CMYK é uma proxy razoável, ou se a solidity que você forneceu bate com o comportamento medido no substrato. Essas são perguntas de imprensa e medição, e o componente não tem competência para respondê-las. A sobrecarga mais simples, só process, é mais rigorosa ainda por design: produz um NChannel só de process e recusa deliberadamente aceitar nomes de spot, porque escrever um spot no array de nomes sem uma entrada /Colorants correspondente produziria um arquivo estruturalmente não conforme, e fabricar uma definição padrão seria pior que falhar
Output intents PDF/X-6n precisam cobrir todo spot registrado
Um arquivo PDF/X-6n de N colorants declara seus colorants duas vezes, e as duas declarações precisam concordar. O AddPDFX6ExternalOutputIntent escreve a referência de perfil ICC externo com sua ColorantTable, e antes de fazê-lo, o ValidateRegisteredSpotOutputColorants percorre todo spot que o documento registrou e levanta exceção se algum estiver faltando na tabela. A checagem roda nos dois sentidos: uma vez que um output intent publicou sua lista de colorants, um registro de spot posterior para um nome fora dessa lista também é recusado. O AddPDFX6ExternalOutputIntentSpotData adiciona os metadados por tinta por cima disso, e aplica suas próprias regras, notavelmente que um colorant carrega ou um valor de solidity ou dados espectrais CxF/X-4, nunca ambos. Essa é a mesma superfície de conformidade discutida no texto sobre validação 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 para um colorant
Order,
['Orange'], [Spectral]); // dados espectrais para outro
finally
Spectral.Free;
end;
Dois detalhes operacionais são fáceis de perder. O registro que sustenta tudo isso é por documento e é limpo nas fronteiras de documento, então carregar um arquivo novo na mesma instância de THotPDF não herda as identidades de spot do documento anterior; esse isolamento é o objetivo, já que uma assinatura vazada rejeitaria trabalho perfeitamente válido no próximo job. E o lado do perfil tem limites rígidos próprios: um output intent externo exige um alvo PDF 2.0, uma URL de perfil absoluta HTTP ou HTTPS, uma assinatura de espaço de cor ICC de quatro bytes e, para PDF/X-6n, entre 2 e 15 colorants com uma assinatura 2CLR a FCLR correspondente
Onde a checagem para
O tratamento de CxF/X-4 é a parte para ser honesto. O HotPDF aplica checagens estruturais de segurança limitadas ao stream espectral e confirma que a identidade da tinta dentro dele corresponde ao colorant que você nomeou. Ele limita o tamanho do payload, rejeita bytes null embutidos, recusa qualquer stream contendo uma declaração DOCTYPE ou ENTITY, exige uma raiz CxF reconhecível e exige exatamente um elemento SpotInkCharacterisation carregando exatamente um SpotInkName igual ao nome do seu colorant. Isso é um portão contra entrada malformada e hostil, não um validador de schema. Não é uma implementação completa do ISO 17972-4, não verifica suas medições espectrais e não audita reversamente grafos de objetos de terceiros pré-existentes arbitrários nem as tabelas internas de colorant dentro de um perfil ICC embutido. Se seu fluxo de trabalho depende de conformidade CxF completa, valide o arquivo com uma ferramenta dedicada antes de entregá-lo ao componente
Uma restrição adjacente morde quem nunca esperou encontrá-la. Uma soft mask de luminosidade não pode usar Separation, DeviceN ou NChannel como /CS do grupo de transparência; o espaço de blending do grupo precisa ser um espaço device ou baseado em CIE, então tinta spot dentro do grupo é resolvida por seu tint transform para o espaço alternativo antes da luminosidade ser computada. O RegisterLuminositySoftMaskState constrói um grupo DeviceGray precisamente para que isso não dê errado por acidente. A consequência prática é que um design rico em spot mascarado assim está sendo avaliado por seu proxy CMYK, não por sua tinta, o que importa quando você compara com uma prova separada como descrito nas notas sobre prova de overprint e dispositivos de renderização
Nada disso elimina a necessidade de uma prova de chapa, mas move uma classe inteira de rejeições de pré-impressão da sala de imprensa de volta para o build. As APIs de NChannel, Separation e output intent PDF/X-6n descritas aqui vêm no HotPDF Delphi Component padrão para Delphi e C++Builder, onde a referência documenta os layouts completos de records e as condições exatas sob as quais cada chamada falha fechando