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

// Conceptual shape of one laid-out run. The engine builds an array of these
// internally; you never construct them yourself, but the fields explain how a
// link's hit box is derived from measured geometry rather than from text.
type
  TRichRunInfo = record
    Dx, Dy : Double;       // top-left, relative to the draw-box origin
    W, H   : Double;       // measured run box (width from the layout pass)
    Text   : AnsiString;   // the run's visible characters
    Href   : AnsiString;   // URI target for an <a> run, '' otherwise
  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

// The same public API the flatten path uses for each anchor run. It produces
// an ISO 32000-1 12.5.6.5 Link annotation: /Subtype /Link with a /URI action
// over the given rectangle. The optional description fills /Contents so a
// screen reader can announce the target.
var
  LinkRect: TRect;
  Annot: THPDFDictionaryObject;
begin
  LinkRect := Rect(72, 690, 268, 706);  // page-space hit box for the 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

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 keeps fields fillable; False freezes them read-only.
    Emitted := Pdf.FlattenLoadedXFA(True);

    // Anything the engine could not map is reported, not raised.
    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 Component untuk Delphi dan C++Builder