تحتاج فاتورة أو ملف PDF للمصروفات حيث تجمع البنود نفسها ذاتيًا وينسق التاريخ نفسه تلقائيًا إلى تثبيت ثلاثة أشياء بنموذج AcroForm الخاص بها، ويقوم HotPDF بإضافة هذه الأشياء الثلاثة جميعًا إلى ملف تم تحميله في Delphi: الدالة ApplyLoadedFieldCalculation لمجاميع AFSimple_Calculate، و ApplyLoadedFieldValidation لتنسيق Acrobat AF ومساعدي التحقق، و EnsureLoadedFieldAppearanceStream لخبز القيمة التي يرسمها العارض بالفعل
إن HotPDF عبارة عن مكون PDF أصلي لـ VCL في Delphi و C++Builder، وتعمل هذه الطرق الثلاثة على مستند قمت بفتحه باستخدام LoadFromFile بدلاً من مستند تبنيه من الصفر؛ حيث تأخذ كل منها فهرس حقل يبدأ من الصفر في مصفوفة AcroForm /Fields — وهو نفس الترتيب الذي ترجعه GetLoadedFormFieldNames — وتكتب مباشرة في قاموس الحقل، لذا فإن المهمة بأكملها هي التحميل، والفهرسة، والإرفاق، ثم SaveLoadedDocument؛ ويعد بناء مجموعة الحقول نفسها مهمة منفصلة مغطاة في إضافة حقول AcroForm إلى ملف PDF محمل في Delphi، بينما تدور هذه الصفحة حول تزويد مجموعة حقول موجودة بالعمليات الحسابية والتنسيق
لماذا يحتاج نموذج PDF إلى برامج نصية لجمع حقوله تلقائيًا؟
لأن ملف الـ PDF لا يحتوي على طبقة جداول بيانات (spreadsheet layer)؛ فالحقل النصي يحمل سلسلة نصية وليس صيغة حسابية، ولا يوجد شيء في الملف يعيد حساب المجموع عند تغيير بند ما لم يتم إرفاق برنامج نصي (script) للقيام بذلك؛ والآلية هي قاموس الإجراءات الإضافية للحقل، /AA (المواصفة ISO 32000-1 الفقرة 12.7.5)، والذي يطابق أحداث التشغيل بالإجراءات: تشغيل ضربة المفتاح (K) عند وصول الأحرف، وتشغيل التنسيق للعرض، وتشغيل التحقق (V) عند التأكيد، وتشغيل الحساب (C) الذي ينطلق كلما تغير أي حقل في النموذج؛ وكل إجراء هو إجراء جافا سكريبت، /S /JavaScript (المواصفة ISO 32000-1 الفقرة 12.6.4.14)، والبرنامج النصي الذي يقوم بتشغيله هو ما ينفذ بالفعل الجمع أو إعادة التنسيق؛ ويشحن Acrobat مكتبة من المساعدين الجاهزين — AFSimple_Calculate و AFNumber_Format و AFDate_FormatEx والبقية — لذا فإن البرنامج النصي المرفق عادة ما يكون استدعاءً من سطر واحد لتلك المكتبة بدلاً من كود مخصص
هناك تفصيل واحد يحدد ما إذا كان النموذج متعدد الخطوات يعطي الإجابة الصحيحة: ترتيب الحساب؛ حيث يقوم Acrobat بتشغيل كل حقل يحمل إجراء حسابي، ويقوم بتشغيلها بالتسلسل الذي تسرده مصفوفة AcroForm /CO، وليس بترتيب الصفحات؛ وتقوم دالة ApplyLoadedFieldCalculation بإرفاق إجراء الحساب بالحقل الذي تسميه؛ وعندما يغذي حقل حسابي حقلًا آخر — كمجموع فرعي يقرأه سطر الضريبة، ثم مجموع كلي يقرأ كليهما — يتعين عليك تأكيد أن ترتيب /CO يسرد كل مدخل قبل الحقل الذي يستهلكه، وإلا سيقرأ المجموع الكلي مجموعًا فرعيًا قديمًا ويتأخر تعديلاً واحدًا إلى الوراء
كيف تجعل نموذج PDF يحسب المجاميع تلقائيًا؟
تأخذ الدالة ApplyLoadedFieldCalculation فهرس الحقل المستهدف، والعملية، وأسماء الحقول المصدر التي يجب تجميعها؛ ويكتب HotPDF التعبير AFSimple_Calculate("SUM", ["lineTotal.1", "lineTotal.2", ...]) في إجراء حساب الحقل المستهدف، ويقوم التعداد THPDFFieldCalcOp باختيار الدالة: fccSum و fccAverage و fccProduct و fccMinimum و fccMaximum تطابق Acrobat SUM و AVG و PRD و MIN و MAX؛ والأسماء المصدر هي أسماء حقول AcroForm، وهي نفس السلاسل النصية التي ترجعها دالة GetLoadedFormFieldNames، لذا فإن المجموع لثلاثة بنود ورسوم الشحن عبارة عن استدعاء واحد
var
Pdf: THotPDF;
Names: THPDFAnsiStringArray;
i, TotalIdx: Integer;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.LoadFromFile('invoice.pdf');
Names := Pdf.GetLoadedFormFieldNames;
TotalIdx := -1;
for i := 0 to High(Names) do
if Names[i] = 'grandTotal' then TotalIdx := i;
if TotalIdx >= 0 then
begin
Pdf.ApplyLoadedFieldCalculation(TotalIdx, fccSum,
['lineTotal.1', 'lineTotal.2', 'lineTotal.3', 'shipping']);
Pdf.EnsureLoadedFieldAppearanceStream(TotalIdx);
end;
Pdf.SaveLoadedDocument('invoice-live.pdf');
finally
Pdf.Free;
end;
end;
إن استدعاء EnsureLoadedFieldAppearanceStream على الإجمالي ليس تجميليًا؛ فالحساب يتم تشغيله داخل العارض، لذا حتى يقوم قارئ يدعم البرامج النصية بفتح الملف، لا يحتوي حقل الإجمالي على محتوى معروض خاص به؛ وخبز دفق المظهر يعطيه رقمًا لعرضه في هذه الأثناء، وهو ما ستلتقطه مهمة الطباعة أو منشئ الصور المصغرة عندما لا يتم تشغيل البرنامج النصي أبدًا
تنسيق الحقول والتحقق من صحتها باستخدام مجموعة مساعدي AF
تقوم دالة ApplyLoadedFieldValidation بإرفاق التنسيق وضربة المفتاح والتحقق من صحة البرامج النصية في استدعاء واحد، ويحدد THPDFFieldValidationKind الذي تمرره أي مساعد Acrobat يتم ربطه؛ فإذا مررت fvkCurrency يكتب HotPDF التعبير AFNumber_Format(2,0,0,0,"$ ",true) كبرنامج نصي للتنسيق مع AFNumber_Keystroke المطابق؛ ويستخدم fvkNumber الرمز "." لوسيطة الفاصل، بينما يصدر fvkPercentage التعبير AFPercent_Format، ويصدر fvkDate التعبير AFDate_FormatEx("dd/mm/yyyy") ما لم تمرر قناعك الخاص في وسيطة DateFormat؛ وتطابق التنسيقات الخاصة — fvkZipCode و fvkSSN و fvkPhone — التعبير AFSpecial_Format مع الرمز الذي يحجزه Acrobat لكل منها، وتأخذ fvkArbitraryMask سلسلة قناع نصية من خلال وسيطة Mask لأي شيء لا تغطيه هذه الإعدادات المسبقة؛ وبالنسبة للأنواع الرقمية، يرفق HotPDF أيضًا برنامج AFRange_Validate النصي بحيث يتم رفض المدخلات الخارجة عن النطاق عند التأكيد
// تنسيق تاريخ الفاتورة كـ ISO، والمبلغ كعملة، وتغطية معرف الضريبة
Pdf.ApplyLoadedFieldValidation(DateIdx, fvkDate, 'yyyy-mm-dd', '');
Pdf.ApplyLoadedFieldValidation(AmountIdx, fvkCurrency, '', '');
Pdf.ApplyLoadedFieldValidation(TaxIdIdx, fvkArbitraryMask, '', '999-99-9999');
Pdf.EnsureLoadedFieldAppearanceStream(AmountIdx);
يغطي مساعدو AF الأشكال الشائعة بحيث لا تضطر إلى كتابة جافا سكريبت يدويًا لها؛ وعندما يحتاج حقل إلى منطق لا يعبر عنه أي إعداد مسبق — كتحقق من المجموع، أو قاعدة عبر الحقول، أو تعبير نمطي مخصص — قم بإرفاق البرنامج النصي الخام بنفسك باستخدام طرق إجراءات مستوى الحقل الموضحة في بناء حقول وإجراءات AcroForm باستخدام HotPDF، والتي تأخذ سلسلة جافا سكريبت مباشرة؛ ويكتب كلا المسارين في نفس قاموس /AA، لذا فإن تنسيق العملة المسبق على حقل والتحقق اليدوي على حقل آخر يتعايشان في نفس المستند
لماذا يجب عليك إعادة إنشاء دفق المظهر؟
لأن السلسلة النصية للقيمة والبكسلات التي يرسمها العارض شيئان مختلفان في الـ PDF، وتغيير أحدهما لا يغير الآخر؛ حيث يخزن الحقل قيمته في /V، ولكن ما يعرضه القارئ يأتي من دفق مظهر الحقل، /AP /N؛ وقم بتحرير /V من خلال رسم البياني للكائنات وسيظل عارض دقيق يعرض المظهر القديم حتى يقوم شيء ما بإعادة إنشائه؛ ويحمل قاموس النموذج علامة /NeedAppearances التي تطلب من العارض إعادة بناء كل مظهر عند الفتح، ويحترم Acrobat ذلك — ولكن العارضين المدمجين في المتصفحات وقارئي الأجهزة المحمولة وخوادم الطباعة ومنشئي الصور المصغرة يتجاهلون ذلك بشكل متكرر ويرسمون أي دفق موجود بالفعل، أو لا شيء على الإطلاق؛ وتقوم EnsureLoadedFieldAppearanceStream بسد هذه الفجوة عن طريق بناء /AP /N Form XObject نفسه، من الحقول /DA و /V و /Rect، بحيث تنتقل القيمة المعروضة داخل الملف بدلاً من الاعتماد على العارض لإعادة رسمها
// خبز المظهر لكل حقل قبل الحفظ، بحيث تظهر القيم في كل مكان
Names := Pdf.GetLoadedFormFieldNames;
for i := 0 to High(Names) do
Pdf.EnsureLoadedFieldAppearanceStream(i);
Pdf.SaveLoadedDocument('invoice-flat-appearance.pdf');
أين تتوقف برامج AF النصية عن العمل
يعتمد كل برنامج نصي من نوع AF* على وجود محرك جافا سكريبت في العارض، ومعظم العارضين ليس لديهم ذلك؛ حيث يقوم Acrobat ومجموعة صغيرة من منتجات سطح المكتب بتشغيل هؤلاء المساعدين، بينما لا تقوم عوارض PDF المدمجة في Chrome و Edge و Firefox والمنصات المحمولة الرئيسية، جنبًا إلى جنب مع المنقطات في جانب الخادم وخطوط أنابيب الطباعة، بتشغيل أي جافا سكريبت للنماذج على الإطلاق؛ وفي تلك القارئات، لا ينطلق الحساب أبدًا ولا يتم تطبيق التنسيق — ويعرض الحقل ببساطة أي دفق مظهر تم حفظه به مؤخرًا، وهو السبب الرئيسي وراء أهمية خبز /AP /N؛ وعامل طبقة AF* كميزة لراحة المستخدمين على قارئ يدعم البرامج النصية، وليس كضمان أبدًا؛ وأي مجموع يجب أن يكون صحيحًا على الخادم — كمبلغ فاتورة ستقوم بالفوترة بناءً عليه، أو رقم ضريبي ستقوم بتقديمه — يجب إعادة حسابه من جانبك بعد عودة النموذج، لأنه لا يمكنك افتراض أن القارئ الذي ملأه قد قام بتشغيل سطر واحد من برنامجك النصي
هناك حالة أخرى تستحق الذكر: النماذج التي وصلت بتنسيق XFA؛ حيث يحمل نموذج XFA الديناميكي محرك الحساب الخاص به في حمولة XML لا يعرف عنها مساعدو AF* شيئًا، لذا فإن إرفاق AFSimple_Calculate بحقل XFA لا يحقق شيئًا؛ قم بتسطيح نموذج XFA إلى نموذج AcroForm عادي أولاً، كما هو مغطى في تسطيح XFA إلى AcroForm في Delphi، وسيتم تطبيق الطرق الثلاث هنا على حقول AcroForm الناتجة تمامًا كما تفعل مع أي نموذج محمل آخر
تتلخص النماذج ذاتية الحساب والتنسيق من نوع AcroForms في ثلاثة استدعاءات للمستند المحمل — إرفاق الحساب، وإرفاق التنسيق والتحقق، وخبز المظهر — متبوعة بـ SaveLoadedDocument؛ وواجهة برمجة تطبيقات النموذج المحمل الموضحة هنا هي جزء من مكون HotPDF لـ Delphi و C++Builder، الذي يوثق التعدادات الكاملة THPDFFieldValidationKind و THPDFFieldCalcOp إلى جانب بقية سطح AcroForm