В PDFium Component за Delphi и Lazarus присвояването на TPdf.Active := True никога не вдига, когато PDF се провали при зареждане: TPdf.SetActive улавя всяко exception и оставя компонента неактивен. За да видите реалната грешка, викайте вместо това TPdf.LoadDocument(Options, Report). Този overload пре-вдига оригиналното exception и пълни TPdfLoadReport със статуса на зареждането, нативния error код на PDFium и дали cross-reference таблицата е трябвало да бъде преизградена
Проблемът обикновено излиза наяве в batch код. Job за извличане на таблици обхожда папка с 13 реални PDF с един споделен TPdf и 7 от тях се връщат като провали. Нито един от 7-те файла всъщност не е повреден. except блоковете около зареждането никога не се задействат, логът обвинява грешните имена на файлове, а първата видима грешка е гол EPdfError за неактивен компонент, вдигнат от четене на свойство няколко реда след зареждането, което реално се е провалило. Два отделни поведения се наслагват, за да се получи тази картина, и и двете работят както е замислено
Защо TPdf.Active := True не вдига, когато PDF се провали при зареждане?
TPdf.SetActive опакова LoadDocument в try..except, който поглъща всеки exception клас и просто оставя компонента неактивен. Поглъщането е нарочно: същият setter върви, когато form designer превключва Active в IDE-то, а лош път не бива да срина IDE-то. По време на изпълнение TPdf.Active просто докладва дали съществува нативен document handle, така че след провалено зареждане чете False и нищо друго не става. Каквото и да е било вдигнато, изчезва — да речем EPdfError от parser-а, stream грешка или EAccessViolation от полузакачен pdfium.dll. Подробните DLL съобщения, описани в диагностицирането на pdfium.dll load провали в Delphi, стигат до вашия handler само чрез извикване, което не ги поглъща
Pdf.FileName := FileName;
try
Pdf.Active := True; // SetActive поглъща всяко load exception
except
on E: Exception do
Log.Add(FileName + ': ' + E.Message); // никога не се изпълнява
end;
// Провалът излиза тук вместо това, като генеричен EPdfError:
// 'Cannot perform this operation on an inactive Pdf1 component'
Log.Add(Format('%s: %d pages', [FileName, Pdf.PageCount]));
// Минимален fix за съществуващ код: проверявайте Active веднага след присвояването;
// от v3.122.1 LastLoadReport пази текста на погълнатата грешка
Pdf.Active := True;
if not Pdf.Active then
Log.Add(FileName + ': load failed: ' + Pdf.LastLoadReport.ErrorMessage);
Провалът най-сетне излиза при първото закрилено извикване. TPdf.PageCount, както повечето document свойства, започва с CheckActive, който вдига EPdfError, назоваващ компонента, но не и файла и не причината. Проверката на Pdf.Active веднага след присвояването превръща погрешно приписано срутване в честен запис „провалило се“. Преди PDFiumPas v3.122.1 причината се губеше в този момент; от v3.122.1 проваленото присвояване заменя LastLoadReport с доклад plsFailed, който носи текста на грешката, така че причината оцелява. Самият exception обект и одитът на байтово ниво все още изискват друга входна точка
Защо преизползването на един TPdf се провали от втория файл нататък?
TPdf.FileName може да бъде присвояван само докато компонентът е неактивен, така че споделена инстанция отхвърля втория файл, преди изобщо да се опита да го зареди. TPdf.SetFileName започва с CheckInactive, а същата защита пази Password и FormFill. След първото успешно зареждане инстанцията остава активна, следващото присвояване вдига, и ако batch цикълът улови това exception и продължи, грешката каца под новото име на файл, докато старият документ е все още отворен. Смесено с погълнатите load провали, логът спира да съответства на реалността. В 13-файловата репродукция споделена инстанция докладва 7 провала, докато свеж TPdf.Create(nil) на документ отвори всичките 13. Задаването на Active := False между файловете също работи, но една инстанция на документ държи всеки файл изолиран по конструкция
Какво ви дава TPdf.LoadDocument с TPdfLoadReport?
TPdf.LoadDocument(const Options: TPdfLoadOptions; out Report: TPdfLoadReport) вдига реалното exception и също ви казва структурирано какво се е случило. Файловият overload зарежда FileName; съседните overload-и приемат TBytes или пойнтер и размер, а LoadCustomDocument(AStream, AOwnsStream, Options, Report) покрива stream-овете. Всяко едно валидира опциите, проверява, че инстанцията е неактивна, изпълнява одит на байтово ниво на header-а, startxref, xref секциите и маркера %%EOF, после извършва нативното зареждане. Одитът е ограничен от същия вид лимити, обсъдени в ресурсните бюджети на parser-а за недоверени PDF-и: TPdfLoadOptions.Default задава AuditByteLimit на 256 MiB, MaxIssues на 256, MaxXrefSections на 1024 и MaxXrefEntries на 4 000 000. При провал методът задава Report.Status := plsFailed и пре-вдига; понеже Report се пише на място, съдържанието му оцелява през exception-а, а копие се съхранява в TPdf.LastLoadReport
Полетата на доклада отговарят на въпросите, от които един batch лог действително се нуждае. Status е едно от plsNotAttempted, plsLoaded, plsLoadedWithRecovery, plsRejected или plsFailed. NativeErrorCode държи FPDF_GetLastError, така че FPDF_ERR_PASSWORD (4) отделя липсваща или грешна парола от повреден файл, докладван като FPDF_ERR_FORMAT (3). UsedRecovery, CrossReferenceTableValid и RecoveryRoute казват дали PDFium е трябвало да преизгради xref таблицата, а Issues изброява всеки одитен наход с Code, Severity, Offset, ObjectNumber и MessageText, като IssuesTruncated е вдигнат, когато MaxIssues е отрязал списъка
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); // една инстанция на документ
try
Pdf.FileName := Files[I];
try
Pdf.LoadDocument(Options, Report);
except
on E: Exception do
begin
// Report е попълнен, макар LoadDocument да е вдигнала
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;
Кога да зареждате с plmStrict?
Ползвайте plmStrict, когато тихо поправен файл е по-лош от отхвърлен — например archive intake, обработка на доказателства или signing pipeline. PDFium тихо възстановява счупена cross-reference таблица (ISO 32000-1 §7.5.4), сканирайки файла за обекти, което е чудесно за viewer и проблем за каквото и да е, което трябва да обработи точно подадените му байтове. След нативното зареждане компонентът пита FPDF_DocumentHasValidCrossReferenceTable. В режим plmCompatible преизграждане дава plsLoadedWithRecovery плюс предупреждение plicNativeCrossReferenceRebuild. В режим plmStrict компонентът разтоварва документа, задава plsRejected, добавя plicStrictModeRejected и вдига EPdfError с „Strict PDF load rejected the document“. Strict режимът отхвърля и всяка одитна грешка, а TPdfLoadOptions.Default(plmStrict) включва RequireFinalEndOfFileMarker, който повишава липсващ %%EOF или данни след последния (§7.5.5) от предупреждение до грешка. xref одитът допълва проверките на ниво обект в валидацията на object и xref stream-ове с 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; // валиден xref, без одитни грешки
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;
Къде TPdf.LastLoadReport спира да казва истината?
TPdf.LastLoadReport е пълен само след LoadDocument overload, приемащ опции, защото само тези overload-и пускат байтовия одит. Успешно Active := True записва доклад в compatible режим без байтов одит, така че AuditAttempted остава False. Преди PDFiumPas v3.122.1 проваленото не записваше нищо, което означаваше, че на споделена инстанция LastLoadReport все още описва предишния файл, често с успокояващ plsLoaded. От v3.122.1 всяко провалено зареждане заменя доклада: провалено Active := True, което все пак оставя компонента неактивен без да вдига, и провалено обикновено извикване на LoadDocument или LoadCustomDocument записват plsFailed с текста на грешката — отново без одит. Още две дупки имат значение на практика. Валидацията на опциите и CheckInactive вървят, преди докладът да е инициализиран, така че отрицателен AuditByteLimit или вече активна инстанция вдигат, без да произведат доклад. А NativeErrorCode има значение само когато PDFium действително се е опитал да парсне; за липсващ файл обвързката вдига, преди PDFium да тръгне, така че логвайте ErrorMessage и текста на exception-а вместо това
Практичното правило е кратко. Дръжте Active := True за designer-вързани viewer-и, където неактивен компонент е приемлив изход. Навсякъде другаде и преди всичко в batch и server код създавайте по един TPdf на документ, викайте LoadDocument(Options, Report), улавяйте exception-а, който вдига, и логвайте Report.Status, NativeErrorCode и Issues на ниво грешка заедно с името на файла. Цената е няколко реда на извикващо място, а всеки провал се приписва на правилния файл с истинската му причина
API-ят за load report, strict режимът и одитът на байтово ниво пътуват с PDFium Component за Delphi, C++Builder и Lazarus, заедно с рендерирането, извличането на текст, попълването на форми и PDF/A валидацията