A Delphihez és Lazarushoz készült PDFium Componentben a TPdf.Active := True sosem dob, ha egy PDF nem tölt be: a TPdf.SetActive minden kivételt elnyel, és a komponenst inaktívan hagyja. A valódi hibáért hívja inkább a TPdf.LoadDocument(Options, Report) túlterhelést. Az újradobja az eredeti kivételt, és egy TPdfLoadReport-ot tölt meg a betöltési állapottal, a natív PDFium hibakóddal, valamint azzal, hogy az xref táblát újjá kellett-e építeni
A probléma jellemzően kötegelt kódban mutatkozik meg. Egy táblakinyerő feladat végigsétál 13 valós világú PDF-en egyetlen közös TPdf-fel, és 7 közülük hibaként jön vissza. Az a 7 fájl közül egyik sem törött ténylegesen. A betöltés körüli except blokkok sosem futnak le, a napló rossz fájlneveket vádol, és az első látható hiba egy csupasz EPdfError egy inaktív komponensről, amely a ténylegesen elbukott betöltés után több sorral egy tulajdonságolvasásból érkezik. Két külön viselkedés rakódik egymásra, hogy ezt a képet adja, és mindkettő úgy működik, ahogy tervezték
Miért nem dob a TPdf.Active := True, ha egy PDF nem tölt be?
A TPdf.SetActive a LoadDocument-et egy try..except-be csomagolja, amely minden kivételosztályt elnyel, és egyszerűen inaktívan hagyja a komponenst. Az elnyelés szándékos: ugyanez a setter fut, amikor egy formtervező az Active-ot állítgatja az IDE-ben, és egy rossz út nem döntheti félre az IDE-t. Futásidőben a TPdf.Active egyszerűen azt jelenti, hogy létezik-e natív dokumentumhandle, így egy elbukott betöltés után False-ot olvas, és semmi más nem történik. Amit dobottak, az eltűnt — legyen az az elemzőtől való EPdfError, egy stream hiba vagy egy félig bekötött pdfium.dll-től való EAccessViolation. A pdfium.dll betöltési hibáinak diagnosztizálásáról Delphiben szóló, részletes DLL üzenetek csak olyan híváson át érnek el az Ön kezelőjéhez, amely nem nyeli el őket
Pdf.FileName := FileName;
try
Pdf.Active := True; // a SetActive elnyel minden betöltési kivételt
except
on E: Exception do
Log.Add(FileName + ': ' + E.Message); // sosem fut le
end;
// A hiba itt bukik fel helyette, általános EPdfError-ként:
// 'Cannot perform this operation on an inactive Pdf1 component'
Log.Add(Format('%s: %d pages', [FileName, Pdf.PageCount]));
// Minimális javítás a meglévő kódhoz: az értékadás után azonnal tesztelje az Active-ot;
// a v3.122.1 óta a LastLoadReport őrzi az elnyelt hiba szövegét
Pdf.Active := True;
if not Pdf.Active then
Log.Add(FileName + ': load failed: ' + Pdf.LastLoadReport.ErrorMessage);
A hiba végre az első védett hívásnál mutatkozik meg. A TPdf.PageCount, a legtöbb dokumentumtulajdonsághoz hasonlóan, CheckActive-cal kezdődik, amely EPdfError-t dob, megnevezve a komponenst, de nem a fájlt és nem az okot. A Pdf.Active azonnali tesztelése az értékadás után félreattribuált összeomlást becsületes „elbukott” bejegyzéssé alakít. A PDFiumPas v3.122.1 előtt az ok ezen a ponton elveszett; a v3.122.1 óta az elbukott értékadás plsFailed riporttal helyettesíti a LastLoadReport-ot, amely hordozza a hibaszöveget, tehát az ok túlél. Maga a kivételobjektum és a bájtszintű audit más belépési pontot kíván
Miért bukik el egyetlen TPdf újrafelhasználása a második fájltól kezdve?
A TPdf.FileName csak akkor rendelhető hozzá, amíg a komponens inaktív, így egy megosztott instance még megpróbálás előtt elutasítja a második fájlt. A TPdf.SetFileName CheckInactive-cal kezdődik, és ugyanez az őr védi a Password-t és a FormFill-t is. Az első sikeres betöltés után az instance aktív marad, a következő értékadás dob, és ha a kötegelt hurok elkapja azt a kivételt, és továbblép, a hiba az új fájlnév alá landol, miközben a régi dokumentum még nyitva van. Az elnyelt betöltési hibákkal keveredve a napló abbahagyja a valóság tükrözését. A 13 fájlos reprodukcióban egy megosztott instance 7 hibát jelentett, miközben dokumentumonként egy friss TPdf.Create(nil) mind a 13-at megnyitotta. A Active := False beállítása a fájlok közt szintén működik, de dokumentumonként egy instance konstrukcióval tartja minden fájlt elkülönítve
Mit ad Önnek a TPdf.LoadDocument TPdfLoadReport-tal?
A TPdf.LoadDocument(const Options: TPdfLoadOptions; out Report: TPdfLoadReport) felhányja a valódi kivételt, és strukturált formában is elmondja, mi történt. A fájl túlterhelés a FileName-t tölti be; a testvér túlterhelések TBytes-t vagy pointert és méretet kapnak, a LoadCustomDocument(AStream, AOwnsStream, Options, Report) pedig a streameket fedi le. Mindegyik érvényesíti az opciókat, ellenőrzi, hogy az instance inaktív-e, bájtszintű auditot futtat a fejlécre, a startxref-re, az xref szekciókra és a %%EOF jelölőre, majd végrehajtja a natív betöltést. Az auditot ugyanolyan jellegű korlátok határolják, amelyekről a parser erőforrás-keretei megbízhatatlan PDF-ekhez szólnak: a TPdfLoadOptions.Default az AuditByteLimit-et 256 MiB-re, a MaxIssues-t 256-ra, a MaxXrefSections-t 1024-re, a MaxXrefEntries-t 4 000 000-ra állítja. Hibánál a metódus Report.Status := plsFailed-re áll, és újradobja; mivel a Report a helyén íródik, tartalma túléli a kivételt, és egy másolat a TPdf.LastLoadReport-ba kerül
A riport mezői megválaszolják azokat a kérdéseket, amelyeket egy kötegelt napló ténylegesen feltesz. A Status a plsNotAttempted, plsLoaded, plsLoadedWithRecovery, plsRejected vagy plsFailed egyike. A NativeErrorCode a FPDF_GetLastError-t tartja, így a FPDF_ERR_PASSWORD (4) elválasztja a hiányzó vagy rossz jelszót a sérült fájltól, amely FPDF_ERR_FORMAT-ként (3) jelentkezik. A UsedRecovery, a CrossReferenceTableValid és a RecoveryRoute elmondja, hogy a PDFiumnak újjá kellett-e építenie az xref táblát, az Issues pedig felsorolja az egyes audit megállapításokat Code-dal, Severity-vel, Offset-tal, ObjectNumber-rel és MessageText-tel, IssuesTruncated-dal arra az esetre, amikor a MaxIssues megrövidíti a listát
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); // dokumentumonként egy instance
try
Pdf.FileName := Files[I];
try
Pdf.LoadDocument(Options, Report);
except
on E: Exception do
begin
// a Report kitöltődik, holott a LoadDocument kivételt dobott
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;
Mikor töltse be plmStrict-tel?
Akkor használja a plmStrict-et, amikor egy csendben javított fájl rosszabb, mint egy elutasított — például archívum-bevételnél, bizonyítékkezelésnél vagy alágírási futószalagnál. A PDFium csendben újjáépít egy törött kereszthivatkozási táblát (ISO 32000-1 §7.5.4) az objektumokért végigsöprve a fájlt, ami nagyszerű egy nézőnek, és probléma bárminek, amelynek pontosan azokat a bájtokat kell feldolgoznia, amelyeket kapott. A natív betöltés után a komponens rákérdez a FPDF_DocumentHasValidCrossReferenceTable-re. plmCompatible módban az újraépítés plsLoadedWithRecovery-t ad plusz egy plicNativeCrossReferenceRebuild figyelmeztetést. plmStrict módban a komponens kirakja a dokumentumot, plsRejected-et állít, felveszi a plicStrictModeRejected-et, és EPdfError-t dob „Strict PDF load rejected the document” üzenettel. A szigorú mód minden audit hibát is elutasít, a TPdfLoadOptions.Default(plmStrict) pedig bekapcsolja a RequireFinalEndOfFileMarker-t, amely a hiányzó %%EOF-ot vagy az utolsó utáni adatot (§7.5.5) figyelmeztetésből hibává lépteti. Az xref audit kiegészíti az objektumszintű ellenőrzéseket, amelyeket a objektum- és xref streamek érvényesítése PDFium VCL-lel tárgyal
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; // érvényes xref, nincs audit hiba
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;
Hol hagyja abba a TPdf.LastLoadReport az igazmondást?
A TPdf.LastLoadReport csak az opciókat kapó LoadDocument túlterhelés után teljes, mert csak azok a túlterhelések futtatják a bájtauditot. Egy sikeres Active := True kompatibilis módú riportot ír bájtaudit nélkül, így az AuditAttempted False marad. A PDFiumPas v3.122.1 előtt egy elbukott semmit sem írt, ami azt jelentette, hogy megosztott instancen a LastLoadReport továbbra is az előző fájlt írta le, gyakran megnyugtató plsLoaded-dal. A v3.122.1 óta minden elbukott betöltés helyettesíti a riportot: egy elbukott Active := True, amely továbbra is inaktívan hagyja a komponenst dobás nélkül, és egy elbukott sima LoadDocument vagy LoadCustomDocument hívás plsFailed-et rögzít a hibaszöveggel, szintén audit nélkül. Két további rés számít a gyakorlatban. Az opcióvalidáció és a CheckInactive a riport inicializálása előtt fut, tehát negatív AuditByteLimit vagy már aktív instance riport nélkül dob. És a NativeErrorCode csak akkor értelmes, ha a PDFium ténylegesen megkísérelte az elemzést; hiányzó fájlnál a wrapper még a PDFium futása előtt dob, ezért naplózza inkább az ErrorMessage-et és a kivételszöveget
A gyakorlati szabály rövid. Tartsa meg az Active := True-t tervezőhöz kötött nézőknél, ahol az inaktív komponens elfogadható kimenet. Minden más helyen, és mindenekelőtt kötegelt és szerverkódban legyen dokumentumonként egy TPdf, hívjon LoadDocument(Options, Report)-ot, kapja el a dobott kivételt, és naplózza a Report.Status-t, a NativeErrorCode-ot és a hibaszintű Issues-t a fájlnévvel együtt. Az ára néhány sor hívóhelyenként, és minden hiba a helyes fájlhoz kerül a valódi okával együtt
A load riport API, a szigorú mód és a bájtszintű audit a Delphihez, C++Builderhez és Lazarushoz készült PDFium Componenttel érkezik, a rajzolás, a szövegkinyerés, az űrlapkitöltés és a PDF/A-ellenőrzés mellett