Artikel Teknis

Merender Font PDF Tak Tertanam dengan Font Sistem di Delphi

Ketika sebuah PDF tak menanam font, komponen HotPDF merender teks itu dengan font Windows terinstal yang dipilih HPDFMapBaseFontToSystem: ia men-decode nama /BaseFont, mencabut bagian style, mencoba beberapa ejaan sampai GDI memastikan family-nya terinstal, mengukur lebar standard 14 yang hilang dari font yang kompatibel metrik, dan mengonversi kode single-byte ke Unicode sebelum menggambar. Setiap langkah itu ada karena versi naifnya gagal di file nyata. Page renderer RenderLoadedPageToBitmap menangani font program tertanam dengan baik; ini kisah font yang sama sekali tak ada di file

Kenapa GDI diam-diam menggambar typeface yang salah untuk font tak tertanam?

GDI tak pernah melaporkan font yang hilang: serahkan CreateFontIndirect sebuah nama face yang tak dikenalnya dan ia diam-diam memilih pengganti, sering kali serif berbeda tanpa weight bold. Renderer awal meneruskan nama PDF hampir apa adanya, jadi TimesNewRoman,Bold, TimesNewRomanPS-BoldMT, dan SegoeUI-Semibold semuanya tak cocok dengan apa pun dan keluar dalam apa pun pilihan GDI. Nama bisa lebih buruk dari itu. ISO 32000-1 §7.3.5 membolehkan sebuah name menulis byte mana pun sebagai #xx, dan produser CJK lazim mengeja nama font sebagai UTF-8 ter-escape atau byte code page legacy; sebelum v2.766.69 escape-nya sendiri menjadi nama face. HPDFMapBaseFontToSystem kini men-decode escape lebih dulu, mengembalikan urutan byte UTF-8 yang valid sebagai karakternya, dan membaca byte tinggi lainnya dalam code page sistem

Mencabut style adalah tempat heuristik menggigit. Koma selalu mengakhiri family (Arial,Bold memberi Arial), tapi tanda hubung hanya melakukannya ketika kata setelahnya adalah style: Bold, Italic, Oblique, Regular, Roman, Medium, Light, Black, Heavy, Semi, Demi, Thin, Extra, Ultra, atau Condensed. Aturan itu menjaga MS-Mincho tetap utuh sambil mengubah Calibri-Light menjadi Calibri. HotPDF lalu mencoba ejaan-ejaan family, disusul ejaan family dengan sufiks PSMT, MT, atau PS yang dicabut. Sejak v2.768.18 ejaan-ejaan itu mencakup setiap pilihan spasi di tempat-tempat sebuah kata bisa dimulai: sebelum kapital yang mengikuti huruf kecil (MyriadPro menjadi Myriad Pro), pada kapital terakhir dari run yang diikuti huruf kecil (UIGothic), dan setelah MS di depan (MSPGothic), dari bentuk penuh spasi sampai nama apa adanya; lebih dari empat tempat semacam itu, hanya bentuk berpenuh spasi dan nama tersambung yang dicoba. Spasi tak bisa diterapkan membabi buta, karena Windows menyimpan beberapa kata dalam bentuk tersambung: SimSun terinstal dengan ejaan persis itu, sedangkan MicrosoftYaHei, MicrosoftJhengHei, dan MSPGothic adalah milik Microsoft YaHei, Microsoft JhengHei, dan MS PGothic. Sebelum v2.768.18 mapper menyelipkan spasi sebelum setiap kapital di dalam, sehingga MicrosoftYaHei dicari sebagai Microsoft Ya Hei dan tak pernah ketemu. Seorang kandidat dianggap terinstal ketika CreateFontIndirect disusul GetTextFace mengembalikan nama yang diminta atau, sejak v2.768.18, ketika tabel name font terpilih mencantumkannya sebagai family, full atau typographic family name dalam bahasa apa pun; jawabannya di-cache per nama, jadi dokumen dengan banyak font yang tak terinstal tak lagi memrobe Windows untuk setiap nama di setiap halaman

Pipeline HotPDF yang merender font PDF tak tertanam dengan font sistem di Delphi: HPDFMapBaseFontToSystem men-decode byte ter-escape #xx di nama /BaseFont, mencabut sufiks style Bold, Italic dan Light sambil menjaga MS-Mincho tetap utuh, membangun kandidat ejaan seperti Myriad Pro dan Microsoft YaHei, dan hanya menerima satu ketika GetTextFace atau tabel name font mengonfirmasi nama terinstalnya
GDI tak pernah melaporkan font yang hilang, ia menggantinya diam-diam — mapping mencoba kandidat-kandidatnya berurutan dan hanya percaya pada nama yang dikembalikan GDI atau yang dicantumkan tabel name font terpilih

Karena fungsi mappingnya publik di unit HPDFRenderFontMetrics, laporan preflight bisa menampilkan family terinstal mana yang akan dipakai merender setiap font tak tertanam, dengan enumerasi font yang THotPDF sudah ekspos untuk dokumen termuat:

uses
  HPDFDoc, HPDFRenderFontMetrics;

procedure ListSystemFontMappings(Pdf: THotPDF; Log: TStrings);
var
  Page, I: Integer;
  Info: THPDFLoadedFontInfo;
begin
  for Page := 0 to Pdf.LoadedPageCount - 1 do
    for I := 0 to Pdf.GetLoadedFontCount(Page) - 1 do
      if Pdf.GetLoadedFontInfo(Page, I, Info) and not Info.IsEmbedded then
        Log.Add(Format('page %d  /%s  %s -> %s',
          [Page + 1, string(Info.ResourceName), string(Info.FontName),
           HPDFMapBaseFontToSystem(Info.FontName)]));
end;

Bagaimana HotPDF mengukur font standard 14 yang tak punya /Widths?

HotPDF mengukur advance yang hilang pada font terinstal dengan metrik yang sama, karena ISO 32000-1 §9.6.2.2 membolehkan font standard 14 menghilangkan /Widths dan library ini tak mengirim tabel AFM. Arial membawa metrik Helvetica, Times New Roman membawa Times, dan Courier New membawa Courier, jadi HPDFMeasureBaseFontWidths membuat face yang cocok pada lfHeight = -1000 dan memanggil GetCharWidth32W; pada tinggi itu hasilnya sudah dalam satuan 1/1000 em yang dipakai lebar PDF. Renderer lebih dulu mengubah setiap kode menjadi Unicode lewat /Encoding, /BaseEncoding, dan /Differences, dengan default StandardEncoding. Export SVG dan ekstraksi teks menginjak satu jebakan lagi: font Type 1 standar tanpa /Encoding sama sekali menghasilkan decoder tanpa informasi encoding, export SVG tak pernah mendaftarkannya, dan semua lebar hasil pengukuran tak terpakai. Menyuplai StandardEncoding yang tersirat memperbaikinya, asalkan ditandai sebagai encoding predefined; mengarahkannya ke jalur nama CMap mendekode setiap kode sebagai 0 dan semua lebar ikut salah

Bold, italic, dan satu-salah-urut di flag font descriptor

Entri /Flags sebuah font descriptor menomori bit-bitnya dari 1, bukan 0, jadi ForceBold adalah bit 19 ($40000) dan Italic bit 7 ($40), menurut ISO 32000-1 Tabel 123. Kode lama menguji $20000, yang merupakan bit 18, SmallCap. Kesalahan itu selamat dari v2.345.0 sampai v2.766.53 karena /FontDescriptor hampir selalu berupa referensi tak langsung dan font builder hanya membaca objek langsung, sehingga seluruh cabang flags tak pernah berjalan, dan kebutaan yang sama mengabaikan /Widths 12 0 R serta menata teks dengan advance fallback 500 unit. Begitu v2.766.53 mulai meresolusi referensi tak langsung lewat renderer, bit itu harus dikoreksi dalam perubahan yang sama, kalau tidak setiap face small-caps mendadak terrender bold:

const
  // ISO 32000-1 Tabel 123 menghitung posisi bit dari 1
  FD_ITALIC     = $00040;  // bit 7
  FD_SMALLCAP   = $20000;  // bit 18, bukan weight
  FD_FORCEBOLD  = $40000;  // bit 19

procedure ApplyDescriptorFlags(Flags: Integer; var LF: TLogFont);
begin
  if (Flags and FD_FORCEBOLD) <> 0 then
    LF.lfWeight := FW_BOLD;
  if (Flags and FD_ITALIC) <> 0 then
    LF.lfItalic := 1;
end;
Penomoran bit entri /Flags font descriptor PDF: ISO 32000-1 Tabel 123 menghitung dari bit 1, menjadikan Italic $40 pada bit 7, SmallCap $20000 pada bit 18 dan ForceBold $40000 pada bit 19, sehingga tes descriptor HotPDF atas $20000 menyasar SmallCap dan tetap tak berbahaya selama referensi /FontDescriptor tak langsung tak pernah diresolusi
Cabang flag adalah dead code selama empat puluh versi karena deskriptornya tak langsung — begitu referensi teresolusi, bit satu-salah-urut itu menjadi teks small-caps yang terlihat

Kenapa font CJK dengan CMap UCS2 mendapat lebar yang salah?

Teks CJK dengan CMap UCS2 predefined menggambar glyph yang tepat tapi spacing yang salah ketika renderer memperlakukan kode sebagai CID, karena /W diindeks oleh CID, bukan oleh kode karakter. Dengan STSong-Light dan UniGB-UCS2-H, kebetulan kodenya sama dengan nilai Unicode, jadi GDI menggambar karakter yang benar dan bug-nya bersembunyi di advance: huruf kecil datang sebagai kode 97 ke atas, jatuh di luar entri /W seperti [1 95 500], dan semuanya menerima lebar default /DW sebesar 1000. Sejak v2.766.56 renderer HotPDF membaca kode lewat rentang codespace CMap (ISO 32000-1 §9.7.6.2) dan memetakannya ke CID sebelum mencari lebar. Hanya tabel UCS2 dan UTF16 bawaan serta stream CMap tertanam yang dipakai; aproksimasi identitas untuk yang seperti GBK-EUC-H hanya akan tampak didukung sambil menghasilkan output yang salah, jadi renderer ini tak berpura-pura

Kenapa teks CJK dengan CMap UCS2 menggambar glyph yang tepat pada advance yang salah: /W diindeks oleh CID sementara kodenya nilai Unicode, sehingga dengan STSong-Light dan UniGB-UCS2-H kode huruf kecil 97 ke atas meleset dari entri /W [1 95 500] dan mengambil default /DW, diperbaiki di HotPDF dengan memetakan kode ke CID lewat rentang codespace CMap
Bug-nya bersembunyi karena kode sama dengan Unicode di sini — glyph tampak benar sementara setiap advance diam-diam memakai default, jadi nilai rendering CJK dari spacing-nya, bukan dari bentuknya

Kenapa karakter beraksen berubah jadi tanda tanya di Windows China?

Kode single-byte tak boleh pernah sampai ke fungsi GDI ANSI ("A"), karena GetGlyphOutlineA dan GetGlyphIndicesA menafsirkan byte dalam code page sistem sementara TextOutA memakai character set font terpilih. Di sistem China, byte $A9 milik Arial (tanda hak cipta di Windows-1252) menjadi lead byte GBK dan terrender sebagai "?", jebakan yang langsung diinjak jalur outline tanpa hint yang ditambahkan di v2.766.83. v2.767.3 menanyakan character set milik font terealisasi dengan GetTextCharset, mengonversinya ke code page lewat TranslateCharsetInfo, menjalankan byte lewat MultiByteToWideChar, dan memanggil fungsi W; font simbol memakai U+F000 plus kodenya sebagai gantinya. Encoding yang tak sepaham dengan Windows-1252 — /Differences, StandardEncoding, MacRomanEncoding — dipetakan ke Unicode sebelum font sistem mana pun melihatnya

Apa batas menggambar dengan font sistem?

Rendering font sistem adalah aproksimasi, dan komponen HotPDF jujur soal di mana ia berhenti. Sebelum v2.768.18 pengecekan instalasi hanya membandingkan nama yang dikembalikan GetTextFace, dan di Windows terlokalisasi fungsi itu melaporkan nama family dalam bahasa sistem, sehingga Microsoft YaHei di Windows China atau Yu Mincho di Windows Jepang dinilai hilang dan digambar dengan pengganti GDI; sejak v2.768.18 face yang kembali dengan nama lain juga dicari di tabel name font, dan font semacam itu ditemukan. Kompatibilitas metrik hanya dijamin untuk keluarga Helvetica, Times, dan Courier; Symbol dipetakan ke Symbol dan ZapfDingbats ke Wingdings, yang merupakan solusi darurat alih-alih kecocokan. Preflight di atas juga hanya melihat font di dictionary /Resources setiap halaman, bukan yang direferensikan dari dalam form XObject. Ketika sebuah kode tetap tak bisa digambar, pelacakan glyph tak teresolusi saat menggambar melaporkannya, sinyal yang lebih baik daripada mengamati thumbnail sepintas

Perbaikan yang tahan lama duduk di sisi penulisan. HotPDF sendiri menulis dengan FontEmbedding disetel True secara default, menggantikan Arial tertanam bahkan ketika kode memanggil SetFont dengan Helvetica, dan teks tertanam melewati renderer glyph font tertanam alih-alih tebak-tebakan di atas. Penjaga murah untuk file yang masuk adalah memperingatkan sebelum merender ketika family hasil mapping tak ada di daftar font screen:

// VCL: Screen.Fonts mencantumkan nama family terinstal (unit Forms)
function MissingSystemFamily(const BaseFont: AnsiString): Boolean;
begin
  Result := Screen.Fonts.IndexOf(HPDFMapBaseFontToSystem(BaseFont)) < 0;
end;

Untuk komponen lengkap, termasuk rendering halaman, ekstraksi teks, dan font subsetting di sisi penulisan, lihat halaman produk HotPDF Delphi PDF component