در PDFium Component for Delphi and Lazarus، انتساب TPdf.Active := True وقتی بارگذاری PDF شکست میخورد هرگز exception نمیدهد: TPdf.SetActive هر exceptionی را میگیرد و کامپوننت را غیرفعال رها میکند. برای دیدن خطای واقعی، بهجایش TPdf.LoadDocument(Options, Report) را صدا بزنید. آن overload استثنای اصلی را دوباره پرتاب میکند و یک TPdfLoadReport را با وضعیت بارگذاری، کد خطای بومی PDFium و اینکه جدول ارجاع متقابل مجبور به بازسازی شده یا نه پر میکند
مشکل معمولاً در کد دستهای سر باز میکند. یک کار استخراج جدول پوشهای از 13 PDF واقعی را با یک TPdf مشترک میگردد و 7 تای آنها شکست برمیگردانند. هیچکدام از آن 7 فایل واقعاً خراب نیست. بلوکهای except دور بارگذاری هرگز آتش نمیگیرند، لاگ نام فایلهای اشتباه را مقصر میکند و اولین خطای دیداری یک EPdfError برهنه دربارهٔ کامپوننت غیرفعال است که چند خط بعد از بارگذاریِ واقعاً شکسته، از یک خواندن پراپرتی بلند میشود. دو رفتار جدا روی هم انباشته میشوند تا این تصویر ساخته شود، و هر دو طبق طراحی کار میکنند
چرا TPdf.Active := True وقتی بارگذاری PDF شکست میخورد exception نمیدهد؟
TPdf.SetActive LoadDocument را درون یک try..except میپیچد که هر کلاس استثنا را میبلعد و کامپوننت را صرفاً غیرفعال میگذارد. بلعیدن عمدی است: همان setter وقتی طراح فرم در IDE مقدار Active را عوض میکند هم اجرا میشود و مسیر بدی نباید IDE را بترکاند. در زمان اجرا TPdf.Active فقط گزارش میدهد آیا handle بومی سندی وجود دارد، پس بعد از بارگذاری شکسته False میخواند و بیشتر از آن هیچ اتفاقی نمیافتد. هرچه پرتاب شده بود گم شده، چه EPdfErrorای از پارسر بوده، چه خطای استریم، چه EAccessViolationای از یک pdfium.dll نیمبسته. پیامهای تفصیلی DLL که در عیبیابی شکستهای بارگذاری pdfium.dll در دلفی توضیح داده شد فقط از طریق فراخوانیای که آنها را نمیبلعد به هندلر شما میرسند
Pdf.FileName := FileName;
try
Pdf.Active := True; // SetActive هر 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 مثل بیشتر پراپرتیهای سند با CheckActive شروع میشود که EPdfErrorای میدهد که نام کامپوننت را میبرد اما نه نام فایل را و نه علت را. آزمایش Pdf.Active بلافاصله بعد از انتساب، یک کرش نادرستنسبتیافته را به یک ثبت صادقانهٔ «شکست خورد» تبدیل میکند. پیش از PDFiumPas v3.122.1 علت در آن نقطه گم میشد؛ از v3.122.1 انتساب شکسته LastLoadReport را با گزارشی plsFailed جایگزین میکند که متن خطا را حمل میکند، پس علت زنده میماند. خود شیء استثنا و audit سطح بایتی هنوز نقطهٔ ورود دیگری میخواهند
چرا استفادهٔ دوباره از یک TPdf از فایل دوم به بعد شکست میخورد؟
TPdf.FileName فقط وقتی کامپوننت غیرفعال است قابل انتساب است، پس یک نمونهٔ مشترک فایل دوم را پیش از هر تلاش برای بارگذاری رد میکند. TPdf.SetFileName با CheckInactive شروع میشود و همان گارد از Password و FormFill هم محافظت میکند. بعد از اولین بارگذاری موفق نمونه فعال میماند، انتساب بعدی exception میدهد، و اگر حلقهٔ دستهای آن استثنا را بگیرد و ادامه بدهد، خطا زیر نام فایل جدید مینشیند در حالی که سند قدیمی هنوز باز است. مخلوط با شکستهای بلعیدهشدهٔ بارگذاری، لاگ از واقعیت فاصله میگیرد. در بازتولید 13 فایلی، یک نمونهٔ مشترک 7 شکست گزارش میکرد، در حالی که یک TPdf.Create(nil) تازه بهازای هر سند هر 13 را باز کرد. ست کردن Active := False بین فایلها هم جواب میدهد، اما یک نمونه بهازای هر سند هر فایل را بهصورت ساختاری ایزوله نگه میدارد
TPdf.LoadDocument با یک TPdfLoadReport چه چیزی به شما میدهد؟
TPdf.LoadDocument(const Options: TPdfLoadOptions; out Report: TPdfLoadReport) استثنای واقعی را پرتاب میکند و همزمان به شکل ساختیافته میگوید چه اتفاقی افتاده. overload فایل FileName را بار میکند؛ overloadهای خواهر TBytes یا اشارهگر و اندازه میگیرند و LoadCustomDocument(AStream, AOwnsStream, Options, Report) استریمها را پوشش میدهد. هر کدام گزینهها را اعتبارسنجی میکند، بررسی میکند نمونه غیرفعال است، یک audit سطح بایتی روی هدر، startxref، بخشهای xref و نشانگر %%EOF اجرا میکند و بعد بارگذاری بومی را انجام میدهد. audit با همان جنس محدودهایی کفوبسته است که در بودجههای منابع پارسر برای PDFهای غیرقابلاعتماد بحث شد: TPdfLoadOptions.Default مقدار AuditByteLimit را 256 MiB، MaxIssues را 256، MaxXrefSections را 1024 و MaxXrefEntries را 4,000,000 میگذارد. در شکست، متد Report.Status := plsFailed را ست و دوباره پرتاب میکند؛ چون Report در جا نوشته میشود محتوایش از استثنا جان سالم به در میبرد و یک کپی در TPdf.LastLoadReport ذخیره میشود
فیلدهای گزارش به سؤالهایی جواب میدهند که یک لاگ دستهای واقعاً لازم دارد. Status یکی از plsNotAttempted، plsLoaded، plsLoadedWithRecovery، plsRejected یا plsFailed است. NativeErrorCode مقدار FPDF_GetLastError را حمل میکند، پس FPDF_ERR_PASSWORD (4) نبودن یا غلط بودن رمز را از فایل آسیبدیدهای که با FPDF_ERR_FORMAT (3) گزارش میشود جدا میکند. UsedRecovery، CrossReferenceTableValid و RecoveryRoute میگویند PDFium مجبور شده جدول xref را بازسازی کند یا نه، و Issues هر یافتهٔ audit را با 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 exception میدهد
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 را به کار ببرید؛ مثل پذیرش آرشیو، مدیریت ادله یا pipeline امضا. PDFium جدول ارجاع متقابل شکسته (ISO 32000-1 §7.5.4) را بیسروصدا با گشتن دنبال شیءها در فایل بازسازی میکند، که برای یک نمایشگر عالی و برای هر چیزی که باید دقیقاً همان بایتهای دادهشده را پردازش کند یک مسئله است. بعد از بارگذاری بومی، کامپوننت FPDF_DocumentHasValidCrossReferenceTable را میپرسد. در حالت plmCompatible بازسازی، plsLoadedWithRecovery بهعلاوهٔ یک هشدار plicNativeCrossReferenceRebuild میدهد. در حالت plmStrict کامپوننت سند را unload میکند، plsRejected را ست، plicStrictModeRejected را اضافه و EPdfError با پیام «Strict PDF load rejected the document» میدهد. حالت سختگیرانه هر خطای audit را هم رد میکند و TPdfLoadOptions.Default(plmStrict) مقدار RequireFinalEndOfFileMarker را روشن میکند، که %%EOF غایب یا داده بعد از آخرین آن (§7.5.5) را از هشدار به خطا ارتقا میدهد. audit مربوط به xref مکمل بررسیهای سطح شیء در اعتبارسنجی استریمهای شیء و xref با 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 معتبر، بدون خطای audit
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 فقط بعد از یک overload از LoadDocument که گزینهها میگیرد کامل است، چون فقط همان overloadها audit بایتی را اجرا میکنند. یک Active := True موفق گزارش حالت سازگار بدون audit بایتی مینویسد، پس AuditAttempted روی False میماند. پیش از PDFiumPas v3.122.1 نسخهٔ شکسته هیچ چیزی نمینوشت، یعنی روی یک نمونهٔ مشترک LastLoadReport همچنان فایل قبلی را توصیف میکرد، اغلب با یک plsLoaded دلگرمکننده. از v3.122.1 به بعد هر بارگذاری شکسته گزارش را جایگزین میکند: یک Active := True شکسته — که همچنان بدون پرتاب کامپوننت را غیرفعال میگذارد — و یک فراخوانی سادهٔ شکستهٔ LoadDocument یا LoadCustomDocument مقدار plsFailed را با متن خطا ثبت میکنند، باز هم بدون audit. دو شکاف دیگر در عمل مهماند. اعتبارسنجی گزینهها و CheckInactive پیش از مقداردهی اولیهٔ گزارش اجرا میشوند، پس یک AuditByteLimit منفی یا نمونهای که از قبل فعال است بدون تولید گزارش exception میدهد. و NativeErrorCode فقط وقتی معنادار است که PDFium واقعاً parse را شروع کرده باشد؛ برای فایل غایب wrapper پیش از اجرای PDFium exception میدهد، پس بهجایش ErrorMessage و متن استثنا را لاگ کنید
قاعدهٔ عملی کوتاه است. Active := True را برای نمایشگرهای متصل به طراح نگه دارید که در آنها کامپوننت غیرفعال نتیجهٔ قابل قبولی است. هر جای دیگر، و بالاتر از همه در کد دستهای و سروری، بهازای هر سند یک TPdf بسازید، LoadDocument(Options, Report) را صدا بزنید، استثنایی که پرتاب میکند را بگیرید و Report.Status، NativeErrorCode و Issuesهای سطح خطا را همراه نام فایل لاگ کنید. هزینه چند خط در هر نقطهٔ فراخوانی است، و هر شکستی به فایل درست با علت واقعیاش نسبت داده میشود
API گزارش بارگذاری، حالت سختگیرانه و audit سطح بایتی همراه PDFium Component برای دلفی، C++Builder و Lazarus عرضه میشوند، در کنار رندر، استخراج متن، پر کردن فرم و اعتبارسنجی PDF/A