PDFium Component memungkinkan aplikasi Delphi memutuskan byte font mana yang digunakan ketika sebuah PDF merujuk font yang tidak disematkannya. ConfigureSystemFontProvider menginstal implementasi IPdfSystemFontProvider yang menerima setiap permintaan pemetaan font yang dibuat PDFium, lengkap dengan nama face, weight, flag italic, charset, dan pitch family, lalu menjawab dengan byte TrueType, TrueType Collection, atau OpenType yang akan digunakan
Ini ada karena font yang tidak disematkan adalah lotere rendering. PDF yang menyebutkan Arial dan tidak menyematkan apa pun akan merender dengan Arial pada sebuah workstation, dengan pengganti yang metric-compatible pada server Linux, dan dengan apa pun yang ditemukan mapper host pada image container yang dikunci. Invoice yang sama tampak berbeda pada masing-masing, line break bergeser, dan pelanggan menerima dokumen yang tidak cocok dengan salinan arsipnya
Mengapa tidak sekadar menginstal font di server?
Kadang itulah jawabannya, dan bila memang itu, lakukan saja. Tetapi cara itu gagal dalam tiga situasi umum. Lisensi mungkin melarang penginstalan font di server untuk rendering otomatis. Image container sering dibangun ulang dan font yang diinstal secara manual hilang pada deployment berikutnya. Dan alur kerja yang diregulasi memerlukan rendering stack yang dapat direproduksi dari artefak di bawah kontrol versi, sesuatu yang tidak dimiliki instalasi font seluruh mesin
Provider mengatasi ketiganya dengan memindahkan keputusan ke dalam aplikasi Anda. Font dikirimkan sebagai resource yang Anda kendalikan, kebijakan pemetaan adalah kode yang dapat Anda tinjau, dan binary yang sama merender secara identik di mana saja karena tidak bergantung pada apa yang kebetulan terinstal
Menginstal sebuah provider
Konfigurasi harus terjadi sebelum library dimuat. PDFium menerima struktur system font info saat inisialisasi dan menyimpan handle yang diserahkannya sesudahnya, sehingga menukar provider saat dokumen masih terbuka akan membuat handle font yang masih dipegang PDFium menjadi tidak valid; komponen ini menolaknya secara langsung alih-alih membiarkannya merusak sebuah render:
uses
PDFium;
type
TAppFontProvider = class(TInterfacedObject, IPdfSystemFontProvider)
public
function ResolveFont(const Request: TPdfSystemFontRequest;
out Font: TPdfSystemFontData): Boolean;
end;
function TAppFontProvider.ResolveFont(const Request: TPdfSystemFontRequest;
out Font: TPdfSystemFontData): Boolean;
var
Path: string;
begin
// Pemetaan deterministik: nama face ditambah weight dan italic memutuskan
// berkas mana yang kita kirim untuk permintaan ini
Path := MapFaceToBundledFile(Request.FaceName, Request.Weight,
Request.Italic, Request.Charset);
Result := Path <> '';
if not Result then
Exit;
Font.FaceName := Request.FaceName;
Font.FontData := LoadFileBytes(Path); // byte sfnt atau TTC lengkap
Font.Charset := Request.Charset;
Font.TTCIndex := 0; // indeks di dalam sebuah collection
end;
var
Policy: TPdfSystemFontPolicy;
begin
Policy := TPdfSystemFontPolicy.Default;
Policy.AllowDefaultFallback := False; // host memutuskan segalanya
Policy.AllowFaceSubstitution := False; // tolak nama face yang berbeda
Policy.MaxFontBytes := 32 * 1024 * 1024;
Policy.MaxCacheEntries := 64;
ConfigureSystemFontProvider(TAppFontProvider.Create, Policy);
// Baru sekarang muat library dan buka dokumen
end;
Teardown berjalan dalam urutan terbalik: provider dilepas dari PDFium terlebih dahulu, baru library dibongkar. Melewatkan pelepasan itu meninggalkan handle font native yang menunjuk ke objek Pascal yang akan segera dibebaskan, yang merupakan access violation shutdown klasik pada kode yang mencampur interface reference-counted dengan library C
Apa yang sebenarnya diputuskan flag policy?
AllowDefaultFallback adalah sakelar antara dua mode operasi. Ketika dimatikan, permintaan yang ditolak provider langsung gagal, yang Anda inginkan saat membuktikan bahwa setiap font dalam sebuah korpus sudah terhitung: celah apa pun langsung terlihat alih-alih ditutupi. Ketika dinyalakan, permintaan yang tidak terselesaikan didelegasikan ke mapper yang dikembalikan FPDF_GetDefaultSystemFontInfo, sementara dunia luar tetap melihat satu wrapper handle yang seragam, dengan nama face, charset, data tabel, dan rutin penghapusan font yang dirutekan dengan benar berdasarkan asalnya
AllowFaceSubstitution mengatur apakah sebuah provider boleh menjawab dengan nama face yang berbeda dari yang diminta. Mematikannya membuat substitusi menjadi keputusan eksplisit alih-alih kecelakaan, yang penting ketika sebuah dokumen menyebutkan font yang metriknya berbeda cukup jauh sehingga mengubah pagination
Komponen ini memvalidasi setiap respons provider sebelum mencapai PDFium: data kosong ditolak, font berukuran berlebihan ditolak terhadap MaxFontBytes, indeks TTC diperiksa, dan tabel sfnt individual disajikan dari direktori font ketika PDFium meminta sebuah tabel alih-alih meminta seluruh berkas. Kapabilitas terakhir itu berarti sebuah provider dapat menyerahkan berkas font lengkap dan membiarkan komponen menjawab query level tabel, alih-alih mengekspos objek Pascal mentah lintas C ABI
Caching tanpa data font yang menggantung
Permintaan pemetaan font berulang terus-menerus selama rendering, sehingga respons di-cache dengan sebuah key yang mencakup setiap parameter pemilihan font, dievict dengan urutan least-recently-used yang dibatasi. Kehalusannya ada pada masa hidup: PDFium mungkin masih membaca byte dari sebuah font yang entri cache-nya baru saja dievict
Cache menyimpan dynamic array reference-counted dan setiap handle native memegang snapshot-nya sendiri, sehingga eviction hanya menjatuhkan sebuah referensi, bukan membebaskan memori yang sedang digunakan. Callback delete melepaskan handle-nya dan menjaga jumlah aktif. Secara praktis, ini berarti MaxCacheEntries dapat disetel untuk memori tanpa risiko apa pun menarik data dari bawah sebuah render yang sedang berjalan
Apakah provider dipanggil pada thread saya?
Tidak, belum tentu. PDFium dapat memanggil mapper dari worker thread-nya sendiri, sehingga sebuah implementasi harus thread safe. Counter bersama, cache, dan observasi konfigurasi masing-masing dilindungi di dalam komponen oleh critical section-nya sendiri, tetapi kode di dalam ResolveFont adalah tanggung jawab Anda untuk membuatnya aman
Bentuk paling aman adalah provider yang tidak menyentuh state bersama yang mutable: baca dari tabel yang dibangun saat startup, muat byte dari sebuah berkas atau resource, kembalikan. Jika sebuah lookup memerlukan cache bersama milik Anda sendiri, jaga itu. Dan jaga exception tetap di dalam implementasi Anda, karena sebuah exception Pascal tidak boleh pernah unwind melewati stack PDFium; komponen ini menangkap pada batas C ABI dan mengonversinya menjadi kegagalan atau fallback default opsional, tetapi mengandalkan itu sebagai alur kontrol normal memakan biaya performa dan menyembunyikan bug. Aturan threading untuk bagian komponen lainnya mengikuti prinsip yang sama dengan yang ada dalam disiplin render lock
Membuktikan pemetaan di produksi
Statistik mengubah substitusi font dari tebakan menjadi sesuatu yang dapat Anda asersikan. GetSystemFontProviderStatistics melaporkan apakah sebuah provider dikonfigurasi dan terinstal, berapa banyak permintaan pemetaan yang dibuat, dan bagaimana mereka dipenuhi, dipilah menjadi cache hit, provider hit, dan default fallback hit, beserta respons yang ditolak, permintaan yang gagal, handle yang aktif, dan font yang di-cache:
var
Stats: TPdfSystemFontStatistics;
begin
Stats := GetSystemFontProviderStatistics;
Writeln(Format('requests=%d cache=%d provider=%d fallback=%d',
[Stats.MapRequests, Stats.CacheHits, Stats.ProviderHits,
Stats.DefaultFallbackHits]));
Writeln(Format('rejected=%d failed=%d handles=%d cached=%d',
[Stats.RejectedProviderResponses, Stats.FailedRequests,
Stats.ActiveHandles, Stats.CachedFonts]));
// Dalam sebuah run konformansi dengan fallback dimatikan, fallback hit
// atau permintaan gagal apa pun berarti sebuah dokumen merujuk font yang tidak kita kirim
if (Stats.DefaultFallbackHits > 0) or (Stats.FailedRequests > 0) then
raise Exception.Create('unmapped font encountered - update the font set');
end;
Angka RejectedProviderResponses yang meningkat adalah sinyal bahwa sebuah provider menjawab dengan data yang ditolak policy, biasanya berkas yang berukuran berlebihan atau face yang disubstitusi, dan itu layak dipasangi alert karena permintaan tersebut secara diam-diam terdegradasi ke fallback atau kegagalan. Untuk mendiagnosis font mana yang sebenarnya dibutuhkan sebuah dokumen sebelum Anda membangun tabel pemetaan, jalur inspeksi dalam menganalisis properti font PDF mencantumkan font yang disematkan dan tidak disematkan per dokumen
Penyediaan font, rendering, dan ekstraksi teks berbagi instance library yang sama di Delphi, C++Builder, dan Lazarus; detail deployment dijelaskan pada halaman PDFium Component untuk Delphi