Een PDF-formulierveld op zichzelf is slechts een vak dat een waarde bevat. Wat ervoor zorgt dat een formulier zich gedraagt als een kleine applicatie is de actie die eraan gekoppeld is: een klik die een sectie verbergt, opgeslagen waarden uit een bestand ophaalt, naar de laatste pagina springt of een script uitvoert dat een kolom optelt. Niets daarvan bevindt zich in het veld. Het bevindt zich in een actiemenu (action dictionary), en ISO 32000-1 organiseert de hele familie in §12.6. Dit artikel bespreekt de acties die een Delphi-programma het vaakst gebruikt en laat zien hoe PDFlibPas elk van deze koppelt aan een veld of een link.
Het mentale model dat u moet onthouden is dat een veld en een actie afzonderlijke objecten zijn die door een referentie met elkaar zijn verbonden. Een widget-annotatie of een link-annotatie bevat een actie in zijn /A-ingang. De actie identificeert het veld waarop deze reageert op basis van de titel, niet op basis van de index. De titel die u aan een veld geeft is dus de referentie die elke latere actie gebruikt om het veld te vinden. Zodra deze splitsing duidelijk is, ziet de API er niet langer uit als een willekeurige verzameling aanroepen, maar als één patroon dat is toegepast op vier soorten acties.
Named actions: navigatie zonder paginanummer
De eenvoudigste acties bevatten helemaal geen parameters. ISO 32000-1 §12.6.4.11, tabel 194, definieert benoemde acties (named actions): de viewer interpreteert tijdens runtime een symbolische naam in plaats van een opgeslagen bestemming te volgen. Vier namen worden universeel ondersteund, en dat zijn precies de namen die een lezer verwacht van een werkbalk: NextPage, PrevPage, FirstPage en LastPage. Omdat de bestemming relatief is aan de pagina die de viewer momenteel toont, een Next-knop die op deze manier is gebouwd werkt op elke pagina zonder dat u een bestemming hoeft te berekenen.
In PDFlibPas is een named action gekoppeld aan een hotspot-rechthoek op de huidige pagina. Het vierde en vijfde integer-argument selecteren de actie en de weergave.
// 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
Er is geen bestemming die u synchroon moet houden, en dat is precies het hele eieren eten. Een named action blijft behouden bij het invoegen en verwijderen van pagina's omdat deze in de eerste plaats nooit een specifieke pagina noemt. Vergelijk dat met een expliciete go-to link, die een doelpagina-index opslaat die u moet hernummeren zodra het document groeit.
De Hide-actie en de valkuil van de array
De Hide-actie, ISO 32000-1 §12.6.4.10, tabel 196, schakelt de zichtbaarheid van een of meer velden om. Het is de schoonste manier om toon- en verberggedrag te bouwen zonder scripting, en het is wat u wilt voor een link 'Details tonen' of voor twee panelen die elkaar uitsluiten waarbij het onthullen van de ene de andere verbergt. De actie bevat een doel in zijn /T-ingang en een boolean /H die de richting bepaalt: verbergen wanneer true, tonen wanneer false.
De subtiliteit zit hem volledig in de manier waarop dat doel is gecodeerd, en dat is het soort detail waardoor een formulier op uw machine werkt maar bij een klant mislukt. Wanneer de actie naar een enkel veld verwijst, wordt /T geschreven als één tekstreeks. Wanneer deze naar meerdere verwijst, wordt /T geschreven als een array van tekstreeksen. Oudere viewers behandelen een array met één element niet op dezelfde manier als een kale string, dus de codering moet splitsen op basis van het aantal: een enkele naam moet worden verzonden als een string, niet als een array met lengte één, wil het breedste scala aan lezers dit respecteren. PDFlibPas neemt die beslissing voor u. U geeft veldnamen door gescheiden door komma's, puntkomma's of regeleinden, en de writer verzendt een enkele string voor één naam en een array voor twee of meer.
// 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);
Omdat de actie niet naar een externe bron verwijst, blijft deze compatibel met PDF/A. De namen die u doorgeeft zijn volledig gekwalificeerde veldtitels, en daarom moet een onderliggend veld binnen een groep worden geadresseerd via het volledige pad met punten in plaats van de kale naam.
ImportData: vooraf invullen vanuit FDF
Waar de Hide-actie herschikt wat al op de pagina staat, de import-data-actie brengt waarden van buitenaf binnen. ISO 32000-1 §12.6.4.8, tabel 198, definieert dit als een actie die de AcroForm vult vanuit een Forms Data Format-bestand op schijf. Dit is the actie achter een knop 'Voorbeeldgegevens opnieuw laden' of 'Standaardwaarden herstellen', waarbij een FDF-bestand naast de PDF wordt meegeleverd en de definitieve veldwaarden bevat. De aanroep weerspiegelt de andere en vereist de hotspot-rechthoek, het pad naar het FDF-bestand en een weergave-bitmasker: Pdf.AddLinkToImportData(40, 660, 120, 18, 'defaults.fdf', 1). Het bestand hoeft niet te bestaan wanneer the PDF wordt gebouwd, maar het moet aanwezig zijn wanneer de gebruiker klikt, en eventuele backslashes in het pad worden voor u herschreven naar de voor PDF gebruikelijke schuine streep.
Eén beperking is het vermelden waard omdat deze vaak voor verrassingen zorgt. Een import-data-actie verwijst naar een extern bestand en is daarom niet toegestaan in PDF/A. Wanneer het document zich in de PDF/A-modus bevindt, retourneert de aanroep nul en voegt niets toe, in plaats van een bestand te produceren dat de validatie niet doorstaat. Als uw workflow gericht is op gearchiveerde uitvoer, moet het vooraf invullen plaatsvinden tijdens het genereren door de veldwaarden rechtstreeks te schrijven, en niet door dit uit te stellen tot een klik.
JavaScript: globale pakketten en scripts per actie
Voor logica die verder gaat dan tonen, verbergen en importeren, maakt de actiefamilie gebruik van JavaScript op documentniveau. Er zijn twee verschillende plaatsen waar een script kan staan, en dat verschil is belangrijk. Een JavaScript-pakket op documentniveau wordt eenmalig voor het hele bestand opgeslagen en uitgevoerd wanneer het document wordt geopend. Dit is de juiste plek voor functiedefinities en gedeelde status. Een script per actie is gekoppeld aan één link of veld en wordt alleen uitgevoerd wanneer dat object wordt geactiveerd. Dit is de juiste plek voor de regel code die een functie aanroept die al in het pakket is gedefinieerd.
PDFlibPas ondersteunt beide. AddGlobalJavaScript slaat een benoemd pakket op op documentniveau; het hergebruiken van een naam vervangt wat eronder was opgeslagen. AddLinkToJavaScript koppelt een script aan een hotspot zodat een klik dit uitvoert.
// 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);
Het bewaren van de functie in het globale pakket en de aanroep in de link is geen kwestie van stijlvoorkeur. Het voorkomt dat dezelfde code wordt gedupliceerd op elk besturingselement dat deze nodig heeft, en het betekent dat een viewer waarin scripting is uitgeschakeld simpelweg niets doet bij een klik, in plaats van vast te lopen op een onjuist opgebouwd inline fragment. Het houdt ook de invoer per actie klein, wat het bestand leesbaar houdt wanneer u het later inspecteert.
Velden, onderliggende velden en het resultaat definitief maken
Acties hebben velden nodig om op reageren, dus het helpt om te zien hoe een veld ontstaat. NewFormField maakt een veld aan op de huidige pagina en retourneert de index ervan; het type (integer) bepaalt de soort, waarbij 1 Text is, 2 Pushbutton, 3 Checkbox, 4 Radiobutton, 5 Choice, 6 Signature en 7 een Parent die onderliggende velden bezit maar zelf niets tekent. De titel die u doorgeeft mag geen punt bevatten, omdat de punt het scheidingsteken is in de volledig gekwalificeerde namen die acties gebruiken om onderliggende velden te adresseren.
Radiogroepen en hiërarchische formulieren worden gebouwd door een parent-veld onderliggende velden (children) te geven. NewChildFormField voegt een child toe onder een benoemde parent, en voor de radio- en keuzeopties voegt AddFormFieldSub de afzonderlijke opties toe en geeft een tijdelijke index terug die u gebruikt om ze te positioneren. Wanneer de interactieve fase voorbij is en u een veld wilt vergrendelen (freezen) zodat de huidige weergave permanente pagina-inhoud wordt, tekent FlattenFormField het veld op de pagina en verwijdert het uit het formulier. Na het samenvoegen (flatten) verschuiven de indexen van latere velden met één positie omlaag. Dat is het belangrijkste om te onthouden als u meerdere velden in een lus samenvoegt.
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;
De flatten-aanroep is met opzet als commentaar gemarkeerd. Laat deze weg en het document wordt geleverd als een live formulier waarvan de acties in de lezer worden geactiveerd. Schakel dit in en het veld wordt omgezet in statische markeringen. Dit is wat u wilt wanneer het formulier is ingevuld en het resultaat als een vastgelegd document moet worden verzonden. Hetzelfde veld, dezelfde code, twee zeer verschillende documenten afhankelijk van of u het vergrendelt.
De juiste actie kiezen
De vier acties verschillen duidelijk in wat ze beïnvloeden. Een named action verplaatst de viewport en heeft geen veld nodig. Een Hide-actie wijzigt de zichtbaarheid en heeft veldtitels nodig, waarbij de codering voor string-versus-array voor u wordt afgehandeld. Een import-data-actie opent een bestand op schijf en is daarom niet toegestaan in PDF/A. Een JavaScript-actie voert willekeurige logica uit en kan het beste worden gesplitst in een globaal pakket met functies en kleine aanroepen per actie. Kies de eenvoudigste optie die het werk doet: een Hide-actie is flexibeler dan een script dat een verborgen vlag instelt, en een named action is duurzamer dan een opgeslagen paginabestemming omdat er geen nummer is dat u moet bijhouden.
Vanaf hier maken twee verwante onderwerpen het plaatje compleet. Als het formulier deel uitmaakt van een toegankelijk document, de structuurboom die schermlezers gebruiken wordt behandeld in ons artikel over tagged PDF en toegankelijkheidsstructuur. Wanneer het ingevulde formulier moet worden vergrendeld en ondertekend, de workflow wordt beschreven in de handleiding voor het nalevings- en ondertekeningsplatform. Alle drie bouwen ze voort op dezelfde engine, die wordt geleverd als de PDF-bibliotheek voor Delphi, naast de API's voor creatie, formulieren en handtekeningen die elders op deze blog worden besproken.