Menyediakan font di produksi
Sekilas pandang
Bagian berjudul “Sekilas pandang”PDF Anda dirender dengan benar di laptop Anda, lalu dikirim ke sebuah kontainer dan keluar sebagai deretan kotak kosong — glyph “tofu” — atau dengan aksen dan karakter non-Latin yang hilang. Penyebabnya hampir selalu sama: font yang Anda pilih tidak ada di image yang ter-deploy.
Native engine NextPDF yang berjalan in-process me-resolve font dari berkas
font yang dapat dibaca oleh font registry. Ia tidak menemukan font OS atau
fontconfig secara otomatis — berkas font yang terpasang di OS hanya membantu
jika Anda secara eksplisit mendaftarkan berkas tersebut atau menambahkan
direktori yang memuatnya ke search path FontRegistry. Sebuah kontainer yang
dibangun dari base image ramping tidak memiliki font yang terpasang via
apt/apk, dan bahkan ketika ada, native engine mengabaikannya kecuali Anda
mengarahkan registry ke berkasnya. Perbaikannya adalah membundel berkas font yang
sebenarnya di dalam aplikasi atau image Anda dan mendaftarkannya ke engine.
Registry membaca berkas TrueType (.ttf), OpenType (.otf), dan TrueType
Collection (.ttc); Type1 (.pfb) legasi juga diterima tetapi jarang
diperlukan untuk pekerjaan baru.
Sebelum Anda mulai, pastikan bagian-bagian ini telah tersedia:
- NextPDF core terpasang.
- Anda memiliki berkas font yang sebenarnya yang ingin Anda gunakan, dan Anda berlisensi untuk menyematkannya. Hak penyematan adalah tanggung jawab Anda — lihat Menyematkan dan men-subset font TrueType.
- Build Anda dapat menyalin berkas-berkas tersebut ke dalam artefak yang ter-deploy.
Ini adalah how-to operasi. Kodenya minimal; pekerjaannya ada pada build dan tata letak filesystem. Untuk mekanik tingkat API dalam mendaftarkan dan men-subset satu face, baca resep embed-and-subset yang ditautkan di atas. Halaman ini membahas cara memasukkan berkas ke dalam server dan mengarahkan engine ke sana.
Mengapa native engine tidak menemukan font OS secara otomatis
Bagian berjudul “Mengapa native engine tidak menemukan font OS secara otomatis”Ada dua jalur rendering yang berbeda, dan kisah font berbeda di antara keduanya.
- Native engine in-process (default,
Document/writeHtml): engine tidak memanggil sistem font sistem operasi ataufontconfiguntuk penemuan. Ia me-resolve sebuah face melalui font registry, yang membaca sebuah berkas font spesifik yang Anda daftarkan atau menemukan satu di dalam direktori yang Anda konfigurasi sebagai search path. Memasang font denganapt-get install fonts-notoatau menjalankanfc-cachetidak melakukan apa pun dengan sendirinya — native engine hanya melihat berkas-berkas tersebut jika Anda mendaftarkannya atau menambahkan direktorinya ke search path registry. - Chrome bridge (perender HTML-ke-PDF yang menggerakkan headless browser):
jalur ini memang menggunakan font terpasang dari host melalui penemuan font
normal milik browser, sehingga paket font
apt/apkdanfontconfigpenting di sana.
Jika Anda membaca panduan umum “pasang paket font sistem ini di Dockerfile Anda”, panduan itu berlaku untuk Chrome bridge, bukan untuk native engine yang dibahas di halaman ini. Untuk generasi native, bundel berkasnya dan daftarkan.
Langkah 1 — Bundel berkas font yang sebenarnya
Bagian berjudul “Langkah 1 — Bundel berkas font yang sebenarnya”Letakkan berkas font di dalam pohon aplikasi Anda sehingga mereka terversioning
dan dikirim dengan setiap build. Lokasi konvensional adalah direktori
resources/fonts/.
your-app/├── resources/│ └── fonts/│ ├── DejaVuSans.ttf│ ├── DejaVuSans-B.ttf│ └── NotoSansCJK-Regular.ttc└── src/Namai berkas sehingga pencarian direktori engine dapat menemukannya berdasarkan
family dan style. Ketika Anda mendaftarkan sebuah direktori (alih-alih berkas
spesifik) dan nantinya memanggil setFont('DejaVuSans', 'B', 12), engine mencari
berkas seperti DejaVuSans-B.ttf, DejaVuSansB.ttf, atau DejaVuSans.ttf di
setiap direktori yang dikonfigurasi. Pencarian direktori membangun nama-nama
kandidat tersebut dari kode style satu-huruf yang sama yang Anda teruskan ke
setFont (B untuk bold, I untuk italic, BI untuk bold-italic), bukan
kata yang dieja penuh — sehingga bentuk yang andal adalah
Family-<StyleCode>.ttf (misalnya DejaVuSans-B.ttf atau DejaVuSans-BI.ttf),
bukan Family-Bold.ttf. Sebuah berkas bernama DejaVuSans-Bold.ttf tidak
pernah ditemukan oleh pencarian direktori; untuk menggunakan berkas semacam itu,
daftarkan secara eksplisit dengan register() — yang mengurai font dan
mengindeksnya di bawah family dan style yang dibaca dari name table berkas itu
sendiri, sehingga nama berkas yang dieja penuh tidak lagi penting (lihat Langkah
2).
Langkah 2 — Daftarkan font ke engine
Bagian berjudul “Langkah 2 — Daftarkan font ke engine”Anda punya dua cara yang setara untuk membuat berkas terlihat. Keduanya melalui
NextPDF\Typography\FontRegistry, yang mengimplementasikan
NextPDF\Contracts\FontRegistryInterface.
Daftarkan berkas spesifik di bawah sebuah alias ketika Anda mengendalikan face yang persis:
use NextPDF\Typography\FontRegistry;
$registry = new FontRegistry();$registry->register(__DIR__ . '/../resources/fonts/DejaVuSans.ttf', alias: 'DejaVuSans');register(string $fontFile, string $alias = '', int $fontIndex = 0) menerima
berkas .ttf, .otf, dan .ttc, plus Type1 .pfb legasi (yang memuat metrik
.afm pendampingnya dari path yang sama); $fontIndex memilih sub-font di dalam
sebuah TrueType Collection (.ttc). register() mengurai berkas dan mengindeks
face berdasarkan family dan style yang dibaca dari name table-nya sendiri,
sehingga nama berkas fisik tidak relevan setelah terdaftar. $alias opsional
hanyalah nama lookup tambahan untuk face — ia bukan kode style dan tidak mengubah
style mana yang disediakan berkas; teruskan ketika Anda ingin memanggil
setFont() dengan nama selain nama family tersemat font. Ia mengembalikan
FontInfo yang telah diurai.
Daftarkan sebuah direktori ketika Anda ingin engine me-resolve face berdasarkan nama dari sebuah folder yang Anda kendalikan:
$registry = new FontRegistry('/var/www/app/resources/fonts');// or, equivalently, after construction:$registry->addFontDirectory('/var/www/app/resources/fonts');Constructor FontRegistry mengambil direktori tersebut sebagai argumen
pertamanya, dan addFontDirectory() menambahkan search path lain. Sebuah
Document polos juga mengekspos addFontDirectory() untuk kasus standalone.
Untuk menggunakan registry yang Anda isi sendiri, bangun dokumen melalui
DocumentFactory, yang mengaitkan registry persis itu ke setiap dokumen yang
dibuatnya:
use NextPDF\Core\DocumentFactory;use NextPDF\Graphics\ImageRegistry;
$factory = new DocumentFactory($registry, new ImageRegistry(maxCacheBytes: 0));
$doc = $factory->create();$doc->addPage();$doc->setFont('DejaVuSans', '', 12);$doc->cell(0, 10, 'Réndéred wîth a bundled face — no tofu.', newLine: true);$doc->save('/tmp/out.pdf');Document::createStandalone() membangun registry internalnya sendiri, sehingga
sebuah face yang Anda daftarkan pada FontRegistry terpisah tidak terlihat
olehnya. Di produksi, lewati DocumentFactory (atau factory framework Anda)
sehingga registry yang terisi adalah yang digunakan.
Konfigurasi framework
Bagian berjudul “Konfigurasi framework”Setiap integrasi framework mengekspos dua konsep yang sama sebagai konfigurasi,
sehingga Anda jarang menyentuh registry secara langsung. Pada nextpdf.php paket
Laravel, fonts_path (default NEXTPDF_FONTS_PATH, jatuh kembali ke
resource_path('fonts')) adalah direktori pencarian, dan preload_fonts adalah
sebuah list path berkas font absolut yang diurai saat boot worker. Arahkan
fonts_path ke direktori yang Anda bundel dan face yang Anda daftarkan
ter-resolve secara otomatis.
Langkah 3 — Sediakan font dalam image Docker
Bagian berjudul “Langkah 3 — Sediakan font dalam image Docker”Dalam sebuah kontainer, berkas font harus menjadi bagian dari layer image,
disalin saat build time. Karena kode aplikasi dan font dikirim bersama ketika
Anda membundelnya di bawah resources/fonts/, sebuah COPY . . biasa sudah
membawanya. Jika Anda menyimpan font di luar konteks build, salin secara
eksplisit dan pastikan path yang Anda daftarkan cocok dengan path di dalam image.
# Native engine: NO system font packages are required.# The native engine does not discover OS-installed fonts automatically; install OS# font packages (`apt-get install fonts-*`) only if you also register them or point# the font registry's search directory at their files.FROM php:8.4-cli
WORKDIR /var/www/app
# Bundle the application, including resources/fonts/, into the image.COPY . /var/www/app
# Make the bundled directory the engine's font search path.ENV NEXTPDF_FONTS_PATH=/var/www/app/resources/fonts
CMD ["php", "bin/generate.php"]Pada filesystem imutabel atau read-only (kontainer
readOnlyRootFilesystem, image serverless, atau host yang dikeraskan), berkas
font dibaca saat generation time dan tidak pernah ditulis, sehingga mount
read-only tidak masalah. Satu-satunya penulisan yang mungkin diinginkan engine
adalah cache font-terurai-nya: berikan direktori itu sebuah volume tulis yang
kecil, atau panaskan dan kunci registry saat boot (bagian berikutnya) sehingga
tidak ada penulisan atau pendaftaran runtime yang dicoba.
Langkah 4 — Panaskan dan verifikasi
Bagian berjudul “Langkah 4 — Panaskan dan verifikasi”Pada worker yang berjalan lama, urai setiap face sekali saat boot, lalu kunci registry sehingga tidak ada pendaftaran per-permintaan yang terjadi dan sebuah salah-konfigurasi gagal dengan lantang alih-alih diam-diam jatuh kembali:
$registry = new FontRegistry('/var/www/app/resources/fonts');$registry->warmup([ '/var/www/app/resources/fonts/DejaVuSans.ttf', '/var/www/app/resources/fonts/DejaVuSans-B.ttf',]);$registry->lock();Setelah lock(), register(), addFontDirectory(), dan warmup() melempar,
yang mengubah kesalahan “path salah di image” menjadi kegagalan boot yang keras
alih-alih halaman tofu di produksi.
Tambahkan pemeriksaan smoke deployment yang merender satu halaman dengan setiap face yang diperlukan. Pemeriksaan header di bawah hanya memverifikasi bahwa dokumen menghasilkan keluaran — ia tidak membuktikan bahwa font terurai, tersemat, atau bahkan ter-resolve. Sebuah face yang tidak dapat ditemukan engine mungkin jatuh kembali ke base font standar (dan, di bawah perilaku non-strict saat ini, sebuah profil konformitas mungkin justru menyediakan pengganti yang dibundel) sambil tetap memancarkan PDF yang valid dan tidak kosong — sehingga bahkan ketika fallback itu terjadi, pemeriksaan ini saja tidak akan menangkap degradasi senyap. Jangan mengandalkan fallback dijamin atau senyap pada setiap jalur; verifikasi program tersemat secara langsung, seperti ditunjukkan di bawah:
$doc = $factory->create();$doc->addPage();$doc->setFont('DejaVuSans', '', 12);$doc->cell(0, 10, 'warmup check', newLine: true);
$pdf = $doc->getPdfData();
// `getPdfData()` would normally throw on a real failure; this header check only// confirms serialization returned PDF bytes, not that any specific font resolved.if (!str_starts_with($pdf, '%PDF')) { throw new RuntimeException('Font warmup smoke check produced no PDF output.');}Untuk benar-benar menggagalkan deploy ketika sebuah face hilang, periksa PDF yang
dipancarkan untuk program font tersemat. Sebuah face terdaftar yang ter-resolve
membawa font dictionary-nya sendiri dengan program tersemat, sehingga menegaskan
keberadaannya menangkap kasus di mana face yang diminta tidak pernah ter-resolve
(apa pun yang dijadikan fallback oleh engine) yang dilewatkan oleh pemeriksaan
header. Kunci mana yang menampung program bergantung pada format outline: outline
TrueType (.ttf, .ttc) menggunakan /FontFile2, outline CFF/OpenType (.otf
dengan outline PostScript) menggunakan /FontFile3, dan Type1 (.pfb) legasi
menggunakan /FontFile.
Jika yang Anda butuhkan hanyalah sinyal “ada program font tersemat” yang agnostik
terhadap format, uji /FontFile saja — karena /FontFile adalah substring
dari baik /FontFile2 maupun /FontFile3, sebuah pemeriksaan substring polos
sudah mencocokkan setiap tipe outline, dan menambahkan /FontFile2//FontFile3
sebagai cabang || ekstra adalah redundan:
if (!str_contains($pdf, '/FontFile')) { throw new RuntimeException('No embedded font program found — face fell back.');}Namun, sebuah substring /FontFile polos tidak dapat membedakan tipe outline.
Untuk membedakannya, cocokkan token persisnya dengan word boundary sehingga
/FontFile tidak juga terpicu pada /FontFile2 atau /FontFile3:
$isTrueType = preg_match('~/FontFile2\b~', $pdf) === 1; // TrueType (.ttf/.ttc)$isCffOtf = preg_match('~/FontFile3\b~', $pdf) === 1; // CFF/OpenType (.otf)$isType1 = preg_match('~/FontFile(?![23])\b~', $pdf) === 1; // Type1 (.pfb)
if (!$isTrueType && !$isCffOtf && !$isType1) { throw new RuntimeException('No embedded font program found — face fell back.');}Bagaimanapun, perlakukan ini sebagai heuristik kasar saja, bukan gerbang
deploy yang andal. Pencarian byte mentah atas PDF yang diserialkan tidak akurat
karena beberapa alasan: program font dapat berada di dalam object stream
terkompresi (di mana /FontFile* tidak pernah muncul sebagai byte polos),
incremental update dapat menambahkan atau menggantikan object,
font non-tersemat atau standard-14 secara sah tidak membawa program font sama
sekali, dan perbedaan serialisasi (urutan object, whitespace, pengkodean
nama) dapat memindahkan atau menyembunyikan token. Paling banter ia
mengonfirmasi suatu face menyematkan sebuah program — tidak pernah bahwa face
spesifik yang Anda inginkan ter-resolve.
Untuk gerbang deploy yang sebenarnya, jangan andalkan pencarian byte. Urai PDF
yang dipancarkan dengan parser PDF atau object inspector yang tepat dan tegaskan
bahwa font object untuk face target Anda membawa program /FontFile//FontFile2//FontFile3
tersemat, atau gunakan asersi font-resolution yang disediakan produk jika ada
yang tersedia untuk integrasi Anda. Regex yang sadar-token di atas berguna untuk
pemeriksaan kewarasan lokal yang cepat, tetapi inspeksi struktural-lah yang
seharusnya menggagalkan deploy. Struktur penyematan dan font dictionary
dijelaskan dalam
Menyematkan dan men-subset font TrueType.
Kasus tepi & jebakan
Bagian berjudul “Kasus tepi & jebakan”createStandalone()memiliki registry-nya sendiri. Sebuah face yang terdaftar padaFontRegistryterpisah tidak terlihat oleh dokumen standalone. GunakanDocumentFactory(atau factory framework) sehingga registry Anda yang menjadi yang aktif.- Berkas style harus ada sebagai berkas. Engine tidak mensintesis bold atau
italic dari sebuah face reguler. Jika Anda memanggil
setFont('DejaVuSans', 'B'), pencarian direktori mencariDejaVuSans-B.ttf,DejaVuSansB.ttf, atauDejaVuSans.ttf(varian huruf kecil dan.otfjuga) — ia membentuk kandidat dari kode style literalB, jadi ia tidak pernah mencariDejaVuSans-Bold.ttf. Sebuah berkas dengan nama yang dieja penuh sepertiDejaVuSans-Bold.ttfhanya ter-resolve ketika Anda mendaftarkannya secara eksplisit denganregister(), yang mengindeksnya berdasarkan family dan style yang dibaca dari name table berkas itu sendiri terlepas dari nama berkas; mengandalkan pencarian direktori untuk menemukannya menghasilkan miss, setelah itu engine mungkin jatuh kembali ke base font (bukan jalur yang dijamin atau selalu senyap) — degradasi yang diperingatkan halaman ini. - Path stream-wrapper dan remote ditolak. Registry menolak path yang memuat
skema URI atau null byte. Daftarkan hanya berkas lokal; untuk font yang diambil
saat runtime gunakan
registerFromBinary()dengan byte mentah. - Registry terkunci bersifat imutabel. Setelah Anda memanggil
lock(), setiapregister(),addFontDirectory(), atauwarmup()berikutnya melempar. Metode lookup tetap tersedia. Daftarkan dan panaskan semuanya sebelum mengunci. - Collection CJK berukuran besar. Daftarkan sub-font yang tepat dari sebuah
.ttcdengan$fontIndex, dan anggarkan untuk subset tersemat yang lebih besar. Lihat catatan CJK pada resep embed-and-subset.
Catatan keamanan
Bagian berjudul “Catatan keamanan”- Sebuah berkas font adalah input biner yang tidak tepercaya. Hanya bundel font dari sumber yang Anda percayai, dan validasi provenans setiap face yang diterima dari pengguna akhir.
- Mengunci registry setelah warmup menghilangkan permukaan mutasi runtime dan membuat kesalahan path gagal saat boot alih-alih diam-diam mendegradasi keluaran.
- Jangan menyisipkan input pengguna ke dalam path berkas yang terdaftar. Daftarkan sekumpulan face terbundel yang tetap; jangan biarkan sebuah permintaan memilih path filesystem yang arbitrer.
Konformitas
Bagian berjudul “Konformitas”Panduan ini tidak membuat klaim standar normatif. Setiap simbol yang ditunjukkan
adalah permukaan publik yang terverifikasi: NextPDF\Typography\FontRegistry
(register(), addFontDirectory(), warmup(), lock(), argumen constructor
direktori), kontrak NextPDF\Contracts\FontRegistryInterface-nya,
NextPDF\Core\DocumentFactory::create(), dan NextPDF\Core\Document::setFont()
/ addFontDirectory(). Kunci fonts_path dan preload_fonts Laravel adalah
konfigurasi terdokumentasi dari paket nextpdf/laravel. Perilaku penyematan dan
subset-tag, dengan kutipan ISO 32000-2-nya, didokumentasikan pada resep
embed-and-subset yang ditautkan di bawah Lihat juga.
Lihat juga
Bagian berjudul “Lihat juga”- Menyematkan dan men-subset font TrueType: resep tingkat-API untuk mendaftarkan satu face dan subset otomatis saat penyimpanan.
- Merender HTML ke halaman PDF: jalur HTML native, yang me-resolve font melalui registry yang sama.
- Mengembalikan PDF yang dihasilkan dari sebuah controller: kaitkan dokumen yang dibangun factory ke dalam respons framework.
- Penggunaan produksi Laravel: konfigurasi font framework dan warmup saat boot worker.