En faktura- eller utläggs-PDF där raderna summerar sig själva och datumet formaterar sig självt behöver tre saker kopplade till sitt AcroForm, och HotPDF lägger till alla tre i en laddad fil i Delphi: ApplyLoadedFieldCalculation för AFSimple_Calculate-totaler, ApplyLoadedFieldValidation för Acrobats AF-format- och valideringshjälpare, och EnsureLoadedFieldAppearanceStream för att baka in det värde som en visare faktiskt ritar
HotPDF är en inbyggd VCL PDF-komponent för Delphi och C++Builder, och dessa tre metoder fungerar på ett dokument som du öppnat med LoadFromFile snarare än ett du bygger från grunden. Var och en tar ett nollbaserat fältindex i AcroForm-matrisen /Fields — samma ordning som GetLoadedFormFieldNames returnerar — och skriver direkt i fältordboken, så hela jobbet består av att ladda, indexera, koppla och anropa SaveLoadedDocument. Att bygga själva fältuppsättningen är en separat uppgift som beskrivs i att lägga till AcroForm-fält i en laddad PDF i Delphi, medan denna sida handlar om att ge en befintlig fältuppsättning aritmetik och formatering
Varför behöver ett PDF-formulär skript för att summera sina egna fält?
Eftersom PDF inte har något kalkylbladslager. Ett textfält innehåller en sträng, inte en formel, och ingenting i filen beräknar om en totalsumma när en rad ändras om inte ett skript är kopplat för att göra det. Mekanismen är fältets ordbok för ytterligare åtgärder, /AA (ISO 32000-1 §12.7.5), som mappar utlösande händelser till åtgärder: en tangenttryckningsutlösare (K) när tecken skrivs in, en formatutlösare för visning, en valideringsutlösare (V) vid verkställande och en beräkningsutlösare (C) som körs närhelst något fält i formuläret ändras. Varje åtgärd är en JavaScript-åtgärd, /S /JavaScript (ISO 32000-1 §12.6.4.14), och skriptet det kör är vad som faktiskt utför summan eller omformateringen. Acrobat levereras med ett bibliotek av färdiga hjälpare — AFSimple_Calculate, AFNumber_Format, AFDate_FormatEx och resten — så det kopplade skriptet är vanligtvis ett enrads-anrop till det biblioteket snarare än skräddarsydd kod
En detalj avgör om ett flerstegsformulär ger rätt svar: beräkningsordning. Acrobat kör varje fält som bär på en beräkningsåtgärd, och den kör dem i den sekvens som AcroForm-matrisen /CO listar, inte i sidordning. ApplyLoadedFieldCalculation kopplar beräkningsåtgärden till det fält du anger; när ett beräknat fält matar ett annat — en delsumma som en skatterad läser, och sedan en totalsumma som läser båda — måste du bekräfta att /CO-ordningen listar varje indata före det fält som konsumerar det, annars kommer totalsumman att läsa en föråldrad delsumma och ligga en redigering efter
Hur gör man så att ett PDF-formulär beräknar totalsummor automatiskt?
ApplyLoadedFieldCalculation tar målfältets index, en operation och namnen på källfälten som den ska slå ihop. HotPDF skriver AFSimple_Calculate("SUM", ["lineTotal.1", "lineTotal.2", ...]) till målfältets beräkningsåtgärd, och THPDFFieldCalcOp-uppräkningen väljer funktionen: fccSum, fccAverage, fccProduct, fccMinimum och fccMaximum mappar till Acrobat SUM, AVG, PRD, MIN och MAX. Källnamnen är AcroForm-fältnamn, samma strängar som GetLoadedFormFieldNames returnerar, så en totalsumma över tre rader och en fraktavgift blir ett enda anrop
var
Pdf: THotPDF;
Names: THPDFAnsiStringArray;
i, TotalIdx: Integer;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.LoadFromFile('invoice.pdf');
Names := Pdf.GetLoadedFormFieldNames;
TotalIdx := -1;
for i := 0 to High(Names) do
if Names[i] = 'grandTotal' then TotalIdx := i;
if TotalIdx >= 0 then
begin
Pdf.ApplyLoadedFieldCalculation(TotalIdx, fccSum,
['lineTotal.1', 'lineTotal.2', 'lineTotal.3', 'shipping']);
Pdf.EnsureLoadedFieldAppearanceStream(TotalIdx);
end;
Pdf.SaveLoadedDocument('invoice-live.pdf');
finally
Pdf.Free;
end;
end;
Anropet EnsureLoadedFieldAppearanceStream på totalsumman är inte kosmetiskt. Beräkningen körs inuti visaren, så tills en skriptkapabel läsare öppnar filen har totalsumman inget eget renderat innehåll. Att baka en utseendeström ger den ett värde att visa under tiden, vilket är vad ett utskriftsjobb eller en miniatyrbildsgenerator kommer att läsa när de inte kör skriptet
Formatera och validera fält med AF-hjälparpaketet
ApplyLoadedFieldValidation kopplar format-, tangenttrycknings- och valideringsskripten i ett anrop, och den THPDFFieldValidationKind du skickar avgör vilken Acrobat-hjälpare den kopplar upp. Skicka fvkCurrency så skriver HotPDF AFNumber_Format(2,0,0,0,"$ ",true) som formatskript med tillhörande AFNumber_Keystroke; fvkNumber använder "." för separatorargumentet, fvkPercentage genererar AFPercent_Format och fvkDate genererar AFDate_FormatEx("dd/mm/yyyy") om du inte skickar din egen mask i argumentet DateFormat. De speciella formaten — fvkZipCode, fvkSSN och fvkPhone — mappar till AFSpecial_Format med den kod som Acrobat reserverar för var och en, och fvkArbitraryMask tar en masksträng via argumentet Mask för allt som dessa förinställningar inte täcker. För de numeriska typerna kopplar HotPDF också ett AFRange_Validate-skript så att ogiltiga indata nekas vid verkställande
// Format the invoice date as ISO, the amount as currency, and mask a tax id
Pdf.ApplyLoadedFieldValidation(DateIdx, fvkDate, 'yyyy-mm-dd', '');
Pdf.ApplyLoadedFieldValidation(AmountIdx, fvkCurrency, '', '');
Pdf.ApplyLoadedFieldValidation(TaxIdIdx, fvkArbitraryMask, '', '999-99-9999');
Pdf.EnsureLoadedFieldAppearanceStream(AmountIdx);
AF-hjälparna täcker de vanliga formerna så att du aldrig behöver skriva JavaScript för them för hand. När ett fält behöver logik som ingen förinställning uttrycker — en kontrollsumma, en regel mellan fält, ett anpassat regex — kopplar du själv det råa skriptet med åtgärdsmetoderna på fältnivå som beskrivs i att bygga AcroForm-fält och åtgärder med HotPDF, vilka tar en JavaScript-sträng direkt. Båda vägarna skriver till samma /AA-ordbok, så ett förinställt valutaformat på ett fält och en handskriven validering på ett annat kan existera i samma dokument
Varför måste du återskapa utseendeströmmen?
Eftersom värdesträngen och pixlarna som en visare ritar är två olika saker i PDF, och att ändra det ena ändrar inte det andra. Ett fält lagrar sitt värde i /V, men vad en läsare renderar kommer från fältets utseendeström, /AP /N. Redigera /V via objektgrafen och en strikt visare visar fortfarande det gamla utseendet tills något återskapar det. Formulärordboken bär på en /NeedAppearances-flagga som ber visaren att återskapa varje utseende vid öppning, och Acrobat respekterar den — men webbläsarintegrerade renderare, mobila läsare, utskriftsservrar och miniatyrbildsgeneratorer ignorerar den ofta och ritar den ström som redan finns, eller ingenting alls. EnsureLoadedFieldAppearanceStream stänger det gapet genom att bygga /AP /N Form XObject självt, från fältets /DA, /V och /Rect, så att det renderade värdet färdas inuti filen istället för att vara beroende av att visaren ritar om det
// Bake an appearance for every field before saving, so values render everywhere
Names := Pdf.GetLoadedFormFieldNames;
for i := 0 to High(Names) do
Pdf.EnsureLoadedFieldAppearanceStream(i);
Pdf.SaveLoadedDocument('invoice-flat-appearance.pdf');
Var AF-skript slutar fungera
Varje AF*-skript beror på att visaren har en JavaScript-motor, och de flesta visare har inte det. Acrobat och en handfull skrivbordsprodukter kör dessa hjälpare; PDF-visarna som är inbyggda i Chrome, Edge, Firefox och de stora mobila plattformarna, tillsammans med rasteriserare på serversidan och utskriftsköer, kör inget form-JavaScript alls. I dessa läsare utlöses beräkningen aldrig och formatet tillämpas aldrig — fältet visar helt enkelt den utseendeström det senast sparades med, vilket är precis varför det är viktigt att baka /AP /N. Behandla AF*-lagret som en bekvämlighet för den delmängd användare som använder en skriptkapabel läsare, aldrig som en garanti. Varje totalsumma som måste vara korrekt på servern — ett fakturabelopp du ska debitera mot, en skattesiffra du ska deklarera — måste beräknas om på din sida efter att formuläret kommer tillbaka, eftersom du inte kan anta att läsaren som fyllde i det körde en enda rad av ditt skript
Ytterligare ett fall är värt att nämna: formulär som anlände som XFA. Ett dynamiskt XFA-formulär bär på sin egen beräkningsmotor i en XML-nyttolast som AF*-hjälparna inte vet någonting om, så att koppla AFSimple_Calculate till ett XFA-fält gör ingenting. Platta till XFA-formuläret till ett vanligt AcroForm först, enligt beskrivningen i XFA- till AcroForm-utplattning i Delphi, så gäller de tre metoderna här på de resulterande AcroForm-fälten exakt som de gör på alla andra laddade formulär
Automatiskt beräknande och självformaterande AcroForms kokar ner till tre anrop på ett laddat dokument — koppla beräkningen, koppla formatet och valideringen, baka utseendet — följt av SaveLoadedDocument. API:et för laddade formulär som visas här är en del av HotPDF Component för Delphi och C++Builder, som dokumenterar de fullständiga THPDFFieldValidationKind- och THPDFFieldCalcOp-uppräkningarna tillsammans med resten av AcroForm-ytan