Artigo Técnico

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

O PDF/E-1 é o perfil de arquivamento para documentos de engenharia, e o PDFlibPas o implementa como um modo de autoria que você liga com SetPDFEMode mais um preflight com limites que lê os content streams operador por operador. O perfil não é um PDF/A com outro rótulo: ele tem namespace de identificação próprio, exigência própria de metadados de ciclo de vida, e uma regra que torna a validação de conteúdo mais estrita que a de qualquer perfil de arquivamento que você já viu

Entregáveis de engenharia são a razão de o perfil existir. Um conjunto de desenhos que precisa continuar legível e comprovadamente inalterado daqui a vinte anos, com um histórico de revisões que sobrevive, e com cores que significam a mesma coisa no plotter do prédio ao lado. Esses requisitos produzem uma especificação cujas exigências ficam em boa parte fora do conteúdo da página, em metadados e gerenciamento de cor, que é exatamente onde um gerador de PDF genérico 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. Ela usa um namespace XMP distinto, http://www.aim.org/pdfe/ns/id/, e o valor da versão precisa aparecer em dois lugares: como entrada de informações do documento e como propriedade XMP qualificada pelo namespace. Emitir só a propriedade XMP, ou só a entrada de informações, produz um arquivo que carrega a intenção e falha na validação

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

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

Porque os espaços de cor se escondem em dicionários de recursos que uma varredura no nível da página nunca alcança. O PDF/E-1 trata DeviceRGB e DeviceCMYK como famílias mutuamente exclusivas para um documento, então validar o perfil significa conhecer todo espaço de cor de dispositivo que qualquer coisa no arquivo usa. Um form XObject tem recursos próprios. Um pattern tem, 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ó confere os recursos da página de nível superior aprova um documento que usa as duas famílias

A varredura, portanto, registra os espaços de cor enquanto percorre páginas, forms, imagens e patterns numa travessia única, e só então decide se o documento é coerente e se o output intent casa. O mesmo raciocínio guia a arquitetura de preflight em geral: travessia parcial produz falso positivo, e um falso positivo numa checagem de conformidade é pior que nenhuma checagem, porque fica registrado 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 dia a cada save.
    // Pergunte antes de salvar se o documento passaria pelo próprio gate
    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;

Metadados de ciclo de vida são uma obrigação a cada save

O PDF/E-1 pede mais que um identificador de documento. O conjunto mínimo inclui o identificador de documento de media management, um identificador de versão, uma rendition class, hora de criação, hora de modificação, hora dos metadados e um título. Isso é um vocabulário de rastreamento 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 esses campos não podem ser definidos na criação do documento. Se a hora de modificação é escrita quando você liga o modo e o documento é editado depois, o snapshot XMP e o estado real do documento se separaram, e um validador que os compara reporta uma inconsistência que ninguém planejou. O modo de autoria, portanto, sincroniza os campos imediatamente antes de cada save, para os metadados descreverem os bytes prestes a ser escritos, e não os bytes que existiam quando o modo foi ligado

Esse é um princípio geral para metadados de conformidade e vale ser enunciado separado do PDF/E: metadado derivado pertence ao caminho de save, não ao de edição. Todo campo calculado a partir do estado do documento tem de ser recalculado no momento em que o estado é congelado, ou é um cache sem invalidação

Diagrama PDF/E-1 do PDFlibPas da varredura de cor de dispositivo no documento inteiro, que percorre os dicionários de recursos de página, form XObject, tiling pattern e imagem coletando 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 save para o snapshot XMP bater com os bytes prestes a serem escritos
A coerência de cor só pode ser julgada depois que uma travessia alcança todos os dicionários de recursos, e o metadado de ciclo de vida derivado é recalculado no momento em que o estado do documento é congelado, não quando o modo é ligado

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

O PDF/E-1 não permite que os operadores de seção de compatibilidade absorvam conteúdo desconhecido. No PDF comum, BX e EX delimitam uma região em que o consumidor deve ignorar operadores que não reconhece, que é a válvula de escape que deixa o produtor emitir construções mais novas sem quebrar leitores antigos. Sob PDF/E-1 essa válvula está fechada, então qualquer operador que o preflight não reconhecer é reportado incondicionalmente, esteja ou não dentro de uma seção de compatibilidade

O efeito sobre um validador é significativo. Ele não pode pular regiões que não entende, o que significa que o parser de operandos tem de parsear de verdade cada operador de cada content stream. É aqui que os limites entram. 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 ajuste de performance. Um arquivo hostil ou simplesmente quebrado pode apresentar um grafo de objetos com ciclos ou uma profundidade de aninhamento que transforma um validador recursivo em um stack overflow, e os limites são o que impede uma passada de validação de virar um vetor de denial-of-service. A mesma postura defensiva está descrita em parse de PDFs não confiáveis com segurança

// Validação standalone de um arquivo que você não produziu, sem
// carregá-lo 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 gate de save conserta e o que ele recusa

O gate divide o trabalho em dois estágios, e a divisão em si é uma ideia de design aproveitável. Primeiro ele normaliza o que é reparável com segurança: os print flags de anotações, os flags no-zoom e no-rotate de anotações de texto, e o flag de geração de aparência no dicionário do form. São configurações com um único valor correto sob o perfil e sem conteúdo informacional, então corrigi-las em silêncio é o certo, e recusar por causa delas seria pedantismo

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

Diagrama do gate de save PDF/E-1 do PDFlibPas para Delphi mostrando o preflight com limites que varre cada operador de content stream sob limites de aninhamento de 128 níveis, um milhão de objetos e 64 MiB, repara em silêncio os flags de impressão, zoom e rotação de anotações, recusa versão errada, identificação, criptografia, output intent, cor de dispositivo ou conteúdo dinâmico de formulário, e reporta os blockers via GetPDFEDiagnostics
O gate repara em silêncio só o que não carrega informação, recusa toda restrição que um reparo distorceria, e transforma a recusa numa lista de blockers via GetPDFEDiagnostics antes que qualquer byte chegue ao disco

Ler os diagnósticos via GetPDFEDiagnostics antes de salvar transforma essa recusa numa lista acionável em vez de uma operação que falhou. Numa pipeline em lote, chame em cada documento, registre os blockers por arquivo, e encaminhe as falhas para uma fila que uma pessoa olha. Isso é bem mais útil que um save que levanta exceção, porque os blockers costumam se agrupar: quarenta documentos falhando pelo mesmo output intent ausente é um conserto, não quarenta

Escolhendo entre os perfis de arquivamento

O PDF/E-1 é o alvo certo quando o entregável é documentação de engenharia com 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 é legibilidade de longo prazo de documentos em geral, e é o perfil com o suporte de validadores mais amplo. Os dois não são intercambiáveis, e um documento pode satisfazer um e falhar no outro

Diagrama de decisão do PDFlibPas comparando os perfis de arquivamento 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 seu namespace XMP próprio com output intent ISO_PDFE1, PDF/A para legibilidade geral de longo prazo com o suporte de validadores mais amplo
Comece por quem valida o arquivo na ponta final: os perfis exigem garantias diferentes de identificação, metadados e cor, e um documento pode satisfazer um e falhar no outro

Se você está escolhendo, comece por quem valida o arquivo na ponta final. Ferramentas de validação PDF/A existem aos montes, 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 exigência contratual, não padrão. Quando um arquivo existente precisa ser elevado a um perfil para o qual nunca foi escrito, o caminho de reparo de metadados em conversão para PDF/A com reparo de metadados é o padrão a seguir, e a mesma forma vale aqui: identificar, reparar o que é seguro, recusar o resto com uma lista

Modo de autoria, preflight de conteúdo com limites e a checagem de conformidade standalone vêm todos com a biblioteca PDF PDFlibPas para Delphi, então um documento pode ser produzido sob o perfil e verificado depois de forma independente por um caminho de código separado, que é o único arranjo em que vale a pena confiar para uma reivindicação de conformidade