Uma casa de pré-impressão devolve o trabalho: a mesma tinta direta separada em duas chapas. O HotPDF evita-o ao tempo de criação escrevendo NChannel como a forma DeviceN de cinco elementos da ISO 32000-2 e mantendo um espaço alternativo canónico e uma transformação de tint por nome de tinta direta para todo o documento, rejeitando a segunda definição em conflito em vez de a emitir
NChannel não é um nome de família de espaços de cor
A primeira coisa a desaprender é o próprio nome. NChannel não é uma família de espaços de cor como Separation e DeviceN são famílias. A ISO 32000-2 §8.6.6.5 descreve-o como um subtipo de DeviceN, pelo que um espaço NChannel conforme se escreve como a matriz de cinco elementos [/DeviceN names alternateSpace tintTransform attributes], e o dicionário de atributos traz /Subtype /NChannel. Não existe nenhuma matriz [/NChannel ...] na especificação. Se alguma vez construiu uma à mão e viu um RIP encolher os ombros, é por isto
O HotPDF errou isto uma vez e depois corrigiu, o que vale a pena dizer sem rodeios porque molda o comportamento do componente hoje. Versões antigas do HotPDF emitiam a forma de nome de família. O THotPDF.RegisterNChannelColorSpace emite agora apenas a forma padrão DeviceN-com-atributos, e como o subtipo NChannel chegou no PDF 1.6, o ponto de entrada do produtor faz gate em RequirePDFVersion(pdf16, ...) e simplesmente declina em alvos mais antigos. O lado da renderização é deliberadamente mais tolerante do que o escritor: o HPDFResolveColorSpace ainda aceita o token antigo /NChannel como família DeviceN, para que ficheiros do escritor antigo continuem a renderizar, mas tudo o que o HotPDF escreve de novo usa a codificação padrão. Tolerante à entrada, rigoroso à saída é a assimetria certa aqui, porque o seu leitor tem de lidar com ficheiros que não criou, enquanto o seu escritor não tem essa desculpa
Porque é que um nome de tinta direta acaba em duas chapas?
Porque um nome de colorante de tinta direta é uma identidade de chapa ao nível do documento, 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 uma transformação de tint diferente, descrevem duas tintas diferentes que por acaso partilham um rótulo. Um RIP a construir separações não tem forma de reconciliar isso, por isso faz a única coisa honesta e entrega-lhe duas chapas. O HotPDF mantém por isso uma assinatura canónica por documento por nome de colorante. O RegisterSpotColorantDefinition compõe essa assinatura a partir do espaço de cor alternativo e da forma da função de tint, e cada 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 registar em silêncio uma segunda variante, e a mensagem é deliberadamente específica quanto ao modo de falha, porque a alternativa é descobri-lo numa prova de chapa três semanas depois
// Orange já estava registado contra DeviceCMYK com a
// transformação de tint 0 / 0.55 / 1 / 0 no tint máximo.
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;
Dividir os nomes mestres entre process e spot
Um NChannel completo tem de dar conta de cada um dos seus nomes de colorante mestres exatamente uma vez, como componente de process ou como colorante de spot. A overload avançada do RegisterNChannelColorSpace recebe os ColorantNames mestres, os ProcessColorantNames, o espaço alternativo, a transformação de tint global, uma matriz de registos THPDFNChannelSpotColorant, e uma ordem de impressão opcional. Cada registo de spot traz o seu próprio nome, a sua própria transformação de tint Separation de uma entrada, uma solidity opcional, e uma função de dot-gain opcional. A transformação de tint global tem de mapear N entradas para a contagem de componentes do espaço alternativo; cada tint de spot tem de 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 especificação pede: /Subtype /NChannel, um dicionário /Process cujo /ColorSpace é o espaço de process e cujo /Components lista os nomes de process pela ordem de componentes desse espaço, um dicionário /Colorants que guarda uma matriz [/Separation name alternate tintfn] real para cada spot, e um dicionário /MixingHints que traz /Solidities, /PrintingOrder e /DotGain quando os forneceu. O nome de recurso devolvido vai para SetFillColorSpace ou SetStrokeColorSpace exatamente como os espaços mais simples cobertos no artigo sobre renderização de cores diretas Separation e DeviceN
O que é que a verificação de consistência realmente rejeita?
Rejeita incoerência estrutural dentro do espaço, e fá-lo antes de um único objeto ser escrito. Os nomes de colorante têm de ser únicos e não podem ser vazios, All ou None. As definições de process e de spot em conjunto têm de cobrir os nomes mestres exatamente, sem colorante a aparecer nos dois papéis e sem nenhum ficar por definir. Quando o espaço alternativo é DeviceCMYK, os componentes de process têm de ser Cyan, Magenta, Yellow, Black por essa ordem, e a contagem de nomes de process tem de coincidir com a contagem de componentes do alternativo. Cada transformação de tint e função de dot-gain tem de ser um objeto de função indireto com a aridade de entrada e saída certa. A solidity tem de ser um valor finito dentro de 0..1. A ordem de impressão tem de ser vazia ou uma permutação completa dos nomes mestres, nunca uma lista parcial. O que não faz é julgar cor: nada aqui verifica que a sua transformação de tint de Orange realmente se parece com a tinta do latão, que a sua composição CMYK é um proxy sensato, ou que a solidity que forneceu corresponde ao comportamento medido no substrato. Essas são questões de impressão e de medição, e o componente não tem autoridade para as responder. A overload mais simples, só de process, é ainda mais rigorosa por desenho: produz um NChannel só de process e recusa deliberadamente aceitar nomes de spot, porque escrever um spot na matriz de nomes sem uma entrada /Colorants correspondente produziria um ficheiro estruturalmente não conforme, e fabricar uma definição predefinida seria pior do que falhar
Os output intents PDF/X-6n têm de cobrir todos os spots registados
Um ficheiro PDF/X-6n de N colorantes declara as suas colorantes duas vezes, e as duas declarações têm de coincidir. O AddPDFX6ExternalOutputIntent escreve a referência de perfil ICC externa com a sua ColorantTable, e antes de o fazer, o ValidateRegisteredSpotOutputColorants percorre todos os spots que o documento registou e levanta exceção se algum faltar na tabela. A verificação corre nos dois sentidos: depois de um output intent ter publicado a sua lista de colorantes, um registo de spot posterior para um nome fora dessa lista também é recusado. O AddPDFX6ExternalOutputIntentSpotData acrescenta os metadados por tinta por cima disso, e impõe as suas próprias regras, nomeadamente que um colorante traz ou um valor de solidity ou dados espectrais CxF/X-4, nunca ambos. Esta é 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 colorante
Order,
['Orange'], [Spectral]); // dados espectrais para outro
finally
Spectral.Free;
end;
Dois detalhes operacionais são fáceis de perder. O registo que suporta tudo isto é por documento e é limpo nas fronteiras de documento, pelo que carregar um ficheiro novo na mesma instância de THotPDF não herda as identidades de spot do documento anterior; esse isolamento é o ponto, porque uma assinatura que vazasse rejeitaria trabalho perfeitamente válido no trabalho seguinte. E o lado do perfil tem limites duros próprios: um output intent externo exige um alvo PDF 2.0, um URL de perfil absoluto HTTP ou HTTPS, uma assinatura de espaço de cor ICC de quatro bytes, e para PDF/X-6n entre 2 e 15 colorantes com uma assinatura correspondente de 2CLR a FCLR
Onde a verificação para
O tratamento de CxF/X-4 é a parte sobre a qual convém ser honesto. O HotPDF aplica verificações estruturais de segurança limitadas ao stream espectral e confirma que a identidade da tinta lá dentro corresponde ao colorante que nomeou. Limita o tamanho do payload, rejeita bytes nulos embebidos, recusa qualquer stream que contenha uma declaração DOCTYPE ou ENTITY, exige uma raiz CxF reconhecível, e exige exatamente um elemento SpotInkCharacterisation que transporte exatamente um SpotInkName igual ao nome do seu colorante. Isso é um gate contra entrada malformada e hostil, não um validador de schema. Não é uma implementação completa da ISO 17972-4, não verifica as suas medições espectrais, e não audita em retrocesso grafos de objetos de terceiros pré-existentes arbitrários nem as tabelas de colorantes internas de um perfil ICC embebido. Se o seu fluxo de trabalho depende de conformidade CxF completa, valide o ficheiro com uma ferramenta dedicada antes de o entregar 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 seu grupo de transparência; o espaço de mistura do grupo tem de ser um espaço de dispositivo ou baseado em CIE, pelo que a tinta direta dentro do grupo é resolvida através da sua transformação de tint para o espaço alternativo antes de a luminosidade ser calculada. O RegisterLuminositySoftMaskState constrói um grupo DeviceGray precisamente para que isto não possa correr mal por acidente. A consequência prática é que um design carregado de spots mascarado desta forma está a ser avaliado através do seu proxy CMYK, não da sua tinta, o que importa quando o compara com uma prova separada como descrito nas notas sobre provas de overprint e dispositivos de renderização
Nada disto remove a necessidade de uma prova de chapa, mas move toda uma classe de rejeições de pré-impressão da sala de impressão de volta para a build. As APIs de NChannel, Separation e output intent PDF/X-6n descritas aqui são distribuídas no HotPDF Delphi Component padrão para Delphi e C++Builder, onde a referência documenta os layouts completos dos registos e as condições exatas em que cada chamada falha de forma segura