În PDFium Component pentru Delphi și Lazarus, asignarea TPdf.Active := True nu ridică niciodată când un PDF nu se încarcă: TPdf.SetActive prinde orice excepție și lasă componenta inactivă. Ca să vedeți eroarea reală, apelați în schimb TPdf.LoadDocument(Options, Report). Acel overload re-ridică excepția originală și umple un TPdfLoadReport cu statusul de încărcare, codul de eroare nativ PDFium și dacă tabela de referințe încrucișate a trebuit reconstruită
Problema iese la suprafață de obicei în codul de batch. Un job de extragere de tabele parcurge un dosar cu 13 PDF-uri reale cu un singur TPdf partajat, iar 7 dintre ele revin ca eșecuri. Niciunul dintre acele 7 fișiere nu e de fapt stricat. Blocurile except din jurul încărcării nu se declanșează niciodată, jurnalul dă vina pe nume de fișiere greșite, iar prima eroare vizibilă e un EPdfError sec despre o componentă inactivă, ridicat dintr-o citire de proprietate la câteva linii după încărcarea care a picat cu adevărat. Două comportări separate se stivuiesc ca să producă imaginea aceasta, și ambele funcționează conform designului
De ce TPdf.Active := True nu ridică când un PDF nu se încarcă?
TPdf.SetActive îmbracă LoadDocument într-un try..except care înghite orice clasă de excepție și pur și simplu lasă componenta inactivă. Înghițirea e deliberată: același setter rulează când un designer de formulare comută Active în IDE, iar o cale greșită nu trebuie să crăpeze IDE-ul. La runtime, TPdf.Active raportează doar dacă există un handle nativ de document, deci după o încărcare eșuată citește False și nu se mai întâmplă nimic. Orice fusese ridicat e dispărut, fie că era un EPdfError de la parser, o eroare de stream sau un EAccessViolation de la un pdfium.dll pe jumătate legat. Mesajele detaliate de DLL descrise în diagnosticarea eșecurilor de încărcare pdfium.dll în Delphi ajung la handler-ul dumneavoastră doar printr-un apel care nu le înghite
Pdf.FileName := FileName;
try
Pdf.Active := True; // SetActive înghite orice excepție de încărcare
except
on E: Exception do
Log.Add(FileName + ': ' + E.Message); // nu se execută niciodată
end;
// Eșecul iese la suprafață aici, ca un EPdfError generic:
// 'Cannot perform this operation on an inactive Pdf1 component'
Log.Add(Format('%s: %d pages', [FileName, Pdf.PageCount]));
// Reparare minimală pentru cod existent: testați Active chiar după asignare;
// din v3.122.1, LastLoadReport păstrează textul erorii înghițite
Pdf.Active := True;
if not Pdf.Active then
Log.Add(FileName + ': load failed: ' + Pdf.LastLoadReport.ErrorMessage);
Eșecul apare în cele din urmă la primul apel păzit. TPdf.PageCount, ca majoritatea proprietăților de document, începe cu CheckActive, care ridică un EPdfError care numește componenta, dar nici fișierul, nici cauza. Testarea lui Pdf.Active imediat după asignare transformă un crash atribuit greșit într-o intrare onestă de „picat". Înainte de PDFiumPas v3.122.1, motivul se pierdea în acel punct; din v3.122.1, asignarea eșuată înlocuiește LastLoadReport cu un raport plsFailed care poartă textul erorii, deci cauza supraviețuiește. Obiectul de excepție în sine și auditul la nivel de byte mai cer totuși un alt punct de intrare
De ce reutilizarea unui singur TPdf pică de la al doilea fișier înainte?
TPdf.FileName poate fi asignat doar când componenta e inactivă, deci o instanță partajată respinge al doilea fișier înainte să încerce măcar să-l încarce. TPdf.SetFileName începe cu CheckInactive, iar aceeași gardă protejează Password și FormFill. După prima încărcare reușită instanța rămâne activă, următoarea asignare ridică, iar dacă bucla de batch prinde excepția aceea și merge mai departe, eroarea aterizează sub noul nume de fișier în timp ce vechiul document e tot deschis. Amestecat cu eșecurile de încărcare înghițite, jurnalul încetează să se mai potrivească cu realitatea. În reproducerea cu 13 fișiere, o instanță partajată raporta 7 eșecuri, în timp ce un TPdf.Create(nil) proaspăt per document deschidea toate 13. Setarea Active := False între fișiere funcționează și ea, dar câte o instanță per document ține fiecare fișier izolat prin construcție
Ce vă oferă TPdf.LoadDocument cu un TPdfLoadReport?
TPdf.LoadDocument(const Options: TPdfLoadOptions; out Report: TPdfLoadReport) ridică excepția reală și vă spune totodată în formă structurată ce s-a întâmplat. Overload-ul de fișier încarcă FileName; overload-urile surori iau TBytes sau un pointer și o mărime, iar LoadCustomDocument(AStream, AOwnsStream, Options, Report) acoperă stream-urile. Fiecare validează opțiunile, verifică că instanța e inactivă, rulează un audit la nivel de byte al antetului, startxref-ului, secțiunilor xref și marcatorului %%EOF, apoi execută încărcarea nativă. Auditul e limitat de același fel de limite discutate în bugetele de resurse ale parserului pentru PDF-uri fără încredere: TPdfLoadOptions.Default setează AuditByteLimit la 256 MiB, MaxIssues la 256, MaxXrefSections la 1024 și MaxXrefEntries la 4.000.000. La eșec, metoda setează Report.Status := plsFailed și re-ridică; pentru că Report e scris pe loc, conținutul lui supraviețuiește excepției, iar o copie e stocată în TPdf.LastLoadReport
Câmpurile raportului răspund la întrebările de care are efectiv nevoie un jurnal de batch. Status este unul dintre plsNotAttempted, plsLoaded, plsLoadedWithRecovery, plsRejected sau plsFailed. NativeErrorCode ține FPDF_GetLastError, deci FPDF_ERR_PASSWORD (4) separă o parolă lipsă sau greșită de un fișier avariat raportat ca FPDF_ERR_FORMAT (3). UsedRecovery, CrossReferenceTableValid și RecoveryRoute spun dacă PDFium a trebuit să reconstruiască tabela xref, iar Issues listează fiecare constatare de audit cu Code, Severity, Offset, ObjectNumber și MessageText, cu IssuesTruncated setat când MaxIssues a tăiat 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); // câte o instanță per document
try
Pdf.FileName := Files[I];
try
Pdf.LoadDocument(Options, Report);
except
on E: Exception do
begin
// Report e umplut chiar dacă LoadDocument a ridicat
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;
Când ar trebui să încărcați cu plmStrict?
Folosiți plmStrict ori de câte ori un fișier reparat în tăcere e mai rău decât unul respins, precum recepția de arhivă, tratarea dovezilor sau un pipeline de semnare. PDFium reconstruiește liniștit o tabelă de referințe încrucișate stricată (ISO 32000-1 §7.5.4) scanând fișierul după obiecte, ceea ce e grozav pentru un viewer și o problemă pentru orice trebuie să proceseze exact byte-ii primiți. După încărcarea nativă, componenta întreabă FPDF_DocumentHasValidCrossReferenceTable. În modul plmCompatible, o reconstrucție dă plsLoadedWithRecovery plus un avertisment plicNativeCrossReferenceRebuild. În modul plmStrict, componenta descarcă documentul, setează plsRejected, adaugă plicStrictModeRejected și ridică EPdfError cu „încărcarea strictă PDF a respins documentul". Modul strict respinge și orice eroare de audit, iar TPdfLoadOptions.Default(plmStrict) activează RequireFinalEndOfFileMarker, care promovează un %%EOF lipsă sau date după ultimul (§7.5.5) de la avertisment la eroare. Auditul xref completează verificările la nivel de obiect din validarea fluxurilor de obiecte și xref cu 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 valid, fără erori de 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;
Unde încetează TPdf.LastLoadReport să mai spună adevărul?
TPdf.LastLoadReport e complet doar după un overload LoadDocument care ia opțiuni, pentru că doar acele overload-uri rulează auditul de byte. Un Active := True reușit scrie un raport în mod compatibil fără audit de byte, deci AuditAttempted rămâne False. Înainte de PDFiumPas v3.122.1, unul eșuat nu scria nimic, ceea ce însemna că pe o instanță partajată LastLoadReport descria încă fișierul anterior, adesea cu un plsLoaded liniștitor. Din v3.122.1, fiecare încărcare eșuată înlocuiește raportul: un Active := True eșuat, care lasă tot componenta inactivă fără să ridice, și un apel simplu LoadDocument sau LoadCustomDocument eșuat înregistrează plsFailed cu textul erorii, tot fără audit. Încă două goluri contează în practică. Validarea opțiunilor și CheckInactive rulează înainte ca raportul să fie inițializat, deci un AuditByteLimit negativ sau o instanță deja activă ridică fără să producă un raport. Iar NativeErrorCode are sens doar când PDFium a încercat efectiv parsarea; pentru un fișier lipsă wrapper-ul ridică înainte ca PDFium să ruleze, deci jurnalizați ErrorMessage și textul excepției în loc
Regula practică e scurtă. Păstrați Active := True pentru viewer-e legate de designer, unde o componentă inactivă e un rezultat acceptabil. Oriunde altundeva, și mai ales în codul de batch și de server, creați câte un TPdf per document, apelați LoadDocument(Options, Report), prindeți excepția pe care o ridică și jurnalizați Report.Status, NativeErrorCode și Issues-urile la nivel de eroare împreună cu numele fișierului. Costul e de câteva linii per loc de apel, iar fiecare eșec ajunge atribuit fișierului corect cu cauza lui reală
API-ul de raport de încărcare, modul strict și auditul la nivel de byte sosesc cu PDFium Component pentru Delphi, C++Builder și Lazarus, alături de randare, extragere de text, umplere de formulare și validare PDF/A