في مكوّن PDFium لـ Delphi وLazarus، لا يرفع إسناد TPdf.Active := True استثناءً قط حين يفشل تحميل PDF: يبتلع TPdf.SetActive كل استثناء ويترك المكوّن غير نشط. لترى الخطأ الحقيقي استدعِ TPdf.LoadDocument(Options, Report) بدلًا منه. يعيد ذلك التحميل الزائد رفع الاستثناء الأصلي ويمأل TPdfLoadReport بحالة التحميل وكود خطأ PDFium الأصلي وهل اضطر جدول الإحالات المرجعية إلى إعادة بناء
تظهر المشكلة عادة في كود الدفعات. مهمة استخراج جداول تجوب مجلدًا من 13 ملف PDF واقعيًا بـ TPdf واحد مشترك، فيعود 7 منها بوصفها إخفاقات. ولا ملف من الـ 7 معطوب فعلًا. كتل except حول التحميل لا تشتعل أبدًا، ويلوم السجل أسماء ملفات خاطئة، وأول خطأ مرئي هو EPdfError مجردة عن مكوّن غير نشط، تُرفع من قراءة خاصية بعد عدة أسطر من التحميل الذي فشل فعلًا. سلوكان منفصلان يتكدان لإنتاج تلك الصورة، وكلاهما يعمل كما صُمم
لماذا لا يرفع TPdf.Active := True استثناءً حين يفشل تحميل PDF؟
يغلّف TPdf.SetActive التحميل عبر LoadDocument في try..except يبتلع كل أصناف الاستثناءات ويكتفي بترك المكوّن غير نشط. والابتلاع مقصود: يجرى الواضع نفسه حين يبدّل مصمم النماذج قيمة Active في الـ IDE، ولا يجوز أن يُسقط مسار سيئ البيئة. وفي زمن التشغيل تكتفي TPdf.Active بالبلاغ عن وجود مقبض مستند أصلي، فبعد تحميل فاشل تُقرأ False ولا يحدث شيء آخر. وكل ما رُفع ضاع، أكان EPdfError من المحلل أم خطأ تدفق أم EAccessViolation من pdfium.dll نصف مربوطة. رسائل DLL التفصيلية المشروحة في تشخيص إخفاقات تحميل pdfium.dll في Delphi لا تبلغ معالجك إلا عبر استدعاء لا يبتلعها
Pdf.FileName := FileName;
try
Pdf.Active := True; // يبتلع SetActive أي استثناء تحميل
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]));
// إصلاح أدنى للكود القائم: افحص 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 يحمل نص الخطأ، فينجو السبب. أما كائن الاستثناء نفسه والتدقيق على مستوى البايتات فيشترطان مدخلًا مختلفًا
لماذا يفشل إعادة استخدام TPdf واحد ابتداءً من الملف الثاني؟
TPdf.FileName لا يُسند إلا والمكوّن غير نشط، فيرفض المثيل المشترك الملف الثاني قبل أن يحاول تحميله أصلًا. تبدأ TPdf.SetFileName بـ CheckInactive، ويحمي الحارس نفسه Password وFormFill. بعد أول تحميل ناجح يبقى المثيل نشطًا، فيرفع الإسناد التالي استثناء، وإن التقطت حلقة الدفعة ذلك الاستثناء وتابعت، هبط الخطأ تحت اسم الملف الجديد بينما المستند القديم ما يزال مفتوحًا. وبالمزج مع إخفاقات التحميل المبتلعة يتوقف السجل عن مطابقة الواقع. في إعادة إنتاج الـ 13 ملفًا بلّغ مثيل مشترك عن 7 إخفاقات، بينما فتح TPdf.Create(nil) جديد لكل مستند الملفات الـ 13 كلها. وضبط Active := False بين الملفات ينجح كذلك، لكن مثيل واحد لكل مستند يُبقي كل ملف معزولًا بالبناء
ماذا يمنحك TPdf.LoadDocument مع TPdfLoadReport؟
ترفع TPdf.LoadDocument(const Options: TPdfLoadOptions; out Report: TPdfLoadReport) الاستثناء الحقيقي وتخبرك كذلك بما جرى في صورة مهيكلة. يحمّل التحميل الزائد الخاص بالملفات FileName؛ وتأخذ التحميلات الزائدة الشقيقة TBytes أو مؤشرًا وحجمًا، ويغطي LoadCustomDocument(AStream, AOwnsStream, Options, Report) التدفقات. يتحقق كل منها من الخيارات، ويفحص أن المثيل غير نشط، ويجري تدقيقًا بايتيًا للترويسة وstartxref وأقسام xref وعلامة %%EOF، ثم ينفذ التحميل الأصلي. ويحده التدقيق حدودًا من النوع نفسه الذي تناقشه ميزانيات موارد المحلل لملفات 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 كل مادة تدقيق بـ 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 كلما كان الملف المرمم بصمت أسوأ من الملف المرفوض، كما في استقبال الأرشيف أو معالجة الأدلة أو خط أنابيب توقيع. يعيد PDFium بناء جدول إحالات مرجعية معطوب بصمت (ISO 32000-1 §7.5.4) بمسح الملف بحثًا عن كائنات، وهذا ممتاز لعارض ومشكلة لكل ما يجب أن يعالج البايتات الممررة إليه بالضبط. بعد التحميل الأصلي يسأل المكوّن FPDF_DocumentHasValidCrossReferenceTable. في وضع plmCompatible ينتج إعادة البناء plsLoadedWithRecovery مع تحذير plicNativeCrossReferenceRebuild. وفي وضع plmStrict يفرغ المكوّن المستند ويضبط plsRejected ويضيف plicStrictModeRejected ويرفع EPdfError بالرسالة "Strict PDF load rejected the document". ويرفض الوضع الصارم أيضًا أي خطأ تدقيق، ويشعل TPdfLoadOptions.Default(plmStrict) خيار RequireFinalEndOfFileMarker الذي يرقّي %%EOF غائبة أو بيانات بعدها (§7.5.5) من تحذير إلى خطأ. ويتكامل تدقيق 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 صالح، بلا أخطاء تدقيق
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 يأخذ خيارات، لأن تلك التحميلات وحدها تجري التدقيق البايتي. يكتب نجاح Active := True تقرير وضع متوافق بلا تدقيق بايتي، فتبقى AuditAttempted قيمتها False. قبل PDFiumPas v3.122.1 كان الفشل لا يكتب شيئًا، أي أن LastLoadReport على مثيل مشترك كان ما يزال يصف الملف السابق، غالبًا بـ plsLoaded مطمئنة. ومنذ v3.122.1 يستبدل كل تحميل فاشل التقرير: فشل Active := True، الذي ما يزال يترك المكوّن غير نشط دون رفع، وفشل استدعاء LoadDocument أو LoadCustomDocument المجرد يسجلان plsFailed مع نص الخطأ، ودون تدقيق كذلك. فجوتان أخريان تهمان عمليًا. يجري التحقق من الخيارات وCheckInactive قبل تهيئة التقرير، فقيمة AuditByteLimit سالبة أو مثيل نشط سلفًا يرفعان دون إنتاج تقرير. وNativeErrorCode ذو معنى فقط حين حاول PDFium التحليل فعلًا؛ فالملف الغائب يرفع الغلاف استثناءً قبل أن يعمل PDFium، فسجّل ErrorMessage ونص الاستثناء بدلًا منه
القاعدة العملية قصيرة. أبقِ Active := True للعارضين المرتبطين بالمصمم حيث يكون المكوّن غير النشط نتيجة مقبولة. وفي كل مكان آخر، وقبل كل شيء في كود الدفعات والخوادم، أنشئ TPdf واحدًا لكل مستند، واستدعِ LoadDocument(Options, Report)، والتقط الاستثناء الذي يرفعه وسجّل Report.Status وNativeErrorCode ومسائل Issues من مستوى الخطأ مع اسم الملف. الثمن بضعة أسطر لكل موضع استدعاء، وينسب كل فشل إلى ملفه الصحيح بسببِه الحقيقي
واجهة تقرير التحميل والوضع الصارم والتدقيق البايتي تُشحن مع PDFium Component لـ Delphi وC++Builder وLazarus، إلى جانب الرسم واستخراج النصوص وملء النماذج والتحقق من PDF/A