Ένα PDF τιμολογίου ή εξόδων όπου τα στοιχεία γραμμής αθροίζονται μόνα τους και η ημερομηνία μορφοποιείται αυτόματα χρειάζεται τρία πράγματα συνδεδεμένα στην AcroForm του, και το HotPDF προσθέτει και τα τρία σε ένα φορτωμένο αρχείο στο Delphi: το ApplyLoadedFieldCalculation για σύνολα AFSimple_Calculate, το ApplyLoadedFieldValidation για τους βοηθούς μορφής και επικύρωσης Acrobat AF, και το EnsureLoadedFieldAppearanceStream για να ψήσει (bake) την τιμή που πράγματι σχεδιάζει ένα πρόγραμμα προβολής
Το HotPDF είναι ένα εγγενές στοιχείο (component) VCL PDF για Delphi και C++Builder, και αυτές οι τρεις μέθοδοι λειτουργούν σε ένα έγγραφο που ανοίξατε με το LoadFromFile και όχι σε ένα που δημιουργείτε από το μηδέν. Καθεμία λαμβάνει έναν δείκτη πεδίου με βάση το μηδέν στον πίνακα /Fields της AcroForm — την ίδια σειρά που επιστρέφει το GetLoadedFormFieldNames — και γράφει απευθείας στο λεξικό του πεδίου, οπότε όλη η εργασία είναι φόρτωση, ευρετηρίαση, προσάρτηση, SaveLoadedDocument. Η δημιουργία του ίδιου του συνόλου πεδίων είναι μια ξεχωριστή εργασία, η οποία καλύπτεται στο προσθήκη πεδίων AcroForm σε φορτωμένο PDF στο Delphi, ενώ αυτή η σελίδα αφορά την παροχή αριθμητικής και μορφοποίησης σε ένα υπάρχον σύνολο πεδίων
Γιατί μια φόρμα PDF χρειάζεται σενάρια (scripts) για να αθροίζει τα δικά της πεδία;
Επειδή το PDF δεν διαθέτει επίπεδο υπολογιστικού φύλλου. Ένα πεδίο κειμένου περιέχει μια συμβολοσειρά, όχι έναν τύπο, και τίποτα στο αρχείο δεν επανυπολογίζει ένα σύνολο όταν αλλάζει ένα στοιχείο γραμμής, εκτός εάν έχει επισυναφθεί ένα σενάριο για να το κάνει. Ο μηχανισμός είναι το λεξικό πρόσθετων ενεργειών πεδίου, /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", ...]) στην ενέργεια υπολογισμού του πεδίου προορισμού, και η απαρίθμηση 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 στο σύνολο δεν είναι κοσμητική. Ο υπολογισμός εκτελείται μέσα στο πρόγραμμα προβολής, επομένως μέχρι ένα πρόγραμμα ανάγνωσης με δυνατότητα σεναρίων να ανοίξει το αρχείο, το πεδίο συνόλου δεν έχει δικό του αποδοθέν περιεχόμενο. Το ψήσιμο (baking) μιας ροής εμφάνισης του δίνει έναν αριθμό για να εμφανίσει στο μεταξύ, τον οποίο θα λάβει μια εργασία εκτύπωσης ή μια γεννήτρια μικρογραφιών όταν δεν εκτελεί ποτέ το σενάριο
Μορφοποίηση και επικύρωση πεδίων με τη σουίτα βοηθών 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, ώστε η είσοδος εκτός εύρους να απορρίπτεται κατά την υποβολή
// 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 γι' αυτές. Όταν ένα πεδίο χρειάζεται λογική που δεν εκφράζεται από καμία προεπιλογή — ένα άθροισμα ελέγχου, έναν κανόνα μεταξύ πεδίων, ένα προσαρμοσμένο regex — συνδέστε το ακατέργαστο σενάριο μόνοι σας με τις μεθόδους ενεργειών σε επίπεδο πεδίου που περιγράφονται στο δημιουργία πεδίων και ενεργειών AcroForm με το HotPDF, οι οποίες λαμβάνουν απευθείας μια συμβολοσειρά JavaScript. Και οι δύο διαδρομές γράφουν στο ίδιο λεξικό /AA, οπότε μια προκαθορισμένη μορφή νομίσματος σε ένα πεδίο και μια χειροποίητη επικύρωση σε ένα άλλο συνυπάρχουν στο ίδιο έγγραφο
Γιατί πρέπει να αναγεννήσετε τη ροή εμφάνισης (appearance stream);
Επειδή η συμβολοσειρά τιμής και τα pixel που σχεδιάζει ένα πρόγραμμα προβολής είναι δύο διαφορετικά πράγματα στο 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');
Πού σταματούν να λειτουργούν τα σενάρια AF
Κάθε σενάριο AF* εξαρτάται από το αν το πρόγραμμα προβολής διαθέτει μηχανή JavaScript, και τα περισσότερα προγράμματα προβολής δεν διαθέτουν. Το Acrobat και μερικά προϊόντα επιφάνειας εργασίας εκτελούν αυτούς τους βοηθούς. Τα προγράμματα προβολής PDF που είναι ενσωματωμένα στα Chrome, Edge, Firefox και τις μεγάλες πλατφόρμες κινητών, μαζί με τους rasterizers στην πλευρά του διακομιστή και τους αγωγούς εκτύπωσης, δεν εκτελούν καθόλου JavaScript φορμών. Σε αυτά τα προγράμματα ανάγνωσης ο υπολογισμός δεν εκτελείται ποτέ και η μορφή δεν εφαρμόζεται ποτέ — το πεδίο εμφανίζει απλώς οποιαδήποτε ροή εμφάνισης αποθηκεύτηκε τελευταία φορά, γι' αυτό ακριβώς έχει σημασία το ψήσιμο (baking) του /AP /N. Αντιμετωπίστε το επίπεδο AF* ως διευκόλυνση για το υποσύνολο των χρηστών σε ένα πρόγραμμα ανάγνωσης με δυνατότητα σεναρίων, ποτέ ως εγγύηση. Οποιοδήποτε σύνολο πρέπει να είναι σωστό στον διακομιστή — ένα ποσό τιμολογίου με το οποίο θα χρεώσετε, ένα φορολογικό ποσό που θα υποβάλετε — πρέπει να επανυπολογιστεί από την πλευρά σας μετά την επιστροφή της φόρμας, επειδή δεν μπορείτε να υποθέσετε ότι το πρόγραμμα ανάγνωσης που τη συμπλήρωσε εκτέλεσε έστω και μία γραμμή του σεναρίου σας
Μία ακόμη περίπτωση αξίζει να αναφερθεί: φόρμες που έφτασαν ως XFA. Μια δυναμική φόρμα XFA φέρει τη δική της μηχανή υπολογισμού σε ένα XML ωφέλιμο φορτίο (payload) για το οποίο οι βοηθοί AF* δεν γνωρίζουν τίποτα, οπότε η προσάρτηση του AFSimple_Calculate σε ένα πεδίο XFA δεν επιτυγχάνει τίποτα. Επιπεδώστε (flatten) πρώτα τη φόρμα XFA σε μια απλή AcroForm, όπως καλύπτεται στο επιπέδωση XFA σε AcroForm στο Delphi, και οι τρεις μέθοδοι εδώ εφαρμόζονται στη συνέχεια στα προκύπτοντα πεδία AcroForm ακριβώς όπως και σε οποιαδήποτε άλλη φορτωμένη φόρμα
Οι αυτόματα υπολογιζόμενες και αυτομορφoποιούμενες AcroForms καταλήγουν σε τρεις κλήσεις σε ένα φορτωμένο έγγραφο — προσάρτηση του υπολογισμού, προσάρτηση της μορφοποίησης και επικύρωσης, ψήσιμο της εμφάνισης — ακολουθούμενες από το SaveLoadedDocument. Το API φορτωμένης φόρμας που εμφανίζεται εδώ αποτελεί μέρος του HotPDF Component για Delphi and C++Builder, το οποίο τεκμηριώνει τις πλήρεις απαριθμήσεις THPDFFieldValidationKind και THPDFFieldCalcOp μαζί με την υπόλοιπη επιφάνεια της AcroForm