HotXLS, komponen Excel Delphi dan C++Builder, memangkas ukuran font PDF yang disematkan lewat subsetting font TrueType: pada saat ekspor PDF, ia memanggil fungsi CreateFontPackage milik library sistem Windows fontsub.dll untuk membangun ulang sebuah font TrueType yang disematkan hanya di sekitar code point Unicode yang benar-benar digunakan sebuah worksheet, alih-alih mengirimkan seluruh file typeface. Sebuah laporan dengan dua ratus baris nama produk berbahasa Cina mungkin hanya membutuhkan beberapa ratus karakter Han berbeda, padahal font CJK yang disertakan Windows biasanya berukuran 5 hingga 20 MB masing-masing. Sematkan satu secara utuh, dan font itu sendiri bisa melebihi berat setiap objek lain dalam PDF digabungkan
fontsub.dll bukan library yang pernah didengar kebanyakan developer Delphi, dan ada alasan untuk itu: Microsoft mengirimkannya sebagai sebuah DLL utility kecil yang jarang didokumentasikan alih-alih sebuah API Win32 utama. HotXLS memperlakukannya sebagai sebuah kemampuan opsional, bukan ketergantungan keras, sehingga bagaimana exporter memuatnya, memanggilnya, dan jatuh kembali ketika tidak ada bercerita sama banyaknya tentang pemrograman Windows defensif seperti halnya tentang format font, dan kedua separuh cerita itu layak ditelusuri
Mengapa teks Unicode membengkakkan ekspor PDF HotXLS?
Exporter PDF milik HotXLS baru menggunakan sebuah font TrueType yang disematkan hanya ketika teks worksheet berada di luar WinAnsi, dan tetap menggunakan keluarga Helvetica bawaan selebihnya, jalur default yang dibahas mendalam di panduan ekspor worksheet-ke-PDF. WinAnsi mencakup teks Eropa Barat cukup baik sehingga banyak workbook yang tidak pernah memicu penyematan font sama sekali: PDF-nya hanya merujuk Helvetica dengan nama dan reader menyediakannya secara lokal, sehingga file tetap kecil. Begitu sebuah sel memuat sesuatu yang tidak bisa direpresentasikan WinAnsi, sebuah nama produk berbahasa Cina, sebuah catatan berbahasa Korea, sebuah simbol nyasar dalam komentar, exporter harus menyematkan sebuah program font sungguhan, karena sebuah PDF reader tidak memiliki sumber glyph fallback untuk karakter di luar 14 font standar
HotXLS menemukan font itu secara otomatis, memindai folder Fonts Windows untuk daftar pendek kandidat yang terinstal, termasuk typeface berkemampuan-CJK yang disertakan Windows untuk rendering Cina dan Korea, kecuali properti UnicodeFontFile milik exporter sudah menunjuk ke sebuah file spesifik, dan font mana pun yang dipilihnya akan disematkan secara utuh sebelum subsetting pernah berjalan. Persyaratan penyematan itu spesifik untuk PDF: jalur ekspor RTF dan HTML milik HotXLS menjaga teks Unicode tetap utuh dengan meng-escape code point ke dalam byte stream alih-alih mengirimkan sebuah program font, itulah sebabnya masalah ukuran yang dibahas artikel ini tidak memiliki padanan pada kedua format itu
uses
lxHandle, lxPDF;
var
Book: TXLSWorkbook;
Exporter: TXLSPDFExport;
begin
Book := TXLSWorkbook.Create;
try
Book.Open('catalog-cn.xlsx');
Exporter := TXLSPDFExport.Create;
try
// Optional: pin a specific CJK-capable font instead of the
// exporter's automatic Windows\Fonts scan.
Exporter.UnicodeFontFile := 'C:\Windows\Fonts\simhei.ttf';
Exporter.SaveAsPDF(Book.ActiveSheet, 'catalog-cn.pdf');
finally
Exporter.Free;
end;
finally
Book.Free;
end;
end;
Apa itu fontsub.dll, dan mengapa tidak menulis subsetter dari nol?
fontsub.dll adalah library sistem Windows kecil, disertakan sejak Windows XP, yang mengekspos satu fungsi yang relevan di sini: CreateFontPackage. Berikan padanya byte dari sebuah font TrueType sumber dan sebuah daftar code point Unicode untuk dipertahankan, dan ia menyerahkan kembali sebuah font minimal yang tetap memenuhi setiap batasan format-font: indeks glyph dinomori ulang, glyf dan loca dibangun ulang hanya di sekitar outline yang dipertahankan, hmtx dan cmap ditulis ulang agar cocok. HotXLS mendeklarasikan tipe pointer fungsi langsung terhadap kontrak tersebut
const
TTFCFP_FLAGS_SUBSET = 1;
TTFMFP_SUBSET = 0;
TTFCFP_MS_PLATFORMID = 3;
TTFCFP_UNICODE_CHAR_SET = 1;
type
TCreateFontPackage = function(puchSrcBuffer: Pointer; ulSrcBufferSize: Cardinal;
var puchFontPackageBuffer: PAnsiChar; var pulFontPackageBufferSize: Cardinal;
var pulBytesWritten: Cardinal; usFlags, usTTCIndex, usSubsetFormat,
usSubsetLanguage, usSubsetPlatform, usSubsetEncoding: Word;
pusSubsetKeepList: PWordArray; usSubsetKeepListCount: Word;
lpfnAllocate, lpfnReAllocate, lpfnFree, reserved: Pointer): Cardinal; cdecl;
Menulis sendiri pekerjaan CreateFontPackage alih-alih memanggilnya akan berarti mengimplementasikan sebuah subsetter TrueType yang benar: menelusuri glyph komposit untuk menarik setiap glyph komponen yang dirujuk sebuah glyph yang dipertahankan, membangun ulang offset loca setelah outline dibuang, menghormati bit izin-penyematan dalam tabel OS/2 milik sebuah font, dan membenarkan semuanya di seluruh font aneh apa pun yang kebetulan terinstal di mesin seorang pelanggan. Microsoft sudah menyelesaikan masalah itu dan mengirimkan solusinya sebagai bagian dari Windows itu sendiri, sehingga memanggil sebuah DLL sistem yang dipeliharanya, diuji terhadap stack rendering-font-nya sendiri, dan didistribusikan ke setiap mesin secara gratis hanya membebani HotXLS satu dynamic load dan satu function pointer; mengimplementasikan ulang logika yang sama berarti memiliki sebuah parser untuk sebuah format biner dengan puluhan tahun kasus tepi, untuk sebuah fitur yang hanya penting ketika sebuah font kebetulan besar
Membangun keep-list dari glyph yang benar-benar dirender
HotXLS membangun keep-list subsetting dari sebuah map yang sudah dipeliharanya karena alasan lain, sehingga pencatatan itu tidak menambah biaya apa pun. Setiap kali kode rendering halaman menggambar sebuah karakter yang membutuhkan font Unicode yang disematkan, ia mencari indeks glyph karakter itu dan mencatat pasangannya di FUnicodeGlyphMap, sebuah tabel glyph-ke-codepoint yang juga menggerakkan CMap PDF ToUnicode sehingga copy-and-paste dari dokumen jadi mengembalikan teks asli alih-alih ID glyph mentah. Pada saat stream konten halaman sudah selesai, map itu sudah mendaftarkan persis kumpulan code point Unicode yang digunakan dokumen tersebut, tidak lebih tidak kurang
var
keepList: array of Word;
keepCount, i: Integer;
codePoint: LongWord;
begin
SetLength(keepList, FUnicodeGlyphMap.Count);
keepCount := 0;
for i := 0 to FUnicodeGlyphMap.Count - 1 do
begin
codePoint := LongWord(StrToIntDef('$' + FUnicodeGlyphMap.ValueFromIndex[i], 0));
if codePoint > 0 then
begin
keepList[keepCount] := Word(codePoint);
Inc(keepCount);
end;
end;
end;
Pada saat finalisasi, HotXLS menelusuri map yang sama itu untuk kedua kalinya guna membangun keep-list yang diharapkan CreateFontPackage, sebuah array polos berisi code point Unicode untuk dipertahankan dalam bentuk 16-bit yang dibutuhkan argumen keep-list API tersebut. Karena argumen itu adalah sebuah array word 16-bit, ia mengalamati Basic Multilingual Plane dengan bersih, yang mencakup teks CJK biasa, Cyrillic, Yunani, dan Arab tanpa komplikasi; sebuah worksheet yang mengandalkan karakter supplementary-plane, emoji tertentu atau skrip historis langka, berada di luar apa yang bisa disebutkan langsung oleh satu entri keep-list, yang merupakan sebuah batas yang layak diketahui alih-alih sebuah cacat, karena sebagian besar spreadsheet bisnis yang berat-Unicode tidak pernah mendekati plane itu sama sekali
Apa yang terjadi ketika fontsub.dll tidak ada?
HotXLS tidak pernah mengasumsikan fontsub.dll ada, dan ekspor PDF tidak pernah gagal karena ketidakhadirannya. Library itu dimuat secara dinamis pada saat sebuah subset dibutuhkan, dengan SafeLoadLibrary dan GetProcAddress alih-alih sebuah import statis, persis karena fontsub.dll bukan API publik terdokumentasi yang dijamin-ada seperti kernel32.dll: ia adalah tooling penyematan-font yang dibundel, dan tidak ada apa pun dalam kontrak Microsoft yang menjanjikan ia bertahan di setiap SKU, setiap servicing branch, atau setiap lapisan kompatibilitas yang mencoba mengemulasi Windows
var
hFontSub: HMODULE;
CreateFontPackage: TCreateFontPackage;
begin
hFontSub := SafeLoadLibrary('FontSub.dll');
if hFontSub = 0 then
Exit; // no subsetting available - keep the full embedded font
try
@CreateFontPackage := GetProcAddress(hFontSub, 'CreateFontPackage');
if not Assigned(CreateFontPackage) then
Exit;
// ... call CreateFontPackage, check its return code ...
finally
FreeLibrary(hFontSub);
end;
end;
Setiap jalur kegagalan melipat kembali ke hasil yang sama. Sebuah DLL yang hilang, sebuah export yang hilang, sebuah kode kembali bukan-nol, atau sebuah font yang tabel OS/2-nya melarang subsetting lewat bit izin-penyematannya, HotXLS sekadar mempertahankan font lengkap yang sudah disematkannya dan melanjutkan. Tidak ada yang memunculkan exception, tidak ada yang membatalkan ekspor, dan kode pemanggil tidak pernah perlu membungkus sebuah optimasi font dalam exception handling-nya sendiri; PDF yang diekspor tetap valid dalam kedua kasus, dan satu-satunya variabel adalah apakah hasilnya kecil atau sedikit lebih besar
Seberapa jauh sebenarnya PDF-nya mengecil?
Subsetting font TrueType milik HotXLS biasanya menyusutkan PDF hasil ekspor sebuah worksheet berat-Unicode menjadi di suatu tempat antara seperduapuluh hingga seperdelapan ukuran tanpa-subset-nya, sebuah pengurangan 8-hingga-20-kali yang skalanya mengikuti seberapa banyak dari sebuah font lengkap yang benar-benar disentuh sebuah dokumen tertentu: sebuah purchase order yang dibangun di sekitar beberapa ratus karakter Cina berbeda hanya mempertahankan beberapa ratus glyph itu dari puluhan ribu yang disertakan sebuah typeface CJK, sementara sebuah sheet yang mencakup campuran karakter lebih luas mempertahankan proporsi yang lebih banyak. HotXLS melapiskan satu langkah kompresi Flate tambahan di atas byte font subset itu sebelum menulisnya ke dalam stream /FontFile2 milik PDF, kompresi yang sama yang sudah dilalui sisa stream konten dokumen, dan tidak satu pun dari itu meminta apa pun tambahan dari kode pemanggil: sebuah worksheet yang tidak pernah meninggalkan WinAnsi tidak pernah menyentuh jalur ini dan tetap mengekspor lewat Helvetica polos, sementara sebuah worksheet yang memang memicu jalur font Unicode mendapatkan subsetting secara otomatis, tanpa properti yang perlu diatur dan tanpa pemanggilan terpisah yang perlu dibuat, dan satu properti yang terlibat, UnicodeFontFile, hanya memilih font mana yang disematkan dan disubset, bukan apakah subsetting terjadi
Subsetting font adalah satu detail di dalam permukaan ekspor PDF yang lebih luas milik HotXLS Delphi Excel Component, berdampingan dengan paginasi, metadata cetak worksheet, dan jalur ekspor CSV, HTML, dan RTF yang disertakannya