Lettertypen voorzien in productie
In één oogopslag
Sectie met titel “In één oogopslag”Je PDF rendert correct op je laptop, gaat dan naar een container en komt eruit als een rij lege vakjes — de “tofu”-glyph — of met ontbrekende accenten en niet-Latijnse tekens. De oorzaak is bijna altijd dezelfde: het lettertype dat je hebt geselecteerd is niet aanwezig in de gedeployde image.
De native, in-process NextPDF-engine lost lettertypen op uit
lettertypebestanden die het lettertyperegister kan lezen. Het ontdekt OS- of
fontconfig-lettertypen niet automatisch — OS-geïnstalleerde lettertypebestanden
helpen alleen als je die bestanden expliciet registreert of hun bovenliggende map
toevoegt aan het zoekpad van het FontRegistry. Een container gebouwd op een slank
base-image heeft geen via apt/apk geïnstalleerde lettertypen, en zelfs wanneer
het die wel heeft, negeert de native engine ze tenzij je het register naar hun
bestanden wijst. De oplossing is de werkelijke lettertypebestanden mee te leveren
binnen je applicatie of image en ze te registreren bij de engine. Het register
leest TrueType- (.ttf), OpenType- (.otf) en TrueType Collection-bestanden
(.ttc); legacy Type1 (.pfb) wordt ook geaccepteerd maar is zelden nodig voor
nieuw werk.
Bevestig voordat je begint dat deze onderdelen op hun plek staan:
- NextPDF core is geïnstalleerd.
- Je hebt de werkelijke lettertypebestanden die je wilt gebruiken, en je hebt het recht ze in te sluiten. Insluitingsrechten zijn jouw verantwoordelijkheid — zie Een TrueType-lettertype insluiten en subsetten.
- Je build kan die bestanden naar het gedeployde artefact kopiëren.
Dit is een operationele how-to. De code is minimaal; het werk zit in de build en de filesystem-indeling. Lees voor de API-niveau-mechanica van het registreren en subsetten van één face het hierboven gelinkte embed-and-subset-recipe. Deze pagina behandelt het op de doos krijgen van de bestanden en het wijzen van de engine naar die bestanden.
Waarom de native engine OS-lettertypen niet automatisch vindt
Sectie met titel “Waarom de native engine OS-lettertypen niet automatisch vindt”Er zijn twee verschillende renderpaden, en het lettertypeverhaal verschilt tussen beide.
- Native in-process engine (de standaard,
Document/writeHtml): de engine roept het lettertypesysteem offontconfigvan het besturingssysteem niet aan voor ontdekking. Het lost een face op via het lettertyperegister, dat een specifiek lettertypebestand leest dat je hebt geregistreerd of er een vindt binnen een map die je als zoekpad hebt geconfigureerd. Een lettertype installeren metapt-get install fonts-notooffc-cachedraaien doet op zichzelf niets — de native engine ziet die bestanden alleen als je ze registreert of hun map toevoegt aan het zoekpad van het register. - Chrome-brug (de HTML-naar-PDF-renderer die een headless browser aanstuurt):
dit pad gebruikt de geïnstalleerde lettertypen van de host wel, via de normale
lettertypeontdekking van de browser, dus
apt/apk-lettertypepakketten enfontconfigzijn daar van belang.
Als je algemene “installeer deze systeemlettertypepakketten in je Dockerfile”- richtlijnen leest, gelden die voor de Chrome-brug, niet voor de native engine die op deze pagina wordt behandeld. Voor native generatie lever je de bestanden mee en registreer je ze.
Stap 1 — De werkelijke lettertypebestanden meeleveren
Sectie met titel “Stap 1 — De werkelijke lettertypebestanden meeleveren”Zet de lettertypebestanden binnen je applicatieboom zodat ze geversioneerd zijn en
met elke build meegaan. Een conventionele locatie is een resources/fonts/-map.
your-app/├── resources/│ └── fonts/│ ├── DejaVuSans.ttf│ ├── DejaVuSans-B.ttf│ └── NotoSansCJK-Regular.ttc└── src/Benoem de bestanden zo dat de mapzoekactie van de engine ze kan vinden op familie
en stijl. Wanneer je een map registreert (in plaats van een specifiek bestand) en
later setFont('DejaVuSans', 'B', 12) aanroept, zoekt de engine in elke
geconfigureerde map naar bestanden zoals DejaVuSans-B.ttf, DejaVuSansB.ttf of
DejaVuSans.ttf. De mapzoekactie bouwt die kandidaatnamen op uit dezelfde
enkele-letter-stijlcode die je aan setFont doorgeeft (B voor bold, I voor
italic, BI voor bold-italic), niet een uitgeschreven woord — dus de betrouwbare
vorm is Family-<StyleCode>.ttf (bijvoorbeeld DejaVuSans-B.ttf of
DejaVuSans-BI.ttf), niet Family-Bold.ttf. Een bestand met de naam
DejaVuSans-Bold.ttf wordt nooit gevonden door de mapzoekactie; om zo’n bestand te
gebruiken, registreer je het expliciet met register() — wat het lettertype parset
en indexeert onder de familie en stijl die uit de eigen name-tabellen van het
bestand worden gelezen, zodat de uitgeschreven bestandsnaam er niet meer toe doet
(zie Stap 2).
Stap 2 — De lettertypen registreren bij de engine
Sectie met titel “Stap 2 — De lettertypen registreren bij de engine”Je hebt twee gelijkwaardige manieren om de bestanden zichtbaar te maken. Beide gaan
via NextPDF\Typography\FontRegistry, dat
NextPDF\Contracts\FontRegistryInterface implementeert.
Registreer een specifiek bestand onder een alias wanneer je de exacte face beheert:
use NextPDF\Typography\FontRegistry;
$registry = new FontRegistry();$registry->register(__DIR__ . '/../resources/fonts/DejaVuSans.ttf', alias: 'DejaVuSans');register(string $fontFile, string $alias = '', int $fontIndex = 0) accepteert
.ttf-, .otf- en .ttc-bestanden, plus legacy Type1 .pfb (dat zijn bijhorende
.afm-metrieken vanaf hetzelfde pad laadt); $fontIndex selecteert een sub-font
binnen een TrueType Collection (.ttc). register() parset het bestand en
indexeert de face op de familie en stijl die uit de eigen name-tabellen worden
gelezen, dus de fysieke bestandsnaam is irrelevant zodra het is geregistreerd. De
optionele $alias is slechts een extra opzoeknaam voor de face — het is geen
stijlcode en verandert niet welke stijl het bestand levert; geef het door wanneer
je setFont() wilt aanroepen met een andere naam dan de ingesloten familienaam van
het lettertype. Het retourneert de geparste FontInfo.
Registreer een map wanneer je wilt dat de engine faces op naam oplost vanuit een map die je beheert:
$registry = new FontRegistry('/var/www/app/resources/fonts');// or, equivalently, after construction:$registry->addFontDirectory('/var/www/app/resources/fonts');De FontRegistry-constructor neemt die map als zijn eerste argument, en
addFontDirectory() voegt meer zoekpaden toe. Een kaal Document stelt
addFontDirectory() ook beschikbaar voor het standalone-geval.
Om een register te gebruiken dat je zelf hebt gevuld, bouw je documenten via
DocumentFactory, dat precies dat register bedraadt in elk document dat het maakt:
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() bouwt zijn eigen interne register, dus een face
die je op een apart FontRegistry hebt geregistreerd, is daarvoor onzichtbaar. Ga
in productie via DocumentFactory (of de factory van je framework) zodat het
gevulde register het register is dat in gebruik is.
Frameworkconfiguratie
Sectie met titel “Frameworkconfiguratie”Elke framework-integratie stelt dezelfde twee concepten beschikbaar als
configuratie, zodat je het register zelden direct aanraakt. In de nextpdf.php van
het Laravel-pakket is fonts_path (standaard NEXTPDF_FONTS_PATH, terugvallend op
resource_path('fonts')) de zoekmap, en preload_fonts is een lijst met absolute
lettertypebestandspaden die tijdens het opstarten van de worker worden geparst.
Wijs fonts_path naar de map die je hebt meegeleverd en je geregistreerde faces
worden automatisch opgelost.
Stap 3 — Lettertypen voorzien in een Docker-image
Sectie met titel “Stap 3 — Lettertypen voorzien in een Docker-image”In een container moeten de lettertypebestanden deel uitmaken van de image-laag,
tijdens de build erin gekopieerd. Omdat de applicatiecode en de lettertypen samen
worden verscheept wanneer je ze onder resources/fonts/ meelevert, draagt een
normale COPY . . ze al mee. Als je lettertypen buiten de buildcontext bewaart,
kopieer ze dan expliciet en zorg dat het pad dat je registreert overeenkomt met het
pad binnen de 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"]Op een immutable of read-only filesystem (een readOnlyRootFilesystem-
container, een serverless image of een gehardende host) worden de
lettertypebestanden gelezen tijdens de generatie en nooit geschreven, dus een
read-only mount is prima. De enige schrijfactie die de engine mogelijk wil, is zijn
cache met geparste lettertypen: geef die map ofwel een klein schrijfbaar volume, of
warm en lock het register tijdens het opstarten (volgende sectie) zodat er tijdens
runtime geen schrijfactie of registratie wordt geprobeerd.
Stap 4 — Opwarmen en verifiëren
Sectie met titel “Stap 4 — Opwarmen en verifiëren”Parse in een langlevende worker elke face eenmaal tijdens het opstarten, en lock dan het register zodat er geen per-request-registratie plaatsvindt en een misconfiguratie luid faalt in plaats van stilzwijgend terug te vallen:
$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();Na lock() werpen register(), addFontDirectory() en warmup() op, wat een
“verkeerd pad in de image”-fout omzet in een harde opstartfout in plaats van een
tofu-pagina in productie.
Voeg een deployment-smoke-check toe die één pagina rendert met elke vereiste face. De header-check hieronder verifieert alleen dat het document uitvoer produceerde — het bewijst niet dat het lettertype parste, insloot of zelfs werd opgelost. Een face die de engine niet kan vinden, kan terugvallen op een standaard base-font (en, onder het huidige niet-strikte gedrag, kan een conformiteitsprofiel in plaats daarvan een meegeleverde vervanger leveren) terwijl het toch een geldige, niet-lege PDF uitzendt — dus zelfs waar die fallback plaatsvindt, vangt deze check alleen de stille degradatie niet. Vertrouw er niet op dat de fallback op elk pad gegarandeerd of stil is; verifieer het ingesloten programma direct, zoals hieronder getoond:
$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.');}Om de deploy daadwerkelijk te laten falen wanneer een face ontbreekt, controleer je
de uitgezonden PDF op het ingesloten lettertypeprogramma. Een geregistreerde face
die oplost, draagt zijn eigen lettertype-dictionary met een ingesloten programma,
dus het asserteren van zijn aanwezigheid vangt het geval waarin de aangevraagde
face nooit oploste (waar de engine ook op terugviel) dat de header-check mist. Welke
key het programma bevat, hangt af van het outline-formaat: TrueType-outlines
(.ttf, .ttc) gebruiken /FontFile2, CFF/OpenType-outlines (.otf met
PostScript-outlines) gebruiken /FontFile3, en legacy Type1 (.pfb) gebruikt
/FontFile.
Als je alleen een formaat-agnostisch “een of ander lettertypeprogramma
ingesloten”-signaal nodig hebt, test dan op /FontFile alleen — omdat
/FontFile een substring is van zowel /FontFile2 als /FontFile3, matcht een
kale substringcheck al elk outline-type, en het toevoegen van
/FontFile2//FontFile3 als extra ||-takken is overbodig:
if (!str_contains($pdf, '/FontFile')) { throw new RuntimeException('No embedded font program found — face fell back.');}Een kale /FontFile-substring kan de outline-types echter niet uit elkaar houden.
Om ze te onderscheiden, match je op het exacte token met een woordgrens zodat
/FontFile niet ook afgaat op /FontFile2 of /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.');}Behandel dit hoe dan ook als een grove heuristiek alleen, geen betrouwbare
deploy-gate. Een ruwe byte-zoekactie over de geserialiseerde PDF is om
verschillende redenen onnauwkeurig: lettertypeprogramma’s kunnen binnen
gecomprimeerde object-streams leven (waar /FontFile* nooit als platte bytes
verschijnt), incrementele updates kunnen objecten toevoegen of vervangen,
niet-ingesloten of standard-14-lettertypen dragen legitiem helemaal geen
lettertypeprogramma, en serialisatieverschillen (objectvolgorde, witruimte,
naamcodering) kunnen het token verplaatsen of verbergen. In het beste geval
bevestigt het dat een of andere face een programma insloot — nooit dat de
specifieke face die je wilde, oploste.
Vertrouw voor een echte deploy-gate niet op de byte-zoekactie. Parse de
uitgezonden PDF met een echte PDF-parser of object-inspector en asserteer dat het
lettertype-object voor je doel-face een ingesloten
/FontFile//FontFile2//FontFile3-programma draagt, of gebruik een door het
product geleverde assertie voor lettertype-oplossing als er een beschikbaar is voor
je integratie. De token-bewuste regexes hierboven zijn nuttig voor een snelle lokale
sanity-check, maar een structurele inspectie is wat de deploy zou moeten laten
falen. De insluitings- en lettertype-dictionarystructuur worden beschreven in
Een TrueType-lettertype insluiten en subsetten.
Randgevallen en valkuilen
Sectie met titel “Randgevallen en valkuilen”createStandalone()heeft zijn eigen register. Een face geregistreerd op een apartFontRegistryis niet zichtbaar voor een standalone document. GebruikDocumentFactory(of de framework-factory) zodat jouw register het actieve is.- Stijlbestanden moeten als bestanden bestaan. De engine synthetiseert geen bold
of italic uit een gewone face. Als je
setFont('DejaVuSans', 'B')aanroept, zoekt de mapzoekactie naarDejaVuSans-B.ttf,DejaVuSansB.ttfofDejaVuSans.ttf(ook kleine letters en.otf-varianten) — het vormt de kandidaat uit de letterlijkeB-stijlcode, dus het zoekt nooit naarDejaVuSans-Bold.ttf. Een bestand met een uitgeschreven naam zoalsDejaVuSans-Bold.ttflost alleen op wanneer je het expliciet registreert metregister(), dat het indexeert op de familie en stijl die uit de eigen name-tabellen van het bestand worden gelezen, ongeacht de bestandsnaam; vertrouwen op de mapzoekactie om het te vinden, levert een miss op, waarna de engine kan terugvallen op een base-font (geen gegarandeerd of altijd-stil pad) — de degradatie waarvoor deze pagina waarschuwt. - Stream-wrapper- en remote-paden worden geweigerd. Het register weigert paden
die een URI-scheme of een null-byte bevatten. Registreer alleen lokale bestanden;
gebruik voor lettertypen die tijdens runtime worden opgehaald
registerFromBinary()met de ruwe bytes. - Een gelockt register is immutable. Zodra je
lock()aanroept, werpt elke latereregister(),addFontDirectory()ofwarmup()op. Opzoekmethoden blijven beschikbaar. Registreer en warm alles op vóór het locken. - CJK-collecties zijn groot. Registreer de juiste sub-font van een
.ttcmet$fontIndex, en houd budget voor een grotere ingesloten subset. Zie de CJK- notities in het embed-and-subset-recipe.
Beveiligingsnotities
Sectie met titel “Beveiligingsnotities”- Een lettertypebestand is niet-vertrouwde binaire invoer. Lever alleen lettertypen mee uit bronnen die je vertrouwt, en valideer de herkomst van elke face die je van eindgebruikers accepteert.
- Het register locken na het opwarmen verwijdert een runtime-mutatie-oppervlak en maakt dat een padfout faalt tijdens het opstarten in plaats van de uitvoer stilzwijgend te degraderen.
- Interpoleer geen gebruikersinvoer in een geregistreerd bestandspad. Registreer een vaste set meegeleverde faces; laat een request geen willekeurig filesystem-pad kiezen.
Conformiteit
Sectie met titel “Conformiteit”Deze gids doet geen normatieve standaardenclaim. Elk getoond symbool is geverifieerd
publiek oppervlak: NextPDF\Typography\FontRegistry (register(),
addFontDirectory(), warmup(), lock(), het map-constructorargument), zijn
NextPDF\Contracts\FontRegistryInterface-contract,
NextPDF\Core\DocumentFactory::create() en NextPDF\Core\Document::setFont() /
addFontDirectory(). De Laravel-keys fonts_path en preload_fonts zijn de
gedocumenteerde configuratie van het nextpdf/laravel-pakket. Het insluitings- en
subset-tag-gedrag, met zijn ISO 32000-2-citaties, is gedocumenteerd in het
embed-and-subset-recipe dat onder Zie ook is gelinkt.
Zie ook
Sectie met titel “Zie ook”- Een TrueType-lettertype insluiten en subsetten: het API-niveau-recipe voor het registreren van één face en de automatische subset bij het opslaan.
- HTML naar een PDF-pagina renderen: het native HTML-pad, dat lettertypen oplost via hetzelfde register.
- Een gegenereerde PDF teruggeven vanuit een controller: bedraad een door de factory gebouwd document in een framework-respons.
- Laravel productiegebruik: de framework-lettertypeconfiguratie en worker-boot-opwarming.