Technical Article

Delphi'de Etkileşimli PDF Formları: Eylemler ve JavaScript

Bir PDF form alanı kendi başına yalnızca bir değer tutan bir kutudur. Bir formu küçük bir uygulama gibi davranmaya zorlayan şey, ona eklenen eylemdir: bir bölümü gizleyen, kaydedilen değerleri bir dosyadan geri çeken, son sayfaya atlayan veya bir sütunu toplayan bir komut dosyası çalıştıran bir tıklama. Bunların hiçbiri alanın içinde yaşamaz. Bir eylem sözlüğünde (action dictionary) yaşar ve ISO 32000-1 tüm bu aileyi §12.6'da düzenler. Bu makale, bir Delphi programının en sık başvurduğu eylemleri ele alıyor ve PDFlibPas'ın her birini bir alana veya bağlantıya nasıl bağladığını gösteriyor.

Akılda tutulması gereken zihinsel model, bir alan ile bir eylemin bir referansla birleştirilmiş ayrı nesneler olduğudur. Bir bileşen ek açıklaması (widget annotation) veya bir bağlantı ek açıklaması (link annotation), /A girişinde bir eylem taşır. Eylem, üzerinde çalıştığı alanı dizine göre değil, başlığına göre adlandırır, bu nedenle bir alana verdiğiniz başlık, sonraki her eylemin onu bulmak için kullandığı tanıtıcıdır (handle). Bu ayrım netleştikten sonra, API rastgele çağrılardan oluşan bir torba gibi görünmeyi bırakır ve dört tür fiile uygulanan tek bir şablon gibi görünmeye başlar.

Adlandırılmış eylemler: sayfa numarası olmadan gezinme

En basit eylemler hiçbir parametre taşımaz. ISO 32000-1 §12.6.4.11, Tablo 194, adlandırılmış eylemleri tanımlar: görüntüleyici, saklanan bir hedefi takip etmek yerine çalışma zamanında sembolik bir adı yorumlar. Dört ad evrensel olarak desteklenir ve bunlar tam olarak bir okuyucunun araç çubuğundan beklediği adlardır: NextPage, PrevPage, FirstPage ve LastPage. Hedef, görüntüleyicinin o anda gösterdiği sayfaya göre göreceli olduğundan, bu şekilde oluşturulmuş bir İleri düğmesi, siz bir hedef hesaplamadan her sayfada çalışır.

PDFlibPas'ta adlandırılmış bir eylem, geçerli sayfadaki etkin bir dikdörtgene (hotspot) eklenir. Dördüncü ve beşinci tamsayı bağımsız değişkenleri, fiili ve görünümü seçer.

// NamedActionType: 0 = NextPage, 1 = PrevPage, 2 = FirstPage, 3 = LastPage
// Options bit 0 (value 1) draws a border around the hotspot
Pdf.AddLinkToNamedAction(500, 560, 60, 18, 0, 1);   // Next
Pdf.AddLinkToNamedAction(40, 560, 60, 18, 1, 1);    // Previous
Pdf.AddLinkToNamedAction(110, 560, 60, 18, 3, 1);   // jump to last page

Senkronize tutulacak bir hedef yoktur, asıl mesele de budur. Adlandırılmış bir eylem, en başta hiçbir sayfayı adlandırmadığı için sayfa ekleme ve silme işlemlerinden etkilenmez. Bunu, belge büyüdüğü an yeniden numaralandırmanız gereken bir hedef sayfa dizinini saklayan açık bir git (go-to) bağlantısıyla karşılaştırın.

Gizle eylemi ve dizi tuzağı

Gizle (Hide) eylemi, ISO 32000-1 §12.6.4.10, Tablo 196, bir veya daha fazla alanın görünürlüğünü değiştirir. Betik yazmadan göster ve gizle davranışları oluşturmanın en temiz yoludur; Detayları Göster bağlantısı veya birini göstermenin diğerini gizlediği karşılıklı olarak dışlayıcı iki panel için istediğiniz şeydir. Eylem, /T girişinde bir hedef ve yönü belirleyen bir boolean /H taşır: doğru (true) olduğunda gizle, yanlış (false) olduğunda göster.

İşin püf noktası tamamen bu hedefin nasıl kodlandığında yatmaktadır ve bu, sizin makinenizde çalışan ancak bir müşterinin makinesinde başarısız olan bir form üreten türden bir ayrıntıdır. Eylem tek bir alanı adlandırdığında, /T tek bir metin dizesi olarak yazılır. Birkaç alanı adlandırdığında, /T metin dizelerinden oluşan bir dizi olarak yazılır. Eski görüntüleyiciler, tek öğeli bir diziye yalın bir dizeyle aynı şekilde davranmaz, bu nedenle kodlamanın sayıya göre dallanması gerekir: en geniş okuyucu yelpazesinin buna uyması için tek bir ad, bir uzunluğundaki bir dizi olarak değil, bir dize olarak yayınlanmalıdır. PDFlibPas bu kararı sizin yerinize verir. Virgül, noktalı virgül veya satır sonu ile ayrılmış alan adlarını iletirsiniz ve yazar, tek bir ad için tek bir dize, iki veya daha fazı için bir dizi yayınlar.

// HideFlag non-zero hides the listed fields (/H true); zero shows them.
// One name -> /T is a text string. Two or more -> /T is an array of strings.
Pdf.AddLinkToHideField(40, 700, 90, 18, 'ShippingAddress', 1, 1);
Pdf.AddLinkToHideField(140, 700, 90, 18,
  'ShippingName,ShippingAddress,ShippingZip', 1, 1);

Eylem hiçbir harici kaynağa başvurmadığı için PDF/A ile uyumlu kalır. İlettiğiniz adlar tam nitelikli alan başlıklarıdır, bu nedenle bir grup içindeki bir alt alanın, yalın yaprak adı yerine tam noktalı yolu üzerinden adreslenmesi gerekir.

ImportData: FDF'den önceden doldurma

Gizle eyleminin sayfada halihazırda bulunanları yeniden düzenlediği yerde, veri aktarma (import-data) eylemi değerleri sayfanın dışından getirir. ISO 32000-1 §12.6.4.8, Tablo 198, bunu AcroForm'u diskteki bir Form Veri Biçimi (FDF) dosyasından dolduran bir eylem olarak tanımlar. Bu, FDF dosyasının PDF'nin yanında gönderildiği ve kurallı alan değerlerini tuttuğu Örnek verileri yeniden yükle veya Varsayılana sıfırla kontrolünün arkasındaki eylemdir. Çağrı diğerlerini yansıtır; etkin dikdörtgeni, FDF'nin yolunu ve bir görünüm bit maskesini alır: Pdf.AddLinkToImportData(40, 660, 120, 18, 'defaults.fdf', 1). Dosyanın oluşturma zamanında var olması gerekmez, ancak kullanıcı tıkladığında mevcut olmalıdır ve yoldaki ters eğik çizgiler sizin için PDF kurallı eğik çizgi formuna yeniden yazılır.

Sık karşılaşılan bir sürpriz olması nedeniyle bir kısıtlamayı açıkça belirtmekte fayda var. Veri aktarma eylemi harici bir dosyayı işaret eder, bu nedenle PDF/A'da izin verilmez. Belge PDF/A modundayken, doğrulama hatası veren bir dosya oluşturmak yerine çağrı sıfır döndürür ve hiçbir şey eklemez. İşlem hattınız arşivsel çıktıyı hedefliyorsa, ön doldurma işlemi tıklama sonrasına ertelenerek değil, oluşturma zamanında alan değerleri doğrudan yazılarak gerçekleşmelidir.

JavaScript: genel paketler ve eylem başına betikler

Gösterme, gizleme ve içe aktarmanın ötesine geçen mantıklar için, eylem ailesi belge düzeyindeki JavaScript'e ulaşır. Bir komut dosyasının yaşayabileceği iki farklı yer vardır ve aralarındaki fark önemlidir. Belge düzeyindeki bir JavaScript paketi, tüm dosya için bir kez depolanır ve belge açıldığında çalışır; bu da onu işlev tanımları ve paylaşılan durumlar için doğru yuva haline getirir. Eylem başına bir komut dosyası, bir bağlantıya veya alana eklenir ve yalnızca o nesne etkinleştirildiğinde çalışır; bu da onu paketin zaten tanımladığı bir işlevi çağıran tek satır için doğru yuva haline getirir.

PDFlibPas her ikisini de sunar. AddGlobalJavaScript, belge düzeyinde adlandırılmış bir paketi depolar; bir adı yeniden kullanmak, altında depolanan her şeyin yerini alır. AddLinkToJavaScript, tıklama ile yürütülmesi için bir komut dosyasını etkin bir alana bağlar.

// Document-level package: define a reusable function once.
Pdf.AddGlobalJavaScript('Totals',
  'function recalcTotal() {' +
  '  var net = this.getField("Net").value;' +
  '  var tax = this.getField("Tax").value;' +
  '  this.getField("Gross").value = Number(net) + Number(tax);' +
  '}');

// Per-action script on a link: just call the shared function.
Pdf.AddLinkToJavaScript(40, 620, 100, 18, 'recalcTotal();', 1);

İşlevi genel pakette tutmak ve çağrıyı bağlantıda yapmak bir stil tercihi değildir. Aynı gövdenin ihtiyaç duyan her kontrolde yinelenmesini önler ve komut dosyası devre dışı bırakılmış bir görüntüleyicinin, hatalı biçimlendirilmiş bir satır içi veri yığınında takılıp kalmak yerine tıklamada hiçbir şey yapmamasını sağlar. Ayrıca eylem başına girişleri küçük tutarak dosyayı daha sonra incelediğinizde okunabilir kalmasını sağlar.

Alanlar, alt alanlar ve sonucu dondurma

Eylemlerin üzerinde çalışacağı alanlara ihtiyacı vardır, bu nedenle bir alanın nasıl oluştuğunu görmek faydalı olacaktır. NewFormField geçerli sayfada bir alan oluşturur ve bunun dizinini döndürür; tamsayı türü çeşidi seçer: 1 Metin, 2 Buton, 3 Onay Kutusu, 4 Radyo Düğmesi, 5 Seçim, 6 İmza ve 7 alt alanlara sahip olan ancak kendisi hiçbir şey çizmeyen bir Üst (Parent) alandır. İlettiğiniz başlık nokta içeremez, çünkü nokta, eylemlerin alt alanlara hitap etmek için kullandığı tam nitelikli adlardaki ayırıcıdır.

Radyo grupları ve hiyerarşik formlar, bir üst alana alt alanlar verilerek oluşturulur. NewChildFormField, adlandırılmış bir üst alanın altına bir alt alan ekler ve radyo ile seçim durumlarında AddFormFieldSub, bağımsız seçenekleri ekler ve her birini konumlandırmak için kullandığınız geçici bir dizin döndürür. Etkileşimli aşama bittiğinde ve bir alanı dondurmak istiyorsanız, böylece mevcut görünümü kalıcı sayfa içeriği haline gelir, FlattenFormField alanı sayfaya çizer ve formdan kaldırır. Düzleştirme (flatten) işleminden sonra, sonraki alanların dizinleri birer birer aşağı kayar; bu, bir döngüde birkaç alanı düzleştirirken hatırlamanız gereken tek şeydir。

var
  Pdf: TPDFlib;
  FldShip: Integer;
begin
  Pdf := TPDFlib.Create;
  try
    Pdf.SetOrigin(1);          // top-left origin
    Pdf.SetPageSize('A4');
    Pdf.NewPage;

    // A text field the Hide action will target by its title.
    FldShip := Pdf.NewFormField('ShippingAddress', 1);
    Pdf.SetFormFieldBounds(FldShip, 40, 120, 240, 20);
    Pdf.SetFormFieldValue(FldShip, '');

    // Wire a Hide link and a navigation link to this page.
    Pdf.DrawText(40, 110, 'Toggle shipping block:');
    Pdf.AddLinkToHideField(220, 100, 70, 16, 'ShippingAddress', 1, 1);
    Pdf.AddLinkToNamedAction(500, 800, 60, 18, 3, 1);  // Last page

    // A document-level script available to every event in the file.
    Pdf.AddGlobalJavaScript('OnOpen',
      'app.alert("Form ready", 3);');

    // Freeze the field if the output should no longer be editable.
    // Pdf.FlattenFormField(FldShip);

    if Pdf.SaveToFile('form_actions.pdf') <> 1 then
      raise Exception.Create('Save failed');
  finally
    Pdf.Free;
  end;
end;

Düzleştirme (flatten) çağrısı bilerek yorum satırı yapılmıştır. Bunu dışarıda bırakırsanız, belge, eylemleri okuyucuda tetiklenen canlı bir form olarak gönderilir. Etkinleştirirseniz, alan statik işaretlere dönüştürülür; form tamamlandığında ve sonucun sabit bir kayıt olarak gitmesi gerektiğinde bunu istersiniz. Dondurup dondurmadığınıza bağlı olarak aynı alan, aynı kod, iki çok farklı belge ortaya çıkarır.

Doğru fiili seçmek

Dört eylem, neye dokunduklarına göre temiz bir şekilde ayrılır. Adlandırılmış bir eylem, görünüm alanını hareket ettirir ve alana ihtiyaç duymaz. Bir Gizle (Hide) eylemi görünürlüğü değiştirir ve alan başlıklarına ihtiyaç duyar; dizeye karşı dizi kodlaması sizin için halledilir. Veri aktarma eylemi diskteki bir dosyaya ulaşır ve bu nedenle PDF/A'da yasaktır. Bir JavaScript eylemi isteğe bağlı mantığı çalıştırır ve en iyi şekilde genel bir işlev paketi ile eylem başına küçük çağrılar arasında bölünür. İşi yapan en basit eyleme yönelin: Gizle eylemi, gizli bayrağı ayarlayan bir betikten daha taşınabilirdir ve adlandırılmış bir eylem, saklanan bir sayfa hedefinden daha dayanıklıdır çünkü korunacak bir numara yoktur.

Buradan itibaren, birbirine yakın iki konu resmi tamamlıyor. Form, erişilebilir bir belgenin parçasıysa, ekran okuyucuların yürüdüğü yapı ağacı etiketli PDF ve erişilebilirlik yapısı hakkındaki makalemizde ele alınmıştır. Tamamlanan formun kilitlenmesi ve imzalanması gerektiğinde, iş akışı uyumluluk ve imzalama tezgahı kılavuzunda açıklanmaktadır. Her üçü de, bu blogun başka yerlerinde ele alınan oluşturma, form ve imza API'lerinin yanı sıra Delphi için PDF kütüphanesi olarak sunulan aynı motor üzerine kuruludur.