PDFium Component for Delphi and Lazarusissa TPdf.Active := Truein sijoittaminen ei koskaan nosta kun PDF:n lataus epäonnistuu: TPdf.SetActive nappaa jokaisen poikkeuksen ja jättää komponentin inaktiiviseksi. Nähdäksesi oikean virheen kutsu sen sijaan TPdf.LoadDocument(Options, Report)ia. Kyseinen overload nostaa alkuperäisen poikkeuksen uudelleen ja täyttää TPdfLoadReportin latausstatuksella, natiivilla PDFium-virhekoodilla ja sillä tiedolla piti ristiviittaustaulukko rakentaa uudelleen
Ongelma nousee yleensä pintaan eräajokoodissa. Taulukointityö kävelee 13 oikean maailman PDF:n kansion läpi yhdellä jaetulla TPdfillä, ja 7 niistä palautuu epäonnistumisina. Yksikään kyseisistä 7 tiedostosta ei oikeasti ole rikki. Latauksen ympärillä olevat except-lohkot eivät koskaan laukea, loki syyttää vääriä tiedostonimiä, ja ensimmäinen näkyvä virhe on paljas EPdfError inaktiivisesta komponentista, nostettuna propertyn lukemisesta useita rivejä sen latauksen jälkeen joka oikeasti epäonnistui. Kaksi erillistä käyttäytymistä pinoavat kuvan, ja molemmat toimivat suunnitelmansa mukaan
Miksi TPdf.Active := True ei nosta kun PDF:n lataus epäonnistuu?
TPdf.SetActive käärii LoadDocumentin try..exceptin sisään joka nielee jokaisen poikkeusluokan ja jättää komponentin yksinkertaisesti inaktiiviseksi. Nielaisu on tahallinen: sama setteri ajaa kun lomakemuotoilija kytkkee Activeia IDE:ssä, eikä huono polku saa kaataa IDE:tä. Ajonaikana TPdf.Active raportoi vain onko natiivia dokumenttihandlea olemassa, joten epäonnistuneen latauksen jälkeen se lukee Falsen eikä muuta tapahdu. Mikä ikinä nostettiin on poissa, olipa se jäsentimen EPdfError, streamivirhe tai puolisidonnaisen pdfium.dll:n EAccessViolation. Artikkelissa pdfium.dll-latausvikojen diagnosoimisesta Delphissä kuvatut yksityiskohtaiset DLL-viestit saavuttavat käsittelijäsi vain kutsun kautta joka ei niele niitä
Pdf.FileName := FileName;
try
Pdf.Active := True; // SetActive nielaisee minkä tahansa latauspoikkeuksen
except
on E: Exception do
Log.Add(FileName + ': ' + E.Message); // ei koskaan suoritu
end;
// Virhe nousee pintaan täällä sen sijaan, yleisenä EPdfErrorina:
// 'Cannot perform this operation on an inactive Pdf1 component'
Log.Add(Format('%s: %d pages', [FileName, Pdf.PageCount]));
// Minimikorjaus olemassa olevaan koodiin: testaa Active heti sijoituksen jälkeen;
// versiosta v3.122.1 alkaen LastLoadReport säilyttää nielaistun virheen tekstin
Pdf.Active := True;
if not Pdf.Active then
Log.Add(FileName + ': load failed: ' + Pdf.LastLoadReport.ErrorMessage);
Virhe näkyy vihdoin ensimmäisessä vartiodussa kutsussa. TPdf.PageCount, kuten useimmat dokumenttipropertyt, alkaa CheckActiveilla, joka nostaa EPdfErrorin joka nimeää komponentin muttei tiedostoa eikä syytä. Pdf.Activein testaus heti sijoituksen jälkeen kääntää väärin attribuoidun kaatumisen rehelliseksi "epäonnistui"-merkinnäksi. Ennen PDFiumPas v3.122.1:ää syy oli menetetty kyseisessä pisteessä; versiosta v3.122.1 alkaen epäonnistunut sijoitus korvaa LastLoadReportin plsFailed-raportilla joka kantaa virhetekstiä, joten syy selviää hengissä. Poikkeusobjekti itse ja tavutason auditointi vaativat yhä toisen sisääntulopisteen
Miksi yhden TPdf:n uudelleenkäyttö epäonnistuu toisesta tiedostosta alkaen?
TPdf.FileNamein voi sijoittaa vain kun komponentti on inaktiivinen, joten jaettu instanssi hylkää toisen tiedoston ennen kuin se koskaan yrittää ladata sitä. TPdf.SetFileName alkaa CheckInactiveilla, ja sama vartio suojaa Passwordia ja FormFillia. Ensimmäisen onnistuneen latauksen jälkeen instanssi pysyy aktiivisena, seuraava sijoitus nostaa, ja jos eräajosilmukka nappaa kyseisen poikkeuksen ja jatkaa matkaansa, virhe laskeutuu uuden tiedostonimen alle kun vanha dokumentti on yhä auki. Sekoitettuna nielaisuihin latausvikoihin loki lakkaa vastaamasta todellisuutta. 13 tiedoston toistossa jaettu instanssi raportoi 7 epäonnistumista, kun taas tuore TPdf.Create(nil) dokumenttia kohti avasi kaikki 13. Active := False:n asettaminen tiedostojen välillä toimii myös, mutta yksi instanssi dokumenttia kohti pitää jokaisen tiedoston eristettynä rakenteesta johtuen
Mitä TPdf.LoadDocument TPdfLoadReportilla antaa sinulle?
TPdf.LoadDocument(const Options: TPdfLoadOptions; out Report: TPdfLoadReport) nostaa oikean poikkeuksen ja kertoo myös mitä tapahtui jäsennellyssä muodossa. Tiedosto-overload lataa FileNamein; sisaroverloadit ottavat TBytesin tai pointterin ja koon, ja LoadCustomDocument(AStream, AOwnsStream, Options, Report) kattaa streamit. Jokainen niistä validoi option, tarkistaa että instanssi on inaktiivinen, ajaa tavutason auditoinnin headerista, startxrefistä, xref-osioista ja %%EOF-merkistä, ja suorittaa sitten natiivin latauksen. Auditointi on rajattu samantyyppisillä rajoilla joita käsittelee artikkeli jäsentimen resurssibudjetit epäluotettaville PDF:eille: TPdfLoadOptions.Default asettaa AuditByteLimitin arvoon 256 MiB, MaxIssuesin arvoon 256, MaxXrefSectionsin arvoon 1024 ja MaxXrefEntriesin arvoon 4 000 000. Epäonnistuessa metodi asettaa Report.Status := plsFailedin ja nostaa uudelleen; koska Report kirjoitetaan paikalleen, sen sisältö selviää poikkeuksesta, ja kopio tallennetaan TPdf.LastLoadReportiin
Raportin kentät vastaavat kysymyksiin joita eräajoloki oikeasti tarvitsee. Status on yksi arvoista plsNotAttempted, plsLoaded, plsLoadedWithRecovery, plsRejected tai plsFailed. NativeErrorCode kantaa FPDF_GetLastErrorin, joten FPDF_ERR_PASSWORD (4) erottaa puuttuvan tai väärän salasanan vahingoittuneesta tiedostosta joka raportoidaan FPDF_ERR_FORMATina (3). UsedRecovery, CrossReferenceTableValid ja RecoveryRoute kertovat joutuiko PDFium rakentamaan xref-taulukon uudelleen, ja Issues listaa jokaisen auditointilöydöksen arvoilla Code, Severity, Offset, ObjectNumber ja MessageText, IssuesTruncated asetettuna kun MaxIssues typisti listan
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); // yksi instanssi dokumenttia kohti
try
Pdf.FileName := Files[I];
try
Pdf.LoadDocument(Options, Report);
except
on E: Exception do
begin
// Report täyttyy vaikka LoadDocument nosti poikkeuksen
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;
Milloin kannattaa ladata plmStrictilla?
Käytä plmStrictia aina kun hiljaisena korjattu tiedosto on pahempi kuin hylätty, kuten arkistovastaanotossa, todisteiden käsittelyssä tai allekirjoitusputkessa. PDFium rakentaa rikkinäisen ristiviittaustaulukon (ISO 32000-1 §7.5.4) hiljaisena uudelleen skannaamalla tiedostoa objektien varalta, mikä on loistavaa katselimelle ja ongelma kaikelle joka on käsiteltävä täsmälleen saamansa tavut. Natiivin latauksen jälkeen komponentti kysyy FPDF_DocumentHasValidCrossReferenceTableia. plmCompatible-modessa uudelleenrakennus tuottaa plsLoadedWithRecoveryin plus plicNativeCrossReferenceRebuild-varoituksen. plmStrict-modessa komponentti purkaa dokumentin, asettaa plsRejectedin, lisää plicStrictModeRejectedin ja nostaa EPdfErrorin viestillä "Strict PDF load rejected the document". Tiukka moda hylkää myös minkä tahansa auditointivirheen, ja TPdfLoadOptions.Default(plmStrict) kytkee päälle RequireFinalEndOfFileMarkerin, joka ylentää puuttuvan %%EOFin tai datan viimeisen jälkeisen (§7.5.5) varoituksesta virheeksi. xref-auditointi täydentää objektitason tarkistukset artikkelissa objekti- ja xref-streamien validoinnissa PDFium VCL:llä
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; // kelvollinen xref, ei auditointivirheitä
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;
Missä TPdf.LastLoadReport lakkaa kertomasta totuutta?
TPdf.LastLoadReport on täydellinen vain option ottavan LoadDocument-overloadin jälkeen, koska vain kyseiset overloadit ajavat tavuauditoinnin. Onnistunut Active := True kirjoittaa compatible-mode-raportin ilman tavuauditointia, joten AuditAttempted pysyy Falsena. Ennen PDFiumPas v3.122.1:ää epäonnistunut ei kirjoittanut mitään, mikä tarkoitti että jaetulla instanssilla LastLoadReport kuvasi yhä edellistä tiedostoa, usein rauhoittavalla plsLoadedilla. Versiosta v3.122.1 alkaen jokainen epäonnistunut lataus korvaa raportin: epäonnistunut Active := True, joka yhä jättää komponentin inaktiiviseksi nostamatta, ja epäonnistunut pelkkä LoadDocument- tai LoadCustomDocument-kutsu kirjaavat plsFailedin virhetekstillä, jälleen ilman auditointia. Kaksi muuta aukkoa merkitsevät käytännössä. Option validointi ja CheckInactive ajavat ennen kuin raportti alustetaan, joten negatiivinen AuditByteLimit tai jo aktiivinen instanssi nostaa tuottamatta raporttia. Ja NativeErrorCode on merkityksellinen vain kun PDFium oikeasti yritti jäsennystä; puuttuvalle tiedostolle kääre nostaa ennen kuin PDFium ajaa, joten lokita sen sijaan ErrorMessage ja poikkeusteksti
Käytännön sääntö on lyhyt. Pidä Active := True suunnittelijaan sidotuille katselimille joissa inaktiivinen komponentti on hyväksyttävä lopputulos. Kaikkialla muualla, ja ennen kaikkea eräajo- ja palvelinkoodissa, luo yksi TPdf dokumenttia kohti, kutsu LoadDocument(Options, Report)ia, nappaa sen nostama poikkeus ja lokita Report.Status, NativeErrorCode ja virhetason Issuesit tiedostonimen kanssa. Hintana on muutama rivi kutsumispaikkaa kohden, ja jokainen epäonnistuminen attribuoidaan oikealle tiedostolle todellisella syyllään
Latausraportti-API, tiukka moda ja tavutason auditointi toimitetaan mukana PDFium Component for Delphi, C++Builder and Lazarus -paketissa, renderöinnin, tekstin poimin, lomakkeiden täytön ja PDF/A-validoinnin rinnalla