U PDFium Component za Delphi i Lazarus, dodeljivanje TPdf.Active := True nikada ne diže izuzetak kad PDF padne na učitavanju: TPdf.SetActive uhvati svaki izuzetak i ostavi komponentu neaktivnom. Da biste videli pravu grešku, pozovite umesto toga TPdf.LoadDocument(Options, Report). Taj overload ponovo diže prvobitni izuzetak i puni TPdfLoadReport statusom učitavanja, nativnim PDFium kodom greške i podacima da li je tabela unakrsnih referenci morala da se ponovo izgradi
Problem obično isplivava u batch kodu. Posao za vađenje tabela prelazi preko fascikle sa 13 realnih PDF-ova sa jednim deljenim TPdf-om, i 7 njih se vraća kao neuspesi. Nijedan od tih 7 fajlova zapravo nije pokvaren. except blokovi oko učitavanja nikada ne opale, log krivi pogrešna imena fajlova, a prva vidljiva greška goli je EPdfError o neaktivnoj komponenti, podignut iz čitanja svojstva nekoliko redova posle učitavanja koje je zaista palo. Dva odvojena ponašanja naslažu se u tu sliku, i oba rade kako su dizajnirana
Zašto TPdf.Active := True ne diže izuzetak kad PDF padne na učitavanju?
TPdf.SetActive obavija LoadDocument u try..except koji guta svaku klasu izuzetaka i jednostavno ostavi komponentu neaktivnom. Gutnja je namerna: isti setter radi kad form designer prebaci Active u IDE-u, i loša putanja ne sme da sruši IDE. U vreme izvršavanja TPdf.Active samo javlja da li nativni handle dokumenta postoji, pa posle palog učitavanja čita se False i ništa drugo se ne dešava. Šta god je podignuto, nestalo je: bio to EPdfError iz parsera, greška streama ili EAccessViolation iz napola povezanog pdfium.dll-a. Detaljne DLL poruke opisane u tekstu o dijagnostikovanju padova učitavanja pdfium.dll-a u Delphi-ju do vašeg hendlera stižu samo kroz poziv koji ih ne guta
Pdf.FileName := FileName;
try
Pdf.Active := True; // SetActive guta svaki izuzetak učitavanja
except
on E: Exception do
Log.Add(FileName + ': ' + E.Message); // nikada se ne izvršava
end;
// Neuspeh se umesto toga pokazuje ovde, kao generički EPdfError:
// 'Cannot perform this operation on an inactive Pdf1 component'
Log.Add(Format('%s: %d pages', [FileName, Pdf.PageCount]));
// Minimalna popravka postojećeg koda: testirajte Active odmah posle dodele;
// 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);
Neuspeh se konačno pokazuje na prvom zaštićenom pozivu. TPdf.PageCount, kao i većina svojstava dokumenta, počinje sa CheckActive, koji diže EPdfError koji imenuje komponentu, ali ne fajl i ne uzrok. Testiranje Pdf.Active odmah posle dodele pretvara pogrešno pripisan pad u pošten unos „palo je". Pre PDFiumPas v3.122.1 razlog je u tom trenutku bio izgubljen; od v3.122.1 pala dodela zamenjuje LastLoadReport izveštajem sa plsFailed koji nosi tekst greške, pa uzrok preživi. Sam objekat izuzetka i audit na nivou bajtova i dalje traže drugu tačku ulaza
Zašto ponovna upotreba jednog TPdf-a pada od drugog fajla nadalje?
TPdf.FileName može se dodeliti samo dok je komponenta neaktivna, pa deljena instanca odbija drugi fajl pre nego što ikada pokuša da ga učita. TPdf.SetFileName počinje sa CheckInactive-om, i ista čuvar brani Password i FormFill. Posle prvog uspešnog učitavanja instanca ostaje aktivna, sledeća dodela diže izuzetak, i ako batch petlja taj izuzetak uhvati i krene dalje, greška dospe pod novo ime fajla dok je stari dokument još otvoren. Pomešano s progutanim neuspesima učitavanja, log prestaje da odgovara stvarnosti. U reprodukciji sa 13 fajlova deljena instanca prijavila je 7 neuspeha, dok je svež TPdf.Create(nil) po dokumentu otvorio svih 13. Postavljanje Active := False između fajlova takođe radi, ali jedna instanca po dokumentu drži svaki fajl izolovanim već po konstrukciji
Šta vam daje TPdf.LoadDocument sa TPdfLoadReport-om?
TPdf.LoadDocument(const Options: TPdfLoadOptions; out Report: TPdfLoadReport) diže pravi izuzetak i kaže vam šta se desilo i u strukturiranom obliku. Fajl overload učitava FileName; srodni overloadi uzimaju TBytes ili pokazivač i veličinu, a LoadCustomDocument(AStream, AOwnsStream, Options, Report) pokriva streamove. Svaki validira opcije, proverava da je instanca neaktivna, sprovodi audit na nivou bajtova nad headerom, startxref-om, xref sekcijama i %%EOF markerom, a tek potom radi nativno učitavanje. Audit ograničavaju iste vrste granica o kojima piše budžet resursa parsera za nepoverljive PDF-ove: TPdfLoadOptions.Default postavlja AuditByteLimit na 256 MiB, MaxIssues na 256, MaxXrefSections na 1024 i MaxXrefEntries na 4.000.000. Pri padu metod postavlja Report.Status := plsFailed i ponovo diže; pošto se Report piše na mestu, njegov sadržaj preživi izuzetak, i kopija se čuva u TPdf.LastLoadReport-u
Polja izveštaja odgovaraju na pitanja koja batch log zaista 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 pogrešnu lozinku od oštećenog fajla prijavljenog kao FPDF_ERR_FORMAT (3). UsedRecovery, CrossReferenceTableValid i RecoveryRoute govore da li je PDFium morao da ponovo izgradi xref tabelu, a Issues nabraja svaki nalaz audita sa Code, Severity, Offset, ObjectNumber i MessageText, uz IssuesTruncated postavljen kada je MaxIssues skratio listu
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 digao izuzetak
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;
Kada se učitavati sa plmStrict?
Koristite plmStrict kad god je tiho popravljen fajl gori od odbijenog, npr. kod prijema u arhivu, rukovanja dokazima ili pipeline-a za potpisivanje. PDFium tiho rekonstruiše pokvarenu tabelu unakrsnih referenci (ISO 32000-1 §7.5.4) skenirajući fajl za objekte, što je sjajno za pregledač a problem za sve što mora obrađivati baš bajtove koje je dobilo. Posle nativnog učitavanja komponenta pita FPDF_DocumentHasValidCrossReferenceTable. U plmCompatible režimu ponovna izgradnja daje plsLoadedWithRecovery plus upozorenje plicNativeCrossReferenceRebuild. U plmStrict režimu komponenta istovara dokument, postavlja plsRejected, dodaje plicStrictModeRejected i diže EPdfError sa „Strict PDF load rejected the document". Strogi režim odbija i svaku grešku audita, a TPdfLoadOptions.Default(plmStrict) uključuje RequireFinalEndOfFileMarker, koji nedostajući %%EOF ili podatke posle poslednjeg (§7.5.5) unapređuje iz upozorenja u grešku. Xref audit dopunjuje provere na nivou objekata iz teksta o validiranju 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; // validan xref, bez grešaka audita
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;
Gde TPdf.LastLoadReport prestaje da govori istinu?
TPdf.LastLoadReport je kompletan tek posle LoadDocument overloada koji uzima opcije, jer samo ti overloadi sprovode bajt audit. Uspešna Active := True dodela piše izveštaj u compatible režimu bez bajt audita, pa AuditAttempted ostaje False. Pre PDFiumPas v3.122.1 pala dodela nije pisala ništa, što je značilo da na deljenoj instanci LastLoadReport još opisuje prethodni fajl, često sa utešnim plsLoaded. Od v3.122.1 svako palo učitavanje zamenjuje izveštaj: pala Active := True dodela, koja i dalje ostavlja komponentu neaktivnom bez dizanja, i pao običan poziv LoadDocument ili LoadCustomDocument beleže plsFailed sa tekstom greške, opet bez audita. Još dve rupe su bitne u praksi. Validacija opcija i CheckInactive rade pre nego što se izveštaj inicijalizuje, pa negativan AuditByteLimit ili već aktivna instanca dižu izuzetak ne proizvevši izveštaj. I NativeErrorCode ima smisla tek kad je PDFium zaista pokušao parsiranje; za fajl koji ne postoji omot diže izuzetak pre nego što PDFium krene, pa logujte ErrorMessage i tekst izuzetka
Praktično pravilo je kratko. Zadržite Active := True za pregledače vezane za dizajnera, gde je neaktivna komponenta prihvatljiv ishod. Svuda drugde, a pre svega u batch i server kodu, pravite jedan TPdf po dokumentu, zovite LoadDocument(Options, Report), uhvatite izuzetak koji diže i logujte Report.Status, NativeErrorCode i Issues na nivou greške zajedno sa imenom fajla. Cena je nekoliko redova po pozivnom mestu, i svaki neuspeh dobije pripisan pravi fajl sa svojim pravim uzrokom
Load report API, strogi režim i audit na nivou bajtova stižu uz PDFium Component za Delphi, C++Builder i Lazarus, uz renderovanje, izvlačenje teksta, popunjavanje formi i PDF/A validaciju