Ett PDF-formulärfält (PDF form field) på egen hand är bara en ruta som håller ett värde. Det som får ett formulär att bete sig som en liten applikation är den åtgärd (action) som är kopplad (attached) till den: ett klick som döljer ett avsnitt, drar tillbaka sparade värden från en fil, hoppar till sista sidan, eller kör ett skript som summerar en kolumn. Inget av detta lever i fältet. Det lever i ett åtgärdslexikon (action dictionary), och ISO 32000-1 organiserar hela familjen i §12.6. Denna artikel går igenom de åtgärder som ett Delphi-program sträcker sig efter oftast, och visar hur PDFlibPas kopplar (wires) var och en till ett fält eller en länk
Den mentala modell värd att behålla är att ett fält och en åtgärd är separata objekt sammanfogade av en referens. En widget-annotering (widget annotation) eller en länkannotering (link annotation) bär på en åtgärd i dess /A-post (entry). Åtgärden namnger fältet den opererar på via titel, inte via index, så den titel du ger ett fält är det handtag (handle) varje senare åtgärd använder för att hitta den. När den uppdelningen (split) väl är tydlig, slutar API:et att se ut som en slumpmässig samling anrop (grab-bag of calls) och börjar se ut som ett mönster tillämpat på fyra typer av verb
Namngivna åtgärder (Named actions): navigering utan ett sidnummer
De enklaste åtgärderna bär inte på några parametrar alls. ISO 32000-1 §12.6.4.11, Tabell 194, definierar namngivna åtgärder: visaren (viewer) tolkar ett symboliskt namn vid körning (at runtime) i stället för att följa en lagrad destination. Fyra namn är universellt stödda, och de är exakt de som en läsare förväntar sig från ett verktygsfält: NextPage, PrevPage, FirstPage och LastPage. Eftersom destinationen är relativ till vilken sida visaren för närvarande visar, fungerar en Nästa-knapp (Next button) byggd på det här sättet på varje sida utan att du beräknar ett mål (target)
I PDFlibPas kopplas (attached) en namngiven åtgärd till en hotspot-rektangel på den aktuella sidan. Det fjärde och femte heltalsargumentet (integer arguments) väljer verbet och utseendet (appearance)
// 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
Det finns ingen destination att hålla synkroniserad, vilket är hela poängen. En namngiven åtgärd överlever infogning och radering av sidor eftersom den över huvud taget (in the first place) aldrig namnger en sida. Kontrastera det mot en explicit gå till-länk (go-to link), som lagrar ett målsidindex (target page index) som du måste numrera om (renumber) i samma ögonblick som dokumentet växer
Åtgärden Hide och dess array-fälla (gotcha)
Hide-åtgärden, ISO 32000-1 §12.6.4.10, Tabell 196, växlar synligheten (toggles the visibility) för ett eller flera fält. Det är det renaste sättet att bygga visa och dölj-beteende utan skript, och det är vad du vill ha för en Visa detaljer-länk (Show details link) eller för två ömsesidigt uteslutande (mutually exclusive) paneler där avslöjandet (revealing) av den ena döljer (conceals) den andra. Åtgärden bär på ett mål i dess /T-post och en boolesk /H som avgör riktningen: dölj när sant, visa när falskt
Spetsfundigheten (subtlety) ligger helt och hållet i hur det målet är kodat, och det är den sortens detalj som producerar ett formulär som fungerar på din maskin och fallerar på en kunds. När åtgärden namnger ett enskilt fält, skrivs /T som en textsträng. När den namnger flera, skrivs /T som en array av textsträngar. Äldre visare behandlar inte en en-elements-array på samma sätt som de behandlar en naken sträng, så kodningen måste förgrena (branch) på antalet (count): ett enskilt namn måste genereras (emitted) som en sträng, inte som en array av längd ett, om den bredaste vidden av läsare (widest range of readers) ska hedra det (honour it). PDFlibPas gör det beslutet åt dig. Du skickar in (pass) fältnamn separerade av kommatecken, semikolon eller radbrytningar, och skrivaren (writer) genererar en enskild sträng för ett namn och en array för två eller fler
// 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);
Eftersom åtgärden inte refererar till någon extern resurs, förblir den kompatibel med PDF/A. Namnen du skickar är fullständigt kvalificerade (fully qualified) fälttitlar, vilket är anledningen till varför ett underfält (child field) inuti en grupp måste adresseras genom dess fullständiga punktseparerade sökväg (full dotted path) snarare än dess nakna lövnamn (bare leaf name)
ImportData: förifyllnad (prefilling) från FDF
Där Hide-åtgärden arrangerar om vad som redan finns på sidan, för (brings) import-data-åtgärden in värden från utanför den. ISO 32000-1 §12.6.4.8, Tabell 198, definierar den som en åtgärd som fyller (populates) i AcroForm från en Forms Data Format-fil på disk. Detta är åtgärden bakom en Ladda om exempeldata- (Reload sample data) eller Återställ till standardvärden-kontroll (Reset to defaults control), där en FDF-fil skeppas bredvid PDF:en och håller de kanoniska fältvärdena. Anropet speglar (mirrors) de andra, tar hotspot-rektangeln, sökvägen till FDF:en och en utseendebitmask (appearance bitmask): Pdf.AddLinkToImportData(40, 660, 120, 18, 'defaults.fdf', 1). Filen behöver inte existera när PDF:en byggs, men den måste finnas närvarande när användaren klickar, och eventuella backstreck (backslashes) i sökvägen skrivs om till PDF-kanonisk snedstrecksform (slash form) åt dig
En restriktion (constraint) är värd att konstatera i klartext eftersom det är en frekvent överraskning. En import-data-åtgärd pekar på en extern fil, så den är inte tillåten i PDF/A. När dokumentet är i PDF/A-läge (PDF/A mode) returnerar anropet noll och lägger inte till någonting, snarare än att producera en fil som fallerar validering. Om din pipeline har arkivutmatning (archival output) som mål, måste förifyllnaden (prefilling) ske vid genereringstiden (generation time) genom att skriva fältvärdena direkt, inte genom att skjuta upp dem (deferring them) till ett klick
JavaScript: globala paket (packages) och per-åtgärd-skript
För logik som går utöver (beyond) visa, dölj och importera, sträcker sig (reaches) åtgärdsfamiljen in i JavaScript på dokumentnivå (document-level JavaScript). Det finns två distinkta platser ett skript kan leva på, och skillnaden spelar roll. Ett JavaScript-paket på dokumentnivå lagras en gång för hela filen och körs när dokumentet öppnas, vilket gör det till det rätta hemmet för funktionsdefinitioner och delat tillstånd (shared state). Ett per-åtgärd-skript är fäst vid en länk eller fält och körs enbart när det objektet aktiveras, vilket gör det till rätt hem för den enda rad som anropar en funktion som paketet redan definierat
PDFlibPas exponerar båda. AddGlobalJavaScript lagrar ett namngivet paket på dokumentnivå; återanvändning av ett namn ersätter (replaces) det som lagrats under det. AddLinkToJavaScript fäster ett skript på en hotspot så att ett klick exekverar (executes) 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);
Att hålla funktionen i det globala paketet och anropet i länken är ingen stilpreferens (style preference). Det undviker duplicering av samma kropp på varje kontroll som behöver den, och det betyder att en visare med skript inaktiverat (disabled) helt enkelt inte gör någonting vid klick snarare än att kvävas (choking) på en felformaterad inbäddad klump (malformed inline blob). Det håller också per-åtgärd-posterna små, vilket håller filen läsbar när du inspekterar den senare
Fält, underfält (child fields) och att frysa (freezing) resultatet
Åtgärder behöver fält att verka på (act on), så det hjälper att se hur ett fält blir till (comes into being). NewFormField skapar ett fält på den aktuella sidan och returnerar dess index; heltalstypen väljer sorten (kind), där 1 är Text, 2 är Tryckknapp (Pushbutton), 3 är Kryssruta (Checkbox), 4 är Radioknapp (Radiobutton), 5 är Val (Choice), 6 är Signatur (Signature), och 7 är en Förälder (Parent) som äger barn (children) men som inte ritar något själv. Titeln du skickar (pass) kan inte innehålla en punkt, eftersom punkten är separatorn (separator) i de fullständigt kvalificerade namn som åtgärder använder för att adressera barn
Radiogrupper och hierarkiska formulär byggs genom att ge ett föräldrafält underfält (children). NewChildFormField lägger till ett barn (child) under en namngiven förälder, och för radio- och val-fallen (choice cases) lägger AddFormFieldSub till de enskilda alternativen och lämnar tillbaka ett tillfälligt index du använder för att positionera var och en. När den interaktiva fasen är över och du vill frysa ett fält så dess aktuella utseende blir permanent sidinnehåll, ritar FlattenFormField fältet på sidan och tar bort det från formuläret. Efter en tillplattning (flatten) förskjuts (shift down) indexen för senare fält med ett, vilket är den enda sak att komma ihåg om du plattar till (flatten) flera fält i en loop
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;
Tillplattnings-anropet (The flatten call) är utkommenterat med avsikt (on purpose). Lämna bort (Leave it out) det och dokumentet skeppas som ett levande formulär (live form) vars åtgärder avfyras (fire) i läsaren. Aktivera det och fältet renderas ner till statiska märken (static marks), vilket är vad du vill ha när formuläret har blivit ifyllt (completed) och resultatet ska färdas som en fixerad post (fixed record). Samma fält, samma kod, två väldigt olika dokument beroende på huruvida du fryser (freeze) det
Att välja rätt verb
De fyra åtgärderna delar upp sig rent utifrån vad de vidrör (touch). En namngiven åtgärd flyttar vyporten (viewport) och behöver inget fält. En Hide-åtgärd ändrar synlighet och behöver fälttitlar, med sträng-kontra-array-kodningen (string-versus-array encoding) hanterad åt dig. En import-data-åtgärd når en fil på disk och är därmed otillåten (off limits) i PDF/A. En JavaScript-åtgärd kör godtycklig (arbitrary) logik och delas bäst mellan ett globalt paket med funktioner och små per-åtgärd-anrop. Sträck dig efter (Reach for) det enklaste som gör jobbet: en Hide-åtgärd är mer portabel (portable) än ett skript som sätter en dold-flagga (hidden flag), och en namngiven åtgärd är mer hållbar (durable) än en lagrad siddestination (stored page destination) eftersom det inte finns något nummer att underhålla
Härifrån avslutar två närliggande (neighbouring) ämnen bilden. Ifall formuläret är del av ett tillgängligt (accessible) dokument, täcks det strukturträd (structure tree) som skärmläsare (screen readers) vandrar igenom i vår artikel om taggad PDF och tillgänglighetsstruktur. När det ifyllda formuläret måste låsas och signeras beskrivs arbetsflödet i arbetsbänksgenomgången för efterlevnad och signering. Alla tre bygger på samma motor, vilken skeppas som PDF library för Delphi jämsides med skapande- (creation), formulär- och signatur-API:erna som täcks på andra ställen i den här bloggen