Technical Article

Vytváranie polí a akcií AcroForm pomocou HotPDF v Delphi

Akcia AcroForm je slovník (dictionary) priradený k prvku (widgetu), ktorý prehliadaču hovorí, čo má urobiť, keď s daným prvkom používateľ interaguje. Kliknite na tlačidlo a prehliadač prečíta jeho slovník akcií: akcia URI otvorí webovú adresu, akcia JavaScript spustí skript, akcia SubmitForm odošle zhromaždené hodnoty polí na koncový bod a akcia ResetForm ich vymaže a vráti na predvolené hodnoty. Akcia predstavuje dáta, nie správanie pevne zabudované do súboru. Norma ISO 32000-1 §12.6 definuje štruktúru tohto slovníka; prehliadač poskytuje engine, ktorý ho interpretuje. Toto rozdelenie je dôležité, pretože aj dokonale zapísaná akcia v PDF neurobí nič, ak čítačka na druhej strane pre ňu nemá podporu. Mnoho problémov s formulármi AcroForm pramení práve z tejto medzery a nie z nesprávne sformovaných polí.

Knižnica HotPDF zapisuje tieto slovníky priamo z Delphi a C++Builderu spolu s prvkami polí, ku ktorým patria. Pri každom interaktívnom formulári sú v hre dve štruktúry: vizuálny prvok (widget), ktorý používateľ vidí na stránke, a mechanizmus poľa s akciou pod ním, ktorý nesie dáta a prepojenia. Upravujú sa nezávisle a jedna z nich môže byť chybná, zatiaľ čo druhá vyzerá v poriadku. Nasledujúce časti sa venujú pomenovaniu polí, samotným akciám tlačidiel, JavaScriptu na úrovni polí a kategórii chýb, ktoré prejdú vizuálnou kontrolou, pretože žijú výhradne v druhej menovanej štruktúre.

Názvy polí sú smerovacie kľúče, nie popisy

Každé pole AcroForm má svoj plne kvalifikovaný názov. Norma ISO 32000-1 §12.7.3 určuje, že tento názov, a nie viditeľný popis (caption), je kľúčom, pod ktorým sa hodnota poľa prenáša pri exporte alebo odoslaní formulára. Vývojári zvyknutí na návrh VCL majú tendenciu považovať názov ovládacieho prvku za súkromný identifikátor kódu, no tu to tak nie je. Predstavuje formát sieťového prenosu.

Z toho v prvom rade vyplýva, že dve polia s rovnakým plne kvalifikovaným názvom nie sú dve samostatné polia. PDF ich vníma ako dve anotácie widgetu toho istého poľa, ktoré zdieľajú spoločnú hodnotu, takže písanie do jedného okamžite aktualizuje druhé. To je presne to, čo chcete, keď sa má meno zákazníka opakovať na každej strane zmluvy. Ide však o chybu, ak cyklus generovania omylom znova použije názov 'Field1' na troch rôznych stranách. Žiadna vizuálna kontrola takýto prípad neodhalí. Každá stránka stále vykresľuje svoje vlastné textové pole a prepojenie sa prejaví, až keď niekto začne písať.

Názvy oddelené bodkou, napríklad applicant.email, vytvárajú hierarchiu. Nadradený uzol applicant zoskupuje svojich potomkov, čo umožňuje resetovať alebo odoslať iba časť formulára. Pomenovanie polí týmto spôsobom od začiatku nič nestojí a vráti sa vám to hneď, ako prijímací systém požiada iba o blok údajov žiadateľa (applicant).

Prepínače (radio buttons) majú vlastné pravidlo. Tlačidlá, ktoré majú fungovať spoločne v rámci jednej voľby, musia zdieľať rovnaký názov skupiny. V knižnici HotPDF volania AddRadioButton, ktoré odovzdávajú rovnaký názov skupiny, pripoja svoje widgety k jednému spoločnému rodičovskému poľu, pričom exportovaná hodnota každého tlačidla (napr. 'basic' alebo 'full') identifikuje zvolenú možnosť. Ak dáte každému tlačidlu iný názov, získate rad nezávislých prepínačov zapnuté/vypnuté namiesto jednej skupiny s výhradným výberom, čo vizuálne vyzerá rovnako, ale správa sa úplne zle.

Vytváranie sady polí stránku po stránke

Knižnica HotPDF umiestňuje polia pomocou metód triedy THPDFPage, takže každé pole patrí objektu stránky, ktorý ho vytvoril. Poradovou pascou, na ktorou si treba dať pozor, je metóda AddPage. Tá okamžite po návrate presmeruje vlastnosť CurrentPage na novú stránku, takže akékoľvek volanie poľa po tomto okamihu skončí na novej stránke, aj keď logicky patrilo na predchádzajúcu stránku. Pred zavolaním AddPage dokončite celú stránku vrátane vykresleného obsahu aj všetkých jej polí.

procedure BuildClaimForm(Pdf: THotPDF);
begin
  // Page 1: applicant block
  Pdf.CurrentPage.AddTextField('applicant.name', '', Rect(50, 700, 300, 722));
  Pdf.CurrentPage.AddTextField('applicant.email', '', Rect(50, 660, 300, 682));
  Pdf.CurrentPage.AddCheckBox('consent', 'Y', Rect(50, 620, 70, 640), False);
  Pdf.CurrentPage.AddRadioButton('coverage', 'basic', Rect(50, 580, 70, 600), True);
  Pdf.CurrentPage.AddRadioButton('coverage', 'full', Rect(90, 580, 110, 600), False);
  Pdf.CurrentPage.AddComboBox('plan', 'Standard',
    ['Basic', 'Standard', 'Premium'], Rect(50, 540, 200, 565));

  Pdf.AddPage;  // CurrentPage now points at page 2
  Pdf.CurrentPage.AddListBox('riders', 'None',
    ['None', 'Flood', 'Earthquake'], Rect(50, 500, 200, 600));
end;

Súradnice používajú konvenciu formátu PDF, kde počiatok súradnicovej sústavy leží v ľavom dolnom rohu stránky. Ide o rovnaký počiatok, aký používa metóda TextOut pre vykreslený text, takže Rect(50, 100, 200, 120) sa nachádza v spodnej časti stránky, nie v hornej. VCL umiestňuje súradnicu Y nahor a zväčšuje ju smerom nadol, takže tabuľka rozloženia prenesená priamo z VCL sa prejaví vertikálne prevrátená a každé pole skončí na nesprávnom konci stránky. Vykonajte túto konverziu raz v spoločnej pomocnej funkcii namiesto toho, aby ste ju robili pri každom volaní, a jedna oprava upraví celý formulár.

Prepojenie tlačidiel s akciami URI, JavaScript a Submit

Obyčajné tlačidlo (push button) je neaktívne, kým k nemu nepriradíte akciu. HotPDF sprístupňuje typy akcií z normy ISO 32000-1 §12.6.4 prostredníctvom enumerácie THPDFButtonAction (baURI, baJavaScript, baSubmitURL, baResetForm, baHide, baShow, baNamed) a poskytuje dve metódy, ktoré vytvoria tlačidlo a priradia mu akciu v jednom volaní.

// Open a help page in the system browser
Pdf.CurrentPage.AddPushButtonWithAction('btnHelp', 'Help',
  'https://www.example.com/claims-help', Rect(320, 700, 420, 730), baURI);

// Run viewer-side JavaScript
Pdf.CurrentPage.AddPushButtonWithAction('btnRecalc', 'Recalculate',
  'app.alert("Totals updated.");', Rect(320, 660, 420, 690), baJavaScript);

// Submit as XFDF and keep empty fields in the payload
Pdf.CurrentPage.AddPushButtonWithSubmitAction('btnSubmit', 'Submit claim',
  'https://api.example.com/claims', Rect(320, 620, 420, 650),
  [sffXFDF, sffIncludeNoValueFields]);

Príznaky odoslania si vyžadujú viac pozornosti, než sa im zvyčajne venuje. Metóda AddPushButtonWithSubmitAction prijíma množinu THPDFSubmitFormFlags. Prázdna množina vygeneruje obyčajný POST požiadavku kódovanú ako URL, čo je síce formát, ktorý prijímajú mnohé testovacie koncové body, ale mnohé produkčné systémy ho odmietajú. Pridanie sffXFDF prepne prenášaný obsah na formát XFDF. Príznak sffGetMethod zmení HTTP metódu. Príznak sffIncludeNoValueFields ponechá v odosielaných dátach aj prázdne polia namiesto ich tichého vynechania, čo je kľúčové v momente, keď prijímajúci systém rozlišuje medzi "chýbajúcim" a "prázdnym" poľom. Množina príznakov je súčasťou kontraktu rozhrania s prijímajúcim koncovým bodom, preto ju dohodnite s tímom, ktorý spracúva odoslané formuláre, a nie až po prvom odmietnutom balíku dát.

JavaScript na úrovni polí: stlačenie klávesu, formát, validácia

Kliknutia na tlačidlá nie sú jediným miestom, kde žijú akcie. HotPDF umožňuje pripojiť JavaScript k udalostiam jednotlivých polí, ktoré spúšťajú prehliadače podporujúce skripty počas zadávania údajov používateľom. K dispozícii sú tri spúšťače, ktoré sa spúšťajú v rôznych fázach životného cyklu vstupu. Akcia stlačenia klávesu (keystroke) sa spúšťa pri príchode každého znaku a znova pri potvrdení. Akcia formátu (format) prepíše zobrazenú hodnotu po potvrdení zmeny, čisto na účely prezentácie. Akcia validácie (validate) má posledné slovo – prijíma alebo odmieta potvrdenú hodnotu predtým, ako sa stane skutočnou hodnotou poľa.

// Reject committed values that are not plausible email addresses
Pdf.AttachFieldKeyStrokeAction('applicant.email',
  'if (event.willCommit && !/^[\w.-]+@[\w.-]+\.\w+$/.test(event.value)) event.rc = false;');

// Display US phone numbers as (NNN) NNN-NNNN
Pdf.AttachFieldFormatAction('applicant.phone',
  'event.value = event.value.replace(/(\d{3})(\d{3})(\d{4})/, "($1) $2-$3");');

// Refuse applicants under 18 at commit time
Pdf.AttachFieldValidateAction('applicant.age',
  'if (parseInt(event.value) < 18) event.rc = false;');

Nastavenie event.rc = false vo vnútri skriptu keystroke alebo validate povie prehliadaču, aby vstup odmietol. Háčik je v tom, že nič z toho nefunguje, ak prehliadač neobsahuje JavaScript engine. Acrobat a niekoľko desktopových produktov ho majú. Väčšina mobilných čítačiek, prehliadačov s integrovaným vykresľovaním a tlačových systémov ho však nemá a tieto skripty ticho ignorujú. Skripty formulárov teda zlepšujú kvalitu údajov iba pre časť používateľov, ktorých čítačka ich spúšťa. Netvoria bezpečnostnú bariéru. Každú odoslanú hodnotu je stále potrebné po prijatí overiť na serveri, pretože nemôžete predpokladať, že klient čokoľvek skontroloval.

Chyby, ktoré prejdú vizuálnou kontrolou

Najťažšie odhaliteľné chyby formulárov AcroForm sú tie, ktoré žijú v dátovej štruktúre, a nie vo vizuálnom vykreslení. Otvorenie a pohľad na súbor vám nič nepovie. Nasledujúce štyri sa vyskytujú dostatočne často na to, aby sme ich pomenovali, a pre každú existuje mechanický test na jej odhalenie pred vydaním.

  • Odchýlka exportovanej hodnoty. Začiarkavacie políčko vytvorené ako AddCheckBox('consent', 'Yes', ...) odosiela hodnotu Yes. Ak však prijímajúci systém očakáva Y, odmietne každé odoslanie, hoci vizuálne stránka vyzerá dokonale. Vyplňte formulár, exportujte ho ako XFDF z programu Acrobat a porovnajte hodnoty so schémou, ktorú prijímateľ skutočne očakáva.
  • Nechcené zrkadlenie hodnôt. Dve polia, ktoré zdieľajú rovnaký plne kvalifikovaný názov, sa zlúčia do jedného. Tento príznak sa prejaví až pri zadávaní údajov a nikdy nie pri generovaní, takže test spočíva v písaní do formulára, a nie v jeho vizuálnom prezeraní.
  • Predvolená hodnota výberového poľa mimo zoznamu možností. Keď aktuálna hodnota odovzdaná metóde AddComboBox nie je jednou z uvedených možností v zozname, prehliadače sa nezhodujú v tom, či ju zobraziť, nechať pole prázdne alebo nahlásiť chybu. Udržujte predvolenú hodnotu v rámci zoznamu možností a nezhody zmiznú.
  • Polia sú po uzavretí procesu stále upraviteľné. HotPDF neobsahuje funkciu sploštenia (flattening) formulárov AcroForm. Podporovaným spôsobom na uzamknutie vyplneného formulára je vytvorenie polí s príznakom ffReadOnly. Ten ponechá hodnotu viditeľnú cez vlastný prúd vzhľadu poľa (appearance stream), no zakáže jej úpravy. Pole zostáva živým objektom formulára, čo očakávajú následné nástroje na spájanie a podpisovanie dokumentov.

Jedno správanie na strane prehliadača stojí za zmienku v súvislosti s možnou regresiou, hoci ho nerieši žiadna zmena v kóde. Firemné inštalácie programu Acrobat môžu z bezpečnostných dôvodov zakázať JavaScript alebo obmedziť ciele odosielania formulárov, takže akcia, ktorá fungovala počas vývoja, môže na zablokovanom počítači zákazníka zostane neaktívna. Naplánujte viditeľné náhradné riešenie (fallback) pre prípad, že tlačidlo neurobí nič, aj keby malo ísť len o vytlačený návod, čo má používateľ urobiť namiesto toho.

Prepojenie práce s formulármi na zvyšok dokumentu

Podpisové pole je samo o sebe typom poľa AcroForm. Formulár, ktorý sa bude neskôr certifikovať alebo podpisovať, je lepšie pripraviť s vyhradeným podpisovým poľom už počas generovania, než ho tam dopĺňať dodatočne. Technické detaily na úrovni bajtov nájdete v sprievodnom článku o digitálnych podpisoch a PAdES podpisovaní pomocou HotPDF. Vstupy, ktoré prichádzajú ako balíky XFA namiesto natívneho AcroForm, sú odlišným prípadom: sploštenie XFA do polí AcroForm je samostatný pracovný postup s vlastnými obmedzeniami, pretože tieto dve technológie formulárov nemôžu v jednom súbore koexistovať.

Metódy pre polia, akcie a spúšťače uvedené v tomto článku sú súčasťou štandardného API komponentu HotPDF pre Delphi a C++Builder. Stránka produktu odkazuje na kompletnú referenčnú príručku vrátane preťažených metód pre príznaky polí a úplného zoznamu príznakov odosielania.