اضغط Tab في نموذج PDF قام بتطويره الكود الخاص بك، ويهبط المؤشر حقلين بعيدًا عما ينبغي، أو يتخطى العمود الثاني تمامًا، أو يقفز مرة أخرى إلى الأعلى بعد الحقل الثالث بدلاً من الرابع. يتوقع الشخص الذي يملأ فاتورة في عارضك أن تمشي لوحة المفاتيح في النموذج بالطريقة التي تمشي بها في كل نموذج ويب استخدمه على الإطلاق. عندما لا تفعل ذلك، يمدون يدهم إلى الماوس، ويبحثون عن المربع التالي، ويقررون بهدوء أن أداتك غير مكتملة. اجتياز الحقل الذي يمكن التنبؤ به هو الفرق بين عارض إدخال بيانات يتحمله الناس وآخر يثقون به، وهي في الغالب مسألة استخدام API التركيز الصحيح بدلاً من تزوير إدخال لوحة المفاتيح بنقرات محاكاة
تستخدم الأمثلة أدناه مكون PDFium، وهو مكون VCL/LCL المستند إلى PDFium لـ Delphi و C++Builder و Lazarus. التنقل هو أحد الأشياء الثلاثة التي يجب على عارض النموذج الحصول عليها بشكل صحيح؛ أما الشقين الآخرين، فهما فتح النموذج بشكل صحيح وحفظ القيم المعبأة بحيث تظهر فعليًا، حيث تختبئ معظم المفاجآت، لذا تم تغطية الثلاثة جميعًا أدناه
فتح نموذج: FormFill و FormType ومسألة XFA
يتطلب الوصول إلى الحقل أن يتم تمكين النظام الفرعي لملء النموذج، والذي يتم التحكم فيه بواسطة خاصية FormFill، قبل فتح المستند. بمجرد التنشيط، يخبرك FormType بنوع النموذج الذي تواجهه، والإجابة تغير مجموعة الميزات التي يمكنك الوعد بها:
Pdf.FileName := FormPath;
Pdf.FormFill := True; // enable before Active; required for any field access
Pdf.Active := True;
case Pdf.FormType of
ftNone:
DisableFormPanel('This document has no interactive form');
ftAcroForm:
BuildFieldList; // full field navigation and editing available
ftXfaFull:
ShowXfaNotice; // XFA renders from its own XML template;
// treat field editing as limited
end;
تتبع ملاحظتان عمليتان ذلك المحول. AcroForm هو نموذج نموذج ISO 32000 القياسي، وهو ما تستهدفه كل API هنا. تدمج مستندات XFA بنية نموذج XML الخاصة بها، لذا فإن الوعد بتحرير XFA الكامل للعميل بعد عرض توضيحي سريع لـ AcroForm هو التزام ستندم عليه. الملاحظة الثانية تتعلق بالآثار الجانبية: إن تعيين FormFill إلى True يؤدي أيضًا إلى تهيئة جافا سكريبت للمستند. في عارض إدخال البيانات، هذا صحيح تمامًا، لأن نصوص الحسابات هي التي تحافظ على تحديث الإجمالي الجاري أثناء كتابة شخص ما. في نافذة معاينة لملفات مجهولة المصدر، يكون هذا خاطئًا تمامًا. تغطي مقالة المعاينة الآمنة لـ PDF جانب FormFill := False من تلك المقايضة
اجتياز مفتاح Tab الذي يهبط حيث يتوقع المستخدمون
بالعودة إلى مشكلة لوحة المفاتيح من الأعلى. الإغراء هو تزوير مفتاح Tab عن طريق تركيب نقرة ماوس على مستطيل عنصر واجهة المستخدم التالي، والذي ينكسر في اللحظة التي يتم فيها تمرير حقل خارج الشاشة أو تداخل عنصرين من عناصر واجهة المستخدم. يقوم API التركيز بتحريك تركيز النموذج الخاص به بشكل مباشر بدلاً من ذلك، دون تخمين هندسي. تغطيه خمس مكالمات: FocusFormField عن طريق الفهرس، FocusNextFormField و FocusPreviousFormField للخطو، FocusedFormFieldIndex لقراءة مكانك، و ClearFormFieldFocus لإسقاط التركيز تمامًا
procedure TFormViewer.HandleTabKey(Shift: TShiftState);
begin
if ssShift in Shift then
PdfView.FocusPreviousFormField
else
PdfView.FocusNextFormField;
UpdateFieldStatus; // e.g. "Field 4 of 17: InvoiceDate"
end;
قطعة السلوك الوحيدة التي تعرقل الناس هي الالتفاف (wrap). يعمل الاجتياز من خلال ترتيب علامات تبويب الصفحة الحالية والحلقات بداخلها: تخط الحقل الأخير وستعود إلى الحقل الأول. ترجع كلتا وظيفتي الخطو الفهرس الميداني الجديد، أو -1 عندما لا تحتوي الصفحة على أي حقول على الإطلاق. هذا التكرار يتم لكل صفحة، وليس لكل مستند، مما يعني أن العبور إلى الصفحة التالية هو وظيفتك، وليس وظيفة المكتبة. قارن الفهرس الذي تم إرجاعه بالفهرس الذي بدأت منه، ولاحظ متى تم التفافه، وقدم PageNumber بنفسك إذا كان النموذج يهدف إلى القراءة كسلسلة واحدة مستمرة. تخط هذا التحقق ونموذج من صفحتين يحاصر المؤشر بصمت في الصفحة الأولى، وهي نكهته الخاصة لشكوى كسر مفتاح Tab
يصبح الاجتياز مفيدًا بمجرد تفاعل بقية واجهة المستخدم معه. يُطلق الحدث OnFormFieldEnter عند وصول التركيز، وفي العارض يُبلغ OnFormFieldFocusChange عن فهرس الحقل الجديد، حتى تتمكن اللوحة الجانبية من البقاء في خطوة مع ما حددته لوحة المفاتيح للتو. عندما تحتاج إلى تعيين عكسي، من موضع شاشة إلى حقل، تقوم الخاصية المفهرسة FormFieldAt باختبار الضرب لمعاينات التلميح النصي (tooltip) ولوحات النقر للتحرير. هناك مكافأة إمكانية وصول هادئة في كل هذا: لأن التركيز يتبع ترتيب الحقل الخاص بالمستند، فإن المسار الذي تقوم بربطه لمفتاح Tab هو نفس المسار الذي يعلنه قارئ الشاشة، بدون أي عمل إضافي
عرض أسماء الحقول بدلاً من أرقام الفهرس الخام يتطلب خاصية واحدة إضافية. تُرجع FormFieldInfo[] سجل TPdfFormFieldInfo لكل فهرس، يحمل اسم الحقل والنوع وحجم الخط والحالة المحددة وقيمة التصدير وعضوية المجموعة، وهو ما يجب أن تعرضه قائمة التنقل ("الحقل 4 من 17: InvoiceDate" بدلاً من "4"). مجموعات أزرار الاختيار (Radio groups) هي الحالة التي تستحق ملف اختبار مخصصًا. يمكن أن تشترك العديد من عناصر واجهة المستخدم في اسم حقل واحد، لذلك تُظهر قائمة مجمعة بسذاجة من عناصر واجهة المستخدم نفس المجموعة عدة مرات وتربك كل من يقرأها
لماذا تظهر القيم المعبأة فارغة، والاستدعاء الذي يصلح ذلك
الشكوى الأخرى التي تملأ طوابير الدعم أكثر إثارة للقلق من مفتاح Tab الذي يسيء التصرف: يتم ملء نموذج برمجيًا، ويفتحه العميل في Acrobat، ويبدو كل حقل فارغًا. انقر داخل حقل وتنبثق قيمته في العرض. البيانات في الملف طوال الوقت. ما هو مفقود هو صورة البيانات، والسبب يستحق الفهم مرة واحدة لأنه يشرح عائلة كاملة من الأخطاء
يُخزن حقل النص لـ AcroForm قيمته في إدخال /V لقاموس الحقل (ISO 32000-1 §12.7.3.3). ما يرسمه العارض بالفعل هو شيء منفصل: تيار مظهر عنصر واجهة المستخدم أسفل /AP (§12.5.5)، وهي قصاصة صغيرة مسبقة التصيير من المحتوى. اكتب /V واترك /AP بمفردها، وينحرف الاثنان عن بعضهما البعض. القيمة موجودة؛ والنسخة المصيرة منها قديمة أو غائبة. يصادف أن يقوم Acrobat بإعادة بناء مظهر الحقل عندما يكتسب التركيز، وهو التفسير الكامل للقيم التي تظهر فقط عند النقر. علامة NeedAppearances القديمة، والتي طلبت من المشاهدين إعادة إنشاء المظاهر لك، لم تعمل أبدًا بشكل موحد وتم إهمالها في PDF 2.0، وتتجاهلها خوادم الطباعة ومولدات الصور المصغرة تمامًا. إنها تطلي /AP ولا شيء آخر، لذلك إذا كان /AP فارغًا فسيقومون بطباعة مربع فارغ
إن تعيين قيمة من خلال FormField[i] يكتب /V فقط. هذا هو السبب في أن ملء النموذج هو سلسلة من ثلاث خطوات، والخطوة التي يسقطها الفريق هي الخطوة الوسطى:
procedure TFormViewer.FillAndSave(const Values: array of WString;
const OutputPath: string);
var
i: Integer;
begin
for i := 0 to Pdf.FormFieldCount - 1 do
Pdf.FormField[i] := Values[i]; // writes /V only
// Rebuild the /AP appearance streams; without this the form
// looks blank in Acrobat until each field is clicked
Pdf.GenerateFormAppearances;
Pdf.SaveAs(OutputPath);
end;
تعتبر GenerateFormAppearances الإصلاح الكامل. إنها تعيد بناء تيار المظهر لكل عنصر واجهة مستخدم من القيم والخطوط والتربيع (quadding) الحالية، لذلك يقوم العارض الذي لا يدير أبدًا حدث تركيز أو خادم طباعة أو مولد صور مصغرة بطلي الحالة المعبأة على أي حال. استدعه مرة واحدة بعد مجموعة التعيينات، وليس مرة واحدة لكل حقل. يقوم إنشاء المظهر بعمل تخطيط حقيقي، وتضاعف استدعاءات كل حقل ذلك عبر نموذج كبير من أجل لا شيء
تُعد إعادة إنشاء المظاهر أيضًا اللحظة التي تؤكد فيها الخطوط والمحاذاة على نفسها، وهي مصدر مفاجأة من الدرجة الثانية. يضع التيار الجديد كل قيمة داخل مستطيل عنصر واجهة المستخدم باستخدام خط الحقل وحجمه وتربيعه. القيمة التي توضع بشكل مريح في نموذج الاختبار الخاص بك يمكن أن تُقص أو تنكمش في نسخة العميل حيث يكون نفس الحقل أضيق. الحقول ذات الحجم التلقائي (حجم الخط صفر) تقلص النص ليناسب؛ الحقول ذات الحجم الثابت تقطعه فقط. كلاهما قانوني، والطريقة الصادقة الوحيدة لمعرفة أيهما يفعله نموذج معين هي إلقاء نظرة على الإخراج المُعاد إنشاؤه بدلاً من السلسلة التي كتبتها. عندما يُبلغ شخص ما عن اقتطاع النص عند حافة مربع، فهذا هو السبب دائمًا تقريبًا
عامل التحقق كجزء من إنهاء العمل، وليس كفكرة متأخرة. افتح الملف المحفوظ في Acrobat وتأكد من أن القيم مرئية قبل لمس أي حقل. ثم قم بطباعته إلى PDF أو إلى صورة من عارض مختلف، وهو العارض الذي يتجاهل منطق النموذج تمامًا، وتأكد من نجاة القيم في ذلك المسار أيضًا. من بينهما، يكتشف هذان الفحصان كل متغير من انجراف /V-versus-/AP
تكوينات الحقل التي تجتاز العرض التوضيحي وتفشل في الميدان
تخفي نماذج العرض التوضيحي النظيفة مجموعة من الحالات المتطرفة التي لا تفعلها ملفات العملاء. تمثل أربع منها معظم تقارير "لقد عملت على جهازي"
- قيم تصدير خانة الاختيار. حالة "التشغيل" ليست دائمًا
Yes. لك الحرية في نموذج في تحديد قيمة التصدير الخاصة به، وكتابة السلسلة الخاطئة يترك المربع غير محدد بصريًا بينما تكون شفرتك مقتنعة بأنها أعدتها. اقرأ قيمة التصدير منFormFieldInfo[]بدلاً من افتراض واحدة - مجموعات أزرار اختيار مشتركة الاسم. حقل واحد، عدة عناصر واجهة مستخدم. تحدد القيمة التي تعينها أي عنصر واجهة مستخدم يتم قراءته كمحدد، لذلك ينتهي رمز واجهة المستخدم الذي يفترض أن اسمًا واحدًا يعين إلى مستطيل واحد برسم حلقة التركيز على الزر الخطأ
- الحقول المحسوبة. يتم تحديث الإجماليات التي تحتفظ بها جافا سكريبت الخاصة بالمستند استجابةً لأحداث الحقل. ملء مبرمج يتجاوز تلك الأحداث إما أن يؤدي إلى إعادة الحساب أو الكتابة فوق الحقول المحسوبة مباشرة. النموذج الذي تختلف فيه بنود السطر والإجمالي أسوأ من كلا الإصلاحين
- الحقول المطلوبة المخفية. تُخفي النماذج الشرطية الحقول التي لا تزال تحمل علامة مطلوبة. حدد مقدمًا ما إذا كان التحقق من صحتك يحترم الرؤية أو العلامة الخام المطلوبة، ثم اكتب هذا القرار في مكان يمكن للدعم العثور عليه
تمييز واحد يستحق التسوية قبل أن يعضك: توليد المظاهر لا يتسطح (flattening). يجعل GenerateFormAppearances القيم مرئية في كل مكان مع ترك الحقول قابلة للتحرير. يخبز التسطيح المظهر في محتوى صفحة ثابت ويزيل التفاعل إلى الأبد، وهو أمر صحيح بالنسبة للنسخة الأرشيفية وخطأ لنموذج لا يزال يتعين على الشخص التالي تعبئته. إذا أبلغ FormType عن ftXfaFull بدلاً من ftAcroForm، فلن يتم تطبيق أي من سطح التحرير هنا بشكل نظيف على أي حال، لأن المستند يُصيَّر من قالب XML الخاص به؛ اكتشف تلك الحالة وأخبر المستخدم، بدلاً من تركهم يجدون الحد بمفردهم
يعد النظام الفرعي لملء النموذج، واجتياز التركيز، وإنشاء المظهر الموضح هنا جزءًا من مكون PDFium لـ Delphi و C++Builder و Lazarus/FPC. إذا كان متصفحك يعالج أيضًا ترميز المراجع جنبًا إلى جنب مع بيانات النموذج، فإن مقالة مراجعة التعليق التوضيحي تغطي هذا النموذج المجاور