Dans le PDFium Component pour Delphi et Lazarus, assigner TPdf.Active := True ne lève jamais rien quand un PDF échoue à se charger : TPdf.SetActive attrape chaque exception et laisse le composant inactif. Pour voir la vraie erreur, appelez plutôt TPdf.LoadDocument(Options, Report). Cette surcharge relance l'exception d'origine et remplit un TPdfLoadReport avec le statut de chargement, le code d'erreur natif PDFium et l'indication de savoir si la table de références croisées a dû être reconstruite
Le problème apparaît d'ordinaire dans du code de lot. Un travail d'extraction de tables parcourt un dossier de 13 PDF du monde réel avec un TPdf partagé, et 7 d'entre eux reviennent en échecs. Aucun des 7 fichiers n'est réellement cassé. Les blocs except autour du chargement ne se déclenchent jamais, le journal accuse les mauvais noms de fichiers, et la première erreur visible est un EPdfError nu à propos d'un composant inactif, levé depuis une lecture de propriété plusieurs lignes après le chargement qui a vraiment échoué. Deux comportements séparés se superposent pour produire ce tableau, et les deux fonctionnent comme conçus
Pourquoi TPdf.Active := True ne lève-t-il rien quand un PDF échoue à se charger ?
TPdf.SetActive enveloppe LoadDocument dans un try..except qui avale toute classe d'exception et laisse simplement le composant inactif. L'avachalement est délibéré : le même setter tourne quand un concepteur de fiches bascule Active dans l'IDE, et un mauvais chemin ne doit pas faire planter l'IDE. À l'exécution, TPdf.Active se contente de rapporter si un handle de document natif existe, donc après un chargement raté, il se lit False et rien d'autre ne se passe. Ce qui avait été levé est perdu, que ce soit un EPdfError de l'analyseur, une erreur de flux ou un EAccessViolation venu d'un pdfium.dll à moitié lié. Les messages DLL détaillés décrits dans diagnostiquer les échecs de chargement de pdfium.dll en Delphi n'atteignent votre gestionnaire qu'à travers un appel qui ne les avale pas
Pdf.FileName := FileName;
try
Pdf.Active := True; // SetActive avale toute exception de chargement
except
on E: Exception do
Log.Add(FileName + ': ' + E.Message); // ne s'exécute jamais
end;
// L'échec apparaît ici à la place, comme un EPdfError générique :
// 'Cannot perform this operation on an inactive Pdf1 component'
Log.Add(Format('%s: %d pages', [FileName, Pdf.PageCount]));
// Correction minimale pour le code existant : testez Active juste après l'assignation ;
// depuis la v3.122.1, LastLoadReport garde le texte de l'erreur avalée
Pdf.Active := True;
if not Pdf.Active then
Log.Add(FileName + ': load failed: ' + Pdf.LastLoadReport.ErrorMessage);
L'échec finit par se montrer au premier appel gardé. TPdf.PageCount, comme la plupart des propriétés de document, commence par CheckActive, qui lève un EPdfError nommant le composant mais pas le fichier ni la cause. Tester Pdf.Active immédiatement après l'assignation transforme un plantage mal attribué en entrée « échec » honnête. Avant PDFiumPas v3.122.1, la raison était perdue à ce stade ; depuis la v3.122.1, l'assignation ratée remplace LastLoadReport par un rapport plsFailed qui porte le texte d'erreur, si bien que la cause survit. L'objet exception lui-même et l'audit au niveau octet exigent toujours un autre point d'entrée
Pourquoi réutiliser un TPdf échoue-t-il à partir du second fichier ?
TPdf.FileName ne peut être assigné que quand le composant est inactif, si bien qu'une instance partagée rejette le second fichier avant même d'essayer de le charger. TPdf.SetFileName commence par CheckInactive, et la même garde protège Password et FormFill. Après le premier chargement réussi, l'instance reste active, l'assignation suivante lève, et si la boucle de lot attrape cette exception et continue, l'erreur atterrit sous le nouveau nom de fichier tandis que l'ancien document est encore ouvert. Mélangés aux échecs de chargement avalés, le journal cesse de coller à la réalité. Dans la reproduction à 13 fichiers, une instance partagée rapportait 7 échecs, tandis qu'un TPdf.Create(nil) neuf par document ouvrait les 13. Mettre Active := False entre les fichiers marche aussi, mais une instance par document garde chaque fichier isolé par construction
Que vous donne TPdf.LoadDocument avec un TPdfLoadReport ?
TPdf.LoadDocument(const Options: TPdfLoadOptions; out Report: TPdfLoadReport) lève la vraie exception et vous dit aussi ce qui s'est passé sous forme structurée. La surcharge fichier charge FileName ; les surcharges sœurs prennent TBytes ou un pointeur et une taille, et LoadCustomDocument(AStream, AOwnsStream, Options, Report) couvre les flux. Chacune valide les options, vérifie que l'instance est inactive, fait un audit au niveau octet de l'en-tête, du startxref, des sections xref et de la marque %%EOF, puis effectue le chargement natif. L'audit est borné par le même genre de limites que celles discutées dans les budgets de ressources de l'analyseur pour les PDF non fiables : TPdfLoadOptions.Default règle AuditByteLimit à 256 Mio, MaxIssues à 256, MaxXrefSections à 1024 et MaxXrefEntries à 4 000 000. En cas d'échec, la méthode pose Report.Status := plsFailed et relance ; parce que Report est écrit sur place, son contenu survit à l'exception, et une copie est stockée dans TPdf.LastLoadReport
Les champs du rapport répondent aux questions dont un journal de lot a réellement besoin. Status vaut l'une de plsNotAttempted, plsLoaded, plsLoadedWithRecovery, plsRejected ou plsFailed. NativeErrorCode contient FPDF_GetLastError, si bien que FPDF_ERR_PASSWORD (4) sépare un mot de passe manquant ou faux d'un fichier endommagé rapporté comme FPDF_ERR_FORMAT (3). UsedRecovery, CrossReferenceTableValid et RecoveryRoute disent si PDFium a dû reconstruire la table xref, et Issues liste chaque constat d'audit avec Code, Severity, Offset, ObjectNumber et MessageText, avec IssuesTruncated levé quand MaxIssues a écourté la liste
uses
SysUtils, Classes, TypInfo, FPdfView, PDFium;
procedure ProcessBatch(Files, Log: TStrings);
var
I: Integer;
Pdf: TPdf;
Options: TPdfLoadOptions;
Report: TPdfLoadReport;
begin
Options := TPdfLoadOptions.Default(plmCompatible);
for I := 0 to Files.Count - 1 do
begin
Pdf := TPdf.Create(nil); // une instance par document
try
Pdf.FileName := Files[I];
try
Pdf.LoadDocument(Options, Report);
except
on E: Exception do
begin
// Report est rempli même si LoadDocument a levé
if Report.NativeErrorCode = FPDF_ERR_PASSWORD then
Log.Add(Files[I] + ': password required')
else
Log.Add(Format('%s: %s (%s)', [Files[I],
GetEnumName(TypeInfo(TPdfLoadStatus), Ord(Report.Status)),
E.Message]));
Continue;
end;
end;
if Report.UsedRecovery then
Log.Add(Files[I] + ': opened after PDFium rebuilt the xref table');
ExtractTables(Pdf, Log);
finally
Pdf.Free;
end;
end;
end;
Quand charger avec plmStrict ?
Utilisez plmStrict chaque fois qu'un fichier réparé en silence est pire qu'un fichier rejeté, comme pour une entrée d'archives, la manipulation de preuves ou un pipeline de signature. PDFium reconstruit tranquillement une table de références croisées cassée (ISO 32000-1 §7.5.4) en scannant le fichier à la recherche d'objets, ce qui est super pour une visionneuse et un problème pour tout ce qui doit traiter exactement les octets qu'il a reçus. Après le chargement natif, le composant interroge FPDF_DocumentHasValidCrossReferenceTable. En mode plmCompatible, une reconstruction donne plsLoadedWithRecovery plus un avertissement plicNativeCrossReferenceRebuild. En mode plmStrict, le composant décharge le document, pose plsRejected, ajoute plicStrictModeRejected et lève EPdfError avec « le chargement PDF strict a rejeté le document ». Le mode strict rejette aussi toute erreur d'audit, et TPdfLoadOptions.Default(plmStrict) active RequireFinalEndOfFileMarker, qui promeut un %%EOF manquant ou des données après le dernier (§7.5.5) d'avertissement à erreur. L'audit xref complète les contrôles au niveau objet de valider les flux d'objets et xref avec PDFium VCL
function AcceptForArchive(const FileName: string; out Reason: string): Boolean;
var
Pdf: TPdf;
Report: TPdfLoadReport;
I: Integer;
begin
Result := False;
Reason := '';
Pdf := TPdf.Create(nil);
try
Pdf.FileName := FileName;
try
Pdf.LoadDocument(TPdfLoadOptions.Default(plmStrict), Report);
Result := True; // xref valide, aucune erreur d'audit
except
on E: EPdfError do
begin
Reason := E.Message;
for I := 0 to High(Report.Issues) do
if Report.Issues[I].Severity = plisError then
Reason := Reason + sLineBreak + Format(' at offset %d: %s',
[Report.Issues[I].Offset, Report.Issues[I].MessageText]);
end;
end;
finally
Pdf.Free;
end;
end;
Où TPdf.LastLoadReport cesse-t-il de dire la vérité ?
TPdf.LastLoadReport n'est complet qu'après une surcharge de LoadDocument qui prend des options, parce que seules ces surcharges font l'audit d'octets. Un Active := True réussi écrit un rapport en mode compatible sans audit d'octets, si bien que AuditAttempted reste False. Avant PDFiumPas v3.122.1, un échec n'écrivait rien, ce qui faisait que sur une instance partagée, LastLoadReport décrivait toujours le fichier précédent, souvent avec un plsLoaded rassurant. Depuis la v3.122.1, chaque chargement raté remplace le rapport : un Active := True raté, qui laisse toujours le composant inactif sans lever, et un appel LoadDocument ou LoadCustomDocument simple raté enregistrent plsFailed avec le texte d'erreur, là encore sans audit. Deux autres trous comptent en pratique. La validation des options et CheckInactive tournent avant que le rapport ne soit initialisé, si bien qu'un AuditByteLimit négatif ou une instance déjà active lève sans produire de rapport. Et NativeErrorCode ne veut dire quelque chose que quand PDFium a réellement tenté l'analyse ; pour un fichier manquant, le wrapper lève avant que PDFium ne tourne, donc journalisez ErrorMessage et le texte de l'exception à la place
La règle pratique est courte. Gardez Active := True pour les visionneuses liées à un concepteur où un composant inactif est un résultat acceptable. Partout ailleurs, et surtout dans le code de lot et de serveur, créez un TPdf par document, appelez LoadDocument(Options, Report), attrapez l'exception qu'il lève et journalisez Report.Status, NativeErrorCode et les Issues de niveau erreur avec le nom du fichier. Le coût, c'est quelques lignes par site d'appel, et chaque échec se retrouve attribué au bon fichier avec sa vraie cause
L'API de rapport de chargement, le mode strict et l'audit au niveau octet sont livrés avec le PDFium Component pour Delphi, C++Builder et Lazarus, aux côtés du rendu, de l'extraction de texte, du remplissage de formulaires et de la validation PDF/A