V PDFium Component za Delphi in Lazarus dodelitev TPdf.Active := True nikoli ne sproži, ko nalaganje PDF spodleti: TPdf.SetActive ujame vsako izjemo in komponento pusti nedejavno. Da vidite pravo napako, namesto tega pokličite TPdf.LoadDocument(Options, Report). Ta preobremenitev znova sproži izvirno izjemo in napolni TPdfLoadReport s statusom nalaganja, izvorno kodo napake PDFium in s tem, ali je bilo treba tabelo navzkrižnih sklicev znova zgraditi
Težava se običajno pokaže v paketni kodi. Opravilo za izločanje tabel obide mapo 13 PDF-jev iz resničnega sveta z enim skupnim TPdf, 7 od njih pa se vrne kot spodleteli. Nobena od teh 7 datotek ni dejansko pokvarjena. Bloki except okoli nalaganja se nikoli ne sprožijo, dnevnik krivi napačna imena datotek, prva vidna napaka pa je goli EPdfError o nedejavni komponenti, sprožen iz branja lastnosti nekaj vrstic po nalaganju, ki je res spodletelo. Dve ločeni obnašanji se zložita in izdelata to sliko, obe pa delujeta, kot je načrtovano
Zakaj TPdf.Active := True ne sproži, ko nalaganje PDF spodleti?
TPdf.SetActive ovije LoadDocument v try..except, ki pogoltne vsak razred izjem in komponento preprosto pusti nedejavno. Pogltanje je namerno: isti nastavljavnik teče, ko oblikovalec obrazcev preklopi Active v IDE, in napačna pot ne sme podreti IDE. Ob izvajanju TPdf.Active le poroča, ali obstaja izvorni ročaj dokumenta, tako da po spodletelem nalaganju prebere False in se nič drugega ne zgodi. Karkoli je bilo sproženo, je izginilo, naj bo to EPdfError razčlenjevalnika, napaka toka ali EAccessViolation od polvezane pdfium.dll. Podrobna sporočila DLL, opisana v diagnosticiranju spodletelih nalaganj pdfium.dll v Delphiju, dosežejo vaš ročaj le skozi klic, ki jih ne pogoltne
Pdf.FileName := FileName;
try
Pdf.Active := True; // SetActive pogoltne vsako izjemo nalaganja
except
on E: Exception do
Log.Add(FileName + ': ' + E.Message); // se nikoli ne izvede
end;
// Spodletelost se pokaže tukaj namesto tega, kot generični EPdfError:
// 'Cannot perform this operation on an inactive Pdf1 component'
Log.Add(Format('%s: %d pages', [FileName, Pdf.PageCount]));
// Minimalen popravek za obstoječo kodo: preizkusite Active takoj po dodelitvi;
// od v3.122.1 LastLoadReport obdrži besedilo poglotjene napake
Pdf.Active := True;
if not Pdf.Active then
Log.Add(FileName + ': load failed: ' + Pdf.LastLoadReport.ErrorMessage);
Spodletelost se končno pokaže pri prvem branjenem klicu. TPdf.PageCount, kot večina lastnosti dokumenta, začne s CheckActive, ki sproži EPdfError, ki poimenuje komponento, ne datoteke in ne vzroka. Preizkus Pdf.Active takoj po dodelitvi spremeni napačno pripisan trk v iskreni vnos »spodletelo«. Pred PDFiumPas v3.122.1 je bil vzrok na tej točki izgubljen; od v3.122.1 spodletela dodelitev zamenja LastLoadReport s poročilom plsFailed, ki nosi besedilo napake, tako da vzrok preživi. Objekt izjeme sam in pregled na ravni bajtov pa še vedno zahtevata drugačno vstopno točko
Zakaj ponovna uporaba enega TPdf spodleti od druge datoteke naprej?
TPdf.FileName se sme dodeliti le, medtem ko je komponenta nedejavna, tako da skupni primerek zavrne drugo datoteko, preden kdaj poskusi, da bi jo naložil. TPdf.SetFileName začne s CheckInactive, isti branilec pa ščiti Password in FormFill. Po prvem uspešnem nalaganju primerek ostane dejaven, naslednja dodelitev sproži, in če paketna zanka ujame to izjemo ter gre naprej, napaka pristne pod novim imenom datoteke, medtem ko je stari dokument še vedno odprt. Pomešano s poglotjenimi spodletelimi nalaganji se dnevnik preneha ujemati z realnostjo. V reprodukciji s 13 datotekami je skupni primerek prijavil 7 spodletelosti, svež TPdf.Create(nil) na dokument pa je odprl vseh 13. Nastavitev Active := False med datotekami deluje tudi, en primerek na dokument pa drži vsako datoteko izolirano po zgradbi
Kaj vam da TPdf.LoadDocument s TPdfLoadReport?
TPdf.LoadDocument(const Options: TPdfLoadOptions; out Report: TPdfLoadReport) sproži pravo izjemo in vam v strukturirani obliki pove tudi, kaj se je zgodilo. Preobremenitev datoteke naloži FileName; sesterske preobremenitve vzamejo TBytes ali kazalec in velikost, LoadCustomDocument(AStream, AOwnsStream, Options, Report) pa pokriva tokove. Vsaka validira možnosti, preveri, da je primerek nedejaven, požene pregled na ravni bajtov po glavi, startxref, odsekih xref in markerju %%EOF, nato pa izvede izvorno nalaganje. Pregled je omejen s tovrstnimi mejami, kot jih razpravlja proračun virov razčlenjevalnika za nezaupljive PDF-je: TPdfLoadOptions.Default nastavi AuditByteLimit na 256 MiB, MaxIssues na 256, MaxXrefSections na 1024 in MaxXrefEntries na 4.000.000. Ob spodletelosti metoda nastavi Report.Status := plsFailed in znova sproži; ker se Report zapiše na mestu, njegova vsebina preživi izjemo, kopija pa se shrani v TPdf.LastLoadReport
Polja poročila odgovorijo na vprašanja, ki jih paketni dnevnik dejansko potrebuje. Status je en od plsNotAttempted, plsLoaded, plsLoadedWithRecovery, plsRejected ali plsFailed. NativeErrorCode drži FPDF_GetLastError, tako da FPDF_ERR_PASSWORD (4) loči manjkajoče ali napačno geslo od pokvarjene datoteke, prijavljene kot FPDF_ERR_FORMAT (3). UsedRecovery, CrossReferenceTableValid in RecoveryRoute povedo, ali je moral PDFium znova zgraditi tabelo xref, Issues pa našteva vsako ugotovitev pregleda s Code, Severity, Offset, ObjectNumber in MessageText, z IssuesTruncated, nastavljenim, kadar je MaxIssues seznam skrajšal
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); // en primerek na dokument
try
Pdf.FileName := Files[I];
try
Pdf.LoadDocument(Options, Report);
except
on E: Exception do
begin
// Report je napolnjen, čeprav je LoadDocument sprožil
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;
Kdaj naj nalagate s plmStrict?
Uporabite plmStrict vsakič, kadar je tiho popravljena datoteka slabša od zavrnjene, na primer pri prevzemu arhivov, ravnanju z dokazi ali cevovodu podpisovanja. PDFium tiho znova zgradi pokvarjeno tabelo navzkrižnih sklicev (ISO 32000-1 §7.5.4) z iskanjem objektov po datoteki, kar je odlično za gledalnik in težava za vse, kar mora obdelati točno bajte, ki jih je dobilo. Po izvornem nalaganju komponenta vpraša FPDF_DocumentHasValidCrossReferenceTable. V načinu plmCompatible znovna zgradba da plsLoadedWithRecovery plus opozorilo plicNativeCrossReferenceRebuild. V načinu plmStrict komponenta razloži dokument, nastavi plsRejected, doda plicStrictModeRejected in sproži EPdfError z »strog način nalaganja PDF je zavrnil dokument«. Strogi način zavrne tudi vsako napako pregleda, TPdfLoadOptions.Default(plmStrict) pa vklopi RequireFinalEndOfFileMarker, ki manjkajoč %%EOF ali podatke za zadnjim (§7.5.5) poviša iz opozorila v napako. Pregled xref dopolnjuje preizkuse na ravni objektov v validiranju tokov objektov in xref s 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; // veljaven xref, brez napak pregleda
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;
Kje se TPdf.LastLoadReport preneha izjavljati resnično?
TPdf.LastLoadReport je popoln šele po preobremenitvi LoadDocument, ki vzame možnosti, ker le te preobremenitve poženejo bajtni pregled. Uspešno Active := True zapiše poročilo v združljivem načinu brez bajtnega pregleda, tako da AuditAttempted ostane False. Pred PDFiumPas v3.122.1 spodletelo ni zapisalo ničesar, kar je pomenilo, da je na skupnem primerku LastLoadReport še vedno opisoval prejšnjo datoteko, pogosto z umirjajočim plsLoaded. Od v3.122.1 vsako spodletelo nalaganje zamenja poročilo: spodletelo Active := True, ki še vedno pusti komponento nedejavno brez sprožitve, in spodletel klic navadnega LoadDocument ali LoadCustomDocument zabeležita plsFailed z besedilom napake, spet brez pregleda. Dve vrzeli še pomembata v praksi. Validacija možnosti in CheckInactive tečeta, preden se poročilo inicializira, tako da negativen AuditByteLimit ali že dejaven primerek sprožita brez izdelave poročila. In NativeErrorCode je smiselen le, kadar je PDFium dejansko poskusil razčlenitev; za manjkajočo datoteko ovijalnik sproži, preden teče PDFium, zato namesto tega beležite ErrorMessage in besedilo izjeme
Praktično pravilo je kratko. Obdržite Active := True za gledalnike, vezane na oblikovalca, kjer je nedejavna komponenta sprejemljiv izid. Povsod drugje, predvsem pa v paketni in strežniški kodi, ustvarite en TPdf na dokument, pokličite LoadDocument(Options, Report), ujemite izjemo, ki jo sproži, in beležite Report.Status, NativeErrorCode ter Issues na ravni napake skupaj z imenom datoteke. Cena je nekaj vrstic na klicno mesto, vsaka spodletelost pa dobi pripisano pravo datoteko s pravim vzrokom
API poročila o nalaganju, strogi način in pregled na ravni bajtov odplujejo z PDFium Component za Delphi, C++Builder in Lazarus, skupaj z izrisovanjem, izločanjem besedil, izpolnjevanjem obrazcev in validacijo PDF/A