Tekan Tab pada sebuah form PDF yang dibangun oleh kode Anda, dan kursor mendarat dua field lebih jauh dari seharusnya, atau melompati kolom kedua sepenuhnya, atau melompat kembali ke atas setelah field ketiga alih-alih field keempat. Orang yang mengisi sebuah invoice di viewer Anda mengharapkan keyboard menelusuri form itu dengan cara yang sama seperti ia menelusuri setiap form web yang pernah mereka gunakan. Ketika itu tidak terjadi, mereka meraih mouse, memburu kotak berikutnya, dan diam-diam menyimpulkan bahwa tool Anda belum selesai. Penelusuran field yang dapat diprediksi adalah pembeda antara sebuah viewer entri data yang ditoleransi orang dan yang dipercaya orang, dan itu hampir sepenuhnya soal menggunakan focus API yang tepat, bukan memalsukan input keyboard dengan klik simulasi
Contoh-contoh di bawah ini menggunakan PDFium Component, sebuah komponen VCL/LCL berbasis PDFium untuk Delphi, C++Builder, dan Lazarus. Navigasi adalah satu dari tiga hal yang harus dilakukan dengan benar oleh sebuah form viewer; dua lainnya, membuka form dengan benar dan menyimpan nilai yang diisi agar benar-benar muncul, adalah tempat sebagian besar kejutan bersembunyi, sehingga ketiganya dibahas di bawah ini
Membuka sebuah form: FormFill, FormType, dan pertanyaan seputar XFA
Akses field membutuhkan subsistem form-fill, yang dikendalikan oleh properti FormFill, untuk diaktifkan sebelum dokumen dibuka. Setelah aktif, FormType memberi tahu Anda jenis form apa yang sedang Anda hadapi, dan jawabannya mengubah kumpulan fitur yang dapat Anda janjikan:
Pdf.FileName := FormPath;
Pdf.FormFill := True; // aktifkan sebelum Active; diperlukan untuk akses field apa pun
Pdf.Active := True;
case Pdf.FormType of
ftNone:
DisableFormPanel('This document has no interactive form');
ftAcroForm:
BuildFieldList; // navigasi dan pengeditan field lengkap tersedia
ftXfaFull:
ShowXfaNotice; // XFA dirender dari templat XML miliknya sendiri;
// perlakukan pengeditan field sebagai terbatas
end;
Dua catatan praktis mengikuti dari switch tersebut. AcroForm adalah model form standar ISO 32000, dan itulah yang menjadi target setiap API di sini. Dokumen XFA menyematkan arsitektur form XML miliknya sendiri, sehingga menjanjikan pelanggan pengeditan XFA penuh setelah sebuah demo AcroForm singkat adalah komitmen yang akan Anda sesali. Catatan kedua adalah soal efek samping: menetapkan FormFill ke True juga menginisialisasi JavaScript dokumen. Pada sebuah viewer entri data, itu benar-benar tepat, karena script kalkulasi adalah yang menjaga total berjalan tetap terkini seiring seseorang mengetik. Pada sebuah jendela pratinjau untuk file yang asal-usulnya tidak diketahui, itu benar-benar keliru. Artikel pratinjau PDF yang aman membahas sisi FormFill := False dari trade-off tersebut
Penelusuran tombol Tab yang mendarat di tempat yang diharapkan pengguna
Kembali ke masalah keyboard dari awal tadi. Godaannya adalah memalsukan Tab dengan mensintesis klik mouse pada rectangle widget berikutnya, yang rusak seketika saat sebuah field ter-scroll keluar layar atau dua widget saling tumpang tindih. Focus API justru menggerakkan focus milik form itu sendiri secara langsung, tanpa tebak-tebakan geometri. Lima panggilan mencakup ini: FocusFormField berdasarkan indeks, FocusNextFormField dan FocusPreviousFormField untuk melangkah, FocusedFormFieldIndex untuk membaca posisi Anda saat ini, dan ClearFormFieldFocus untuk melepaskan focus sepenuhnya
procedure TFormViewer.HandleTabKey(Shift: TShiftState);
begin
if ssShift in Shift then
PdfView.FocusPreviousFormField
else
PdfView.FocusNextFormField;
UpdateFieldStatus; // misalnya "Field 4 dari 17: InvoiceDate"
end;
Satu perilaku yang sering membuat orang tersandung adalah wrap-nya. Penelusuran bekerja melalui urutan tab pada halaman yang aktif dan berputar di dalamnya: melangkahi field terakhir dan Anda kembali ke yang pertama. Kedua fungsi pelangkah mengembalikan indeks field yang baru, atau -1 ketika halaman sama sekali tidak memiliki field. Perputaran itu berlaku per halaman, bukan per dokumen, yang berarti berpindah ke halaman berikutnya adalah tugas Anda, bukan tugas pustaka ini. Bandingkan indeks yang dikembalikan dengan indeks tempat Anda memulai, perhatikan saat ia telah berputar, dan majukan PageNumber sendiri jika form dimaksudkan untuk dibaca sebagai satu urutan yang berkesinambungan. Lewati pemeriksaan itu dan sebuah form dua halaman akan diam-diam memerangkap kursor di halaman satu, yang merupakan varian tersendiri dari keluhan Tab yang rusak
Penelusuran menjadi berguna begitu bagian UI lainnya bereaksi terhadapnya. Event OnFormFieldEnter terpicu saat focus tiba, dan pada viewer, OnFormFieldFocusChange melaporkan indeks field yang baru, sehingga sebuah panel samping dapat tetap selaras dengan apa pun yang baru saja dipilih oleh keyboard. Ketika Anda membutuhkan pemetaan sebaliknya, dari sebuah posisi layar ke sebuah field, properti terindeks FormFieldAt melakukan hit-testing untuk pratinjau tooltip dan panel click-to-edit. Ada keuntungan aksesibilitas yang senyap dalam semua ini: karena focus mengikuti urutan field milik dokumen itu sendiri, jalur yang Anda rangkai untuk tombol Tab adalah jalur yang sama yang diumumkan oleh sebuah screen reader, tanpa kerja tambahan
Menampilkan nama field, bukan sekadar nomor indeks mentah, membutuhkan satu properti lagi. FormFieldInfo[] mengembalikan sebuah record TPdfFormFieldInfo per indeks, membawa nama field, tipe, ukuran font, status checked, nilai ekspor, dan keanggotaan grup, yang merupakan apa yang seharusnya ditampilkan oleh sebuah daftar navigasi ("Field 4 dari 17: InvoiceDate", bukan sekadar "4"). Radio group adalah kasus yang layak mendapat file uji khusus. Beberapa widget dapat berbagi satu nama field yang sama, sehingga sebuah daftar yang disusun secara naif dari widget akan menampilkan grup yang sama beberapa kali dan membingungkan siapa pun yang membacanya
Mengapa nilai yang diisi muncul kosong, dan panggilan yang memperbaikinya
Keluhan lain yang memenuhi antrean dukungan lebih meresahkan daripada sebuah tombol Tab yang berkelakuan buruk: sebuah form diisi secara programatis, pelanggan membukanya di Acrobat, dan setiap field terlihat kosong. Klik ke dalam sebuah field dan nilainya langsung tampak. Datanya ada di dalam file sepanjang waktu. Yang hilang adalah gambaran dari data tersebut, dan alasannya layak dipahami sekali karena itu menjelaskan seluruh keluarga bug
Sebuah field teks AcroForm menyimpan nilainya dalam entri /V pada dictionary field (ISO 32000-1 §12.7.3.3). Apa yang sebenarnya digambar oleh sebuah viewer adalah hal yang terpisah: appearance stream milik widget di bawah /AP (§12.5.5), sebuah cuplikan konten kecil yang sudah dirender sebelumnya. Tulis /V dan biarkan /AP tidak tersentuh, dan keduanya akan bergeser saling menjauh. Nilainya ada di sana; versi hasil render darinya basi atau tidak ada. Acrobat kebetulan membangun ulang appearance sebuah field ketika field itu mendapat focus, yang merupakan seluruh penjelasan bagi nilai yang hanya muncul saat diklik. Flag NeedAppearances yang lama, yang meminta viewer untuk membangun ulang appearance untuk Anda, tidak pernah bekerja secara seragam dan sudah deprecated di PDF 2.0, dan print server serta pembuat thumbnail mengabaikannya sepenuhnya. Mereka menggambar /AP dan tidak ada yang lain, sehingga jika /AP kosong, mereka mencetak sebuah kotak kosong
Menetapkan sebuah nilai melalui FormField[i] hanya menulis /V. Itulah sebabnya mengisi sebuah form adalah urutan tiga langkah, dan langkah yang sering terlewat oleh tim adalah langkah tengahnya:
procedure TFormViewer.FillAndSave(const Values: array of WString;
const OutputPath: string);
var
i: Integer;
begin
for i := 0 to Pdf.FormFieldCount - 1 do
Pdf.FormField[i] := Values[i]; // hanya menulis /V
// Bangun ulang appearance stream /AP; tanpa ini form
// akan terlihat kosong di Acrobat sampai setiap field diklik
Pdf.GenerateFormAppearances;
Pdf.SaveAs(OutputPath);
end;
GenerateFormAppearances adalah keseluruhan perbaikannya. Ia membangun ulang appearance stream setiap widget dari nilai, font, dan quadding yang berlaku saat ini, sehingga sebuah viewer yang tidak pernah menjalankan event focus, sebuah print server, atau sebuah pembuat thumbnail, tetap menggambar keadaan yang sudah terisi. Panggil ini satu kali setelah sekumpulan penetapan nilai, bukan satu kali per field. Pembuatan appearance melakukan pekerjaan layout yang sesungguhnya, dan panggilan per-field akan melipatgandakan itu di seluruh form besar tanpa guna
Membangun ulang appearance juga menjadi momen ketika font dan perataan menegaskan dirinya, yang menjadi sumber sebuah kejutan orde kedua. Stream yang baru menata setiap nilai di dalam rectangle widget menggunakan font, ukuran, dan quadding milik field tersebut. Sebuah nilai yang muat dengan nyaman di form uji Anda bisa terpotong atau menyusut pada salinan milik pelanggan tempat field yang sama lebih sempit. Field berukuran otomatis (ukuran font nol) menyusutkan teks agar muat; field berukuran tetap hanya akan memotongnya. Keduanya sah, dan satu-satunya cara jujur untuk mengetahui mana yang dilakukan oleh sebuah form tertentu adalah dengan melihat output yang dibangun ulang, bukan string yang Anda tulis. Ketika seseorang melaporkan teks terpotong di tepi sebuah kotak, inilah alasannya hampir selalu
Perlakukan verifikasi sebagai bagian dari penyelesaian pekerjaan, bukan sebagai renungan belakangan. Buka file yang sudah disimpan di Acrobat dan pastikan nilai-nilainya terlihat sebelum Anda menyentuh field mana pun. Kemudian cetak file itu ke PDF atau ke sebuah gambar dari viewer yang berbeda, satu yang sepenuhnya mengabaikan logika form, dan pastikan nilai-nilainya bertahan melalui jalur itu juga. Bersama-sama, kedua pemeriksaan itu menangkap setiap varian dari pergeseran /V-versus-/AP
Konfigurasi field yang lolos demo tetapi gagal di lapangan
Form demo yang bersih menyembunyikan sekumpulan edge case yang tidak disembunyikan oleh file pelanggan. Empat di antaranya menjadi penyebab sebagian besar laporan "berhasil di komputer saya"
- Nilai ekspor checkbox. Status "on" tidak selalu
Yes. Sebuah form bebas mendefinisikan nilai ekspornya sendiri, dan menulis string yang salah membuat kotak terlihat tidak tercentang secara visual padahal kode Anda yakin telah mengaturnya. Baca nilai ekspor dariFormFieldInfo[], jangan mengasumsikannya begitu saja - Radio group dengan nama bersama. Satu field, beberapa widget. Nilai yang Anda tetapkan menentukan widget mana yang terbaca sebagai terpilih, sehingga kode UI yang mengasumsikan satu nama dipetakan ke satu rectangle akan berakhir menggambar cincin focus pada tombol yang salah
- Field terkalkulasi. Total yang dipertahankan oleh JavaScript dokumen diperbarui sebagai respons terhadap event field. Sebuah pengisian programatis yang melewati event tersebut harus memicu perhitungan ulang atau menimpa field terkalkulasi secara langsung. Sebuah form tempat item baris dan totalnya tidak sesuai lebih buruk daripada kedua perbaikan itu sekalipun
- Field wajib yang tersembunyi. Form kondisional menyembunyikan field yang masih ditandai required. Tentukan sejak awal apakah validasi Anda menghormati visibilitas atau flag required mentahnya, lalu catat keputusan itu di suatu tempat yang bisa ditemukan oleh tim support
Satu perbedaan layak diselesaikan sebelum ia menggigit Anda: membangkitkan appearance bukanlah flattening. GenerateFormAppearances membuat nilai terlihat di mana-mana sambil tetap membiarkan field dapat diedit. Flattening memanggang appearance ke dalam konten halaman statis dan mencabut interaktivitasnya untuk selamanya, yang tepat untuk sebuah salinan arsip dan keliru untuk sebuah form yang masih harus diisi oleh orang berikutnya. Jika FormType melaporkan ftXfaFull alih-alih ftAcroForm, tidak ada satu pun permukaan pengeditan di sini yang berlaku dengan mulus, karena dokumen itu dirender dari templat XML miliknya sendiri; deteksi kasus itu dan beri tahu pengguna, alih-alih membiarkan mereka menemukan batasnya sendiri
Subsistem form-fill, penelusuran focus, dan pembuatan appearance yang ditunjukkan di sini adalah bagian dari PDFium Component untuk Delphi, C++Builder, dan Lazarus/FPC. Jika viewer Anda juga menangani markup peninjau di samping data form, artikel peninjauan anotasi membahas model yang bersebelahan itu