Technical Article

Automatizar Verificações de Preflight PDF em Delphi com HotPDF

O ficheiro abre perfeitamente no seu computador. O Acrobat exibe-o, a pré-visualização de impressão parece correta e todas as páginas estão presentes. Depois é enviado para a tipografia ou para o sistema de arquivo que processa o seu lote mensal e é rejeitado: imagens RGB num trabalho em CMYK, ausência da chave /Trapped, uma intenção de saída (output intent) que não corresponde à prensa. Não havia nada de errado com o documento que fosse visível a olho nu. Estava incorreto em relação a um perfil, e o perfil foi verificado num local fora do seu controlo. Preflight é o nome dado a esta verificação na área de pré-impressão, e a questão principal é onde ela deve ocorrer quando os PDFs são gerados pelo seu próprio código Delphi, e não a partir do ambiente de trabalho de um designer.

O HotPDF não disponibiliza uma função de preflight para chamada direta. O componente inclui uma janela de relatório de preflight na sua demonstração de interface gráfica (GUI), mas não existe qualquer API associada que possa ser invocada por um serviço ou por um script de compilação, e sugerir o contrário apenas o faria procurar um método inexistente. Isto pode parecer uma lacuna, até perceber que, para ficheiros gerados por si, chamar um validador sobre o seu próprio output não faz sentido. O programador já controla todas as propriedades que um validador inspecionaria. A divisão útil passa por tornar o gerador incapaz de emitir um ficheiro incorreto e, em seguida, comprovar isso com uma ferramenta externa.

Por que razão valida o seu próprio output de forma diferente

O preflight tradicional pressupõe um ficheiro de origem externa. Este terá sido produzido por um designer, outra aplicação ou uma cadeia de edições desconhecida, e o utilizador inspeciona-o por não ter ideia do que está no seu interior. Um documento produzido pelo seu código não é um desconhecido. A incorporação de fontes, o espaço de cor, a intenção de saída (output intent), o bloco de metadados: o seu programa decidiu tudo isto milissegundos antes de o ficheiro ser gravado no disco. Inspecioná-lo posteriormente para descobrir as decisões que acabou de tomar é trabalho redundante. A abordagem mais eficiente é limitar essas opções para que um ficheiro não conforme nunca chegue a ser gerado.

Existe também uma questão de credibilidade para manter a validação externa. Uma biblioteca que valide o seu próprio output está a avaliar o seu próprio trabalho. Quando o sistema de arquivo de um cliente ou o RIP de uma tipografia rejeita o seu ficheiro, o argumento "o nosso componente diz que está correto" não tem valor. Mas o veredicto do veraPDF ou do Acrobat tem, pois a outra parte utiliza as mesmas ferramentas.

Tornar a conformidade numa definição, e não numa lista de verificação

A camada de prevenção resume-se à configuração. Defina PDFACompliance ou PDFXCompliance antes de BeginDoc e o HotPDF aplicará as regras correspondentes durante todo o processo de geração: incorpora fontes, monitoriza o uso de DeviceRGB e DeviceCMYK em relação à intenção de saída (output intent) declarada e rejeita funcionalidades proibidas pelo perfil. As contradições surgem em EndDoc, onde as validações de conformidade lançam exceções, em vez de permitirem a distribuição silenciosa de algo que irá falhar no destino. Após a gravação do ficheiro, as mesmas propriedades devolvem o que foi efetivamente aplicado, que é o dado essencial para o registo do seu pipeline:

// After EndDoc: record the enforced profiles with the run metadata
if Pdf.PDFACompliance <> '' then
  Log('Generated as PDF/A level ' + Pdf.PDFACompliance);
if Pdf.PDFXCompliance <> '' then
  Log('Generated as PDF/X profile ' + Pdf.PDFXCompliance);

Registe estas flags na mesma linha de log que o hash de dados de entrada e a versão do HotPDF. No dia em que um validador e o seu gerador discordarem sobre um ficheiro, essa linha revelará qual o modelo (template) que o produziu e qual a compilação da biblioteca que estava em execução, e o debate que consumiria uma tarde inteira resolve-se com um simples comando grep. As intenções de saída, perfis ICC e a etiquetagem associados a estas flags encontram-se detalhados no guia para a produção de PDF/A, PDF/X e PDF/UA com o HotPDF.

Uma barreira inicial económica para ficheiros não gerados por si

Nem todos os pipelines são puramente generativos. Os clientes carregam PDFs, os parceiros anexam-nos a emails. Submeter cada um desses ficheiros a um validador estrutural completo desperdiça tempo de fila com documentos que nem sequer conseguem ser abertos. A API Direct File do HotPDF lê o suficiente da estrutura do ficheiro para responder a "isto é mesmo um PDF utilizável" sem carregar toda a árvore de objetos, sendo ideal para falhar rapidamente (fail-fast):

function TriagePdf(Pdf: THotPDF; const FileName: string): Boolean;
var
  Handle, Pages: Integer;
begin
  Result := False;
  Handle := Pdf.DAOpenFileReadOnly(FileName, '');
  if Handle <= 0 then
    Exit;  // structurally unreadable: quarantine, do not validate
  try
    Pages := Pdf.DAGetPageCount(Handle);
    Result := Pages > 0;
  finally
    Pdf.DACloseFile(Handle);
  end;
end;

Dois factos sobre esta API determinam como a deve implementar. O atalho de poupança de memória aplica-se apenas a inputs não encriptados; se fornecer uma palavra-passe ao DAOpenFileReadOnly, este reverte silenciosamente para uma análise completa, pelo que um ficheiro que saiba estar encriptado deve ser submetido a DecryptFile para gerar uma cópia simples de trabalho antes da triagem. E o DAGetPageCount não tem valor num handle que não abriu corretamente, pelo que a validação do handle deve ser rigorosa e um resultado não positivo traduz-se numa rejeição imediata, e não numa nova tentativa. Mais padrões destes estão disponíveis no artigo sobre a API Direct File para fluxos de trabalho com PDFs grandes.

veraPDF, executado como parte da compilação

Para qualquer ficheiro que declare como PDF/A ou PDF/UA, o veraPDF é o validador ideal para integrar no fluxo. Funciona em modo headless, processa lotes, emite resultados em XML ou JSON e identifica cada falha pela cláusula ISO correspondente. Deste modo, uma falha de regra contra a cláusula 6.2.2 da ISO 19005-1 remete diretamente para uma definição do gerador, evitando estimativas. O controlo a partir do Delphi realiza-se através do controlo básico de processos:

function RunVeraPdf(const PdfFile, ReportFile: string): Cardinal;
var
  Cmd: string;
  SI: TStartupInfo;
  PI: TProcessInformation;
begin
  Cmd := Format('cmd /c verapdf.bat --format xml "%s" > "%s"',
    [PdfFile, ReportFile]);
  FillChar(SI, SizeOf(SI), 0);
  SI.cb := SizeOf(SI);
  if not CreateProcess(nil, PChar(Cmd), nil, nil, False,
      CREATE_NO_WINDOW, nil, nil, SI, PI) then
    RaiseLastOSError;
  try
    WaitForSingleObject(PI.hProcess, 120000);  // bound the wait per file
    GetExitCodeProcess(PI.hProcess, Result);
  finally
    CloseHandle(PI.hThread);
    CloseHandle(PI.hProcess);
  end;
end;

Esse limite de tempo (timeout) justifica-se por completo. Um ficheiro corrompido pode conduzir qualquer analisador (parser) a um bloqueio irreversível, e uma espera indefinida dentro de um processo da fila arrastará todo o processamento subsequente. Limite a espera, defina um código de erro específico para a expiração do tempo e encaminhe o ficheiro para análise humana. Ao ler o resultado, analise o XML em busca de identificadores de regras, e não de texto legível. Os IDs das regras sobrevivem a atualizações do validador, ao contrário da formulação das mensagens. Um código estável permite que um engenheiro de suporte pesquise ocorrências em problemas registados anteriormente.

A forma como executa o lote é tão importante como a aprovação de cada ficheiro. Utilize um processo por ficheiro, e não um por lote, para que um input problemático custe apenas o timeout desse ficheiro. Limite o número de processos de validação ao número de núcleos (cores) da CPU, já que a geração do relatório XML consome muito processamento e a sobrealocação apenas degrada o rendimento. Defina também um limite de tamanho na receção, pois um livro digitalizado de dois gigabytes bloqueará a fila de espera, por muito paciente que o analisador seja. Nada disto é preflight no sentido estrito. É a diferença entre um mecanismo de validação que suporta o volume do fecho do mês e um que é desativado na primeira noite em que bloqueia o pipeline às 2 da manhã.

O PDF/X é o ponto fraco nesta abordagem. O veraPDF não o valida, pelo que o controlo prático continua a ser o Preflight do Acrobat com o perfil ISO 15930 especificado pela sua tipografia. O Acrobat exige intervenção humana, o que implica amostragem em vez de cobertura total: o primeiro ficheiro gerado a partir de um novo modelo, acrescido de uma pequena amostra aleatória de cada lote, enquanto a barreira automatizada trata de tudo o que pode ser processado sem operador. Uma validação por amostragem que realmente seja executada supera uma automação total que nunca chegue a ser concluída.

Um relatório que ainda desejará ler daqui a um ano

Uma barreira de preflight compensa em dobro. Primeiro ao deter um ficheiro incorreto à entrada, e mais tarde quando alguém questionar por que razão um determinado documento foi aprovado. Este segundo cenário é o que deve ditar o formato do relatório, pois é nele que um relatório simples o deixará sem respostas. Para cada ficheiro verificado, guarde o hash do input, as flags de conformidade e a versão da biblioteca do gerador, o nome e versão do validador, o perfil de validação utilizado, o resultado de aprovação ou falha, e os IDs das regras violadas acompanhados dos números das páginas. Guarde o relatório junto ao ficheiro que este descreve. Se o colocar num sistema separado, esse sistema será desativado muito antes de o arquivo que documenta deixar de ser necessário.

As exceções também devem ser registadas por escrito. Quando um cliente insiste em enviar um ficheiro rejeitado pelo validador, a solução não passa por relaxar as regras para todos. Registe quem aprovou o ficheiro, com que justificação e até que data, anexando essa autorização especial (waiver) ao relatório respetivo. Uma autorização com um nome associado e prazo de validade é uma decisão assumida por alguém. Uma verificação comentada no código sob o pretexto de ser "temporária" é apenas um incidente com data marcada.

Outro hábito valioso: quando um ficheiro falhar, copie-o para uma pasta de regressão identificada antes de qualquer intervenção. Praticamente todos os problemas de preflight dignos de depuração remontam a um input específico, e as equipas que conservam estes inputs corrigem as reincidências em menos de uma hora, em vez de esperarem que voltem a surgir em produção. As propriedades de conformidade e a API Direct File apresentadas aqui integram o HotPDF Component para Delphi e C++Builder, cuja documentação detalha todas as chamadas.