Uma ferramenta de pré-voo em lote é um programa de console sem janela, apontado para uma pasta de PDFs, que valida cada um contra os padrões de conformidade que você nomeia e deixa para trás prova legível por máquina do que ele encontrou. Ninguém senta e o assiste. Ele roda às duas da manhã sob o cron ou o Agendador de Tarefas do Windows, ou como um portão em uma pipeline de CI, e a próxima pessoa a se importar com a sua saída é um agendador lendo um código de saída ou um auditor abrindo um relatório semanas depois. Isso muda o que "correto" significa. O motor de pré-voo do Componente PDFium, uma biblioteca PDF de código-fonte para Delphi, C++Builder, e Lazarus, torna as próprias chamadas de validação quase triviais. O trabalho que decide se a ferramenta ganha o seu sustento senta em torno dessas chamadas: qual perfil você verificou, o que o código de saída disse ao agendador, e se o relatório que teria pegado um erro ainda existe quando alguém vai procurar por ele
O contrato: o que um agendador pode realmente ver
Um executor de CI ou o Agendador de Tarefas do Windows vê exatamente duas coisas da sua ferramenta: o código de saída e quaisquer arquivos que ele tenha deixado para trás. Linhas de log, cores do console, saída de progresso: tudo isso é para um humano assistindo ao vivo, e às duas da manhã ninguém está. Então corrija o vocabulário do código de saída antes que você toque na API, e o mantenha chato:
0: todo arquivo obedeceu a todo perfil solicitado1: pelo menos um arquivo produziu descobertas (findings) de validação2: a própria ferramenta falhou em pelo menos um arquivo (entrada corrompida, bloqueio/lock, travamento/crash)
A distinção entre os códigos 1 e 2 é a que as equipes pulam e mais tarde se arrependerão. Um PDF corrupto que não abrirá não é uma falha de validação. Dobre-o para dentro do código 1 e um caminhão de scans danificados aparece nos seus dashboards como um colapso de conformidade repentino, enviando alguém para correr atrás de uma regressão de padrões que nunca aconteceu, quando a história real é um scanner quebrado a montante.
Mais dois itens pertencem ao contrato. O primeiro é um timeout por arquivo. Um PDF patológico, milhares de páginas com estruturas de objetos profundamente aninhadas, pode segurar uma única passagem de validação por minutos, e uma janela noturna não tem paciência para isso. Mate o trabalho daquele arquivo no prazo final, conte-o como uma falha de ferramenta, e mantenha o lote se movendo. O segundo é um diretório de quarentena: mova toda entrada que estourou o tempo ou inabríve para o lado em vez de deixá-la no lugar. Ao longo de alguns meses esse diretório acumula silenciosamente os piores documentos que os seus clientes reais enviam, e esse corpus vale mais para o teste de lançamento do que qualquer amostra sintética que você poderia escrever à mão
Escolhendo padrões, e por que o nível de conformidade importa
A enumeração TPdfPreflightStandard cobre as famílias que surgem na prática: ppsPdfA para a conformidade de arquivo ISO 19005, ppsPdfUa para acessibilidade ISO 14289, ppsPdfX para troca de impressão, mais ppsPdfE, ppsPdfR, e ppsPdfVT para trabalho de engenharia, raster e dados variáveis. Dentro de uma família o motor lê o nível de conformidade que o documento alega e o relata por padrão no ConformanceName do resultado. Nomear a família é raramente o suficiente, porque o nível é onde a real diferença vive. O PDF/A-2b promete reprodutibilidade visual e nada mais. O PDF/A-3a adiciona uma demanda para marcação de estrutura lógica e permite arquivos fonte embutidos, o que é uma barra muito mais difícil de se limpar para material escaneado que não tem nenhuma árvore de tags de forma alguma. Erre nisso em qualquer direção e o lote mente para você. Se a sua política de retenção realmente quer o PDF/A-2b mas você falha arquivos por faltarem tags de estrutura, o relatório se enche de descobertas que ninguém nunca vai corrigir. Aceite qualquer rótulo PDF/A sem verificar o nível e você assinará em baixo em documentos que atendem a uma barra mais fraca do que você prometeu. Mandatos de acessibilidade de compradores do governo cada vez mais empilham o PDF/UA no topo de tudo isso, o que não adiciona nenhum custo à corrida porque BuildPdfPreflightReport (da unidade FPdfPreflightReport) pega um conjunto de padrões:
Report := BuildPdfPreflightReport(Pdf, [ppsPdfA, ppsPdfUa]);
Uma chamada avalia ambos os padrões e devolve um único registro de relatório consolidado
Por que uma lista vazia de descobertas não é uma aprovação
O relatório enumera as descobertas por padrão, e uma lista de problemas vazia significa apenas "nenhum problema foi encontrado nos padrões que realmente rodaram." Essa é uma alegação mais estreita do que "o arquivo está em conformidade com o padrão com o qual você se importa," e a lacuna entre os dois é onde o pré-voo em lote apodrece silenciosamente. Um erro de digitação na configuração que derruba o ppsPdfA do conjunto produz exatamente a mesma lista de problemas vazia que um arquivo genuinamente limpo produz. Então trate o silêncio como suspeito. Ande pelo Report.Results e afirme duas coisas para cada padrão que você teve a intenção de verificar: que uma entrada de resultado para ele exista, e que a sua bandeira IsCompliant, apoiada por Status = pfsPass, seja verdadeira. Um trabalho noturno que equipara "sem descobertas" com "pronto para arquivo" sem nunca confirmar quais padrões foram avaliados é a maneira clássica que uma pasta de arquivos não conformes navega por meses, até que um auditor externo abra um com o veraPDF e o arquivo todo seja colocado em questão
Uma segunda armadilha se esconde no que uma descoberta sequer é. Cada TPdfPreflightIssue carrega um Code, uma Category, uma Description e uma Recommendation, e nomeia a regra que foi violada, não uma página ou um objeto. Isso é uma escolha de design com consequências para o loop de feedback. O relatório diz à equipe de produção qual classe de defeito existe, uma fonte não embutida ou um identificador XMP ausente, e encontrar o objeto ofensor específico é o trabalho da ferramenta de remediação a jusante, não do validador. Construa os seus consumidores de relatório contra os valores Code estáveis, nunca contra o texto de descrição legível por humanos, o qual pode ser reescrito entre as versões sem aviso prévio
Arquivos de relatório para máquinas e para a pessoa de sobreaviso (on call)
O registro de relatório escreve as mesmas descobertas em cinco formatos: SaveJsonToFile, SaveCsvToFile, SaveHtmlToFile, SaveTextToFile, e SaveMarkdownToFile, cada um com uma função de estilo ToJson correspondente quando você quer a string na memória em vez de no disco. Resista à vontade de escolher um. Escreva o JSON para o pipeline, para que a CI possa anexá-lo ao registro de trabalho e analisar os códigos de problema e os status por padrão sem extrair o texto. Escreva o HTML para o humano que é bipado, porque ele abre em qualquer navegador sem nenhuma ferramenta. Os dois juntos custam uma linha extra por arquivo e poupam ao seu engenheiro de sobreaviso a única pior tarefa no processamento em lote, que é fazer a engenharia reversa de um blob JSON bruto às duas da manhã para descobrir qual arquivo quebrou. Uma disciplina importa mais do que a escolha do formato: derive cada nome de relatório do nome do arquivo de entrada, nunca de um timestamp, ou duas corridas paralelas intercalarão os relatórios e você não poderá mais correspondê-los aos seus arquivos de entrada
Os limites de gravidade pertencem à configuração em vez de ao código. Uma anotação sem descrição alternativa é uma falha rígida para um portal de submissão PDF/UA e uma nota ignorável para um arquivo interno, no entanto, é a descoberta idêntica em ambos. Exponha um nível de falha por perfil para que a política possa mudar sem uma recompilação, e carimbe o nível que estava em vigor no próprio resumo do trabalho. No próximo trimestre ninguém vai se lembrar sob qual limite o lote de outubro passado rodou, e o resumo é o único lugar onde essa memória sobrevive
Isolando arquivos para que um PDF ruim não afunde o lote
procedure RunPreflightBatch(const InputDir, ReportDir: string;
out FilesWithFindings, ToolFailures: Integer);
var
SR: TSearchRec;
Pdf: TPdf;
Report: TPdfPreflightReport;
begin
FilesWithFindings := 0;
ToolFailures := 0;
if FindFirst(InputDir + '*.pdf', faAnyFile, SR) = 0 then
try
repeat
Pdf := TPdf.Create(nil); // fresh instance per file: no state bleed
try
try
Pdf.FileName := InputDir + SR.Name;
Pdf.Active := True;
if not Pdf.Active then // load failures are silent, not raised
raise EPdfError.Create('Cannot open ' + SR.Name);
Report := BuildPdfPreflightReport(Pdf, [ppsPdfA, ppsPdfUa]);
Report.SaveJsonToFile(ReportDir + ChangeFileExt(SR.Name, '.json'));
Report.SaveHtmlToFile(ReportDir + ChangeFileExt(SR.Name, '.html'));
if Report.TotalIssueCount > 0 then
Inc(FilesWithFindings);
except
on E: Exception do
begin
Inc(ToolFailures); // exit-code-2 territory, not a validation verdict
WriteLn(ErrOutput, SR.Name + ': ' + E.Message);
end;
end;
finally
Pdf.Free;
end;
until FindNext(SR) <> 0;
finally
FindClose(SR);
end;
end;
Três escolhas deliberadas vivem naquele loop. Um TPdf novo por arquivo garante que um documento que corrompe o estado do motor não possa envenenar os arquivos que o seguem. A verificação explícita do Active ganha o seu lugar porque Active := True engole os erros de carregamento em vez de levantá-los; deixe cair a guarda e um arquivo truncado segue à deriva para dentro da chamada de validação antes de falhar em algum lugar a jusante com uma mensagem enganosa. O try..except interno vive dentro do escopo por-arquivo de propósito, então uma única exceção sobe o contador de falhas e o loop continua. Você quer relatórios limpos para os 4.999 arquivos bons mesmo quando o arquivo 5.000 é triturado. E ambos os formatos de relatório são escritos no disco antes que o veredicto seja contabilizado, o que significa que a evidência sobrevive mesmo se um bug mais tarde na lógica do resumo contar errado
O mapeamento do código de saída então colapsa para algumas linhas no arquivo de projeto:
begin
RunPreflightBatch(ParamStr(1), ParamStr(2), Findings, Failures);
if Failures > 0 then
Halt(2)
else if Findings > 0 then
Halt(1);
// falling through exits with 0: every file conformed
end.
O que o pré-voo não fará por você
O motor detecta; ele não repara. Uma descoberta sobre uma fonte não embutida ou um espaço de cores dependente de dispositivo é uma ordem de trabalho para quem quer que produza os arquivos, e o validador não tem maneira de corrigi-la no lugar. Então planeje o loop de feedback deliberadamente. Os relatórios têm que pousar onde a equipe de produção realmente os leia, ou as mesmas descobertas reaparecerão toda noite até que alguém finalmente pergunte por que a taxa de conformidade nunca melhore. Também compensa fazer a verificação cruzada de uma amostra de veredictos contra um validador independente, veraPDF para o PDF/A ou o pré-voo do Acrobat para o PDF/X, antes que um auditor externo faça a verificação cruzada deles para você. Quando dois motores discordam em um arquivo de cliente real, aquele documento não é um incômodo; ele é exatamente o caso de regressão que o seu teste de lançamento estava perdendo. Guarde-o, nomeie-o e rode-o em cada build
Mais um pareamento vale a pena conhecer. O mesmo motor de validação direciona as verificações interativas em uma UI de revisão, então essa CLI sem cabeça e uma bancada de trabalho de revisão de entrada de PDF voltada para o analista podem compartilhar um vocabulário único de validação em vez de se afastarem com o tempo. E porque [ppsPdfA, ppsPdfUa] avalia a acessibilidade na mesma passagem, o lado PDF/UA do lote se alinha limpamente com o trabalho do lado do visualizador como construir um leitor PDF acessível no Delphi. Perfis, formatos de relatório e a API de pré-voo completa são documentados na página do produto para o Componente PDFium