Un bureau de numérisation pour lequel j'ai été consulté exécutait chaque nuit un job qui marquait des milliers d'enregistrements numérisés comme « prêts pour l'archive ». Six mois plus tard, un auditeur externe a échantillonné l'archive avec veraPDF et trouvé des violations PDF/A dans des fichiers que le job avait laissés passer. Le job n'avait pas menti, pas exactement : il avait vérifié le mauvais profil, réduit tous les résultats à un bit succès/échec, et supprimé les fichiers de rapport qui auraient révélé l'écart immédiatement. Gardez cet incident en tête lorsque vous branchez le moteur de preflight de PDFium Component, bibliothèque PDF en code source pour Delphi, C++Builder et Lazarus, dans un outil en ligne de commande : les appels de validation sont la partie facile, et le contrat qui les entoure — profils, codes de sortie, conservation des rapports — est l'endroit où le preflight par lots réussit ou pourrit silencieusement.
Le contrat : ce qu'un ordonnanceur voit réellement
Un runner CI ou le Planificateur de tâches Windows voit exactement deux choses de votre outil : le code de sortie et les fichiers qu'il a laissés derrière lui. Tout le reste — lignes de log, couleurs de console, progression — s'adresse aux humains qui regardent en direct. Avant de toucher l'API, fixez donc le vocabulaire des codes de sortie et gardez-le banal :
0— chaque fichier respecte chaque profil demandé1— au moins un fichier a produit des constats de validation2— l'outil lui-même a échoué sur au moins un fichier (entrée corrompue, verrou, crash)
La distinction entre les codes 1 et 2 est celle que les équipes sautent puis regrettent. Un PDF corrompu qui ne peut pas être ouvert n'est pas un échec de validation ; le traiter comme tel signifie qu'un camion de scans endommagés apparaît dans vos métriques comme un effondrement soudain de conformité, au lieu d'être l'incident opérationnel qu'il est vraiment.
Deux autres éléments du contrat méritent chacun un indicateur : un timeout par fichier et un répertoire de quarantaine. Un PDF pathologique — milliers de pages, structures d'objets profondément imbriquées — peut bloquer une passe de validation pendant des minutes, et une fenêtre nocturne ne se soucie pas de la raison. Arrêtez le job du fichier à l'échéance, comptez-le comme échec d'outil, mettez l'entrée de côté pour inspection, et laissez le lot avancer. Le répertoire de quarantaine devient alors un corpus auto-collecté des pires documents réellement envoyés par vos clients, ce qui vaut plus pour les tests de version que n'importe quel échantillon synthétique.
Choisir les standards : PDF/A-2b n'est pas PDF/A-3a
L'énumération TPdfPreflightStandard sélectionne les familles de standards qui comptent en pratique : ppsPdfA pour la conformité d'archivage ISO 19005, ppsPdfUa pour l'accessibilité ISO 14289, ppsPdfX pour l'échange d'impression, plus ppsPdfE, ppsPdfR et ppsPdfVT pour les workflows d'ingénierie, raster et données variables. Dans une famille, le moteur détecte le niveau de conformité que le document revendique et le rapporte par standard dans le ConformanceName du résultat — et le niveau compte. PDF/A-2b affirme seulement la reproductibilité visuelle ; PDF/A-3a exige en plus un balisage de structure logique et autorise les fichiers sources incorporés, une barre plus stricte et beaucoup plus difficile pour du matériau scanné. Si votre politique de conservation dit PDF/A-2b, rejeter des fichiers pour absence de balises de structure remplit le rapport de constats que personne ne veut corriger ; accepter n'importe quelle étiquette PDF/A sans vérifier le niveau promet trop peu. Les obligations d'accessibilité publiques ajoutent de plus en plus PDF/UA par-dessus, ce qui ne coûte rien à inclure car BuildPdfPreflightReport (depuis l'unité FPdfPreflightReport) accepte un ensemble :
Report := BuildPdfPreflightReport(Pdf, [ppsPdfA, ppsPdfUa]);
Un appel, deux standards, un seul enregistrement de rapport consolidé.
Lire le rapport : le silence n'est pas la conformité
Le rapport énumère les constats par standard. Une liste de problèmes vide signifie donc « aucun problème trouvé dans les standards exécutés », ce qui n'est pas la même affirmation que « le fichier est conforme au standard qui vous importe ». Si une faute de configuration a retiré ppsPdfA de l'ensemble, la liste de problèmes est tout aussi vide. Parcourez toujours Report.Results et affirmez deux choses pour chaque standard prévu : qu'une entrée de résultat existe pour lui, et que son indicateur IsCompliant (adossé à Status = pfsPass) est vrai. C'est précisément le mode de panne de l'histoire d'audit ci-dessus : le job assimilait « aucun constat » à « prêt pour l'archive » sans jamais vérifier quels standards avaient été évalués.
Le second piège se cache dans la nature d'un constat : chaque TPdfPreflightIssue porte un Code, une Category, une Description et une Recommendation — il nomme la règle violée, pas un numéro de page. Cela façonne la boucle de retour : le rapport dit à l'équipe de production quelle classe de défaut corriger (une police non incorporée, un identifiant XMP manquant), et localiser l'objet fautif précis est le travail de l'outil de remédiation, pas du validateur. Écrivez vos consommateurs de rapport contre les valeurs stables de Code, plutôt qu'en analysant un texte descriptif susceptible d'être reformulé entre versions.
Des fichiers de rapport pour les machines et pour l'astreinte
L'enregistrement de rapport écrit les mêmes constats dans cinq formats — SaveJsonToFile, SaveCsvToFile, SaveHtmlToFile, SaveTextToFile et SaveMarkdownToFile (avec les fonctions de style ToJson correspondantes lorsque vous voulez la chaîne au lieu d'un fichier). Résistez à l'envie d'en choisir un seul. JSON est pour le pipeline : attachez-le à l'enregistrement du job et laissez la CI analyser les codes de problème et les statuts par standard. HTML est pour l'opérateur réveillé par l'alerte : il s'ouvre dans n'importe quel navigateur sans outillage. Écrire les deux coûte une ligne de plus par fichier et supprime la pire expérience d'astreinte en traitement par lots, à savoir rétroconcevoir un blob JSON à deux heures du matin. Gardez un nommage déterministe — dérivez chaque nom de rapport du nom du fichier d'entrée, jamais d'un horodatage — sinon des exécutions parallèles entrelaceront des rapports impossibles à rattacher à leurs entrées.
Les seuils de sévérité appartiennent à la configuration, pas au code. Le même constat — une annotation sans description alternative, par exemple — est un échec bloquant pour un portail de soumission PDF/UA et une note ignorable pour une archive interne. Exposez un niveau d'échec par profil afin que la politique puisse changer sans recompilation, et enregistrez le niveau en vigueur dans le résumé du job, car au trimestre suivant personne ne se souviendra du seuil utilisé par le lot d'octobre.
Isoler les fichiers pour qu'un mauvais PDF ne coule pas le lot
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;
Trois choix délibérés vivent dans cette boucle. Un TPdf neuf par fichier garantit qu'un document qui corrompt l'état moteur ne puisse pas empoisonner ses successeurs. Le contrôle explicite de Active compte parce que définir Active := True absorbe les erreurs de chargement au lieu de les lever ; sans cette garde, un fichier tronqué dériverait jusqu'à l'appel de validation avant d'échouer, avec un message trompeur. Le try..except interne se trouve dans le périmètre du fichier, donc une exception incrémente le compteur d'échecs et la boucle continue : l'auditeur veut les rapports des 4 999 bons fichiers même si le fichier 5 000 est tronqué. Et les deux formats de rapport sont écrits avant que le verdict soit compté, afin que la preuve survive même si la logique de résumé a un bug.
Le mapping des codes de sortie se réduit alors à quelques lignes dans le fichier projet :
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.
Ce que le preflight ne fera pas pour vous
Le moteur détecte ; il ne répare pas. Un constat sur une police non incorporée ou un espace couleur dépendant du périphérique est un ordre de travail pour celui qui produit les fichiers, pas quelque chose que le validateur peut corriger sur place. Planifiez la boucle de retour en conséquence : les rapports doivent arriver là où l'équipe de production les lit, sinon les mêmes constats réapparaîtront chaque nuit jusqu'à ce que quelqu'un se demande pourquoi la courbe ne fléchit jamais. Il est aussi utile de recouper un échantillon de verdicts avec un validateur indépendant — veraPDF pour PDF/A, le preflight d'Acrobat pour PDF/X — avant qu'un auditeur externe ne le fasse pour vous. Deux moteurs qui s'accordent constituent une preuve forte ; un seul moteur reste une opinion.
Questions fréquentes
Puis-je valider PDF/A et PDF/UA dans la même passe ?
Oui. BuildPdfPreflightReport prend un ensemble de standards, donc [ppsPdfA, ppsPdfUa] évalue les deux en une seule exécution avec un rapport consolidé. Les contrôles PDF/UA s'associent aussi naturellement au travail d'accessibilité côté visionneuse, comme les modèles décrits pour construire un lecteur PDF accessible en Delphi.
Pourquoi mon rapport ne montre-t-il aucun problème pour un fichier que veraPDF rejette ?
Confirmez d'abord que le standard a réellement été exécuté : parcourez Report.Results pour trouver une entrée dont Standard correspond et vérifiez son indicateur IsCompliant, au lieu de déduire depuis une liste de constats vide. Si les standards concordent et que les moteurs divergent encore, conservez le document comme cas de régression nommé — les verdicts divergents sur de vrais fichiers clients sont exactement ce dont les tests de version ont besoin.
Le preflight corrige-t-il les problèmes qu'il trouve ?
Non. Il rapporte les violations de couleur, police, structure et métadonnées avec des codes, des catégories et des recommandations ; la remédiation se fait dans le workflow producteur. Budgétez cette boucle, pas seulement le contrôle.
Les profils, formats de rapport et l'API preflight complète sont documentés sur la page produit : PDFium Component. Le même moteur alimente les contrôles interactifs dans une interface de revue ; cette CLI et votre atelier de revue des PDF entrants peuvent donc partager un vocabulaire de validation unique.