Teknisk artikel

Automatisk beregning af PDF-formularer i Delphi med HotPDF

En faktura- eller udgifts-PDF, hvor linjeposterne lægger sig selv sammen, og datoen formaterer sig selv, har brug for tre ting boltet på sin AcroForm, og HotPDF tilføjer alle tre til en indlæst fil i Delphi: ApplyLoadedFieldCalculation til AFSimple_Calculate-totaler, ApplyLoadedFieldValidation til Acrobat AF-format- og valideringshjælpere, og EnsureLoadedFieldAppearanceStream til at bage den værdi, en fremviser faktisk tegner

HotPDF er en indfødt VCL PDF-komponent til Delphi og C++Builder, og disse tre metoder fungerer på et dokument, du har åbnet med LoadFromFile, snarere end et, du bygger fra bunden. Hver tager et 0-baseret feltindeks i AcroForm /Fields-arrayet — den samme rækkefølge som GetLoadedFormFieldNames returnerer — og skriver direkte i feltordbogen, så hele jobbet er indlæs, indeksér, tilknyt, SaveLoadedDocument. Opbygning af selve feltsættet er en separat opgave, dækket i tilføjelse af AcroForm-felter til en indlæst PDF i Delphi, mens denne side handler om at give et eksisterende feltsæt aritmetik og formatering

Hvorfor har en PDF-formular brug for scripts til at lægge sine egne felter sammen?

Fordi PDF ikke har noget regnearkslag. Et tekstfelt indeholder en streng, ikke en formel, og intet i filen genberegner en total, når en linjepost ændres, medmindre der er tilknyttet et script til at gøre det. Mekanismen er ordbogen for feltets yderligere handlinger, /AA (ISO 32000-1 §12.7.5), som kortlægger udløserbegivenheder til handlinger: En tastetryksudløser (K), når tegn ankommer, en formatudløser til visning, en valideringsudløser (V) ved bekræftelse og en beregningsudløser (C), der udløses, når et hvilket som helst felt på formularen ændres. Hver handling er en JavaScript-handling, /S /JavaScript (ISO 32000-1 §12.6.4.14), og scriptet, det kører, er det, der faktisk udfører summen eller omformateringen. Acrobat leverer et bibliotek af færdige hjælpere — AFSimple_Calculate, AFNumber_Format, AFDate_FormatEx og resten — så det tilknyttede script is normalt et enkeltlinjekald til det bibliotek snarere end skræddersyet kode

Én detalje afgør, om en flertrinsformular giver det rigtige svar: Beregningsrækkefølge. Acrobat kører hvert felt, der bærer en beregningshandling, og den kører dem i den rækkefølge, AcroForm-/CO-arrayet viser, ikke i siderækkefølge. ApplyLoadedFieldCalculation tilknytter beregningshandlingen til det felt, du navngiver; når et beregnet felt føder et andet — f.eks. en subtotal, som en skattelinje læser, og derefter en hovedtotal, der læser begge — skal du bekræfte, at /CO-rækkefølgen viser hvert input forud for det felt, der forbruger det, ellers vil hovedtotalen læse en forældet subtotal og halte én redigering bagefter

Hvordan får du en PDF-formular til at beregne totaler automatisk?

ApplyLoadedFieldCalculation tager målfeltets indeks, en handling og navnene på de kildefelter, den skal samle. HotPDF skriver AFSimple_Calculate("SUM", ["lineTotal.1", "lineTotal.2", ...]) i målfeltets beregningshandling, og THPDFFieldCalcOp-opregningen vælger funktionen: fccSum, fccAverage, fccProduct, fccMinimum og fccMaximum kortlægger til Acrobat SUM, AVG, PRD, MIN og MAX. Kildenavnene er AcroForm-feltnavne, de samme strenge som GetLoadedFormFieldNames leverer tilbage, så en total over tre linjeposter og et forsendelsesgebyr er et enkelt kald

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;

Kaldet af EnsureLoadedFieldAppearanceStream på totalen er ikke kosmetisk. Beregningen kører inde i fremviseren, så indtil en script-kompatibel læser åbner filen, har totalfeltet intet renderet indhold i sig selv. At bage en udseendestrøm giver det et tal at vise i mellemtiden, hvilket er det, et printjob eller en miniaturegenerator vil hente, når de aldrig kører scriptet

Formatering og validering af felter med AF-hjælper-pakken

ApplyLoadedFieldValidation tilknytter format-, tastetryks- og valideringsscripts i ét kald, og den THPDFFieldValidationKind, du angiver, afgør, hvilkin Acrobat-hjælper den forbinder. Angiv fvkCurrency, og HotPDF skriver AFNumber_Format(2,0,0,0,"$ ",true) som formatscriptet med det tilsvarende AFNumber_Keystroke; fvkNumber bruger "." til separator-argumentet, fvkPercentage udsender AFPercent_Format, og fvkDate udsender AFDate_FormatEx("dd/mm/yyyy"), medmindre du sender din egen maske i DateFormat-argumentet. De specielle formater — fvkZipCode, fvkSSN og fvkPhone — kortlægger til AFSpecial_Format med den kode, Acrobat reserverer til hver, og fvkArbitraryMask tager en maskestreng via Mask-argumentet til alt det, disse forudindstillinger ikke dækker. For de numeriske typer tilknytter HotPDF også et AFRange_Validate-script, så input uden for området afvises ved bekrætselse

// 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);

Hvorfor skal du genskabe udseendestrømmen?

Fordi værdistrengen og de pixels, en fremviser tegner, er to forskellige ting i en PDF, og at ændre den ene ændrer ikke den anden. Et felt gemmer sin værdi i /V, men det, en læser renderer, kommer fra feltets udseendestrøm, /AP /N. Rediger /V via objektgrafen, og en streng fremviser viser stadig det gamle udseende, indtil noget genskaber det. Formularordbogen bærer et /NeedAppearances-flag, der beder fremviseren om at genopbygge ethvert udseende ved åbning, og Acrobat respekterer det — men browser-indlejrede renderere, mobile læsere, printservere og miniaturegeneratorer ignorerer det ofte og tegner den strøm, der allerede er til stede, eller slet ingenting. EnsureLoadedFieldAppearanceStream lukker det hul ved selv at opbygge /AP /N Form XObject ud fra feltets /DA, /V og /Rect, så den renderede værdi rejser med inde i filen i stedet for at afhænge af, at fremviseren tegner den igen

// 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');

Hvor AF-scripts holder op med at virke

Ethvert AF*-script afhænger af, at fremviseren har en JavaScript-motor, og de fleste fremvisere har ikke en. Acrobat og en håndfuld skrivebordsprodukter kører disse hjælpere; de PDF-fremvisere, der er indbygget i Chrome, Edge, Firefox og de store mobile platforme, samt server-side rastereringsværktøjer og printpipelines, kører overhovedet ingen formular-JavaScript. I disse læsere udløses beregningen aldrig, og formatet anvendes aldrig — feltet viser blot den udseendestrøm, det sidst blev gemt med, hvilket er præcis derfor, bagning af /AP /N er vigtigt. Behandl AF*-laget som en bekvemmelighed for den undergruppe af brugere, der bruger en script-kompatibel læser, aldrig som en garanti. Enhver total, der skal være korrekt på serveren — et fakturabeløb, du vil fakturere, et skattetal, du vil indberette — skal genberegnes på din side, efter at formularen kommer tilbage, fordi du ikke kan antage, at den læser, der udfyldte den, kørte en eneste linje af dit script

Endnu et tilfælde er værd at nævne: Formularer, der ankom som XFA. En dynamisk XFA-formular bærer sin egen beregningsmotor i en XML-indholdspakke, som AF*-hjælperne intet ved om, så tilknytning af AFSimple_Calculate til et XFA-felt gør intet. Flad først XFA-formularen ud til en almindelig AcroForm, som beskrevet i XFA til AcroForm-fladgøring i Delphi, og de tre metoder her gælder derefter for de resulterende AcroForm-felter præcis som for enhver anden indlæst formular

Automatisk beregning og selvformatering af AcroForms bunder i tre kald på et indlæst dokument — tilknyt beregningen, tilknyt formatet og valideringen, bag udseendet — efterfulgt af SaveLoadedDocument. API'en for indlæste formularer vist her er en del af HotPDF Component til Delphi og C++Builder, som dokumenterer de fulde THPDFFieldValidationKind- og THPDFFieldCalcOp-opregninger sammen med resten af AcroForm-fladen