Uno strumento di preflight in batch è un programma da console senza finestra, puntato su una cartella di PDF, che valida ciascuno di essi rispetto agli standard di conformità che indicate e lascia dietro di sé una prova leggibile dalle macchine di ciò che ha trovato. Nessuno sta lì a guardarlo. Gira alle due di notte sotto cron o Utilità di pianificazione di Windows, oppure come gate in una pipeline di CI, e la persona successiva a interessarsi del suo risultato è uno scheduler che legge un codice di uscita oppure un auditor che apre un report settimane dopo. Questo cambia il significato di corretto. Il motore di preflight di PDFium Component, una libreria PDF con sorgenti per Delphi, C++Builder e Lazarus, rende quasi banali le chiamate di validazione in sé. Il lavoro che decide se lo strumento si ripaga sta attorno a quelle chiamate: quale profilo avete controllato, che cosa ha detto il codice di uscita allo scheduler, e se il report che avrebbe intercettato un errore esiste ancora quando qualcuno va a cercarlo
Il contratto: che cosa vede davvero uno scheduler
Un runner di CI o Utilità di pianificazione di Windows vede esattamente due cose del vostro strumento: il codice di uscita e i file che ha lasciato dietro di sé. Righe di log, colori sulla console, output di avanzamento: tutto quello serve a un essere umano che guarda in diretta, e alle due di notte non c'è nessuno. Quindi fissate il vocabolario dei codici di uscita prima di toccare l'API, e tenetelo noioso:
0: ogni file era conforme a ogni profilo richiesto1: almeno un file ha prodotto riscontri di validazione2: lo strumento stesso ha fallito su almeno un file (input corrotto, lock, crash)
La distinzione fra i codici 1 e 2 è quella che i team saltano e poi rimpiangono. Un PDF corrotto che non si apre non è un fallimento di validazione. Mettetelo insieme al codice 1 e un camion di scansioni danneggiate comparirà nelle vostre dashboard come un crollo improvviso della conformità, mandando qualcuno a inseguire una regressione sugli standard che non è mai avvenuta, quando la storia vera è uno scanner rotto a monte
Altri due elementi appartengono al contratto. Il primo è un timeout per file. Un PDF patologico, migliaia di pagine con strutture di oggetti profondamente annidate, può trattenere una singola passata di validazione per minuti, e una finestra notturna non ha pazienza per questo. Terminate il lavoro su quel file alla scadenza, contatelo come fallimento dello strumento e tenete il batch in movimento. Il secondo è una directory di quarantena: spostate da parte ogni input scaduto o non apribile invece di lasciarlo dov'è. Nel giro di qualche mese quella directory accumula in silenzio i peggiori documenti che i vostri clienti reali inviano, e quel corpus vale per i test di rilascio più di qualsiasi campione sintetico che potreste scrivere a mano
Scegliere gli standard, e perché il livello di conformità conta
L'enumerazione TPdfPreflightStandard copre le famiglie che si incontrano nella pratica: ppsPdfA per la conformità di archiviazione ISO 19005, ppsPdfUa per l'accessibilità ISO 14289, ppsPdfX per lo scambio di stampa, più ppsPdfE, ppsPdfR e ppsPdfVT per lavori di ingegneria, raster e dati variabili. Dentro una famiglia il motore legge il livello di conformità dichiarato dal documento e lo riporta per standard nel campo ConformanceName del risultato. Nominare la famiglia raramente basta, perché è nel livello che sta la differenza vera. PDF/A-2b promette riproducibilità visiva e nulla di più. PDF/A-3a aggiunge la richiesta di taggatura della struttura logica e consente file sorgente incorporati, un'asticella molto più difficile da superare per materiale scansionato che non ha alcun albero di tag. Sbagliate in una direzione o nell'altra e il batch vi mente. Se la vostra politica di conservazione vuole in realtà PDF/A-2b ma bocciate i file per tag di struttura mancanti, il report si riempie di riscontri che nessuno correggerà mai. Accettate qualsiasi etichetta PDF/A senza controllare il livello e approvate documenti che soddisfano un requisito più debole di quello promesso. I mandati di accessibilità degli acquirenti pubblici impilano sempre più spesso PDF/UA sopra tutto questo, il che non aggiunge costo all'esecuzione perché BuildPdfPreflightReport (dalla unit FPdfPreflightReport) accetta un insieme di standard:
Report := BuildPdfPreflightReport(Pdf, [ppsPdfA, ppsPdfUa]);
Una sola chiamata valuta entrambi gli standard e restituisce un unico record di report consolidato
Perché un elenco di riscontri vuoto non è una promozione
Il report enumera i riscontri per standard, e un elenco di problemi vuoto significa soltanto nessun problema trovato negli standard che sono stati effettivamente eseguiti. È un'affermazione più stretta di il file è conforme allo standard che vi interessa, e lo spazio fra le due è il punto in cui il preflight in batch marcisce in silenzio. Un errore di battitura nella configurazione che toglie ppsPdfA dall'insieme produce esattamente lo stesso elenco vuoto di un file davvero pulito. Quindi considerate sospetto il silenzio. Percorrete Report.Results e verificate due cose per ogni standard che intendevate controllare: che una voce di risultato per esso esista, e che il suo flag IsCompliant, sostenuto da Status = pfsPass, sia vero. Un lavoro notturno che equipara nessun riscontro a pronto per l'archivio senza mai confermare quali standard siano stati valutati è il modo classico in cui una cartella di file non conformi passa liscia per mesi, finché un auditor esterno non ne apre uno con veraPDF e l'intero archivio finisce sotto accusa
Una seconda trappola si nasconde in che cosa sia davvero un riscontro. Ogni TPdfPreflightIssue porta con sé un Code, una Category, una Description e una Recommendation, e nomina la regola violata, non una pagina o un oggetto. È una scelta di progetto con conseguenze sul ciclo di feedback. Il report dice al team che produce i file quale classe di difetto esiste, un font non incorporato o un identificatore XMP mancante, e trovare lo specifico oggetto colpevole è compito dello strumento di rimedio a valle, non del validatore. Costruite i consumatori del vostro report sui valori stabili di Code, mai sul testo leggibile della descrizione, che può essere riformulato fra un rilascio e l'altro senza preavviso
File di report per le macchine e per chi è di turno
Il record di report scrive gli stessi riscontri in cinque formati: SaveJsonToFile, SaveCsvToFile, SaveHtmlToFile, SaveTextToFile e SaveMarkdownToFile, ciascuno con una funzione corrispondente in stile ToJson quando volete la stringa in memoria anziché su disco. Resistete alla tentazione di sceglierne uno solo. Scrivete JSON per la pipeline, così la CI può allegarlo al record del job e interpretare i codici dei problemi e gli stati per standard senza raschiare testo. Scrivete HTML per la persona che riceve la chiamata, perché si apre in qualsiasi browser senza alcun strumento. I due insieme costano una riga in più per file e risparmiano al vostro reperibile il compito peggiore dell'elaborazione in batch, cioè decifrare un blob JSON grezzo alle due di notte per scoprire quale file si è rotto. Una disciplina conta più della scelta del formato: ricavate il nome di ogni report dal nome del file di input, mai da un timestamp, altrimenti due esecuzioni parallele intrecceranno report che non riuscirete più a ricondurre ai loro input
Le soglie di gravità appartengono alla configurazione, non al codice. Un'annotazione senza descrizione alternativa è un fallimento netto per un portale di invio PDF/UA e una nota trascurabile per un archivio interno, eppure è il riscontro identico in entrambi i casi. Esponete un livello di fallimento per profilo così che la politica possa cambiare senza ricompilare, e imprimete nel riepilogo del job il livello che era in vigore. Il trimestre prossimo nessuno ricorderà con quale soglia sia girato il batch dello scorso ottobre, e il riepilogo è l'unico posto in cui quella memoria sopravvive
Isolare i file perché un solo PDF difettoso non affondi il batch
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); // istanza nuova per file: nessuno stato residuo
try
try
Pdf.FileName := InputDir + SR.Name;
Pdf.Active := True;
if not Pdf.Active then // i fallimenti di caricamento sono silenziosi
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); // territorio del codice 2, non un verdetto di validazione
WriteLn(ErrOutput, SR.Name + ': ' + E.Message);
end;
end;
finally
Pdf.Free;
end;
until FindNext(SR) <> 0;
finally
FindClose(SR);
end;
end;
In quel ciclo vivono tre scelte deliberate. Un TPdf nuovo per ogni file garantisce che un documento che corrompe lo stato del motore non possa avvelenare i file che lo seguono. Il controllo esplicito su Active si guadagna il proprio posto perché Active := True inghiotte gli errori di caricamento invece di sollevarli; togliete quella guardia e un file troncato prosegue fino alla chiamata di validazione per poi fallire da qualche parte a valle con un messaggio fuorviante. Il try..except interno sta dentro lo scope del singolo file di proposito, così una sola eccezione incrementa il contatore dei fallimenti e il ciclo prosegue. Volete report puliti per i 4.999 file buoni anche quando il file numero 5.000 è a brandelli. Ed entrambi i formati di report vengono scritti su disco prima che il verdetto sia contato, il che significa che la prova sopravvive anche se un bug successivo nella logica di riepilogo conta male
La mappatura sui codici di uscita si riduce poi a poche righe nel file di progetto:
begin
RunPreflightBatch(ParamStr(1), ParamStr(2), Findings, Failures);
if Failures > 0 then
Halt(2)
else if Findings > 0 then
Halt(1);
// arrivare in fondo esce con 0: ogni file era conforme
end.
Che cosa il preflight non farà per voi
Il motore rileva; non ripara. Un riscontro su un font non incorporato o su uno spazio colore dipendente dal dispositivo è un ordine di lavoro per chi produce i file, e il validatore non ha modo di correggerlo sul posto. Quindi pianificate il ciclo di feedback in modo deliberato. I report devono arrivare dove il team di produzione li legge davvero, altrimenti gli stessi riscontri ricompariranno ogni notte finché qualcuno non chiederà finalmente perché il tasso di conformità non migliora mai. Conviene anche verificare un campione di verdetti con un validatore indipendente, veraPDF per PDF/A o il preflight di Acrobat per PDF/X, prima che lo faccia un auditor esterno al posto vostro. Quando due motori non concordano su un file reale di un cliente, quel documento non è una scocciatura; è esattamente il caso di regressione che mancava ai vostri test di rilascio. Conservatelo, dategli un nome ed eseguitelo a ogni build
Vale la pena conoscere un ultimo abbinamento. Lo stesso motore di validazione guida i controlli interattivi in una interfaccia di revisione, quindi questa CLI headless e un workbench di revisione dei PDF in ingresso rivolto agli analisti possono condividere un unico vocabolario di validazione invece di divergere nel tempo. E poiché [ppsPdfA, ppsPdfUa] valuta l'accessibilità nella stessa passata, il lato PDF/UA del batch si allinea in modo pulito con il lavoro sul viewer come costruire un reader PDF accessibile in Delphi. Profili, formati di report e l'API di preflight completa sono documentati nella pagina di prodotto di PDFium Component