Technical Article

Interaktivni PDF obrasci u Delphiu: Akcije i JavaScript

Polje PDF obrasca samo po sebi je samo okvir koji sadrži vrijednost. Ono što čini da se obrazac ponaša kao mala aplikacija je akcija koja mu je pridružena: klik koji skriva odjeljak, dohvaća spremljene vrijednosti iz datoteke, skače na zadnju stranicu ili pokreće skriptu koja zbraja stupac. Ništa od toga ne živi u polju. Živi u rječniku akcija, a ISO 32000-1 organizira cijelu obitelj u §12.6. Ovaj članak prolazi kroz akcije kojima Delphi program najčešće pristupa i pokazuje kako ih PDFlibPas povezuje s poljem ili poveznicom.

Mentalni model koji vrijedi zapamtiti jest da su polje i akcija odvojeni objekti povezani referencom. Bilješka widgeta ili bilješka poveznice nosi akciju u svom unosu /A. Akcija imenuje polje na kojem radi prema naslovu, a ne prema indeksu, tako da je naslov koji date polju referenca koju svaka kasnija akcija koristi da ga pronađe. Kada je ta podjela jasna, API prestaje izgledati kao skup nasumičnih poziva i počinje izgledati kao jedan uzorak primijenjen na četiri vrste glagola.

Imenovane akcije: navigacija bez broja stranice

Najjednostavnije akcije uopće ne nose parametre. ISO 32000-1 §12.6.4.11, tablica 194, definira imenovane akcije: preglednik interpretira simboličko ime u vremenu izvođenja umjesto da slijedi pohranjeno odredište. Četiri imena su univerzalno podržana, i to su točno ona koja čitatelj očekuje na alatnoj traci: NextPage, PrevPage, FirstPage i LastPage. Budući da je odredište relativno u odnosu na stranicu koju preglednik trenutno prikazuje, gumb Next izrađen na ovaj način radi na svakoj stranici bez da morate računati cilj.

U PDFlibPas-u imenovana akcija je priložena pravokutniku vruće točke (hotspot) na trenutnoj stranici. Četvrti i peti cjelobrojni argumenti odabiru glagol i izgled.

// 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

Nema odredišta koje treba držati sinkroniziranim, što je i cijela poanta. Imenovana akcija preživljava umetanje i brisanje stranica jer uopće ne imenuje stranicu. Usporedite to s eksplicitnom poveznicom za prijelaz (go-to link), koja pohranjuje indeks ciljne stranice koji morate ponovno numerirati čim dokument naraste.

Akcija Hide i njezina zamka s poljem

Akcija Hide, ISO 32000-1 §12.6.4.10, tablica 196, mijenja vidljivost jednog ili više polja. To je najčišći način za izradu ponašanja prikazivanja i skrivanja bez skriptiranja, i to je ono što želite za poveznicu Prikaži detalje ili za dvije međusobno isključive ploče gdje otkrivanje jedne skriva drugu. Akcija nosi cilj u svom unosu /T i booleovu vrijednost /H koja odlučuje o smjeru: sakrij kada je true, prikaži kada je false.

Suptilnost je u potpunosti u tome kako je taj cilj kodiran, i to je vrsta detalja koja proizvodi obrazac koji radi na vašem računalu, ali ne radi kod klijenta. Kada akcija imenuje jedno polje, /T se piše kao jedan tekstualni niz. Kada ih imenuje nekoliko, /T se piše kao niz (array) tekstualnih nizova. Stariji preglednici ne tretiraju niz od jednog elementa isto kao i običan niz znakova, pa kodiranje mora ovisiti o broju: jedno ime mora biti emitirano kao niz znakova (string), a ne kao niz od jednog elementa (array), ako želimo da ga podržava najširi raspon preglednika. PDFlibPas donosi tu odluku umjesto vas. Prosljeđujete nazive polja odvojene zarezima, točka-zarezima ili prijelomima redaka, a pisac emitira jedan niz znakova za jedno ime i niz za dva ili više.

// 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);

Budući da akcija ne referencira nikakav vanjski resurs, ostaje kompatibilna s PDF/A. Imena koja prosljeđujete su potpuno kvalificirani naslovi polja, zbog čega se podređeno polje unutar grupe mora osloviti putem njegove pune staze s točkama, a ne samo njegovim imenom lista.

ImportData: unaprijed ispunjavanje iz FDF-a

Tamo gdje akcija Hide preraspoređuje ono što je već na stranici, akcija uvoza podataka (import-data) donosi vrijednosti izvana. ISO 32000-1 §12.6.4.8, tablica 198, definira je kao akciju koja popunjava AcroForm iz datoteke formata Forms Data Format (FDF) na disku. Ovo je akcija iza kontrola Ponovno učitaj uzorak podataka ili Vrati na zadano, gdje se FDF datoteka isporučuje uz PDF i sadrži kanonske vrijednosti polja. Poziv zrcali ostale, uzimajući pravokutnik vruće točke, stazu do FDF-a i bitnu masku izgleda: Pdf.AddLinkToImportData(40, 660, 120, 18, 'defaults.fdf', 1). Datoteka ne mora postojati kada se PDF gradi, ali mora biti prisutna kada korisnik klikne, a sve kose crte u stazi se prepisuju u PDF-kanonski oblik kose crte za vas.

Jedno ograničenje vrijedi jasno navesti jer je česta iznenađenje. Akcija uvoza podataka (import-data) pokazuje na vanjsku datoteku, pa stoga nije dopuštena u PDF/A. Kada je dokument u PDF/A načinu rada, poziv vraća nulu i ne dodaje ništa, umjesto da proizvede datoteku koja ne prolazi provjeru. Ako vaš cjevovod cilja arhivski izlaz, unaprijed ispunjavanje se mora dogoditi u vrijeme generiranja izravnim pisanjem vrijednosti polja, a ne odgađanjem za klik.

JavaScript: globalni paketi i skripte po akciji

Za logiku koja nadilazi prikazivanje, skrivanje i uvoz, obitelj akcija poseže za JavaScriptom na razini dokumenta. Postoje dva različita mjesta na kojima skripta može živjeti, i razlika je važna. JavaScript paket na razini dokumenta pohranjuje se jednom za cijelu datoteku i pokreće se kada se dokument otvori, što ga čini pravim mjestom za definicije funkcija i zajedničko stanje. Skripta po akciji je priložena jednoj poveznici ili polju i pokreće se samo kada se taj objekt aktivira, što je čini pravim mjestom za jedan redak koji poziva funkciju koju je paket već definirao.

PDFlibPas izlaže oboje. AddGlobalJavaScript pohranjuje imenovani paket na razini dokumenta; ponovno korištenje imena zamjenjuje sve što je bilo pohranjeno pod njim. AddLinkToJavaScript prilaže skriptu vrućoj točki tako da je klik izvršava.

// 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);

Držanje funkcije u globalnom paketu, a poziva u poveznici, nije stvar stila. Time se izbjegava dupliciranje istog tijela na svakoj kontroli koja ga treba, a to znači da preglednik s onemogućenim skriptiranjem jednostavno ne čini ništa pri kliku umjesto da se sruši na neispravnom ugrađenom objektu. Također drži unose po akciji malenima, što datoteku čini čitljivom kada je kasnije pregledavate.

Polja, podređena polja i zamrzavanje rezultata

Akcije trebaju polja na kojima će djelovati, pa pomaže vidjeti kako polje nastaje. NewFormField stvara polje na trenutnoj stranici i vraća njegov indeks; cjelobrojni tip odabire vrstu, pri čemu je 1 Text, 2 Pushbutton, 3 Checkbox, 4 Radiobutton, 5 Choice, 6 Signature i 7 Parent koji posjeduje podređene elemente, ali sam ne crta ništa. Naslov koji prosljeđujete ne smije sadržavati točku, jer je točka separator u potpuno kvalificiranim nazivima koje akcije koriste za adresiranje podređenih polja.

Radio grupe i hijerarhijski obrasci grade se dodjeljivanjem podređenih elemenata nadređenom polju. NewChildFormField dodaje podređeno polje pod imenovanim nadređenim poljem, a za radio gumbe i izbore AddFormFieldSub dodaje pojedinačne opcije i vraća privremeni indeks koji koristite za pozicioniranje svake od njih. Kada interaktivna faza završi i želite zamrznuti polje tako da njegov trenutni izgled postane trajni sadržaj stranice, FlattenFormField iscrtava polje na stranici i uklanja ga iz obrasca. Nakon izravnavanja (flatten), indeksi kasnijih polja pomiču se prema dolje za jedan, što je jedina stvar koju trebate zapamtiti ako izravnavate nekoliko polja u petlji.

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;

Poziv za izravnavanje (flatten) je namjerno komentiran. Izostavite ga i dokument se isporučuje kao aktivni obrazac čije se akcije aktiviraju u čitaču. Omogućite ga i polje se pretvara u statične oznake, što je ono što želite kada je obrazac dovršen i rezultat treba putovati kao fiksni zapis. Isto polje, isti kod, dva vrlo različita dokumenta ovisno o tome jeste li ga zamrznuli.

Odabir pravog glagola

Četiri akcije se jasno dijele prema onome što dotiču. Imenovana akcija pomiče prikaz i ne treba joj polje. Akcija Hide mijenja vidljivost i treba naslove polja, pri čemu je kodiranje između niza znakova i niza elemenata (string-versus-array) riješeno za vas. Akcija uvoza podataka (import-data) pristupa datoteci na disku i stoga je zabranjena u PDF/A. JavaScript akcija pokreće proizvoljnu logiku i najbolje ju je podijeliti između globalnog paketa funkcija i malih poziva po akciji. Posegnite za najjednostavnijom koja radi posao: akcija Hide je prenosivija od skripte koja postavlja skrivenu zastavicu, a imenovana akcija je trajnija od pohranjenog odredišta stranice jer nema broja koji treba održavati.

Odavde dvije susjedne teme dovršavaju sliku. Ako je obrazac dio pristupačnog dokumenta, stablo strukture kojim prolaze čitači zaslona pokriveno je u našem članku o označenom PDF-u i strukturi pristupačnosti. Kada se popunjeni obrazac mora zaključati i potpisati, tijek rada je opisan u vodiču za radnu površinu za usklađenost i potpisivanje. Sve tri se grade na istom motoru, koji se isporučuje kao PDF knjižnica za Delphi uz API-je za izradu, obrasce i potpise koji su pokriveni drugdje na ovom blogu.