Techninis straipsnis

Automatiškai perskaičiuojamos PDF formos Delphi aplinkoje naudojant HotPDF

Sąskaitos faktūros arba išlaidų PDF failui, kuriame eilučių elementai patys sumuojasi, o data pati pritaiko savo formatą, reikia trijų dalykų, prikabintų prie jo „AcroForm“, ir HotPDF prideda visus tris prie įkelto failo Delphi aplinkoje: ApplyLoadedFieldCalculation skirtas AFSimple_Calculate tarpinėms sumoms, ApplyLoadedFieldValidation skirtas „Acrobat“ AF formato bei patvirtinimo pagalbininkams ir EnsureLoadedFieldAppearanceStream, skirtas iškepti reikšmei, kurią peržiūros programa iš tikrųjų nupiešia

HotPDF yra vietinis VCL PDF komponentas, skirtas Delphi ir C++Builder, ir šie trys metodai veikia su dokumentu, kurį atidarėte naudodami LoadFromFile, o ne su tokiu, kurį kuriate nuo nulio. Kiekvienas iš jų priima nuliu pagrįstą lauko indeksą „AcroForm“ /Fields masyve — ta pačia tvarka, kurią grąžina GetLoadedFormFieldNames — ir rašo tiesiai į lauko žodyną, todėl visas darbas yra įkelti, indeksuoti, prikabinti ir iškviesti SaveLoadedDocument. Pačio laukų rinkinio kūrimas yra atskira užduotis, aprašyta straipsnyje „AcroForm“ laukų pridėjimas prie įkelto PDF Delphi aplinkoje, o šiame puslapyje kalbama apie aritmetikos ir formatavimo suteikimą esamam laukų rinkiniui

Kodėl PDF formai reikalingi scenarijai, kad ji pati susumuotų savo laukus?

Todėl, kad PDF neturi skaičiuoklės sluoksnio. Teksto laukas saugo eilutę, o ne formulę, ir niekas faile neperskaičiuoja sumos, kai pasikeičia eilutės elementas, nebent tam yra prijungtas scenarijus. Šis mechanizmas yra lauko papildomų veiksmų žodynas /AA (ISO 32000-1 §12.7.5), susiejantis paleidimo įvykius su veiksmais: klavišo paspaudimo paleidiklis (K), kai atkeliauja simboliai, formatavimo paleidiklis rodymui, patvirtinimo paleidiklis (V) pateikiant duomenis ir perskaičiavimo paleidiklis (C), suveikiantis, kai pasikeičia bet kuris formos laukas. Kiekvienas veiksmas yra „JavaScript“ veiksmas, /S /JavaScript (ISO 32000-1 §12.6.4.14), o jo vykdomas scenarijus yra tai, kas atlieka sumavimą arba performatavimą. „Acrobat“ pateikia paruoštų pagalbinių programų biblioteką — AFSimple_Calculate, AFNumber_Format, AFDate_FormatEx ir kitas —, todėl prijungtas scenarijus paprastai yra vienos eilutės iškvietimas į tą biblioteką, o ne ranka parašytas kodas

Viena detalė nusprendžia, ar kelių žingsnių forma pateikia teisingą atsakymą: skaičiavimo tvarka. „Acrobat“ paleidžia kiekvieną lauką, kuris turi perskaičiavimo veiksmą, ir paleidžia juos tokia seka, kokia nurodyta „AcroForm“ /CO masyve, o ne puslapių tvarka. ApplyLoadedFieldCalculation prideda perskaičiavimo veiksmą prie jūsų nurodyto lauko; kai vienas calculated laukas maitina kitą — pavyzdžiui, tarpinė suma, kurią nuskaito mokesčių eilutė, o po to bendra suma, kuri nuskaito abi —, turite patikrinti, ar /CO tvarkoje kiekviena įvestis yra nurodyta prieš lauką, kuris ją sunaudoja, kitaip bendra suma nuskaitys pasenusią tarpinę sumą ir atsiliks vienu žingsniu

Kaip priversti PDF formą automatiškai perskaičiuoti sumas?

ApplyLoadedFieldCalculation priima tikslinio lauko indeksą, operaciją ir šaltinio laukų, kuriuos ji turėtų agreguoti, pavadinimus. HotPDF įrašo AFSimple_Calculate("SUM", ["lineTotal.1", "lineTotal.2", ...]) į tikslinio lauko perskaičiavimo veiksmą, o THPDFFieldCalcOp išvardijimas parenka funkciją: fccSum, fccAverage, fccProduct, fccMinimum ir fccMaximum atitinka „Acrobat“ SUM, AVG, PRD, MIN ir MAX. Šaltinio pavadinimai yra „AcroForm“ laukų pavadinimai, tos pačios eilutės, kurias grąžina GetLoadedFormFieldNames, todėl bendra suma iš trijų eilučių ir siuntimo mokesčio yra sukuriama vienu iškvietimu

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;

EnsureLoadedFieldAppearanceStream iškvietimas ant sumos lauko nėra kosmetinis. Skaičiavimas veikia peržiūros programos viduje, todėl kol scenarijus palaikanti peržiūros programa neatidaro failo, sumos laukas neturi savo atvaizduoto turinio. Išvaizdos srauto iškepimas duoda jam reikšmę, kurią galima parodyti laikinai, o tai yra svarbu spausdinimui arba eskizų generatoriui, kuris nevykdo scenarijų

Laukų formatavimas ir patvirtinimas naudojant AF pagalbinių programų rinkinį

ApplyLoadedFieldValidation vienu iškvietimu prikabina formatavimo, klavišų paspaudimo ir patvirtinimo scenarijus, o jūsų perduotas THPDFFieldValidationKind nusprendžia, kurį „Acrobat“ pagalbininką jis prijungs. Perduokite fvkCurrency ir HotPDF įrašo AFNumber_Format(2,0,0,0,"$ ",true) kaip formatavimo scenarijų su atitinkamu AFNumber_Keystroke; fvkNumber naudoja tašką "." kaip skirtuko argumentą, fvkPercentage generuoja AFPercent_Format, o fvkDate generuoja AFDate_FormatEx("dd/mm/yyyy"), nebent DateFormat argumente nurodysite savo kaukę. Specialūs formatai — fvkZipCode, fvkSSN ir fvkPhone — susiejami su AFSpecial_Format su kodu, kurį „Acrobat“ rezervuoja kiekvienam iš jų, o fvkArbitraryMask priima kaukės eilutę per Mask argumentą viskam, ko šie šablonai neapima. Skaitiniams tipams HotPDF taip pat prideda AFRange_Validate scenarijų, kad duomenų pateikimo metu būtų atmetamos reikšmės, esančios už leistino diapazono ribų

// 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 pagalbininkai apima įprastus atvejus, todėl jums nereikia rašyti JavaScript kodo rankiniu būdu. Kai laukui reikalinga logika, kurios joks šablonas neišreiškia — kontrolinė suma, kryžminio lauko taisyklė, pasirinktinis regex —, prisekite neapdorotą scenarijų patys naudodami lauko lygio veiksmų metodus, aprašytus skyriuje „AcroForm“ laukų ir veiksmų kūrimas su HotPDF, kurie priima JavaScript eilutę tiesiogiai. Abu keliai rašo į tą patį /AA žodyną, todėl iš anksto nustatytas valiutos formatas viename lauke ir pačių parašytas patvirtinimas kitame lauke gali bendrai egzistuoti tame pačiame dokumente

Kodėl privalote iš naujo sugeneruoti išvaizdos srautą?

Todėl, kad reikšmės eilutė ir taškai, kuriuos nupiešia peržiūros programa, PDF formatu yra du skirtingi dalykai, ir pakeitus vieną, kitas nepasikeičia. Laukas saugo savo reikšmę /V, tačiau tai, ką atvaizduoja skaitytuvas, ateina iš lauko išvaizdos srauto /AP /N. Redaguokite /V per objektų grafiką ir griežta peržiūros programa vis tiek rodys seną išvaizdą, kol kas nors jos nesugeneruos iš naujo. Formos žodynas turi /NeedAppearances vėliavėlę, kuri prašo peržiūros programos atidarius atstatyti kiekvieną išvaizdą, ir „Acrobat“ į tai atsižvelgia — tačiau naršyklėse įmontuoti atvaizduotojai, mobilieji skaitytuvai, spausdinimo serveriai ir eskizų generatoriai dažnai tai ignoruoja ir piešia bet kokį jau esamą srautą arba išvis nieko nepiešia. EnsureLoadedFieldAppearanceStream užpildo šią spragą pats sukurdamas /AP /N Form XObject iš lauko /DA, /V ir /Rect, todėl atvaizduota reikšmė keliauja faile, o ne priklauso nuo peržiūros programos perpiešimo

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

Kur AF scenarijai nustoja veikti

Kiekvienas AF* scenarijus prikaišo nuo to, ar peržiūros programa turi „JavaScript“ variklį, o dauguma jų jo neturi. „Acrobat“ ir keletas darbalaukio produktų vykdo šias pagalbines programas; PDF peržiūros programos, įmontuotos į „Chrome“, „Edge“, „Firefox“ ir pagrindines mobiliąsias platformas, taip pat serverio rastravimo programos bei spausdinimo konvejeriai išvis nevykdo formų „JavaScript“. Šiose programose skaičiavimas niekada nesuveikia, o formatas niekada netaikomas — laukas tiesiog rodo tą išvaizdos srautą, su kuriuo buvo išsaugotas paskutinį kartą, o tai ir yra priežastis, kodėl /AP /N iškepimas yra svarbus. Vertinkite AF* sluoksnį kaip patogumą tiems vartotojams, kurie naudoja scenarijus palaikančią peržiūros programą, bet niekada nelaikykite to garantija. Bet kuri bendra suma, kuri turi būti teisinga serveryje — sąskaitos faktūros suma arba mokesčių skaičius —, turi būti perskaičiuota jūsų pusėje, kai forma sugrįžta, nes negalite daryti prielaidos, kad ją užpildęs skaitytuvas įvykdė bent vieną jūsų scenarijaus eilutę

Verta paminėti dar vieną atvejį: formas, kurios atkeliavo kaip XFA. Dinamiška XFA forma turi savo skaičiavimo variklį XML naudingajame krovinyje, apie kurį AF* pagalbininkai nieko nežino, todėl AFSimple_Calculate prikabinimas prie XFA lauko nieko neduoda. Pirmiausia suploštinkite XFA formą iki paprastos „AcroForm“, kaip aprašyta skyriuje XFA pavertimas „AcroForm“ Delphi aplinkoje, ir tada trys čia aprašyti metodai bus taikomi gautiems „AcroForm“ laukams lygiai taip pat, kaip ir bet kuriai kitai įkeltai formai

Automatiškai perskaičiuojamos ir pačios save formatuojančios „AcroForm“ susiveda į tris iškvietimus įkeltame dokumente — prikabinti perskaičiavimą, prikabinti formatavimą bei patvirtinimą ir iškepti išvaizdą —, po kurių seka SaveLoadedDocument. Čia parodytas įkeltos formos API yra HotPDF komponento, skirto Delphi ir C++Builder, dalis, kuri dokumentuoja visas THPDFFieldValidationKind bei THPDFFieldCalcOp išvardijimo reikšmes kartu su likusiu „AcroForm“ funkcionalumu