Teknik Makale

PDF Yaşam Döngüsü Eylemleri: Delphi'de Catalog /AA ile Page /AA

Yerel Delphi ve C++Builder PDF kütüphanesi olan PDFlibPas, bir PDF belgesine otomatik davranışı asmak için iki ayrı yer verir: Catalog'un /AA sözlüğünde saklanan WillClose, WillSave, DidSave, WillPrint ve DidPrint gibi belge düzeyinde yaşam döngüsü eylemleri ve bunun yerine her Page nesnesinin kendi /AA sözlüğünde saklanan sayfa düzeyinde yaşam döngüsü eylemleri -Open ve Close. İki kapsayıcıyı karıştırmak, bir yaşam döngüsü eyleminin sessizce hiçbir şey yapmamasının tek en yaygın yoludur

Motive eden durumlar sıradandır. Bir finans ekibi, dosya yalnızca açıldığında değil, yazdırma gerçekten başladığı anda bir yazdırma zaman damgası basan ve kimin yazdırdığını günlüğe kaydeden bir hesap özeti şablonu ister. Formlar-yoğun bir iş akışı, okuyucunun PDF istemcisi pencereyi kapatmasına izin verilmeden önce alan değerlerinin otomatik olarak bir sunucuya itilmesine ihtiyaç duyar; böylece kapatılan bir sekme hiçbir zaman kayıp bir düzenleme anlamına gelmez. Çok sayfalı bir rapor, yalnızca o sayfa ekranda olduğu sürece görünen sayfaya özel bir banner ister. PDF gerçekte bu tür davranış için belge ve sayfanın altında üçüncü bir katman sunar -tek bir form alanının veya bağlantının kendi /A girdisine bağlı eylemler, etkileşimli form eylemleri ve JavaScript üzerine bir tamamlayıcı makalenin konusu- ama bu makale onun üzerindeki iki katmanda kalır: tüm belge ve tek bir sayfa

Hangi tetikleyiciler belge Catalog'unun /AA'sında yaşar?

Beş tetikleyici Catalog /AA sözlüğünde yaşar ve her biri tüm belgeyi etkileyen bir olay için tetiklenir, tek bir sayfayı değil. ISO 32000-1 §12.6.3 (Tetikleyici Olaylar), belge düzeyi anahtarlarını WC, WS, DS, WP ve DP olarak listeler -/AA sözlüğüne yazılan gerçek iki harfli adlar- sırasıyla WillClose, WillSave, DidSave, WillPrint ve DidPrint için ve PDFlibPas bu kümeyi TPDFlibDocumentActionTrigger numaralandırmasında tam olarak yansıtır: datWillClose, datWillSave, datDidSave, datWillPrint, datDidPrint. SetDocumentAction, beşinden herhangi birini bağlayan tek giriş noktasıdır ve aldığı ActionKind parametresi, düz bir URI'den bir betiğe ve bir hedef atlamasına kadar, kütüphanedeki her eylem-kurucu çağrısında paylaşılan on PDF_ACTION_BUILDER_* sabitinden biridir. Bir GoTo, uzak-dosya, gömülü-dosya veya Launch eyleminin tetiklendiğinde gerçekte ne yaptığı, nereye bağlandığından farklı bir sorudur ve bu, GoTo, uzak, gömülü ve başlatma eylemleri üzerine bir tamamlayıcı makalenin konusudur -bu makale, eylem-türü sorusu yerine kapsayıcı sorusuyla, Catalog mı Page mi, kalır

var
  Lib: TPDFlib;
begin
  Lib := TPDFlib.Create;
  try
    Lib.AddStandardFont(4);
    Lib.DrawText(40, 700, 'Quarterly statement');
    Lib.SetDocumentAction(datWillSave, PDF_ACTION_BUILDER_WEB,
      'https://example.com/audit/will-save', '', 0, 0);
    Lib.SetDocumentAction(datWillClose, PDF_ACTION_BUILDER_SUBMIT,
      'https://example.com/forms/submit', 'CustomerName;OrderTotal', 0, 0);
    Lib.SaveToFile('statement.pdf');
  finally
    Lib.Free;
  end;
end;

Bir sayfa düzeyi tetikleyici bir belge düzeyi olandan nasıl farklıdır?

Sayfa düzeyi bir tetikleyici yalnızca bağlı olduğu tek Page nesnesi için tetiklenir ve PDFlibPas onu Catalog'unki yerine o sayfanın kendi /AA sözlüğünde saklar. Yalnızca iki sayfa tetikleyicisi vardır, Open ve Close, ISO 32000-1'in bir sayfanın ek-eylemler sözlüğü için tanımladığı O ve C anahtarlarına karşılık gelir ve PDFlibPas bunları SetPageAction aracılığıyla patOpen ve patClose olarak sunar; bu, SelectPage ile o anda seçili olan sayfaya bağlanır -bir belge boyunca döngü yaparken bir çağrının her yere uygulanmasını beklediğiniz ilk seferde önemli bir ayrıntı, çünkü hiçbir zaman öyle olmaz. Her iki tür tetikleyiciyi bağlamak da dosyanın minimum PDF sürümünü yükseltir ve iki kapsayıcı farklı tabanlar ister: PDFlibPas, bir Catalog /AA girdisi ilk yazıldığında belgeyi en az PDF 1.4'e ve bir Page /AA girdisi ilk yazıldığında en az PDF 1.5'e yükseltir, içinde hangi eylem türü oturuyor olursa olsun. Bu, eylemin kendisinin kendi başına gerektirdiğinin üzerine katmanlanmış kapsayıcı düzeyinde bir gerekliliktir; bu yüzden tek başına yalnızca PDF 1.1 gerektirecek çıplak bir URI eylemi, bir sayfa-açma tetikleyicisine sarıldığında yine de tüm dosyayı PDF 1.5'e çeker

Lib.SelectPage(3);
Lib.SetPageAction(patOpen, PDF_ACTION_BUILDER_JAVASCRIPT,
  'app.alert("Section 3: internal review only");', '', 0, 0);
Lib.SetPageAction(patClose, PDF_ACTION_BUILDER_WEB,
  'https://example.com/analytics/page-3-closed', '', 0, 0);

Yaşam döngüsü eylemlerini okumak ve kaldırmak

GetDocumentActionInfo ve GetPageActionInfo'nun ikisi de bir TPDFlibActionInfo kaydı döndürür ve o tetikleyiciye hiçbir şey bağlı olmadığında Kind alanı akNone olarak geri gelir; bu yüzden kayıttaki başka herhangi bir alana güvenmeden önce Kind'ı kontrol edin -URI, JavaScript, FileName ve geri kalanı, yalnızca Kind'ın gerçekte bildirdiği tek eylem türü için anlamlıdır, çünkü aynı kayıt şekli kurucunun üretebileceği her eylem türünde yeniden kullanılır. RemoveDocumentAction ve RemovePageAction'ın her biri tek bir tetikleyiciyi temizler ve kaldıracak bir şey bulduklarında 1, tetikleyici zaten boşken 0 bildirir; kaldırılan girdi /AA sözlüğünde kalan sonuncu olduğunda, PDFlibPas, Catalog veya sayfada sarkan, anlamsız bir kapsayıcı bırakmak yerine artık boş olan /AA'yı kendisi siler

var
  Info: TPDFlibActionInfo;
begin
  Info := Lib.GetDocumentActionInfo(datWillSave);
  if Info.Kind = akURI then
    WriteLn('WillSave calls out to: ', string(Info.URI));

  if Lib.RemoveDocumentAction(datWillSave) = 1 then
    Lib.SetDocumentAction(datWillSave, PDF_ACTION_BUILDER_WEB,
      'https://example.com/audit/will-save-v2', '', 0, 0);
end;

PDF/A yaşam döngüsü eylemlerine hiç izin veriyor mu?

Hayır. PDF/A uyumluluğu, yalnızca riskli görünen eylem türlerini değil tüm ek-eylemler kapsayıcısını reddeder, çünkü ISO 19005, bir arşiv dosyasının o zamana kadar var olmayabilecek bir betik motoruna veya bir ağ bağlantısına bağımlı olmadan onlarca yıl sonra hâlâ aynı şekilde render edilmesi gerektiği varsayımıyla PDF'in etkileşimli-eylem modelini kısıtlar. Hem SetDocumentAction hem de SetPageAction'ın arkasındaki paylaşılan kurucu olan SetLifecycleAction, ActionKind'a hiç bakmadan önce PDFAMode'u kontrol eder; bu yüzden yalnızca bir şirket web sayfasını açan bir URI eylemi veya yalnızca bir sonraki sayfaya git anlamına gelen bir Named eylemi, tehlikeli olanla aynı ağa yakalanır -bir güvenlik incelemecisinin normalde işaretlemeyeceği hiçbir şey, yine de engellenir, çünkü kısıtlama duruma göre değil yapısaldır. Pratik tehlike, reddin sessiz olmasıdır: SetDocumentAction ve SetPageAction'ın ikisi de bir istisna fırlatmadan 0 döndürür; bu yüzden dönüş değerini hiç kontrol etmeyen bir çağrı yeri, taşıması gereken tetikleyiciyi sessizce eksik bırakan bir belge gönderir

Lib.SetPDFAMode(2); // PDF/A-1b
if Lib.SetDocumentAction(datWillClose, PDF_ACTION_BUILDER_NAMED,
     '', '', 0, 0) = 0 then
  // rejected: PDF/A-1b forbids Catalog /AA, even a plain Named action
  WriteLn('lifecycle action not attached');

Akılda tutulmaya değer bir asimetri var. RemoveDocumentAction ve RemovePageAction, PDFAMode'u hiç kontrol etmez; bu yüzden zaten uyumsuz yaşam döngüsü eylemleri taşıyan bir dosyayı yükleyip PDF/A-uyumlu bir kayda giderken onları çıkarmak beklendiği gibi çalışır -yalnızca yazma yolu, yeni bir tetikleyici bağlamak, uyumluluk moduna kapılanmıştır

Bir WillOpen tetikleyicisi olmadan açılışta-yazdırma nereye oturur?

Catalog /AA sözlüğünün hiç WillOpen girdisi yoktur, tasarım gereği -ISO 32000-1'de belge düzeyi /AA, tam olarak beş anahtar tanımlar, WillClose, WillSave, DidSave, WillPrint ve DidPrint ve o listedeki hiçbir şey yalnızca bir dosya açıldığı için tetiklenmez. Açılış-zamanı kancası, PDFlibPas'ın kendi çağrı ailesi aracılığıyla sunduğu ayrı bir Catalog girdisinde, /OpenAction'da yaşar; bunların arasında SetOpenActionJavaScript, SetOpenActionDestination ve SetOpenActionNamedDestination vardır ve hiçbiri /AA sözlüğüne veya TPDFlibDocumentActionTrigger numaralandırmasına hiç dokunmaz. Ancak iki mekanizma birleşir ve bir açılışta-yazdırma şablonunun gerçekte genellikle ihtiyaç duyduğu şey budur: şablonu, /OpenAction'ının yazdırma işini başlatacağı şekilde kurun, tipik olarak görüntüleyicinin kendi yazdırma komutunu çağıran bir JavaScript eylemi ve yazdırmanın kendisi, WillPrint ve DidPrint'e karşı çalışacak bir şey verir -sayfalar spool'a girmeden önce basılmış bir zaman damgası, bittiklerinde yazılan bir denetim girdisi

Bu tetikleyiciler PDF görüntüleyicileri genelinde ne kadar güvenilir?

PDF/A dışında bile her görüntüleyici bunları çalıştırmaz; bu yüzden bir yaşam döngüsü eylemini bir garanti değil bir istek olarak ele alın. Acrobat ve çoğu tam masaüstü okuyucusu tüm kümeyi sadakatle çalıştırır, ama gerçek dünya PDF tüketiminin büyük bir kısmı bir ek-eylemler sözlüğüne hiç dokunmaz: tarayıcıya-gömülü görüntüleyiciler, çoğu mobil okuyucu ve neredeyse her sunucu tarafı render veya metin çıkarma boru hattı ya /AA'yı tamamen göz ardı eder ya da yalnızca onun dar bir dilimini onurlandırır; başsız dönüşümün onlara bağlanacak bir yazdırma işlemi olmadığından WillPrint ve DidPrint tipik olarak en kötü performansı gösterir. Bir WillClose gönderim-formu eylemi form verisini yakalayan tek yolsa, güvenilir bir yol değildir -onu açık bir gönder düğmesiyle eşleştirin ve otomatik tetikleyiciyi onu destekleyen okuyucular için bir kolaylık olarak ele alın

Belge, sayfa ve alan tetikleyicileri, aynı altta yatan eylem-sözlüğü makinesinin üç katmanıdır ve kapsayıcı netleştiğinde, geri kalanı doğru ActionKind sabitini seçmek ve dönüş kodunu kontrol etmektir. Bu yaşam döngüsü tetikleyicileri, bu makalenin değindiği daha geniş eylem-kurucu API'siyle birlikte, standart PDFlibPas Delphi PDF kütüphanesi'nin bir parçası olarak, ürün belgelerinde tam tetikleyici ve eylem-türü referansıyla birlikte gönderilir