Et PDF-formularfelt i sig selv er bare en boks, der indeholder en værdi. Det, der får en formular til at fungere som et lille program, er den handling, der er knyttet til den: et klik, der skjuler en sektion, henter gemte værdier fra en fil, hopper til den sidste side eller kører et script, der lægger en kolonne sammen. Intet af dette findes i feltet. Det findes i en handlingsordbog (action dictionary), og ISO 32000-1 organiserer hele familien i §12.6. Denne artikel gennemgår de handlinger, som et Delphi-program oftest har brug for, og viser, hvordan PDFlibPas forbinder hver enkelt til et felt eller et link.
Den mentale model, man bør huske, er, at et felt og en handling er separate objekter, der er forbundet med en reference. En widget-annotering eller en link-annotering indeholder en handling i sit /A-felt. Handlingen navngiver det felt, den opererer på, efter titel, ikke efter indeks, så den titel, du giver et felt, er det håndtag (handle), enhver senere handling bruger til at finde det. Når først denne opdeling er klar, holder API'en op med at ligne en tilfældig samling af kald og begynder at ligne ét mønster anvendt på fire typer handlinger.
Navngivne handlinger: navigation uden et sidenummer
De enkleste handlinger har slet ingen parametre. ISO 32000-1 §12.6.4.11, tabel 194, definerer navngivne handlinger (named actions): fremviseren tolker et symbolsk navn under kørslen i stedet for at følge en gemt destination. Fire navne er universelt understøttet, og de er præcis dem, en læser forventer fra en værktøjslinje: NextPage, PrevPage, FirstPage og LastPage. Fordi destinationen er relativ i forhold til den side, som fremviseren aktuelt viser, fungerer en Næste-knap bygget på denne måde på alle sider, uden at du skal beregne et mål.
I PDFlibPas er en navngiven handling knyttet til et hotspot-rektangel på den aktuelle side. Det fjerde og femte heltalsargument vælger handlingen og udseendet.
// 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
Der er ingen destination, der skal holdes synkroniseret, hvilket er hele pointen. En navngiven handling overlever indsættelse og sletning af sider, fordi den aldrig navngiver en side i første omgang. Kontraster dette med et eksplicit go-to-link, som gemmer et målsideindeks, som du skal omnummerere i samme øjeblik, dokumentet vokser.
Hide-handlingen og dens array-fælde
Hide-handlingen, ISO 32000-1 §12.6.4.10, tabel 196, slår synligheden af et eller flere felter til og fra. Det er den reneste måde at bygge vis/skjul-funktionalitet på uden scripting, og it er det, du ønsker til et Vis detaljer-link eller til to gensidigt udelukkende paneler, hvor visning af det ene skjuler det andet. Handlingen bærer et mål i sit /T-felt og en boolean /H, der bestemmer retningen: skjul når sand, vis når falsk.
Subtiliteten ligger helt i, hvordan dette mål er kodet, og det er den type detalje, der resulterer i en formular, der virker på din maskine, men fejler hos en kunde. Når handlingen navngiver et enkelt felt, skrives /T som en enkelt tekststreng. Når den navngiver flere, skrives /T som et array af tekststrenge. Ældre fremvisere behandler ikke et array med ét element på samme måde, som de behandler en ren streng, så kodningen skal forgrene sig baseret på antallet: et enkelt navn skal udgives som en streng, ikke som et array af længde én, hvis det bredeste udvalg af læsere skal respektere det. PDFlibPas tager denne beslutning for dig. Du sender feltnavne adskilt af kommaer, semikoloner eller linjeskift, og skriveren udgiver en enkelt streng for ét navn og et array for to eller flere.
// 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);
Fordi handlingen ikke refererer til nogen ekstern ressource, forbliver den kompatibel med PDF/A. De navne, du angiver, er fuldt kvalificerede feltnavne, og det er grunden til, at et underfelt i en gruppe skal adresseres via dets fulde stisystem med punktummer frem for blot dets rå navn.
ImportData: forududfyldning fra FDF
Hvor Hide-handlingen omarrangerer det, der allerede er på siden, bringer import-data-handlingen værdier ind udefra. ISO 32000-1 §12.6.4.8, tabel 198, definerer det som en handling, der udfylder AcroForm ud fra en Forms Data Format-fil (FDF) på disken. Dette er handlingen bag en Genindlæs eksempeldata- eller Nulstil til standardindstillinger-knap, hvor en FDF-fil følger med PDF-filen og indeholder de kanoniske feltværdier. Kaldet afspejler de andre og tager hotspot-rektanglet, stien til FDF-filen og en bitmaske for udseendet: Pdf.AddLinkToImportData(40, 660, 120, 18, 'defaults.fdf', 1). Filen behøver ikke at eksistere, når PDF-filen bygges, men den skal være til stede, når brugeren klikker, og eventuelle backslashes i stien omskrives til PDF-standard skråstreg for dig.
Én begrænsning er værd at nævne tydeligt, da den ofte kommer bag på folk. En import-data-handling peger på en ekstern fil, så den er ikke tilladt i PDF/A. Når dokumentet er i PDF/A-tilstand, returnerer kaldet nul og tilføjer intet i stedet for at producere en fil, der fejler ved validering. Hvis din pipeline er rettet mod arkiveringsoutput, skal forududfyldning ske på genereringstidspunktet ved at skrive feltværdierne direkte, og ikke ved at udskyde det til et klik.
JavaScript: globale pakker og handling-specifikke scripts
Til logik, der rækker ud over vis, skjul og import, rækker handlingsfamilien ind i JavaScript på dokumentniveau. Der er to forskellige steder, et script kan bo, og forskellen er vigtig. En JavaScript-pakke på dokumentniveau gemmes én gang for hele filen og kører, når dokumentet åbnes, hvilket gør den til det rette sted for funktionsdefinitioner og delt tilstand. Et handling-specifikt script er knyttet til ét link eller felt og kører kun, når det pågældende objekt aktiveres, hvilket gør det til det rette sted for den enkelte linje, der kalder en funktion, som pakken allerede har defineret.
PDFlibPas understøtter begge dele. AddGlobalJavaScript gemmer en navngiven pakke på dokumentniveau; genbrug af et navn erstatter det, der tidligere var gemt under det. AddLinkToJavaScript knytter et script to et hotspot, så et klik udfører det.
// 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);
At holde funktionen i den globale pakke og kaldet i linket er ikke et spørgsmål om stil. Det undgår duplikering af den samme kode i alle kontrolelementer, der har brug for den, og det betyder, at en fremviser med scripting deaktiveret blot intet gør ved klik i stedet for at gå i stå på en fejlbehæftet indlejret kodeblok. Det holder også de handling-specifikke poster små, hvilket gør filen læsbar, når du inspicerer den senere.
Felter, underfelter og frysning af resultatet
Handlinger har brug for felter at agere på, så det hjælper at se, hvordan et felt bliver til. NewFormField opretter et felt på den aktuelle side og returnerer dets indeks; heltalstypen vælger typen, hvor 1 er Text, 2 er Pushbutton, 3 er Checkbox, 4 er Radiobutton, 5 er Choice, 6 er Signature og 7 er en Parent, der ejer underfelter, men ikke tegner noget selv. Den titel, du angiver, må ikke indeholde et punktum, da punktummet bruges som separator i de fuldt kvalificerede navne, som handlinger bruger til at adressere underfelter.
Radiogrupper og hierarkiske formularer opbygges ved at give et overordnet felt (parent) underfelter (children). NewChildFormField tilføjer et underfelt under en navngiven parent, og for radio- og valgmulighedstilfælde tilføjer AddFormFieldSub de enkelte indstillinger og returnerer et midlertidigt indeks, du bruger til at placere hver enkelt. Når den interaktive fase er forbi, og du vil fryse et felt, så dets aktuelle udseende bliver til permanent sideindhold, tegner FlattenFormField feltet på siden og fjerner det fra formularen. Efter en fladgøring (flatten) rykker indeksene for de efterfølgende felter ét trin ned, hvilket er det eneste, du skal huske, hvis du fladgør flere felter i en løkke.
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;
Fladgøringskaldet er med vilje kommenteret ud. Undlad det, og dokumentet leveres som en aktiv formular, hvis handlinger udløses i læseren. Aktiver det, og feltet renderes til statiske markeringer, hvilket er det, du ønsker, når formularen er udfyldt, og resultatet skal sendes som en fast låst registrering. Det samme felt, den samme kode, to meget forskellige dokumenter afhængigt af, om du fryser det.
Valg af den rigtige handling
De fire handlinger adskiller sig tydeligt efter, hvad de påvirker. En navngiven handling flytter visningsområdet og kræver intet felt. En Hide-handling ændrer synlighed og kræver feltnavne, hvor streng-over-for-array-kodningen håndteres for dig. En import-data-handling tilgår en fil på disken og er derfor udelukket i PDF/A. En JavaScript-handling kører vilkårlig logik og opdeles bedst i en global pakke af funktioner og små specifikke kald. Vælg den enkleste løsning, der klarer opgaven: en Hide-handling er mere portabel end et script, der sætter et skjult-flag, og en navngiven handling er mere holdbar end en gemt sidedestination, fordi der ikke er noget sidetal, der skal vedligeholdes.
Herfra fuldender to beslægtede emner billedet. Hvis formularen er en del af et tilgængeligt dokument, det strukturelle træ, som skærmlæsere gennemgår, er dækket i vores artikel om tagget PDF og tilgængelighedsstruktur. Når den udfyldte formular skal låses og signeres, er arbejdsgangen beskrevet i gennemgangen af arbejdsbænken til overholdelse og signering. Alle tre bygger på den samme motor, der leveres som PDF-biblioteket til Delphi sammen med API'erne til oprettelse, formularer og signaturer, som er dækket andre steder på denne blog.