Artikel Teknis

Meratakan Hyperlink Rich-Text XFA ke Link PDF di Delphi

XFA, XML Forms Architecture, sudah usang. ISO 32000-1 memuat format ini di §12.7 dengan catatan bahwa format ini dihapus dari PDF 2.0, dan penampil modern satu per satu menghentikan dukungan mesin XFA mereka. Namun, tidak ada yang mengosongkan arsip. Formulir intake pemerintah, aplikasi asuransi, dan laporan perbankan dibuat sebagai XFA selama hampir dua dekade, dan file-file tersebut masih berdatangan ke kotak masuk dan pipeline dokumen hingga hari ini. Ketika penampil yang dulu merendernya berhenti melakukannya, formulir berubah menjadi halaman kosong dengan placeholder "silakan buka di pembaca lain". Solusi yang tahan lama adalah meratakan XFA menjadi konten PDF statis yang bisa digambar oleh pembaca mana pun

Bagian sulit dari perataan itu bukan pada field-nya. Kotak teks dan kotak centang cukup mudah dipetakan ke widget AcroForm. Bagian sulitnya adalah rich text yang disimpan XFA di dalam elemen draw, dalam blok <exData contentType="text/html">. Blok itu adalah subset HTML dengan gaya inline dan, sering kali, anchor. Memuatnya ke halaman berarti mereproduksi teks bergaya dan hyperlink yang aktif, dan hyperlink adalah tempat sebagian besar implementasi secara diam-diam menyerah

Seperti apa rich text XFA sebenarnya

Isi exData adalah irisan kecil XHTML. Paragraf adalah <p>; rentang karakter bergaya adalah <span> dengan CSS inline untuk ketebalan, posisi, warna, dan ukuran; dan hyperlink adalah <a href="..."> yang membungkus teks yang terlihat. Satu baris bisa memiliki beberapa span berturutan, masing-masing dengan gaya berbeda, dan salah satunya bisa berupa anchor. Gaya bukan hiasan yang bisa diabaikan. Klausa yang dirender dalam warna merah tebal karena merupakan peringatan hukum harus tetap tebal dan merah setelah diratakan, atau dokumen yang diratakan salah merepresentasikan aslinya

Jadi mesin ratakan tidak bisa memperlakukan blok sebagai satu string. Mesin harus menelusuri struktur inline, menyelesaikan gaya efektif setiap run dengan melapisi CSS inline span di atas font dasar elemen draw, dan meletakkan run-run secara berurutan di sepanjang baris. HotPDF memodelkan setiap fragmen yang sudah ditata sebagai record TXFARichRun internal. Record ini membawa teks run, gaya yang sudah diselesaikan, kotak yang diukur, dan, untuk anchor, Href yang ditunjuknya

Menata run dari kiri ke kanan

Pemosisian adalah tempat rich text berhenti menjadi masalah parsing dan menjadi masalah penyusunan huruf. Run-run berbagi satu baris, jadi setiap run dimulai di tempat run sebelumnya berakhir. Tidak ada markup yang merekam posisi-posisi tersebut; mereka harus diukur. Rutinitas internal LayoutRichText mesin mengukur setiap run dengan metrik font yang sama yang nantinya akan melukisnya, lalu menetapkan offset horizontal run ke jumlah kumulatif semua lebar run sebelumnya. Run satu dimulai di origin kotak draw, run dua dimulai pada lebar run satu, run tiga pada lebar gabungan dua pertama, dan seterusnya di sepanjang baris

Inilah mengapa keselarasan font pengukuran begitu penting. Pass tata letak mengukur advance; pass render terpisah menggambar glyph. Jika kedua pass tersebut berbeda tentang font, kotak yang dihitung tata letak tidak akan berada di bawah glyph yang dilukis renderer. HotPDF menjaga keduanya sinkron dengan memetakan gaya yang sudah diselesaikan setiap run ke spesifikasi font, melalui helper internal RunStyleToFontSpec, yang cocok dengan default renderer sendiri yaitu Arial pada 10 poin. Advance yang diukur dan teks yang digambar kemudian sepakat, dan kotak yang dihitung run benar-benar mencakup karakter yang dilihat pembaca

Diagram HotPDF yang meratakan blok rich-text exData XFA di Delphi menjadi run bergaya yang disusun kiri ke kanan dengan lebar terukur, di mana run jangkar mempertahankan href-nya dan setiap fragmen menjadi record TXFARichRun
Engine flatten menelusuri struktur inline exData, menyelesaikan style setiap run, dan mengukur lebar dengan font render sehingga box yang ditata duduk tepat di bawah glyph yang dilukis
// Bentuk konseptual dari satu run yang sudah ditata. Mesin membangun array dari run-run ini
// secara internal; Anda tidak pernah menyusunnya sendiri, tetapi field-field ini menjelaskan bagaimana
// kotak hit link diturunkan dari geometri yang diukur, bukan dari teks.
type
  TRichRunInfo = record
    Dx, Dy : Double;       // kiri-atas, relatif terhadap origin kotak draw
    W, H   : Double;       // kotak run terukur (lebar dari pass tata letak)
    Text   : AnsiString;   // karakter terlihat dari run
    Href   : AnsiString;   // target URI untuk run <a>, '' jika bukan
  end;

Dari run anchor ke anotasi Link PDF

Hyperlink dalam PDF yang sudah selesai bukan bagian dari konten halaman. Ini adalah objek terpisah, yaitu anotasi Link, yang dijelaskan dalam ISO 32000-1 §12.5.6.5. Anotasi memiliki /Rect yang mendefinisikan persegi panjang yang bisa diklik di halaman dan aksi yang diaktifkan saat persegi panjang diklik. Untuk link eksternal, aksinya adalah aksi URI: /S /URI dengan alamat target sebagai string /URI-nya. Teks yang terlihat di bawahnya adalah konten halaman biasa; anotasi adalah zona panas tak terlihat yang diletakkan di atasnya

Jalur ratakan mengikuti model ini persis. Ketika run membawa Href, HotPDF pertama menggambar teks bergaya, lalu membangun anotasi Link di atas kotak run. Titik masuk publik untuk anotasi tersebut adalah metode halaman AddURILink, yang membuat objek /Type /Annot /Subtype /Link dengan aksi /URI dan mengembalikan kamus anotasi. Persegi panjangnya adalah kotak run yang diukur, diterjemahkan dari koordinat lokal elemen draw ke koordinat halaman. Hasilnya adalah link yang mendarat tepat pada teks anchor dan tidak di tempat lain

Diagram jalur flatten HotPDF yang menerjemahkan kotak draw-lokal terukur dari run jangkar ke koordinat halaman dan memancarkan anotasi Subtype Link dengan action URI yang Rect-nya merangkul teks jangkar
Run anchor menjadi teks terlukis plus annotasi Link yang /Rect-nya adalah box terukur milik run yang diterjemahkan ke koordinat halaman, dengan action URI dibuat oleh AddURILink
// API publik yang sama yang dipakai jalur ratakan untuk setiap run anchor. Ia menghasilkan
// anotasi Link ISO 32000-1 12.5.6.5: /Subtype /Link dengan aksi /URI
// di atas persegi panjang yang diberikan. Deskripsi opsional mengisi /Contents sehingga
// screen reader dapat mengumumkan targetnya.
var
  LinkRect: TRect;
  Annot: THPDFDictionaryObject;
begin
  LinkRect := Rect(72, 690, 268, 706);  // kotak hit ruang-halaman untuk run
  Annot := Pdf.CurrentPage.AddURILink(LinkRect,
    'https://www.example.gov/appeal', 'File an appeal online');
end;

Mengapa hit box harus berasal dari lebar yang diukur

Menggoda untuk membayangkan menemukan link dengan mencari teks yang terlihat di halaman dan menggambar persegi panjang di sekitar apa yang ditemukan. Itu tidak berhasil, dan alasannya fundamental terhadap cara penyimpanan teks yang diratakan. Run bergaya dilukis dengan font subset yang tertanam. Font subset menomori ulang glyph yang disimpannya, sehingga aliran konten halaman menyimpan kode CID heksadesimal, bukan kode karakter asli. Byte di halaman bukan huruf yang dibaca manusia, dan tidak bisa dicari sebagai teks. Pencarian caption anchor tidak menemukan apa pun, karena caption tersebut tidak ada sebagai teks literal di mana pun dalam aliran

Satu-satunya jangkar yang andal untuk persegi panjang adalah geometri yang sudah dihasilkan pass tata letak. Offset dan lebar yang diukur setiap run dihitung saat mengalirkan baris, sebelum glyph mana pun dinomori ulang, dan menggambarkan di mana teks akan muncul secara fisik. HotPDF karenanya mengambil persegi panjang link langsung dari kotak yang sudah ditata run daripada dari pencarian teks apa pun. Karena pengukuran menggunakan font render, kotak sudah benar terlepas dari subsetting. Geometri bertahan dari pengkodean; teks tidak. Itulah seluruh argumen untuk pemosisian berbasis lebar yang diukur, dan itulah mengapa perata yang mencoba memasang link dengan pencarian teks menghasilkan zona hit yang bergeser atau menghilang

Diagram yang menunjukkan mengapa HotPDF mengambil kotak hit tautan XFA dari geometri run terukur: font subset menomori ulang glyph menjadi kode CID sehingga pencarian teks tak menemukan apa pun, sementara offset dan lebar lintasan tata letak bertahan dan menghasilkan Rect yang benar
Font subset tersemat menomori ulang glyph menjadi kode CID, sehingga pencarian teks tak menemukan apa pun; offset dan lebar hasil ukur lintasan layout adalah satu-satunya jangkar yang selamat dari encoding

Menggerakkan ratakan dari kode Anda

Untuk PDF yang sudah berisi paket XFA, titik masuknya adalah FlattenLoadedXFA. Muat dokumen, panggil metodenya, dan simpan hasilnya. Parameter Editable memutuskan apa yang terjadi pada field formulir: berikan True untuk mempertahankannya sebagai widget AcroForm yang bisa diisi, atau False untuk menandai setiap widget sebagai read-only agar keluaran menjadi catatan yang sudah dibekukan. Blok draw rich-text, dengan run bergaya dan anotasi link mereka, dihasilkan dalam kedua kasus. Fungsi mengembalikan jumlah widget yang dihasilkannya

var
  Pdf: THotPDF;
  Emitted, i: Integer;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.LoadFromFile('xfa_appeal_form.pdf');
    // True membuat field tetap dapat diisi; False membekukannya menjadi read-only.
    Emitted := Pdf.FlattenLoadedXFA(True);

    // Apa pun yang tidak dapat dipetakan mesin dilaporkan, bukan dimunculkan.
    for i := 0 to Pdf.XFAFlattenWarnings.Count - 1 do
      Writeln('XFA warning: ', Pdf.XFAFlattenWarnings[i]);

    Pdf.SaveLoadedDocument('appeal_form_flat.pdf');
    Writeln('Widgets emitted: ', Emitted);
  finally
    Pdf.Free;
  end;
end;

Selalu baca XFAFlattenWarnings setelah pemanggilan. Daftar dibersihkan di awal setiap ratakan dan mengakumulasi satu baris untuk setiap elemen yang ditolak mesin untuk dirender: jenis field yang tidak didukung, gambar draw yang tidak bisa didekode, blok exData tanpa span yang bisa digunakan. Tidak ada yang memunculkan pengecualian, sehingga daftar peringatan kosong adalah bukti bahwa semuanya berhasil dipetakan, dan yang tidak kosong memberitahu Anda persis aslinya mana yang perlu diperiksa. Ketika Anda memegang XFA mentah sebagai byte XDP daripada PDF yang sudah dimuat, metode saudara ApplyXFAAsAcroForm mengambil byte tersebut secara langsung dan berbagi jalur kode yang sama dan perilaku peringatan yang sama. Metode komplementer AddXFAPacket berjalan ke arah sebaliknya, menyematkan paket XFA ke dalam dokumen yang sedang Anda bangun

Mengonfirmasi hasilnya di pembaca

Buka file yang sudah diratakan di Acrobat, atau penampil apa pun yang saat ini tersedia, dan periksa dua hal. Pertama, rich text dirender dengan gaya yang utuh: run tebal berwarna tebal, run berwarna membawa warnanya, dan span duduk dalam urutan yang benar pada baris daripada saling tumpang tindih atau keluar dari kotak. Kedua, hyperlink aktif. Arahkan kursor ke anchor dan bilah status harus menampilkan alamat target; klik dan aksi URI harus membukanya. Gunakan inspektor anotasi penampil untuk mengonfirmasi masing-masing adalah anotasi /Link asli yang /Rect-nya memeluk teks anchor, duduk di atas konten yang sekarang berupa glyph yang dilukis biasa daripada XFA yang dirender oleh formulir. Kombinasi itu - teks statis bergaya ditambah anotasi Link asli pada persegi panjang yang tepat - adalah yang membuat dokumen yang diratakan bertahan lebih lama dari mesin XFA yang tidak lagi dibutuhkannya

Meratakan field itu sendiri - kotak teks, kotak centang, dan daftar pilihan yang mengelilingi rich text ini - dibahas dalam panduan kami tentang meratakan form XFA menjadi widget AcroForm. Untuk kisah lebih luas tentang membangun dan menempatkan anotasi Link secara manual, di luar yang dihasilkan jalur ratakan, lihat bekerja dengan anotasi PDF di HotPDF. Keduanya dibangun di atas model anotasi dan form yang sama yang tersedia dalam HotPDF Delphi Component untuk Delphi dan C++Builder