Техническа статия

Автоматично изчисляващи се PDF формуляри в Delphi с HotPDF

PDF фактура или отчет за разходи, при които позициите се сумират автоматично, а датата се форматира сама, се нуждаят от три неща, добавени към техния AcroForm, и HotPDF може да добави и трите към зареден файл в Delphi: ApplyLoadedFieldCalculation за суми по AFSimple_Calculate, ApplyLoadedFieldValidation за форматиращите и валидиращи помощници на Acrobat AF и EnsureLoadedFieldAppearanceStream за генериране на стойността, която четецът действително изчертава

HotPDF е нативен VCL PDF компонент за Delphi и C++Builder и тези три метода работят върху документ, който сте отворили с LoadFromFile, а не върху такъв, който изграждате от нулата. Всеки от тях приема нулев базиран индекс на поле в масива /Fields на AcroForm – същата подредба, която връща методът GetLoadedFormFieldNames – и пише директно в речника на полето, така че целият процес се свежда до зареждане, индексиране, прикачване и SaveLoadedDocument. Създаването на самите полета е отделна задача, разгледана в статията за добавяне на полета на AcroForm към зареден PDF в Delphi, докато тази страница описва как да предоставите аритметика и форматиране на съществуващи полета

Защо PDF формулярът се нуждае от скриптове, за да сумира собствените си полета?

Тъй като PDF няма табличен слой. Текстовото поле съдържа низ, а не формула, и нищо във файла не преизчислява общата сума при промяна на позиция, освен ако за това не е прикачен скрипт. Механизмът е речникът за допълнителни действия на полето – /AA (ISO 32000-1 §12.7.5), който свързва задействащи събития с действия: задействане на натискане на клавиш (K) при пристигане на символи, задействане на формат за показване, задействане на валидиране (V) при потвърждение и задействане на изчисление (C), което се стартира винаги, когато някое поле във формуляра се промени. Всяко действие е JavaScript действие – /S /JavaScript (ISO 32000-1 §12.6.4.14), а скриптът, който се изпълнява, е този, който реално извършва сумирането или преформатирането. Acrobat се доставя с библиотека от готови помощници – AFSimple_Calculate, AFNumber_Format, AFDate_FormatEx и други – така че прикаченият скрипт обикновено е едноредово извикване към тази библиотека, а не потребителски код

Една подробност определя дали многоетапният формуляр дава правилния отговор: редът на изчисляване. Acrobat изпълнява всяко поле, което носи действие за изчисляване, и ги изпълнява в последователността, изброена в масива /CO на AcroForm, а не по ред на страниците. ApplyLoadedFieldCalculation прикачва действието за изчисляване към полето, което посочите; когато едно изчислено поле захранва друго – например междинна сума, която данъчен ред чете, последвана от обща сума, която чете и двете – трябва да се уверите, че подредбата в /CO изброява всеки вход преди полето, което го консумира, в противен случай общата сума ще прочете остаряла междинна сума и ще изостава с една стъпка назад

Как да накарате PDF формуляр да изчислява общите суми автоматично?

Методът ApplyLoadedFieldCalculation приема индекса на целевото поле, операция и имената на изходните полета, които трябва да агрегира. HotPDF запива AFSimple_Calculate("SUM", ["lineTotal.1", "lineTotal.2", ...]) в изчисляващото действие на целевото поле, а изброяването THPDFFieldCalcOp избира функцията: fccSum, fccAverage, fccProduct, fccMinimum и fccMaximum съответстват на Acrobat функциите SUM, AVG, PRD, MIN и MAX. Имената на източниците са имена на полета в AcroForm – същите низове, които GetLoadedFormFieldNames връща обратно, така че общата сума над три позиции и такса за доставка е просто едно извикване

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 върху общата сума не е козметично. Изчислението се извършва вътре в четеца, така че докато съвместим със скриптове софтуер не отвори файла, полето за обща сума няма собствено рендирано съдържание. Генерирането на поток за визуализация му дава стойност, която да показва междувременно – това е стойността, която процесът за печат или генераторът на миниатюри ще прочетат, когато скриптът не се изпълнява

Форматиране и валидиране на полета с пакета от помощници на AF

Методът ApplyLoadedFieldValidation прикачва форматиращите, клавишните и валидиращите скриптове с едно извикване, а подаденият THPDFFieldValidationKind определя кой помощник на Acrobat да се свърже. Предайте fvkCurrency и HotPDF ще запише AFNumber_Format(2,0,0,0,"$ ",true) като форматен скрипт със съответстващия му AFNumber_Keystroke; fvkNumber използва "." за разделител, fvkPercentage записва AFPercent_Format, а fvkDate записва AFDate_FormatEx("dd/mm/yyyy"), освен ако не предадете собствена маска в параметъра DateFormat. Специалните формати – fvkZipCode, fvkSSN и fvkPhone – се картографират към AFSpecial_Format с кода, който Acrobat резервира за всеки от тях, а fvkArbitraryMask приема низ с маска чрез аргумента Mask за всичко останало, което предварителните настройки не покриват. За числовите типове HotPDF също така прикачва скрипт AFRange_Validate, така че входящи данни извън диапазона да бъдат отхвърляни при потвърждаване

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

Защо трябва да регенерирате потока за визуализация (Appearance Stream)?

Тъй като стойността на низа и пикселите, които четецът изчертава, са две различни неща в PDF, и промяната на едното не променя другото. Полето съхранява стойността си в /V, но това, което четецът рендира, идва от неговия поток за визуализация – /AP /N. Редактирайте /V през графа от обекти и стриктният четец пак ще показва стария изглед, докато нещо не го регенерира. Речникът на формуляра съдържа флаг /NeedAppearances, който изисква от четеца да възстанови всеки изглед при отваряне, и Acrobat се съобразява с него – но вградените в браузъра визуализатори, мобилните четци, сървърите за печат и генераторите на миниатюри често го игнорират и изчертават потока, който вече присъства, или изобщо нищо. Методът EnsureLoadedFieldAppearanceStream затваря тази празнота, като изгражда самия обект /AP /N Form XObject от полетата /DA, /V и /Rect, так че рендираната стойност се пренася вътре във файла, вместо да зависи от това дали четецът ще я преизчертае

Къде спират да работят AF скриптовете

Всеки AF* скрипт зависи от това дали четецът разполага с JavaScript двигател, а повечето четци нямат такъв. Acrobat и няколко десктоп приложения изпълняват тези помощници; вградените PDF четци в Chrome, Edge, Firefox и основните мобилни платформи, заедно със сървърните визуализатори и конвейерите за печат, изобщо не изпълняват JavaScript във формуляри. В тези четци изчислението никога не се стартира и форматът никога не се прилага – полето просто показва потока за визуализация, с който последно е било записано, което е и причината генерирането на /AP /N да е толкова важно. Разглеждайте слоя AF* като удобство за потребителите със съвместими четци, но никога като гаранция. Всяка обща сума, която трябва да бъде коректна на сървъра – сума по фактура, по която ще фактурирате, или данъчна цифра, която ще подадете – трябва да бъде преизчислена от ваша страна след връщането на формуляра, тъй като не можете да приемете, че четецът, който го е попълнил, е изпълнил и един ред от вашия скрипт

Заслужава си да споменем още един случай: формуляри, получени като XFA. Динамичният XFA формуляр носи собствен двигател за изчисления в XML структура, за която помощниците на AF* не знаят нищо, така че прикачването на AFSimple_Calculate към поле на XFA няма да постигне нищо. Първо сведете (flatten) XFA формуляра до обикновен AcroForm, както е описано в статията за изравняване на XFA към AcroForm в Delphi, и трите метода тук ще се приложат към получените полета на AcroForm точно по същия начин, както към всеки друг зареден формуляр

Автоматично изчисляващите се и самоформатиращи се AcroForms се свеждат до три извиквания на функции върху зареден документ – прикачване на изчислението, прикачване на формата и валидирането и генериране на изгледа – последвани от SaveLoadedDocument. Показаният тук приложен програмен интерфейс (API) за заредени формуляри е част от HotPDF Component за Delphi и C++Builder, който документира пълните изброявания THPDFFieldValidationKind и THPDFFieldCalcOp заедно с останалата част от функциите на AcroForm