Glyph yang hilang di PDF bukanlah error. Produsen meminta karakter yang tidak dapat dipetakan oleh font terpilih, font mengembalikan indeks glyph nol, dan berkas yang keluar valid secara struktural, terbuka di mana saja, dan menampilkan kotak kosong di tempat seharusnya ada nama atau jumlah. Tidak ada yang di pipeline pembuatan mengetahuinya. Penerimanya yang tahu. HotPDF menutup lingkaran itu dengan TrackUnresolvedGlyphs: nyalakan dan jalur menggambar teks mencatat setiap code point yang pencarian glyph-nya terselesaikan ke indeks nol, menembakkan OnUnresolvedGlyph sekali per temuan unik dengan code point-nya, font yang gagal, script yang dimilikinya, dan saran font yang akan mencakupnya
Deteksi adalah separuh jawabannya. Separuh lainnya adalah SetFontFallbackChain, yang mendaftarkan daftar font terurut per script, sehingga kasus-kasus umum menyelesaikan dirinya sendiri dan hanya celah yang sungguhan mencapai handler Anda. Bersama-sama keduanya mengubah kelas defek yang dulu dilaporkan pelanggan menjadi pemeriksaan build-time
Mengapa glyph yang hilang tidak melempar apa pun?
Karena ISO 32000 tidak membebankan kewajiban apa pun pada produsen untuk memverifikasi cakupan, dan indeks glyph nol adalah glyph yang sah. Ia adalah .notdef, yang outlinenya dipilih perancang font: biasanya persegi kosong atau berongga, kadang tidak ada sama sekali. Viewer yang menggambarnya berperilaku benar. Ekstraksi teks bahkan bisa mengembalikan karakter yang tepat, karena pemetaan /ToUnicode ditulis dari teks sumber alih-alih dari outline, sehingga pemeriksaan round-trip otomatis dengan senang hati lolos atas dokumen yang teks tampaknya berlubang
Konsekuensi praktisnya adalah cakupan harus diperiksa pada saat menggambar, ketika pustaka masih tahu code point mana yang diminta dan glyph mana yang benar-benar ditawarkan font. Setelahnya informasinya hilang
Detektor harus mengawasi status subset, bukan device context
Di sinilah implementasi pertama keliru, dan alasannya layak dipahami karena berlaku untuk pemeriksaan cakupan apa pun yang dipasangkan ke pipeline teks. HotPDF punya dua jalur teks. Satu memancarkan melalui font TrueType Unicode yang didaftarkan dengan peta karakter in-memory yang dibangun saat pendaftaran. Yang lain adalah jalur GDI legacy yang membuat device context dan handle font baru untuk setiap rangkaian karakter
Menilai cakupan dari jalur GDI adalah mustahil. Pemetaannya bukan pemetaan yang berakhir di content stream yang dipancarkan, dan keduanya tidak tersinkronisasi, sehingga detektor yang membaca hasil GDI melaporkan seluruh rentang ASCII yang dapat dicetak sebagai tidak terselesaikan. Jawaban yang otoritatif berada di font terdaftar: peta karakter yang di-parse oleh RegisterUnicodeTTF, dikuerikan melalui GetUnicodeGlyphForCodepoint. Detektor karenanya digerbangkan pada status subset-ready, bukan pada kondisi GDI apa pun, dan ia sekadar tidak berjalan pada dokumen yang tidak pernah mendaftarkan font Unicode, yang benar karena dokumen-dokumen itu memang dibatasi pada encoding standar
Jebakan kedua duduk di sebelahnya. Nama keluarga GDI sebuah font dan nama PostScript yang diekstrak dari binary font saat pendaftaran adalah string yang berbeda, dan bukan dengan cara yang bisa Anda normalisasi: keluarga bernama Arial Unicode MS membawa nama PostScript ArialMT. Gerbang apa pun yang ditulis sebagai "apakah font yang sedang terpilih adalah yang kita daftarkan", dibandingkan berdasarkan nama, adalah kode mati yang tidak pernah menembak. Gerbangkan pada status, jangan pernah pada nama font
Jangan menguji detektor glyph dengan emoji
Kasus uji yang jelas adalah wajah tersenyum, dan itu akan meyakinkan Anda bahwa detektornya rusak. Code point emoji umum di bidang astral terselesaikan melalui jalur sintesis private-use yang memetakannya langsung ke indeks glyph, sehingga mereka tidak pernah mencapai cabang cakupan umum. Detektornya berperilaku benar dan ujinya mengukur jalur yang salah
Pakai code point yang belum dialokasikan sebagai gantinya. U+0378 secara permanen tidak dialokasikan di Unicode, sehingga tidak ada font yang dapat memetakannya secara sah, dan ia menjalankan persis cabang yang ingin Anda verifikasi. Distinksi antara "fiturnya rusak" dan "ujinya memilih input yang mem-bypass fitur" memakan jam-jam nyata, dan code point yang tidak dialokasikan adalah cara termurah menghindarinya
type
TCoverageAudit = class
private
FFindings: TStringList;
public
procedure Handle(Sender: TObject;
const Info: THPDFUnresolvedGlyphInfo);
property Findings: TStringList read FFindings;
end;
procedure TCoverageAudit.Handle(Sender: TObject;
const Info: THPDFUnresolvedGlyphInfo);
begin
// Menembak sekali per code point unik, bukan sekali per kemunculan
FFindings.Add(Format('U+%.4X missing in %s (script %d), try: %s',
[Info.CodePoint, String(Info.FontName), Ord(Info.Script),
String(Info.SuggestedFonts)]));
end;
// Mengkabelkannya ke dalam pekerjaan pembuatan
Pdf := THotPDF.Create(nil);
try
Pdf.TrackUnresolvedGlyphs := True;
Pdf.OnUnresolvedGlyph := Audit.Handle;
Pdf.RegisterUnicodeTTF('C:\Windows\Fonts\arial.ttf');
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('Arial', [], 11);
Pdf.CurrentPage.TextOut(50, 720, 0, CustomerName);
Pdf.EndDoc;
if Audit.Findings.Count > 0 then
// Gagalkan pekerjaan alih-alih mengirim halaman dengan kotak-kotak di atasnya
raise Exception.Create(Audit.Findings.Text);
finally
Pdf.Free;
end;
Rantai fallback per script, bukan per font
Alasan fallback dilingkupkan berdasarkan script alih-alih berdasarkan font sumber adalah celah cakupan mengelompok berdasarkan sistem tulisan. Font teks Latin kekurangan Devanagari, Thai, Han, dan emoji, semuanya sekaligus, dan pengganti untuk masing-masing adalah font yang berbeda. Mendeklarasikan satu rantai per script karenanya menggambarkan deployment yang nyata: satu font Latin untuk teks badan, satu font CJK, satu font emoji, satu penangkap segala
// THPDFFontScript mencakup hfsCommon, hfsLatin, hfsGreek, hfsCyrillic,
// hfsHebrew, hfsArabic, hfsIndic, hfsSoutheastAsian, hfsCJK, hfsKana,
// hfsHangul, hfsEmoji dan hfsOther
Pdf.SetFontFallbackChain(hfsCJK,
['Microsoft YaHei', 'SimSun', 'Yu Gothic']);
Pdf.SetFontFallbackChain(hfsArabic, ['Segoe UI', 'Arial']);
Pdf.SetFontFallbackChain(hfsEmoji, ['Segoe UI Emoji']);
Pdf.SetFontFallbackChain(hfsOther, ['Arial Unicode MS']);
Fallback dan deteksi saling melengkapi, bukan alternatif. Rantai menangani cakupan yang Anda antisipasi; detektor melaporkan cakupan yang tidak, yang pada sistem yang memproses data pelanggan arbitrer adalah separuh yang menarik. Perhatikan bahwa mengganti font mengubah metrik, sehingga paragraf yang jatuh kembali bisa reflow; jika layout-nya penting, perilaku closure dan subsetting font penggantinya layak dibaca di artikel closure subset font, dan script yang membutuhkan penataan ulang atau penyambungan ditangani oleh tahap shaping yang dijelaskan dalam shaping teks script kompleks
Cara memasang perilaku tambahan tanpa mempertaruhkan jalur yang ada
Rilis yang sama menambahkan fallback tabel kern legacy untuk jarak pasangan, dan cara ia dilingkupkan adalah pola yang layak disalin. Alih-alih menambahkan titik keputusan baru ke logika kerning, fallback itu menggantung pada cabang early-exit yang sudah ada untuk font tanpa tabel GPOS. Font modern dengan GPOS tidak pernah mencapainya, sehingga perilakunya tidak berubah secara konstruksi, bukan karena pengujian. Jalur yang tidak mendaftarkan font Unicode menghasilkan dua offset nol, sehingga tidak berubah pula
Itulah wujud umum retrofit berisiko rendah di pustaka rendering yang matang: temukan cabang yang saat ini tidak menghasilkan apa pun dan letakkan perilaku baru di sana. Itu mengubah "kami percaya ini tidak meregresi apa pun" menjadi "ini tidak mungkin meregresi apa pun", yang jauh lebih baik untuk dikatakan tentang engine teks yang dilalui invoice orang lain
Jadikan gerbang, bukan log
Temuan cakupan hanya berguna jika ada yang gagal karenanya. Dalam layanan pembuat dokumen, susunan yang produktif adalah membiarkan pelacakan menyala dalam pekerjaan regresi malam terhadap korpus nama pelanggan, alamat, dan deskripsi produk nyata, dan menggagalkan pekerjaan pada setiap temuan. Karena event itu menembak sekali per code point unik alih-alih sekali per kemunculan, outputnya tetap cukup kecil untuk dibaca bahkan ketika satu script utuh hilang
Di produksi handler yang sama lebih baik dipakai sebagai telemetri: catat code point dan fontnya, terus sajikan dokumennya, dan biarkan agregatnya memberi tahu script mana yang harus ditambahkan berikutnya ke set font deployment. Perilaku rendering untuk font tertanam dan yang diganti dibahas lebih lanjut di rendering glyph font tertanam, dan daftar property lengkap termasuk TrackUnresolvedGlyphs didokumentasikan di halaman produk HotPDF Delphi PDF component