Ett PDF-formulärfält i sig är bara en ruta som innehåller ett värde. Det som får ett formulär att bete sig som en liten applikation är åtgärden som är kopplad till det: ett klick som döljer en sektion, hämtar sparade värden från en fil, hoppar till sista sidan eller kör ett skript som summerar en kolumn. Inget av detta finns i fältet. Det finns i en åtgärdsordbok (action dictionary), och ISO 32000-1 organiserar hela familjen i §12.6. Denna artikel går igenom de åtgärder som ett Delphi-program oftast använder och visar hur PDFlibPas kopplar var och en till ett fält eller en länk.
Den mentala modell som är värd att komma ihåg är att ett fält och en åtgärd är separata objekt som är kopplade via en referens. En gränssnittskomponent (widget annotation) eller en länkannotering (link annotation) bär en åtgärd i sin /A-post. Åtgärden namnger fältet den opererar på med dess titel, inte efter index, så titeln du ger ett fält är det handtag som varje senare åtgärd använder för att hitta det. När den uppdelningen är tydlig slutar API:et att se ut som en slumpmässig samling anrop och börjar se ut som ett mönster som tillämpas på fyra typer av verb.
Namngivna åtgärder: navigering utan ett sidnummer
De enklaste åtgärderna har inga parametrar alls. ISO 32000-1 §12.6.4.11, tabell 194, definierar namngivna åtgärder (named actions): visningsprogrammet tolkar ett symboliskt namn vid körning istället för att följa en sparad destination. Fyra namn stöds universellt, 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 den sida som visningsprogrammet för närvarande visar, fungerar en Nästa-knapp som är byggd på detta sätt på varje sida utan att du behöver beräkna ett mål.
I PDFlibPas är en namngiven åtgärd kopplad till en klickbar rektangel (hotspot) på den aktuella sidan. Det fjärde och femte heltalsargumentet väljer verbet och utseendet.
// 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 borttagning av sidor eftersom den aldrig namnger en sida från början. Jämför det med en explicit gå-till-länk (go-to link), som sparar ett målsidesindex som du måste numrera om så fort dokumentet växer.
Hide-åtgärden och dess array-fälla
Hide-åtgärden, ISO 32000-1 §12.6.4.10, tabell 196, växlar synligheten för ett eller flera fält. Det är det smidigaste 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 eller för två ömsesidigt uteslutande paneler där visning av den ena döljer den andra. Åtgärden bär ett mål i sin /T-post och en boolesk /H som avgör riktningen: dölj när sant (true), visa när falskt (false).
Det subtila ligger helt i hur målet är kodat, och det är den typen av detalj som skapar ett formulär som fungerar på din maskin men misslyckas hos en kund. 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 visningsprogram behandlar inte en array med ett element på samma sätt som en ren sträng, så kodningen måste förgrenas baserat på antalet: ett enskilt namn måste skrivas som en sträng, inte som en array med längden ett, om det ska respekteras av så många läsare som möjligt. PDFlibPas fattar det beslutet åt dig. Du skickar med fältnamn separerade med kommatecken, semikolon eller radbrytningar, och skrivaren genererar en enskild sträng för ett namn och en array för två eller flera.
// 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 med är fullständigt kvalificerade fälttitlar, vilket är anledningen till att ett underfält inuti en grupp måste adresseras via sin fullständiga punktseparerade sökväg snarare än bara sitt lövnamn.
ImportData: förhandsifyllning från FDF
Där Hide-åtgärden arrangerar om det som redan finns på sidan, hämtar åtgärden för dataimport (import-data action) in värden utifrån. ISO 32000-1 §12.6.4.8, tabell 198, definierar den som en åtgärd som fyller i AcroForm från en FDF-fil (Forms Data Format) på disken. Detta är åtgärden bakom kontroller som "Läs in exempeldata igen" eller "Återställ till standardvärden", där en FDF-fil levereras bredvid PDF-filen och innehåller de kanoniska fältvärdena. Anropet speglar de andra och tar den klickbara rektangeln, sökvägen till FDF-filen och en bitmask för utseende: Pdf.AddLinkToImportData(40, 660, 120, 18, 'defaults.fdf', 1). Filen behöver inte existera när PDF-filen byggs, men den måste vara närvarande när användaren klickar, och eventuella omvända snedstreck (backslash) i sökvägen skrivs om till den PDF-kanoniska snedstrecksformen åt dig.
En begränsning är värd att nämna tydligt eftersom den ofta kommer som en överraskning. En dataimportå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 returnerar anropet noll och lägger inte till någonting, istället för att producera en fil som misslyckas i valideringen. Om ditt arbetsflöde är inriktat på arkivering måste förhandsifyllningen ske vid genereringstiden genom att skriva fältvärdena direkt, inte genom att skjuta upp dem till ett klick.
JavaScript: globala paket och skript per åtgärd
För logik som sträcker sig längre än att visa, dölja och importera sträcker sig åtgärdsfamiljen in i JavaScript på dokumentnivå. Det finns två distinkta platser där ett skript kan ligga, och skillnaden är viktig. 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 stället för funktionsdefinitioner och delat tillstånd. Ett skript per åtgärd är kopplat till en specifik länk eller ett fält och körs endast när det objektet aktiveras, vilket gör det till det rätta stället för den rad som anropar en funktion som paketet redan har definierat.
PDFlibPas exponerar båda. AddGlobalJavaScript lagrar ett namngivet paket på dokumentnivå; att återanvända ett namn ersätter det som tidigare lagrats under det. AddLinkToJavaScript kopplar ett skript till en klickbar yta så att ett klick kör 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 inte bara en stilfråga. Det undviker att duplicera samma kodkropp på varje kontroll som behöver den, och det innebär att ett visningsprogram med inaktiverat skriptstöd helt enkelt inte gör någonting vid klick istället för att krascha på en felaktig inbäddad kodsekvens. Det håller också posterna för varje enskild åtgärd små, vilket gör filen läsbar när du inspekterar den senare.
Fält, underfält och att frysa resultatet
Åtgärder behöver fält att agera på, så det underlättar att se hur ett fält skapas. NewFormField skapar ett fält på den aktuella sidan och returnerar dess index; heltalstypen väljer typ av fält, där 1 är Text, 2 är Pushbutton (tryckknapp), 3 är Checkbox (kryssruta), 4 är Radiobutton (radioknapp), 5 är Choice (valruta), 6 är Signature (signatur) och 7 är en Parent (förälder) som äger underfält men inte ritar något själv. Titeln du skickar med får inte innehålla en punkt, eftersom punkten är separatorn i de fullständigt kvalificerade namnen som åtgärder använder för att adressera underfält.
Radioknappsgrupper och hierarkiska formulär byggs genom att ge ett föräldrafält underfält. NewChildFormField lägger till ett underfält under en namngiven förälder, och för radio- och valalternativ lägger AddFormFieldSub till de enskilda alternativen och ger tillbaka ett tillfälligt index som du använder för att placera var och en. När den interaktiva fasen är över och du vill frysa ett fält så att dess aktuella utseende blir permanent sidinnehåll, ritar FlattenFormField fältet på sidan och tar bort det från formuläret. Efter en plattläggning (flatten) skiftar indexen för senare fält ned med ett, vilket är det viktigaste att komma ihåg om du plattar till 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;
Plattläggningsanropet (flatten) är bortkommenterat med flit. Utelämna det och dokumentet levereras som ett levande formulär vars åtgärder körs i läsaren. Aktivera det och fältet renderas ned till statiske element, vilket är vad du vill ha när formuläret har fyllts i och resultatet ska överföras som en fast post. Samma fält, samma kod, två mycket olika dokument beroende på om du fryser det eller inte.
Att välja rätt verb
De fyra åtgärderna är tydligt uppdelade efter vad de påverkar. En namngiven åtgärd flyttar vyn (viewport) och behöver inget fält. En Hide-åtgärd ändrar synlighet och behöver fälttitlar, där kodningen för sträng-mot-array hanteras åt dig. En import-data-åtgärd når en fil på disken och är därför inte tillåten i PDF/A. En JavaScript-åtgärd kör godtycklig logik och delas bäst upp mellan ett globalt funktionspaket och små anrop per åtgärd. Välj det enklaste alternativet som gör jobbet: en Hide-åtgärd är mer portabel än ett skript som sätter en dold-flagga, och en namngiven åtgärd är mer hållbar än en sparad sid-destination eftersom det inte finns något sidnummer att underhålla.
Härifrån kompletterar två relaterade ämnen bilden. Om formuläret är en del av ett tillgänglighetsanpassat dokument, täcks det strukturerade trädet som skärmläsare använder i vår artikel om taggad PDF och tillgänglighetsstruktur. När det ifyllda formuläret ska låsas och signeras beskrivs arbetsflödet i genomgången av arbetsbänken för efterlevnad och signering. Alla tre bygger på samma motor, som levereras som PDF-biblioteket för Delphi tillsammans med API:erna för skapande, formulär och signaturer som beskrivs på andra ställen i den här bloggen.