Technical Article

تسطيح روابط XFA النصية المنسقة إلى روابط PDF في Delphi

تنسيق XFA، وهو بنية نماذج XML، تم إهماله. يحمله معيار ISO 32000-1 في القسم 12.7 مع ملاحظة أنه تمت إزالته من PDF 2.0، وتتخلى برامج العرض الحديثة عن محركات XFA الخاصة بها واحداً تلو الآخر. لم يفرغ أي من هذا الأرشيف. تمت كتابة نماذج الإدخال الحكومية، وطلبات التأمين، والبيانات المصرفية كـ XFA لأكثر من عقدين من الزمن، وما زالت هذه الملفات تصل إلى صناديق الوارد وخطوط عمل المستندات اليوم. عندما يتوقف برنامج العرض الذي اعتاد على عرضها عن القيام بذلك، يتحول النموذج إلى صفحة فارغة بها عنصر نائب "يرجى الفتح في قارئ مختلف". الإصلاح الدائم هو تسطيح XFA إلى محتوى PDF ثابت يمكن لأي قارئ رسمه.

الجزء الصعب في هذا التسطيح ليس الحقول. تعين صناديق النصوص وخانات الاختيار إلى أدوات AcroForm بشكل نظيف تماماً. الجزء الصعب هو النص المنسق (rich text) الذي يخزنه XFA داخل عنصر رسم، في كتلة <exData contentType="text/html">. هذه الكتلة عبارة عن مجموعة فرعية من HTML مع تنسيق داخلي، وغالباً ما تحتوي على روابط مرساة (anchors). الحصول عليها على الصفحة يعني إعادة إنتاج النص المنسق والروابط التشعبية النشطة معاً، والروابط التشعبية هي المكان الذي تستسلم فيه معظم الحلول البرمجية بصمت.

كيف يبدو نص XFA المنسق بالفعل

جسم exData عبارة عن شريحة صغيرة من XHTML. الفقرة هي <p>، والمجال المنسق للحروف هو <span> مع تنسيق CSS داخلي خاص به للوزن والميل واللون والحجم، والرابط التشعبي هو <a href="..."> يغلف نصه المرئي. يمكن لسطر واحد أن يحمل عدة مجالات على التوالي، لكل منها تنسيق مختلف، ويمكن أن يكون أحدها رابط مرساة. التنسيق ليس زينة يمكن التخلي عنها. الشرط المعروض باللون الأحمر العريض لأنه تحذير قانوني يجب أن يظل عريضاً وأحمر بعد التسطيح، وإلا فإن المستند المسطح يشوه المستند الأصلي.

لذا لا يمكن لمحرك التسطيح معاملة الكتلة كسلسلة نصية واحدة. يتعين عليه السير في الهيكل الداخلي، وحل النمط الفعال لكل مسار عن طريق وضع تنسيق CSS الداخلي للمجال فوق الخط الأساسي لعنصر الرسم، ووضع المسارات تلو الآخر عبر السطر. يمثل HotPDF كل قطعة من هذه القطع الموضوعة كسجل TXFARichRun داخلي. يحمل السجل نص المسار، ونمطه الذي تم حله، وصندوقه المقاس، وبالنسبة للمرساة، عنوان Href الذي تشير إليه.

ترتيب المسارات من اليسار إلى اليمين

تحديد الموضع هو النقطة التي يتوقف فيها النص المنسق عن كونها مشكلة تحليل ويصبح مشكلة صف حروف. تتشارك المسارات في سطر واحد، لذا يبدأ كل مسار من حيث انتهى المسار السابق. لا توجد علامات تسجل تلك المواضع، بل يجب قياسها. يقيس روتين LayoutRichText الداخلي للمحرك كل مسار بنفس مقاييس الخط التي سترسمه لاحقاً، ثم يضبط الإزاحة الأفقية للمسار على المجموع التراكمي لجميع عروض المسارات السابقة. يبدأ المسار الأول عند أصل صندوق الرسم، ويبدأ المسار الثاني عند عرض المسار الأول، والثالث عند العرض المشترك للمسارين الأولين، وهكذا عبر السطر.

هذا هو السبب في أن محاذاة خط القياس مهمة للغاية. يمرر تخطيط الموضع القياسات المسبقة، بينما يرسم ممر عرض منفصل المحارف. إذا اختلف هذان ممرات العرض حول الخط، فإن الصناديق التي حسبها التخطيط لن تقع تحت المحارف التي يرسمها العارض. يحافظ HotPDF على توافقهما عن طريق تعيين النمط الذي تم حله لكل مسار على مواصفات خط، من خلال الأداة المساعدة الداخلية RunStyleToFontSpec، والتي تطابق قيم العارض الافتراضية لخط Arial بحجم 10 نقاط. عندئذ يتفق القياس المسبق والنص المرسوم، ويغطي صندوق المسار المحسوب الحروف التي يراها القارئ بالفعل.

// Conceptual shape of one laid-out run. The engine builds an array of these
// internally; you never construct them yourself, but the fields explain how a
// link's hit box is derived from measured geometry rather than from text.
type
  TRichRunInfo = record
    Dx, Dy : Double;       // top-left, relative to the draw-box origin
    W, H   : Double;       // measured run box (width from the layout pass)
    Text   : AnsiString;   // the run's visible characters
    Href   : AnsiString;   // URI target for an <a> run, '' otherwise
  end;

من مسار مرساة إلى تعليق توضيحي لرابط PDF

الرابط التشعبي في ملف PDF النهائي ليس جزءاً من محتوى الصفحة. إنه كائن منفصل، وهو تعليق توضيحي للرابط (Link annotation)، موضح في القسم 12.5.6.5 من معيار ISO 32000-1. يحتوي التعليق التوضيحي على /Rect يحدد المستطيل القابل للنقر على الصفحة وإجراءً يتم تشغيله عند النقر على المستطيل. بالنسبة للرابط الخارجي، يكون الإجراء هو إجراء URI: أي /S /URI مع العنوان المستهدف كسلسلة نصية لـ /URI. النص المرئي تحته هو محتوى صفحة عادي، والتعليق التوضيحي هو المنطقة الساخنة غير المرئية الموضوعة فوقه.

يتبع مسار التسطيح هذا النموذج تماماً. عندما يحمل المسار عنوان Href، يرسم HotPDF أولاً النص المنسق، ثم يبني تعليقاً توضيحياً للرابط فوق صندوق المسار. نقطة الدخول العامة لهذا التعليق التوضيحي هي طريقة الصفحة AddURILink، والتي تنشئ الكائن /Type /Annot /Subtype /Link مع إجراء /URI وتعيد قاموس التعليق التوضيحي. ومستطيله هو صندوق المسار المقاس، والمترجم من الإحداثيات المحلية لعنصر الرسم إلى إحداثيات الصفحة. والنتيجة هي رابط يستقر بدقة على نص المرساة وليس في أي مكان آخر.

// The same public API the flatten path uses for each anchor run. It produces
// an ISO 32000-1 12.5.6.5 Link annotation: /Subtype /Link with a /URI action
// over the given rectangle. The optional description fills /Contents so a
// screen reader can announce the target.
var
  LinkRect: TRect;
  Annot: THPDFDictionaryObject;
begin
  LinkRect := Rect(72, 690, 268, 706);  // page-space hit box for the run
  Annot := Pdf.CurrentPage.AddURILink(LinkRect,
    'https://www.example.gov/appeal', 'File an appeal online');
end;

لماذا يجب أن يأتي صندوق النقر من العروض المقاسة

من المغري تخيل تحديد موقع الرابط من خلال البحث في الصفحة عن نصه المرئي ورسم المستطيل حول ما يتم العثور عليه. هذا لا يعمل، والسبب جوهري في كيفية تخزين النص المسطح. يتم رسم المسارات المنسقة باستخدام خطوط فرعية مضمنة. يعيد الخط الفرعي ترقيم المحارف التي يحتفظ بها، لذا يحتوي تدفق محتوى الصفحة على رموز CID ست عشرية، وليس رموز الحروف الأصلية. البايتات الموجودة على الصفحة ليست الحروف التي يقرأها الإنسان، ولا يمكن البحث عنها كنص. البحث عن تسمية المرساة لا يجد شيئاً، لأن تلك التسمية لا توجد كنص حرفي في أي مكان في التدفق.

المرساة الوحيدة الموثوقة للمستطيل هي الهندسة التي أنتجها ممر التخطيط بالفعل. تم حساب إزاحة كل مسار وعرضه المقاس أثناء تدفق السطر، قبل إعادة ترقيم أي محرف، وهي تصف أين سيظهر النص فعلياً على الصفحة. لذلك يأخذ HotPDF مستطيل الرابط مباشرة من الصندوق الموضوع للمسار بدلاً من أي بحث نصي. ونظراً لأن القياس استخدم خط العرض، فإن الصناديق تكون صحيحة بغض النظر عن التجزئة الفرعية. تنجو الهندسة من الترميز، بينما لا ينجو النص. هذا هو الحجة الكاملة لتحديد الموضع بناءً على العرض المقاس، ولهذا السبب فإن برنامج التسطيح الذي يحاول تركيب الروابط لاحقاً عن طريق البحث النصي ينتج مناطق نقر تنحرف أو تختفي.

تشغيل التسطيح من الكود الخاص بك

بالنسبة لملف PDF الذي يحتوي بالفعل على حزمة XFA، فإن نقطة الدخول هي FlattenLoadedXFA. قم بتحميل المستند، واستدع الطريقة، واحفظ النتيجة. يحدد المعامل Editable ما يحدث لحقول النموذج: مرر True لإبقائها كأدوات AcroForm قابلة للتعبئة، أو False لوصف كل أداة بأنها للقراءة فقط بحيث تكون المخرجات عبارة عن سجل مجمد. يتم إنتاج كتل رسم النصوص المنسقة، بمساراتها المنسقة وتعليقات الروابط التوضيحية، في كلتا الحالتين. تعيد الدالة عدد الأدوات التي أصدرتها.

var
  Pdf: THotPDF;
  Emitted, i: Integer;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.LoadFromFile('xfa_appeal_form.pdf');
    // True keeps fields fillable; False freezes them read-only.
    Emitted := Pdf.FlattenLoadedXFA(True);

    // Anything the engine could not map is reported, not raised.
    for i := 0 to Pdf.XFAFlattenWarnings.Count - 1 do
      Writeln('XFA warning: ', Pdf.XFAFlattenWarnings[i]);

    Pdf.SaveLoadedDocument('appeal_form_flat.pdf');
    Writeln('Widgets emitted: ', Emitted);
  finally
    Pdf.Free;
  end;
end;

اقرأ دائماً XFAFlattenWarnings بعد الاستدعاء. يتم مسح القائمة عند بدء كل عملية تسطيح وتتراكم أسطر لكل عنصر رفض المحرك عرضه: نوع حقل غير مدعوم، أو صورة رسم لم يتم فك ترميزها، أو كتلة exData لا تحتوي على مجالات صالحة للاستخدام. لا يثير أي من ذلك استثناءً، لذا فإن قائمة التحذيرات الفارغة هي دليلك على أن كل شيء قد تم تعيينه، وتخبرك القائمة غير الفارغة بالضبط بأي المستندات الأصلية يجب فحصها. عندما تحتفظ بملف XFA الخام كبايتات XDP بدلاً من ملف PDF محمل، فإن الطريقة الشقيقة ApplyXFAAsAcroForm تأخذ تلك البايتات مباشرة وتتشارك نفس مسار الكود ونفس سلوك التحذيرات. بينما تذهب الطريقة المكملة AddXFAPacket في الاتجاه الآخر، حيث تضمن حزمة XFA في مستند تقوم ببنائه.

تأكيد النتيجة في برنامج العرض

افتح الملف المسطح في Acrobat، أو أي برنامج عرض حالي، وتحقق من شيئين. أولاً، أن النص المنسق قد تم عرضه مع الحفاظ على تنسيقه: المسارات العريضة تظل عريضة، والملونة تحمل لونها، والمجالات تقع بالترتيب الصحيح على السطر بدلاً من التداخل أو الخروج عن الصندوق. ثانياً، أن الروابط التشعبية نشطة. مرر الماوس فوق المرساة ويجب أن يظهر شريط الحالة العنوان المستهدف، وانقر عليه ويجب أن يفتحه إجراء URI. استخدم مفتش التعليقات التوضيحية لبرنامج العرض لتأكيد أن كل رابط هو تعليق توضيحي حقيقي لـ /Link يحضن مستطيله /Rect نص المرساة، ويقع فوق محتوى أصبح الآن محارف مرسومة عادية بدلاً من نموذج XFA. هذا المزيج، النص الثابت المنسق بالإضافة إلى التعليقات التوضيحية الحقيقية للروابط على المستطيلات الصحيحة، هو ما يجعل المستند المسطح يعيش لفترة أطول من محركات XFA التي لم يعد بحاجة إليها.

تسطيح الحقول نفسها، مثل صناديق النصوص، وخانات الاختيار، وقوائم الخيارات التي تحيط بهذا النص المنسق، مغطى في دليلنا حول تسطيح نماذج XFA إلى أدوات AcroForm. وللحصول على القصة الأوسع لبناء ووضع التعليقات التوضيحية للروابط يدوياً، بخلاف تلك التي يولدها مسار التسطيح، راجع العمل مع تعليقات PDF التوضيحية في HotPDF. يعتمد كلاهما على نفس نموذج التعليقات التوضيحية والنماذج الذي يشحن مع مكون HotPDF لـ Delphi وC++Builder.