نعم — يدعم PDFium نماذج XFA، مع انقسام واحد مهم. فيستطيع PDFium Component اكتشاف نموذج XFA واستخراج حزمه template وdatasets وconfig كملفات XML خام باستخدام مكتبة DLL القياسية، دون أي اعتماد على بيئة تشغيل. أما تشغيل نموذج XFA ديناميكي — أي تخطيط وصفه بلغة XML في صفحات حية تفاعلية — فمشكلة منفصلة تحتاج إضافةً إلى ذلك إلى البناء المزوّد بـ V8. ويواصل المجتمع السؤال عمّا إذا كان PDFium "يدعم XFA" كما لو كانت قدرة واحدة بنعم أو لا، والجواب الصادق أن قراءة بيانات XFA وتصيير نموذج XFA شيئان مختلفان تمامًا ببصمتي اعتماد مختلفتين تمامًا
يستعرض هذا المقال النصفين مع PDFium Component، الغلاف الأصلي لـ VCL حول PDFium لـ Delphi وC++Builder. أولًا مسار الاكتشاف والاستخراج الساكن — FormType، وخاصية XFA، وقارئات GetXfaPacket* — ثم مسار بيئة التشغيل الديناميكية بقيد V8 الخاص به، وأخيرًا مسابير القدرات التي تتيح لك الفشل بسلاسة حين لا تستطيع مكتبة DLL المنشورة تشغيل المحرك
هل يدعم PDFium نماذج XFA؟
اقرأها كقدرتين لا قدرة واحدة. فاستخراج XFA الساكن متاح دائمًا: فتصديرات FPDF_GetXFAPacket* منخفضة المستوى التي تقف خلف GetXfaPacketCount وGetXfaPacketName وGetXfaPacketContent مُصرَّفة في كل pdfium.dll حديثة بشكل معقول، حتى في بناء أُطفئ فيه XFA. ولهذا لا يحمل سحب XML الخام لـ template وdatasets وconfig من نموذج أي اعتماد على بيئة تشغيل على الإطلاق — إنه قراءة بنيوية محضة، دون أي محرك. أما XFA الديناميكي فهو القدرة الأخرى: تشغيل النموذج فعليًا، وتقييم حساباته، وترك محرك تخطيط XFA يقسّمه إلى صفحات. وهذا المسار موجود فقط في بناء PDFium مُصرَّف بدعم XFA فوق محرك JavaScript المسمّى V8، وهو الجزء الذي تتجادل حوله فعليًا معظم نقاشات "هل يدعم PDFium نماذج XFA"
وتؤطّر المعايير هذا الانقسام بوضوح. فـ XFA معرَّف خارج قواعد PDF الأساسية في Adobe XFA 3.3 (2012)، ويصف PDF 1.7 §8.6.7 كيفية حمل نموذج XFA داخل ملف PDF: تقيم حزم النموذج تحت مدخل /XFA في قاموس AcroForm، والمستند الذي يجب أن يخطّطه معالج XFA قبل أن يعني أي شيء يضبط /NeedsRendering true في الكتالوج. وهاتان العلامتان هما بالضبط ما يبحث عنه PDFium Component، وبالضبط ما يمكنك الاعتماد عليه حين تفكّر في ملف لم تره قط
بمَ يختلف XFA عن AcroForm؟
AcroForm هو نموذج PDF الكلاسيكي: تعليقات توضيحية من نوع widget — حقول نصية، ومربعات اختيار، ومجموعات أزرار اختيار — مثبَّتة عند إحداثيات ثابتة على صفحات PDF عادية. فالصفحة التي تراها هي الصفحة الموجودة في الملف، والحقول تجلس فوقها. أما XFA فيتخذ النهج المعاكس. إذ يوصَف النموذج التفاعلي بالكامل بلغة XML، وفي مستند XFA كامل (ديناميكي) تكون صفحات PDF فعليًا مجرد عنصر نائب — فالمحتوى الحقيقي يولَّد وقت الفتح بواسطة محرك XFA يقرأ قالب XML ويخطّطه، متمددًا أو متقلصًا ليلائم البيانات. وهذا ما يعلنه /NeedsRendering true: فمن دون معالج XFA قد تكون الصفحات الساكنة فارغة أو لا تحمل سوى تنبيه "يرجى الفتح في عارض متوافق"
وحمولة XFA نفسها مجموعة من الحزم المسمّاة، وثلاث منها تحمل كل ما تحتاجه معظم عمليات التكامل. فحزمة template هي تعريف النموذج — الحقول والتخطيط والحسابات والبرامج النصية. وحزمة datasets تحمل بيانات النسخة الفعلية، أي قيم الحقول المعبّأة كشجرة XML. وحزمة config تحمل تعليمات المعالجة لمحرك XFA. وتوجد حزم أخرى — localeSet وconnectionSet وغلاف xdp — لكن لتدقيق نموذج أو ترحيل البيانات منه، تكون template مع datasets عادةً هي العمل كله. ولأن تلك البيانات مجرد XML جالس في دفق، فقراءتها لا تتطلب تنفيذ أي شيء أبدًا، وهذا هو السبب الجذري في أن الاستخراج الساكن خالٍ من الاعتماديات
اكتشاف نموذج XFA في Delphi
يبدأ الاكتشاف كما تبدأ كل مهمة في PDFium Component: اضبط FileName، واقلب Active := True، وتحقق من أنه فُتح فعلًا. فـ Active := True لا يرفع استثناءً أبدًا — الملف المفقود أو كلمة المرور الخاطئة أو مكتبة DLL الغائبة تترك Active عند False فحسب — ولذا فالحارس إلزامي. وبمجرد فتح المستند، تخبرك FormType بنموذج النماذج الذي يستخدمه. واثنتان من قيم TPdfFormType هما XFA: فـ ftXfaFull نموذج XFA كامل ديناميكي، من النوع الذي يحتاج إلى تصيير، بينما ftXfaForeground هي XFAF، المجموعة الفرعية الأمامية حيث يُرسم محتوى XFA فوق صفحات AcroForm عادية بخلاف ذلك كي تبقى الصفحات الساكنة ذات معنى. والقيمة المنطقية XFA اختصار لـ "هذا مستند XFA بأي من النكهتين". وإذا كنت تعمل أيضًا مع حقول مستوى widget في مستند XFAF أو AcroForm، فالآليات مشروحة في دليل التنقّل بين حقول النماذج في PDFium
var
Pdf: TPdf;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'application-form.pdf';
Pdf.Active := True;
if not Pdf.Active then
Exit; // تالف أو مشفَّر أو DLL مفقودة
case Pdf.FormType of
ftXfaFull:
Log('Full (dynamic) XFA form');
ftXfaForeground:
Log('XFAF: XFA layered over static AcroForm pages');
ftAcroForm:
Log('Classic AcroForm');
else
Log('No interactive form');
end;
if Pdf.XFA then
Log('Document carries an XFA packet payload');
finally
Pdf.Active := False;
Pdf.Free;
end;
end;
استخراج حزم XFA كملفات XML خام
الاستخراج هو النصف عالي القيمة الخالي من الاعتماديات. احرس بـ XfaStaticReadable — فهي تؤكد أن تصديرات قارئ الحزم حُلّت في مكتبة DLL المحمَّلة — ثم اجتز الحزم بالفهرس. تُعيد GetXfaPacketCount عدد الحزم التي يحملها النموذج، وتعطي GetXfaPacketName كل حزمة اسمها، وتُعيد GetXfaPacketContent البايتات الخام، وهي للحزم المعروفة XML بترميز UTF-8. وحين تعرف مسبقًا الحزمة التي تريدها، تحلّ المساعدات المسمّاة GetXfaTemplate وGetXfaDatasets وGetXfaConfig الاسم عنك وتُعيد TBytes مباشرة. ولمهمة ترحيل بيانات فإن المهمة هي GetXfaDatasets: فهي تعطيك قيم الحقول المُرسَلة كمستند XML يمكنك تحليله وتعيينه إلى مخططك الخاص، وهو النوع نفسه من العمل اللاحق الذي ستؤديه بعد استخراج النص من ملف PDF
procedure ExtractXfaData(Pdf: TPdf; const Dir: string);
var
I, Count: Integer;
PacketName: string;
Content, Datasets: TBytes;
begin
if not Pdf.XfaStaticReadable then
Exit; // تصديرات قارئ الحزم غير موجودة
Count := Pdf.GetXfaPacketCount;
for I := 0 to Count - 1 do
begin
PacketName := Pdf.GetXfaPacketName(I);
Content := Pdf.GetXfaPacketContent(I); // XML خام بترميز UTF-8
TFile.WriteAllBytes(
TPath.Combine(Dir, PacketName + '.xml'), Content);
end;
// أو اقفز مباشرة إلى قيم الحقول المعبّأة بالاسم:
Datasets := Pdf.GetXfaDatasets;
if Length(Datasets) > 0 then
ParseAndMap(Datasets);
end;
لماذا يحتاج تصيير XFA الديناميكي إلى محرك V8؟
الخلاصة أولًا: لأن نموذج XFA الديناميكي برنامج صغير، لا رسم ساكن. فحزمة template تحمل حسابات FormCalc وJavaScript وعمليات تحقق وبرامج أحداث نصية، وعلى محرك تخطيط XFA تنفيذها ليقرّر حتى كيف تبدو الصفحات. ولا ينفّذ PDFium ذلك المحرك إلا في بناء مُصرَّف بدعم XFA فوق V8، محرك JavaScript من Google — وهو السبب نفسه في أن مكتبة DLL المزوّدة بـ V8 (pdfium32v8.dll أو pdfium64v8.dll) أكبر بشكل ملحوظ من البناء القياسي. ومن دون V8 لا أحد يشغّل البرامج النصية، فلا صفحات مصيَّرة لعرضها، بل فقط XML الخام الذي يمكنك قراءته ساكنًا أصلًا
ولاختيار ذلك البناء قيد صارم لا يمكنك التصميم حوله: إنه قرار لمرة واحدة لكل عملية. فلا تستطيع عملية واحدة تحميل كل من pdfium.dll القياسية والبناء المزوّد بـ V8 — فهما يصدّران الرموز نفسها ويحتفظ V8 بحالة عزل عامة — ولذا يجب على PDFium Component اختيار بناء V8 قبل تحميل المكتبة للمرة الأولى على الإطلاق. ولجعل ذلك تلقائيًا، تفحص LoadDocument الملف مسبقًا بحثًا عن علامتي /XFA و/NeedsRendering true (الفحص نفسه المكشوف كدالة مستقلة PdfFileLooksXfa) وتضبط EnableV8Engine := True قبل التحميل حين تجدهما. ومتى صارت pdfium.dll عادية مقيمة بالفعل، لا يعود المفتاح قادرًا على التأثير، وتلك الحالة هي ما يبلّغ عنه مسار بيئة التشغيل المفقودة
begin
// ألقِ نظرة بحثًا عن XFA قبل تحميل PDFium للمرة الأولى، كي يظل
// اختيار البناء المزوّد بـ V8 ممكنًا. تفحص PdfFileLooksXfa
// /XFA و/NeedsRendering true دون تحليل الملف كاملًا.
if PdfFileLooksXfa('dynamic-form.pdf') then
EnableV8Engine := True;
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'dynamic-form.pdf';
Pdf.Active := True;
if Pdf.Active and Pdf.XFA then
begin
if Pdf.XfaRuntimeAvailable then
RenderXfaPages(Pdf) // المحرك حي: تخطيط ديناميكي
else
ExtractXfaData(Pdf, 'C:\out'); // التراجع إلى الحزم الساكنة
end;
finally
Pdf.Active := False;
Pdf.Free;
end;
end;
التعامل بسلاسة مع غياب بيئة تشغيل XFA
ثلاثة مسابير تخبرك بالضبط بمقدار قدرة XFA التي لديك، والكود الصادق يفحصها بدلًا من الافتراض. فـ XfaFeaturesAvailable فحص عام لوجود تصديرات مساعدات XFA/V8 في مكتبة DLL المحمَّلة. وXfaStaticReadable تؤكد، لكل مستند، أن قارئات الحزم حُلّت — وهي حارس الاستخراج. وXfaRuntimeAvailable هي القوية: فهي صحيحة فقط حين يكون هذا المستند بعينه قد فعّل محرك XFA فعليًا، أي أنه XFA، وبناء V8 محمَّل، وFPDF_LoadXFA نجحت. ويمكن لمستند أن يبلّغ عن XFA = True ومع ذلك XfaRuntimeAvailable = False كلما افتقرت مكتبة DLL المنشورة إلى XFA أو كان البناء القياسي محمَّلًا بالفعل
وحين يُفتح مستند XFA لكن المحرك لا يستطيع العمل، لا يقامر PDFium Component. بل يثبّت النموذج على الوضع الساكن الآمن بدلًا من طلب بيئة تشغيل ديناميكية لا يستطيع الوفاء بها، ويرفع OnXfaRuntimeMissing مرة واحدة كي يتفاعل المضيف — عادةً بإخبار المستخدم بإعادة التشغيل بالبناء المزوّد بـ V8، مع الاستمرار في تقديم البيانات القابلة للقراءة ساكنًا في هذه الأثناء. وتوصيل ذلك المعالج هو الفرق بين تراجع نظيف ونموذج لا يعرض شيئًا بصمت
procedure TForm1.PdfXfaRuntimeMissing(Sender: TObject);
begin
// يُطلق مرة واحدة حين يُفتح مستند XFA لكن المحرك لا يستطيع العمل:
// pdfium.dll عادية كانت محمَّلة بالفعل، أو البناء يفتقر إلى XFA/V8.
ShowMessage('This is a dynamic XFA form. Restart with the ' +
'V8-enabled build to render it; its packet data is still readable.');
end;
وكن صادقًا بشأن الحدّ في منتجك أيضًا. فإذا لم تشحن مكتبة DLL المزوّدة بـ V8 أبدًا، فلن تُصيَّر نماذج XFA الديناميكية لمستخدميك أبدًا — لكنك لا تزال تستطيع اكتشافها، واستخراج كل حزمة، وسحب البيانات المُرسَلة، وهذا يكفي للفئة الكبيرة من المهام التي تدور فعليًا حول إخراج البيانات من النماذج الحكومية والمصرفية القديمة لا حول عرضها تفاعليًا. احتفظ ببناء V8 الأثقل للحالات التي تحتاج حقًا إلى نموذج حي قابل للتعبئة، ودع مسابير القدرات توجّه كل مستند إلى المسار الذي يستطيع فعلًا سلوكه. وإذا كان خط استخراجك يعالج الملفات المضمَّنة أيضًا، فتنطبق فلسفة القراءة فقط نفسها على مرفقات PDF في Delphi
واجهات FormType وXFA وGetXfaPacketCount وGetXfaPacketName وGetXfaPacketContent وGetXfaTemplate وGetXfaDatasets وGetXfaConfig ومسابير القدرات البرمجية المبيّنة هنا جزء من PDFium Component لـ Delphi وC++Builder