En faktura- eller utleggs-PDF der varelinjene legger seg sammen og datoen formaterer seg selv, trenger tre ting koblet til sitt AcroForm, og HotPDF legger til alle tre i en innlastet fil i Delphi: ApplyLoadedFieldCalculation for AFSimple_Calculate-totaler, ApplyLoadedFieldValidation for Acrobat AF-format- og valideringshjelpere, og EnsureLoadedFieldAppearanceStream for å forhåndsgenerere (bake) verdien en visningsprogramvare faktisk tegner
HotPDF er en innfødt VCL PDF-komponent for Delphi og C++Builder, og disse tre metodene opererer på et dokument du åpnet med LoadFromFile i stedet for ett du bygger fra bunnen av. Hver av dem tar en nullbasert feltindeks inn i AcroForms /Fields-tabell — den samme rekkefølgen som GetLoadedFormFieldNames returnerer — og skriver direkte inn i feltordboken, so hele jobben består i å laste inn, indeksere, koble til, og SaveLoadedDocument. Å bygge selve feltsettet er en separat oppgave, dekket i legge til AcroForm-felt i en innlastet PDF i Delphi, mens denne siden handler om å gi et eksisterende feltsett aritmetikk og formatering
Hvorfor trenger et PDF-skjema skript for å summere sine egne felt?
Fordi PDF ikke har noe regnearklag. Et tekstfelt inneholder en streng, ikke en formel, og ingenting i filen beregner en total på nytt når en varelinje endres med mindre et skript er tilkoblet for å gjøre det. Mekanismen er feltets tilleggshandlings-ordbok (additional-actions dictionary), /AA (ISO 32000-1 §12.7.5), som kartlegger utløsende hendelser til handlinger: en tastetrykk-utløser (K) når tegn ankommer, en format-utløser for visning, en validerings-utløser (V) ved lagring, og en beregnings-utløser (C) som utløses hver gang et felt på skjemaet endres. Hver handling er en JavaScript-handling, /S /JavaScript (ISO 32000-1 §12.6.4.14), og skriptet den kjører er det som faktisk utfører summen eller omformateringen. Acrobat leveres med et bibliotek av ferdige hjelpere — AFSimple_Calculate, AFNumber_Format, AFDate_FormatEx og resten — så det tilkoblede skriptet er vanligvis et enkeltkall inn i dette biblioteket i stedet for spesialtilpasset kode
Én detalj avgjør om et flertrinnsskjema gir riktig svar: beregningsrekkefølge. Acrobat kjører alle felt som har en beregningshandling, og den kjører dem i den rekkefølgen AcroForms /CO-tabell lister opp, ikke i siderekkefølge. ApplyLoadedFieldCalculation kobler beregningshandlingen til feltet du navngir; når et beregnet felt mater et annet — en delsum som en avgiftslinje leser, og deretter en totalsum som leser begge — må du bekrefte at /CO-rekkefølgen lister hver inndata foran feltet som bruker den, ellers vil totalsummen lese en foreldet delsum og ligge én redigering bak
Hvordan får du et PDF-skjema til å beregne totaler automatisk?
ApplyLoadedFieldCalculation tar målfeltindeksen, en operasjon, og navnene på kildefeltene den skal aggregere. HotPDF skriver AFSimple_Calculate("SUM", ["lineTotal.1", "lineTotal.2", ...]) inn i målfeltets beregningshandling, og THPDFFieldCalcOp-opplistingen velger funksjonen: fccSum, fccAverage, fccProduct, fccMinimum og fccMaximum kartlegger til henholdsvis Acrobat SUM, AVG, PRD, MIN og MAX. Kildenavnene er AcroForm-feltnavn, de samme strengene som GetLoadedFormFieldNames gir tilbake, så en totalsum over tre varelinjer og en fraktkostnad er et enkelt kall
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;
Det automatiske hoppet over blob-felt er en standard, ikke en tvangstrøye. Når du trenger kontroll per felt — en tilpasset kolonneetikett, en smalere bredde, eller å utelate en intern nøkkelkolonne som ikke er en blob — oppretter du en TPDFlibTableExporter direkte og kobler deg til dens OnFieldFilter-hendelse, som utløses én gang per felt og gir deg en endringsbar spesifikasjon. Sett Include to False for å utelate feltet, eller angi ColumnWidth og DisplayLabel for å overstyre standardverdiene. Denne hendelsen er også der du ekskluderer en bred memo-kolonne som du ikke vil at skal blåse opp siden
Formatere og validere felt med AF-hjelper-pakken
ApplyLoadedFieldValidation kobler til format-, tastetrykk- og valideringsskriptene i ett enkelt kall, og THPDFFieldValidationKind du sender inn bestemmer hvilken Acrobat-hjelper den kobler opp. Send inn fvkCurrency og HotPDF skriver AFNumber_Format(2,0,0,0,"$ ",true) som formatskript med tilhørende AFNumber_Keystroke; fvkNumber bruker "." for separator-argumentet, fvkPercentage sender ut AFPercent_Format, og fvkDate sender ut AFDate_FormatEx("dd/mm/yyyy") med mindre du sender din egen maske i DateFormat-argumentet. De spesielle formatene — fvkZipCode, fvkSSN og fvkPhone — kartlegger til AFSpecial_Format med koden Acrobat reserverer for hver, og fvkArbitraryMask tar en maskestreng via Mask-argumentet for alt annet som ikke dekkes av forhåndsinnstillingene. For de numeriske typene legger HotPDF også til et AFRange_Validate-skript slik at inndata utenfor verdiområdet avvises ved lagring
// 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-hjelperne dekker de vanlige formene slik at du aldri trenger å skrive JavaScript for dem selv. Når et felt trenger logikk ingen forhåndsinnstilling kan uttrykke — en sjekksum, en kryssfeltsregel, en tilpasset regex — kobler du til det rå skriptet selv med handlingmetodene på feltnivå beskrevet i bygging av AcroForm-felt og handlinger med HotPDF, som tar en JavaScript-streng direkte. Begge rutene skriver inn i den samme /AA-ordboken, slik at et forhåndsinnstilt valutaformat på ett felt og en manuelt skrevet validering på et annet kan eksistere side om side i samme dokument
Hvorfor må du generere utseendestrømmen på nytt?
Fordi verdistrengen og pikslene en visningsprogramvare tegner er to forskjellige ting i PDF, og endring av den ene endrer ikke den andre. Et felt lagrer verdien sin i /V, men det en leser rendrer kommer fra feltets utseendestrøm (appearance stream), /AP /N. Hvis du redigerer /V gjennom objektgrafen, vil en streng visningsprogramvare fortsatt vise det gamle utseendet til noe genererer det på nytt. Skjemaordboken har et /NeedAppearances-flagg som ber visningsprogrammet om å gjenoppbygge alle utseender ved åpning, og Acrobat respekterer det — men nettleserinnbygde rendrere, mobil-lesere, utskriftsservere og miniatyrbildegeneratorer ignorerer det ofte og tegner den strømmen som allerede er til stede, eller ingenting i det hele tatt. EnsureLoadedFieldAppearanceStream tetter dette gapet bygger /AP /N Form XObject selv fra feltets /DA, /V og /Rect, slik at den rendrede verdien følger med inni filen i stedet for å være avhengig av at visningsprogrammet tegner den på nytt
// 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');
Hvor AF-skript slutter å fungere
Alle AF*-skript er avhengige av at visningsprogrammet har en JavaScript-motor, og de fleste visningsprogrammer har ikke det. Acrobat og et fåtall skrivebordsprodukter kjører disse hjelperne; PDF-viserne som er innebygd i Chrome, Edge, Firefox og de store mobilplattformene, sammen med rasteriserere på serversiden og utskriftskøer, kjører ikke skjemajavascript i det hele tatt. I disse leserne vil beregningen aldri utløses og formatet vil aldri bli brukt — feltet viser ganske enkelt den utseendestrømmen det sist ble lagret med, som er akkurat grunnen til at forhåndsgenerering (baking) av /AP /N er så viktig. Behandle AF*-laget som en praktisk fordel for den delen av brukerne som bruker en leser med skriptstøtte, aldri som en garanti. Alle totaler som må være korrekte på serversiden — et fakturabeløp du vil fakturere mot, eller et avgiftstall du skal sende inn — må beregnes på nytt på din side etter at skjemaet kommer i retur, fordi du ikke kan anta at leseren som fylte det ut kjørte en eneste linje av skriptet ditt
Ett tilfelle til er verdt å nevne: skjemaer som kom som XFA. Et dynamisk XFA-skjema bærer sin egen beregningsmotor i en XML-nyttelast (payload) som AF*-hjelperne ikke vet noe om, så å koble AFSimple_Calculate til et XFA-felt har ingen effekt. Flat ut XFA-skjemaet til et vanlig AcroForm først, som beskrevet i XFA- til AcroForm-utflatning i Delphi, og de tre metodene her vil deretter gjelde for de resulterende AcroForm-feltene akkurat som de gjør for alle andre innlastede skjemaer
Autoberegnende PDF-skjemaer i Delphi med HotPDF koker ned til tre API-kall på et innlastet dokument — koble til beregning, koble til format og validering, forhåndsgenerere utseendestrømmen — etterfulgt av SaveLoadedDocument. loaded-form-API-et som vises her er en del av HotPDF Component for Delphi og C++Builder, som dokumenterer de fulle THPDFFieldValidationKind- og THPDFFieldCalcOp-opplistinger ved siden av resten av AcroForm-grensesnittet