TPdf.SetFocusedFormFieldText di PDFium Component menulis ke dalam buffer edit hidup milik field form yang sedang terfokus, dan untuk sebuah form XFA buffer itu tidak pernah sampai ke packet datasets yang diserialisasi ke disk — sehingga sebuah nilai yang diketik pengguna, dan yang dikonfirmasi kode Anda telah diterima, diam-diam hilang lain kali file itu dibuka. Field AcroForm tidak memiliki masalah ini: pemanggilan yang sama meng-commit ke entry /V milik field begitu fokus berpindah. Seorang pengguna yang mengisi sebuah form intake XFA, menyimpan, dan membuka kembali menemukan field jumlah kosong lagi tidak sedang menabrak sebuah gangguan rendering — mereka menabrak batas apa yang diekspos mesin PDFium itu sendiri untuk menulis data form
Ini adalah pertanyaan yang lebih sempit daripada mendeteksi sebuah form XFA sejak awal, atau membuat JavaScript-nya berjalan: bukan "apakah PDFium mendukung XFA" dan bukan "bagaimana saya menjalankan script AcroForm" tetapi secara spesifik apa yang terjadi pada sebuah nilai setelah SetFocusedFormFieldText melaporkan sukses. Versi singkatnya adalah AcroForm dan XFA bukan dua dialek dari model form yang sama sejauh jalur tulis PDFium peduli — mereka adalah dua model form dengan dua hubungan yang sama sekali berbeda antara apa yang diketik pengguna dan apa yang benar-benar ditangkap sebuah save, dan mencampuradukkan keduanya adalah yang mengubah satu pemanggilan API baris tunggal menjadi sebuah tiket dukungan tiga minggu setelah deployment pilot seorang pelanggan berjalan. Artikel JavaScript AcroForm menunjukkan pemanggilan baris tunggal itu dan menyatakan hasil AcroForm-versus-XFA dalam sebuah komentar kode; yang ini tetap pada API yang sama itu dan menelusuri jalur tulis internal, bukti packet-datasets bahwa penulisan XFA tidak pernah mendarat, mengapa celah itu berada di PDFium itu sendiri alih-alih binding Delphi, dan sebuah workaround tambal-XML-Anda-sendiri untuk dokumen yang membutuhkan edit itu bertahan dari sebuah save
Bagaimana SetFocusedFormFieldText Menulis Sebuah Nilai Field?
TPdf.SetFocusedFormFieldText bekerja dengan mensimulasikan sebuah edit tingkat-keystroke, bukan dengan menusukkan sebuah nilai ke dalam model dokumen secara langsung. Secara internal ia memanggil FORM_SelectAllText untuk memilih konten field terfokus saat ini, lalu FORM_ReplaceSelection untuk menimpa seleksi itu dengan string baru — dua operasi yang sama yang akan dipicu sebuah select-all-and-type berbasis-keyboard. Karena penulisan itu melalui jalur editing-teks interaktif PDFium alih-alih melewatinya, script keystroke, format, atau calculate apa pun yang terikat ke field itu terpicu persis seperti akan terpicu untuk manusia yang mengetik, yang membuat API ini berguna untuk pengisian form programatik dalam sebuah viewer yang menjaga JavaScript tetap hidup. Padanan sisi-baca adalah FocusedFormFieldText, didukung FORM_GetFocusedText, dan ia mencerminkan buffer hidup yang sama yang baru saja ditulis SetFocusedFormFieldText
if Pdf.FocusedFormFieldIndex >= 0 then
begin
if Pdf.SetFocusedFormFieldText('1284.50') then
Log('Buffer now reads: ' + Pdf.FocusedFormFieldText)
else
Log('No field is focused, or it does not accept text');
end
else
Log('Focus a field first - FocusFormField or a real click');
Mengapa AcroForm Mempertahankan Nilai itu dan XFA Kehilangannya?
Field teks dan combo AcroForm bertahan karena lingkungan form-fill milik PDFium sendiri meng-commit buffer edit untuk Anda: seketika field itu kehilangan fokus, buffer tersebut ditulis ke dalam entry /V milik field, key yang sama yang dilihat setiap reader PDF yang sesuai standar untuk mengetahui nilai tersimpan sebuah field. TPdf.ClearFormFieldFocus — yang memanggil FORM_ForceToKillFocus di baliknya — memaksa commit itu sesuai permintaan, sehingga kode yang mengatur sebuah nilai secara programatik tidak perlu menunggu sebuah klik mouse sungguhan di tempat lain dalam UI. Simpan segera setelahnya, dan teks baru itu menjadi bagian dari object graph dokumen sebelum TPdf.SaveAs pernah berjalan, karena /V adalah sebuah entry sungguhan dalam sebuah dictionary field sungguhan, bukan sesuatu yang ditempelkan belakangan
Pdf.FocusFormField(FieldIndex);
Pdf.SetFocusedFormFieldText('1284.50');
Pdf.ClearFormFieldFocus; // forces the /V commit now
Pdf.SaveAs('invoice-acroform.pdf');
// Reopen and confirm - this is an AcroForm document, so it holds
Pdf.Active := False;
Pdf.FileName := 'invoice-acroform.pdf';
Pdf.Active := True;
Pdf.FocusFormField(FieldIndex);
Assert(Pdf.FocusedFormFieldValue = '1284.50'); // passes
Di Mana Sebenarnya Sebuah Edit Field XFA Hidup?
Field XFA tidak memiliki pengkabelan semacam itu. Teks yang diketik pengguna mendarat dalam sebuah buffer CPWL_Edit milik lapisan rendering dan interaksi XFA PDFium, dan lapisan itu tidak memiliki jalur kode yang menyalin buffer itu kembali ke dalam packet datasets yang disimpan dalam PDF. TPdf.GetXfaDatasets membuat celah itu terlihat: panggil itu sebelum dan sesudah sebuah edit pada sebuah field XFA dan byte yang dikembalikannya identik, karena metode itu membaca packet asli yang dibuka dokumen tersebut, tidak pernah state hidup dari widget yang baru saja Anda edit. Tidak ada apa pun tentang itu yang sebuah bug caching atau sebuah masalah timing-refresh — packet datasets di disk dan buffer edit di memori sekadar dua bagian state berbeda yang tidak pernah dihubungkan API publik PDFium
var
Before, After: TBytes;
begin
Before := Pdf.GetXfaDatasets;
Pdf.FocusFormField(FieldIndex);
Pdf.SetFocusedFormFieldText('1284.50');
After := Pdf.GetXfaDatasets;
// Before and After are byte-for-byte identical on an XFA document -
// the edit never touched the packet GetXfaDatasets reads from
end;
Apakah Ini Bug PDFium Component atau Keterbatasan PDFium?
Bagian yang hilang berada di PDFium itu sendiri, bukan di binding Delphi di atasnya. API publik PDFium tidak memiliki FPDF_SetXFAPacket untuk menyuntikkan sebuah packet yang diperbarui dan tidak memiliki FPDF_SaveAsXFA untuk meminta mesin XFA menyerialkan DOM saat ini kembali ke XML datasets sebelum sebuah save. FPDF_SaveAsCopy — ekspor yang menjadi landasan TPdf.SaveAs — menulis object graph dokumen yang sudah dimiliki PDFium; ia tidak memiliki hook untuk meminta mesin XFA membersihkan state hidupnya lebih dulu, karena hook itu tidak ada di hulu. PDFium Component tidak bisa menambahkan rekonsiliasi yang tidak pernah diimplementasikan PDFium itu sendiri, dan merilis sebuah serializer DOM-ke-XML buatan sendiri yang menebak-nebak state XFA internal PDFium akan lebih buruk daripada celah yang jujur: ia akan terlihat berfungsi sampai versi PDFium berikutnya mengubah sesuatu yang tidak bisa dilihat siapa pun di luar proyek tersebut
Batas ini muncul selama audit v2.13.2 yang sama yang membangun SetFocusedFormFieldText sejak awal. FORM_ReplaceSelection sudah terikat dalam tabel import DLL selama beberapa versi tanpa pernah dipanggil dari kode Pascal, dan menambahkan jalur tulis yang akhirnya menggunakannya itulah yang membuat celah persistensi ini cukup konkret untuk didokumentasikan alih-alih teoritis. Ronde audit yang sama menemukan sebuah celah yang tidak berkaitan tetapi berkaitan-dalam-semangat: JavaScript AcroForm diam-diam dinonaktifkan sejak v2.13.0 karena platform JS hanya dikabel di dalam cabang inisialisasi XFA, sehingga dokumen AcroForm biasa dengan app.alert atau field terhitung tidak pernah mendapat mesin script sama sekali. Yang itu bisa diperbaiki — memperluas platform JS ke setiap dokumen terlepas dari XFA — dan dirilis di versi yang sama; celah persistensi yang dibahas di sini tidak bisa diperbaiki, karena alasan di atas. Perbaikan JavaScript dan event host-veto di sekitarnya dibahas di menjalankan JavaScript AcroForm dengan PDFium Component
Apa yang Seharusnya Anda Lakukan Terhadap Ini di Delphi?
Untuk dokumen AcroForm, perbaikannya tidak lebih dari sebuah kebiasaan baik: panggil ClearFormFieldFocus (atau sebaliknya pindahkan fokus) sebelum SaveAs kapan pun sebuah nilai diatur secara programatik, alih-alih mengasumsikan sebuah interaksi UI belakangan akan memicu commit untuk Anda. Untuk sebuah dokumen yang mungkin AcroForm atau XFA — yang merupakan kasus umum dalam sebuah viewer serba-guna — periksa FormType atau boolean XFA sebelum Anda menjanjikan seorang pemanggil bahwa sebuah save akan menempel, dan baca mendeteksi form XFA dan mengekstrak packet XFA untuk set lengkap pemeriksaan, termasuk kasus XFAF di mana konten XFA dilapiskan di atas widget AcroForm yang sebaliknya biasa yang memang menghormati /V
Untuk sebuah form XFA dinamis sungguhan di mana nilai yang diedit harus bertahan dari sebuah save, buffer edit interaktif sama sekali bukan alat yang tepat. Jalur yang tahan lama adalah memperlakukan GetXfaDatasets sebagai baseline Anda, bukan hasil Anda: baca sekali ketika dokumen dibuka, simpan catatan Anda sendiri tentang apa yang diubah pengguna field demi field — persis nilai yang sudah dimiliki UI Anda, karena PDFium tidak akan menyerahkannya kembali kepada Anda setelah fakta — tambal itu ke dalam XML baseline sendiri, dan kendalikan output Anda sendiri. Sebuah penulisan yang melalui XML yang dikendalikan kode Anda sendiri bertahan dari sebuah save yang tidak pernah bisa dilakukan buffer CPWL_Edit
function ExportEditedXfaValue(Pdf: TPdf; const FieldPath,
NewValue: string): TBytes;
var
DatasetsXml: string;
begin
// GetXfaDatasets ships with PDFium Component; PatchXmlNode below is
// your own helper over your own XML library, nothing PDFium provides
DatasetsXml := TEncoding.UTF8.GetString(Pdf.GetXfaDatasets);
DatasetsXml := PatchXmlNode(DatasetsXml, FieldPath, NewValue);
Result := TEncoding.UTF8.GetBytes(DatasetsXml);
end;
Menangkap Celah Ini Sebelum Seorang Pelanggan Melakukannya
TPdf.SaveAs mengembalikan True baik sebuah nilai field XFA bertahan atau tidak, karena dari sudut pandang PDFium, save itu benar-benar berhasil — ia menulis setiap byte yang diminta untuk ditulis. Itu membuat ini persis jenis cacat yang lolos dari sebuah smoke test dan sampai ke seorang pelanggan: tidak ada yang memunculkan exception, tidak ada yang mencatat log, file itu terbuka baik-baik saja, hanya nilai spesifik itu yang salah. Sebuah test round-trip yang benar-benar membuka kembali file yang disimpan dan membandingkan nilai field — atau membandingkan GetXfaDatasets sebelum dan sesudah, sesuai contoh sebelumnya — layak masuk ke dalam suite regresi untuk viewer mana pun yang membiarkan pengguna mengedit konten XFA, bukan hanya jalur AcroForm yang kebetulan bekerja secara default
Tak satu pun dari ini sebuah cacat untuk dilaporkan terhadap PDFium Component sebanyak sebuah batas untuk dirancang di sekitarnya: SetFocusedFormFieldText melakukan persis apa yang dikatakan namanya untuk kedua model form, dan perbedaan hasilnya terlacak bersih ke apa yang dikabel masing-masing AcroForm dan XFA ke buffer itu di sisi PDFium. API, primitif focus dan save, dan pembaca packet yang dirujuk di sini adalah bagian dari PDFium Component untuk Delphi dan C++Builder