U PDFium Componentu za Delphi i Lazarus dodjela TPdf.Active := True nikad ne podiže iznimku kad PDF padne pri loadanju: TPdf.SetActive hvata svaku iznimku i komponentu ostavlja neaktivnom. Da vidite pravu grešku, pozovite umjesto toga TPdf.LoadDocument(Options, Report). Taj overload ponovno podiže izvornu iznimku i puni TPdfLoadReport statusom loadanja, nativnim PDFium kodom greške i time je li tablicu unakrsnih referenci trebalo ponovno sagraditi
Problem obično izbije u batch kodu. Posao izvlačenja tablica obilazi mapu od 13 PDF-ova iz stvarnog svijeta s jednim zajedničkim TPdf, i 7 ih se vrati kao neuspjesi. Nijedna od tih 7 datoteka zapravo nije slomljena. except blokovi oko loadanja nikad ne okinu, log krivi druge datoteke, a prva vidljiva greška goli je EPdfError o neaktivnoj komponenti, podignut iz čitanja svojstva nekoliko linija nakon loadanja koje je stvarno palo. Dva odvojena ponašanja narežu se jedna na drugo i proizvedu tu sliku, i oboje rade po dizajnu
Zašto TPdf.Active := True ne podiže iznimku kad PDF padne pri loadanju?
TPdf.SetActive omata LoadDocument u try..except koji guta svaku klasu iznimke i komponentu jednostavno ostavi neaktivnom. Gutanje je namjerno: isti setter radi kad dizajner forma uključi Active u IDE-u, i krivi put ne smije srušiti IDE. U vrijeme izvršavanja TPdf.Active samo javlja postoji li nativni handle dokumenta, pa nakon pala loadanja čita False i ništa se dalje ne događa. Što je god bilo podignuto, nestalo je, bilo da je to bio EPdfError iz parsera, greška streama ili EAccessViolation iz napola vezanog pdfium.dll-a. Detalne DLL poruke opisane u članku o dijagnosticiranju load grešaka pdfium.dll-a u Delphiju do Vašeg handlera dođu samo kroz poziv koji ih ne guta
Pdf.FileName := FileName;
try
Pdf.Active := True; // SetActive guta svaku load iznimku
except
on E: Exception do
Log.Add(FileName + ': ' + E.Message); // nikad se ne izvršava
end;
// Neuspjeh izbije ovdje umjesto toga, kao generički EPdfError:
// 'Cannot perform this operation on an inactive Pdf1 component'
Log.Add(Format('%s: %d pages', [FileName, Pdf.PageCount]));
// Minimalni popravak postojećeg koda: testirajte Active odmah iza dodjele;
// od v3.122.1 LastLoadReport čuva tekst progutane greške
Pdf.Active := True;
if not Pdf.Active then
Log.Add(FileName + ': load failed: ' + Pdf.LastLoadReport.ErrorMessage);
Neuspjeh napokon izbije na prvom čuvanom pozivu. TPdf.PageCount, kao i većina svojstava dokumenta, počinje s CheckActive, koji podiže EPdfError koji imenuje komponentu ali ne datoteku i ne uzrok. Testiranje Pdf.Active odmah iza dodjele krivo pripisanu pad pretvara u pošten unos "palo". Prije PDFiumPas v3.122.1 razlog je bio izgubljen u tom trenutku; od v3.122.1 pala dodjela zamjenjuje LastLoadReport plsFailed izvještajem koji nosi tekst greške, pa uzrok preživi. Sam objekt iznimke i audit na razini bajtova i dalje traže drugi ulaz
Zašto ponovna upotreba jednog TPdf-a padne od druge datoteke nadalje?
TPdf.FileName smije se dodijeliti samo dok je komponenta neaktivna, pa zajednička instanca drugu datoteku odbije prije nego je ikada pokuša loadati. TPdf.SetFileName počinje s CheckInactive, a isti čuvar štiti Password i FormFill. Nakon prvog uspješnog loadanja instanca ostaje aktivna, sljedeća dodjela podiže iznimku, i ako batch petlja tu iznimku uhvati i krene dalje, greška slijeće pod novim imenom dok je stari dokument još otvoren. Pomiješano s progutanim load neuspjesima, log prestaje odgovarati stvarnosti. U reprodukciji s 13 datoteka zajednička je instanca javila 7 neuspjeha, dok je svježi TPdf.Create(nil) po dokumentu otvorio svih 13. Postavljanje Active := False između datoteka isto radi, ali jedna instanca po dokumentu svaku datoteku drži izoliranom po konstrukciji
Što Vam daje TPdf.LoadDocument s TPdfLoadReportom?
TPdf.LoadDocument(const Options: TPdfLoadOptions; out Report: TPdfLoadReport) podiže pravu iznimku i govori Vam što se dogodilo u strukturiranom obliku. File overload loada FileName; srodni overloadi uzimaju TBytes ili pokazivač i veličinu, a LoadCustomDocument(AStream, AOwnsStream, Options, Report) pokriva streamove. Svaki validira opcije, provjerava je li instanca neaktivna, izvodi audit bajtova headera, startxref, xref sekcija i markera %%EOF, pa izvodi nativni load. Audit granice su iste vrste granica o kojima govori parser resursni budžeti za nepouzdane PDF-ove: TPdfLoadOptions.Default postavlja AuditByteLimit na 256 MiB, MaxIssues na 256, MaxXrefSections na 1024 i MaxXrefEntries na 4.000.000. Pri padu metoda postavlja Report.Status := plsFailed i ponovno podiže; budući da se Report piše na mjestu, njegov sadržaj preživi iznimku, a kopija se sprema u TPdf.LastLoadReport
Polja izvještaja odgovaraju na pitanja koja batch log stvarno treba. Status je jedan od plsNotAttempted, plsLoaded, plsLoadedWithRecovery, plsRejected ili plsFailed. NativeErrorCode drži FPDF_GetLastError, pa FPDF_ERR_PASSWORD (4) razdvaja nedostajuću ili krivu lozinku od oštećene datoteke javljene kao FPDF_ERR_FORMAT (3). UsedRecovery, CrossReferenceTableValid i RecoveryRoute kažu je li PDFium morao sagraditi xref tablicu ponovno, a Issues nabraja svaki audit nalaz s Code, Severity, Offset, ObjectNumber i MessageText, s IssuesTruncated postavljenim kad je MaxIssues skratio popis
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); // jedna instanca po dokumentu
try
Pdf.FileName := Files[I];
try
Pdf.LoadDocument(Options, Report);
except
on E: Exception do
begin
// Report je popunjen iako je LoadDocument podigao iznimku
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;
Kad loadati s plmStrict?
plmStrict koristite kad god je tiho popravljena datoteka gora od odbijene, poput arhivskog prijema, rukovanja dokazima ili pipelinea potpisivanja. PDFium tiho rekonstruira slomljenu tablicu unakrsnih referenci (ISO 32000-1 §7.5.4) skenirajući datoteku za objektima, što je sjajno za viewer a problem za sve što mora obraditi točno bajtove koje je dobilo. Nakon nativnog loadanja komponenta pita FPDF_DocumentHasValidCrossReferenceTable. U plmCompatible modu obnova daje plsLoadedWithRecovery plus upozorenje plicNativeCrossReferenceRebuild. U plmStrict modu komponenta istovaruje dokument, postavlja plsRejected, dodaje plicStrictModeRejected i podiže EPdfError s "Strict PDF load rejected the document". Strogi mod odbija i svaku audit grešku, a TPdfLoadOptions.Default(plmStrict) uključuje RequireFinalEndOfFileMarker, koji nedostajući %%EOF ili podatke iza zadnjeg (§7.5.5) iz upozorenja uzdiže u grešku. Xref audit dopunjuje provjere na razini objekata iz validacije object i xref streamova uz 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; // valjan xref, bez audit grešaka
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;
Gdje TPdf.LastLoadReport prestaje govoriti istinu?
TPdf.LastLoadReport je potpun tek nakon LoadDocument overloada koji uzima opcije, jer samo ti overloadi izvode audit bajtova. Uspješno Active := True piše izvještaj kompatibilnog moda bez audit bajtova, pa AuditAttempted ostaje False. Prije PDFiumPas v3.122.1 palo nije pisalo ništa, što je značilo da na zajedničkoj instanci LastLoadReport i dalje opisuje prethodnu datoteku, često s umirujućim plsLoaded. Od v3.122.1 svako palo loadanje zamjenjuje izvještaj: pala Active := True, koja komponentu i dalje ostavlja neaktivnom bez podizanja, i pali obični poziv LoadDocument ili LoadCustomDocument bilježe plsFailed s tekstom greške, opet bez audita. Još dvije rupe važne su u praksi. Validacija opcija i CheckInactive rade prije nego je izvještaj inicijaliziran, pa negativan AuditByteLimit ili već aktivna instanca podižu iznimku bez proizvedenog izvještaja. A NativeErrorCode ima smisla samo kad je PDFium stvarno pokušao parsirati; za nedostajuću datoteku wrapper podiže iznimku prije nego PDFium radi, pa logirajte ErrorMessage i tekst iznimke umjesto toga
Praktično pravilo kratko je. Zadržite Active := True za viewere vezane uz dizajner gdje je neaktivna komponenta prihvatljiv ishod. Svugdje drugdje, i ponajprije u batch i server kodu, stvarajte jedan TPdf po dokumentu, zovite LoadDocument(Options, Report), uhvatite iznimku koju podiže i logirajte Report.Status, NativeErrorCode i greškovne Issues zajedno s imenom datoteke. Cijena je par linija po call siteu, i svaki neuspjeh dobije pripisan pravoj datoteci sa svojim pravim uzrokom
Load report API, strogi mod i audit na razini bajtova isporučuju se uz PDFium Component za Delphi, C++Builder i Lazarus, uz renderanje, izvlačenje teksta, popunjavanje formi i PDF/A validaciju