פעולת AcroForm היא מילון המחובר לווידג'ט ומורה לצופן מה לעשות כאשר משהו קורה לאותו ווידג'ט. לחץ על כפתור והצופן קורא את מילון הפעולה שלו: פעולת URI פותחת כתובת אינטרנט, פעולת JavaScript מריצה סקריפט, פעולת SubmitForm מפרסמת את ערכי השדות שנאספו לנקודת קצה, ופעולת ResetForm מאפסת אותם לברירות המחדל. הפעולה היא נתונים, לא התנהגות שאפויה לקובץ. ISO 32000-1 §12.6 מגדיר את צורת המילון; הצופן מספק את המנוע המפרש אותה. הפיצול הזה חשוב מפני שפעולה הכתובה בצורה מושלמת ב-PDF עדיין לא עושה דבר אם לקורא בצד השני אין מנוע עבורה, והרבה מהצרות עם AcroForm נובעות מהפער הזה ולא משדה לא תקין
HotPDF כותב את המילונים האלה ישירות מ-Delphi ו-C++Builder, לצד ווידג'טי השדות שאליהם הם מחוברים. שתי מבניות פועלות בכל טופס אינטראקטיבי: הווידג'ט שהמשתמש רואה על הדף, והשדה עם מנגנון הפעולה מתחתיו שנושא את הנתונים וחיווט. ניתן לערוך אותם בנפרד, וכל אחד יכול להיות שגוי בזמן שהשני נראה תקין. הסעיפים להלן עוברים על מתן שמות לשדות, פעולות הכפתורים עצמן, JavaScript ברמת שדה, ועל הפגמים שעוברים בדיקה ויזואלית מפני שהם חיים כולם במבנית השנייה
שמות שדות הם מפתחות ניתוב, לא כיתובים
כל שדה AcroForm נושא שם מלא. ISO 32000-1 §12.7.3 קובע שאותו שם, ולא הכיתוב הגלוי, הוא המפתח שתחתיו ערך השדה נסע כאשר הטופס מיוצא או נשלח. מפתחים המגיעים מעיצוב VCL נוטים להתייחס לשם פקד כמזהה קוד פרטי — וזה לא המצב כאן. הוא פורמט העברה
הדבר הראשון שנובע מכך הוא ששני שדות בעלי אותו שם מלא אינם שני שדות. PDF מתייחס אליהם כשתי הערות ווידג'ט של שדה אחד, חולקים ערך אחד, כך שהקלדה באחד מעדכנת את השני מיד. זה בדיוק מה שרוצים כאשר שם לקוח חייב לחזור בכל עמוד של חוזה. זה באג כאשר לולאת יצירה משתמשת מחדש ב-'Field1' בשלושה עמודים בטעות. שום בדיקה ויזואלית אינה תופסת את המקרה השני. כל עמוד עדיין מצייר את התיבה שלו, והקישור מתגלה רק כאשר מישהו מתחיל להקליד
שמות עם נקודה כגון applicant.email בונים היררכיה. צומת האב applicant מקבץ את ילדיו, מה שמאפשר לאפס או לשלוח רק חלק מהטופס. מתן שמות לשדות בדרך זו מההתחלה לא עולה כלום, ומשתלם בפעם הראשונה שהמערכת הנקלטת מבקשת רק את בלוק המבקש
לכפתורי רדיו יש כלל משלהם. כפתורים שאמורים לעבור ביניהם חייבים לשתף שם קבוצה. ב-HotPDF, קריאות AddRadioButton המעבירות את אותו שם קבוצה מחברות את ווידג'טיהן לשדה אב אחד, וערך הייצוא של כל כפתור ('basic' או 'full') מזהה את האפשרות הנבחרת. תן לכל כפתור שם שונה ותקבל שורה של מתגים בלתי תלויים במקום קבוצה בלעדית אחת, שנראית זהה ומתנהגת בצורה שגויה
יצירת מערכת השדות עמוד אחרי עמוד
HotPDF ממקם שדות דרך שיטות THPDFPage, כך שכל שדה שייך לאובייקט הדף שיצר אותו. מלכודת הרצף לשים לב אליה היא AddPage. היא מצביעה מחדש את CurrentPage לעמוד החדש ברגע שהיא חוזרת, כך שכל קריאת שדה אחריה נוחתת בעמוד החדש גם כאשר השדה שייך לוגית לעמוד שזה עתה עזבת. סיים כל עמוד, תוכן מצויר ושדות יחד, לפני שתקרא ל-AddPage
procedure BuildClaimForm(Pdf: THotPDF);
begin
// Page 1: applicant block
Pdf.CurrentPage.AddTextField('applicant.name', '', Rect(50, 700, 300, 722));
Pdf.CurrentPage.AddTextField('applicant.email', '', Rect(50, 660, 300, 682));
Pdf.CurrentPage.AddCheckBox('consent', 'Y', Rect(50, 620, 70, 640), False);
Pdf.CurrentPage.AddRadioButton('coverage', 'basic', Rect(50, 580, 70, 600), True);
Pdf.CurrentPage.AddRadioButton('coverage', 'full', Rect(90, 580, 110, 600), False);
Pdf.CurrentPage.AddComboBox('plan', 'Standard',
['Basic', 'Standard', 'Premium'], Rect(50, 540, 200, 565));
Pdf.AddPage; // CurrentPage now points at page 2
Pdf.CurrentPage.AddListBox('riders', 'None',
['None', 'Flood', 'Earthquake'], Rect(50, 500, 200, 600));
end;
הקואורדינטות משתמשות במוסכמת PDF, עם הראשית בפינה השמאלית התחתונה של הדף. זהו אותו ראשית שבו TextOut משתמש לטקסט מצויר, כך ש-Rect(50, 100, 200, 120) יושב קרוב לתחתית דף Letter, לא לראש. VCL מציב את Y בראש ומגדיל אותו כלפי מטה, כך שטבלת פריסה שמועברת ישירות יוצאת הפוכה אנכית, כל שדה הפוך לקצה הלא נכון של הדף. בצע את ההמרה פעם אחת בעוזר משותף במקום בכל אתר קריאה, ותיקון אחד יתקן את כל הטופס
חיווט כפתורים לפעולות URI, JavaScript ושליחה
כפתור לחיצה אינו פעיל עד שמחוברת אליו פעולה. HotPDF חושף את סוגי הפעולות מ-ISO 32000-1 §12.6.4 דרך המניין THPDFButtonAction (baURI, baJavaScript, baSubmitURL, baResetForm, baHide, baShow, baNamed), ומספק שתי שיטות שיוצרות את הכפתור וקושרות את פעולתו בקריאה אחת
// Open a help page in the system browser
Pdf.CurrentPage.AddPushButtonWithAction('btnHelp', 'Help',
'https://www.example.com/claims-help', Rect(320, 700, 420, 730), baURI);
// Run viewer-side JavaScript
Pdf.CurrentPage.AddPushButtonWithAction('btnRecalc', 'Recalculate',
'app.alert("Totals updated.");', Rect(320, 660, 420, 690), baJavaScript);
// Submit as XFDF and keep empty fields in the payload
Pdf.CurrentPage.AddPushButtonWithSubmitAction('btnSubmit', 'Submit claim',
'https://api.example.com/claims', Rect(320, 620, 420, 650),
[sffXFDF, sffIncludeNoValueFields]);
דגלי השליחה ראויים ליותר מחשבה ממה שהם בדרך כלל מקבלים. AddPushButtonWithSubmitAction מקבל קבוצה מסוג THPDFSubmitFormFlags, וקבוצה ריקה מייצרת POST מקודד URL רגיל, שהוא הפורמט שנקודות קצה לדוגמה רבות מקבלות ונקודות ייצור רבות דוחות. הוספת sffXFDF מחליפה את המטען ל-XFDF. sffGetMethod משנה את פועל ה-HTTP. sffIncludeNoValueFields שומר שדות ריקים במטען במקום להפיל אותם בשקט, מה שחשוב ברגע שהצרכן מבחין בין "נעדר" ל"ריק". קבוצת הדגלים היא חלק מחוזה הממשק שלך עם נקודת הקצה הנקלטת, אז הסכם אותה עם הצוות שמנתח את ההגשה, לא אחרי אצווה הדחייה הראשונה
JavaScript ברמת שדה: הקשה, פורמט, אימות
לחיצות כפתור אינן המקום היחיד שבו חיות פעולות. HotPDF גם מחבר JavaScript לאירועי השדה לכל אחד שצופנים בעלי יכולת סקריפט מפעילים בזמן שמשתמש מזין נתונים. יש שלושה טריגרים, והם מופעלים בנקודות שונות במחזור חיי הקלט. פעולת הקשה רצה עם הגעת כל תו, ושוב בעת אישור. פעולת פורמט מחדשת את הערך המוצג לאחר שינוי שאושר, אך לצורך הצגה בלבד. פעולת אימות מקבלת את המילה האחרונה, מקבלת או דוחה את הערך שאושר לפני שהופך לערך השדה
// Reject committed values that are not plausible email addresses
Pdf.AttachFieldKeyStrokeAction('applicant.email',
'if (event.willCommit && !/^[\w.-]+@[\w.-]+\.\w+$/.test(event.value)) event.rc = false;');
// Display US phone numbers as (NNN) NNN-NNNN
Pdf.AttachFieldFormatAction('applicant.phone',
'event.value = event.value.replace(/(\d{3})(\d{3})(\d{4})/, "($1) $2-$3");');
// Refuse applicants under 18 at commit time
Pdf.AttachFieldValidateAction('applicant.age',
'if (parseInt(event.value) < 18) event.rc = false;');
הגדרת event.rc = false בתוך סקריפט הקשה או אימות מורה לצופן לדחות את הקלט. הבעיה היא שאף אחד מזה לא רץ אלא אם הצופן כולל מנוע JavaScript. Acrobat ומספר מוצרי שולחן עבודה כוללים אחד. רוב הקוראים הניידים, המעבדים המוטמעים במוצרים אחרים, ושרשרות הדפסה אינם כוללים אחד, והם מסירים את הסקריפטים ללא תלונה. אז סקריפטי שדות משפרים את איכות הנתונים עבור תת-קבוצת המשתמשים שהקורא שלהם מריץ אותם, וזה כל מה שהם עושים. הם אינם גבול אבטחה. כל ערך שנשלח עדיין צריך לעבור אימות בשרת עם הגעתו, מפני שלא ניתן להניח שהלקוח בדק דבר
פגמים שעוברים בדיקה ויזואלית
פגמי AcroForm הקשים ביותר לתפוס הם אלה שחיים במבנה הנתונים ולא בעיבוד, מפני שפתיחת הקובץ והסתכלות עליו לא אומרת לך דבר. ארבעה מופיעים מספיק פעמים כדי להיות ראויים לאזכור, ולכל אחד יש בדיקה מכנית שמוצאת אותו לפני הגרסה
- סטיית ערך ייצוא. תיבת סימון שנוצרה כ-
AddCheckBox('consent', 'Yes', ...)מפרסמתYes. צרכן שמתאים עלYדוחה כל הגשה בזמן שהדף נראה מושלם. מלא את הטופס, יצא אותו כ-XFDF מ-Acrobat, וחשב הפרשים בין הערכים לסכמה שהצרכן באמת מצפה לה - שיקוף ערך מקרי. שני שדות החולקים שם מלא מתמזגים לאחד. הסימפטום מופיע בזמן הזנת נתונים ולעולם לא בזמן יצירה, כך שהבדיקה היא להקליד בטופס, לא לעבד אותו ולבדוק את התוצאה
- ערכי combo מחוץ לרשימת האפשרויות. כאשר הערך הנוכחי שמועבר ל-
AddComboBoxאינו אחת מהאפשרויות הרשומות, צופנים חלוקים בדעתם אם להציג אותו, לרוקן אותו, או לסמן אותו. שמור את ברירת המחדל ברשימה והחוסר הסכמה ייעלם - שדות שנותרים עריכים לאחר סגירת זרימת העבודה. ל-HotPDF אין קריאת הגדרת מראה לשדות AcroForm. הדרך הנתמכת להקפיא טופס שהושלם היא ליצור את השדות עם הדגל
ffReadOnly, שמשאיר את הערך גלוי דרך זרם המראה של השדה עצמו תוך סירוב לעריכות. השדה נשאר אובייקט טופס חי, שזה בדיוק מה שכלי הרכבה וחתימה שלאחר מכן מצפים למצוא
התנהגות אחת מצד הצופן ראויה לציון רגרסיה גם אם שום שינוי קוד אינו מטפל בה. פריסות Acrobat ארגוניות יכולות להשבית JavaScript או להגביל יעדי שליחה לפי מדיניות, כך שפעולה שעבדה בכל גרסת פיתוח יכולה להיות מתה על שולחן עבודה נעול של לקוח. תכנן חלופה גלויה למקרה שבו הכפתור לא עושה דבר, גם אם החלופה היא רק הוראה מודפסת המורה למשתמש מה לעשות במקום
היכן עבודת הטופס מתחברת לשאר המסמך
שדה חתימה הוא בעצמו סוג שדה AcroForm. טופס שייסוב או ייחתם לאחר מכן עדיף לשריין את השדה הזה במהלך היצירה מאשר לתלות אותו בו אחר כך, והסיבות ברמת הבייט נמצאות במאמר הנלווה על חתימות דיגיטליות וחתימת PAdES עם HotPDF. קלטים שמגיעים כחבילות XFA ולא כ-AcroForm מקורי הם מצב שונה: השטחת XFA לשדות AcroForm היא זרימת עבודה משלה עם מודל אובדן משלה, מפני שטכנולוגיות הטפסים השתיים לא יכולות להתקיים בקובץ אחד
שיטות השדה, הפעולה והטריגר שמוצגות כאן הן חלק מממשק ה-API הסטנדרטי של HotPDF Component עבור Delphi ו-C++Builder; דף המוצר מקשר את ההפניה המלאה, כולל עומסי יתר של דגל-שדה ומניין דגל-שליחה המלא