Dans une chaîne de traitement de sinistres d'assurance sur laquelle j'ai travaillé, un seul fichier entrant a fait perdre une demi-journée. Le « contrat signé » téléversé par un courtier était un PDF chiffré par mot de passe propriétaire qui encapsulait un formulaire XFA : l'extracteur de texte en aval renvoyait des chaînes vides, l'indexeur classait le dossier comme document blanc, et personne ne l'a vu avant l'appel de l'assuré. La panne ne venait pas de l'extracteur. Elle venait du fait qu'aucun code n'avait réellement regardé le fichier avant de le router. Toute équipe qui accepte des PDF venus de l'extérieur finit par construire le même outil : un atelier d'admission qui inspecte chaque document et décide où il peut aller. PDFium Component, bibliothèque en code source VCL/LCL pour visualiser et inspecter des documents avec Delphi, C++Builder et Lazarus, fournit les appels d'introspection nécessaires ; le reste de cet article explique quels appels répondent à quelles questions, et où ils peuvent vous induire en erreur.
Cinq questions avant de router un fichier
Retirez la grille et la bande de vignettes, et le tri d'admission se résume à cinq questions :
- Le fichier peut-il être ouvert, et avec quel mot de passe ?
- Que prétend-il être : titre, auteur, date de création ?
- Contient-il du contenu actif ou risqué : JavaScript, formulaire XFA, fichiers incorporés ?
- Existe-t-il du texte extractible, ou s'agit-il d'un scan à envoyer vers l'OCR ?
- Avec tous ces signaux, quelle file reçoit le fichier : traitement sans intervention, revue manuelle ou quarantaine ?
Chaque question correspond à un ou deux appels de PDFium Component. Deux correspondances ont des angles vifs qui expliquent la plupart des routages erronés que j'ai débogués en production : les métadonnées du document vivent à deux endroits différents, et le chiffrement n'empêche pas toujours le document de s'ouvrir.
Ouvrir au plus bas coût : formulaires désactivés, aucune page rendue
Le tri doit faire l'ouverture la moins coûteuse possible. Définir FormFill := False avant Active := True indique au composant de ne pas créer l'environnement de remplissage des formulaires, ce qui réduit le temps de chargement et, tout aussi important pour des fichiers d'origine inconnue, empêche tout JavaScript de document de s'initialiser. Aucune des propriétés d'inspection utilisées plus bas n'exige le rendu d'une page ; une passe de tri n'a donc jamais besoin de produire le moindre bitmap.
procedure InspectIncoming(const IncomingPath: string; var Rec: TIntakeRecord);
var
Pdf: TPdf;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := IncomingPath;
Pdf.FormFill := False; // no form environment, no JavaScript init
Pdf.Active := True; // failure is silent: Active simply stays False
if not Pdf.Active then
begin
Rec.OpenFailed := True; // damaged file or user-password lock
Exit; // the finally block still runs
end;
Rec.PageCount := Pdf.PageCount;
CollectIdentity(Pdf, IncomingPath, Rec);
CollectRiskSignals(Pdf, Rec);
finally
Pdf.Active := False;
Pdf.Free; // never leak the instance on a malformed file
end;
end;
Le contrôle après l'affectation n'est pas facultatif, et c'est volontairement un contrôle plutôt qu'un gestionnaire d'exception : lorsque le moteur ne peut pas charger le fichier, le composant absorbe l'EPdfError interne et laisse Active à False au lieu de la propager. Un code qui attend une exception lira tranquillement PageCount sur un document qui ne s'est jamais ouvert. Si le workflow de rejet a besoin du texte d'erreur exact du moteur, lisez le fichier dans un tableau d'octets et appelez la surcharge LoadDocument qui prend des TBytes ; ce chemin lève bien EPdfError avec le message, y compris dans le cas du mot de passe. Le try..finally garde aussi toute son utilité : les services d'admission tournent sans surveillance pendant des semaines, et aucune exception ultérieure ne doit laisser fuir l'instance TPdf ni conserver un verrou que la passe de reprise rencontrera plus tard.
Le débit devient rarement le goulot d'étranglement. Avec le remplissage de formulaires désactivé et sans rendu, une ouverture de tri est dominée par les E/S, et un seul worker inspecte sans difficulté plusieurs fichiers par seconde depuis un disque local. Si le volume d'admission dépasse un jour un worker, partitionnez le travail par fichier plutôt que par contrôle : les cinq questions partagent une seule ouverture, et les répartir entre processus multiplierait l'étape la plus chère au lieu de l'amortir.
Les métadonnées vivent à deux endroits, et elles se contredisent
ISO 32000-1 définit deux emplacements pour les métadonnées de document : le dictionnaire d'informations du document (clause 14.3.3) et un paquet XMP attaché au catalogue (clause 14.3.2). Les propriétés Title, Author, Subject et CreationDate lisent le dictionnaire Info (avec MetaText[] pour toute autre clé, et DecodeDate pour analyser la chaîne de date D:YYYYMMDD...). Le piège est que les producteurs modernes écrivent de plus en plus uniquement du XMP, tendance qu'ISO 32000-2 officialise en dépréciant la plupart des clés du dictionnaire Info dans PDF 2.0. Dans un outil d'admission, le symptôme est concret : votre atelier affiche un titre vide alors qu'Adobe Acrobat en montre un, parce qu'Acrobat s'est replié sur dc:title dans le paquet XMP, que les propriétés du dictionnaire Info ne consultent jamais.
procedure CollectIdentity(Pdf: TPdf; const FilePath: string;
var Rec: TIntakeRecord);
begin
Rec.Title := Pdf.Title; // Info dictionary value
Rec.Author := Pdf.Author;
Rec.CreatedAt := Pdf.CreationDate; // raw PDF date string ("D:2026...")
// An empty Info title does not mean the document is untitled. The
// component does not expose the XMP packet, so probe the raw file
// bytes for the dc:title element before trusting the blank.
if (Rec.Title = '') and FileContainsText(FilePath, 'dc:title') then
Include(Rec.Flags, ifTitleInXmpOnly);
end;
Même le sondage grossier par sous-chaîne ci-dessus mérite sa place : « métadonnées présentes, mais pas là où regardent les outils hérités » est un fait pertinent pour tout pipeline d'archive qui indexe sur le titre ou l'auteur. Si votre index aval ne lit que le dictionnaire Info, les fichiers signalés ainsi deviendront silencieusement introuvables.
Des fichiers chiffrés qui s'ouvrent quand même
Un document chiffré n'échoue pas nécessairement à l'ouverture. Le gestionnaire de sécurité standard (ISO 32000-1 clause 7.6.3) distingue le mot de passe utilisateur, nécessaire pour ouvrir le document, du mot de passe propriétaire, qui limite seulement des permissions comme l'impression et la copie. Une grande partie des documents métier « protégés » sont chiffrés avec un mot de passe propriétaire et un mot de passe utilisateur vide : ils s'ouvrent sans invite, se déchiffrent entièrement et comptent sur les visionneuses pour respecter volontairement les indicateurs de permission. C'est une règle d'usage, pas une protection, et vos états d'admission doivent refléter la différence.
Détecter le chiffrement après une ouverture réussie demande un appel moteur plus un repli : FPDF_GetSecurityHandlerRevision(Pdf.Document) renvoie -1 pour les fichiers non protégés et sinon la révision du gestionnaire, tandis que Pdf.Permissions qui renvoie autre chose que le masque tous bits à 1 $FFFFFFFF constitue le signal de corroboration. Pour les fichiers réellement verrouillés par mot de passe utilisateur, affectez Password avant de définir Active := True ; si l'ouverture échoue encore, routez le fichier vers un état bloqué qui demande les identifiants à l'expéditeur par un canal sûr au lieu de réessayer à l'aveugle. Et résistez à la tentation de traiter « chiffré » comme une quarantaine automatique : dans la plupart des secteurs saturés de documents, les fichiers chiffrés mais ouvrables sont le cas normal, pas le cas suspect.
Contenu actif : JavaScript, XFA et fichiers incorporés
Trois constats doivent toujours atteindre la décision de routage. D'abord JavaScript : l'événement OnUnsupportedFeature signale les fonctionnalités structurelles comme XFA ou le contenu 3D au moment où le moteur les rencontre, mais il ne détecte pas JavaScript ; vérifiez plutôt JavaScriptActionCount et traitez un résultat non nul comme du contenu actif. Ensuite XFA : lorsque FormType renvoie ftXfaFull, les pages visibles ne sont souvent guère plus qu'un rendu du modèle XFA, et l'extraction de texte classique verra du texte standard plutôt que les valeurs saisies. Enfin les pièces jointes : un PDF est un format conteneur, et AttachmentCount vous indique si celui-ci transporte des passagers.
procedure CollectRiskSignals(Pdf: TPdf; var Rec: TIntakeRecord);
var
i, PageNo: Integer;
Ext: string;
begin
Rec.IsEncrypted := Assigned(FPDF_GetSecurityHandlerRevision) and
(FPDF_GetSecurityHandlerRevision(Pdf.Document) <> -1);
Rec.HasForms := Pdf.FormType <> ftNone;
Rec.IsXfa := Pdf.FormType = ftXfaFull;
Rec.HasJavaScript := Pdf.JavaScriptActionCount > 0;
// AnnotationCount is a per-page property; walk the pages to total
// it. Loading a page object renders nothing, so this stays cheap.
Rec.Annotations := 0;
for PageNo := 1 to Pdf.PageCount do
begin
Pdf.PageNumber := PageNo;
Inc(Rec.Annotations, Pdf.AnnotationCount);
end;
Rec.Attachments := Pdf.AttachmentCount;
for i := 0 to Rec.Attachments - 1 do
begin
Ext := LowerCase(ExtractFileExt(string(Pdf.AttachmentName[i])));
if (Ext = '.exe') or (Ext = '.js') or (Ext = '.vbs') or (Ext = '.dll') then
Include(Rec.Flags, ifDangerousAttachment);
end;
end;
Deux détails de cette boucle méritent attention. Le nom de pièce jointe vient de l'intérieur du document ; ne le réutilisez donc jamais comme chemin de sortie sans l'assainir — un nom incorporé comme ..\..\start.exe est une traversée de chemin qui attend un appel de sauvegarde négligent. Et une liste de blocage par extension est un déclencheur, pas une garantie ; son rôle est de forcer une décision humaine, pas de certifier que le fichier est propre.
Transformer les signaux en états de routage
Un modèle d'états exploitable demande moins d'états que la plupart des équipes l'imaginent : ready (aucun blocage, texte présent), review (ouverture réussie mais quelque chose demande un regard humain — formulaire XFA, JavaScript, couche texte vide, titre seulement dans XMP), blocked (mot de passe utilisateur requis) et damaged (échec d'ouverture). Enregistrez les preuves avec l'état — hachage du fichier, nombre de pages, indicateurs exacts, message d'erreur moteur pour les fichiers endommagés — car la personne qui contestera une décision de routage le fera des semaines plus tard, sur un fichier qui aura peut-être été remplacé ou modifié entre-temps.
Lorsqu'un opérateur doit vraiment regarder un fichier en quarantaine, ne le confiez pas à la visionneuse shell par défaut. Rendez-le dans un panneau durci où scripts et gestion des liens sont désactivés, comme dans l'approche décrite pour construire une surface d'aperçu PDF sécurisée en Delphi. Et si votre admission alimente une archive soumise à des exigences de conformité, la passe de tri est l'endroit naturel pour planifier un contrôle plus profond ; la validation preflight par lots selon des profils PDF/A et PDF/UA reprend exactement là où cette inspection s'arrête.
Questions fréquentes
Comment vérifier si un PDF est protégé par mot de passe en Delphi ?
Ouvrez-le avec PDFium Component et interrogez le gestionnaire de sécurité : FPDF_GetSecurityHandlerRevision(Pdf.Document) renvoie -1 pour les fichiers non protégés. Si Active reste à False sans mot de passe, le fichier utilise très probablement un mot de passe utilisateur ; affectez Password et réessayez. S'il s'ouvre correctement mais qu'un gestionnaire de sécurité est présent, le fichier porte seulement une protection par mot de passe propriétaire : il est entièrement lisible, et les indicateurs de permission dans Permissions sont consultatifs.
Pourquoi la propriété Title renvoie-t-elle une chaîne vide alors qu'Acrobat affiche un titre ?
Le titre est stocké uniquement dans le paquet de métadonnées XMP, pas dans le dictionnaire d'informations du document lu par Title. Le composant n'expose pas le paquet XMP ; sondez donc les octets bruts du fichier pour chercher dc:title et signalez le fichier aux pipelines qui indexent sur les métadonnées du dictionnaire Info.
PDFium Component peut-il détecter JavaScript dans un PDF ?
Oui — vérifiez JavaScriptActionCount ou énumérez les actions de niveau document via JavaScriptActions. Ne vous fiez pas à l'événement OnUnsupportedFeature pour cela ; il signale des fonctionnalités comme XFA et la 3D, mais pas les scripts.
La page produit du composant couvre la licence, l'API d'inspection complète et les démonstrations fournies, dont un inspecteur de document de type admission : PDFium Component.