PDFlibPas memberikan developer Delphi dan C++Builder tiga jenis action untuk navigasi yang meninggalkan halaman saat ini: GoToR (Go To Remote) membuka sebuah halaman spesifik di file PDF lain, GoToE (Go To Embedded) membuka sebuah file PDF yang disematkan di dalam dokumen saat ini, dan Launch menjalankan sebuah program eksternal atau membuka sebuah file lewat shell sistem operasi. Ketiganya hidup di ISO 32000-1 §12.6.4, bagian Action Types yang juga mendefinisikan action GoTo sehari-hari, dan masing-masing membawa jebakannya sendiri bagi yang tidak waspada: sebuah nomor halaman yang berarti berbeda bergantung pemanggilan mana yang membangunnya, sebuah target yang sebuah nama alih-alih path file, dan sepasang parameter string yang terlihat identik tetapi melayani dua viewer berbeda
Tidak ada apa pun dari ini hipotetis. Sebuah paket referensi teknis — sebuah manual utama, sebuah PDF spesifikasi yang diperbarui distributor dengan jadwalnya sendiri, sebuah utility kalibrasi yang diinstal berdampingan dengan keduanya — mengandalkan persis pengkabelan lintas-dokumen semacam ini: sebuah cross-reference yang harus mendarat di halaman 5 file specs, sebuah data sheet yang layak dikirim di dalam manual alih-alih di sebelahnya, sebuah link yang menyerahkan langsung ke tool kalibrasi. Artikel ini adalah bayangan cermin dari membaca kembali action bookmark dan anotasi dari sebuah PDF yang sudah ada: tulisan itu membahas mengonsumsi sebuah action GoToR, Launch, atau GoToE yang sudah ditulis producer lain ke dalam sebuah file; yang ini membahas membangun ketiga jenis action yang sama itu dari nol, termasuk aturan tingkat-field yang diberlakukan PDFlibPas sebelum ia meng-commit satu byte pun
Tiga cara sebuah action PDF meninggalkan halaman saat ini
PDFlibPas memisahkan navigasi lokal dari segala sesuatu lainnya pada key /S milik action, dan GoToR, GoToE, dan Launch adalah tiga subtipe yang targetnya berada di luar halaman saat ini: GoToR di bawah ISO 32000-1 §12.6.4.3, GoToE di bawah §12.6.4.4, dan Launch di bawah §12.6.4.5, semuanya di dalam bagian §12.6.4 Action Types yang lebih luas yang juga mendefinisikan action GoTo sehari-hari. Destination sebuah action GoTo polos menamai sebuah objek halaman yang sudah ada di dalam dokumen, sehingga PDFlibPas bisa memvalidasinya segera; GoToR dan GoToE tidak bisa melakukan itu dengan cara yang sama, karena file eksternal itu bahkan mungkin tidak ada di mesin ini dan jumlah halaman sebuah embedded file bukan sesuatu yang dilacak dokumen host, sehingga keduanya membawa sebuah referensi yang belum terselesaikan alih-alih sebuah link keras — sebuah spesifikasi file plus sebuah destination untuk GoToR, sebuah nama embedded-file plus sebuah halaman target untuk GoToE — sementara Launch sepenuhnya menjatuhkan konsep destination dan sekadar menamai sesuatu untuk dijalankan atau dibuka sistem operasi. Pemisahan itu muncul sebagai dua keluarga pemanggilan di sisi tulis: builder tingkat-tinggi, sekali-jalan seperti AddLinkToFile, AddLinkToFileEx, AddLinkToEmbeddedPDF, dan AddLinkToLocalFile membuat sebuah anotasi link page-hotspot dan action-nya bersama, mencakup kebanyakan layout sungguhan — sebuah baris teks atau sebuah icon yang diklik seorang pembaca — sementara setter tingkat-lebih-rendah seperti SetActionRemoteDestinationEx, SetActionLaunchOptions, dan padanan AddActionNext*-nya melekatkan atau menggantikan sebuah action pada sesuatu yang sudah Anda pegang sebuah handle-nya: sebuah bookmark yang sudah ada, sebuah trigger form-field, atau sebuah event lifecycle tingkat-dokumen atau tingkat-halaman. Kedua keluarga itu berakhir menulis bentuk dictionary yang sama; perbedaannya adalah di mana Anda berdiri saat memanggilnya, dan, sebagaimana dibahas bagian berikutnya, apa arti sebuah nomor halaman saat Anda melakukannya
Bagaimana Anda membangun sebuah link GoToR yang membuka sebuah halaman di file PDF lain?
Sebuah action GoToR membutuhkan dua hal — sebuah spesifikasi file dan sebuah destination di dalam file itu — dan PDFlibPas mengekspos dua pemanggilan berbeda untuk menyediakan bagian kedua itu, masing-masing dengan konvensi penomoran-halaman sendiri. AddLinkToFile dan AddLinkToFileEx, builder page-hotspot tingkat-tinggi, memvalidasi argumen Page atau DestPage-nya sebagai lebih besar dari nol, penomoran berbasis-1 yang sama yang digunakan PDFlibPas di mana pun lagi, termasuk SelectPage. SetActionRemoteDestinationEx, setter tingkat-lebih-rendah yang digunakan untuk melekatkan atau menggantikan sebuah action GoToR pada sesuatu yang sudah Anda miliki handle-nya, sebaliknya memvalidasi DestPage sebagai lebih besar dari atau sama dengan nol dan menulisnya langsung ke dalam array destination eksplisit action itu tanpa penyesuaian: ia menginginkan indeks halaman mentah, berbasis-nol dokumen target, penomoran yang dispesifikasikan ISO 32000-1 untuk sebuah destination eksplisit remote. Panggil setter tingkat-rendah dengan angka yang sama yang akan Anda serahkan ke builder tingkat-tinggi dan link itu terbuka satu halaman lebih awal
var
Lib: TPDFlib;
ActionID: Integer;
begin
Lib := TPDFlib.Create;
try
if Lib.LoadFromFile('manual.pdf', '') = 1 then
begin
Lib.SelectPage(12);
// Page is 1-based here, same as SelectPage above: this opens
// the fifth page of specs.pdf.
Lib.AddLinkToFile(72, 700, 200, 16, 'specs.pdf', 5, 0, 0, 0);
// A later maintenance pass repoints the same link at a
// reorganized file. SetActionRemoteDestinationEx edits the
// action directly, and DestPage here is the zero-based index
// PDF itself uses for a remote explicit destination -- "the
// fifth page" is now 4, not 5.
ActionID := Lib.GetAnnotActionID(1);
Lib.SetActionRemoteDestinationEx(ActionID, 'specs-2026.pdf',
4, Ord(dkFit), 0, 0, 0, 0, 0, 0, -1);
end;
finally
Lib.Free;
end;
end;
Sisa argumen SetActionRemoteDestinationEx sama literalnya. ValueMask adalah sebuah bit set — 1 untuk left, 2 untuk top, 4 untuk right, 8 untuk bottom, 16 untuk zoom — dan PDFlibPas memeriksanya terhadap DestType sebelum menulis apa pun: sebuah destination dkFitR harus menyerahkan persis 15 (keempat tepi, tanpa zoom), dkFit dan dkFitB harus menyerahkan 0, dan dkFitH/dkFitV hanya menerima satu koordinat relevannya. Bit yang Anda biarkan tidak diatur di dalam sebuah mask yang sebaliknya valid tidak dihilangkan dari array; mereka ditulis sebagai sebuah null PDF eksplisit, yang diperlakukan ISO 32000-1 sebagai "pertahankan nilai apa pun yang sudah dimiliki viewer" untuk koordinat itu — sebuah cara yang sah untuk mengatakan "lompat ke halaman ini, biarkan zoom saja" alih-alih sebuah kelalaian. Zoom itu sendiri disimpan sebagai sebuah pecahan dari nilai yang Anda serahkan, sehingga sebuah pemanggilan yang meminta 150 persen menyerahkan array itu sebuah nilai tersimpan 1.5, dan rentang input validnya 0 hingga 6400
Bagaimana Anda menghubungkan ke sebuah PDF yang disematkan di dalam dokumen Anda sendiri?
AddLinkToEmbeddedPDF membangun action GoToE, dan argumen targetnya, EmbeddedFileName, adalah sebuah nama alih-alih sebuah path: ia harus cocok dengan string Title yang sudah diserahkan ke EmbedFile ketika lampiran itu dibuat, karena title itu adalah key literal yang disimpan PDFlibPas dalam name tree /EmbeddedFiles milik dokumen, dan GoToE menyelesaikan dengan mencari nama itu, bukan dengan menyentuh filesystem lagi. Fungsi itu hanya memeriksa bahwa EmbeddedFileName tidak kosong dan TargetPage setidaknya 1 — serahkan sebuah nama yang sebenarnya tidak pernah disematkan dan pemanggilan itu tetap mengembalikan sukses, action-nya tetap ditulis, dan link itu sekadar gagal terselesaikan untuk setiap pembaca yang mengkliknya
var
Lib: TPDFlib;
begin
Lib := TPDFlib.Create;
try
Lib.NewDocument;
Lib.NewPage;
// The Title argument becomes the key PDFlibPas stores in the
// document's EmbeddedFiles name tree -- that string, not
// "datasheet.pdf", is the target GoToE resolves against.
if Lib.EmbedFile('Datasheet', 'datasheet.pdf', 'application/pdf') = 1 then
Lib.AddLinkToEmbeddedPDF(72, 700, 200, 16, 'Datasheet', 3, 0, 0);
Lib.SaveToFile('manual.pdf');
finally
Lib.Free;
end;
end;
Dua batas versi bertumpuk di sini, bukan satu. EmbedFile membutuhkan PDF 1.4 untuk name tree /EmbeddedFiles, dan AddLinkToEmbeddedPDF secara terpisah menaikkan batas itu ke PDF 1.6 untuk tipe action GoToE itu sendiri, sehingga minimum efektif untuk dokumen mana pun yang menggunakan fitur ini adalah 1.6, bukan 1.4. Perhatikan juga bahwa TargetPage di sini berbasis-1, konvensi PDFlibPas biasa — sebuah kontras yang disengaja dengan DestPage berbasis-nol yang baru saja dibahas bagian sebelumnya, dan sebuah pengingat bahwa skema penomoran-halaman mana yang berlaku bergantung pada jenis action dan pemanggilan spesifiknya, bukan pada satu aturan menyeluruh. Dictionary target action itu juga bisa membawa sebuah entry /R berisi C untuk child atau P untuk parent, mendukung sebuah rantai dua-lompatan ke dalam sebuah embedded file atau kembali keluar ke container-nya, meski AddLinkToEmbeddedPDF hanya pernah membangun arah child, karena itulah yang masuk akal dari sebuah dokumen yang melakukan penyematan alih-alih yang disematkan
Action Launch: satu FileName, dua target string yang tidak bisa dipertukarkan
SetActionLaunchOptions menulis target file sebuah action Launch ke dua key berbeda dari satu argumen FileName tunggal, dan kedua key itu memegang dua jenis string berbeda. Key tingkat-atas /F mendapat sebuah dictionary spesifikasi-file, dibangun lewat jalur konversi-path yang sama yang digunakan PDFlibPas untuk GoToR, yang merupakan bentuk portabel yang didefinisikan ISO 32000-1 §7.11.3 untuk sebuah dictionary spesifikasi file. Sub-dictionary /Win, ketika PDFlibPas menulis satu, mendapat key /F-nya sendiri diatur ke nilai FileName mentah persis seperti diserahkan, tanpa konversi sama sekali, karena /Win /F didokumentasikan dalam ISO 32000-1 §12.6.4.5 sebagai sebuah string path Windows polos yang dimaksudkan hanya untuk dibaca sebuah viewer Windows. Serahkan sebuah path portabel yang sudah dikonversi mengharapkan kedua key berakhir identik dan salinan /Win akan membawa apa pun yang Anda serahkan ke fungsi itu, tidak tersentuh
var
Lib: TPDFlib;
ActionID: Integer;
begin
Lib := TPDFlib.Create;
try
if Lib.LoadFromFile('manual.pdf', '') = 1 then
begin
Lib.SelectPage(1);
Lib.AddLinkToLocalFile(72, 660, 220, 16, 'calibrate.exe', 0);
ActionID := Lib.GetAnnotActionID(1);
// Operation 0 leaves this as a normal open -- pass 1 to ask a
// Windows viewer to print instead. Parameters and
// DefaultDirectory only ever reach /Win /P and /Win /D, never
// the top-level /F.
Lib.SetActionLaunchOptions(ActionID, 'calibrate.exe',
'/silent /profile:default', 'C:\Tools\Calibration', 0, -1);
end;
finally
Lib.Free;
end;
end;
Perlakukan Launch sebagai action paling gesekan-tinggi dari ketiganya, karena seluruh tujuannya adalah menjalankan sebuah program atau membuka sebuah file di luar sandbox PDF, dan setiap viewer utama memperlakukannya sesuai. Enhanced Security milik Adobe Acrobat memblokir atau meminta konfirmasi pada action Launch secara default kecuali target berada di sebuah lokasi yang secara eksplisit dipercaya, dan kebanyakan deployment Acrobat enterprise membiarkan perlindungan itu tetap aktif. Sebuah action Launch dalam sebuah dokumen yang diserahkan kepada publik karenanya bukan sebuah trigger yang andal: rencanakan agar ia diblokir, diminta konfirmasi, atau diam-diam diabaikan oleh viewer mana pun yang membuka file itu, dan simpan untuk lingkungan tertutup di mana Anda juga mengendalikan pengaturan trust viewer — sebuah kios internal, sebuah rollout korporat terkendali, sebuah dokumen yang tidak pernah meninggalkan sebuah mesin yang Anda kelola
Gerbang PDF/A: mengapa pemanggilan GoToR dan Launch bisa mengembalikan nol
Baik SetActionRemoteDestinationEx maupun SetActionLaunchOptions menolak secara langsung ketika dokumen target berada dalam mode kesesuaian PDF/A apa pun: keduanya memeriksa mode PDF/A dokumen sebagai kondisi pertama mereka dan keluar dengan sebuah hasil 0 sebelum menyentuh action itu, tanpa exception yang dimunculkan. Ini disengaja. Pembatasan PDF/A pada action interaktif mengeluarkan Launch secara spesifik, karena memberi sebuah file arsip kemampuan menjalankan sebuah program sembarang persis merupakan jenis perilaku bergantung-lingkungan yang ada untuk dicegah format arsip jangka-panjang, dan PDFlibPas menerapkan gerbang konservatif yang sama pada setter remote go-to dalam jalur kode yang sama. Konsekuensi praktisnya mudah terlewat selama pengembangan: pemanggilan identik yang bekerja pada sebuah PDF biasa akan dikompilasi, berjalan, dan diam-diam tidak melakukan apa-apa pada sebuah dokumen yang dimuat dengan sebuah level kesesuaian PDF/A yang diatur, jadi periksa nilai kembali alih-alih mengasumsikan sukses — sebuah 0 di sini bukan sebuah error input-cacat, itu library menolak sebuah permintaan yang bertentangan dengan klaim kesesuaian dokumen itu sendiri
Di mana GoToR, GoToE, dan Launch cocok dalam sebuah workflow PDFlibPas yang lebih besar
Ketiga jenis action dalam artikel ini tidak semuanya menjangkau tempat yang sama. Artikel pendamping tentang trigger action lifecycle dokumen dan halaman membahas SetDocumentAction dan SetPageAction, yang bisa melekatkan sebuah action GoToR atau Launch ke sebuah trigger seperti WillClose lewat konstanta bersama PDF_ACTION_BUILDER_REMOTE_DESTINATION dan PDF_ACTION_BUILDER_LAUNCH — builder yang sama yang juga mencakup sebuah trigger URI atau JavaScript polos. GoToE tidak memiliki konstanta semacam itu dan sama sekali tidak memiliki jalur ke builder generik itu; AddLinkToEmbeddedPDF adalah satu-satunya cara PDFlibPas membangun satu, yang membuatnya secara ketat sebuah action page-hotspot, tidak pernah sebuah trigger tingkat-dokumen atau tingkat-halaman. Di mana GoToR dan Launch memang menjangkau builder generik, trade-off-nya adalah kontrol: ia membangun sebuah GoToR yang hanya menunjuk ke sebuah destination remote bernama dan sebuah action Launch dengan hanya sebuah nama file dan parameter, sementara pengalamatan halaman-dan-tipe-fit eksplisit dan opsi launch khusus-Windows yang dibahas artikel ini hanya dijangkau lewat SetActionRemoteDestinationEx dan SetActionLaunchOptions secara langsung
Satu properti keamanan layak diketahui sebelum membangun sebuah tool pemeliharaan di sekitar setter ini. SetActionRemoteDestinationEx dan SetActionLaunchOptions membangun seluruh action pengganti dalam sebuah dictionary scratch lebih dulu, dan hanya menghapus dan menyalin key /F, /D atau /Win, dan /NewWindow ke atas action hidup begitu salinan scratch itu tervalidasi — sehingga sebuah pemanggilan yang gagal validasi, baik dari sebuah ValueMask di luar rentang atau sebuah FileName kosong, meninggalkan action asli, dan rantai /Next apa pun yang sudah menggantung di dalamnya, sepenuhnya tidak tersentuh alih-alih ditimpa setengah. Itu penting karena action GoToR dan Launch keduanya bisa duduk di dalam sebuah rantai /Next yang dibangun dengan AddActionNextRemoteDestinationEx, AddActionNextLaunchEx, atau yang lebih umum AddActionNextEx, membiarkan satu trigger tunggal memicu sebuah entri log JavaScript dan kemudian sebuah lompatan remote secara berurutan. Konstruksi GoToR, GoToE, dan Launch sebagaimana dijelaskan di sini adalah bagian dari PDFlibPas, library PDF native untuk Delphi dan C++Builder