Artigo Técnico

A cor de dispositivo PDF/A é julgada pelo que a página pinta

O veraPDF sinaliza a ISO 19005-1 6.2.3.3 num arquivo que nunca desenha RGB, chama um re f solto de não calibrado, ou reprova a página 2 enquanto uma página 1 idêntica passa. O PDF/A julga essas regras pelo que cada página pinta, então o validador de PDF/A do PDFium Component para Delphi rastreia cada página através de forms, patterns, glifos e anotações antes de decidir sobre cor e transparência

O que 6.2.3.3 e 6.2.4.3 realmente exigem

A ISO 19005-1 6.2.3.3 comprime três regras independentes num único parágrafo. Um arquivo em conformidade pode usar DeviceRGB ou DeviceCMYK, mas não deve usar os dois; se qualquer espaço de cores não calibrado for usado, o arquivo deve conter um OutputIntent de PDF/A-1; e o DeviceRGB só é permitido com uma intent cujo perfil é RGB, o DeviceCMYK só com uma CMYK. O DeviceGray fica fora dessa lista porque a cláusula segue explicando a conversão de cinza sob qualquer uma das intents. A intent em si é opcional: a 6.2.2 diz que um arquivo pode especificar uma, e só conta uma entrada OutputIntents com /S /GTS_PDFA1 e um stream de perfil ICC válido em /DestOutputProfile. Então um verificador que exige uma intent de todo arquivo rejeita documentos em conformidade que nunca pintam cor de dispositivo. A Parte 1 não menciona DefaultRGB nem DefaultCMYK, mas uma cor remapeada através deles é renderizada num espaço baseado em CIE (ISO 32000-1 §8.6.5.6), o que satisfaz a exigência de independência de dispositivo da 6.2.3.1; a suíte de testes do veraPDF para a 6.2.3.3 a trata como calibrada, e o PDFium Component lê a cláusula do mesmo jeito. A proibição de mistura fica à parte: um arquivo PDF/A-1 que pinta RGB e CMYK falha mesmo quando entradas Default calibraram ambos. A ISO 19005-2 6.2.4.3 derruba essa proibição e calibra cada espaço nos termos próprios dele, como a coluna da direita abaixo mostra, então o PDFium Component roda uma varredura para cada parte e aplica a checagem de mistura só a declarações PDF/A-1

Espaço de cores de dispositivo pintadoPDF/A-1 (ISO 19005-1 6.2.3.3)PDF/A-2 e posteriores (ISO 19005-2 6.2.4.3)
DeviceRGBDefaultRGB em escopo ou uma intent RGB; não ao lado de DeviceCMYKDefaultRGB em escopo, ou um OutputIntent RGB
DeviceCMYKDefaultCMYK em escopo ou uma intent CMYK; não ao lado de DeviceRGBDefaultCMYK em escopo, ou um OutputIntent CMYK
DeviceGrayDefaultGray em escopo, ou qualquer OutputIntent PDF/A-1DefaultGray em escopo, ou qualquer OutputIntent PDF/A

O que conta como usar um espaço de cores?

Usar um espaço de cores significa pintar com ele num caminho que um leitor vai renderizar. Declará-lo não basta. A suíte do veraPDF faz o ponto com um arquivo aprovado na 6.2.3.3: um Form XObject nomeado define CMYK com K e k, fica no dicionário de recursos da página, nunca é referenciado do conteúdo da página, e o OutputIntent do arquivo é RGB. Uma busca por nomes de cores de dispositivo no arquivo inteiro reprova esse arquivo, e perde um que pinta só DeviceRGB sob uma intent CMYK, onde nenhum par de nomes conflita. O PDFium Component percorre o grafo de desenho em vez disso, construído sobre o scanner de preflight PDF/A byte a byte dele. De cada página, a varredura conta a cor pintada por:

  • os content streams da página, e todo Form XObject que eles invocam com Do, forms aninhados incluídos
  • um pattern selecionado com scn ou SCN, lido com as Resources do próprio pattern
  • os CharProcs de uma fonte Type 3 selecionada com Tf
  • image XObjects e imagens inline, pelo espaço de cores que o dicionário próprio deles nomeia
  • appearance streams de anotações, pintados por cima da página; quando o /AP /N de um widget é um dicionário de estados, todo estado conta, porque alternar o campo pinta aquele que o /AS não selecionou

A metade mais sutil da alcançabilidade é a cor que o conteúdo jamais nomeia. q 0 0 10 10 re f Q não contém operador de cor nenhum. Ainda pinta. O espaço de cores inicial do estado gráfico é DeviceGray (ISO 32000-1 Table 52), então o fill é preto DeviceGray, não calibrado sob toda parte, a menos que exista um OutputIntent de PDF/A ou um DefaultGray. A varredura portanto rastreia se algo já escolheu uma cor: rg, k, g e as formas de stroke deles selecionam um espaço de dispositivo, cs e CS selecionam o que nomeiam, e um operador de fill, stroke ou texto alcançado antes de qualquer escolha registra DeviceGray. /CS0 cs 1 0 0 /P0 scn já escolheu um espaço, então aquele scn não é cinza

O caminho de pintura alcançável que o PDFium Component percorre no Delphi antes de decidir sobre cor de dispositivo PDF/A: forms, patterns, glifos Type 3, imagens e appearance streams de anotações
declarar um espaço de cores não basta — a varredura conta o que cada página pinta, incluindo o cinza de dispositivo que um re f solto pega do estado inicial

Imagens inline trazem o espaço de cores delas e as amostras cruas delas para dentro do próprio content stream, e um scanner precisa ler as duas com cuidado. O /CS pode ser uma abreviação (G, RGB, CMYK, I) ou um nome de recurso como um que resolve para [/Indexed /DeviceRGB 255 13 0 R], cuja base conta; e os bytes entre ID e EI são binário arbitrário, então o EI só termina a imagem entre whitespace, senão um rg dentro das amostras comprimidas vira um operador fantasma

% página 1: DefaultRGB em escopo, e um XObject que nada invoca
4 0 obj << /Type /Page /Parent 2 0 R /Contents 5 0 R
           /Resources << /ColorSpace << /DefaultRGB 9 0 R >>
                         /XObject << /Fm0 6 0 R >> >> >>
endobj
% página 2: o mesmo content stream, sem subdicionário ColorSpace
7 0 obj << /Type /Page /Parent 2 0 R /Contents 5 0 R /Resources << >> >>
endobj
5 0 obj << /Length 46 >> stream
1 0 0 rg 72 72 200 100 re f
q 0 0 10 10 re f Q
endstream
endobj
6 0 obj << /Type /XObject /Subtype /Form /BBox [0 0 50 50] /Length 24 >> stream
0 0 0 1 k 0 0 50 50 re f
endstream
endobj
9 0 obj [/ICCBased 10 0 R]
endobj

Cada sintoma da abertura se depreende deste fragmento. O /Fm0 nunca encontra um Do, então o k dele é irrelevante. O re f solto nas duas páginas é DeviceGray sem nada para calibrá-lo. O rg da página 1 está coberto pelo DefaultRGB nos recursos com que a página 1 pinta; a página 2 roda o content stream idêntico contra recursos que não têm o mapeamento, então os mesmos bytes ficam não calibrados lá. Esperado para uma declaração PDF/A-1b: pvaiMissingOutputIntent com pvaiMissingIccProfile, movidos pelo RGB da página 2 e pelo cinza das duas páginas. O form não contribui com nada; invocado, o CMYK dele somaria a proibição de mistura do PDF/A-1 por cima

Espaços de cores Default pertencem ao dicionário de recursos que pinta

Os mapeamentos Default são procurados no subdicionário ColorSpace do dicionário de recursos atual (ISO 32000-1 §8.6.5.6), o que os torna exatamente tão locais quanto os recursos em si; um documento não tem default global. Um image XObject não tem Resources próprio, então uma imagem cujo dicionário diz /ColorSpace /DeviceCMYK é calibrada pelo DefaultCMYK de qualquer página ou form que a desenhe, e a mesma imagem desenhada de dois lugares pode estar calibrada num e não no outro. Form XObjects, tiling patterns e procedimentos de glifo Type 3 carregam as Resources próprias deles, então o DefaultCMYK de uma página não alcança o interior deles, enquanto um mapeamento no dicionário próprio deles alcança. O PDFium Component dá escopo aos mapeamentos do mesmo jeito. O veredito volta como um record TPdfAValidationResult: a Conformance detectada mais um conjunto Issues. Isso basta para triar uma pasta

uses
  System.SysUtils, System.Classes, System.TypInfo, FPdfPdfa;

procedure ReportColorFindings(const FileName: string; Log: TStrings);
const
  ColorRules: TPdfAValidationIssues = [pvaiMissingOutputIntent,
    pvaiMissingIccProfile, pvaiMixedDeviceColorSpaces, pvaiBlendSpaceUndefined];
var
  Source: TFileStream;
  Res: TPdfAValidationResult;
  Found: TPdfAValidationIssues;
  Issue: TPdfAValidationIssue;
begin
  Source := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
  try
    Res := ValidatePdfACompliance(Source);   // lê a partir da posição 0
  finally
    Source.Free;
  end;
  Log.Add(ExtractFileName(FileName) + ': ' +
    GetEnumName(TypeInfo(TPdfAConformance), Ord(Res.Conformance)));
  Found := Res.Issues * ColorRules;
  for Issue in Found do
    case Issue of
      pvaiMissingOutputIntent:
        Log.Add('  reachable uncalibrated device color, no PDF/A OutputIntent');
      pvaiMissingIccProfile:
        Log.Add('  no OutputIntent with a DestOutputProfile ICC stream');
      pvaiMixedDeviceColorSpaces:
        Log.Add('  RGB/CMYK use disagrees with the intent, or PDF/A-1 paints both');
      pvaiBlendSpaceUndefined:
        Log.Add('  transparent page without a blending color space');
    end;
  if Found = [] then
    Log.Add('  device color and blending rules satisfied');
end;

O pvaiMissingOutputIntent e o pvaiMissingIccProfile aparecem juntos quando a varredura alcança cor de dispositivo não calibrada e não existe OutputIntent de PDF/A. O pvaiMissingIccProfile sozinho significa que uma intent existe mas não carrega perfil, o que a 6.2.2 não aceita como intent de PDF/A. O pvaiMixedDeviceColorSpaces cobre tanto a proibição de mistura do PDF/A-1 quanto uma divergência de componentes: DeviceRGB não calibrado sob uma intent de quatro componentes, ou DeviceCMYK sob uma de três. O conjunto nomeia a regra em vez da página, então num documento longo ou mesclado, compare quais páginas declaram entradas Default e quais não

Uma imagem DeviceCMYK desenhada a partir de dois dicionários de recursos no Delphi: calibrada pelo DefaultCMYK numa página e não calibrada noutra, como o PDFium Component reporta
espaços de cores default pertencem ao dicionário de recursos que pinta, então a mesma imagem pode estar calibrada onde uma página a desenha e não calibrada onde um form desenha

Quando uma página transparente precisa de um espaço de mesclagem próprio?

Uma página transparente só precisa de um espaço de cores de mesclagem próprio quando o documento não tem OutputIntent de PDF/A. A ISO 19005-2 6.2.10 (6.2.9 no PDF/A-4) faz da intent o espaço de cores de mesclagem default no sentido da ISO 32000-1 §11.3.4; sem uma intent, toda página que contém transparência deve carregar um Group cujo dicionário de atributos (ISO 32000-1 Table 147) inclua CS. Esse CS também é cobrado pela 6.2.4, então um grupo de página de /DeviceCMYK sob uma intent sRGB é reportado como se fosse ausente, e o PDFium Component levanta pvaiBlendSpaceUndefined para ambos; CalRGB ou um espaço de grupo ICCBased não tem essa dependência. O PDF/A-1 não chega a essa questão, porque a ISO 19005-1 6.4 elimina a transparência por completo

A parte difícil é a expressão contém transparência. O Anexo A da ISO 19005-2 é normativo e testa da página para fora; uma contagem no arquivo inteiro de chaves /SMask ou valores /ca responde outra pergunta. Buscas por bytes também colidem com sintaxe de transparência em outro lugar: /S /Trans, a ação de transição proibida, é prefixo de todo grupo /S /Transparency, e /Screen é tanto um subtipo de anotação proibido quanto um modo de mesclagem padrão, então um nome não significa nada até ser pareado com a chave dele. Seguindo o Anexo A, o PDFium Component trata uma página como transparente quando qualquer um destes é alcançado a partir dela:

  • um ExtGState que o conteúdo seleciona com gs e que tem um dicionário de soft mask, um ca ou CA abaixo de 1, ou um modo de mesclagem diferente de Normal; um declarado mas jamais selecionado não conta
  • um Form XObject invocado com Do cujo /Group tem /S /Transparency, ou cujo próprio conteúdo atende uma destas condições; um tiling pattern é julgado como um form
  • um image XObject com um stream SMask ou um valor SMaskInData acima de 0
  • uma descrição de glifo de uma fonte Type 3 com que a página mostra texto, julgada como um form
  • um appearance stream de anotação, todo estado de widget incluído, e um modo de mesclagem no próprio dicionário da anotação

Os grupos também retroalimentam a regra de cor de dispositivo, e aqui o corpus nos corrigiu. A suíte do veraPDF para a 6.2.4.3 guarda arquivos PDF/A-4 sem OutputIntent algum que pintam RGB de dispositivo e devem passar, porque o /Group de cada página nomeia um CS calibrado. Nós assumimos que só grupos isolados podiam calibrar, já que um grupo não isolado compõe sobre o fundo dele, e isso estava errado para grupos de página: a Table 147 nota que uma página imposta diretamente sobre o meio de saída é efetivamente isolada independente do I, então o CS dela é respeitado. Num Form XObject ou numa aparência de anotação a flag decide mesmo; a suíte tem um arquivo de aprovação e um de reprovação que diferem só no /I true. O DeviceGray não recebe calibração de grupo nenhum, porque a 6.2.4.3 pede DefaultGray ou uma intent, e um grupo cujo CS é um espaço de dispositivo precisa ser calibrado ele mesmo. O PDFium Component portanto deixa um grupo de página calibrar seja lá o que o /I dele disser, deixa um grupo de form ou de aparência calibrar só quando isolado, mantém o cinza fora da calibração por grupo, e ainda checa um espaço de dispositivo que um grupo nomeie

Matriz PDF/A para Delphi: quando uma página transparente precisa de um CS de mesclagem próprio e como o PDFium Component julga a calibração de grupos de página, form e aparência
um output intent é o espaço de mesclagem default, e um grupo de página calibra cor de dispositivo seja lá o que a flag de isolamento dele disser, enquanto um grupo de form precisa de /I true

Um OutputIntent em vez de um grupo em cada página

Se o seu próprio código escreve o acervo, comece com um OutputIntent de PDF/A no nível do documento. Uma entrada de catálogo calibra o DeviceGray em todo lugar, cobre o espaço de dispositivo correspondente em cada página, e é o espaço de mesclagem default de toda página transparente. Grupos de página e entradas Default têm escopo de um dicionário de recursos cada, e a página que uma mesclagem posterior adiciona sem eles é a que reprova. O PDF 2.0 adiciona OutputIntents ao objeto de página (ISO 32000-2 §7.7.3.3 e §14.11.5); o PDFium Component aceita essa intent de nível de página para o PDF/A-4 quando o catálogo não tem nenhuma, enquanto as partes 1 a 3, construídas sobre PDF 1.4 a 1.7, olham só para o catálogo. Para conteúdo RGB, o SaveAsPdfAToStream adiciona uma intent de catálogo construída sobre o perfil ICC sRGB embutido do writer de PDF/A, e validar a saída mostra se o conteúdo era mesmo só RGB

uses
  System.SysUtils, System.Classes, PDFium, FPdfPdfa;

function ArchiveWithSrgbIntent(const InFile, OutFile: string;
  out Remaining: TPdfAValidationIssues): Boolean;
var
  Pdf: TPdf;
  Opts: TPdfASaveOptions;
  Output: TMemoryStream;
  Check: TPdfAValidationResult;
begin
  Result := False;
  Remaining := [];
  Pdf := TPdf.Create(nil);
  Output := TMemoryStream.Create;
  try
    Pdf.FileName := InFile;
    Pdf.Active := True;
    if not Pdf.Active then
      Exit;                           // o arquivo não pôde ser aberto
    Opts := TPdfASaveOptions.Default;
    Opts.Conformance := pac2b;        // o PDF/A-1 proibiria a transparência de cara
    // IccProfileData permanece vazio: o perfil sRGB IEC61966-2.1 embutido é usado
    if not Pdf.SaveAsPdfAToStream(Output, Opts) then
      Exit;
    Check := ValidatePdfACompliance(Output);
    Remaining := Check.Issues;
    // Esperado para conteúdo só RGB: pvaiMissingOutputIntent e
    // pvaiBlendSpaceUndefined somem. pvaiMixedDeviceColorSpaces agora significa
    // um caminho alcançável pinta DeviceCMYK que nenhum DefaultCMYK calibra
    if Check.IsCompliant then
    begin
      Output.SaveToFile(OutFile);
      Result := True;
    end;
  finally
    Output.Free;
    Pdf.Free;
  end;
end;

Um pvaiMixedDeviceColorSpaces que sobrevive à intent sRGB é a varredura reportando DeviceCMYK num caminho alcançável, e esse documento precisa de um mapeamento DefaultCMYK nas páginas que o pintam, ou de um perfil de saída CMYK, antes que o PDFium Component o aceite como PDF/A-2b