Samotné pole PDF formuláře je jen políčko pro hodnotu. To, co z formuláře dělá malou aplikaci, je akce k němu připojená: kliknutí, které skryje sekci, načte uložené hodnoty ze souboru, skočí na poslední stránku nebo spustí skript sčítající sloupec. Nic z toho nežije v samotném poli. Žije to v adresáři akcí (action dictionary) a ISO 32000-1 organizuje celou tuto rodinu v §12.6. Tento článek provází akcemi, po kterých program v Delphi sahá nejčastěji, a ukazuje, jak je PDFlibPas propojuje s polem nebo odkazem.
Důležitý mentální model spočívá v tom, že pole a akce jsou samostatné objekty spojené referencí. Anotace widgetu nebo anotace odkazu nese akci ve svém záznamu /A. Akce odkazuje na pole, se kterým pracuje, jeho názvem (title), nikoli indexem, takže název, který poli dáte, je identifikátorem, který každá pozdější akce používá k jeho vyhledání. Jakmile je toto rozdělení jasné, rozhraní API přestane vypadat jako náhodná směs volání a začne vypadat jako jeden vzorec aplikovaný na čtyři druhy operací.
Pojmenované akce: navigace bez čísla stránky
Nejjednodušší akce nenesou vůbec žádné parametry. ISO 32000-1 §12.6.4.11, tabulka 194, definuje pojmenované akce (named actions): prohlížeč interpretuje symbolický název za běhu namísto následování uloženého cíle. Univerzálně podporovány jsou čtyři názvy, což jsou přesně ty, které čtenář očekává na panelu nástrojů: NextPage, PrevPage, FirstPage a LastPage. Protože cíl je relativní k jakékoli stránce, kterou prohlížeč právě zobrazuje, tlačítko Další vytvořené tímto způsobem funguje na každé stránce, aniž byste museli počítat cíl.
V PDFlibPas je pojmenovaná akce připojena k aktivnímu obdélníku (hotspot rectangle) na aktuální stránce. Čtvrtý a pátý celočíselný argument vybírají operaci a vzhled.
// 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
Není zde žádný cíl, který by bylo nutné udržovat synchronizovaný, což je hlavní výhoda. Pojmenovaná akce přežije vložení i smazání stránky, protože na konkrétní stránku vůbec neodkazuje. Porovnejte to s explicitním odkazem typu go-to, který ukládá index cílové stránky, jejž musíte přečíslovat v momentě, kdy se dokument rozroste.
Akce Hide a záludnost s polem
Akce Hide, ISO 32000-1 §12.6.4.10, tabulka 196, přepíná viditelnost jednoho nebo více polí. Je to nejčistší způsob, jak vytvořit chování pro zobrazení a skrytí bez skriptování, což je přesně to, co chcete pro odkaz Zobrazit podrobnosti nebo pro dva vzájemně se vylučující panely, kde odhalení jednoho skryje druhý. Akce nese cíl ve své položce /T a boolean /H, který určuje směr: skrýt při hodnotě true, zobrazit při false.
Záludnost spočívá výhradně v tom, jak je tento cíl kódován, což je přesně ten detail, který způsobí, že formulář funguje na vašem počítači, ale selže u zákazníka. Když akce pojmenovává jediné pole, /T se zapíše jako jeden textový řetězec. Pokud jich pojmenovává několik, /T se zapíše jako pole textových řetězců. Starší prohlížeče nezacházejí s jednoprvkovým polem stejně jako s prostým řetězcem, takže kódování se musí větvit podle počtu: jediné jméno musí být zapsáno jako řetězec, nikoli jako pole o délce jedna, pokud jej má respektovat co nejširší škála čteček. PDFlibPas toto rozhodnutí dělá za vás. Předáte názvy polí oddělené čárkami, středníky nebo zalomením řádků a zapisovač vygeneruje buď jeden řetězec pro jedno jméno, nebo pole pro dvě a více jmen.
// 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);
Vzhledem k tomu, že akce neodkazuje na žádný externí zdroj, zůstává kompatibilní s PDF/A. Názvy, které předáváte, jsou plně kvalifikované názvy polí, což je důvod, proč podřízené pole v rámci skupiny musí být adresováno svou plnou tečkovanou cestou, nikoli pouze svým vlastním názvem.
ImportData: předvyplnění z FDF
Zatímco akce Hide mění uspořádání toho, co již na stránce je, akce import-data přináší hodnoty zvenčí. ISO 32000-1 §12.6.4.8, tabulka 198, ji definuje jako akci, která naplní AcroForm ze souboru Forms Data Format (FDF) na disku. Jedná se o akci za ovládacími prvky jako Znovu načíst ukázková data nebo Obnovit výchozí, kdy se soubor FDF dodává vedle PDF a obsahuje kanonické hodnoty polí. Volání zrcadlí ostatní, přičemž přebírá aktivní obdélník, cestu k FDF a masku vzhledu: Pdf.AddLinkToImportData(40, 660, 120, 18, 'defaults.fdf', 1). Soubor nemusí existovat při sestavování PDF, ale musí být přítomen, když uživatel klikne, a případná zpětná lomítka v cestě jsou automaticky přepsána na kanonická lomítka PDF.
Jedno omezení stojí za to uvést na rovinu, protože bývá častým překvapením. Akce import-data ukazuje na externí soubor, takže v PDF/A není povolena. Když je dokument v režimu PDF/A, volání vrátí nulu a nic nepřidá, namísto vytvoření souboru, který by selhal při validaci. Pokud vaše pipeline cílí na archivní výstup, předvyplnění musí proběhnout v době generování přímým zápisem hodnot polí, nikoli odkládáním na kliknutí.
JavaScript: globální balíčky a skripty pro jednotlivé akce
Pro logiku, která přesahuje zobrazení, skrytí a import, sahá rodina akcí k JavaScriptu na úrovni dokumentu. Existují dvě odlišná místa, kde může skript žít, a na rozdílu záleží. Globální JavaScriptový balíček na úrovni dokumentu je uložen jednou pro celý soubor a spouští se při otevření dokumentu, což z něj dělá správné místo pro definice funkcí a sdílený stav. Skript pro konkrétní akci je připojen k jednomu odkazu nebo poli a spouští se pouze při aktivaci daného objektu, což z něj činí ideální domov pro řádek, který volá funkci, kterou balíček již definoval.
PDFlibPas zpřístupňuje obojí. AddGlobalJavaScript ukládá pojmenovaný balíček na úrovni dokumentu; opětovné použití názvu nahradí to, co pod ním bylo uloženo. AddLinkToJavaScript připojí skript k aktivní oblasti, takže jej kliknutí spustí.
// 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);
Ponechání funkce v globálním balíčku a volání v odkazu není otázkou stylu. Zabraňuje to duplikování stejného kódu u každého ovládacího prvku, který jej potřebuje, a znamená to, že prohlížeč s vypnutým skriptováním při kliknutí jednoduše neudělá nic, místo aby selhal na nesprávně formátovaném inline kódu. Také to udržuje položky jednotlivých akcí malé, což zachovává čitelnost souboru při jeho pozdější kontrole.
Pole, podřízená pole a zmrazení výsledku
Akce potřebují pole, na která mohou působit, takže pomůže vidět, jak pole vzniká. NewFormField vytvoří pole na aktuální stránce a vrátí jeho index; celočíselný typ určuje druh, kde 1 je Text, 2 je Pushbutton, 3 is Checkbox, 4 is Radiobutton, 5 is Choice, 6 is Signature a 7 is Parent (rodič), který vlastní podřízená pole, ale sám nic nekreslí. Název, který předáváte, nesmí obsahovat tečku, protože tečka slouží jako oddělovač v plně kvalifikovaných názvech, které akce používají k adresování podřízených prvků.
Skupiny přepínačů (radio groups) a hierarchické formuláře se sestavují tak, že rodičovskému poli přidělíte podřízená. NewChildFormField přidá podřízené pole pod pojmenovaného rodiče a v případě přepínačů a výběrů AddFormFieldSub přidá jednotlivé možnosti a vrátí dočasný index, který použijete k umístění každé z nich. Když je interaktivní fáze u konce a vy chcete pole zmrazit, aby se jeho aktuální vzhled stal trvalým obsahem stránky, FlattenFormField vykreslí pole na stránku a odebere jej z formuláře. Po zploštění se indexy následujících polí posunou o jeden dolů, což je jediná věc, kterou je třeba mít na paměti, pokud zplošťujete několik polí v cyklu.
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;
Volání flatten je záměrně zakomentováno. Pokud jej vynecháte, dokument se odešle jako živý formulář, jehož akce se spouštějí ve čtečce. Pokud jej povolíte, pole se vykreslí do statických prvků, což je to, co chcete, když byl formulář vyplněn a výsledek by měl putovat jako neměnný záznam. Stejné pole, stejný kód, dva velmi odlišné dokumenty v závislosti na tom, zda jej zmrazíte.
Výběr správné operace
Tyto čtyři akce se jasně dělí podle toho, s čím pracují. Pojmenovaná akce posouvá zobrazení a nepotřebuje žádné pole. Akce Hide mění viditelnost a vyžaduje názvy polí, přičemž kódování řetězce versus pole je vyřešeno za vás. Akce import-data přistupuje k souboru na disku, a proto je v PDF/A zakázána. Akce JavaScriptu spouští libovolnou logiku a je nejlepší ji rozdělit mezi globální balíček funkcí a malá volání pro jednotlivé akce. Zvolte tu nejjednodušší, která splní daný úkol: akce Hide je přenosnější než skript nastavující příznak skrytí a pojmenovaná akce je odolnější než uložený cíl stránky, protože není třeba udržovat žádné číslo.
Na toto téma navazují dvě oblasti, které doplňují celkový obraz. Pokud je formulář součástí přístupného dokumentu, strom struktury, který procházejí čtečky obrazovky, je popsán v našem článku o tagovaném PDF a struktuře přístupnosti. Pokud je nutné dokončený formulář uzamknout a podepsat, pracovní postup je popsán v našem průvodci pro shodu a podpisový workbench. Všechny tři staví na stejném enginu, který je dodáván jako PDF knihovna pro Delphi spolu s rozhraními API pro vytváření, formuláře a podpisy, o nichž se píše jinde na tomto blogu.