يضبط مكوّن PDFium الآن FPDF_FORMFILLINFO.version على 2 لكل بيئة تعبئة استمارات يهيئها، لأن الإصدار الذي تقبله بنية PDFium أصلية خاصية لتلك البنية لا للمستند الذي يُفتح. ترفض pdfium.v8.dll المفعّلة بـ XFA الإصدار 1 رفضًا قاطعًا، فكان ملف AcroForm عادي يُفتح عبرها يفشل في FPDFDOC_InitFormFillEnvironment بلا أي XFA في الأفق. إصلاح v3.116.0 صغير، لكن الخطأ وراءه عام ويستحق التسمية: حقل إصدار بروتوكول يصف تخطيط الذاكرة الذي يتوقعه الطرف الآخر، ولا يجوز قط اشتقاقه من كونك تحتاج صدفةً الميزات التي يحملها ذلك التخطيط
لماذا يفشل FPDFDOC_InitFormFillEnvironment على PDF عادي مع pdfium.v8.dll؟
تفشل البيئة لأن بنية PDFium المفعّلة بـ XFA تتحقق من حقل version قبل أن تفعل أي شيء، وكانت منطق الغلاف القديم يسلمها 1 كلما لم يكن المستند الحالي استمارة XFA. العرَض في مضيف دلفي هو EPdfError يُرفع من TPdf.InitializeFormFill برسالة Cannot initialize form fill environment، أثناء فتح فاتورة عادية أو نموذج ضريبي لا يحمل سوى حقول نص AcroForm. الملف نفسه يفتح سليمًا على pdfium.dll العادية. والـ DLL نفسه يفتح مستند XFA حقيقي سليمًا. الاكتساب الوحيد المكسور هو V8 مع مستند غير XFA، وهو بالضبط الاكتساب الذي يهبط إليه مضيف بعد تشغيل EnableV8Engine لجافاسكربت AcroForm، أو بعد أن التزمت الاختيارية التلقائية في LoadDocument العمليةَ بـ pdfium.v8.dll من أجل ملف XFA سابق. ذلك الالتزام على مستوى العملية كلها: تُقرأ EnableV8Engine قبل أول LoadLibrary، وحماً نُقلت بنية XFA مرّ كل PDF عادي لاحق عبر تهيئة البيئة نفسها مقابل الثنائية نفسها. لم يفعل المضيف شيئًا خاطئًا؛ سأل الغلاف السؤال الخطأ حين ملأ السجل. وإن كنت ما تزال تقرر أي ثنائية تشحن أصلًا، فتغطي ملاحظتنا عن نشر PDFium DLL وتشخيص فشل التحميل الاختيار بين العادية وV8، وتفترض هذه المقالة أن بنية V8 موجودة في العملية سلفًا
ماذا يعد حقل الإصدار في FPDF_FORMFILLINFO فعلًا؟
يخبر FPDF_FORMFILLINFO.version الـ PDFium أي حقول السجل يجوز له قراءتها، ويربط الترويسة العامة fpdf_formfill.h القيمَ المقبولة بكيفية ترجمة المكتبة لا بالمستند. بمعادلة الصياغة، للعقد ثلاثة أجزاء. يغطي الإصدار 1 النداءات المستقرة من FFI_Invalidate حتى FFI_DoGoToAction زائد مؤشر m_pJsPlatform. بنية بلا وحدة XFA تقبل 1 أو 2، ومع 2 ستستدعي كذلك النداءات التجريبية الإضافية. بنية بوحدة XFA تشترط 2، نقطة، وتكرر الترويسة ذلك الشرط مرتين كأنها توقعت أن يفوته الناس. ولا يذكر العقد المستند في أي موضع. الإصدار تصريح عن السجل الذي أسندته: بالقيمة 2 أنت تعد بأن الذاكرة بعد m_pJsPlatform موجودة وتحمل إما مؤشرات دوال صالحة أو NULL
منطقة الإصدار 2 هي حيث تسكن كل آلية XFA. تبدأ بـ xfa_disabled، وهي FPDF_BOOL تصفها الترويسة مهملًا دون الإصدار 2 وذو معنى فقط حين تُترجم وحدة XFA داخليًا، وتتابع بسبعة عشر مؤشر دالة، من FFI_DisplayCaret حتى FFI_DoURIActionWithKeyboardModifier. كل واحد منها موثق «مطلوب لـ XFA وإلا فيُضبط NULL». تلك الصياغة مفتاح الإصلاح كله. NULL ليست حالة خطأ لتلك الخانات؛ إنها الحالة الموثقة لمضيف لا يقود XFA. سجل حُيّ بـ FillChar ثم وُسم بوصفه الإصدار 2 يحقق العقد على بنية غير XFA تمامًا كما يحققه سجل الإصدار 1، وهو السجل الوحيد الذي ستأخذه بنية XFA
الاختيار القديم ربط ABI بالمستند
كان العيب شرطًا واحدًا بدا معقولًا معزولًا. يحسب TPdf.InitializeFormFill راية RuntimeReady من ثلاث حقائق: أن المستند يبلّغ نوع استمارة XFA عبر TPdf.XFA، وأن مساعدات سلاسل XFA حُلّت عبر XfaFeaturesAvailable، وأن تصديرات V8 حُلّت عبر V8FeaturesAvailable. وقبل v3.116.0 كانت تلك الراية نفسها تختار الإصدار أيضًا
// v3.115.0 وما قبله: إصدار ABI تلتبع المستند
RuntimeReady := XFA and XfaFeaturesAvailable and V8FeaturesAvailable;
if RuntimeReady then
FFormFillInfo.Info.version := 2
else
FFormFillInfo.Info.version := 1;
// ... وفرع غياب بيئة التشغيل قرّرها من جديد
else if XFA then
begin
FFormFillInfo.Info.version := 1;
FFormFillInfo.Info.xfa_disabled := 1;
if Assigned(FOnXfaRuntimeMissing) then
FOnXfaRuntimeMissing(Self);
end;
اقرأه والترويسة في يدك وسيكون الفشل واضحًا. RuntimeReady قيمتها false لكل مستند AcroForm عادي، فأعلن كل مستند عادي الإصدار 1. على pdfium.dll لا بأس. أما على pdfium.v8.dll، وهي البنية المفعّلة بـ XFA، فيفحص PDFium الحقل ويجده دون الـ 2 المطلوبة ويعيد FPDF_FORMHANDLE فارغًا، يحوله CheckPdf إلى الاستثناء أعلاه. كان قصد الكود القديم دفاعيًا: أبقِ الإصدار 1 كي لا تقرأ بنية XFA قط خانات الإصدار 2 غير المسندة. دافع عن مشكلة تستبعدها الترويسة سلفًا وخلق مشكلة تحذر منها الترويسة صراحةً. يقرر الكود المصحح الإصدار مرة واحدة، مقدمًا، مما هو عليه السجل فيزيائيًا
procedure TPdf.InitializeFormFill;
var
RuntimeReady: Boolean;
begin
FXfaRuntimeUsable := False;
FXfaPageCountOverride := -1; // علامة مميزة: استخدم شجرة الصفحات الساكنة
if not FormFill then
Exit;
FillChar(FFormFillInfo, SizeOf(FFormFillInfo), 0);
FFormFillInfo.Pdf := Self;
// سجل الإصدار 2 الكامل أُسند ومُحي فوق. يقبل PDFium
// الإصدار 2 دون XFA ويشترطه في كل بنية مفعّلة بـ XFA،
// حتى حين لا يحوي هذا المستند استمارة XFA.
FFormFillInfo.Info.version := 2;
FFormFillInfo.Info.xfa_disabled := 1;
// يبوّب RuntimeReady نداءات XFA وxfa_disabled، لا الإصدار قط.
RuntimeReady := XFA and XfaFeaturesAvailable and V8FeaturesAvailable;
...
أين ما يزال مكان RuntimeReady: النداءات وxfa_disabled
يحتفظ RuntimeReady بعمله بوابةً لسلوك XFA؛ إنه ببساطة لم يعد يلمس تخطيط السجل. نداءات الإصدار 1، FFI_Invalidate وFFI_SetTimer وFFI_GetPage وFFI_DoURIAction وFFI_DoGoToAction وبقية تلك الكتلة، توصل بلا شرط لأن AcroForm وXFA كلاهما يعتمدان عليها. أما مؤشرات الإصدار 2 السبعة عشر فتُسند داخل فرع RuntimeReady فقط، مع xfa_disabled := 0. وحين يكون المستند XFA وبيئة التشغيل غير موجودة، يبقى السجل على الإصدار 2 بـ xfa_disabled عند 1 وخانات الإصدار 2 تركت NULL، ويرفع الغلاف OnXfaRuntimeMissing ليقترح المضيف إعادة التشغيل على pdfium.v8.dll. بعد وجود البيئة، لا تُستدعى FPDF_LoadXFA إلا حين كانت RuntimeReady قيمتها true، وفقط الإرجاع true يضبط FXfaRuntimeUsable التي تبلغ عنها TPdf.XfaRuntimeAvailable
if RuntimeReady then
begin
FFormFillInfo.Info.xfa_disabled := 0; // 0 = XFA مفعّلة
FFormFillInfo.Info.FFI_DisplayCaret := FormFillDisplayCaret;
FFormFillInfo.Info.FFI_GetCurrentPageIndex := FormFillGetCurrentPageIndex;
FFormFillInfo.Info.FFI_SetCurrentPage := FormFillSetCurrentPage;
FFormFillInfo.Info.FFI_GotoURL := FormFillGotoURL;
FFormFillInfo.Info.FFI_GetPageViewRect := FormFillGetPageViewRect;
FFormFillInfo.Info.FFI_PageEvent := FormFillPageEvent;
FFormFillInfo.Info.FFI_PopupMenu := FormFillPopupMenu;
FFormFillInfo.Info.FFI_OpenFile := FormFillOpenFile;
FFormFillInfo.Info.FFI_EmailTo := FormFillEmailTo;
// ... FFI_UploadTo حتى FFI_DoURIActionWithKeyboardModifier
end
else if XFA then
begin
// بيئة التشغيل غير متاحة: أبقِ الإصدار 2، واترك XFA معطلة، وأخبر المضيف.
if Assigned(FOnXfaRuntimeMissing) then
FOnXfaRuntimeMissing(Self);
end;
FFormHandle := FPDFDOC_InitFormFillEnvironment(FDocument, FFormFillInfo.Info);
CheckPdf(FFormHandle <> nil, 'Cannot initialize form fill environment');
if RuntimeReady then
FXfaRuntimeUsable := FPDF_LoadXFA(FDocument) <> 0;
تفصيلان في تلك الكتلة سهلا الخطأ حين تكتب ربطك الخاص. يُعاد ضبط FXfaPageCountOverride إلى -1 علامة مميزة قبل حدوث أي شيء، فيرجع PageCount إلى شجرة الصفحات الساكنة حتى يبلّغ FFI_PageEvent عن إعادة ترقيم؛ والصفر هناك سيصرّح بصمت بمستند فارغ. وكل نداء من نداءات الإصدار 2 روتين cdecl ساكن يسترد TPdf المالك من السجل ويبلع أي استثناء Pascal قبل العودة إلى PDFium، وهو الانضباط الذي تفصله ملاحظتنا عن تقسية ABI من PDFium في دلفي في FFI_OpenFile. لا شيء في تغيير الإصدار يرخي أيا من القاعدتين
هل الإصدار 2 آمنة حين لا تحمل DLL وحدة XFA؟
نعم، والسبب في السجل لا في وعد من المكتبة. على بنية غير XFA تقول الترويسة إن الإصدار 2 يجعل النداءات التجريبية تُستدعى كذلك، فالسؤال ماذا يجد PDFium حين ينظر. TPdfFormFillInfo سجل مضغوط عضو Info فيه هو FPDF_FORMFILLINFO كاملة بكل حقول الإصدار 2، ويحي InitializeFormFill الكل بـ FillChar قبل لمس بايت واحد. فعلى pdfium.dll عادية مع مستند عادي ترى المكتبة الإصدار 2، وxfa_disabled مضبوطًا، وNULL في كل خانة تجريبية، وهو بالضبط الحالة التي تصفها الترويسة لمضيف لا ينفذ XFA. لا يوجد سجل مبتور تقرأه المكتبة فتتجاوزه، لأن السجل لم يكن قط أقصر من الإصدار 2. كان المنطق القديم يدافع عن عدم تطابق تخطيط أزالته إعلانات Pascal سلفًا
الحد الذي يستحق التصريح به بصدق هو الذي لا يستطيع السجل تغطيته. الإصدار 2 على مستند عادي لا يشغل جافاسكربت ولا سكربتات XFA ولا أيًّا من أحداث المضيف خلف تلك النداءات. يُلحق m_pJsPlatform فقط حين تكون V8FeaturesAvailable قيمتها true، وتبقى XFA معطلة ما لم تكن RuntimeReady قيمتها true، ويستمر TPdf.XFA في الإبلاغ عن نوع الاستمارة من FPDF_GetFormType بصرف النظر عما تفاوضت عليه البيئة. والمضيف الذي يريد معرفة هل سترسم XFA الديناميكية فعلًا عليه أن يواصل قراءة XfaRuntimeAvailable بعد أن تصير Active قيمتها true، كما توصي ملاحظتنا عن كشف استمارات XFA واستخراج حزم XFA، لا أن يستنتج شيئًا من حقل الإصدار
procedure TMainForm.PdfXfaRuntimeMissing(Sender: TObject);
begin
// يشتعل من InitializeFormFill حين يكون المستند XFA لكن pdfium.dll
// المحمّل لا يستطيع تشغيل المحرك. ما يزال نمط الاستمارة يُفتح،
// لأن الإصدار 2 مُرّر على كل حال؛ فقط بيئة تشغيل XFA معطلة.
StatusBar.SimpleText :=
'XFA form detected; restart with pdfium.v8.dll to enable dynamic rendering';
end;
procedure TMainForm.OpenDocument(const FileName: string);
begin
Pdf.Active := False;
Pdf.OnXfaRuntimeMissing := PdfXfaRuntimeMissing;
Pdf.FormFill := True;
Pdf.FileName := FileName;
Pdf.Active := True; // لم يعد يرفع استثناء على PDF عادي تحت pdfium.v8.dll
if Pdf.XFA and not Pdf.XfaRuntimeAvailable then
ShowStaticXfaWarning;
end;
إصدار البروتوكول وتوافر الميزات محوران مختلفان
القاعدة العامة التي تلتبع هذا الإصلاح أن حقل إصدار في بنية نداءات يجيب عن سؤال «ما حجم هذا السجل وماذا يجوز لك أن تقرأ منه»، بينما يجيب كشف الميزات عن «أي من تلك الخانات سيفعل شيئًا مفيدًا». الأول يحدده الثنائية الأصلية وإعلانات Pascal التي رجمت ضدها. والثاني يتغير بحسب المستند وجدول تصديرات DLL وضبط المضيف. طيّ الاثنين في قيمة منطقية واحدة مغرٍ لأن حالة XFA صادفت أنها تحتاج الاثنين، لكن لحظة أن تفرض بنية حدًا أدنى للإصدار ينهار الطي مع كل مستند لا يحتاج الميزة. استمارات XFA، الموصوفة في ISO 32000-1 §12.7.8 بوصفها حمولة XML تسكن بجوار قاموس AcroForm، هي الميزة هنا؛ وتخطيط السجل هو البروتوكول، وللـ PDFium أن تصرّ على التخطيط قبل أن تنظر إلى الملف أصلًا. ويظهر الشكل نفسه في أي موضع تصوّ فيه مكتبة C بنيةً: كتلة معلومات عارض، سجل خيارات رسم، جدول نداءات منصة. والنمط الآمن هو الذي يتبعه InitializeFormFill المصحح. أعلن أحدث تخطيط تفهمه، وحِه كليًا، واضبط الإصدار ليطابق ذلك التخطيط بلا شرط، ثم دع فحوص القدرات تقرر أي الخانات تُملأ. وإن أضافت ترويسة PDFium مستقبلًا إصدار 3، فالتغيير في الإعلان وفي ذلك الإسناد الواحد، لا في فرع تابع للمستند سيكون خاطئًا لأي اكتساب لم يجرّبه أحد
تهيئة تعبئة الاستمارات المصححة تأتي في مكوّن PDFium لدلفي وLazarus وC++Builder، وتنطبق على Win32 وWin64 سواء لأن البنيتين تتشاركان إعلان السجل نفسه. وإن كان تطبيقك يختار سلفًا pdfium.v8.dll لأشكال AcroForm المدفوعة بجافاسكربت، فهذا هو التغيير الذي يجعله يفتح بقية أرشيف PDF عندك عبر الثنائية نفسها دون استثناء خاص لبيئة الاستمارات