يحول PDFium Component نماذج XFA الساكنة إلى حقول AcroForm عادية من Delphi عبر FlattenPdfXfaToAcroForm في وحدة FPdfDocumentXfaFlatten. تقرأ الدالة حزمتي قالب XFA و datasets، وتقابل كل حقل موضع مطلقاً حقلاً طرفياً من AcroForm بتدفق مظهر خاص به، وتلحق النتيجة مراجعةً تدريجية، فيفتح ملف PDF المحول نموذجاً عادياً في عارضات لم تدعم XFA قط
الدافع عادة تذكرة دعم لا بند خطة. نموذج حكومي أو تأميني أُلف في مصمم حفظه XFA، يعمل في قارئ مكتبي واحد، وفي كل مكان آخر يفتح صفحة فارغة تقول إن المستند يتطلب عارضاً أحدث. أُهملت XFA في PDF 2.0 (ISO 32000-2)، ولم تنفذها المتصفحات والعارضات المحمولة قط، ولا تعرض PDFium نفسها XFA إلا بالبناء الممكن بـ V8. نماذج XFA الساكنة، التي تخطيطها مثبت عند وقت التصميم، تحمل في القالب هندسة كافية لإعادة بنائها حقول AcroForm، وهذا بالضبط الشريحة التي يعالجها هذا المحول
ماذا ينتج FlattenPdfXfaToAcroForm فعلاً؟
ينتج FlattenPdfXfaToAcroForm مراجعة AcroForm على مستوى البايتات ملحقة بنسخة غير متغيرة من المصدر، ولا يعيد أبداً كتابة الملف الأصلي. لا تكشف PDFium واجهة عامة لإنشاء حقول نماذج، فلا يمر المكوّن بـ PDFium لهذه المهمة إطلاقاً: يقرأ المراجعة النشطة بقارئ كائناته الخاص، ويبني قواميس حقول وتعليقات widget و XObjects مظهر ومورداً خط /Helv كائنات PDF خاماً، ويسلسلها تحديثاً تدريجياً متفرقاً بمعنى ISO 32000-1 §7.5.6
يحمل قاموس /AcroForm الجديد /Fields و /DA و /DR دون مدخل /XFA. عندما يبقي المصدر AcroForm الخاصة به كائناً غير مباشر يُعاد كتابة ذلك الكائن في مكانه؛ ويُستبدل القاموس المضمّن داخل الكتالوج. وفي الحالين لا تشير المراجعة النشطة بعد الآن إلى حزم XFA، فيفتح العارض الذي يفهم النموذجين حقول AcroForm بدل محاولة تخطيط نموذج XFA. بايتات XFA الأصلية ما تزال في المراجعة الأسبق، وهذا ما يعنيه تحديث تدريجي، لكن لا شيء في المراجعة الحالية يشير إليها
uses
System.SysUtils, System.Classes, System.IOUtils, FPdfDocumentXfaFlatten;
procedure ConvertXfaForm(const SourceFile, TargetFile: string);
var
Source: TFileStream;
Options: TPdfXfaFlattenOptions;
OutputPdf: TBytes;
Report: TPdfXfaFlattenReport;
Fields: TPdfXfaFlattenFields;
begin
Options := TPdfXfaFlattenOptions.Default;
Options.MaxFields := 1024; // الافتراضي 512 والمدى المسموح 1..4096
Source := TFileStream.Create(SourceFile, fmOpenRead or fmShareDenyWrite);
try
if not FlattenPdfXfaToAcroForm(Source, Options, OutputPdf, Report, Fields) then
raise Exception.Create('XFA conversion failed: ' + Report.ErrorText);
finally
Source.Free;
end;
TFile.WriteAllBytes(TargetFile, OutputPdf);
Writeln(Format('%d template fields, %d bound to datasets, %d written on %d pages',
[Report.TemplateFields, Report.DatasetBound, Report.FieldsWritten, Report.PagesWritten]));
end;
تعيد الدالة False وتملأ Report.ErrorText لأي مدخل مرفوض أو مشوه، وتعيد موضع تدفق المصدر في طريقها للخارج. الإلغاء هو مسار الاستثناء الوحيد الذي ينتشر: إذا انطلق Options.CancellationToken أُعيد رفع EPdfOperationCancelled بعد ملء التقرير. ويحمل TPdfXfaFlattenReport أيضاً SourceSha256 و OutputSha256، وهو ملائم عندما يلزم لوظيفة دفعية أن تثبت أي مدخل أنتج أي ملف محول. يضبط TPdfXfaFlattenOptions.Default ميزانية حزم 8 MiB، و512 حقلاً، وسقف مخرجات 512 MiB؛ ولا يجوز أن يكون المصدر مشفراً
قراءة حزمتي القالب و datasets
يقبل PDFium Component التخطيطين اللذين يسمح بهما ISO 32000-1 §12.7.8 لمدخل /XFA: صيغة المصفوفة التي تناوب أسماء الحزم ومراجع التدفقات، وصيغة XDP ذات التدفق الواحد حيث يحمل تدفق واحد مستند <xdp:xdp> كاملاً. قد تكون تدفقات الحزم غير مضغوطة أو /FlateDecode دون منبئ، ويُحد فك الضغط بـ MaxPacketBytes
حالة التدفق الواحد هي حيث ينهار التعامل الساذج مع XML. يكتب المصممون عادة <xfa:datasets> ببادئة نطاق بينما يجلس القالب بلا بادئة، فالتقطيع باسم الوسم الحرفي يجد حزمة ويفوّت الأخرى. يطابق المحول أسماء العناصر والسمات بالاسم المحلي، بعد فحص منفصل بأن أزواج وسوم الفتح والإغلاق تتطابق باسمها الكامل المسبوق. وإن كنت تحتاج فقط إلى النظر في الحزم لا تحويلها فتغطي دليل كشف نماذج XFA واستخلاص حزمها بواسطة PDFium VCL ذلك المسار القرائي فقط
قيم الحقول تأتي من موضعين. عنصر <value> في القالب يزود الافتراضي، وحزمة datasets تزود البيانات المعبأة، مربوطة باسم الحقل. تتكرر أسماء الحقول في النماذج الحقيقية، فيقارن الرابط سلسلة أجداد <subform> المسماة بمسار الأجداد في البيانات ويأخذ أطول مطابقة غير متضاربة. وعندما يتشارك حقلا ورقة اسماً تحت subforms مختلفة يصير الـ /T في AcroForm SubformPath.Name فيبقى الحقلان مميزين بعد التحويل
لماذا يحتاج choiceList علماً Combo صريحاً؟
لدى XFA و PDF افتراضان متعاكسان لحقول الاختيار، فالمحول الذي لا ينسخ أعلاماً يحول كل قائمة منسدلة إلى صندوق قائمة. <choiceList> في XFA ذات open="userControl" الافتراضية أو ذات open="onEntry" قائمة منسدلة؛ وحقل /FT /Ch في PDF بلا أعلام صندوق قائمة ممرِّغ، وعلم Combo وحده (موضع البت 18 في الجدول 230 من ISO 32000-1، القيمة 131072، البت نفسه الذي تسميه PDFium FPDF_FORMFLAG_CHOICE_COMBO) يجعله صندوق تحرير وسرد. لذلك يضبط المحول علم Combo لحالات المنسدلة ويرفع الأعلام فارغة فقط عندما يقول القالب open="always" أو open="multiSelect"، ما يبقي القائمة مفتوحة دوماً. قلبت الإصدارات قبل 3.181.1 قيمتي Combo و Pushbutton وبحثت عن open="on" غير قياسية، فالنماذج المسطحة بها تستحق إعادة التشغيل
بقية مطابقة الواجهة تتبع جداول أعلام الحقول في ISO 32000-1 §12.7.4:
textEditوnumericEditوdateTimeEditتصير/FT /Tx؛ ويحصل حقل نص متعدد الأسطر على علم Multiline (/Ff 4096، الجدول 228) وتدفق مظهر مقسم الأسطر- تصير
passwordEdit/FT /Txبعلم Password (/Ff 8192) ومظهر يرسم نجمة واحدة لكل scalar في Unicode لا لكل وحدة كود UTF-16 - تصير
checkButtonصندوق اختيار/FT /Btnتكون قيمته/Yesعندما تكون البيانات المرتبطة1و/Offخلاف ذلك - تصير
<exclGroup>أباً radio واحداً/FT /Btn /Ff 32768 (الجدول 226) بـ/Vو/DVو/Optو/Kids، مع widget ابن لكل عضو بـ/ASخاصة و/APثنائية الحالة - تصير
imageEditزر دفع يرسم مظهره الصورة؛ وتغرز JPEG أساسية حرفياً تحت/DCTDecode، بينما تفك PNG ترميزها وتعيد ترميزها بكسلات/FlateDecodeمع/SMaskإضافية عندما تحمل PNG ألفا أو شفافيةtRNS
القيم التي ليست ASCII قابلة للطباعة خالصة، بما فيها فواصل الأسطر، تُكتب سلاسل <FEFF...> بصيغة UTF-16BE، فينجو اسم ياباني أو عنوان من سطرين في الرحلة. ويطابق مخرج Fields ما كُتب، وهو ما يجعله مسار تدقيق ملائماً
procedure DumpConvertedFields(const Fields: TPdfXfaFlattenFields);
const
KindNames: array[TPdfXfaFlattenFieldKind] of string = (
'text', 'check', 'choice', 'image', 'numeric', 'dateTime', 'password', 'radio');
var
I: Integer;
F: TPdfXfaFlattenField;
begin
for I := 0 to High(Fields) do
begin
F := Fields[I];
Writeln(Format('%s.%s [%s] page %d at %.1f,%.1f size %.1f x %.1f',
[F.SubformPath, F.Name, KindNames[F.Kind], F.PageIndex + 1, F.X, F.Y, F.W, F.H]));
case F.Kind of
fxfkChoice:
Writeln(Format(' %d items, list box: %s', [Length(F.Choices), BoolToStr(F.ListBox, True)]));
fxfkRadio:
Writeln(Format(' %d radio widgets, value "%s"', [Length(F.RadioWidgets), F.Value]));
fxfkImage:
Writeln(Format(' %d image bytes', [Length(F.ImageData)]));
else
Writeln(' value "' + F.Value + '"');
end;
end;
end;
الهندسة ووحدات القياس وتوجيه الصفحات
تتحول هندسة XFA إلى فضاء مستخدم PDF بقلب كل حقل مقابل ارتفاع MediaBox لصفحته الخاصة، لأن XFA تقيس نزولاً من أعلى الصفحة و PDF تقيس صعوداً من الأسفل. تتراكم الإزاحات من عناصر <subform> المتداخلة في النزول عبر القالب، ويجب أن ينتهي كل حقل بعرض وارتفاع موجبين وإلا رُفض التحويل
تقبل القياسات الوحدات التي تبثها المصممات الحقيقية: أعداداً مجردة و pt، مع in (72 نقطة)، و mm (72 / 25.4)، و cm (72 / 2.54)، و pc (12)، و px (0.75)، و mp (0.001، ميلي نقاط). التحليل مستقل عن المحلية، فتثبيت Windows ألماني لا يحول 8.5in إلى هراء. ولاحقة وحدة مجهولة تُرفض بدل قرائها نقاطاً، وهو ما كان سينقل الحقل خطأً بصمت
يتبع توجيه الصفحات <pageSet>: تُرقَّم عناصر <pageArea> الشقيقة من واحد بترتيب التصريح وتطالب بالحقول المتداخلة تحتها، بينما تبقى الحقول بلا جدّ <pageArea> في الصفحة الأولى. وينال كل صفحة تحديث /Annots خاصاً بها. حقل يُوجَّه بعد الصفحة الأخيرة في الـ PDF خطأ لا widget أُلقى في الصفحة الأولى
أي نماذج XFA يرفضها المحول؟
يرفض المحول أي شيء لا يمكن معرفة تخطيطه دون تشغيل محرك تخطيط XFA. ذلك يعني سمة layout غير position على <subform> أو <exclGroup> (المتدفق tb و lr-tb وأشباههما، وهو ما تُبنى منه النماذج الديناميكية)، وأي عنصر <script>، وأي نوع واجهة خارج السبعة المسرودة أعلاه. النماذج الديناميكية التي تكبر صفحاتها وقت التشغيل تخص سلسلة أدوات أخرى؛ وتوضح مقالة إعادة تقسيم صفحات XFA الديناميكية وعدّ الصفحات في PDFium Component ما يتضمنه ذلك التخطيط الحي
الرفض مقصود. subform المتدفق بلا إحداثيات ثابتة، فأي «تحويل» سيضطر لاختراع مواضع، والنموذج المبرمج غالباً يحسب مجاميعاً أو يبدل ظهوراً لا يستطيع AcroForm ساكن استنساخها. إنتاج PDF يبدو تاماً لكنه فقد بصمت مجموعاً محسوباً أسوأ من خطأ واضح في Report.ErrorText. تعرف مواصفة XFA 3.3 أكثر بكثير من هذه المجموعة الفرعية، والمحول لا يتظاهر بخلاف ذلك
مزالق عند كتابة مراجعة AcroForm والتحقق منها
برزت مشكلتان أثناء بناء الكاتب، وكلتاهما تنطبق على كل من ينتج تحديثات تدريجية يدوياً. الأولى: يجب أن يكون الكتالوج دوماً جزءاً من مجموعة التحديث، حتى عندما لا تتغير بايتاته لأن AcroForm كائن غير مباشر يُعاد كتابته في مكان آخر. يشتق كاتب التحديث المتفرق المقطع الختامي /Root من الكائنات التي يحملها، والتحديث دون كائن الجذر ينتج مراجعة لا تستطيع العارضات مرساتها
الثانية تخص التحقق. تحميل الملف المحول عبر غلاف PDFium يطلق تهيئة بيئة النموذج بمجرد أن يحوي المستند /AcroForm، وكان ملف pdfium.dll المضيف في إعداد الاختبار ينقصه التصدير FORM_GetTextDirection، ففشلت التهيئة قبل فحص أي حقل. لذلك تستخدم فحوصات القراءة العائدة قارئ كائنات المكوّن، الذي يحلل المراجعة النشطة دون لمس طبقة النماذج
uses
System.SysUtils, System.Classes, FPdfCompress;
function HasActiveAcroForm(const Pdf: TBytes): Boolean;
var
Stream: TBytesStream;
Objects: TPdfActiveObjects;
Revision: TPdfSparseRevisionInfo;
ErrorText: string;
begin
Stream := TBytesStream.Create(Pdf);
try
if not ReadPdfActiveObjects(Stream, TPdfActiveObjectReadOptions.Default,
Objects, Revision, ErrorText) then
raise Exception.Create(ErrorText);
// يجب أن يشير الكتالوج النشط إلى AcroForm؛ و AcroForm مضمّن
// كتبه المحول لا يحمل مدخل /XFA
Result := Pos('/AcroForm', string(Revision.CatalogDictionary)) > 0;
finally
Stream.Free;
end;
end;
تدفقات المظهر بسيطة عن قصد: Helvetica بمقاس 10 نقاط لقيم النص والاختيار، ومربع مرسوم حدوداً لصناديق الاختيار، ومربع داخلي معبأ لحالة الـ radio المنتقاة. تجعل النموذج المحول مقروءاً في كل مكان، لكنها ليست نسخة بكسلية من عرض XFA الأصلي، فالخطوط والألوان والحدود المعرفة في القالب لا تُنقل. وإن كان لا بد أن يبدو نموذج مطابقاً لمخرج المصمم فاعرضه مرة بالبناء الممكن بـ XFA وقارن قبل تحويل أنبوب إنتاج
FAQ
هل يحتاج FlattenPdfXfaToAcroForm بناء V8 أو XFA من PDFium؟
لا. يحلل FlattenPdfXfaToAcroForm الحزم ويكتب المراجعة بكود PDFium Component، فيعمل مع pdfium.dll القياسي ولا ينفذ أي سكربت XFA
هل يعالج المحول مستندات XFA المشفرة؟
ليس مباشرة. يُرفض المصدر المشفر بخطأ، ففك تشفير المستند أولاً وحوّل النسخة المفكوكة
هل تظل الحقول المحولة قابلة للتحرير؟
نعم. يحوي المخرج حقول AcroForm حقيقية للنص وصناديق الاختيار و radio والاختيار وأزرار الدفع بقيمها وتدفقات مظهرها، فيستطيع المستخدمون مواصلة ملء النموذج في أي عارض يدعم AcroForm
تحويل XFA الساكنة واستخلاص حزم XFA وزمن تشغيل XFA الممكن بـ V8 كلها من PDFium Component لـ Delphi و C++Builder، الذي يغلّف PDFium لمشاريع Delphi و C++Builder و Lazarus