Technický článek

Vytváření polí a akcí AcroForm s HotPDF v Delphi

Akce AcroForm je slovník připojený k widgetu, který prohlížeči říká, co má udělat, když se s daným widgetem něco stane. Klikněte na tlačítko a prohlížeč přečte jeho slovník akce: akce URI otevře webovou adresu, akce JavaScript spustí skript, akce SubmitForm odešle shromážděné hodnoty polí na koncový bod, akce ResetForm je vrátí zpět na výchozí hodnoty. Akce jsou data, ne chování zapečené do souboru. ISO 32000-1 §12.6 definuje podobu slovníku; prohlížeč dodává engine, který jej interpretuje. Na tomto rozdělení záleží, protože akce dokonale zapsaná do PDF stále nedělá nic, pokud čtečka na druhé straně nemá engine, který by ji interpretoval, a hodně trápení s AcroForm pramení právě z této mezery, ne z chybně sestaveného pole

HotPDF zapisuje tyto slovníky přímo z Delphi a C++Builderu, spolu s widgety polí, na kterých visí. U každého interaktivního formuláře jsou ve hře dvě struktury: widget, který uživatel vidí na stránce, a pole plus mechanismus akcí pod ním, který nese data a propojení. Upravují se nezávisle na sobě a jedna z nich může být chybná, zatímco druhá vypadá v pořádku. Následující oddíly postupně probírají pojmenování polí, samotné akce tlačítek, JavaScript na úrovni pole a třídu závad, které přežijí vizuální kontrolu, protože žijí zcela ve druhé struktuře

Vrstva widgetů AcroForm v HotPDF mapovaná na podkladové hodnoty polí, slovník akce submit a neshodu exportní hodnoty souhlasu
Uživatelé klikají na vrstvu widgetů, zatímco hodnoty putují skrze vrstvu polí a akcí pod ní, kde zůstávají nesrovnalosti neviditelné

Názvy polí jsou směrovací klíče, ne popisky

Každé pole AcroForm nese plně kvalifikovaný název. ISO 32000-1 §12.7.3 určuje, že právě tento název, nikoli viditelný popisek, je klíčem, pod nímž hodnota pole cestuje při exportu nebo odeslání formuláře. Vývojáři přicházející z návrhu ve VCL mají sklon zacházet s názvem ovládacího prvku jako se soukromým identifikátorem v kódu, ale zde jím není. Je to přenosový formát

První, co z toho plyne, je, že dvě pole se stejným plně kvalifikovaným názvem nejsou dvě pole. PDF je považuje za dvě widgetové anotace jednoho pole, které sdílí jednu hodnotu, takže psaní do jednoho okamžitě aktualizuje i druhé. Přesně to chcete, když se jméno zákazníka musí opakovat na každé stránce smlouvy. Je to chyba, když generovací smyčka omylem znovu použije 'Field1' na třech stránkách. Žádná vizuální kontrola druhý případ nezachytí. Každá stránka si stále kreslí vlastní rámeček a propojení vyjde najevo, až když někdo začne psát

Názvy s tečkami, jako applicant.email, vytvářejí hierarchii. Nadřazený uzel applicant seskupuje své potomky, což umožňuje, aby reset nebo odeslání cílily jen na část formuláře. Pojmenovávat pole tímto způsobem od začátku nic nestojí a vyplatí se to hned při první příležitosti, kdy přijímající systém požádá jen o blok applicant

Přepínací tlačítka (radio buttons) mají vlastní pravidlo. Tlačítka, která se mají přepínat společně, musí sdílet název skupiny. V HotPDF volání AddRadioButton, která předávají stejný název skupiny, připojí své widgety k jednomu nadřazenému poli a exportní hodnota každého tlačítka ('basic' nebo 'full') identifikuje zvolenou možnost. Dáte-li každému tlačítku odlišný název, dostanete řadu nezávislých přepínačů zapnuto/vypnuto místo jedné vzájemně se vylučující skupiny, což vypadá vizuálně stejně, ale chová se chybně

Vytváření sady polí stránku po stránce

HotPDF umisťuje pole prostřednictvím metod THPDFPage, takže každé pole patří objektu stránky, který jej vytvořil. Past v pořadí volání, na kterou je třeba dát pozor, je AddPage. Ta přesměruje CurrentPage na novou stránku ve chvíli, kdy se vrátí, takže jakékoli volání pole po ní skončí na nové stránce, i když pole logicky patřilo na stránku, kterou jste právě opustili. Každou stránku, vykreslený obsah i pole dohromady, dokončete dřív, než zavoláte AddPage

procedure BuildClaimForm(Pdf: THotPDF);
begin
  // Stránka 1: blok applicant
  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 nyní ukazuje na stránku 2
  Pdf.CurrentPage.AddListBox('riders', 'None',
    ['None', 'Flood', 'Earthquake'], Rect(50, 500, 200, 600));
end;

Souřadnice používají konvenci PDF, s počátkem v levém dolním rohu stránky. Je to stejný počátek, jaký pro kreslený text používá TextOut, takže Rect(50, 100, 200, 120) leží blízko spodního okraje stránky Letter, ne u horního. VCL má Y nahoře a nechává jej růst směrem dolů, takže tabulka rozvržení přenesená beze změny vyjde vertikálně zrcadlově a každé pole skončí na špatném konci stránky. Proveďte převod jednou ve sdílené pomocné funkci místo na každém místě volání, a jediná oprava opraví celý formulář

Propojení tlačítek s akcemi URI, JavaScript a submit

Tlačítko push button je neaktivní, dokud k němu není připojena akce. HotPDF zpřístupňuje typy akcí z ISO 32000-1 §12.6.4 prostřednictvím výčtu THPDFButtonAction (baURI, baJavaScript, baSubmitURL, baResetForm, baHide, baShow, baNamed) a poskytuje dvě metody, které tlačítko vytvoří a jeho akci naváží jedním voláním

Typy akcí push button v HotPDF v Delphi: odkazy baURI, skripty baJavaScript a odesílání SubmitForm s explicitními příznaky formátu
Jedno vazební volání připojí kterýkoli ze tří slovníků akcí a jen varianta submit nese smlouvu příznaků s přijímajícím koncovým bodem
// Otevře nápovědu v systémovém prohlížeči
Pdf.CurrentPage.AddPushButtonWithAction('btnHelp', 'Help',
  'https://www.example.com/claims-help', Rect(320, 700, 420, 730), baURI);

// Spustí JavaScript na straně prohlížeče
Pdf.CurrentPage.AddPushButtonWithAction('btnRecalc', 'Recalculate',
  'app.alert("Totals updated.");', Rect(320, 660, 420, 690), baJavaScript);

// Odešle jako XFDF a ponechá prázdná pole v datech
Pdf.CurrentPage.AddPushButtonWithSubmitAction('btnSubmit', 'Submit claim',
  'https://api.example.com/claims', Rect(320, 620, 420, 650),
  [sffXFDF, sffIncludeNoValueFields]);

Příznaky pro submit si zaslouží víc pozornosti, než jim obvykle věnujeme. AddPushButtonWithSubmitAction přijímá množinu THPDFSubmitFormFlags a prázdná množina vytvoří obyčejný url-encoded POST, což je formát, který mnoho ukázkových koncových bodů přijme a mnoho produkčních koncových bodů odmítne. Přidání sffXFDF přepne payload na XFDF. sffGetMethod mění HTTP metodu. sffIncludeNoValueFields ponechá v payloadu prázdná pole, místo aby je tiše zahodilo, což je důležité ve chvíli, kdy příjemce rozlišuje mezi „nepřítomné" a „prázdné". Sada příznaků je součástí vaší rozhraní smlouvy s přijímajícím koncovým bodem, takže ji dohodněte s týmem, který odeslání parsuje, a ne až po první odmítnuté dávce

JavaScript na úrovni pole: keystroke, format, validate

Kliknutí na tlačítko není jediné místo, kde akce žijí. HotPDF také připojuje JavaScript k událostem jednotlivých polí, které prohlížeče se skriptovací podporou vyvolávají během zadávání dat uživatelem. Existují tři spouštěče a spouštějí se v různých bodech životního cyklu zadávání. Akce keystroke běží při příchodu každého znaku a znovu při potvrzení. Akce format přepíše zobrazovanou hodnotu poté, co je změna potvrzena, čistě kvůli prezentaci. Akce validate má poslední slovo — přijme, nebo odmítne potvrzenou hodnotu dřív, než se stane hodnotou pole

Životní cyklus událostí JavaScriptu na úrovni pole HotPDF od keystroke přes validate po format, s varováním před validací na straně serveru dole
Skripty keystroke a validate můžou vstup odmítnout, zatímco formát jen retušuje zobrazení; žádný skript nepřežije čtečku bez JavaScript engine
// Odmítne potvrzené hodnoty, které nejsou věrohodné e-mailové adresy
Pdf.AttachFieldKeyStrokeAction('applicant.email',
  'if (event.willCommit && !/^[\w.-]+@[\w.-]+\.\w+$/.test(event.value)) event.rc = false;');

// Zobrazí americká telefonní čísla ve formátu (NNN) NNN-NNNN
Pdf.AttachFieldFormatAction('applicant.phone',
  'event.value = event.value.replace(/(\d{3})(\d{3})(\d{4})/, "($1) $2-$3");');

// Odmítne žadatele mladší 18 let při potvrzení
Pdf.AttachFieldValidateAction('applicant.age',
  'if (parseInt(event.value) < 18) event.rc = false;');

Nastavení event.rc = false uvnitř skriptu keystroke nebo validate říká prohlížeči, aby vstup odmítl. Háček je v tom, že nic z toho neběží, pokud prohlížeč neobsahuje JavaScriptový engine. Acrobat a několik dalších desktopových produktů jej má. Většina mobilních čteček, rendererů vestavěných do prohlížečů a tiskových pipeline jej nemá a skripty bez řečí zahodí. Skripty na úrovni pole tedy zlepšují kvalitu dat jen pro tu podmnožinu uživatelů, jejichž čtečka je spustí, a nic víc nedělají. Nejsou to bezpečnostní hranice. Každá odeslaná hodnota se stále musí ověřit na serveru, jakmile tam dorazí, protože nelze předpokládat, že klient cokoli zkontroloval

Vady, které projdou vizuální kontrolou

Nejtěžší vady AcroForm k odhalení jsou ty, které žijí v datové struktuře, ne ve vykreslování, protože otevření souboru a pohled na něj vám neřekne vůbec nic. Čtyři se objevují dostatečně často na to, aby stálo za to je pojmenovat, a každá má mechanický test, který ji odhalí ještě před vydáním

  • Rozjetí exportní hodnoty. Zaškrtávací pole vytvořené jako AddCheckBox('consent', 'Yes', ...) odesílá Yes. Příjemce, který porovnává s Y, odmítne každé odeslání, ačkoli stránka vypadá dokonale. Vyplňte formulář, exportujte jej z Acrobatu jako XFDF a porovnejte hodnoty se schématem, které příjemce skutečně očekává
  • Neúmyslné zrcadlení hodnot. Dvě pole se stejným plně kvalifikovaným názvem se sloučí do jednoho. Příznak se projeví až při zadávání dat, nikdy při generování, takže správný test je do formuláře psát, ne jej vykreslit a výsledek pouze prohlédnout
  • Hodnoty comba mimo seznam možností. Když aktuální hodnota předaná do AddComboBox není žádnou z uvedených možností, prohlížeče se neshodnou na tom, zda ji zobrazit, vyprázdnit, nebo označit. Udržujte výchozí hodnotu uvnitř seznamu a neshoda zmizí
  • Pole editovatelná i po uzavření workflow. HotPDF nemá pro pole AcroForm žádné volání pro zploštění vzhledu. Podporovaný způsob, jak vyplněný formulář zamknout, je vytvořit pole s příznakem ffReadOnly, který ponechá hodnotu viditelnou prostřednictvím vlastního appearance streamu pole a zároveň odmítá úpravy. Pole zůstává živým objektem formuláře, což je přesně to, co dále v řetězci očekávají nástroje pro sestavování a podepisování

Jedno chování na straně prohlížeče stojí za regresní poznámku, i když jej žádná změna kódu neřeší. Podnikové nasazení Acrobatu může JavaScript zakázat nebo omezit cíle odesílání firemní politikou, takže akce, která fungovala v každém vývojovém buildu, může na uzamčeném zákaznickém desktopu zůstat mrtvá. Naplánujte viditelnou záložní variantu pro případ, že tlačítko nic neudělá, i kdyby tou záložní variantou byl jen tištěný pokyn, co má uživatel udělat místo toho

Kde se práce s formulářem propojuje se zbytkem dokumentu

Podpisové pole je samo o sobě typem pole AcroForm. U formuláře, který bude později certifikován nebo dodatečně podepsán, je lepší toto pole rezervovat už při generování, než jej dodatečně dolepovat, a důvody na úrovni bajtů najdete v doprovodném článku o digitálních podpisech a podepisování PAdES pomocí HotPDF. Vstupy, které přicházejí jako balíčky XFA místo nativního AcroForm, jsou jiná situace: zploštění XFA do polí AcroForm je vlastní workflow s vlastním modelem ztrát, protože obě formulářové technologie nemohou koexistovat v jednom souboru

Metody pro pole, akce a spouštěče ukázané zde jsou součástí standardního API HotPDF Delphi Component pro Delphi a C++Builder; produktová stránka odkazuje na úplnou referenci, včetně přetížení pro příznaky polí a kompletního výčtu příznaků pro submit