O HotPDF renderiza as cores especiais Separation e DeviceN em páginas PDF carregadas resolvendo o espaço de cores através de HPDFResolveColorSpace, avaliando a função tint-transform com HPDFEvalTintTransform e convertendo o resultado através do espaço alternativo em RGB para visualização no ecrã. Desde a versão v2.375.0, esta cadeia de processamento suporta os quatro tipos de funções PDF, incluindo o avaliador de PostScript Type 4; isto permite que documentos preparados para impressão com tintas Pantone exibam as cores corretas em vez de cores de substituição. Este artigo descreve o funcionamento do fluxo de renderização e as falhas detetadas na sua implementação
O problema manifesta-se sempre da mesma forma: um ficheiro PDF gerado por uma gráfica contém títulos com uma cor especial (spot color) específica, elementos gráficos com uma mistura DeviceN de duas tintas e texto a preto simples. No Acrobat, a visualização apresenta-se correta; contudo, na sua aplicação Delphi o título é desenhado a preto, ou não é exibido de todo, o que motiva queixas de utilizadores apontando falhas no seu software. O ficheiro está correto: a falha reside simplesmente no facto de o renderizador não suportar os espaços de cores utilizados
Por que razão as cores especiais são renderizadas a preto?
As cores especiais são desenhadas a preto, ou ocultadas, se o renderizador implementar apenas os operadores de cor do dispositivo (rg, g, k) ignorando os genéricos. A norma ISO 32000-1 §8.6 define três grupos de espaços de cores: de dispositivo (DeviceGray, DeviceRGB, DeviceCMYK), baseados em CIE (CalGray, CalRGB, Lab, ICCBased) e especiais (Indexed, Separation, DeviceN, Pattern). Os espaços exteriores ao grupo do dispositivo são selecionados com os operadores genéricos: cs e CS definem o espaço pelo nome contido no dicionário de recursos da página; sc, SC, scn e SCN fornecem os valores dos componentes. Se o renderizador omitir estes operadores, a cor mantém a definição anterior, que no início da página corresponde ao preto padrão de DeviceGray
O HotPDF adicionou os operadores genéricos ao renderizador de página na versão v2.333.0, a par de um fluxo de resolução unificado: cada entrada do recurso /ColorSpace (seja nome, array ou referência indireta) é convertida num registo THPDFColorSpace, e as solicitações de cor de preenchimento ou traço são processadas por uma chamada a HPDFResolveColor. O enumerador indica a cobertura suportada:
type
THPDFColorSpaceFamily = (csfDeviceGray, csfDeviceRGB, csfDeviceCMYK,
csfIndexed, csfCalGray, csfCalRGB, csfLab,
csfICCBased, csfSeparation, csfDeviceN,
csfUnsupported);
function HPDFResolveColorSpace(Obj: THPDFObject): THPDFColorSpace;
function HPDFResolveColor(const CS: THPDFColorSpace;
const Comps: THPDFColorComps; CompCount: Integer): THPDFRenderColor;
Uma opção de desenho revelou-se acertada: tratar csfUnsupported como uma família válida e não como um erro de execução. Desta forma, se o renderizador não souber interpretar um determinado espaço de cores, reverte para um comportamento de recurso (fallback) em vez de interromper o desenho da página. Tal comportamento coincide com o dos visualizadores padrão e evita que um preenchimento complexo oculte as restantes informações do documento
Como as funções tint-transform convertem o valor de uma tinta em cor real?
O espaço de cores Separation contém três variáveis: o nome da tinta, o espaço de cores alternativo e a função tint-transform. O array [/Separation /PANTONE485 /DeviceCMYK f] determina que o valor 0.8 scn deve ser processado pela função f com o argumento 0.8, desenhando-se os quatro canais CMYK resultantes. O DeviceN expande este conceito para N substituindo por uma função de N argumentos de entrada. O nome da tinta tem apenas caráter informativo no ecrã; a função de transformação dita as semânticas do desenho, pelo que interpretar o espaço omitindo a função não produz resultados úteis
O método HPDFEvalTintTransform constitui o motor de cálculo desta conversão. Introduzido na versão v2.334.0, avalia funções exponenciais Type 2 (C0 + x^N * (C1 - C0) com limites de Domain e Range), funções de costura Type 3 (com sub-funções recursivas selecionadas pelos limites) e funções amostradas Type 0 com amostras de 8, 16 e 32 bits. O Type 0 baseia-se na tabela de mapeamento abordada no artigo sobre criação de LUTs de cor Type 0; no desenho, o percurso é efetuado por ordem inversa, traduzindo as amostras binárias em valores de componentes
As funções de calculadora PostScript Type 4 foram as últimas a ser implementadas. Até à versão v2.375.0, revertiam para cores neutras de recurso; a partir desta versão, o método HPDFEvalPostScriptCalculator executa o conjunto completo de operadores da norma ISO 32000-1 §7.10.5 numa pilha de operandos delimitada (operações aritméticas, lógicas, manipulação de pilhas com roll e condicionais if/ifelse). As semânticas são rigorosas: o operador round do PostScript arredonda frações de meio para o maior valor inteiro (pelo que o arredondamento simétrico do Delphi não pode ser utilizado); operadores trigonométricos funcionam em graus (o método atan devolve valores em [0, 360)); e exp representa a potência de dois operandos (e não o exponencial natural). Este avaliador também suporta graduações de cor baseadas em funções, o que viabilizou os perfis de cálculo no artigo sobre desenho de graduações axiais e radiais
// Avaliar a conversão tint-transform em Separation/DeviceN (Type 0/2/3/4)
function HPDFEvalTintTransform(FuncObj: THPDFObject;
const Inputs: THPDFColorComps; InputCount: Integer;
out AltComps: THPDFColorComps): Boolean;
// Calculadora PostScript Type 4, conjunto de operadores ISO 32000-1 7.10.5
function HPDFEvalPostScriptCalculator(const Prog: TBytes;
const Inputs: THPDFColorComps; InputCount: Integer;
var Outputs: THPDFColorComps; OutCount: Integer): Boolean;
A precisão decimal avaliada por software em precisão dupla (double) pode apresentar ligeiras discrepâncias de arredondamento em relação a um processador RIP ou a outras implementações de limites de Range. Tais desvios são insignificantes para visualização no ecrã e testes de regressão; contudo, se implementar fluxos de prova de cor (proofing), o processamento tint-transform representa apenas a primeira fase, exigindo um motor CMM completo a jusante
Mapeamento de CalGray, CalRGB, Lab e ICCBased sem motor ICC
Famílias baseadas em CIE seguem o segundo fluxo do motor. O HotPDF converte coordenadas Lab usando o mapeamento Lab para XYZ e sRGB (incluindo o limiar de 6/29 na função de transferência inversa), tratando o CalRGB com definições de gama por canal e matriz linear 3x3, e CalGray com gama única. Para otimização de velocidade, a matriz de transformação XYZ para sRGB é adaptada (via Bradford) ao ponto branco (white point) definido no espaço de cores e guardada em cache no registo THPDFColorSpace; isto reduz o processamento por píxel a uma única multiplicação 3x3, independentemente das propriedades do iluminante
Espaços ICCBased recebem uma abordagem pragmática. A especificação PDF exige que cada fluxo ICCBased defina a propriedade /Alternate ou deduza uma a partir do número de componentes /N, permitindo a visualizadores sem motor de cor apresentar a informação graficamente. O HotPDF processa ICCBased através do espaço alternativo, ou assume DeviceGray, DeviceRGB ou DeviceCMYK de acordo com o valor 1, 3 ou 4 de /N na ausência de definição, sem ler os bytes do perfil de cor. Isto elimina a dependência do motor lcms e o custo de processamento de perfis, sacrificando a precisão colorimétrica: se o perfil divergir substancialmente da definição alternativa, será exibida a cor alternativa. Trata-se da mesma simplificação adotada pela maioria dos visualizadores para exibição no ecrã e miniaturas
A falha que inviabilizava a pesquisa de espaços de cores por nome
A versão v2.333.0 continha uma falha que persistiu durante quarenta e duas versões: os manipuladores de cs e CS pesquisavam o operando preservando a barra inicial (/CS0) no dicionário de recursos cujas chaves eram gravadas sem a barra (CS0). Por conseguinte, todas as pesquisas falhavam e a cor revertia de forma silenciosa para o preto de DeviceGray. O sintoma visual era difícil de detetar: uma cor Separation com o valor 1 scn convertia-se para o valor 1.0 de DeviceGray, desenhando a branco (o que, numa página branca, passava despercebido). A correção, introduzida na versão v2.375.0, aplicou um utilitário de normalização de nomes a todas as pesquisas de recursos
Duas falhas adicionais foram corrigidas: em primeiro lugar, referências indiretas a objetos do tipo array eram devolvidas sem resolução: o renderizador possuía métodos específicos para fluxos e dicionários, pelo que /CS0 5 0 R apontando para um array [/Separation ...] isolado retornava a referência binária interpretando o espaço como não suportado. Em segundo lugar, o método HPDFReadNumericArray exigia correspondência exata de tamanho para arrays PDF em relação ao buffer. A leitura das propriedades /C0 e /C1 de funções Type 2 para buffers de quatro canais falhava em espaços alternativos de um ou três canais, mantendo os arrays a zero e forçando a renderização a preto de tonalidades não-CMYK, comportamento corrigido na versão v2.376.0 com a introdução do método tolerante HPDFReadNumericArrayUpTo. Lição: chaves PDF que contenham arrays de tamanho variável devem ser lidas com utilitários flexíveis sob pena de silenciar valores válidos
Como testar a renderização de cores especiais com precisão?
A falha na suite de testes foi analisada: o teste original SeparationRendersDistinguishable validava apenas se a imagem resultante não era totalmente preta. Como os desvios desenhavam elementos a cinzento ou branco, a imagem continha píxeis e o teste passava com a funcionalidade inoperacional. Testes vagos como "não vazio" ou "não totalmente preto" falham na deteção de erros de renderização, dado que a maioria das falhas continua a gerar píxeis na imagem
A validação recomendada deve focar a cor esperada: desenhar um PDF de teste simplificado onde o canal Separation resolva numa cor conhecida e contar os píxeis com essa tonalidade. A renderização em bitmap para análise utiliza o método RenderLoadedPageToBitmap detalhado no guia de renderização de página para bitmap:
var
Pdf: THotPDF;
Bmp: TBitmap;
X, Y, RedHits: Integer;
Px: TColor;
begin
Pdf := THotPDF.Create(nil);
try
if Pdf.LoadFromFile('spot-red-fixture.pdf', '') > 0 then
begin
Bmp := Pdf.RenderLoadedPageToBitmap(0, 96);
try
RedHits := 0;
for Y := 0 to Bmp.Height - 1 do
for X := 0 to Bmp.Width - 1 do
begin
Px := Bmp.Canvas.Pixels[X, Y];
if (GetRValue(Px) > 180) and (GetGValue(Px) < 100) and
(GetBValue(Px) < 100) then
Inc(RedHits);
end;
// Validar a cor esperada: exige uma área com píxeis predominantemente vermelhos
// sem aceitar a mera verificação "não totalmente preto"
Assert(RedHits > 500);
finally
Bmp.Free;
end;
end;
finally
Pdf.Free;
end;
end;
O ficheiro de teste (fixture) é tão relevante quanto a validação. Um PDF simplificado com apenas um preenchimento Separation garante que a cor obtida é inequívoca; ficheiros reais correm mais linhas de código mas dificultam identificar a origem de falhas. A contagem de píxeis da cor esperada passou a constituir o teste mínimo para qualquer renderização, por ser o único método eficaz na deteção destas falhas
O desenho de cores especiais e espaços CIE integra as rotinas do HotPDF Component para Delphi e C++Builder, a par do motor de funções, graduações de cor e exportação para bitmaps