Artigo Técnico

Documentos de Engenharia PDF/E-1 em Delphi com PDFlibPas

O PDF/E-1 é o perfil de arquivo para documentos de engenharia, e o PDFlibPas implementa-o como um modo de autoria que ativa com SetPDFEMode mais um preflight limitado que lê os content streams operador a operador. O perfil não é um PDF/A com outro rótulo: tem o seu próprio namespace de identificação, o seu próprio requisito de metadados de ciclo de vida, e uma regra que torna a validação de conteúdo mais estrita do que qualquer perfil de arquivo que já encontrou

Os entregáveis de engenharia são a razão de ser do perfil. Um conjunto de desenhos que tem de continuar legível e comprovadamente inalterado daqui a vinte anos, com um histórico de revisões que sobrevive, e com cor que significa o mesmo no plotter do outro edifício. Esses requisitos produzem uma especificação cujas exigências estão maioritariamente fora do conteúdo da página, nos metadados e na gestão de cor, que é precisamente onde um escritor de PDF genérico as erra

Identificação própria, não uma variação do PDF/A

A primeira coisa a acertar é que a identificação PDF/E-1 não pode ser produzida adaptando o padrão do PDF/A ou do PDF/X. Usa um namespace XMP distinto, http://www.aim.org/pdfe/ns/id/, e o valor da versão tem de aparecer em dois sítios: como entrada de informação do documento e como propriedade XMP qualificada pelo namespace. Emitir só a propriedade XMP, ou só a entrada de informação, produz um ficheiro que carrega a intenção e falha a validação

O output intent tem uma forma igualmente específica. O PDF/E-1 exige um perfil ICC incorporado com o identificador de subtipo ISO_PDFE1, e o perfil tem de ter uma contagem de componentes que case com a família de cor de dispositivo que o documento realmente usa. Essa última cláusula é onde as implementações falham discretamente, porque significa que o intent não pode ser escolhido à partida e depois ignorado

Porque é que a cor de dispositivo precisa de uma varredura do documento inteiro?

Porque os espaços de cor escondem-se em dicionários de recursos que uma varredura ao nível da página nunca alcança. O PDF/E-1 trata DeviceRGB e DeviceCMYK como famílias mutuamente exclusivas num documento, por isso validar o perfil significa conhecer todos os espaços de cor de dispositivo que qualquer coisa no ficheiro usa. Um form XObject tem os seus próprios recursos. Um padrão também, e uma imagem também. Um tiling pattern dentro de um form XObject dentro de uma página está três níveis abaixo, e um validador que só verifica os recursos da página de topo passa um documento que usa as duas famílias

A varredura por isso regista os espaços de cor enquanto percorre páginas, forms, imagens e padrões como uma única travessia, e só então decide se o documento é coerente e se o output intent casa. O mesmo raciocínio orienta a arquitetura do preflight em geral: travessias parciais produzem falsas aprovações, e uma falsa aprovação numa verificação de conformidade é pior do que nenhuma verificação, porque fica registada como evidência

var
  Lib: TPDFlib;
  Diag: WideString;
begin
  Lib := TPDFlib.Create(nil);
  try
    Lib.LoadFromFile('assembly-drawings.pdf');

    if Lib.SetPDFEMode(1) = 0 then
      raise Exception.Create('PDF/E author mode was refused');

    // O modo de autoria mantém os metadados de ciclo de vida em passo a cada gravação.
    // Pergunte antes de gravar se o documento passaria no seu próprio portão
    if not Lib.PDFEReadyForSave then
    begin
      Diag := Lib.GetPDFEDiagnostics;
      Writeln('PDF/E blockers: ', Diag);
      Exit;
    end;

    Lib.SaveToFile('assembly-drawings-pdfe.pdf');
  finally
    Lib.Free;
  end;
end;

Os metadados de ciclo de vida são uma obrigação a cada gravação

O PDF/E-1 pede mais do que um identificador de documento. O conjunto mínimo inclui o identificador de documento de gestão de media, um identificador de versão, uma classe de rendição, hora de criação, hora de modificação, hora dos metadados e um título. É um vocabulário de rastreio de revisões, e existe porque se espera que um entregável de engenharia seja reemitido e não escrito uma única vez

A consequência para uma implementação é que estes campos não podem ser definidos na criação do documento. Se a hora de modificação é escrita quando ativa o modo e o documento é editado depois, o snapshot XMP e o estado real do documento divergem, e um validador que os compara reporta uma inconsistência que ninguém pretendia. O modo de autoria por isso sincroniza os campos imediatamente antes de cada gravação, para os metadados descreverem os bytes prestes a ser escritos e não os bytes que existiam quando o modo foi ativado

Este é um princípio geral para metadados de conformidade e vale a pena enunciá-lo separadamente do PDF/E: metadados derivados pertencem ao caminho de gravação, não ao caminho de edição. Qualquer campo calculado a partir do estado do documento tem de ser recalculado no momento em que o estado é congelado, ou é uma cache sem invalidação

Diagrama PDF/E-1 do PDFlibPas da varredura de cor de dispositivo do documento inteiro, que percorre os dicionários de recursos de página, form XObject, tiling pattern e imagem recolhendo as famílias DeviceRGB e DeviceCMYK antes de julgar a coerência, ao lado dos campos de metadados de ciclo de vida que o modo de autoria ressincroniza imediatamente antes de cada gravação para o snapshot XMP casar com os bytes prestes a ser escritos
A coerência de cor só pode ser julgada depois de uma travessia alcançar todos os dicionários de recursos, e os metadados de ciclo de vida derivados são recalculados no momento em que o estado do documento é congelado, não quando o modo é ativado

A regra que torna a validação de conteúdo estrita

O PDF/E-1 não permite que os operadores da secção de compatibilidade absorvam conteúdo desconhecido. Num PDF ordinário, BX e EX delimitam uma região em que um consumidor deve ignorar operadores que não reconhece, que é a válvula de escape que permite a um produtor emitir construções mais recentes sem partir leitores antigos. Sob o PDF/E-1 essa válvula está fechada, por isso qualquer operador que o preflight não reconheça é reportado incondicionalmente, esteja ou não dentro de uma secção de compatibilidade

O efeito num validador é significativo. Não pode saltar regiões que não compreende, o que significa que o parser de operandos tem de analisar realmente todos os operadores em todos os content streams. É aqui que entram os limites. A travessia é limitada a 128 níveis de aninhamento, um milhão de objetos e 64 MiB de conteúdo, e esses limites não são afinação de desempenho. Um ficheiro hostil ou simplesmente avariado pode apresentar um grafo de objetos com ciclos ou uma profundidade de aninhamento que transforma um validador recursivo num stack overflow, e os limites são o que impede uma passagem de validação de se tornar um vetor de denial-of-service. A mesma postura defensiva está descrita em analisar PDFs não fidedignos com segurança

// Validação autónoma de um ficheiro que não produziu, sem o carregar
// numa instância de documento
var
  Issues: TStringList;
  Stream: TFileStream;
  I: Integer;
begin
  Issues := TStringList.Create;
  Stream := TFileStream.Create('incoming.pdf', fmOpenRead or fmShareDenyWrite);
  try
    if CheckCompliancePDFE(Stream, '', 0, Issues) = 0 then
      for I := 0 to Issues.Count - 1 do
        Writeln('PDF/E: ', Issues[I]);
  finally
    Stream.Free;
    Issues.Free;
  end;
end;

O que o portão de gravação repara e o que recusa

O portão divide o seu trabalho em duas fases, e a divisão é por si só uma ideia de design aproveitável. Primeiro normaliza o que é seguramente reparável: as flags de impressão das anotações, as flags no-zoom e no-rotate das anotações de texto, e a flag de geração de aparência no dicionário do formulário. São definições com um único valor correto sob o perfil e sem conteúdo informativo, por isso corrigi-las silenciosamente está certo e recusar por causa delas seria pedantismo

Depois verifica as restrições que não podem ser reparadas sem alterar o que o documento significa: versão, identificação, encriptação, output intent, coerência de cor de dispositivo e a presença de conteúdo de formulário dinâmico. Um documento que falhe qualquer uma dessas é recusado, porque inventar um output intent ou escolher uma família de cores em nome do autor produziria um ficheiro que passa a validação e deturpa o conteúdo

Diagrama do portão de gravação PDF/E-1 do PDFlibPas para Delphi mostrando o preflight limitado que analisar todos os operadores de content stream sob limites de aninhamento de 128 níveis, um milhão de objetos e 64 MiB, repara silenciosamente as flags de impressão, zoom e rotação de anotações, recusa versão, identificação, encriptação, output intent, cor de dispositivo ou conteúdo de formulário dinâmico errados, e reporta bloqueadores através de GetPDFEDiagnostics
O portão repara silenciosamente apenas o que não transporta informação, recusa toda a restrição que uma reparação distorceria, e transforma a recusa numa lista de bloqueadores através de GetPDFEDiagnostics antes de quaisquer bytes chegarem ao disco

Ler os diagnósticos através de GetPDFEDiagnostics antes de gravar transforma essa recusa numa lista acionável em vez de uma operação falhada. Num pipeline de lote, chame-o em todos os documentos, registe os bloqueadores por ficheiro, e encaminhe as falhas para uma fila que um humano olha. Isso é muito mais útil do que uma gravação que levanta exceção, porque os bloqueadores costumam agrupar-se: quarenta documentos a falhar pelo mesmo output intent em falta são uma correção, não quarenta

Escolher entre os perfis de arquivo

O PDF/E-1 é o alvo certo quando o entregável é documentação de engenharia com um ciclo de vida de revisões, e especificamente quando a coerência de cor de dispositivo importa porque o output vai para plotters e impressoras de grande formato. O PDF/A é o alvo certo quando o objetivo é a legibilidade a longo prazo de documentos em geral, e é o perfil com o apoio de validadores mais alargado. Os dois não são intercambiáveis, e um documento pode satisfazer um e falhar o outro

Diagrama de decisão do PDFlibPas comparando os perfis de arquivo PDF/E-1 e PDF/A para Delphi: PDF/E-1 para entregáveis de engenharia com ciclos de vida de revisões, cor de plotter e validação contratual sob o seu próprio namespace XMP com output intent ISO_PDFE1, PDF/A para legibilidade geral a longo prazo com o apoio de validadores mais alargado
Comece por quem valida o ficheiro no destino final: os perfis exigem garantias diferentes de identificação, metadados e cor, e um documento pode satisfazer um falhando o outro

Se está a escolher, comece por quem valida o ficheiro no destino final. As ferramentas de validação PDF/A estão por todo o lado, e o preflight correspondente no PDFlibPas está descrito em preflight PDF/A e PDF/UA. A validação PDF/E é mais especializada e costuma ser um requisito contratual e não uma predefinição. Quando um arquivo existente tem de ser elevado a um perfil para o qual nunca foi escrito, o caminho de reparação de metadados em converter para PDF/A com reparação de metadados é o padrão a seguir, e a mesma forma aplica-se aqui: identificar, reparar o que é seguro, recusar o resto com uma lista

O modo de autoria, o preflight de conteúdo limitado e a verificação de conformidade autónoma são todos distribuídos com a biblioteca PDF Delphi PDFlibPas, por isso um documento pode ser produzido sob o perfil e verificado de forma independente depois através de um caminho de código separado, que é o único arranjo em que vale a pena confiar para uma declaração de conformidade