Een factuur- of onkosten-PDF waarin de posten zichzelf optellen en de datum zichzelf opmaakt, heeft drie dingen nodig die aan het AcroForm zijn gekoppeld, en HotPDF voegt alle drie toe aan een geladen bestand in Delphi: ApplyLoadedFieldCalculation voor AFSimple_Calculate-totalen, ApplyLoadedFieldValidation voor de Acrobat AF-indelings- en validatiehulpmiddelen, and EnsureLoadedFieldAppearanceStream om de waarde te bakken die een viewer daadwerkelijk tekent
HotPDF is een native VCL PDF-component voor Delphi en C++Builder, en deze drie methoden werken op een document dat u hebt geopend met LoadFromFile in plaats van een document dat u vanaf nul opbouwt. Elk neemt een 0-gebaseerde veldindex in de AcroForm /Fields-array — dezelfde volgorde die GetLoadedFormFieldNames retourneert — en schrijft rechtstreeks in het velddictionary, dus het hele werk is laden, indexeren, koppelen, SaveLoadedDocument. Het opbouwen van de veldenset zelf is een aparte taak, behandeld in AcroForm-velden toevoegen aan een geladen PDF in Delphi, terwijl deze pagina gaat over het toevoegen van rekenfuncties en opmaak aan een bestaande veldenset
Waarom heeft een PDF-formulier scripts nodig om zijn eigen velden op te tellen?
Omdat PDF geen spreadsheetlaag heeft. Een tekstveld bevat een string, geen formule, en niets in het bestand herberekent een totaal wanneer een post verandert, tenzij er een script is gekoppeld om dat te doen. Het mechanisme is het additional-actionsdictionary van het veld, /AA (ISO 32000-1 §12.7.5), dat trigger-gebeurtenissen koppelt aan acties: een toetsaanslag-trigger (K) wanneer tekens binnenkomen, een opmaak-trigger voor weergave, een validatie-trigger (V) bij het bevestigen, en een berekenings-trigger (C) die wordt geactiveerd telkens wanneer een veld op het formulier verandert. Elke actie is een JavaScript-actie, /S /JavaScript (ISO 32000-1 §12.6.4.14), en het script dat wordt uitgevoerd is wat daadwerkelijk de som of de herformattering uitvoert. Acrobat levert een bibliotheek met kant-en-klare hulpmiddelen — AFSimple_Calculate, AFNumber_Format, AFDate_FormatEx en de rest — dus het gekoppelde script is meestal een aanroep van één regel naar die bibliotheek in plaats van handgeschreven code
Eén detail bepaalt of een formulier met meerdere stappen het juiste antwoord geeft: de berekeningsvolgorde. Acrobat voert elk veld uit dat een berekeningsactie bevat, en voert deze uit in de volgorde die de AcroForm /CO-array vermeldt, niet in paginavolgorde. ApplyLoadedFieldCalculation koppelt de berekeningsactie aan het veld dat u noemt; wanneer het ene berekende veld het andere voedt — een subtotaal dat door een belastingregel wordt gelezen, en vervolgens een eindtotaal dat beide leest — moet u controleren of de /CO-volgorde elke invoer vóór het veld vermeldt dat deze verbruikt, anders leest het eindtotaal een verouderd subtotaal en loopt het één bewerking achter
Hoe zorgt u ervoor dat een PDF-formulier automatisch totalen berekent?
ApplyLoadedFieldCalculation neemt de doelveldindex, een bewerking en de namen van de bronvelden die moeten worden samengevoegd. HotPDF schrijft AFSimple_Calculate("SUM", ["lineTotal.1", "lineTotal.2", ...]) in de berekeningsactie van het doelveld, en de THPDFFieldCalcOp-opsomming kiest de functie: fccSum, fccAverage, fccProduct, fccMinimum en fccMaximum verwijzen naar Acrobat SUM, AVG, PRD, MIN en MAX. De bronnamen zijn AcroForm-veldnamen, dezelfde strings die GetLoadedFormFieldNames teruggeeft, dus een totaal over drie posten en verzendkosten is een enkele aanroep
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;
De aanroep EnsureLoadedFieldAppearanceStream op het totaal is niet cosmetisch. De berekening wordt uitgevoerd binnen de viewer, dus totdat een voor scripts geschikte lezer het bestand opent, heeft het totaalveld geen eigen gerenderde inhoud. Het bakken van een appearance-stream geeft het in de tussentijd een getal om te tonen, wat een afdruktaak of een miniatuurgenerator zal oppikken wanneer deze het script nooit uitvoert
Velden opmaken en valideren met de AF-hulpmiddelenset
ApplyLoadedFieldValidation koppelt de opmaak-, toetsaanslag- en validatiescripts in één aanroep, en de THPDFFieldValidationKind die u doorgeeft bepaalt welk Acrobat-hulpmiddel wordt gekoppeld. Geef fvkCurrency door en HotPDF schrijft AFNumber_Format(2,0,0,0,"$ ",true) als het opmaakscript met de bijbehorende AFNumber_Keystroke; fvkNumber gebruikt "." voor het scheidingstekenargument, fvkPercentage zendt AFPercent_Format uitzendt, en fvkDate zendt AFDate_FormatEx("dd/mm/yyyy") uit, tenzij u uw eigen masker doorgeeft in het DateFormat-argument. De speciale formaten — fvkZipCode, fvkSSN en fvkPhone — verwijzen naar AFSpecial_Format met de code die Acrobat voor elk reserveert, en fvkArbitraryMask neemt een maskerstring via het Mask-argument voor alles wat die voorinstellingen niet dekken. Voor de numerieke typen koppelt HotPDF ook een AFRange_Validate-script zodat invoer buiten het bereik wordt geweigerd bij het bevestigen
// 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);
Waarom moet u de appearance-stream opnieuw genereren?
Omdat de waardestring en de pixels die een viewer tekent twee verschillende dingen zijn in PDF, en het wijzigen van de ene de andere niet verandert. Een veld slaat zijn waarde op in /V, maar wat een lezer rendert komt uit de appearance-stream van het veld, /AP /N. Bewerk /V via de objectgrafiek en een strikte viewer toont nog steeds de oude weergave totdat iets deze opnieuw genereert. Het formulierdictionary bevat een /NeedAppearances-vlag die de viewer vraagt om elke weergave bij het openen opnieuw op te bouwen, en Acrobat respecteert deze — maar in de browser ingebedde renderers, mobiele lezers, afdrukservers en miniatuurgeneratoren negeren deze vaak en tekenen de stream die al aanwezig is, of helemaal niets. EnsureLoadedFieldAppearanceStream overbrugt dat gat door zelf het /AP /N Form XObject te bouwen, op basis van het veld /DA, /V en /Rect, zodat de gerenderde waarde in het bestand meereist in plaats van afhankelijk te zijn van de viewer om deze opnieuw te tekenen
// 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');
Waar AF-scripts ophouden met werken
Elk AF*-script is afhankelijk van de aanwezigheid van een JavaScript-engine in de viewer, en de meeste viewers hebben die niet. Acrobat en een handvol desktopproducten voeren deze hulpmiddelen uit; de PDF-viewers die zijn ingebouwd in Chrome, Edge, Firefox en the belangrijkste mobiele platforms, samen met rasterizers en afdruktaken aan de serverzijde, voeren helemaal geen JavaScript uit. In die lezers wordt de berekening nooit uitgevoerd en wordt de opmaak nooit toegepast — het veld toont simpelweg de appearance-stream waarmee het voor het laatst is opgeslagen, en dat is precies waarom het bakken van /AP /N belangrijk is. Behandel de AF*-laag als een gemak voor de subset van gebruikers op een lezer die scripts ondersteunt, nooit als een garantie. Elk totaal dat correct moet zijn op de server — een factuurbedrag dat u in rekening brengt, een belastingbedrag dat u indient — moet aan uw zijde opnieuw worden berekend nadat het formulier terugkomt, omdat u er niet van uit kunt gaan dat de lezer die het heeft ingevuld een enkele regel van uw script heeft uitgevoerd
Nog één geval is de moeite waard om te noemen: formulieren dat als XFA zijn binnengekomen. Een dynamisch XFA-formulier bevat zijn eigen berekeningsengine in een XML-payload waarvan de AF*-hulpmiddelen niets weten, dus het koppelen van AFSimple_Calculate aan een XFA-veld heeft geen effect. Vlak het XFA-formulier eerst af naar een gewoon AcroForm, zoals behandeld in XFA naar AcroForm afvlakken in Delphi, en de drie methoden hier zijn dan van toepassing op de resulterende AcroForm-velden, precies zoals ze dat doen op elk ander geladen formulier
Automatisch berekenende en zelfopmakende AcroForms komen neer op drie aanroepen op een geladen document — koppel de berekening, koppel de opmaak en validatie, bak de weergave — gevolgd door SaveLoadedDocument. De hier getoonde API voor geladen formulieren maakt deel uit van de HotPDF-component voor Delphi en C++Builder, die de volledige THPDFFieldValidationKind- en THPDFFieldCalcOp-opsommingen documenteert naast de rest van het AcroForm-oppervlak