PDFlibPas, library PDF native Delphi dan C++Builder, memberikan sebuah dokumen PDF dua tempat terpisah untuk menggantungkan perilaku otomatis: action lifecycle tingkat-dokumen seperti WillClose, WillSave, DidSave, WillPrint dan DidPrint, disimpan dalam dictionary /AA milik Catalog, dan action lifecycle tingkat-halaman — Open dan Close — disimpan dalam dictionary /AA milik masing-masing objek Page sebagai gantinya. Mencampuradukkan kedua container itu adalah cara paling umum tunggal sebuah action lifecycle diam-diam tidak melakukan apa-apa
Kasus yang memotivasi ini biasa saja. Sebuah tim keuangan menginginkan sebuah template statement yang mencap timestamp cetak dan mencatat siapa yang mencetaknya persis saat pencetakan benar-benar dimulai, bukan ketika file itu sekadar terbuka. Sebuah workflow berat-form membutuhkan nilai field yang didorong ke sebuah server secara otomatis sebelum klien PDF reader diizinkan menutup window, sehingga sebuah tab yang tertutup tidak pernah berarti sebuah edit yang hilang. Sebuah laporan multi-halaman menginginkan sebuah banner khusus-halaman yang muncul hanya selagi halaman itu berada di layar. PDF sebenarnya menawarkan sebuah tingkat ketiga di bawah dokumen dan halaman untuk perilaku semacam ini — action yang dilekatkan ke entry /A milik sebuah field form atau link individual — topik sebuah artikel pendamping tentang action form interaktif dan JavaScript — tetapi artikel ini tetap pada kedua tingkat di atasnya: seluruh dokumen, dan satu halaman tunggal
Trigger apa yang hidup di /AA Catalog dokumen?
Lima trigger hidup di dictionary /AA Catalog, dan setiap satunya terpicu untuk sebuah event yang memengaruhi seluruh dokumen, bukan satu halaman tunggal. ISO 32000-1 §12.6.3 (Trigger Events) mendaftarkan key tingkat-dokumen sebagai WC, WS, DS, WP dan DP — nama dua-huruf literal yang ditulis ke dalam dictionary /AA — untuk WillClose, WillSave, DidSave, WillPrint dan DidPrint secara berurutan, dan PDFlibPas mencerminkan set itu persis dalam enumerasi TPDFlibDocumentActionTrigger: datWillClose, datWillSave, datDidSave, datWillPrint, datDidPrint. SetDocumentAction adalah titik masuk tunggal yang melekatkan salah satu dari lima itu, dan parameter ActionKind yang diterimanya adalah salah satu dari sepuluh konstanta PDF_ACTION_BUILDER_* yang dibagi di seluruh pemanggilan action-builder dalam library, dari sebuah URI polos hingga sebuah script hingga sebuah lompatan destination. Apa yang sebenarnya dilakukan sebuah action GoTo, remote-file, embedded-file atau Launch begitu terpicu adalah pertanyaan berbeda dari di mana ia dilekatkan, dan itu menjadi topik sebuah artikel pendamping tentang action GoTo, remote, embedded dan launch — yang ini tetap pada pertanyaan container, Catalog atau Page, alih-alih pertanyaan jenis-action
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;
Bagaimana sebuah trigger tingkat-halaman berbeda dari yang tingkat-dokumen?
Sebuah trigger tingkat-halaman hanya terpicu untuk objek Page tunggal tempatnya dilekatkan, dan PDFlibPas menyimpannya dalam dictionary /AA milik halaman itu sendiri alih-alih milik Catalog. Hanya ada dua trigger halaman, Open dan Close, sesuai dengan key O dan C yang didefinisikan ISO 32000-1 untuk dictionary additional-actions sebuah halaman, dan PDFlibPas mengeksposnya sebagai patOpen dan patClose lewat SetPageAction, yang melekat ke halaman mana pun yang sedang terpilih lewat SelectPage — sebuah detail yang penting kali pertama Anda melakukan loop di seluruh dokumen mengharapkan satu pemanggilan berlaku di mana pun, karena itu tidak pernah demikian. Melekatkan salah satu jenis trigger juga menaikkan versi PDF minimum file tersebut, dan kedua container itu meminta batas bawah berbeda: PDFlibPas menaikkan dokumen ke setidaknya PDF 1.4 kali pertama ia menulis sebuah entry /AA Catalog, dan ke setidaknya PDF 1.5 kali pertama ia menulis sebuah entry /AA Page, terlepas dari jenis action mana yang duduk di dalamnya. Itu adalah sebuah persyaratan tingkat-container yang dilapiskan di atas apa pun yang dibutuhkan action itu sendiri dengan sendirinya, sehingga sebuah action URI telanjang yang hanya akan membutuhkan PDF 1.1 dengan sendirinya tetap menarik seluruh file naik ke PDF 1.5 begitu dibungkus dalam sebuah trigger page-open
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);
Membaca dan menghapus action lifecycle
GetDocumentActionInfo dan GetPageActionInfo keduanya mengembalikan sebuah record TPDFlibActionInfo, dan field Kind kembali akNone kapan pun trigger itu tidak memiliki apa pun yang terlekat, sehingga periksa Kind sebelum memercayai field lain mana pun pada record itu — URI, JavaScript, FileName dan sisanya hanya bermakna untuk satu jenis action yang benar-benar dilaporkan Kind, karena bentuk record yang sama digunakan kembali di seluruh setiap tipe action yang bisa dihasilkan builder tersebut. RemoveDocumentAction dan RemovePageAction masing-masing menghapus satu trigger tunggal dan melaporkan 1 ketika menemukan sesuatu untuk dihapus, 0 ketika trigger itu sudah kosong; ketika entry yang dihapus adalah yang terakhir tersisa dalam dictionary /AA, PDFlibPas menghapus /AA yang sekarang kosong itu sendiri alih-alih meninggalkan sebuah container menggantung yang tidak berarti di Catalog atau halaman tersebut
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;
Apakah PDF/A mengizinkan action lifecycle sama sekali?
Tidak. Kesesuaian PDF/A menolak seluruh container additional-actions, bukan hanya jenis action yang terdengar berisiko, karena ISO 19005 membatasi model action-interaktif PDF berdasarkan asumsi bahwa sebuah file arsip harus dirender dengan cara yang sama beberapa dekade dari sekarang, tanpa bergantung pada sebuah mesin scripting atau sebuah koneksi jaringan yang mungkin tidak ada saat itu. SetLifecycleAction, builder bersama di balik baik SetDocumentAction maupun SetPageAction, memeriksa PDFAMode sebelum ia pernah melihat ActionKind, sehingga sebuah action URI yang sekadar membuka sebuah halaman web perusahaan atau sebuah action Named yang hanya berarti pergi ke halaman berikutnya tertangkap dalam jaring yang sama seperti yang berbahaya — tidak ada apa pun yang biasanya akan ditandai seorang security reviewer, tetap terhalang, karena pembatasan itu struktural alih-alih kasus-per-kasus. Bahaya praktisnya adalah penolakan itu diam: baik SetDocumentAction maupun SetPageAction mengembalikan 0 tanpa memunculkan sebuah exception, sehingga sebuah titik pemanggilan yang tidak pernah memeriksa nilai kembali merilis sebuah dokumen yang diam-diam kehilangan trigger yang seharusnya dibawanya
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');
Satu asimetri layak diingat. Baik RemoveDocumentAction maupun RemovePageAction tidak pernah memeriksa PDFAMode, sehingga memuat sebuah file yang sudah membawa action lifecycle tidak-sesuai dan menghapusnya dalam perjalanan menuju sebuah save yang sesuai-PDF/A bekerja persis seperti diharapkan — hanya jalur tulis, melekatkan sebuah trigger baru, yang dipagari mode kepatuhan
Di mana print-on-open cocok tanpa sebuah trigger WillOpen?
Dictionary /AA Catalog sama sekali tidak memiliki entry WillOpen, sesuai desain — /AA tingkat-dokumen dalam ISO 32000-1 mendefinisikan persis lima key, WillClose, WillSave, DidSave, WillPrint dan DidPrint, dan tidak ada apa pun dalam daftar itu yang terpicu murni karena sebuah file dibuka. Hook waktu-buka hidup dalam sebuah entry Catalog terpisah, /OpenAction, yang diekspos PDFlibPas lewat keluarga pemanggilannya sendiri, SetOpenActionJavaScript, SetOpenActionDestination dan SetOpenActionNamedDestination di antaranya, tak satu pun menyentuh dictionary /AA atau enumerasi TPDFlibDocumentActionTrigger sama sekali. Kedua mekanisme itu memang bisa dikombinasikan, meski begitu, dan itulah biasanya yang sebenarnya dibutuhkan sebuah template print-on-open: bangun template itu sehingga /OpenAction-nya memulai job cetak, biasanya sebuah action JavaScript yang memanggil perintah cetak milik viewer sendiri, dan pencetakan itu sendiri adalah yang memberi WillPrint dan DidPrint sesuatu untuk dijalankan terhadapnya — sebuah timestamp dicap sebelum halaman di-spool, sebuah entry audit ditulis begitu selesai
Seberapa andal trigger ini di seluruh PDF viewer?
Tidak setiap viewer menjalankannya, bahkan di luar PDF/A, jadi perlakukan sebuah action lifecycle sebagai sebuah permintaan alih-alih sebuah jaminan. Acrobat dan kebanyakan pembaca desktop penuh mengeksekusi seluruh set itu dengan setia, tetapi sebagian besar konsumsi PDF dunia-nyata sama sekali tidak pernah menyentuh sebuah dictionary additional-actions: viewer tersemat-browser, kebanyakan pembaca mobile, dan hampir setiap pipeline rendering atau ekstraksi-teks sisi-server baik mengabaikan /AA sepenuhnya atau hanya menghormati sebagian sempit darinya, dengan WillPrint dan DidPrint biasanya bernasib terburuk karena konversi headless tidak memiliki operasi cetak untuk mereka pancangkan. Jika sebuah action submit-form WillClose adalah satu-satunya jalur yang menangkap data form, itu bukan jalur yang andal — pasangkan dengan sebuah tombol submit eksplisit, dan perlakukan trigger otomatis sebagai sebuah kenyamanan untuk pembaca yang kebetulan mendukungnya
Trigger dokumen, halaman dan field adalah tiga tingkat dari mesin action-dictionary yang mendasari sama, dan begitu container-nya jelas, sisanya adalah memilih konstanta ActionKind yang tepat dan memeriksa kode kembalinya. Trigger lifecycle ini, beserta API action-builder yang lebih luas yang disentuh artikel ini, disertakan sebagai bagian dari PDFlibPas Delphi PDF library standar, dengan referensi trigger dan jenis-action lengkap dalam dokumentasi produk