Nel PDFium Component per Delphi e Lazarus, assegnare TPdf.Active := True non solleva mai quando un PDF non si carica: TPdf.SetActive cattura ogni eccezione e lascia il componente inattivo. Per vedere l'errore vero, chiamate invece TPdf.LoadDocument(Options, Report). Quell'overload rilancia l'eccezione originale e riempie un TPdfLoadReport con lo stato di caricamento, il codice errore nativo di PDFium e se la tabella cross-reference abbia dovuto essere ricostruita
Il problema di solito emerge nel codice batch. Un lavoro di estrazione tabelle percorre una cartella di 13 PDF reali con un solo TPdf condiviso, e 7 di loro tornano come fallimenti. Nessuno dei 7 file è davvero rotto. I blocchi except attorno al caricamento non scattano mai, il log incolpa i nomi di file sbagliati, e il primo errore visibile è un nudo EPdfError su un componente inattivo, sollevato da una lettura di proprietà diverse righe dopo il caricamento che ha davvero fallito. Due comportamenti separati si impilano per produrre quel quadro, ed entrambi funzionano come progettato
Perché TPdf.Active := True non solleva quando un PDF non si carica?
TPdf.SetActive incapsula LoadDocument in un try..except che ingoia ogni classe di eccezione e lascia semplicemente il componente inattivo. L'ingozzata è deliberata: lo stesso setter gira quando un designer di form commuta Active nell'IDE, e un percorso sbagliato non deve crashare l'IDE. A run time TPdf.Active riferisce solo se esiste un handle di documento nativo, quindi dopo un caricamento fallito si legge False e non succede nient'altro. Qualunque cosa sia stata sollevata è sparita, che fosse un EPdfError dal parser, un errore di stream o un EAccessViolation da un pdfium.dll mezzo collegato. I messaggi dettagliati della DLL descritti in diagnosticare i fallimenti di caricamento di pdfium.dll in Delphi raggiungono il vostro handler solo attraverso una chiamata che non li ingoia
Pdf.FileName := FileName;
try
Pdf.Active := True; // SetActive ingoia qualunque eccezione di caricamento
except
on E: Exception do
Log.Add(FileName + ': ' + E.Message); // non esegue mai
end;
// Il fallimento emerge invece qui, come un generico EPdfError:
// 'Cannot perform this operation on an inactive Pdf1 component'
Log.Add(Format('%s: %d pages', [FileName, Pdf.PageCount]));
// Correzione minima per codice esistente: testate Active subito dopo l'assegnamento;
// dalla v3.122.1 LastLoadReport conserva il testo dell'errore ingoiato
Pdf.Active := True;
if not Pdf.Active then
Log.Add(FileName + ': load failed: ' + Pdf.LastLoadReport.ErrorMessage);
Il fallimento alla fine si mostra alla prima chiamata protetta. TPdf.PageCount, come la maggior parte delle proprietà del documento, inizia con CheckActive, che solleva un EPdfError nominando il componente ma non il file e non la causa. Testare Pdf.Active subito dopo l'assegnamento trasforma un crash attribuito male in una voce onesta "fallito". Prima di PDFiumPas v3.122.1 la ragione si perdeva in quel punto; dalla v3.122.1 l'assegnamento fallito sostituisce LastLoadReport con un report plsFailed che porta il testo dell'errore, quindi la causa sopravvive. L'oggetto eccezione in sé e l'audit a livello di byte richiedono comunque un punto di ingresso diverso
Perché riutilizzare un solo TPdf fallisce dal secondo file in poi?
TPdf.FileName può essere assegnato solo mentre il componente è inattivo, quindi un'istanza condivisa rifiuta il secondo file prima ancora di provare a caricarlo. TPdf.SetFileName inizia con CheckInactive, e la stessa guardia protegge Password e FormFill. Dopo il primo caricamento riuscito l'istanza resta attiva, l'assegnamento successivo solleva, e se il loop batch cattura quell'eccezione e va avanti, l'errore atterra sotto il nuovo nome di file mentre il vecchio documento è ancora aperto. Mescolati ai fallimenti di caricamento ingoiati, il log smette di corrispondere alla realtà. Nella riproduzione da 13 file un'istanza condivisa riferiva 7 fallimenti, mentre un fresco TPdf.Create(nil) per documento ne apriva tutti e 13. Impostare Active := False tra i file funziona anch'esso, ma un'istanza per documento tiene ogni file isolato per costruzione
Che cosa vi dà TPdf.LoadDocument con un TPdfLoadReport?
TPdf.LoadDocument(const Options: TPdfLoadOptions; out Report: TPdfLoadReport) solleva l'eccezione vera e vi dice anche che cosa è successo in forma strutturata. L'overload per file carica FileName; gli overload fratelli prendono TBytes o un puntatore e una dimensione, e LoadCustomDocument(AStream, AOwnsStream, Options, Report) copre gli stream. Ognuno valida le opzioni, controlla che l'istanza sia inattiva, esegue un audit a livello di byte di header, startxref, le sezioni xref e il marcatore %%EOF, poi esegue il caricamento nativo. L'audit è limitato dallo stesso genere di limiti discussi in i budget di risorse del parser per PDF non fidati: TPdfLoadOptions.Default imposta AuditByteLimit a 256 MiB, MaxIssues a 256, MaxXrefSections a 1024 e MaxXrefEntries a 4.000.000. Al fallimento il metodo imposta Report.Status := plsFailed e rilancia; poiché Report viene scritto sul posto, il suo contenuto sopravvive all'eccezione, e una copia viene memorizzata in TPdf.LastLoadReport
I campi del report rispondono alle domande di cui un log batch ha davvero bisogno. Status è uno tra plsNotAttempted, plsLoaded, plsLoadedWithRecovery, plsRejected o plsFailed. NativeErrorCode contiene FPDF_GetLastError, così FPDF_ERR_PASSWORD (4) separa una password mancante o sbagliata da un file danneggiato riferito come FPDF_ERR_FORMAT (3). UsedRecovery, CrossReferenceTableValid e RecoveryRoute dicono se PDFium abbia dovuto ricostruire la tabella xref, e Issues elenca ogni riscontro dell'audit con Code, Severity, Offset, ObjectNumber e MessageText, con IssuesTruncated impostato quando MaxIssues ha tagliato la lista
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); // una istanza per documento
try
Pdf.FileName := Files[I];
try
Pdf.LoadDocument(Options, Report);
except
on E: Exception do
begin
// Report viene riempito anche se LoadDocument ha sollevato
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;
Quando caricare con plmStrict?
Usate plmStrict ogni volta che un file riparato in silenzio è peggiore di uno rifiutato, come intake di archivio, gestione di prove o una pipeline di firma. PDFium ricostruisce in silenzio una tabella cross-reference rotta (ISO 32000-1 §7.5.4) scandendo il file alla ricerca di oggetti, il che è ottimo per un viewer e un problema per qualsiasi cosa debba processare esattamente i byte che ha ricevuto. Dopo il caricamento nativo, il componente interroga FPDF_DocumentHasValidCrossReferenceTable. In modalità plmCompatible una ricostruzione produce plsLoadedWithRecovery più un warning plicNativeCrossReferenceRebuild. In modalità plmStrict il componente scarica il documento, imposta plsRejected, aggiunge plicStrictModeRejected e solleva EPdfError con "Strict PDF load rejected the document". La modalità strict rifiuta anche ogni errore di audit, e TPdfLoadOptions.Default(plmStrict) attiva RequireFinalEndOfFileMarker, che promuove un %%EOF mancante o dati dopo l'ultimo (§7.5.5) da warning a errore. L'audit xref completa i controlli a livello di oggetto di validare stream di oggetti e xref con 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 valida, nessun errore di 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;
Dove TPdf.LastLoadReport smette di dire la verità?
TPdf.LastLoadReport è completo solo dopo un overload di LoadDocument che prende le opzioni, perché solo quegli overload eseguono l'audit dei byte. Un Active := True riuscito scrive un report in modalità compatibile senza audit dei byte, quindi AuditAttempted resta False. Prima di PDFiumPas v3.122.1 uno fallito non scriveva nulla, il che significava che su un'istanza condivisa LastLoadReport descriveva ancora il file precedente, spesso con un rassicurante plsLoaded. Dalla v3.122.1 ogni caricamento fallito sostituisce il report: un Active := True fallito, che lascia comunque il componente inattivo senza sollevare, e una chiamata fallita a LoadDocument semplice o LoadCustomDocument registrano plsFailed col testo dell'errore, anche qui senza audit. Altri due buchi contano nella pratica. La validazione delle opzioni e CheckInactive girano prima che il report sia inizializzato, quindi un AuditByteLimit negativo o un'istanza già attiva sollevano senza produrre un report. E NativeErrorCode è significativo solo quando PDFium ha davvero tentato il parsing; per un file mancante il wrapper solleva prima che PDFium giri, quindi scrivete nel log ErrorMessage e il testo dell'eccezione
La regola pratica è breve. Tenete Active := True per i viewer legati al designer dove un componente inattivo è un esito accettabile. Ovunque altro, e sopra tutto nel codice batch e server, create un TPdf per documento, chiamate LoadDocument(Options, Report), catturate l'eccezione che solleva e scrivete nel log Report.Status, NativeErrorCode e gli Issues di livello errore insieme al nome del file. Il costo è qualche riga per call site, e ogni fallimento viene attribuito al file giusto con la sua causa vera
L'API del load report, la modalità strict e l'audit a livello di byte viaggiano con il PDFium Component for Delphi, C++Builder and Lazarus, insieme a rendering, estrazione testo, riempimento moduli e validazione PDF/A