Tehnični članak

Interaktivni obrazci PDF v Delphi: Dejanja in JavaScript

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 PDF Library for Delphi 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

Diagram PDF Library for Delphi pripisa povezave ali gradnika PDF, ki se sklicuje na en sam slovar dejanj, ki se razveji v glagole dejanj named, hide, import-data in JavaScript, kartica import-data pa je označena kot izključena iz PDF/A
En sam slovar dejanj za povezavo ali gradnikom napaja štiri različne glagole — premikanje pogleda, vidnost, uvoz z diska in skripte

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 PDF Library for Delphi 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
// Bit 0 v Options (vrednost 1) nariše obrobo okoli vroče točke
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);   // skoči na zadnjo stran

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. PDF Library for Delphi 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

Diagram PDF Library for Delphi pravil kodiranja dejanja Hide, kjer točno eno ime polja postane besedilni niz /T, dve ali več imen pa polje nizov, ter opombe o smeri zastavice skrivanja, varnosti PDF/A in popolnoma kvalificiranih naslovih polj
Zapisovalnik se razveji glede na število polj, tako da samotno ime odpotuje kot goli niz, ki ga spoštujejo starejši bralniki, dve ali več pa postane polje, ki se preklopi skupaj
// Neničelni HideFlag skrije navedena polja (/H true); ničla jih prikaže
// Eno ime -> /T je besedilni niz. Dve ali več imen -> /T je polje nizov
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

PDF Library for Delphi 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

Diagram PDF Library for Delphi dvoravenskega modela JavaScript PDF, kjer globalni paket ravni dokumenta ob odpiranju dokumenta definira recalcTotal, vsaka povezava pa nosi enovrstični skript dejanja, ki ga pokliče ob kliku
Definirajte recalcTotal enkrat v paketu ravni dokumenta in vsaka klikljiva točka naj dobi dostop do njega z enovrstičnim klicem na dejanje
// Paket na ravni dokumenta: enkrat definiraj funkcijo za ponovno uporabo
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);' +
  '}');

// Skript posameznega dejanja na povezavi: samo pokliči skupno funkcijo
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;

    // Besedilno polje, ki ga bo dejanje Hide ciljalo po naslovu
    FldShip := Pdf.NewFormField('ShippingAddress', 1);
    Pdf.SetFormFieldBounds(FldShip, 40, 120, 240, 20);
    Pdf.SetFormFieldValue(FldShip, '');

    // Na to stran poveži povezavo Hide in navigacijsko povezavo
    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

    // Skript na ravni dokumenta, na voljo vsakemu dogodku v datoteki
    Pdf.AddGlobalJavaScript('OnOpen',
      'app.alert("Form ready", 3);');

    // Zakleni polje, če izhod ne sme biti več urejljiv
    // 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