Uma ferramenta de preflight em lote (batch preflight tool) é um programa de consola sem interface gráfica que, apontado para uma pasta de ficheiros PDF, valida cada um deles em relação aos padrões de conformidade que definir, deixando um registo legível por máquina com o que encontrou. Ninguém o acompanha em tempo real. Executa-se às duas da manhã através de um cron ou do Agendador de Tarefas do Windows, ou como uma etapa num fluxo de trabalho de CI, e a pessoa seguinte a interessar-se pelo resultado será um agendador de tarefas a ler um código de saída (exit code) ou um auditor a analisar um relatório semanas mais tarde. Isto altera a definição de "correto". O motor de preflight do PDFium Component, uma biblioteca PDF com código-fonte para Delphi, C++Builder e Lazarus, torna as chamadas de validação simples. O trabalho que decide se a ferramenta é realmente útil reside no desenvolvimento em torno dessas chamadas: que perfil validou, o que o código de saída comunicou ao agendador de tarefas e se o relatório que detetaria um erro continua a existir quando alguém o procura.
O contrato: o que um agendador de tarefas consegue ver
Um executador de CI ou o Agendador de Tarefas do Windows veem exatamente duas coisas a partir da sua ferramenta: o código de saída e os ficheiros que esta deixou para trás. Linhas de registo, cores de consola, mensagens de progresso: tudo isso destina-se a um humano que acompanha a execução ao vivo, e às duas da manhã ninguém está a assistir. Portanto, defina o vocabulário do código de saída antes de mexer na API e mantenha-o simples:
0: todos os ficheiros cumprem todos os perfis solicitados1: pelo menos um ficheiro produziu inconformidades de validação2: a própria ferramenta falhou em pelo menos um ficheiro (entrada corrompida, bloqueio de ficheiro, falha crítica)
A distinção entre os códigos 1 e 2 é o detalhe que as equipas ignoram e mais tarde lamentam. Um PDF corrompido que não abre não constitui uma falha de validação. Se o incluir no código 1, uma grande quantidade de digitalizações danificadas aparecerá nos seus painéis de controlo como um colapso repentino de conformidade, enviando alguém a investigar uma perda de padrões que nunca existiu, quando a verdadeira causa é um digitalizador com problemas na origem.
Mais dois elementos pertencem a este contrato. O primeiro é um limite de tempo (timeout) por ficheiro. Um PDF problemático, com milhares de páginas e estruturas de objetos complexas, pode reter uma única validação durante minutos, e uma janela de execução noturna não tem tolerância para isso. Termine o processo desse ficheiro no limite do tempo, conte-o como uma falha da ferramenta e continue a executar o lote. O segundo é um diretório de quarentena: mova cada ficheiro que excedeu o tempo limite ou que não pôde ser aberto para o lado, em vez de o deixar no local de origem. Ao longo de alguns meses, esse diretório acumulará os piores documentos enviados pelos seus clientes reais, e esse conjunto de ficheiros é mais valioso para testes do que qualquer amostra sintética criada manualmente.
Escolher padrões e a importância do nível de conformidade
A enumeração TPdfPreflightStandard abrange as famílias que surgem na prática: ppsPdfA para conformidade de arquivo ISO 19005, ppsPdfUa para acessibilidade ISO 14289, ppsPdfX para intercâmbio de impressão, além de ppsPdfE, ppsPdfR e ppsPdfVT para engenharia, rasterização e dados variáveis. Numa família, o motor lê o nível de conformidade que o documento declara e reporta-o por padrão no campo ConformanceName do resultado. Identificar a família raramente é suficiente, porque o nível é onde reside a diferença real. O PDF/A-2b promete reprodutibilidade visual e nada mais. O PDF/A-3a acrescenta uma exigência de marcação de estrutura lógica (structure tagging) e permite ficheiros de origem incorporados, o que é um obstáculo muito maior para material digitalizado que não possui qualquer árvore de etiquetas. Ignore isto em qualquer direção e a análise em lote transmitirá informações erradas. Se a sua política de retenção exige PDF/A-2b mas rejeita ficheiros por falta de etiquetas de estrutura, o relatório encher-se-á de problemas que ninguém resolverá. Aceite qualquer etiqueta PDF/A sem verificar o nível de conformidade e estará a avaliar documentos que cumprem requisitos mais fracos do que os prometidos. Os mandatos de acessibilidade dos compradores governamentais adicionam cada vez mais o PDF/UA sobre tudo isto, o que não acrescenta custo à execução porque a função BuildPdfPreflightReport (da unidade FPdfPreflightReport) recebe um conjunto de padrões:
Report := BuildPdfPreflightReport(Pdf, [ppsPdfA, ppsPdfUa]);
Uma chamada avalia ambos os padrões e devolve um único relatório consolidado.
Por que uma lista de conclusões vazia não significa conformidade
O relatório enumera as conclusões por padrão, e uma lista de problemas vazia significa apenas "nenhum problema encontrado nos padrões que foram realmente executados". Esta é uma afirmação mais estreita do que "o ficheiro está em conformidade com o padrão pretendido", e essa diferença é onde o preflight em lote se degrada silenciosamente. Um erro de configuração que remova ppsPdfA do conjunto produz exatamente a mesma lista vazia de problemas que um ficheiro limpo. Por isso, trate o silêncio com suspeita. Percorra o Report.Results e valide duas coisas para cada padrão que pretendia verificar: se existe uma entrada de resultado para o mesmo e se o seu sinalizador IsCompliant, apoiado por Status = pfsPass, é verdadeiro. Um trabalho noturno que equipare "sem conclusões" a "pronto para arquivo" sem confirmar que padrões foram avaliados é o caminho clássico para que ficheiros não conformes passem despercebidos durante meses, até que um auditor externo abra um com veraPDF e coloque todo o arquivo em causa.
Uma segunda armadilha esconde-se no que é uma conclusão (finding). Cada TPdfPreflightIssue carrega um Code, uma Category, uma Description e uma Recommendation, e identifica a regra violada, não uma página ou um objeto. Trata-se de uma decisão de design com consequências no ciclo de feedback. O relatório informa a equipa produtora sobre qual a classe de defeito que existe, como uma fonte não incorporada ou um identificador XMP em falta, e encontrar o objeto infrator específico é tarefa do fluxo de trabalho posterior e não do validador. Desenvolva os seus leitores de relatórios com base nos valores estáveis de Code e nunca com base no texto de descrição humana, que pode ser reformulado entre versões sem aviso prévio.
Ficheiros de relatório para computadores e para o técnico de prevenção
O registo de relatório grava as conclusões em cinco formatos: SaveJsonToFile, SaveCsvToFile, SaveHtmlToFile, SaveTextToFile e SaveMarkdownToFile, cada um com uma função ToJson correspondente quando necessita da string em memória. Evite escolher apenas um. Grave em JSON para o fluxo de automação, permitindo ao CI anexar o relatório ao registo da tarefa e analisar códigos de problemas e estados por padrão sem ler texto bruto. Grave em HTML para o técnico que recebe o alerta, pois abre-se em qualquer navegador sem qualquer ferramenta instalada. Juntos, os dois formatos representam uma linha extra de código por ficheiro e poupam ao engenheiro a pior tarefa no processamento em lote: decifrar um JSON bruto às duas da manhã para saber qual o ficheiro que causou a falha. Uma disciplina importa mais do que a escolha do formato: derive o nome de cada relatório a partir do nome do ficheiro de entrada, e nunca a partir de uma marca de tempo, caso contrário duas execuções paralelas misturarão relatórios que já não conseguirá associar às respetivas origens.
Os limiares de gravidade devem residir na configuração e não no código. Uma anotação sem descrição alternativa constitui uma falha grave para um portal de submissão PDF/UA e uma nota ignorável para um arquivo interno, embora se trate do mesmo problema. Disponibilize um nível de rejeição por perfil para que a política possa mudar sem necessidade de recompilar, e registe o nível que estava em vigor no próprio resumo da tarefa. No próximo trimestre, ninguém se lembrará de qual o limiar sob o qual o lote correu em outubro, e o resumo é o único local onde essa informação subsiste.
Isolar os ficheiros para que um PDF danificado não comprometa 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 residem nesse ciclo. Uma instância nova de TPdf por ficheiro garante que um documento que corrompa o estado do motor não contamine os ficheiros seguintes. A verificação explícita de Active é necessária porque Active := True absorve os erros de carregamento em vez de os lançar; retire esta verificação e um ficheiro truncado prosseguirá para a chamada de validação antes de falhar em algum ponto posterior com uma mensagem enganadora. O bloco try..except interno reside propositadamente no âmbito do ficheiro, pelo que uma única exceção incrementa o contador de falhas e o ciclo prossegue. Pretende obter relatórios limpos para os 4999 ficheiros corretos, mesmo que o ficheiro 5000 esteja corrompido. E ambos os formatos de relatório são gravados no disco antes do veredicto ser contabilizado, o que garante que a evidência sobrevive mesmo se um erro posterior na lógica de resumo falhar na contagem.
O mapeamento do código de saída resume-se então a algumas linhas no ficheiro do 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 preflight não fará por si
O motor deteta; não repara. Uma conclusão sobre uma fonte não incorporada ou um espaço de cores dependente do dispositivo constitui uma instrução de trabalho para quem produz os ficheiros, e o validador não tem forma de a corrigir no local. Planeie o fluxo de feedback de forma deliberada. Os relatórios devem ser entregues onde a equipa de produção os consiga ler, caso contrário as mesmas inconformidades reaparecerão todas as noites até que alguém finalmente pergunte por que a taxa de conformidade nunca melhora. Também compensa verificar uma amostra de decisões contra um validador independente, veraPDF para PDF/A ou o preflight do Acrobat para PDF/X, antes que um auditor externo o faça. Quando dois motores divergem num ficheiro de cliente real, esse documento não constitui um problema; é precisamente o caso de regressão que faltava nos seus testes. Guarde-o, atribua-lhe um nome e execute-o em cada compilação.
Mais uma combinação que vale a pena conhecer. O mesmo motor de validação alimenta as verificações interativas numa interface de utilizador de revisão, pelo que esta CLI sem interface gráfica e um analista que utilize o painel de receção e análise de PDF podem partilhar um vocabulário de validação único em vez de divergirem ao longo do tempo. E como a validação [ppsPdfA, ppsPdfUa] avalia a acessibilidade na mesma passagem, o lado PDF/UA do lote alinha-se perfeitamente com o trabalho visual como construir um leitor de PDF acessível em Delphi. Os perfis, formatos de relatório e a API de preflight completa encontram-se documentados na página do produto para o PDFium Component.