קובץ PDF של חשבונית או הוצאות שבו פריטי השורות מסתכמים מעצמם והתאריך מעצב את עצמו זקוק לשלושה דברים המחוברים לטופס AcroForm שלו, ו-HotPDF מוסיפה את שלושתם לקובץ טעון בדלפי: ApplyLoadedFieldCalculation עבור סכומי AFSimple_Calculate, ApplyLoadedFieldValidation עבור עיצוב ה-AF של Acrobat ועוזרי האימות, ו-EnsureLoadedFieldAppearanceStream כדי לאפות את הערך שמציג באמת מצייר
HotPDF הוא רכיב PDF מקורי של VCL עבור דלפי ו-C++Builder, ושלוש שיטות אלו פועלות על מסמך שפתחת באמצעות LoadFromFile ולא על מסמך שאתה בונה מאפס. כל אחת מקבלת אינדקס שדה מבוסס 0 לתוך מערך /Fields של ה-AcroForm — אותו סדר ש-GetLoadedFormFieldNames מחזירה — וכותבת ישירות לתוך מילון השדות, כך שכל העבודה היא טעינה, אינדקס, חיבור, ו-SaveLoadedDocument. בניית סט השדות עצמו היא משימה נפרדת, המכוסה ב-הוספת שדות AcroForm ל-PDF טעון בדלפי, בעוד שדף זה עוסק במתן פעולות אריתמטיקה ועיצוב לסט שדות קיים
מדוע טופס PDF זקוק לתסריטים (scripts) כדי לסכם את השדות של עצמו?
מכיוון של-PDF אין שכבת גיליון אלקטרוני. שדה טקסט מחזיק מחרוזת, לא נוסחה, ושום דבר בקובץ אינו מחשב מחדש סכום כולל כאשר פריט שורה משתנה, אלא אם כן מחובר תסריט (script) שיעשה זאת. המנגנון הוא מילון הפעולות הנוספות של השדה, /AA (ISO 32000-1 §12.7.5), שממפה אירועים מעוררים לפעולות: מעורר לחיצת מקש (K) עם הגעת תווים, מעורר עיצוב לתצוגה, מעורר אימות (V) בעת אישור, ומעורר חישוב (C) המופעל בכל פעם ששדה כלשהו בטופס משתנה. כל פעולה היא פעולת JavaScript, /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", ...]) לתוך פעולת החישוב של שדה המטרה, והאנומרציה (enumeration) THPDFFieldCalcOp בוחרת את הפונקציה: fccSum, fccAverage, fccProduct, fccMinimum ו-fccMaximum ממופות ל-SUM, AVG, PRD, MIN ו-MAX של Acrobat. שמות המקור הם שמות שדות 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 על הסכום הכולל אינה קוסמטית. החישוב מורץ בתוך המציג, כך שעד שקורא בעל יכולת הרצת תסריטים (scripts) יפתח את הקובץ, לשדה הסכום הכולל אין תוכן מרונדר משלו. אפיית זרם מראה (appearance stream) מעניקה לו נתון להצגה בינתיים, וזהו מה שעבודת הדפסה או מחולל תמונות ממוזערות יקבלו כאשר הם אינם מריצים את התסריט
עיצוב ואימות שדות באמצעות ערכת העוזרים 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 com עם הקוד ש-Acrobat שומרת לכל אחד, ו-fvkArbitraryMask מקבלת מחרוזת מסכה דרך ארגומנט Mask עבור כל מה שהגדרות קבועות אלו אינן מכסות. עבור הסוגים הנומריים HotPDF מחברת גם תסריט AFRange_Validate כך שקלט מחוץ לטווח מסורב בעת האישור
// Format the invoice date as ISO, the amount as currency, and mask a tax id
Pdf.ApplyLoadedFieldValidation(DateIdx, fvkDate, 'yyyy-mm-dd', '');
Pdf.ApplyLoadedFieldValidation(AmountIdx, fvkCurrency, '', '');
Pdf.ApplyLoadedFieldValidation(TaxIdIdx, fvkArbitraryMask, '', '999-99-9999');
Pdf.EnsureLoadedFieldAppearanceStream(AmountIdx);
עוזרי ה-AF מכסים את הצורות הנפוצות כך שלעולם אינך כותב עבורם JavaScript ידנית. כאשר שדה זקוק ללוגיקה שאף הגדרה קבועה מראש אינה מבטאת — סיכום ביקורת (checksum), כלל הצלבת שדות, custom regex — חבר את התסריט הגולמי בעצמך באמצעות שיטות הפעולה ברמת השדה המתוארות ב-בניית שדות ופעולות AcroForm עם HotPDF, המקבלות מחרוזת JavaScript ישירות. שני הנתיבים כותבים לאותו מילון /AA, כך שעיצוב מטבע קבוע מראש בשדה אחד ואימות שנכתב ידנית בשדה אחר מתקיימים יחד באותו מסמך
מדוע עליך ליצור מחדש את זרם המראה (appearance stream)?
מכיוון שמחרוזת הערך והפיקסלים שמציג מצייר הם שני דברים שונים ב-PDF, ושינוי אחד אינו משנה את השני. שדה מאחסן את הערך שלו ב-/V, אך מה שקורא מציג מגיע מזרם המראה של השדה, /AP /N. ערוך את /V דרך גרף האובייקטים ומציג קפדן עדיין יציג את המראה הישן עד שמשהו ייצור אותו מחדש. מילון הטופס נושא דגל /NeedAppearances המבקש מהמציג לבנות מחדש כל מראה בעת הפתיחה, ו-Acrobat מכבדת אותו — אך מרנדרים המוטמעים בדפדפן, קוראים ניידים, שרתי הדפסה ומחוללי תמונות ממוזערות נוטים להתעלם ממנו ולצייר כל זרם שכבר קיים, או לא לצייר דבר כלל. EnsureLoadedFieldAppearanceStream סוגרת פער זה על ידי בניית ה-Form XObject של /AP /N בעצמה, מתוך ה-/DA, /V ו-/Rect של השדה, כך שהערך המרונדר נוסע בתוך הקובץ במקום להיות תלוי במציג שיצייר אותו מחדש
// Bake an appearance for every field before saving, so values render everywhere
Names := Pdf.GetLoadedFormFieldNames;
for i := 0 to High(Names) do
Pdf.EnsureLoadedFieldAppearanceStream(i);
Pdf.SaveLoadedDocument('invoice-flat-appearance.pdf');
היכן שתסריטי (scripts) ה-AF מפסיקים לעבוד
כל תסריט (script) *AF תלוי בכך שלמציג יש מנוע JavaScript, ולרוב המציגים אין כזה. Acrobat ומספר מצומצם של מוצרים לשולחן העבודה מריצים את העוזרים האלה; מציגי ה-PDF המובנים בתוך Chrome, Edge, Firefox והפלטפורמות הניידות המובילות, יחד עם מרנדרים בצד השרת וצינורות הדפסה, אינם מריצים JavaScript של טפסים כלל. בקוראים אלה החישוב לעולם אינו מופעל והעיצוב לעולם אינו מוחל — השדה פשוט מציג כל זרם מראה שעמו הוא נשמר לאחרונה, וזו בדיוק הסיבה שאפיית /AP /N חשובה. התייחס לשכבת ה-*AF כנוחות עבור תת-קבוצה של משתמשים בקורא בעל יכולת הרצת תסריטים, לעולם לא כערובה. כל סכום כולל שחייב להיות נכון בשרת — סכום חשבונית שתחייב לפיו, נתון מס שתגיש — חייב להיות מחושב מחדש בצד שלך לאחר שהטופס חוזר, מכיוון שאינך יכול להניח שהקורא שמילא אותו הריץ שורה אחת של התסריט שלך
מקרה אחד נוסף ראוי לציון: טפסים שהגיעו כ-XFA. טופס XFA דינמי נושא מנוע חישוב משלו בתוך מטען XML שעוזרי ה-*AF אינם יודעים עליו דבר, ולכן חיחיבור AFSimple_Calculate לשדה XFA אינו משיג דבר. השטח (flatten) את טופס ה-XFA לטופס AcroForm פשוט תחילה, כפי שמכוסה ב-השטחת XFA ל-AcroForm בדלפי, ושלוש השיטות כאן יחולו אז על שדות ה-AcroForm הנוצרים בדיוק כפי שהן חלות על כל טופס טעון אחר
טפסים בחישוב עצמי ובעלי עיצוב עצמי של AcroForm מתמצים בשלוש קריאות במסמך טעון — חיבור החישוב, חיבור העיצוב והאימות, ואפיית המראה — ולאחריהן SaveLoadedDocument. ממשק ה-API של טפסים טעונים המוצג כאן הוא חלק מ-HotPDF Component עבור דלפי ו-C++Builder, המתעד את האנומרציות המלאות THPDFFieldValidationKind ו-THPDFFieldCalcOp לצד שאר ממשק ה-AcroForm