Artigo Técnico

Limites de Implementação PDF/A e Codificação de Tipos de Letra

O PDFium Component valida os limites de implementação do ISO 19005-1 Annex C — tokens de nome de 127 bytes, 8191 elementos de matriz, 4095 entradas de dicionário e 28 níveis de aninhamento de contentor — e reporta um tipo de letra TrueType simbólico que transporta uma entrada /Encoding. Ambas as verificações correm no caminho de análise de bytes, pelo que uma aplicação Delphi ou Lazarus obtém o veredito sem carregar a DLL do PDFium de todo

Estas são as falhas que mais intrigam as pessoas, porque o documento parece bem. Renderiza, imprime, todos os tipos de letra estão embutidos, a intenção de saída está presente. Depois um validador rejeita-o por um dicionário que tem 4096 entradas, e nada no documento visível explica porquê

O que é que os limites do Annex C estão efetivamente a proteger?

Interoperabilidade com implementações que antecedem o seu gerador. O Annex C transporta os limites de implementação da Referência PDF para todas as partes PDF/A, e os números não são arbitrários — descrevem o que um leitor conforme era historicamente exigido de gerir. Um ficheiro que os exceda pode abrir perfeitamente num visualizador moderno e falhar no leitor de arquivo em que um sistema de registos se padronizou há quinze anos, que é precisamente o cenário que o PDF/A existe para evitar

Os quatro limites são inclusivos. Um token de nome de exatamente 127 bytes valida; 128 não. Uma matriz com exatamente 8191 elementos valida; 8192 não. O PDFium Component fixa ambos os lados de todos os limites no seu conjunto de testes por essa razão, porque um erro off-by-one numa verificação de limite produz o pior tipo de validador: um que rejeita ficheiros conformes e é acreditado à mesma

uses FPdfPdfa;

var
  Src: TFileStream;
  Res: TPdfAValidationResult;
begin
  Src := TFileStream.Create('archive.pdf', fmOpenRead or fmShareDenyWrite);
  try
    Res := ValidatePdfACompliance(Src);
    if pvaiArrayOverLimit in Res.Issues then
      Memo1.Lines.Add('An array carries more than 8191 elements');
    if pvaiDictOverLimit in Res.Issues then
      Memo1.Lines.Add('A dictionary carries more than 4095 entries');
    if pvaiNestingOverLimit in Res.Issues then
      Memo1.Lines.Add('Containers nest deeper than 28 levels');
    if pvaiNameOverLimit in Res.Issues then
      Memo1.Lines.Add('A name token is longer than 127 bytes');
  finally
    Src.Free;
  end;
end;

Que geradores é que atingem efetivamente estes limites?

Os que constroem estrutura programaticamente, que é a maioria da saída de linha de negócio. Um formulário com vários milhares de campos produz um array /Annots ou um array AcroForm /Fields que cresce para além de 8191. Uma página cujo dicionário de recursos acumula uma entrada por imagem gerada ou instância de tipo de letra cruza 4095. Árvores de estrutura geradas profundamente — um documento marcado construído por recursão sobre um modelo de dados aninhado — passam de 28 níveis sem ninguém dar conta, porque ninguém olha para a profundidade de aninhamento

Nomes longos vêm de um hábito diferente: codificar dados em tokens de nome. Um nome de colorante construído a partir de um identificador de cliente, um grupo de conteúdo opcional nomeado após um caminho de ficheiro completo, um campo de formulário cujo nome totalmente qualificado concatena seis níveis de hierarquia. Nomes são baratos de gerar e fáceis de tornar longos, e 127 bytes desaparecem mais depressa do que se esperaria quando uma etiqueta codificada em UTF-8 está envolvida

A correção é estrutural em todos os casos. Dividir a matriz, dividir o dicionário, aplanar o aninhamento, encurtar o nome — a recomendação de preflight para cada questão nomeia o limite concreto em vez de dizer que o ficheiro é inválido. Injeção de marcadores não pode ajudar aqui: estas não são reclamações de metadados, são a forma do grafo de objetos

Porque razão um tipo de letra TrueType simbólico não pode transportar /Encoding

Porque o ISO 19005-1 §6.3.7 admite apenas o cmap embutido do tipo para tipos de letra TrueType simbólicos, e uma entrada /Encoding contradiz-lo-ia. Um tipo simbólico mapeia códigos a glifos pelos seus próprios termos — é isso que simbólico significa. Adicione uma tabela de codificação e há agora duas respostas à pergunta "que glifo seleciona o byte 0x41", sem nenhuma regra no ficheiro a dizer qual ganha. Leitores diferentes resolvem-no de forma diferente, e um documento que renderiza como texto num visualizador renderiza como dingbats noutro

O PDFium Component lê o sinalizador simbólico a partir do /FontDescriptor, quer o descritor seja escrito inline no dicionário de tipo de letra quer referenciado indiretamente. Um tipo de letra TrueType não simbólico mantém o seu /WinAnsiEncoding ou /MacRomanEncoding exigido sem ser sinalizado, porque para tipos não simbólicos a codificação é exatamente o que a norma pede. A verificação dispara na contradição, não na presença de uma codificação

if pvaiSymbolicTrueTypeEncoding in Res.Issues then
  Memo1.Lines.Add(
    'A symbolic TrueType font carries /Encoding; PDF/A admits only its ' +
    'built-in cmap (ISO 19005-1 6.3.7)');

A fonte prática deste defeito é o subsetting de tipos de letra feito por um produtor que trata todos os tipos TrueType da mesma forma. Symbol, Wingdings, tipos de código de barras e tipos de ícones são os transportadores habituais — exatamente os tipos que um documento de negócio usa para caixas de verificação, logótipos e códigos de barras, e exatamente os que ninguém reexamina quando um documento falha validação por "tipos de letra"

Como as questões chegam a um relatório de preflight

Os quatro limites de contentor são classificados sob estrutura; a questão da codificação TrueType simbólica é classificada sob conteúdo. Essa divisão importa quando um relatório vai para duas pessoas diferentes: os achados de estrutura costumam pertencer a quem escreveu o gerador, e os achados de conteúdo costumam pertencer a quem forneceu os ativos

Cada questão transporta uma recomendação que nomeia o remédio em termos concretos — encurtar tokens de nome para 127 bytes ou menos, dividir matrizes para que nenhuma transporte mais de 8191 elementos, remover /Encoding de tipos de letra TrueType simbólicos. Um relatório que diz "não conforme com PDF/A" inicia uma investigação. Um relatório que diz que limite foi excedido e por quê termina uma

Validar sem a DLL, e porque é que isso importa aqui

Todas as verificações acima correm contra os bytes do ficheiro, pelo que funcionam num serviço que não tem o binário PDFium implementado, num passo de compilação, ou numa máquina onde carregar uma DLL nativa é um problema de política. Essa é uma linha de desenho deliberada no PDFium Component: as verificações que podem ser respondidas a partir da estrutura são respondidas a partir da estrutura, e a DLL é reservada para as que genuinamente precisam de um motor de renderização

Para o fluxo de trabalho envolvente — correr validação sobre uma pasta, produzir relatórios, e decidir o que fazer com os achados — veja os guias de validação de preflight PDF/A em Delphi e da CLI de relatório de preflight em lote. Para a escolha de perfil de arquivo que está acima de todas estas verificações, as notas sobre conformidade de arquivo PDF/A cobrem que parte e nível visar antes de começar a corrigir achados

O PDFium Component envolve o motor PDFium para Delphi, C++Builder e Lazarus com uma API VCL de alto nível e um conjunto de validadores de conformidade que correm com ou sem a DLL — veja a página do produto PDFium Component para as normas e plataformas suportadas