Artikel Teknis

Formulir PDF Penghitungan Otomatis di Delphi dengan HotPDF

Faktur atau PDF pengeluaran di mana item baris menjumlahkan dirinya sendiri dan tanggal memformat dirinya sendiri memerlukan tiga hal yang dipasang pada AcroForm-nya, dan HotPDF menambahkan ketiganya ke file yang dimuat di Delphi: ApplyLoadedFieldCalculation untuk total AFSimple_Calculate, ApplyLoadedFieldValidation untuk format Acrobat AF dan pembantu validasi, dan EnsureLoadedFieldAppearanceStream untuk memanggang (bake) nilai yang sebenarnya digambar oleh viewer

HotPDF adalah komponen PDF VCL asli untuk Delphi dan C++Builder, dan ketiga metode ini beroperasi pada dokumen yang Anda buka dengan LoadFromFile daripada dokumen yang Anda buat dari awal. Masing-masing menerima indeks field berbasis nol ke dalam array AcroForm /Fields — urutan yang sama dengan yang dikembalikan oleh GetLoadedFormFieldNames — dan menulis langsung ke dalam kamus field, sehingga seluruh pekerjaannya adalah memuat, mengindeks, melampirkan, lalu SaveLoadedDocument. Membangun set field itu sendiri adalah tugas terpisah, yang dibahas dalam menambahkan field AcroForm ke PDF yang dimuat di Delphi, sedangkan halaman ini membahas tentang memberikan operasi aritmatika dan pemformatan pada set field yang sudah ada

Mengapa formulir PDF membutuhkan skrip untuk menjumlahkan field-nya sendiri?

Karena PDF tidak memiliki lapisan spreadsheet. Field teks menyimpan string, bukan rumus, dan tidak ada dalam file yang menghitung ulang total saat item baris berubah kecuali jika ada skrip yang dilampirkan untuk melakukannya. Mekanismenya adalah kamus tindakan tambahan field, /AA (ISO 32000-1 §12.7.5), yang memetakan peristiwa pemicu ke tindakan: pemicu penekanan tombol (keystroke) (K) saat karakter masuk, pemicu format untuk tampilan, pemicu validasi (V) saat commit, dan pemicu penghitungan (C) yang dipicu setiap kali ada field pada formulir yang berubah. Setiap tindakan adalah tindakan JavaScript, /S /JavaScript (ISO 32000-1 §12.6.4.14), dan skrip yang dijalankannya adalah apa yang sebenarnya melakukan penjumlahan atau pemformatan ulang. Acrobat menyertakan pustaka pembantu siap pakai — AFSimple_Calculate, AFNumber_Format, AFDate_FormatEx, dan sisanya — sehingga skrip yang dilampirkan biasanya berupa panggilan satu baris ke pustaka tersebut dan bukan kode khusus buatan sendiri

Satu detail menentukan apakah formulir multi-langkah memberikan jawaban yang benar: urutan penghitungan. Acrobat menjalankan setiap field yang membawa tindakan penghitungan, dan menjalankannya dalam urutan yang tercantum dalam array AcroForm /CO, bukan dalam urutan halaman. ApplyLoadedFieldCalculation melampirkan tindakan penghitungan ke field yang Anda tentukan; ketika satu field terhitung memberi umpan ke field lainnya — subtotal yang dibaca oleh baris pajak, kemudian total keseluruhan yang membaca keduanya — Anda harus memastikan urutan /CO mencantumkan setiap input sebelum field yang mengonsumsinya, atau total keseluruhan akan membaca subtotal yang usang dan tertinggal satu langkah perubahan

Bagaimana cara membuat formulir PDF menghitung total secara otomatis?

ApplyLoadedFieldCalculation menerima indeks field target, operasi, dan nama field sumber yang harus digabungkannya. HotPDF menulis AFSimple_Calculate("SUM", ["lineTotal.1", "lineTotal.2", ...]) ke dalam tindakan penghitungan field target, dan enumerasi THPDFFieldCalcOp memilih fungsinya: fccSum, fccAverage, fccProduct, fccMinimum, dan fccMaximum memetakan ke SUM, AVG, PRD, MIN, dan MAX milik Acrobat. Nama sumber adalah nama field AcroForm, string yang sama yang dikembalikan oleh GetLoadedFormFieldNames, sehingga total di atas tiga item baris dan biaya pengiriman adalah satu panggilan tunggal

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;

Pemicu dan format date

(这里原本是 `Formatting and validating fields with the AF helper suite`)

Memformat dan memvalidasi field dengan rangkaian pembantu AF

Panggilan EnsureLoadedFieldAppearanceStream pada total bukanlah kosmetik. Penangkapan (rendering) total berjalan di dalam viewer, jadi sebelum pembaca yang mampu menjalankan skrip membuka file, field total tidak memiliki konten terendernya sendiri. Memanggang (baking) stream tampilan memberinya angka untuk ditampilkan sementara waktu, yang merupakan apa yang akan diambil oleh pekerjaan cetak atau pembuat thumbnail ketika mereka tidak menjalankan skrip

Memformat dan memvalidasi field dengan rangkaian pembantu AF

ApplyLoadedFieldValidation melampirkan skrip format, keystroke, dan validasi dalam satu panggilan, dan THPDFFieldValidationKind yang Anda berikan menentukan pembantu Acrobat mana yang disambungkan. Berikan fvkCurrency dan HotPDF akan menulis AFNumber_Format(2,0,0,0,"$ ",true) sebagai skrip format dengan AFNumber_Keystroke yang cocok; fvkNumber menggunakan "." untuk argumen pemisah, fvkPercentage memancarkan AFPercent_Format, dan fvkDate memancarkan AFDate_FormatEx("dd/mm/yyyy") kecuali jika Anda meneruskan mask Anda sendiri dalam argumen DateFormat. Format khusus — fvkZipCode, fvkSSN, dan fvkPhone — memetakan ke AFSpecial_Format dengan kode yang dicadangkan Acrobat untuk masing-masing, dan fvkArbitraryMask menerima string mask melalui argumen Mask untuk apa pun yang tidak dicakup oleh preset tersebut. Untuk jenis numerik, HotPDF juga melampirkan skrip AFRange_Validate sehingga input di luar jangkauan ditolak saat commit

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

Pembantu AF mencakup bentuk-bentuk umum sehingga Anda tidak perlu menulis JavaScript secara manual untuk mereka. Ketika suatu field membutuhkan logika yang tidak diekspresikan oleh preset mana pun — checksum, aturan lintas-field, regex kustom — lampirkan skrip mentah itu sendiri dengan metode tindakan tingkat field yang dijelaskan dalam membangun field dan tindakan AcroForm dengan HotPDF, yang menerima string JavaScript secara langsung. Kedua jalur menulis ke dalam kamus /AA yang sama, sehingga format mata uang prasetel pada satu field dan validasi buatan sendiri pada field lainnya dapat berdampingan dalam dokumen yang sama

Mengapa Anda harus meregenerasi stream tampilan?

Field menyimpan nilainya di /V, tetapi apa yang dirender oleh pembaca berasal dari stream tampilan field, /AP /N. Edit /V melalui grafik objek dan viewer yang ketat akan tetap menunjukkan tampilan lama hingga ada sesuatu yang meregenerasinya. Kamus formulir membawa bendera /NeedAppearances yang meminta viewer untuk membangun kembali setiap tampilan saat dibuka, dan Acrobat mematuhinya — tetapi renderer tersemat browser, pembaca seluler, server cetak, dan pembuat thumbnail sering kali mengabaikannya dan menggambar stream apa pun yang sudah ada, atau tidak menggambar sama sekali. EnsureLoadedFieldAppearanceStream menutup celah tersebut dengan membangun Form XObject /AP /N itu sendiri, dari field /DA, /V, dan /Rect, sehingga nilai yang dirender berjalan di dalam file daripada bergantung pada viewer untuk menggambarnya kembali

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

Di mana skrip AF berhenti bekerja

Setiap skrip AF* bergantung pada viewer yang memiliki mesin JavaScript, dan sebagian besar viewer tidak memilikinya. Acrobat dan beberapa produk desktop menjalankan pembantu ini; viewer PDF yang terpasang di Chrome, Edge, Firefox, dan platform seluler utama, bersama dengan rasterizer sisi server dan saluran cetak, tidak menjalankan JavaScript formulir sama sekali. Pada pembaca tersebut, penghitungan tidak pernah dipicu dan format tidak pernah diterapkan — field hanya menampilkan stream tampilan apa pun yang terakhir kali disimpan, yang merupakan alasan utama mengapa memanggang /AP /N itu penting. Perlakukan lapisan AF* sebagai kenyamanan bagi subset pengguna pada pembaca yang mampu menjalankan skrip, jangan pernah sebagai jaminan. Setiap total yang harus benar di server — jumlah faktur yang akan Anda tagihkan, angka pajak yang akan Anda ajukan — harus dihitung ulang di pihak Anda setelah formulir kembali, karena Anda tidak dapat mengasumsikan pembaca yang mengisinya menjalankan satu baris pun dari skrip Anda

Satu kasus lagi yang patut disebutkan: formulir yang datang sebagai XFA. Formulir XFA dinamis membawa mesin penghitungannya sendiri dalam payload XML yang tidak diketahui oleh pembantu AF*, sehingga melampirkan AFSimple_Calculate to field XFA tidak menghasilkan apa-apa. Ratakan formulir XFA menjadi AcroForm biasa terlebih dahulu, seperti yang dibahas dalam perataan XFA ke AcroForm di Delphi, dan tiga metode di sini kemudian berlaku untuk field AcroForm yang dihasilkan persis seperti yang mereka lakukan pada formulir lain yang dimuat

AcroForm penghitungan otomatis dan pemformatan mandiri bermuara pada tiga panggilan pada dokumen yang dimuat — melampirkan penghitungan, melampirkan format dan validasi, memanggang tampilan — diikuti oleh SaveLoadedDocument. API formulir termuat yang ditunjukkan di sini adalah bagian dari HotPDF Component untuk Delphi dan C++Builder, yang mendokumentasikan enumerasi lengkap THPDFFieldValidationKind dan THPDFFieldCalcOp bersama dengan sisa permukaan AcroForm lainnya