Artigo Técnico

PDF/X-1a no HotPDF: pegando conteúdo RGB no Delphi

Quando PDFXCompliance é configurado como 'X-1a', o HotPDF valida a cor do conteúdo bem no fim do trabalho: o EndDoc chama THotPDF.EnsurePDFX1aContentColorSpaces, que rejeita qualquer conteúdo DeviceRGB, CalRGB ou ICCBased RGB em qualquer lugar de qualquer página e levanta uma exceção que nomeia o recurso infrator. O arquivo nunca é gravado como um documento PDF/X-1a que a gráfica vai devolver

Essa última parte é o ponto. Um arquivo PDF/X-1a com um logo RGB perdido parece perfeitamente bem na tela, passa numa revisão casual e então falha no preflight da gráfica dois dias antes da janela de impressão. Pegar isso no seu build Delphi, com uma mensagem que diz qual página e qual imagem, é muito mais barato que descobrir por um operador de pré-impressão irritado

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

O PDF/X-1a rejeita RGB porque o formato promete que toda cor já está final para uma condição de impressão CMYK específica. A ISO 15930-1:2001 §6.2.3 limita a cor do 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 arquivo nomeia essa condição de impressão, por exemplo FOGRA39 coated offset, e o fluxo receptor está no direito de mandar os números direto para as chapas sem nenhuma conversão de cor

RGB quebra esse contrato. Um valor RGB descreve uma cor relativa a um display ou a um working space, não uma receita de tinta, então alguém a jusante teria de decidir como separá-la, e o X-1a existe justamente para tirar essa decisão do receptor. A ISO 32000-1 §8.6 agrupa os color spaces do PDF em famílias device, CIE-based e especiais, e os membros RGB aparecem em mais de uma família: DeviceRGB é um espaço device, CalRGB e ICCBased com três componentes são CIE-based, e um espaço Indexed ou Separation pode esconder uma base ou alternate RGB por baixo. Um checker que só procura o operador rg perde a maioria deles. 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 grava a identificação PDF/X-1a:2001 no pacote XMP

Se o seu conteúdo realmente é RGB e deve continuar assim, o X-1a é o alvo errado. O HotPDF limita essa checagem ao 'X-1a' apenas, então 'X-3' e 'X-4', que permitem cor baseada em ICC e calibrada, não são afetados

O que o EnsurePDFX1aContentColorSpaces realmente confere

O EnsurePDFX1aContentColorSpaces combina uma flag de hora do desenho com uma varredura recursiva dos recursos de cada página, e roda por último no gate PDF/X. O EndDoc primeiro impõe os requisitos gerais do PDF/X: Title precisa estar definido, Trapped precisa ser 'True', 'False' ou 'Unknown', um OutputIntent GTS_PDFX precisa existir (EnsurePDFXOutputIntent) e toda página precisa de um TrimBox ou ArtBox (EnsurePDFXPageBoxes). Só depois disso a checagem de cores roda

Sequência do gate PDF/X dentro do EndDoc do HotPDF para Delphi: metadados, OutputIntent e page boxes primeiro, depois a checagem de cor de conteúdo RGB
a checagem de cor roda por último no gate X-1a, e uma flag de hora do desenho significa que RGB desenhado antes de a conformidade ser atribuída ainda é pegado

O primeiro caminho é uma flag de documento inteiro. Os setters de cor RGB do HotPDF, o embedding de imagens bitmap e as entradas de cor de anotações registram uso de DeviceRGB enquanto você 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:

  • Color spaces nomeados no /ColorSpace, incluindo a abreviação de imagem inline RGB como sinônimo de DeviceRGB
  • O /ColorSpace de todo Image XObject
  • Form XObjects e tiling Patterns, recursando nos próprios /Resources aninhados
  • Shading Patterns e entradas no dicionário /Shading da página, pelo /ColorSpace do shading
  • Espaços base Indexed, alternates Separation e DeviceN, e espaços base de Pattern, seguidos até uma profundidade de aninhamento de 8

Três coisas são rejeitadas: DeviceRGB, CalRGB e espaços ICCBased cujo profile stream declara N = 3. Dicionários de recursos compartilhados entre Form XObjects e patterns são visitados uma vez, então um conjunto de forms com referências mútuas não manda a varredura num loop. O método é público no THotPDF, mas no uso normal você nunca o chama, porque o EndDoc faz isso por você

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

Desenhando uma página X-1a no Delphi que passa na checagem

Um documento X-1a do HotPDF em conformidade 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 profile FOGRA39; o AddPDFXOutputIntent recebe o identificador da condição de saída, uma string de informações, o stream do profile ICC, a 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);   // obrigatório em toda 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 checagem de cores roda aqui
  finally
    Pdf.Free;
  end;
end;

Toda chamada baseada em TColor é uma armadilha nesse contexto. SetRGBFillColor, SetRGBStrokeColor e SetRGBColor recebem uma cor GDI e emitem DeviceRGB, então uma rotina auxiliar escrita anos atrás para relatórios voltados à tela contaminará um trabalho de impressão no momento em que for reutilizada. Use SetCMYKFillColor, SetCMYKStrokeColor ou SetCMYKColor com componentes na faixa de 0 a 1, ou os equivalentes Gray

Cores spot e gradientes CMYK

Colorantes spot são permitidos no X-1a desde que o alternate deles seja CMYK ou Gray. O RegisterSeparation constrói o espaço Separation e devolve o nome do recurso dele, e o RegisterAxialGradient escolhe o color space do shading pela quantidade de componentes que você passar, com quatro componentes significando DeviceCMYK

var
  SpotCS, Ramp: AnsiString;
begin
  // Tinta spot com uma build 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 de 100%
  Pdf.CurrentPage.Rectangle(72, 420, 200, 80);
  Pdf.CurrentPage.Fill;

  // 4 componentes por stop: 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 para a mesma chamada de gradiente e você recebe um shading DeviceRGB, que a varredura de recursos rejeita sob o nome do shading pattern. O mesmo vale para um Separation registrado com 'DeviceRGB' como alternate: o nome spot parece inofensivo, mas a varredura segue o alternate e encontra RGB por baixo. Como tais tintas spot são compostas quando um viewer ou RIP recua para o alternate está coberto no artigo sobre renderizar cores spot Separation e DeviceN no 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 própria contagem de componentes, então um JPEG CMYK de quatro componentes ou um JPEG em tons de cinza está bom e um JPEG RGB comum não está. Converta a arte para o profile de impressão no seu pipeline de imagem antes de ela chegar ao PDF

Por que o /DefaultRGB não salva um arquivo PDF/X-1a?

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

O EnsurePDFX1aContentColorSpaces ignora deliberadamente esse mapeamento. Um valor RGB caracterizado continua sendo RGB, e o OutputIntent do X-1a é uma condição de impressão CMYK, então alguém ainda teria de convertê-lo. Se você lembra de uma diferença entre os dois perfis de arquivamento e impressão, que seja esta: o PDF/A pergunta se uma cor é inequívoca, o PDF/X-1a pergunta se ela já é tinta

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

Como a checagem roda no EndDoc sobre conteúdo já escrito, a ordem em que você configura as coisas não importa. RGB desenhado antes de o PDFXCompliance ser atribuído, ou antes de o OutputIntent ser registrado, ainda é pegado. Isso importa para código que constrói páginas em rotinas compartilhadas e decide o output profile tarde, às vezes a partir de uma configuração de usuário 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;                               // ainda levanta: DeviceRGB registrado

Uma flag de hora do desenho tem uma armadilha que vale conhecer se você construir uma checagem similar por conta própria. Toda página nova é inicializada com um graphics state preto via SetRGBColor(0), que emite um par 0 0 0 rg e 0 0 0 RG, e se aquele boilerplate de page setup pudesse setar a flag DeviceRGB, todo documento pareceria contaminado por RGB, tanto para essa checagem quanto para a checagem de consistência de cores do PDF/A. O HotPDF salva e restaura a flag em torno dessa inicialização, então só a cor que você 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 ela 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 trocar para X-3 ou X-4. O validador levanta um Exception simples na primeira violação, então faça match no texto em vez de numa classe de exceção

Anatomia da exceção RGB do EndDoc no HotPDF em Delphi: a flag de hora do desenho reporta uso no documento inteiro enquanto a varredura de recursos nomeia a página e a chave exatas
leia a localização no início da mensagem — um relatório de documento inteiro manda você ao 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 hora do desenho disparou. Não há número de página, porque a flag é de documento inteiro e é conferida antes da varredura. Procure no seu código de desenho chamadas SetRGB*, bitmaps comprimidos com Flate, JPEGs RGB e cores de anotação TColor
  • page 3 image XObject /Im2 uses DeviceRGB content ou page 1 shading Pattern /P1 uses DeviceRGB content vem da varredura de recursos e aponta para uma chave de recurso exata
  • uses an ICCBased RGB colour space (N=3) depois de uma localização ColorSpace /... é o resultado típico de registrar um profile sRGB com RegisterICCProfile e selecioná-lo via SetFillColorSpace, um caminho que a flag de hora do desenho não vê
  • Localizações aninhadas se encadeiam, por exemplo page 2 Form XObject /Fm1 ColorSpace /CS1, então você pode seguir o caminho da página até o recurso que carrega 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);  // seu próprio logger
    raise;
  end;
end;

Como só a primeira violação é reportada, consertar um trabalho com várias fontes de RGB é iterativo: conserte, recompile, leia a próxima mensagem. Para trabalhos em lote vale registrar a mensagem completa por arquivo 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 gate RGB se encaixa no seu preflight

O EnsurePDFX1aContentColorSpaces é um gate RGB focado que roda dentro do seu build, e ele combina com um preflight PDF/X-1a completo em vez de substituir um. Ele cobre as famílias RGB, pela varredura de recursos a partir do /Resources de cada página e pela flag de hora do desenho que os setters de cor do HotPDF mantêm para operadores inline rg e RG; color spaces aninhados são seguidos até a 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, então rode o arquivo final por um validador externo como parte do seu build para ter o veredito completo de conformidade. O quadro mais amplo 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 assistido está coberto em automatizar checagens de preflight PDF no 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 o download de avaliação estão na página do produto HotPDF Delphi PDF component