Technical Article

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 PDFlibPas 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.

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

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

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.

PDFlibPas 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.

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

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