PDFlibPas menyelesaikan karakter yang tidak bisa digambar oleh font yang dipilih dengan mencari fallback chain dari face yang terinstal, cluster demi cluster, sambil mempertahankan shaping dan urutan run bidirectional. Anda mengaktifkannya dengan SetAutomaticFontFallback, memperluas chain-nya dengan AddFontFallback, dan hanya font fallback yang benar-benar dipakai untuk output yang di-embed ke dalam file
Masalah yang dipecahkannya adalah masalah yang dihadapi setiap document generator pada saat pertama sebuah nama pelanggan tiba dalam skrip yang tidak pernah diantisipasi oleh font template. Kegagalannya senyap, dan itulah yang membuatnya mahal
Mengapa teks yang tidak didukung menghilang, bukannya memunculkan error?
Karena PDF tidak memiliki konsep font yang tidak bisa menggambar sebuah karakter. Font simple memetakan byte code ke nama glyph lewat sebuah encoding; font composite memetakan code lewat CMap ke glyph index. Minta glyph yang tidak dimiliki face itu dan Anda akan mendapatkan glyph index nol, .notdef, yang oleh sebagian besar face digambar sebagai tidak ada apa-apa atau sebagai kotak kosong. File-nya valid secara struktural, text operator-nya well formed, dan halamannya tetap dirender. Hanya saja kosong di tempat nama seharusnya berada
Tidak ada apa pun di ISO 32000-1 yang mengharuskan sebuah producer menyadarinya. Generator yang menulis teks tanpa memeriksa coverage menghasilkan PDF yang secara teknis conforming namun diam-diam kehilangan konten, dan kehilangan itu baru muncul di layar pelanggan berminggu-minggu kemudian. Inilah sebabnya fitur fallback dan laporan missing-glyph dirilis bersamaan: menyelesaikan apa yang bisa diselesaikan hanya separuh pekerjaan, dan melaporkan apa yang tidak bisa diselesaikan adalah separuh lainnya
Fallback terjadi per cluster, bukan per code point
Granularity adalah detail yang membedakan implementasi yang benar-benar berfungsi dari yang sekadar tampak masuk akal. Teks bukanlah rangkaian karakter yang saling independen. Sebuah suku kata Devanagari, sebuah emoji dengan skin-tone modifier, huruf dasar dengan combining mark: masing-masing adalah satu cluster yang harus dirender oleh satu font, karena keputusan shaping di dalamnya bergantung pada tabel di dalam face tersebut
PDFlibPas menyelesaikan cluster, sehingga cluster yang dicakup oleh sebuah face fallback digambar sepenuhnya oleh face itu. Memecah di tengah cluster dan menggambar separuh dari font utama dan separuh dari fallback akan menghasilkan output yang secara teknis ada tetapi tampak rusak secara visual, yang bisa dibilang lebih buruk daripada kekosongan yang Anda mulai. Urutan run juga dipertahankan, sehingga fallback di dalam run right-to-left tidak mengubah urutan teks di sekitarnya; mesin yang sama mendasari vertical layout yang dijelaskan di penulisan vertikal untuk bahasa Jepang dan China
var
Lib: TPDFlib;
begin
Lib := TPDFlib.Create;
try
Lib.SetOrigin(1);
Lib.SetAutomaticFontFallback(1);
// Urutan pencarian: match pertama menang, jadi taruh face paling luas di akhir
Lib.AddFontFallback('Microsoft YaHei'); // China Sederhana (Simplified)
Lib.AddFontFallback('Meiryo'); // Jepang
Lib.AddFontFallback('Segoe UI Symbol');
Lib.AddFontFallback('Segoe UI Emoji');
Lib.SetMissingGlyphPolicy(PDF_MISSING_GLYPH_REPORT);
Lib.AddTrueTypeFont('Arial', 1); // 1 = embed face-nya
Lib.SetTextSize(11);
Lib.DrawText(72, 720, 'Invoice for 北京示例科技有限公司');
Lib.DrawText(72, 700, 'Delivery status: on time');
Lib.SaveToFile('invoice.pdf');
finally
Lib.Free;
end;
end;
Susun chain-nya dengan sengaja. Resolusi mengambil face pertama yang mencakup cluster tersebut, sehingga font pan-Unicode yang luas jika ditaruh di posisi pertama akan memenangkan hampir semuanya dan face khusus skrip yang Anda pilih dengan cermat tidak akan pernah dikonsultasikan. Taruh face yang spesifik di awal dan catch-all di akhir
Report atau abort: kegagalan seperti apa yang Anda inginkan?
SetMissingGlyphPolicy menerima PDF_MISSING_GLYPH_REPORT, default yang kompatibel, atau PDF_MISSING_GLYPH_ABORT. Di bawah kebijakan report, operasi teks tetap berlanjut, code point yang tidak bisa diselesaikan tetap dibuang seperti sebelumnya, dan masing-masing dicatat. Di bawah kebijakan abort, operasi teks ditolak sebelum konten apa pun ditulis dan LastErrorCode diatur ke 521
Pilih berdasarkan tujuan dokumennya. Sekumpulan laporan internal sebaiknya tetap dirender dan mencatat celah yang ada, karena laporan yang sedikit tidak lengkap hari ini lebih baik daripada tidak ada laporan sama sekali. Kontrak yang mengikat secara hukum, invoice, atau apa pun yang memuat nama sebaiknya abort, karena karakter yang diam-diam dibuang dalam nama pihak adalah cacat yang ingin Anda temukan di proses Anda sendiri, bukan dalam sebuah sengketa. Kebijakan abort gagal sebelum penulisan, sehingga tidak ada content stream setengah jadi yang tertinggal
var
Lib: TPDFlib;
Report: WideString;
begin
Lib := TPDFlib.Create;
try
Lib.SetMissingGlyphPolicy(PDF_MISSING_GLYPH_ABORT);
// ... build the document ...
if Lib.DrawText(72, 660, CustomerName) <> 1 then
if Lib.LastErrorCode = PDFLIB_ERROR_MISSING_GLYPH then
begin
Report := Lib.GetMissingGlyphReportJSON;
// {"valid":false,"policy":1,"eventCount":1,"events":[
// {"sequence":1,"documentIndex":0,"page":1,"utf16Index":12,
// "codePoint":21271,"unicode":"U+5317","fontName":"Arial",
// "fontType":"TrueType","operation":"DrawText"}]}
EscalateToOperator(Report);
end;
finally
Lib.Free;
end;
end;
Laporan ini sengaja dibuat machine-readable dan dibatasi. Setiap event membawa halaman, index UTF-16 di dalam string, code point dalam bentuk numerik maupun U+XXXX, font yang dipilih, tipenya, dan operasi yang mengalami masalah, sehingga sebuah support ticket bisa menyebut karakter yang tepat, bukan sekadar mendeskripsikan gejalanya. Tracker ini menyimpan 256 event paling akhir, yang cukup untuk mendiagnosis sebuah dokumen dan cukup kecil sehingga run yang patologis tidak bisa mengubah diagnostik menjadi masalah memori
Pengukuran dan penggambaran harus sejalan
Pengukuran lebar menggunakan keputusan fallback cluster-aware yang sama dengan penggambaran. Ini terdengar jelas dan justru menjadi hal yang paling sering salah dilakukan oleh lapisan fallback buatan sendiri: mereka menambal jalur penggambaran, membiarkan pengukuran tetap pada font utama, dan setiap text box, right alignment, serta kolom tabel akhirnya dihitung dari lebar yang tidak cocok dengan apa yang sebenarnya dirender
Karena kedua jalur itu berbagi resolusi yang sama, sebuah string yang diukur sebelum digambar akan menempati lebar sesuai hasil pengukurannya, termasuk run fallback-nya. Itulah yang membuat fallback aman diaktifkan secara global, bukan hanya di tempat-tempat yang Anda audit secara manual
Hanya yang Anda pakai yang di-embed
Font fallback di-embed secara lazy: face di dalam chain yang tidak pernah menyelesaikan satu cluster pun tidak menyumbang apa-apa ke output. Dokumen yang berisi satu karakter China dan 5.000 karakter Latin tidak akan membawa face CJK yang lengkap; ia membawa apa yang dihasilkan oleh proses subsetting untuk satu glyph itu, yang merupakan perilaku yang dijelaskan di optimisasi ukuran file dan font subsetting
Sifat lazy itu membuat chain yang luas murah untuk dikonfigurasi. Daftarkan face yang mungkin dibutuhkan oleh kumpulan dokumen Anda di setiap locale yang Anda layani, dan setiap PDF individual hanya membayar untuk apa yang benar-benar dipakainya. Untuk dokumen yang tidak Anda hasilkan sendiri, di mana face yang hilang sudah berada di dalam file yang sudah ada, jalur perbaikannya berbeda dan dibahas di meng-embed font yang hilang ke dalam PDF yang sudah ada
Satu catatan deployment layak dinyatakan dengan jelas: fallback diselesaikan terhadap face yang terinstal di mesin yang menjalankan kode. Server tanpa font CJK terinstal tidak punya apa pun untuk di-fallback, dan laporannya akan memberi tahu Anda hal itu pada dokumen pertama, bukan setelah keluhan pertama. Sertakan font yang Anda andalkan, dan pastikan lisensinya untuk meng-embed-nya
PDFlibPas adalah pustaka PDF Delphi, C++Builder, dan Lazarus dengan interface DLL dan ActiveX yang setara, sehingga API fallback dan missing-glyph juga tersedia bagi caller non-Pascal. Dokumentasi lengkap ada di halaman pustaka PDF Delphi PDFlibPas