Technikai cikk

Automatikusan számoló PDF űrlapok Delphiben a HotPDF-fel

Egy olyan számla vagy költség-PDF, ahol a tételek maguktól összeadódnak, a dátum pedig önmagát formázza, három dolgot igényel az AcroForm-hoz csatolni, és a HotPDF mindhármat hozzáadja a betöltött fájlhoz Delphiben: az ApplyLoadedFieldCalculation metódust az AFSimple_Calculate végösszegekhez, az ApplyLoadedFieldValidation metódust az Acrobat AF formázási és validálási segédekhez, valamint az EnsureLoadedFieldAppearanceStream metódust, hogy beégesse a megjelenítő által ténylegesen kirajzolt értéket

A HotPDF egy natív VCL PDF komponens Delphihez és C++Builderhez, és ez a három metódus egy olyan dokumentumon dolgozik, amelyet a LoadFromFile segítségével nyitott meg, nem pedig egy teljesen újként felépített dokumentumon. Mindegyik egy 0-alapú mezőindexet vesz fel az AcroForm /Fields tömbjében – ugyanabban a sorrendben, ahogyan azt a GetLoadedFormFieldNames visszaadja –, és közvetlenül a mezőszótárba ír, így a teljes feladat a betöltésből, indexelésből, csatolásból és a SaveLoadedDocument hívásból áll. A mezőkészlet felépítése egy külön feladat, amelyet az AcroForm mezők hozzáadása betöltött PDF-hez Delphiben című cikk tárgyal, míg ez az oldal a meglévő mezőkészlet számtani műveletekkel és formázással való ellátásáról szól

Miért van szükség szkriptekre a PDF űrlap saját mezőinek összegzéséhez?

Mert a PDF-nek nincs táblázatkezelő rétege. Egy szövegmező egy karakterláncot tartalmaz, nem pedig egy képletet, és semmi sem számolja újra a végösszeget a fájlban, amikor egy tétel megváltozik, hacsak nincs csatolva egy szkript, amely elvégzi ezt. A mechanizmus a mező kiegészítő műveletek szótára (additional-actions dictionary), az /AA (ISO 32000-1 §12.7.5), amely az eseményindítókat rendeli a műveletekhez: egy leütési eseményindító (K), amikor a karakterek megérkeznek, egy formázási eseményindító a megjelenítéshez, egy validálási eseményindító (V) véglegesítéskor, és egy számítási eseményindító (C), amely akkor fut le, ha az űrlap bármely mezője megváltozik. Minden művelet egy JavaScript művelet, /S /JavaScript (ISO 32000-1 §12.6.4.14), és az általa futtatott szkript végzi el a tényleges összeadást vagy újraformázást. Az Acrobat kész segédfüggvények könyvtárát szállítja – AFSimple_Calculate, AFNumber_Format, AFDate_FormatEx és a többi –, így a csatolt szkript általában egy egysoros hívás ebbe a könyvtárba ahelyett, hogy egyedi kód lenne

Egy részlet dönti el, hogy egy többlépcsős űrlap helyes választ ad-e: a számítási sorrend. Az Acrobat minden olyan mezőt futtat, amely számítási műveletet hordoz, és abban a sorrendben futtatja őket, amelyet az AcroForm /CO tömbje felsorol, nem pedig oldalsorrendben. Az ApplyLoadedFieldCalculation hozzárendeli a számítási műveletet a megnevezett mezőhöz; ha az egyik számított mező táplálja a másikat – egy részösszeg, amelyet az adósor olvas be, majd egy végösszeg, amely mindkettőt olvassa –, ellenőriznie kell, hogy a /CO sorrend minden bemenetet a fogyasztó mező előtt sorol-e fel, különben a végösszeg elavult részösszeget fog beolvasni, és egy szerkesztési lépéssel lemarad

Hogyan érhető el, hogy a PDF űrlap automatikusan számolja a végösszegeket?

Az ApplyLoadedFieldCalculation átveszi a célmező indexét, a műveletet, valamint azon forrásmezők neveit, amelyeket összegeznie kell. A HotPDF a AFSimple_Calculate("SUM", ["lineTotal.1", "lineTotal.2", ...]) hívást írja a célmező számítási műveletébe, és a THPDFFieldCalcOp felsorolás választja ki a függvényt: a fccSum, fccAverage, fccProduct, fccMinimum és fccMaximum értékek az Acrobat SUM, AVG, PRD, MIN és MAX függvényeknek felelnek meg. A forrásnevek AcroForm mezőnevek, ugyanazok a karakterláncok, amelyeket a GetLoadedFormFieldNames ad vissza, így a három tétel és egy szállítási díj feletti végösszeg kiszámítása egyetlen hívás

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;

Az EnsureLoadedFieldAppearanceStream meghívása a végösszegen nem esztétikai kérdés. A számítás a megjelenítőben fut le, így amíg egy szkriptképes olvasó meg nem nyitja a fájlt, a végösszegmezőnek nincs saját renderelt tartalma. A megjelenítési folyamat (appearance stream) beégetése ad neki egy értéket, amelyet addig is megjeleníthet, és ezt fogja átvenni a nyomtatási feladat vagy a bélyegkép-generátor is, ha soha nem futtatja a szkriptet

Mezők formázása és validálása az AF segédkészlet segítségével

Az ApplyLoadedFieldValidation egyetlen hívásban társítja a formázó, leütési és validáló szkripteket, és az átadott THPDFFieldValidationKind dönti el, hogy melyik Acrobat segédet kapcsolja be. Ha a fvkCurrency értéket adja át, a HotPDF az AFNumber_Format(2,0,0,0,"$ ",true) hívást írja be formázó szkriptként a hozzá tartozó AFNumber_Keystroke eseménnyel; a fvkNumber a "." karaktert használja elválasztó argumentumként, a fvkPercentage a AFPercent_Format hívást bocsátja ki, a fvkDate pedig a AFDate_FormatEx("dd/mm/yyyy") hívást, kivéve, ha saját maszkot ad meg a DateFormat argumentumban. A speciális formátumok – fvkZipCode, fvkSSN és fvkPhone – az AFSpecial_Format segédhez kapcsolódnak az Acrobat által nekik fenntartott kódokkal, a fvkArbitraryMask pedig egy maszk karakterláncot vesz át a Mask argumentumon keresztül minden máshoz, amit az előre beállított értékek nem fednek le. A numerikus típusoknál a HotPDF egy AFRange_Validate szkriptet is csatol, így a tartományon kívüli bemenet véglegesítéskor elutasításra kerül

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

Az AF segédek lefedik a gyakori formákat, így soha nem kell kézzel írnia JavaScriptet hozzájuk. Ha egy mezőnek olyan logikára van szüksége, amelyet semelyik előre beállított érték nem fejez ki – ellenőrzőösszeg, mezők közötti szabály, egyedi regex –, csatolja a nyers szkriptet közvetlenül a AcroForm mezők és műveletek építése a HotPDF-fel című cikkben leírt mezőszintű műveleti metódusokkal, amelyek közvetlenül egy JavaScript karakterláncot vesznek át. Mindkét útvonal ugyanabba a /AA szótárba ír, így az előre beállított pénznemformátum az egyik mezőn és a kézzel írt validálás a másikon megférnek ugyanabban a dokumentumban

Miért kell újragenerálni a megjelenítési folyamatot?

Mert az érték-karakterlánc és a megjelenítő által kirajzolt pixelek két különböző dolgot jelentenek a PDF-ben, és az egyik megváltoztatása nem változtatja meg a másikat. Egy mező az értékét a /V bejegyzésben tárolja, de amit egy olvasó kirajzol, az a mező megjelenítési folyamatából, a /AP /N-ből származik. Ha az objektumgráfon keresztül szerkeszti a /V-t, egy szigorú megjelenítő továbbra is a régi megjelenést mutatja, amíg valami újra nem generálja azt. Az űrlapszótár hordoz egy /NeedAppearances jelzőt, amely arra kéri a megjelenítőt, hogy megnyitáskor építse újra az összes megjelenést, és az Acrobat tiszteletben is tartja ezt – de a böngészőbe ágyazott renderelők, a mobilolvasók, a nyomtatószerverek és a bélyegkép-generátorok gyakran figyelmen kívül hagyják, és a már meglévő folyamot rajzolják ki, vagy egyáltalán semmit. Az EnsureLoadedFieldAppearanceStream áthidalja ezt a rést azáltal, hogy maga építi fel a /AP /N Form XObject-et a mező /DA, /V és /Rect bejegyzéseiből, így a renderelt érték a fájlon belül utazik, ahelyett, hogy a megjelenítőre támaszkodna annak újrarajzolásában

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

Ahol az AF szkriptek működése véget ér

Minden AF* szkript feltételezi, hogy a megjelenítő rendelkezik JavaScript motorral, a legtöbb megjelenítő azonban nem rendelkezik ilyennel. Az Acrobat és néhány asztali termék futtatja ezeket a segédeket; a Chrome, Edge, Firefox és a nagyobb mobilplatformok beépített PDF-megjelenítői, valamint a szerveroldali raszterezők és nyomtatási folyamatok egyáltalán nem futtatnak űrlap-JavaScriptet. Ezekben az olvasókban a számítás soha nem indul el, a formátum pedig soha nem alkalmazódik – a mező egyszerűen azt a megjelenítési folyamatot mutatja, amellyel utoljára elmentették, és pontosan ezért fontos az /AP /N beégetése. Tekintse az AF* réteget kényelmi szolgáltatásnak a felhasználók azon része számára, akik szkriptképes olvasót használnak, de soha ne vegye garanciának. Minden olyan végösszeget, amelynek helyesnek kell lennie a szerveren – egy számlaösszeg, amelyet ki fog számlázni, vagy egy adóösszeg, amelyet be fog nyújtani –, újra ki kell számítania az Ön oldalán az űrlap beérkezése után, mivel nem feltételezheti, hogy az azt kitöltő olvasó a szkript egyetlen sorát is futtatta

Még egy esetet érdemes megemlíteni: az XFA-ként érkezett űrlapokat. Egy dinamikus XFA űrlap saját számítási motort hordoz egy XML hasznos teherben, amelyről az AF* segédek semmit sem tudnak, így az AFSimple_Calculate csatolása egy XFA mezőhöz nem ér el semmit. Lapítsa ki először az XFA űrlapot egy egyszerű AcroForm-má, ahogyan azt a XFA lapítása AcroForm-má Delphiben című cikk bemutatja, és az itt leírt három metódus ezután pontosan ugyangy alkalmazható az eredményül kapott AcroForm mezőkre, mint bármely más betöltött űrlapra

Az automatikusan számoló és önmagukat formázó AcroFormok három hívásra vezethetők vissza a betöltött dokumentumon – a számítás csatolása, a formázás és validálás csatolása, a megjelenés beégetése –, amelyet a SaveLoadedDocument követ. Az itt bemutatott betöltött űrlap API a Delphihez és C++Builderhez készült HotPDF Component része, amely dokumentálja a teljes THPDFFieldValidationKind és THPDFFieldCalcOp felsorolásokat az AcroForm felület többi részével együtt