Teknik Makale

HotPDF ile Delphi'de Otomatik Hesaplanan PDF Formları

Satır öğelerinin kendi kendini topladığı ve tarihin kendini biçimlendirdiği bir fatura veya masraf PDF'i için AcroForm'una üç şeyin eklenmesi gerekir ve HotPDF bu üçünü de Delphi'de yüklü bir dosyaya ekler: AFSimple_Calculate toplamları için ApplyLoadedFieldCalculation, Acrobat AF biçim ve doğrulama yardımcıları için ApplyLoadedFieldValidation ve bir görüntüleyicinin fiilen çizdiği değeri hazırlamak için EnsureLoadedFieldAppearanceStream

HotPDF, Delphi ve C++Builder için yerel bir VCL PDF bileşenidir ve bu üç yöntem, sıfırdan oluşturduğunuz bir belge yerine LoadFromFile ile açtığınız bir belge üzerinde çalışır. Her biri AcroForm /Fields dizisine sıfır tabanlı bir alan dizinini kabul eder — GetLoadedFormFieldNames yönteminin döndürdüğü sıra ile aynıdır — ve doğrudan alan sözlüğüne yazar, bu nedenle tüm iş yükleme, dizinleme, bağlama ve SaveLoadedDocument işlemlerinden ibarettir. Alan kümesini oluşturmanın kendisi ayrı bir görevdir ve Delphi'de yüklü bir PDF'e AcroForm alanları ekleme makalesinde ele alınmıştır; bu sayfa ise mevcut bir alan kümesine aritmetik ve biçimlendirme kazandırmakla ilgilidir

Bir PDF formunun kendi alanlarını toplamak için neden betiklere ihtiyacı vardır?

Çünkü PDF'in bir elektronik tablo katmanı yoktur. Bir metin alanı formülü değil, bir dizeyi tutar ve bir betik eklenmedikçe satır öğesi değiştiğinde dosyadaki hiçbir şey toplamı yeniden hesaplamaz. Mekanizma, tetikleme olaylarını eylemlerle eşleyen alan ek eylemleri sözlüğüdür: /AA (ISO 32000-1 §12.7.5): karakterler geldikçe bir tuş vuruşu tetikleyicisi (K), görüntüleme için bir biçimlendirme tetikleyicisi, onayda bir doğrulama tetikleyicisi (V) ve formdaki herhangi bir alan değiştiğinde çalışan bir hesaplama tetikleyicisi (C). Her eylem bir JavaScript eylemidir: /S /JavaScript (ISO 32000-1 §12.6.4.14) ve çalıştırdığı betik, toplamı veya yeniden biçimlendirmeyi fiilen gerçekleştiren şeydir. Acrobat, hazır yardımcı kütüphaneleri — AFSimple_Calculate, AFNumber_Format, AFDate_FormatEx ve geri kalanlar — ile birlikte gelir, bu nedenle eklenen betik genellikle özel kod yerine o kütüphaneye yapılan tek satırlık bir çağrıdır

Çok adımlı bir formun doğru yanıtı verip vermeyeceğini bir ayrıntı belirler: hesaplama sırası. Acrobat, hesaplama eylemi taşıyan her alanı çalıştırır ve bunları sayfa sırasına göre değil, AcroForm /CO dizisinin listelediği sırada çalıştırır. ApplyLoadedFieldCalculation hesaplama eylemini adlandırdığınız alana ekler; bir hesaplanan alan diğerini beslediğinde — bir vergi satırının okuduğu bir alt toplam, ardından her ikisini de okuyan bir genel toplam gibi — /CO sırasının her girdiyi onu tüketen alandan önce listelediğini doğrulamanız gerekir; aksi takdirde genel toplam eski bir alt toplamı okur ve bir düzenleme geride kalır

Bir PDF formunun toplamları otomatik olarak hesaplamasını nasıl sağlarsınız?

ApplyLoadedFieldCalculation hedef alan dizinini, bir işlemi ve birleştirmesi gereken kaynak alanların adlarını kabul eder. HotPDF, hedef alan hesaplama eylemine AFSimple_Calculate("SUM", ["lineTotal.1", "lineTotal.2", ...]) yazar ve THPDFFieldCalcOp listesi işlevi belirler: fccSum, fccAverage, fccProduct, fccMinimum ve fccMaximum işlevleri sırasıyla Acrobat SUM, AVG, PRD, MIN ve MAX işlevlerine eşlenir. Kaynak adları, GetLoadedFormFieldNames yönteminin geri verdiği dize dizisiyle aynı olan AcroForm alan adlarıdır; bu sayede üç satır öğesi ve bir gönderim ücreti üzerindeki toplam tek bir çağrıdır

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;

Toplam üzerindeki EnsureLoadedFieldAppearanceStream çağrısı kozmetik değildir. Hesaplama görüntüleyicinin içinde çalışır, bu nedenle betik çalıştırabilen bir okuyucu dosyayı açana kadar toplam alanının kendine ait işlenmiş bir içeriği olmaz. Bir görünüm akışı hazırlamak (baking), bu süreçte gösterilecek bir değer sağlar; bu, betiği hiç çalıştırmadığında bir yazdırma işinin veya bir küçük resim oluşturucunun alacağı şeydir

AF yardımcı paketi ile alanları biçimlendirme ve doğrulama

ApplyLoadedFieldValidation biçim, tuş vuruşu ve doğrulama betiklerini tek bir çağrıda ekler ve geçirdiğiniz THPDFFieldValidationKind değeri hangi Acrobat yardımcısını bağlayacağını belirler. fvkCurrency değerini geçin; HotPDF, biçim betiği olarak AFNumber_Format(2,0,0,0,"$ ",true) yazar ve eşleşen AFNumber_Keystroke'u ekler; fvkNumber ayırıcı parametresi için "." kullanır, fvkPercentage eylemi AFPercent_Format üretir ve fvkDate eylemi DateFormat parametresinde kendi maskenizi geçmediğiniz sürece AFDate_FormatEx("dd/mm/yyyy") üretir. Özel biçimler — fvkZipCode, fvkSSN ve fvkPhone —, Acrobat'ın her biri için ayırdığı kodla AFSpecial_Format'a eşlenir ve fvkArbitraryMask, bu ön ayarların kapsamadığı her şey için Mask parametresi aracılığıyla bir maske dizesi kabul eder. Sayısal türler için HotPDF, AFRange_Validate betiğini de ekler, böylece aralık dışı girdi onayda reddedilir

// 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 yardımcıları yaygın şekilleri kapsar, bu nedenle onlar için asla elle JavaScript yazmazsınız. Bir alan hiçbir ön ayarın ifade etmediği bir mantığa ihtiyaç duyduğunda — bir sağlama toplamı, alanlar arası bir kural, özel bir düzenli ifade (regex) gibi —, doğrudan bir JavaScript dizesi kabul eden HotPDF ile AcroForm alanları ve eylemleri oluşturma makalesinde ele alınan alan düzeyindeki eylem yöntemleriyle ham betiği kendiniz bağlayın. Her iki yol da aynı /AA sözlüğüne yazar, bu nedenle bir alanda önceden ayarlanmış bir para birimi biçimi ve diğerinde elle yazılmış bir doğrulama aynı belgede bir arada bulunabilir

Görünüm akışını neden yeniden oluşturmalısınız?

Çünkü değer dizesi ve bir görüntüleyicinin çizdiği pikseller PDF'te iki farklı şeydir ve birini değiştirmek diğerini değiştirmez. Bir alan kendi değerini /V içinde saklar, ancak bir okuyucunun işlediği şey alan görünüm akışından, yani /AP /N'den gelir. Nesne grafiği aracılığıyla /V değerini düzenleyin; katı bir görüntüleyici, bir şey onu yeniden oluşturana kadar eski görünümü göstermeye devam eder. Form sözlüğü, görüntüleyiciden açılışta her görünümü yeniden oluşturmasını isteyen bir /NeedAppearances bayrağı taşır ve Acrobat buna uyar — ancak tarayıcıya gömülü işleyiciler, mobil okuyucular, yazdırma sunucuları ve küçük resim oluşturucular bunu sıklıkla görmezden gelir ve halihazırda mevcut olan akışı çizer veya hiçbir şey çizmez. EnsureLoadedFieldAppearanceStream, /AP /N Form XObject'ini alanın /DA, /V ve /Rect özelliklerinden kendisi oluşturarak bu boşluğu kapatır, böylece işlenen değer görüntüleyicinin onu yeniden çizmesine bağlı kalmak yerine dosyanın içinde seyahat eder

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

AF betiklerinin çalışmayı durdurduğu yer

Her AF* betiği görüntüleyicinin bir JavaScript motoruna sahip olmasına bağlıdır ve çoğu görüntüleyicide bu motor yoktur. Acrobat ve bir avuç masaüstü ürünü bu yardımcıları çalıştırır; Chrome, Edge, Firefox ve ana mobil platformlara yerleşik PDF görüntüleyicileri, sunucu tarafı pikselleştiriciler ve yazdırma veri yolları ile birlikte hiçbir form JavaScript'i çalıştırmaz. Bu okuyucularda hesaplama asla tetiklenmez ve biçim asla uygulanmaz — alan sadece en son kaydedildiği görünüm akışını gösterir, bu da /AP /N hazırlamanın (baking) neden önemli olduğunu açıklar. AF* katmanını, betik çalıştırabilen bir okuyucundaki kullanıcı alt kümesi için bir kolaylık olarak değerlendirin, asla bir garanti olarak değil. Sunucuda doğru olması gereken herhangi bir toplam — fatura tutarı, beyan edeceğiniz bir vergi miktarı —, form geri geldikten sonra sizin tarafınızda yeniden hesaplanmalıdır; çünkü onu dolduran okuyucunun betiğinizin tek bir satırını çalıştırdığını varsayamazsınız

Belirtilmeye değer bir durum daha var: XFA olarak gelen formlar. Dinamik bir XFA formu, kendi hesaplama motorunu AF* yardımcılarının hiçbir şey bilmediği bir XML veri yükünde taşır, bu nedenle bir XFA alanına ApplyLoadedFieldCalculation bağlamak hiçbir şey kazandırmaz. Delphi'de XFA'den AcroForm'a Düzleştirme makalesinde ele alındığı gibi, önce XFA formunu düz bir AcroForm'a düzleştirin; buradaki üç yöntem daha sonra ortaya çıkan AcroForm alanlarına diğer tüm yüklü formlarda olduğu gibi uygulanır

Otomatik hesaplanan ve kendi kendini biçimlendiren AcroForm'lar, yüklü bir belgede yapılan üç çağrıya — hesaplamayı bağla, biçim ve doğrulamayı bağla, görünümü hazırla — ve ardından SaveLoadedDocument çağrısına indirgenir. Burada gösterilen yüklü form API'si, AcroForm yüzeyinin geri kalanıyla birlikte tam THPDFFieldValidationKind and THPDFFieldCalcOp listelerini belgeleyen Delphi ve C++Builder için HotPDF Bileşeni'nin bir parçasıdır