Polje obrazca PDF je samo po sebi le polje, ki vsebuje vrednost. Tisto, zaradi česar se obrazec obnaša kot majhna aplikacija, je nanj pripeto dejanje: klik, ki skrije razdelek, povleče shranjene vrednosti iz datoteke, skoči na zadnjo stran ali zažene skript, ki sešteje stolpec. Nič od tega ne živi v polju. Živi v slovarju dejanj, standard ISO 32000-1 pa celotno družino organizira v §12.6. Ta članek obravnava dejanja, po katerih program v okolju Delphi najpogosteje poseže, in prikazuje, kako PDFlibPas vsako od njih poveže s poljem ali povezavo.
Miselni model, ki ga je vredno obdržati, je, da sta polje in dejanje ločena objekta, povezana z referenco. Pripomba gradnika (widget) ali povezave nosi dejanje v svojem vnosu /A. Dejanje poimenuje polje, na katerem deluje, po naslovu in ne po indeksu, so naslov, ki ga dodelite polju, ročica, ki jo vsako kasnejše dejanje uporabi za njegovo iskanje. Ko je ta delitev jasna, API preneha izgledati kot naključna zbirka klicev in začne izgledati kot en vzorec, uporabljen za štiri vrste glagolov.
Imenovana dejanja: navigacija brez številke strani
Najenostavnejša dejanja sploh ne vsebujejo parametrov. ISO 32000-1 §12.6.4.11, tabela 194, definira imenovana dejanja: pregledovalnik med izvajanjem interpretira simbolno ime, namesto da bi sledil shranjenemu cilju. Splošno podprta so štiri imena, ki so natanko tista, ki jih bralec pričakuje v orodni vrstici: NextPage, PrevPage, FirstPage in LastPage. Ker je cilj relativen glede na stran, ki jo pregledovalnik trenutno prikazuje, gumb Naprej, zgrajen na ta način, deluje na vsaki strani, ne da bi vam bilo treba izračunati cilj.
V PDFlibPas je imenovano dejanje pripeto na aktivno pravokotno območje (hotspot) na trenutni strani. Četrti in peti celoštevilski argument izbereta glagol in videz.
// NamedActionType: 0 = NextPage, 1 = PrevPage, 2 = FirstPage, 3 = LastPage
// Options bit 0 (value 1) draws a border around the hotspot
Pdf.AddLinkToNamedAction(500, 560, 60, 18, 0, 1); // Next
Pdf.AddLinkToNamedAction(40, 560, 60, 18, 1, 1); // Previous
Pdf.AddLinkToNamedAction(110, 560, 60, 18, 3, 1); // jump to last page
Cilja ni treba sinhronizirati, kar je bistvo. Imenovano dejanje preživi vstavljanje in brisanje strani, saj sploh nikoli ne poimenuje strani. Primerjajte to z eksplicitno povezavo za prehod (go-to), ki shrani ciljni indeks strani, ki ga morate preštevilčiti takoj, ko se dokument poveča.
Dejanje Skrij in njegova zanka z matriko
Dejanje Skrij (Hide), ISO 32000-1 §12.6.4.10, tabela 196, preklaplja vidnost enega ali več polj. To je najčistejši način za izgradnjo obnašanja prikazovanja in skrivanja brez skriptiranja in je tisto, kar želite za povezavo Prikaži podrobnosti ali za dve medsebojno izključujoči se plošči, kjer razkritje ene prikrije drugo. Dejanje nosi cilj v svojem vnosu /T in logično vrednost /H, ki določa smer: skrij, ko je true, prikaži, ko je false.
Drobna posebnost je v celoti v tem, kako je ta cilj kodiran, in to je tista podrobnost, ki povzroči, da obrazec deluje na vaši napravi, pri stranki pa ne. Ko dejanje poimenuje eno polje, se /T zapiše kot en besedilni niz. Ko jih poimenuje več, se /T zapiše kot matrika besedilnih nizov. Starejši pregledovalniki ne obravnavajo matrike z enim elementom enako kot navaden niz, zato se mora kodiranje razvejati glede na število: eno ime mora biti oddano kot niz, ne kot matrika dolžine ena, če želimo, da ga spoštuje čim širši krog bralnikov. PDFlibPas to odločitev sprejme namesto vas. Imena polj podate ločena z vejicami, podpičji ali prelomi vrstic, pisec pa izpiše en niz za eno ime in matriko za dve ali več imen.
// HideFlag non-zero hides the listed fields (/H true); zero shows them.
// One name -> /T is a text string. Two or more -> /T is an array of strings.
Pdf.AddLinkToHideField(40, 700, 90, 18, 'ShippingAddress', 1, 1);
Pdf.AddLinkToHideField(140, 700, 90, 18,
'ShippingName,ShippingAddress,ShippingZip', 1, 1);
Ker se dejanje ne sklicuje na noben zunanji vir, ostaja združljivo s PDF/A. Imena, ki jih posredujete, so polno kvalificirani naslovi polj, zato je treba podrejeno polje znotraj skupine nasloviti prek njegove celotne poti s pikami in ne le z njegovim samostojnim imenom.
ImportData: predizpolnjevanje iz FDF
Kjer dejanje Skrij prerazporedi tisto, kar je že na strani, dejanje uvoza podatkov prinese vrednosti od zunaj. ISO 32000-1 §12.6.4.8, tabela 198, ga definira kot dejanje, ki napolni AcroForm iz datoteke Forms Data Format na disku. To je dejanje, ki stoji za kontrolnikom Ponovno naloži vzorčne podatke ali Ponastavi na privzeto, kjer je datoteka FDF priložena poleg PDF in vsebuje kanonične vrednosti polj. Klic posnema ostale, saj sprejme aktivni pravokotnik, pot do datoteke FDF in bitno masko videza: Pdf.AddLinkToImportData(40, 660, 120, 18, 'defaults.fdf', 1). Datoteka ob izdelavi PDF ni nujno prisotna, mora pa biti na voljo, ko uporabnik klikne, vse poševnice nazaj v poti pa se namesto vas prepišejo v kanonično obliko poševnic PDF.
Eno omejitev je vredno jasno izpostaviti, saj pogosto predstavlja presenečenje. Dejanje uvoza podatkov kaže na zunanjo datoteko, zato v načinu PDF/A ni dovoljeno. Ko je dokument v načinu PDF/A, klic vrne nič in ne doda ničesar, namesto da bi ustvaril datoteko, ki ne bi prestala preverjanja. Če je vaš cilj arhivski izhod, se mora predizpolnjevanje zgoditi ob času generiranja z neposrednim pisanjem vrednosti polj, ne pa z odlaganjem na klik.
JavaScript: globalni paketi in skripti za posamezna dejanja
Za logiko, ki presega prikazovanje, skrivanje in uvoz, družina dejanj poseže po JavaScriptu na ravni dokumenta. Skript lahko živi na dveh različnih mestih in ta razlika je pomembna. Paket JavaScript na ravni dokumenta se shrani enkrat za celotno datoteko in se zažene ob odprtju dokumenta, zaradi česar je primerno mesto za definicije funkcij in skupno stanje. Skript za posamezno dejanje pa je pripet na eno povezavo ali polje in se zažene le, ko je ta objekt aktiviran, kar je primerno mesto za tisto eno vrstico, ki kliče funkcije, ki jih je paket že definiral.
PDFlibPas omogoča oboje. AddGlobalJavaScript shrani poimenovan paket na ravni dokumenta; ponovna uporaba imena zamenja tisto, kar je bilo pod njim shranjeno. AddLinkToJavaScript pripne skript na aktivno območje, tako da ga klik izvede.
// Document-level package: define a reusable function once.
Pdf.AddGlobalJavaScript('Totals',
'function recalcTotal() {' +
' var net = this.getField("Net").value;' +
' var tax = this.getField("Tax").value;' +
' this.getField("Gross").value = Number(net) + Number(tax);' +
'}');
// Per-action script on a link: just call the shared function.
Pdf.AddLinkToJavaScript(40, 620, 100, 18, 'recalcTotal();', 1);
Hranjenje funkcije v globalnem paketu in klica v povezavi ni vprašanje stilske preference. S tem se izognemo podvajanju istega telesa na vsakem kontrolniku, ki ga potrebuje, in pomeni, da pregledovalnik z onemogočenim skriptiranjem ob kliku preprečeno ne naredi ničesar, namesto da bi se zrušil zaradi nepravilno oblikovanega vdelanega podatkovnega bloka. Prav tako ohranja vnose za posamezna dejanja majhne, kar ohranja datoteko čitljivo ob kasnejšem pregledu.
Polja, podrejena polja in zamrznitev rezultata
Dejanja potrebujejo polja, na katerih delujejo, zato je koristno videti, kako polje nastane. NewFormField ustvari polje na trenutni strani in vrne njegov indeks; celoštevilska vrsta izbere vrsto polja, pri čemer je 1 besedilo (Text), 2 gumb (Pushbutton), 3 potrditveno polje (Checkbox), 4 radijski gumb (Radiobutton), 5 izbira (Choice), 6 podpis (Signature) in 7 nadrejeno polje (Parent), ki si lasti podrejena polja, vendar samo ne izrisuje ničesar. Naslov, ki ga posredujete, ne sme vsebovati pike, saj je pika ločilo v polno kvalificiranih imenih, ki jih dejanja uporabljajo za naslavljanje podrejenih polj.
Skupine radijskih gumbov in hierarhični obrazci se zgradijo tako, da se nadrejenemu polju dodelijo podrejena polja. NewChildFormField doda podrejeno polje pod poimenovano nadrejeno polje, pri radijskih gumbih in izbirah pa AddFormFieldSub doda posamezne možnosti in vrne začasni indeks, ki ga uporabite za pozicioniranje vsake od njih. Ko je interaktivna faza končana in želite polje zamrzniti, tako da njegov trenutni videz postane trajna vsebina strani, FlattenFormField izriše polje na stran in ga odstrani iz obrazca. Po sploščitvi se indeksi kasnejših polj zamaknejo navzdol za eno mesto, kar je edina stvar, ki si jo morate zapomniti, če sploščite več polj v zanki.
var
Pdf: TPDFlib;
FldShip: Integer;
begin
Pdf := TPDFlib.Create;
try
Pdf.SetOrigin(1); // top-left origin
Pdf.SetPageSize('A4');
Pdf.NewPage;
// A text field the Hide action will target by its title.
FldShip := Pdf.NewFormField('ShippingAddress', 1);
Pdf.SetFormFieldBounds(FldShip, 40, 120, 240, 20);
Pdf.SetFormFieldValue(FldShip, '');
// Wire a Hide link and a navigation link to this page.
Pdf.DrawText(40, 110, 'Toggle shipping block:');
Pdf.AddLinkToHideField(220, 100, 70, 16, 'ShippingAddress', 1, 1);
Pdf.AddLinkToNamedAction(500, 800, 60, 18, 3, 1); // Last page
// A document-level script available to every event in the file.
Pdf.AddGlobalJavaScript('OnOpen',
'app.alert("Form ready", 3);');
// Freeze the field if the output should no longer be editable.
// Pdf.FlattenFormField(FldShip);
if Pdf.SaveToFile('form_actions.pdf') <> 1 then
raise Exception.Create('Save failed');
finally
Pdf.Free;
end;
end;
Klic za sploščitev je namenoma zakomentiran. Če ga izpustite, bo dokument oddan kot aktiven obrazec, katerega dejanja se sprožijo v bralniku. Če ga omogočite, se polje izriše v statične oznake, kar je tisto, kar želite, ko je obrazec izpolnjen in mora rezultat potovati kot fiksni zapis. Isto polje, ista koda, dva zelo različna dokumenta, odvisno od tega, ali ga zamrznete.
Izbira pravega glagola
Štiri dejanja se jasno delijo glede na to, česa se dotikajo. Imenovano dejanje premakne vidno polje in ne potrebuje polja. Dejanje Skrij spreminja vidnost in potrebuje naslove polj, pri čemer je kodiranje niz-proti-matriki urejeno namesto vas. Dejanje uvoza podatkov doseže datoteko na disku in je zato v PDF/A prepovedano. Dejanje JavaScript izvaja poljubno logiko in ga je najbolje razdeliti med globalni paket funkcij in majhne klice za posamezna dejanja. Posezite po najpreprostejšem, ki opravi delo: dejanje Skrij je bolj prenosljivo kot skript, ki nastavi zastavico za skrito, imenovano dejanje pa je trajnejše od shranjenega cilja strani, saj ni številke, ki bi jo bilo treba vzdrževati.
Od tu sliko dopolnjujeta dve sorodni temi. Če je obrazec del dostopnega dokumenta, je strukturno drevo, ki ga berejo bralniki zaslona, obravnavano v našem članku o označenem PDF in dostopni strukturi. Ko je treba izpolnjen obrazec zakleniti in podpisati, je delovni tok opisan v navodilih za delovno mizo za skladnost in podpisovanje. Vse tri gradijo na istem pogonu, ki je na voljo kot knjižnica PDF za Delphi skupaj z vmesniki API za ustvarjanje, obrazce in podpise, ki so obravnavani drugje na tem blogu.