Artigo Técnico

Verificação de cor PDF/X-1a: apanhar conteúdo RGB em Delphi

Quando o PDFXCompliance está em 'X-1a', o HotPDF valida a cor de conteúdo no fim absoluto do trabalho: o EndDoc chama THotPDF.EnsurePDFX1aContentColorSpaces, que rejeita qualquer conteúdo DeviceRGB, CalRGB ou ICCBased RGB em qualquer página e levanta uma exceção que nomeia o recurso em causa. O ficheiro nunca chega a ser escrito como documento PDF/X-1a que a gráfica devolva

Essa última parte é que importa. Um ficheiro PDF/X-1a com um logo RGB perdido parece perfeitamente bem no ecrã, passa uma revisão descuidada, e depois falha o preflight na gráfica dois dias antes da janela de impressão. Apanhá-lo na sua build Delphi, com uma mensagem que diz que página e que imagem, é muito mais barato do que descobrir por um operador de pré-impressão chateado

Porque é que o PDF/X-1a rejeita conteúdo RGB?

O PDF/X-1a rejeita RGB porque o formato promete que toda a cor já está final para uma condição de impressão CMYK específica. O ISO 15930-1:2001 §6.2.3 limita a cor de conteúdo a DeviceCMYK, DeviceGray e colorantes spot, ou seja espaços Separation e DeviceN cujos alternates são CMYK ou Gray. O OutputIntent GTS_PDFX embutido no ficheiro nomeia essa condição de impressão, por exemplo FOGRA39 offset couché, e o fluxo recetor tem o direito de mandar os números diretos às chapas sem conversão de cor nenhuma

O RGB quebra esse contrato. Um valor RGB descreve uma cor relativa a um ecrã ou a um espaço de trabalho, não uma receita de tinta, por isso alguém a jusante teria de decidir como a separar, e o X-1a existe precisamente para tirar essa decisão ao recetor. O ISO 32000-1 §8.6 agrupa os espaços de cor PDF nas famílias device, CIE-based e special, e os membros RGB aparecem em mais de uma família: DeviceRGB é um espaço device, CalRGB e ICCBased de três componentes são CIE-based, e um espaço Indexed ou Separation pode esconder uma base ou alternate RGB por baixo. Um verificador que só procure o operador rg perde a maioria. A edição posterior PDF/X-1a:2003 (ISO 15930-4) mantém a mesma regra de cor; note que o valor 'X-1a' do HotPDF escreve a identificação PDF/X-1a:2001 no pacote XMP

Se o seu conteúdo é mesmo RGB e deve ficar assim, o X-1a é o alvo errado. O HotPDF limita esta verificação ao 'X-1a' apenas, por isso o 'X-3' e o 'X-4', que permitem cor ICC-based e calibrada, não são afetados

O que o EnsurePDFX1aContentColorSpaces verifica de facto

O EnsurePDFX1aContentColorSpaces combina uma flag de tempo de desenho com uma passagem recursiva pelos recursos de cada página, e corre em último lugar no portão PDF/X. O EndDoc primeiro impõe os requisitos PDF/X gerais: o Title tem de estar definido, o Trapped tem de ser 'True', 'False' ou 'Unknown', um OutputIntent GTS_PDFX tem de existir (EnsurePDFXOutputIntent), e cada página precisa de um TrimBox ou ArtBox (EnsurePDFXPageBoxes). Só depois de isso passar é que a verificação de corre corre

Sequência do portão PDF/X dentro do EndDoc do HotPDF para Delphi: metadados, OutputIntent e caixas de página primeiro, depois a verificação de cor de conteúdo RGB
a verificação de corre corre em último no portão X-1a, e uma flag de tempo de desenho significa que RGB desenhado antes de a conformidade ser atribuída continua a ser apanhado

O primeiro caminho é uma flag ao nível do documento. Os setters de cor RGB do HotPDF, o embedding de imagens bitmap e as entradas de cor de anotações registam o uso de DeviceRGB enquanto desenha, o que cobre operadores rg e RG inline que nunca aparecem num dicionário de recursos. O segundo caminho percorre o /Resources de cada página e inspeciona:

  • Espaços de cor nomeados em /ColorSpace, incluindo a abreviatura de imagem inline RGB como sinónimo de DeviceRGB
  • O /ColorSpace de cada Image XObject
  • Form XObjects e Patterns de mosaico, por recursão nos seus próprios /Resources aninhados
  • Shading Patterns e entradas no dicionário /Shading da página, via o /ColorSpace do shading
  • Espaços base Indexed, alternates Separation e DeviceN, e espaços base de Patterns, seguidos até uma profundidade de aninhamento de 8

Três coisas são rejeitadas: DeviceRGB, CalRGB, e espaços ICCBased cujo stream de perfil declara N = 3. Dicionários de recursos partilhados entre Form XObjects e patterns são visitados uma vez, por isso um conjunto de forms mutuamente referenciados não mete a passagem num loop. O método é público no THotPDF, mas em uso normal nunca o chama, porque o EndDoc fá-lo por si

Onde o RGB se esconde nos recursos de página PDF: forms aninhados, patterns, shadings e alternates levam a passagem do HotPDF em Delphi a DeviceRGB, CalRGB ou ICCBased RGB
a passagem segue recursos aninhados até profundidade 8 e rejeita espaços DeviceRGB, CalRGB e ICCBased com N = 3 onde quer que apareçam

Desenhar uma página X-1a em Delphi que passe a verificação

Um documento X-1a HotPDF conforme precisa de um OutputIntent CMYK, de um TrimBox em cada página, e de cor definida só pelas APIs CMYK, Gray e spot. O esqueleto abaixo usa o perfil FOGRA39; o AddPDFXOutputIntent recebe o identificador da condição de saída, uma string de informação, o stream do perfil ICC, a sua contagem de componentes, e o espaço alternate

var
  Pdf: THotPDF;
  Profile: TFileStream;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := 'brochure-x1a.pdf';
    Pdf.Title := 'Spring brochure, press edition';
    Pdf.Trapped := 'False';
    Pdf.PDFXCompliance := 'X-1a';
    Pdf.BeginDoc;

    Profile := TFileStream.Create('CoatedFOGRA39.icc', fmOpenRead or fmShareDenyWrite);
    try
      Pdf.AddPDFXOutputIntent('FOGRA39 (ISO 12647-2:2004)',
        'Coated FOGRA39 offset', Profile, 4, 'DeviceCMYK');
    finally
      Profile.Free;
    end;
    Pdf.CurrentPage.SetTrimBox(0, 0, 612, 792);   // exigido em cada página

    Pdf.CurrentPage.SetCMYKFillColor(0, 0.5, 0.8, 0.1);
    Pdf.CurrentPage.Rectangle(72, 600, 468, 120);
    Pdf.CurrentPage.Fill;

    Pdf.CurrentPage.SetGrayFillColor(0.2);         // texto DeviceGray
    Pdf.CurrentPage.SetFont('Arial', [], 14);
    Pdf.CurrentPage.TextOut(72, 560, 0, 'Spring catalog, press edition');
    Pdf.EndDoc;                                    // a verificação de cor corre aqui
  finally
    Pdf.Free;
  end;
end;

Toda a chamada baseada em TColor é uma armadilha neste contexto. O SetRGBFillColor, o SetRGBStrokeColor e o SetRGBColor recebem todos uma cor GDI e emitem DeviceRGB, por isso uma rotina auxiliar escrita há anos para relatórios virados para ecrã contaminará um trabalho de impressão no momento em que for reutilizada. Use SetCMYKFillColor, SetCMYKStrokeColor ou SetCMYKColor com componentes no intervalo de 0 a 1, ou os equivalentes Gray

Cores spot e gradientes CMYK

Colorantes spot são permitidas no X-1a desde que o seu alternate seja CMYK ou Gray. O RegisterSeparation constrói o espaço Separation e devolve o nome do recurso, e o RegisterAxialGradient escolhe o espaço de cor do shading pela contagem de componentes que passar, com quatro componentes a significar DeviceCMYK

var
  SpotCS, Ramp: AnsiString;
begin
  // Tinta spot com uma construção CMYK aproximada como alternate
  SpotCS := Pdf.RegisterSeparation('Corporate Red', 'DeviceCMYK',
    [0, 1, 0.8, 0.05]);
  Pdf.CurrentPage.SetFillColorSpace(SpotCS);
  Pdf.CurrentPage.SetFillColor([1.0]);           // tint 100%
  Pdf.CurrentPage.Rectangle(72, 420, 200, 80);
  Pdf.CurrentPage.Fill;

  // 4 componentes por extremo: o shading é DeviceCMYK, não DeviceRGB
  Ramp := Pdf.RegisterAxialGradient(72, 300, 540, 300,
    [0, 0, 0, 0], [1, 0.6, 0, 0.2]);
  Pdf.CurrentPage.SetFillPattern(Ramp);
  Pdf.CurrentPage.Rectangle(72, 260, 468, 80);
  Pdf.CurrentPage.Fill;
end;

Passe três componentes à mesma chamada de gradiente e obtém um shading DeviceRGB, que a passagem de recursos rejeita sob o nome do shading pattern. O mesmo vale para uma Separation registada com 'DeviceRGB' como alternate: o nome spot parece inofensivo, mas a passagem segue o alternate e encontra RGB por baixo. Como tais tintas spot são compostas quando um viewer ou RIP recai no alternate está coberto no artigo sobre renderizar cores spot Separation e DeviceN em Delphi

Imagens raster precisam da mesma disciplina. O HotPDF fixa bitmaps comprimidos com Flate em DeviceRGB, enquanto um JPEG embutido com icJpeg mantém a sua própria contagem de componentes, por isso um JPEG CMYK de quatro componentes ou um JPEG em tons de cinza está bem e um JPEG RGB comum não está. Converta a arte para o perfil da impressão no seu pipeline de imagem antes de chegar ao PDF

Porque é que o /DefaultRGB não salva um ficheiro PDF/X-1a?

O /DefaultRGB não ajuda o X-1a porque a regra é sobre o que a impressão recebe, não sobre se o RGB é caracterizado. Para PDF/A, o HotPDF deixa o RegisterDefaultColorSpace('DefaultRGB', ...) absorver o uso de DeviceRGB: uma vez que um espaço de cor Default mapeia DeviceRGB para um perfil ICC, o conteúdo é independente de dispositivo e a verificação de consistência PDF/A aceita-o. Developers que passaram por esse fluxo tendem a tentar a mesma correção aqui

O EnsurePDFX1aContentColorSpaces ignora deliberadamente esse mapeamento. Um valor RGB caracterizado continua a ser RGB, e o OutputIntent X-1a é uma condição de impressão CMYK, por isso alguém teria de converter de qualquer forma. Se se lembrar de uma diferença entre os dois perfis de arquivo e impressão, seja esta: o PDF/A pergunta se uma cor é inequívoca, o PDF/X-1a pergunta se já é tinta

Ordem de desenho, e o page setup que não conta

Como a verificação corre no EndDoc sobre conteúdo já escrito, a ordem em que configura as coisas não importa. RGB desenhado antes de o PDFXCompliance ser atribuído, ou antes de o OutputIntent ser registado, continua a ser apanhado. Isso importa para código que constrói páginas em rotinas partilhadas e decide o perfil de saída tarde, às vezes a partir de uma definição de utilizador escolhida depois de metade do documento existir

Pdf.BeginDoc;
// ... OutputIntent e TrimBox como antes ...
Pdf.CurrentPage.SetRGBFillColor(clRed);   // helper legado, emite DeviceRGB
Pdf.CurrentPage.Rectangle(72, 72, 200, 100);
Pdf.CurrentPage.Fill;

Pdf.PDFXCompliance := 'X-1a';             // decidido depois de desenhar
Pdf.EndDoc;                               // continua a levantar: DeviceRGB registado

Uma flag de tempo de desenho tem uma armadilha que vale a pena conhecer se construir uma verificação semelhante. Cada página nova é inicializada com um estado gráfico preto através do SetRGBColor(0), que emite um par 0 0 0 rg e 0 0 0 RG, e se esse boilerplate de configuração de página pudesse pôr a flag DeviceRGB, todos os documentos pareceriam contaminados por RGB, tanto para esta verificação como para a verificação de consistência de cor PDF/A. O HotPDF salva e restaura a flag em volta dessa inicialização, por isso só a cor que realmente desenha conta

Como encontrar o conteúdo RGB quando o EndDoc levanta?

Leia a localização no início da mensagem de exceção, porque diz qual dos dois caminhos encontrou o problema. A mensagem tem a forma PDFXCompliance 'X-1a': <where> uses <what>, seguida da referência ISO 15930-1 e do conselho de converter para CMYK ou Gray ou mudar para X-3 ou X-4. O validador levanta uma Exception simples na primeira violação, por isso faça match no texto em vez de numa classe de exceção

Anatomia da exceção RGB do EndDoc do HotPDF em Delphi: a flag de tempo de desenho reporta uso ao nível do documento enquanto a passagem de recursos nomeia a página e a chave exatas
leia a localização no início da mensagem — um reporte ao nível do documento manda-o para o código de desenho, um nome de página e recurso aponta para uma chave
  • document content uses DeviceRGB (rg/RG operators, RGB images or annotation colours recorded during drawing) significa que a flag de tempo de desenho disparou. Não há número de página, porque a flag é ao nível do documento e é verificada antes da passagem. Procure no seu código de desenho chamadas SetRGB*, bitmaps comprimidos com Flate, JPEGs RGB e cores de anotações TColor
  • page 3 image XObject /Im2 uses DeviceRGB content ou page 1 shading Pattern /P1 uses DeviceRGB content vem da passagem de recursos e aponta para uma chave de recurso exata
  • uses an ICCBased RGB colour space (N=3) após uma localização ColorSpace /... é o resultado típico de registar um perfil sRGB com RegisterICCProfile e selecioná-lo através do SetFillColorSpace, um caminho que a flag de tempo de desenho não vê
  • Localizações aninhadas encadeiam-se, por exemplo page 2 Form XObject /Fm1 ColorSpace /CS1, por isso pode seguir o caminho da página até ao recurso que traz o espaço RGB
try
  Pdf.EndDoc;
except
  on E: Exception do
  begin
    if Pos('X-1a', E.Message) > 0 then
      LogPrepressFailure(Pdf.FileName, E.Message);  // o seu próprio logger
    raise;
  end;
end;

Como só a primeira violação é reportada, corrigir um trabalho com várias fontes de RGB é iterativo: corrigir, recompilar, ler a mensagem seguinte. Para trabalhos em batch compensa registrar a mensagem completa por ficheiro e manter uma lista curta de infratores conhecidos, como um bitmap de logo ou um helper de gráficos que ainda recebe TColor

Onde o portão RGB cabe no seu preflight

O EnsurePDFX1aContentColorSpaces é um portão RGB direcionado que corre dentro da sua build, e emparelha com um preflight PDF/X-1a completo em vez de o substituir. Cobre as famílias RGB, através da passagem de recursos do /Resources de cada página e da flag de tempo de desenho que os setters de cor do HotPDF mantêm para operadores rg e RG inline; espaços de cor aninhados são seguidos até profundidade 8 como guarda contra ciclos malformados, e a primeira violação é reportada por EndDoc. Outras famílias de cor como Lab ou CalGray são assunto do preflight completo, por isso passe o ficheiro final por um validador externo como parte da sua build para obter o veredicto de conformidade completo. O quadro mais alargado da saída de conformidade está em o guia de saída PDF/A, PDF/X e PDF/UA com o HotPDF, e ligar um validador a um pipeline não vigiado está coberto em automatizar verificações de preflight PDF em Delphi

O HotPDF é um componente PDF VCL nativo para Delphi e C++Builder com saída PDF/X, PDF/A e PDF/UA, APIs de cor CMYK e spot, e suporte a OutputIntent ICC. Detalhes, licenciamento e download de trial estão na página do produto HotPDF Delphi PDF component