حقل نموذج PDF بحد ذاته هو مجرد مربع يحمل قيمة. ما يجعل النموذج يتصرف مثل تطبيق صغير هو الإجراء المرتبط به: نقرة تخفي قسماً، أو تسترجع القيم المحفوظة من ملف، أو تنتقل إلى الصفحة الأخيرة، أو تشغل برنامجاً نصياً (script) يجمع عموداً. لا يوجد أي من هذا في الحقل. بل يوجد في قاموس الإجراءات (action dictionary)، وينظم معيار ISO 32000-1 العائلة بأكملها في القسم 12.6. يستعرض هذا المقال الإجراءات التي يستخدمها برنامج دلفي في أغلب الأحيان ويوضح كيف يقوم PDFlibPas بربط كل منها بحقل أو رابط.
النموذج الذهني الذي يجدر الاحتفاظ به هو أن الحقل والإجراء هما كائنان منفصلان مرتبطان بمرجع (reference). يحمل التعليق التوضيحي للأداة (widget annotation) أو التعليق التوضيحي للرابط (link annotation) إجراءً في إدخال /A الخاص به. يسمي الإجراء الحقل الذي يعمل عليه بالعنوان، وليس بالفهرس (index)، لذلك فإن العنوان الذي تطلقه على الحقل هو المعرف الذي يستخدمه كل إجراء لاحق للعثور عليه. بمجرد أن يصبح هذا الفصل واضحاً، تتوقف واجهة برمجة التطبيقات (API) عن الظهور كمجموعة عشوائية من الاستدعاءات وتبدأ في الظهور كنمط واحد مطبق على أربعة أنواع من الأفعال.
الإجراءات المسماة (Named actions): التنقل بدون رقم صفحة
أبسط الإجراءات لا تحمل أي معلمات (parameters) على الإطلاق. يعرّف معيار ISO 32000-1 القسم 12.6.4.11، الجدول 194، الإجراءات المسماة (named actions): يفسر العارض (viewer) اسماً رمزياً في وقت التشغيل بدلاً من اتباع وجهة مخزنة. توجد أربعة أسماء مدعومة عالمياً، وهي بالضبط ما يتوقعه القارئ من شريط الأدوات: NextPage و PrevPage و FirstPage و LastPage. نظراً لأن الوجهة نسبية لأي صفحة يعرضها العارض حالياً، فإن زر "التالي" (Next) المبني بهذه الطريقة يعمل على كل صفحة دون أن تضطر إلى حساب الوجهة.
في PDFlibPas، يتم إرفاق إجراء مسمى بمستطيل نقطة فعالة (hotspot) على الصفحة الحالية. تحدد الوسيطتان (arguments) الصحيحتان الرابعة والخامسة الفعل (verb) والمظهر (appearance).
// NamedActionType: 0 = NextPage, 1 = PrevPage, 2 = FirstPage, 3 = LastPage
// Options bit 0 (value 1) draws a border around the hotspot
Pdf.AddLinkToNamedAction(500, 560, 60, 18, 0, 1); // Next
Pdf.AddLinkToNamedAction(40, 560, 60, 18, 1, 1); // Previous
Pdf.AddLinkToNamedAction(110, 560, 60, 18, 3, 1); // jump to last page
لا توجد وجهة للاحتفاظ بمزامنتها، وهذا هو الهدف الأساسي. يظل الإجراء المسمى سارياً عند إدراج صفحة أو حذفها لأنه لا يسمي صفحة في المقام الأول. قارن ذلك برابط انتقال صريح (go-to link)، والذي يخزن فهرس صفحة مستهدفة يجب عليك إعادة ترقيمه في اللحظة التي يكبر فيها المستند.
إجراء الإخفاء (Hide action) وفخ المصفوفة (array) الخاص به
يقوم إجراء الإخفاء (Hide action)، وفقاً لمعيار ISO 32000-1 القسم 12.6.4.10، الجدول 196، بتبديل رؤية (visibility) حقل واحد أو أكثر. إنها الطريقة الأنظف لبناء سلوك إظهار وإخفاء بدون برمجة نصية، وهو ما تريده لرابط "إظهار التفاصيل" (Show details) أو لوحتين متنافستين (mutually exclusive) حيث يؤدي كشف إحداهما إلى إخفاء الأخرى. يحمل الإجراء هدفاً في إدخال /T الخاص به وقيمة منطقية /H تحدد الاتجاه: إخفاء عندما تكون القيمة true، وإظهار عندما تكون القيمة false.
تكمن الدقة تماماً في كيفية ترميز هذا الهدف، وهو نوع من التفاصيل التي تنتج نموذجاً يعمل على جهازك ويفشل على جهاز العميل. عندما يسمي الإجراء حقلاً واحداً، تتم كتابة /T كسلسلة نصية واحدة. عندما يسمي عدة حقول، تتم كتابة /T كمصفوفة من السلاسل النصية. لا تعامل العوارض القديمة مصفوفة مكونة من عنصر واحد بنفس الطريقة التي تعامل بها سلسلة مجردة، لذلك يجب أن يتفرع الترميز بناءً على العدد: يجب إصدار اسم واحد كسلسلة، وليس كمصفوفة بطول واحد، إذا كنت تريد أن يستجيب لها النطاق الأوسع من القراء. يتخذ PDFlibPas هذا القرار نيابة عنك. أنت تمرر أسماء الحقول مفصولة بفواصل، أو فواصل منقوطة، أو فواصل أسطر (line breaks)، ويصدر الكاتب سلسلة واحدة للاسم الواحد ومصفوفة لاسمين أو أكثر.
// HideFlag non-zero hides the listed fields (/H true); zero shows them.
// One name -> /T is a text string. Two or more -> /T is an array of strings.
Pdf.AddLinkToHideField(40, 700, 90, 18, 'ShippingAddress', 1, 1);
Pdf.AddLinkToHideField(140, 700, 90, 18,
'ShippingName,ShippingAddress,ShippingZip', 1, 1);
نظراً لأن الإجراء لا يشير إلى مورد خارجي، فإنه يظل متوافقاً مع PDF/A. الأسماء التي تمررها هي عناوين حقول مؤهلة بالكامل (fully qualified)، ولهذا السبب يجب توجيه الحقل الفرعي (child field) داخل مجموعة من خلال مساره المنقط الكامل بدلاً من اسمه الورقي (leaf name) المجرد.
استيراد البيانات (ImportData): التعبئة المسبقة من FDF
حيثما يعيد إجراء الإخفاء ترتيب ما هو موجود بالفعل على الصفحة، فإن إجراء استيراد البيانات (import-data action) يجلب قيماً من خارجها. يعرّفه معيار ISO 32000-1 القسم 12.6.4.8، الجدول 198، بأنه إجراء يملأ نموذج AcroForm من ملف تنسيق بيانات النماذج (Forms Data Format) على القرص. هذا هو الإجراء الذي يقف خلف عنصر تحكم "إعادة تحميل بيانات العينة" (Reload sample data) أو "إعادة التعيين إلى الإعدادات الافتراضية" (Reset to defaults)، حيث يتم إرفاق ملف FDF بجوار ملف PDF ويحتوي على قيم الحقول الأساسية. يحاكي الاستدعاء الاستدعاءات الأخرى، حيث يأخذ مستطيل النقطة الفعالة، والمسار إلى FDF، وقناع بت للمظهر (appearance bitmask): Pdf.AddLinkToImportData(40, 660, 120, 18, 'defaults.fdf', 1). لا يحتاج الملف إلى أن يكون موجوداً عند إنشاء ملف PDF، ولكن يجب أن يكون موجوداً عندما ينقر المستخدم، وتتم إعادة كتابة أي خطوط مائلة عكسية (backslashes) في المسار إلى صيغة الخط المائل القياسية لـ PDF نيابة عنك.
يستحق أحد القيود ذكره بوضوح لأنه غالباً ما يكون مفاجئاً. يشير إجراء استيراد البيانات إلى ملف خارجي، لذا فهو غير مسموح به في PDF/A. عندما يكون المستند في وضع PDF/A، يرجع الاستدعاء صفراً ولا يضيف أي شيء بدلاً من إنتاج ملف يفشل في التحقق من الصحة. إذا كان مسارك (pipeline) يستهدف المخرجات الأرشيفية، فيجب أن تتم التعبئة المسبقة في وقت الإنشاء عن طريق كتابة قيم الحقول مباشرة، وليس عن طريق تأجيلها حتى يتم النقر عليها.
جافا سكريبت (JavaScript): الحزم العامة (global packages) والبرامج النصية لكل إجراء (per-action scripts)
بالنسبة للمنطق الذي يتجاوز الإظهار، والإخفاء، والاستيراد، تصل عائلة الإجراءات إلى جافا سكريبت (JavaScript) على مستوى المستند. هناك مكانان متميزان يمكن أن يتواجد فيهما البرنامج النصي، والفرق بينهما مهم. يتم تخزين حزمة جافا سكريبت على مستوى المستند مرة واحدة للملف بأكمله وتعمل عند فتح المستند، مما يجعلها الموطن المناسب لتعريفات الوظائف (functions) والحالة المشتركة (shared state). يتم إرفاق برنامج نصي لكل إجراء برابط أو حقل واحد ويعمل فقط عند تنشيط هذا الكائن، مما يجعله الموطن المناسب للسطر الواحد الذي يستدعي وظيفة تم تعريفها مسبقاً في الحزمة.
يُعرض PDFlibPas كليهما. يقوم AddGlobalJavaScript بتخزين حزمة مسماة على مستوى المستند؛ إعادة استخدام اسم يستبدل ما كان مخزناً تحته. يقوم AddLinkToJavaScript بإرفاق برنامج نصي بنقطة فعالة بحيث تؤدي النقرة إلى تنفيذه.
// Document-level package: define a reusable function once.
Pdf.AddGlobalJavaScript('Totals',
'function recalcTotal() {' +
' var net = this.getField("Net").value;' +
' var tax = this.getField("Tax").value;' +
' this.getField("Gross").value = Number(net) + Number(tax);' +
'}');
// Per-action script on a link: just call the shared function.
Pdf.AddLinkToJavaScript(40, 620, 100, 18, 'recalcTotal();', 1);
الاحتفاظ بالوظيفة في الحزمة العامة والاستدعاء في الرابط ليس مجرد تفضيل في الأسلوب. إنه يتجنب تكرار نفس الهيكل (body) على كل عنصر تحكم يحتاجه، ويعني أن العارض الذي تم تعطيل البرمجة النصية فيه لا يفعل شيئاً ببساطة عند النقر بدلاً من التعثر (choking) في كتلة مضمنة مشوهة. كما أنه يحافظ على صغر حجم الإدخالات الخاصة بكل إجراء، مما يبقي الملف قابلاً للقراءة عند فحصه لاحقاً.
الحقول، والحقول الفرعية، وتجميد النتيجة (freezing the result)
تحتاج الإجراءات إلى حقول للعمل عليها، لذا من المفيد أن نرى كيف يتم إنشاء الحقل. يقوم NewFormField بإنشاء حقل على الصفحة الحالية ويُرجع فهرسه؛ يحدد النوع الصحيح (integer type) الصنف، حيث 1 للنص (Text)، و 2 لزر الدفع (Pushbutton)، و 3 لمربع الاختيار (Checkbox)، و 4 لزر الراديو (Radiobutton)، و 5 للاختيار (Choice)، و 6 للتوقيع (Signature)، و 7 للوالد (Parent) الذي يمتلك فروعاً (children) ولكنه لا يرسم أي شيء بنفسه. العنوان الذي تمرره لا يمكن أن يحتوي على نقطة، لأن النقطة هي الفاصل (separator) في الأسماء المؤهلة بالكامل التي تستخدمها الإجراءات لمخاطبة الفروع.
يتم بناء مجموعات أزرار الراديو (Radio groups) والنماذج الهرمية عن طريق إعطاء حقل والد (parent field) فروعاً. يضيف NewChildFormField فرعاً تحت والد مسمى، وفي حالات أزرار الراديو والاختيار، يضيف AddFormFieldSub الخيارات الفردية ويعيد فهرساً مؤقتاً تستخدمه لوضع كل واحد منها. عندما تنتهي المرحلة التفاعلية وتريد تجميد حقل بحيث يصبح مظهره الحالي محتوى صفحة دائماً، يقوم FlattenFormField برسم الحقل على الصفحة وإزالته من النموذج. بعد التسوية (flatten)، تنتقل فهارس الحقول اللاحقة لأسفل بمقدار واحد، وهو الشيء الوحيد الذي يجب تذكره إذا قمت بتسوية عدة حقول في حلقة (loop).
var
Pdf: TPDFlib;
FldShip: Integer;
begin
Pdf := TPDFlib.Create;
try
Pdf.SetOrigin(1); // top-left origin
Pdf.SetPageSize('A4');
Pdf.NewPage;
// A text field the Hide action will target by its title.
FldShip := Pdf.NewFormField('ShippingAddress', 1);
Pdf.SetFormFieldBounds(FldShip, 40, 120, 240, 20);
Pdf.SetFormFieldValue(FldShip, '');
// Wire a Hide link and a navigation link to this page.
Pdf.DrawText(40, 110, 'Toggle shipping block:');
Pdf.AddLinkToHideField(220, 100, 70, 16, 'ShippingAddress', 1, 1);
Pdf.AddLinkToNamedAction(500, 800, 60, 18, 3, 1); // Last page
// A document-level script available to every event in the file.
Pdf.AddGlobalJavaScript('OnOpen',
'app.alert("Form ready", 3);');
// Freeze the field if the output should no longer be editable.
// Pdf.FlattenFormField(FldShip);
if Pdf.SaveToFile('form_actions.pdf') <> 1 then
raise Exception.Create('Save failed');
finally
Pdf.Free;
end;
end;
استدعاء التسوية معطل (commented out) عن قصد. اتركه خارجاً وسيشحن المستند كنموذج حي تنطلق إجراءاته في القارئ. قم بتمكينه وسوف يتم تصيير (rendered) الحقل إلى علامات ثابتة، وهو ما تريده عندما يكتمل النموذج ويجب أن تنتقل النتيجة كسجل ثابت. نفس الحقل، ونفس الكود، ومستندان مختلفان تماماً بناءً على ما إذا كنت ستجمده أم لا.
اختيار الفعل المناسب
تنقسم الإجراءات الأربعة بشكل نظيف حسب ما تلمسه. يقوم الإجراء المسمى بتحريك إطار العرض (viewport) ولا يحتاج إلى حقل. يغير إجراء الإخفاء (Hide action) الرؤية (visibility) ويحتاج إلى عناوين حقول، مع التعامل مع ترميز السلسلة مقابل المصفوفة (string-versus-array encoding) نيابة عنك. يصل إجراء استيراد البيانات إلى ملف على القرص وبالتالي فهو غير مسموح به في PDF/A. يقوم إجراء جافا سكريبت بتشغيل منطق تعسفي ومن الأفضل تقسيمه بين حزمة عامة من الوظائف (functions) واستدعاءات صغيرة لكل إجراء. استخدم أبسط إجراء ينجز المهمة: إجراء الإخفاء أكثر قابلية للنقل من برنامج نصي يقوم بتعيين علامة مخفية (hidden flag)، والإجراء المسمى أكثر متانة من وجهة صفحة مخزنة لأنه لا يوجد رقم يجب الحفاظ عليه.
من هنا، يكمل موضوعان متجاوران الصورة. إذا كان النموذج جزءاً من مستند يمكن الوصول إليه (accessible document)، فإن شجرة البنية التي يتجول فيها قراء الشاشة مغطاة في مقالنا حول PDF الموسوم وبنية إمكانية الوصول (tagged PDF and accessibility structure). عندما يتعين قفل النموذج المكتمل وتوقيعه، يتم وصف سير العمل في نظرة عامة على طاولة عمل الامتثال والتوقيع (compliance and signing workbench). تعتمد الثلاثة جميعها على نفس المحرك، والذي يتم شحنه كـ مكتبة PDF لدلفي جنباً إلى جنب مع واجهات برمجة تطبيقات (APIs) الإنشاء والنموذج والتوقيع المغطاة في مكان آخر من هذه المدونة.