Tehnički članak

Interaktivni PDF obrasci u Delphi-ju: Akcije i JavaScript

PDF polje obrasca samo po sebi je samo okvir koji sadrži vrednost. Ono što čini da se obrazac ponaša kao mala aplikacija je akcija koja mu je pridružena: klik koji skriva odeljak, preuzima sačuvane vrednosti iz datoteke, skače na poslednju stranicu ili pokreće skriptu koja sabira kolonu. Ništa od toga ne živi u polju. To živi u rečniku akcija, a ISO 32000-1 organizuje celu porodicu u §12.6. Ovaj članak prolazi kroz akcije koje Delphi program najčešće koristi i pokazuje kako ih PDF Library for Delphi povezuje sa poljem ili linkom

Mentalni model koji vredi zapamtiti jeste da su polje i akcija odvojeni objekti povezani referencom. Napomena vidžeta ili napomena linka nosi akciju u svom /A unosu. Akcija imenuje polje na kojem radi prema nazivu, a ne prema indeksu, tako da je naziv koji date polju ručica koju svaka kasnija akcija koristi da ga pronađe. Kada je ta podela jasna, API prestaje da izgleda kao gomila nasumičnih poziva i počinje da izgleda kao jedan šablon primenjen na četiri vrste glagola

Dijagram PDF Library for Delphi o PDF link ili widget anotaciji koja referencira jedan action rečnik koji se grana na akcijske glagole named, hide, import-data i JavaScript, pri čemu je import-data kartica označena kao isključena iz PDF/A
Jedan rečnik akcija iza veze ili vidžeta hrani četiri različita glagola — kretanje viewport-a, vidljivost, uvoz sa diska i skriptanje

Imenovane akcije: navigacija bez broja stranice

Najjednostavnije akcije uopšte ne nose parametre. ISO 32000-1 §12.6.4.11, Tabela 194, definiše imenovane akcije: čitač tumači simboličko ime u vreme izvršavanja umesto da prati sačuvanu destinaciju. Četiri imena su univerzalno podržana, i to su upravo ona koja čitalac očekuje na traci sa alatkama: NextPage, PrevPage, FirstPage i LastPage. Pošto je destinacija relativna u odnosu na stranicu koju čitač trenutno prikazuje, dugme Sledeća napravljeno na ovaj način radi na svakoj stranici bez potrebe da računate cilj

U PDF Library for Delphi-u, imenovana akcija se povezuje sa pravougaonikom osetljivim na klik na trenutnoj stranici. Četvrti i peti celobrojni argument biraju glagol i izgled

// NamedActionType: 0 = NextPage, 1 = PrevPage, 2 = FirstPage, 3 = LastPage
// Bit 0 parametra Options (vrednost 1) iscrtava ivicu oko hotspot-a
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);   // idi na poslednju stranicu

Nema destinacije koju treba držati sinhronizovanom, što je i poenta. Imenovana akcija preživljava umetanje i brisanje stranica jer uopšte ne navodi stranicu. Uporedite to sa eksplicitnim go-to linkom koji čuva indeks ciljne stranice koji morate ponovo numerisati čim dokument poraste

Akcija Hide i zamka sa nizom

Akcija Hide, ISO 32000-1 §12.6.4.10, Tabela 196, menja vidljivost jednog ili više polja. To je najčistiji način da se napravi ponašanje prikazivanja i skrivanja bez skriptovanja, i to je ono što želite za link Prikaži detalje ili za dva međusobno isključiva panela gde otkrivanje jednog skriva drugi. Akcija nosi cilj u svom /T unosu i logičku vrednost /H koja određuje smer: sakrij kada je tačno, prikaži kada je netačno

Suptilnost je u potpunosti u načinu na koji je taj cilj kodiran, i to je vrsta detalja koja proizvodi obrazac koji radi na vašoj mašini, ali otkaže kod klijenta. Kada akcija imenuje jedno polje, /T se piše kao jedan tekstualni niz. Kada imenuje više njih, /T se piše kao niz tekstualnih nizova. Stariji čitači ne tretiraju niz od jednog elementa isto kao i običan tekstualni niz, tako da kodiranje mora da se grana u zavisnosti od broja: jedno ime mora biti emitovano kao tekstualni niz, a ne kao niz dužine jedan, kako bi ga poštovao najširi krug čitača. PDF Library for Delphi donosi tu odluku umesto vas. Proleđujete imena polja odvojena zarezima, tačka-zarezima ili prelomima reda, a pisac emituje jedan tekstualni niz za jedno ime i niz za dva ili više imena

Dijagram PDF Library for Delphi o pravilima kodiranja PDF Hide akcije gde tačno jedno ime polja postaje /T tekstualni niz dok dva ili više imena postaju niz stringova, uz napomene o smeru hide zastavice, PDF/A sigurnosti i potpuno kvalifikovanim naslovima polja
Pisac grana po broju polja pa usamljeno ime ide kao goli string koji stariji čitači poštuju, dok dva ili više postaju niz koji se prebacuje zajedno
// Nenulta vrednost HideFlag skriva navedena polja (/H true); nula ih prikazuje
// Jedno ime -> /T je tekstualni niz. Dva ili više -> /T je niz stringova
Pdf.AddLinkToHideField(40, 700, 90, 18, 'ShippingAddress', 1, 1);
Pdf.AddLinkToHideField(140, 700, 90, 18,
  'ShippingName,ShippingAddress,ShippingZip', 1, 1);

Pošto akcija ne referencira spoljni resurs, ostaje kompatibilna sa PDF/A standardom. Imena koja prosleđujete su potpuno kvalifikovani nazivi polja, zbog čega se podređeno polje unutar grupe mora adresirati preko svoje pune putanje sa tačkama, a ne samo preko svog osnovnog imena lista

ImportData: popunjavanje iz FDF-a

Tamo gde akcija Hide reorganizuje ono što je već na stranici, akcija uvoza podataka unosi vrednosti spolja. ISO 32000-1 §12.6.4.8, Tabela 198, definiše je kao akciju koja popunjava AcroForm iz datoteke Forms Data Format na disku. Ovo je akcija koja stoji iza kontrola poput Ponovo učitaj uzorak podataka ili Resetuj na podrazumevane vrednosti, gde se FDF datoteka isporučuje pored PDF-a i sadrži kanonske vrednosti polja. Poziv preslikava ostale, uzimajući pravougaonik osetljiv na klik, putanju do FDF-a i bitmasku izgleda: Pdf.AddLinkToImportData(40, 660, 120, 18, 'defaults.fdf', 1). Datoteka ne mora da postoji kada se PDF pravi, ali mora biti prisutna kada korisnik klikne, a sve kose crte unazad u putanji se automatski prepisuju u kanonsku kosu crtu PDF-a

Jedno ograničenje vredi jasno navesti jer je česta tema iznenađenja. Akcija uvoza podataka ukazuje na spoljnu datoteku, tako da nije dozvoljena u PDF/A formatu. Kada je dokument u PDF/A režimu, poziv vraća nulu i ne dodaje ništa, umesto da proizvede datoteku koja ne prolazi validaciju. Ako vaš proces cilja na arhivski izlaz, popunjavanje se mora dogoditi u vreme generisanja direktnim upisivanjem vrednosti polja, a ne odlaganjem za klik

JavaScript: globalni paketi i skripte po akciji

Za logiku koja prevazilazi prikazivanje, skrivanje i uvoz, porodica akcija seže do JavaScript-a na nivou dokumenta. Postoje dva različita mesta gde skripta može da živi, i ta razlika je važna. JavaScript paket na nivou dokumenta se čuva jednom za ceo fajl i pokreće se kada se dokument otvori, što ga čini pravim mestom za definicije funkcija i deljeno stanje. Skripta po akciji je zakačena za jedan link ili polje i pokreće se samo kada se taj objekat aktivira, što je čini pravim mestom za onu jednu liniju koja poziva funkciju koju je paket već definisao

PDF Library for Delphi izlaže oba. AddGlobalJavaScript čuva imenovani paket na nivou dokumenta; ponovna upotreba imena zamenjuje sve što je bilo sačuvano pod njim. AddLinkToJavaScript dodaje skriptu pravougaoniku osetljivom na klik, tako da je klik izvršava

Dijagram PDF Library for Delphi o dvoslojnom PDF JavaScript modelu gde globalni paket nivoa dokumenta definiše recalcTotal pri otvaranju dokumenta, a svaki link nosi jednoredske skripte po akciji koje ga pozivaju na klik
Definišite recalcTotal jednom u paketu nivoa dokumenta i pustite svaku klikabilnu vrelu tačku da joj priđe jednoredočnim pozivom po akciji
// Paket na nivou dokumenta: jednom definiši funkciju za ponovnu upotrebu
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);' +
  '}');

// Skripta po radnji na linku: samo pozovi zajedničku funkciju
Pdf.AddLinkToJavaScript(40, 620, 100, 18, 'recalcTotal();', 1);

Držanje funkcije u globalnom paketu i poziva u linku nije stvar stilskih preferencija. Time se izbegava dupliranje istog tela na svakoj kontroli kojoj je potrebno, a to znači da čitač sa isključenim skriptovanjem jednostavno ne radi ništa na klik umesto da se zaguši neispravnim inline podacima. Takođe održava unose po akciji malim, što čini datoteku čitljivom kada je kasnije pregledate

Polja, podređena polja i zamrzavanje rezultata

Akcijama su potrebna polja na kojima će delovati, pa je korisno videti kako polje nastaje. NewFormField kreira polje na trenutnoj stranici i vraća njegov indeks; celobrojni tip bira vrstu, gde je 1 Text, 2 Pushbutton, 3 Checkbox, 4 Radiobutton, 5 Choice, 6 Signature i 7 Parent koji poseduje podređena polja ali sam ne crta ništa. Naziv koji prosledite ne može sadržati tačku, jer je tačka separator u potpuno kvalifikovanim imenima koja akcije koriste za adresiranje podređenih polja

Radio grupe i hijerarhijski obrasci se grade tako što se roditeljskom polju dodeljuju podređena polja. NewChildFormField dodaje podređeno polje pod imenovanim roditeljem, a za slučajeve radio dugmadi i izbora AddFormFieldSub adds the individual options and hands back a temporary index you use to position each one. Kada se interaktivna faza završi i želite da zamrznete polje tako da njegov trenutni izgled postane trajni sadržaj stranice, FlattenFormField iscrtava polje na stranici i uklanja ga iz obrasca. Nakon ravnanja, indeksi kasnijih polja se pomeraju naniže za jedan, što je jedina stvar koju treba zapamtiti ako ravna više polja u petlji

var
  Pdf: TPDFlib;
  FldShip: Integer;
begin
  Pdf := TPDFlib.Create;
  try
    Pdf.SetOrigin(1);          // top-left origin
    Pdf.SetPageSize('A4');
    Pdf.NewPage;

    // Tekstualno polje koje će radnja Hide ciljati po naslovu
    FldShip := Pdf.NewFormField('ShippingAddress', 1);
    Pdf.SetFormFieldBounds(FldShip, 40, 120, 240, 20);
    Pdf.SetFormFieldValue(FldShip, '');

    // Poveži Hide link i navigacioni link sa ovom stranicom
    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

    // Skripta na nivou dokumenta dostupna svakom događaju u datoteci
    Pdf.AddGlobalJavaScript('OnOpen',
      'app.alert("Form ready", 3);');

    // Zamrzni polje ako izlaz više ne treba da bude izmenjiv
    // Pdf.FlattenFormField(FldShip);

    if Pdf.SaveToFile('form_actions.pdf') <> 1 then
      raise Exception.Create('Save failed');
  finally
    Pdf.Free;
  end;
end;

Poziv za ravnanje je namerno komentarisan. Izostavite ga i dokument se isporučuje kao aktivan obrazac čije se akcije pokreću u čitaču. Omogućite ga i polje se renderuje u statične oznake, što je ono što želite kada se obrazac popuni i rezultat treba da putuje kao fiksni zapis. Isto polje, isti kod, dva veoma različita dokumenta u zavisnosti od toga da li ga zamrzavate

Izbor pravog glagola

Četiri akcije se jasno dele prema onome što dodiruju. Imenovana akcija pomera prozor prikaza i ne zahteva polje. Akcija Hide menja vidljivost i zahteva nazive polja, pri čemu je kodiranje u vidu teksta naspram niza rešeno umesto vas. Akcija uvoza podataka pristupa datoteci na disku i stoga je zabranjena u PDF/A formatu. JavaScript akcija pokreće proizvoljnu logiku i najbolje ju je podeliti na globalni paket funkcija i male pozive po akciji. Posegnite za najjednostavnijom koja obavlja posao: Hide akcija je portabilnija od skripte koja postavlja skrivenu zastavicu, a imenovana akcija je trajnija od sačuvane destinacije stranice jer nema broja koji treba održavati

Odavde dve srodne teme zaokružuju sliku. Ako je obrazac deo pristupačnog dokumenta, stablo strukture koje čitači ekrana prate pokriveno je u našem članku o označenom PDF-u i strukturi pristupačnosti. Kada popunjeni obrazac mora biti zaključan i potpisan, radni proces je opisan u vodiču kroz radno okruženje za usklađenost i potpisivanje. Sva tri se oslanjaju na isti mehanizam, koji se isporučuje kao PDF biblioteka za Delphi zajedno sa API-jima za kreiranje, obrasce i potpise koji su pokriveni na drugim mestima na ovom blogu