Odborný článok

Interaktívne PDF formuláre v Delphi: Akcie a JavaScript

Samotné pole PDF formulára je len políčko, ktoré obsahuje hodnotu. To, čo robí formulár podobným malej aplikácii, je k nemu pripojená akcia: kliknutie, ktoré skryje sekciu, načíta uložené hodnoty zo súboru, preskočí na poslednú stránku alebo spustí skript, ktorý spočíta stĺpec. Nič z toho nežije v samotnom poli. Žije to v slovníku akcií a norma ISO 32000-1 organizuje celú túto rodinu v §12.6. Tento článok prechádza akcie, po ktorých program v Delphi siaha najčastejšie, a ukazuje, ako PDF Library for Delphi prepája každú z nich s poľom alebo odkazom

Mentálny model, ktorý stojí za to si zapamätať, je, že pole a akcia sú samostatné objekty spojené odkazom. Anotácia widgetu alebo anotácia odkazu nesie akciu vo svojej položke /A. Akcia pomenúva pole, s ktorým pracuje, jeho názvom, nie indexom, takže názov, ktorý priradíte poľu, je identifikátorom, ktorý každá neskoršia akcia používa na jeho vyhľadanie. Akonáhle je toto rozdelenie jasné, API prestane vyzerať ako nesúrodá sada volaní a začne vyzerať ako jeden vzor aplikovaný na štyri druhy slovies

Diagram PDF Library for Delphi anotácie odkazu alebo widgetu PDF odkazujúcej na jeden slovník akcie, ktorý sa rozvetvuje na akčné slovesá named, hide, import-data a JavaScript, pričom karta import-data je označená ako vylúčená z PDF/A
Jeden akčný slovník za odkazom alebo widgetom živí štyri odlišné slovesá — pohyb viewportu, viditeľnosť, import z disku a skriptovanie

Pomenované akcie: navigácia bez čísla stránky

Najjednoduchšie akcie nenesú vôbec žiadne parametre. ISO 32000-1 §12.6.4.11, tabuľka 194, definuje pomenované akcie: prehliadač interpretuje symbolický názov za behu namiesto sledovania uloženého cieľa. Univerzálne podporované sú štyri názvy, a sú to presne tie, ktoré čitateľ očakáva na paneli nástrojov: NextPage, PrevPage, FirstPage a LastPage. Keďže cieľ je relatívny k tomu, ktorú stránku prehliadač práve zobrazuje, tlačidlo Ďalší vytvorené týmto spôsobom funguje na každej stránke bez toho, aby ste museli počítať cieľovú stránku

V PDF Library for Delphi je pomenovaná akcia pripojená k aktívnemu obdĺžniku (hotspotu) na aktuálnej stránke. Štvrtý a piaty celočíselný argument vyberajú sloveso a vzhľad

// NamedActionType: 0 = NextPage, 1 = PrevPage, 2 = FirstPage, 3 = LastPage
// Bit 0 v Options (hodnota 1) vykresľuje okraj okolo aktívnej zóny
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čte na poslednú stranu

Neexistuje žiadny cieľ, ktorý by bolo potrebné synchronizovať, čo je hlavným zmyslom. Pomenovaná akcia prežije vloženie aj vymazanie stránky, pretože na prvom mieste nikdy nepomenúva konkrétnu stránku. Porovnajte to s explicitným odkazom prechodu (go-to link), ktorý ukladá index cieľovej stránky, ktorý musíte prečíslovať v momente, keď sa dokument rozrastie

Akcia Hide a jej chyták s polom

Akcia Hide, ISO 32000-1 §12.6.4.10, tabuľka 196, prepína viditeľnosť jedného alebo viacerých polí. Je to najčistejší spôsob, ako vytvoriť správanie zobrazenia a skrytia bez skriptovania, a je to presne to, čo chcete pre odkaz typu Zobraziť podrobnosti alebo pre dva navzájom sa vylučujúce panely, kde odhalenie jedného skryje druhý. Akcia nesie cieľ vo svojej položke /T a boolovskú hodnotu /H, ktorá určuje smer: skryť pri hodnote true, zobraziť pri false

Jemnosť spočíva výhradne v tom, ako je tento cieľ zakódovaný, a je to presne ten typ detailu, ktorý spôsobí, že formulár funguje na vašom počítači, ale zlyhá u zákazníka. Keď akcia pomenúva jediné pole, /T sa zapíše ako jeden textový reťazec. Keď ich pomenúva viacero, /T sa zapíše ako pole textových reťazcov. Staršie prehliadače nezaobchádzajú s jednoprvkovým polom rovnako ako s holým reťazcom, takže kódovanie sa musí vetviť podľa počtu: jeden názov must byť emitovaný ako reťazec, nie ako pole dĺžky jedna, ak ho má rešpektovať čo najširší okruh čítačiek. PDF Library for Delphi robí toto rozhodnutie za vás. Názvy polí odovzdávate oddelené čiarkami, bodkočiarkami alebo zlomom riadku a zapisovač vygeneruje jeden reťazec pre jeden názov a pole pre dva alebo viac názvov

Diagram PDF Library for Delphi pravidiel kódovania akcie Hide PDF, kde práve jedno meno poľa sa stane textovým reťazcom /T, zatiaľ čo dve alebo viac mien sa stane poľom reťazcov, plus poznámky o smere príznaku hide, bezpečnosti PDF/A a plne kvalifikovaných názvoch polí
Zapisovač sa vetví podľa počtu polí, takže osamotený názov odchádza ako holý reťazec, ktorý rešpektujú staršie prehliadače, zatiaľ čo dva a viac sa stáva poľom prepínaným spolu
// Nenulový HideFlag skryje uvedené polia (/H true); nula ich zobrazí
// Jedno meno -> /T je textový reťazec. Dve alebo viac -> /T je pole reťazcov
Pdf.AddLinkToHideField(40, 700, 90, 18, 'ShippingAddress', 1, 1);
Pdf.AddLinkToHideField(140, 700, 90, 18,
  'ShippingName,ShippingAddress,ShippingZip', 1, 1);

Keďže akcia neodkazuje na žiadny externý zdroj, zostáva kompatibilná s PDF/A. Názvy, ktoré odovzdávate, sú plne kvalifikované názvy polí, a preto musí byť dcérske pole v skupine adresované cez svoju úplnú bodkovanú cestu, a nie len cez svoj samostatný názov listu

ImportData: predvyplnenie z FDF

Zatiaľ čo akcia Hide mení usporiadanie toho, čo už na stránke je, akcia importu dát prináša hodnoty zvonku. ISO 32000-1 §12.6.4.8, tabuľka 198, ju definuje ako akciu, ktorá vypĺňa AcroForm zo súboru formátu FDF (Forms Data Format) na disku. Toto je akcia za ovládacími prvkami typu Znova načítať vzorové dáta alebo Obnoviť predvolené nastavenia, kde sa súbor FDF dodáva vedľa PDF a obsahuje kanonické hodnoty polí. Volanie zrkadlí ostatné, berie aktívny obdĺžnik, cestu k FDF a masku vzhľadu: Pdf.AddLinkToImportData(40, 660, 120, 18, 'defaults.fdf', 1). Súbor nemusí existovať pri vytváraní PDF, ale musí byť prítomný, keď používateľ klikne, a všetky spätné lomky v ceste sa automaticky prepíšu do kanonického tvaru lomky PDF

Jedno obmedzenie stojí za to uviesť jasne, pretože je často prekvapením. Akcia importu dát ukazuje na externý súbor, takže v PDF/A nie je povolená. Keď je dokument v režime PDF/A, volanie vráti nulu a nepridá nič, namiesto toho, aby vytvorilo súbor, ktorý zlyhá pri validácii. Ak vaša spracovateľská reťaz mieri na archívny výstup, predvyplnenie sa musí uskutočniť v čase generovania priamym zápisom hodnôt polí, nie odložením na kliknutie

JavaScript: globálne balíky a skripty pre jednotlivé akcie

Pre logiku, ktorá presahuje zobrazovanie, skrývanie a import, rodina akcií siaha po JavaScripte na úrovni dokumentu. Existujú dve odlišné miesta, kde môže skript žiť, a na tomto rozdiele záleží. Globálny JavaScriptový balík na úrovni dokumentu sa ukladá raz pre celý súbor a spúšťa sa pri otvorení dokumentu, vďaka čomu je správnym domovom pre definície funkcií a zdieľaný stav. Skript pre konkrétnu akciu je pripojený k jednému odkazu alebo polu a spúšťa sa len vtedy, keď je tento objekt aktivovaný, čo z neho robí správne miesto pre ten jeden riadok, ktorý volá funkciu, ktorú balík už definoval

PDF Library for Delphi sprístupňuje oboje. AddGlobalJavaScript ukladá pomenovaný balík na úrovni dokumentu; opätovné použitie názvu nahradí všetko, čo bolo pod ním uložené. AddLinkToJavaScript pripojí skript k hotspotu, takže kliknutie ho spustí

Diagram PDF Library for Delphi dvojvrstvového modelu JavaScript PDF, kde globálny paket na úrovni dokumentu definuje recalcTotal pri otvorení dokumentu a každý odkaz nesie jednoriadkový skript na akciu, ktorý ho zavolá pri kliknutí
Definujte recalcTotal raz v balíku na úrovni dokumentu a nechajte každú klikateľnú horúcu škvrnu dosiahnuť ju jednoriadkovým volaním na akciu
// Balík na úrovni dokumentu: raz definujte opakovane použiteľnú funkciu
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 pre akciu na odkaze: stačí zavolať zdieľanú funkciu
Pdf.AddLinkToJavaScript(40, 620, 100, 18, 'recalcTotal();', 1);

Uchovávanie funkcie v globálnom balíku a volania v odkaze nie je otázkou štýlu. Zabraňuje to duplikovaniu rovnakého tela na každom ovládacom prvku, ktorý ho potrebuje, a znamená to, že prehliadač s vypnutým skriptovaním pri kliknutí jednoducho neurobí nič, namiesto toho, aby zlyhal na nesprávne naformátovanom inline objekte. Tiež to udržuje položky jednotlivých akcií malé, čo zachováva čitateľnosť súboru pri jeho neskoršom skúmaní

Polia, dcérske polia a zmrazenie výsledku

Akcie potrebujú polia, na ktoré môžu pôsobiť, preto pomôže vidieť, ako pole vzniká. NewFormField vytvorí pole na aktuálnej stránke a vráti jeho index; celočíselný typ vyberá druh, kde 1 je Text, 2 je Pushbutton, 3 je Checkbox, 4 je Radiobutton, 5 je Choice, 6 je Signature a 7 je Parent, ktorý vlastní dcérske prvky, ale sám nič nevykresľuje. Názov, ktorý odovzdáte, nemôže obsahovať bodku, pretože bodka je oddeľovačom v plne kvalifikovaných názvoch, ktoré akcie používajú na adresovanie dcérskych prvkov

Rádiové skupiny a hierarchické formuláre sa vytvárajú tak, že sa rodičovskému polu priradia dcérske polia. NewChildFormField pridá dcérske pole pod pomenovaného rodiča a v prípade prepínačov a výberov AddFormFieldSub pridá jednotlivé možnosti a vráti dočasný index, ktorý použijete na umiestnenie každej z nich. Keď sa interaktívna fáza skončí a vy chcete pole zmraziť, aby sa jeho aktuálny vzhľad stal trvalým obsahom stránky, FlattenFormField vykreslí pole na stránku a odstráni ho z formulára. Po sploštení (flatten) sa indexy neskorších polí posunú o jeden nadol, čo je jediná vec, na ktorú treba pamätať, ak splošťujete niekoľko polí v cykle

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

    // Textové pole, na ktoré akcia Hide zacieli podľa jeho názvu
    FldShip := Pdf.NewFormField('ShippingAddress', 1);
    Pdf.SetFormFieldBounds(FldShip, 40, 120, 240, 20);
    Pdf.SetFormFieldValue(FldShip, '');

    // Pripojte odkaz Hide a navigačný odkaz na túto stranu
    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 úrovni dokumentu dostupný pre každú udalosť v súbore
    Pdf.AddGlobalJavaScript('OnOpen',
      'app.alert("Form ready", 3);');

    // Zmrazte pole, ak výstup už nemá byť upraviteľný
    // Pdf.FlattenFormField(FldShip);

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

Volanie flatten je zakomentované úmyselne. Ak ho vynecháte, dokument sa odošle ako živý formulár, ktorého akcie sa spúšťajú v čítačke. Ak ho povolíte, pole sa vykreslí do statických značiek, čo je presne to, čo chcete, keď bol formulár dokončený a výsledok by mal cestovať ako pevný záznam. Rovnaké pole, rovnaký kód, dva veľmi odlišné dokumenty v závislosti od toho, či ho zmrazíte

Výber správneho slovesa

Štyri akcie sa jasne delia podľa toho, čoho sa týkajú. Pomenovaná akcia posúva výrez zobrazenia a nepotrebuje žiadne pole. Akcia Hide mení viditeľnosť a vyžaduje názvy polí, pričom kódovanie reťazca versus poľa sa rieši za vás. Akcia importu dát pristupuje k súboru na disku, a preto je v PDF/A zakázaná. Akcia JavaScriptu spúšťa ľubovoľnú logiku a najlepšie je rozdeliť ju na globálny balík funkcií a malé volania pre jednotlivé akcie. Siahnite po najjednoduchšej z nich, ktorá splní úlohu: akcia Hide je prenosnejšia ako skript, ktorý nastavuje príznak skrytia, a pomenovaná akcia je odolnejšia ako uložený cieľ stránky, pretože netreba udržiavať žiadne číslo

Odtiaľto obraz dopĺňajú dve susediace témy. Ak je formulár súčasťou prístupného dokumentu, strom štruktúry, ktorým prechádzajú čítačky obrazovky, je popísaný v našom články o tagovanom PDF a štruktúre prístupnosti. Keď musí byť dokončený formulár uzamknutý a podpísaný, pracovný postup je opísaný v sprievodcovi pracovným priestorom pre zhodu a podpisovanie. Všetky tri stavajú na rovnakom engine, ktorý sa dodáva ako PDF knižnica pre Delphi spolu s rozhraniami API pre vytváranie, formuláre a podpisy, ktoré sú popísané inde na tomto blogu