PDF faktura ili obračun troškova gde se stavke automatski sabiraju, a datum se sam formatira, zahteva tri stvari povezane sa AcroForm-om, a HotPDF dodaje sve tri u učitanu datoteku u Delphi-ju: metodu ApplyLoadedFieldCalculation za ukupne vrednosti AFSimple_Calculate, ApplyLoadedFieldValidation za Acrobat AF pomoćnike (helpers) za formatiranje i validaciju i EnsureLoadedFieldAppearanceStream da bi se ugradila (bake) vrednost koju pregledač zapravo iscrtava
HotPDF je izvorna VCL PDF komponenta za Delphi i C++Builder, i ove tri metode rade na dokumentu koji ste otvorili pomoću LoadFromFile, a ne na onom koji gradite od nule. Svaka metoda uzima indeks polja u bazi od 0 unutar niza AcroForm /Fields — isti redosled koji vraća metoda GetLoadedFormFieldNames — i piše direktno u rečnik polja, tako da se ceo posao svodi na učitavanje, indeksiranje, povezivanje i SaveLoadedDocument. Izgradnja samog skupa polja je odvojeni zadatak, pokriven u članku o dodavanju AcroForm polja u učitani PDF u Delphi-ju, dok je ova stranica o dodavanju aritmetike i formatiranja postojećem skupu polja
Zašto su PDF obrascu potrebne skripte da bi sabirao sopstvena polja?
Zato što PDF nema sloj tabelarnih proračuna (spreadsheet layer). Tekstualno polje sadrži string, a ne formulu, i ništa u datoteci ne preračunava ukupnu vrednost kada se stavka promeni, osim ako nije povezana skripta koja to radi. Taj mehanizam je rečnik dodatnih akcija polja, /AA (ISO 32000-1 §12.7.5), koji mapira događaje okidača na akcije: okidač pritiska tastera (K) kako pristižu znakovi, okidač formata za prikaz, okidač validacije (V) pri potvrdi unosa i okidač izračunavanja (C) koji se aktivira kad god se bilo koje polje na obrascu promeni. Svaka akcija je JavaScript akcija, /S /JavaScript (ISO 32000-1 §12.6.4.14), a skripta koju pokreće je ono što zapravo vrši sabiranje ili reformatiranje. Acrobat isporučuje biblioteku gotovih pomoćnika — AFSimple_Calculate, AFNumber_Format, AFDate_FormatEx i ostale — tako da je povezana skripta obično poziv od jedne linije koda u tu biblioteku, a ne prilagođeni kod
Jedan detalj odlučuje da li obrazac u više koraka daje ispravan rezultat: redosled izračunavanja (calculation order). Acrobat pokreće svako polje koje nosi akciju izračunavanja, i to redosledom kojim ih navodi niz AcroForm /CO, a ne redosledom stranica. Metoda ApplyLoadedFieldCalculation povezuje akciju izračunavanja sa poljem koje navedete. Kada jedno izračunato polje hrani drugo — podzbir koji čita poreska linija, a zatim krajnji zbir koji čita oba — morate potvrditi da redosled u nizu /CO navodi svaki ulaz pre polja koje ga koristi, inače će krajnji zbir pročitati zastareli podzbir i kasniti jednu izmenu unazad
Kako učiniti da PDF obrazac automatski izračunava ukupne vrednosti?
Metoda ApplyLoadedFieldCalculation uzima indeks ciljnog polja, operaciju i nazive izvornih polja koje treba da agregira. HotPDF upisuje AFSimple_Calculate("SUM", ["lineTotal.1", "lineTotal.2", ...]) u akciju izračunavanja ciljnog polja, a enumeracija THPDFFieldCalcOp bira funkciju: fccSum, fccAverage, fccProduct, fccMinimum i fccMaximum se mapiraju u Acrobat funkcije SUM, AVG, PRD, MIN i MAX. Izvorni nazivi su nazivi AcroForm polja, isti stringovi koje vraća metoda GetLoadedFormFieldNames, tako da je ukupan zbir preko tri stavke i cene isporuke jedan jedini poziv
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;
Poziv metode EnsureLoadedFieldAppearanceStream na ukupnom zbiru nije kozmetičke prirode. Izračunavanje se vrši unutar pregledača, pa dok čitač koji podržava skripte ne otvori datoteku, polje ukupnog zbira nema sopstveni renderovani sadržaj. Ugradnja toka izgleda (appearance stream) daje mu cifru koju može da prikaže u međuvremenu, što je ono što će posao štampanja ili generator sličica pokupiti ako nikada ne pokrenu skriptu
Formatiranje i validacija polja pomoću AF paketa pomoćnika
Metoda ApplyLoadedFieldValidation povezuje skripte za formatiranje, unos i validaciju u jednom pozivu, a prosleđeni tip THPDFFieldValidationKind odlučuje koji će se Acrobat pomoćnik povezati. Prosledite fvkCurrency i HotPDF upisuje AFNumber_Format(2,0,0,0,"$ ",true) kao skriptu formata sa odgovarajućom AFNumber_Keystroke skriptom. fvkNumber koristi "." za argument separatora, fvkPercentage emituje AFPercent_Format, a fvkDate emituje AFDate_FormatEx("dd/mm/yyyy") osim ako ne prosledite sopstvenu masku u argumentu DateFormat. Specijalni formati — fvkZipCode, fvkSSN i fvkPhone — mapiraju se u AFSpecial_Format sa kodom koji Acrobat rezerviše za svaki, a fvkArbitraryMask uzima string maske preko argumenta Mask za sve što ti predlošci ne pokrivaju. Za numeričke tipove HotPDF takođe povezuje skriptu AFRange_Validate tako da se unos van opsega odbija pri potvrdi
// 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 pomoćnici pokrivaju uobičajene oblike tako da nikada ne morate sami da pišete JavaScript za njih. Kada je polju potrebna logika koju nijedan šablon ne izražava — kontrolni zbir, pravilo za više polja ili prilagođeni regex — sami povežite sirovu skriptu pomoću metoda akcija na nivou polja opisanih u članku o izgradnji AcroForm polja i akcija pomoću HotPDF-a, koje direktno uzimaju JavaScript string. Oba puta pišu u isti rečnik /AA, tako da unapred podešeni format valute na jednom polju i ručno napisana validacija na drugom koegzistiraju u istom dokumentu
Zašto morate regenerisati tok izgleda (appearance stream)?
Zato što su string vrednosti i pikseli koje pregledač iscrtava dve različite stvari u PDF-u, i promena jedne ne menja drugu. Polje čuva svoju vrednost u /V, ali ono što čitač renderuje dolazi iz toka izgleda polja, /AP /N. Izmenite /V kroz graf objekata i striktan pregledač će i dalje prikazivati stari izgled dok ga nešto ne regeneriše. Rečnik obrazaca nosi zastavicu /NeedAppearances koja traži od pregledača da ponovo izgradi svaki izgled pri otvaranju, i Acrobat to poštuje — ali rendereri ugrađeni u pregledače, mobilni čitači, serveri za štampanje i generatori sličica često to ignorišu i crtaju onaj tok koji je već prisutan, ili uopšte ništa ne crtaju. Metoda EnsureLoadedFieldAppearanceStream premošćuje taj jaz tako što sama gradi /AP /N Form XObject iz polja /DA, /V i /Rect, tako da renderovana vrednost putuje unutar datoteke umesto da zavisi od toga da li će je pregledač ponovo iscrtati
// 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');
Gde AF skripte prestaju da rade
Svaka AF* skripta zavisi od toga da li pregledač ima JavaScript mehanizam, a većina pregledača ga nema. Acrobat i nekoliko desktop proizvoda pokreću ove pomoćnike. PDF pregledači ugrađeni u Chrome, Edge, Firefox i glavne mobilne platforme, zajedno sa serverskim rasterizerima i radnim tokovima za štampanje, uopšte ne izvršavaju JavaScript obrazaca. U tim čitačima izračunavanje se nikada ne pokreće i format se nikada ne primenjuje — polje jednostavno prikazuje onaj tok izgleda sa kojim je poslednji put sačuvano, što je upravo razlog zašto je ugradnja /AP /N toka važna. Tretirajte AF* sloj kao pogodnost za podskup korisnika koji koriste čitač koji podržava skripte, a nikada kao garanciju. Bilo koji ukupni iznos koji mora biti tačan na serveru — iznos fakture po kojoj ćete vršiti naplatu, poreska cifra koju ćete prijaviti — mora se ponovo izračunati na vašoj strani nakon što se obrazac vrati, jer ne možete pretpostaviti da je čitač koji ga je popunio izvršio ijednu liniju vaše skripte
Vredi pomenuti još jedan slučaj: obrasce koji su stigli kao XFA. Dinamički XFA obrazac nosi sopstveni mehanizam za izračunavanje u XML sadržaju o kojem AF* pomoćnici ne znaju ništa, tako da povezivanje AFSimple_Calculate sa XFA poljem ne postiže ništa. Prvo poravnajte (flatten) XFA obrazac u običan AcroForm, kao što je pokriveno u članku o poravnavanju XFA u AcroForm u Delphi-ju, a tri ovde opisane metode će se potom primeniti na rezultujuća AcroForm polja tačno onako kako se primenjuju na bilo koji drugi učitani obrazac
Automatski proračunski i samoformatirajući AcroForm obrasci svode se na tri poziva na učitanom dokumentu — povežite proračun, povežite format i validaciju, ugradite izgled — nakon čega sledi poziv SaveLoadedDocument. Ovde prikazani API za učitane obrasce deo je HotPDF komponente za Delphi i C++Builder, koja dokumentuje kompletne enumeracije THPDFFieldValidationKind i THPDFFieldCalcOp, zajedno sa ostatkom AcroForm funkcionalnosti