Schriften in der Produktion bereitstellen
Auf einen Blick
Abschnitt betitelt „Auf einen Blick“Ihr PDF wird auf Ihrem Laptop korrekt gerendert, geht dann in einen Container und kommt als Reihe leerer Kästchen heraus – die „Tofu“-Glyphe – oder mit fehlenden Akzenten und nicht-lateinischen Zeichen. Die Ursache ist fast immer dieselbe: Die ausgewählte Schrift ist im bereitgestellten Image nicht vorhanden.
Die native, prozessinterne NextPDF-Engine löst Schriften aus Schriftdateien
auf, die die Font-Registry lesen kann. Sie entdeckt OS- oder
fontconfig-Schriften nicht automatisch – OS-installierte Schriftdateien helfen
nur, wenn Sie diese Dateien explizit registrieren oder ihr enthaltendes
Verzeichnis zum Suchpfad der FontRegistry hinzufügen. Ein aus einem schlanken
Basis-Image gebauter Container hat keine per apt/apk installierten Schriften,
und selbst wenn doch, ignoriert die native Engine sie, sofern Sie die Registry
nicht auf deren Dateien verweisen. Die Lösung besteht darin, die tatsächlichen
Schriftdateien innerhalb Ihrer Anwendung oder Ihres Images zu bündeln und sie bei
der Engine zu registrieren. Die Registry liest TrueType- (.ttf), OpenType-
(.otf) und TrueType-Collection-Dateien (.ttc); Legacy-Type1 (.pfb) wird
ebenfalls akzeptiert, ist für neue Arbeiten aber selten nötig.
Bestätigen Sie vor dem Start, dass diese Teile vorhanden sind:
- NextPDF Core ist installiert.
- Sie verfügen über die tatsächlichen Schriftdateien, die Sie verwenden möchten, und Sie sind berechtigt, sie einzubetten. Die Einbettungsrechte liegen in Ihrer Verantwortung – siehe TrueType-Schriftart einbetten und subsetten.
- Ihr Build kann diese Dateien in das bereitgestellte Artefakt kopieren.
Dies ist eine betriebliche Anleitung. Der Code ist minimal; die Arbeit steckt im Build und im Dateisystem-Layout. Für die API-Mechanik des Registrierens und Subsettens eines einzelnen Schnitts lesen Sie das oben verlinkte Embed-and-subset-Recipe. Diese Seite behandelt, wie Sie die Dateien auf die Box bekommen und die Engine auf sie verweisen.
Warum die native Engine OS-Schriften nicht automatisch findet
Abschnitt betitelt „Warum die native Engine OS-Schriften nicht automatisch findet“Es gibt zwei verschiedene Rendering-Pfade, und die Schriftgeschichte unterscheidet sich zwischen ihnen.
- Native prozessinterne Engine (der Standard,
Document/writeHtml): Die Engine ruft für die Erkennung nicht das Schriftsystem oderfontconfigdes Betriebssystems auf. Sie löst einen Schnitt über die Font-Registry auf, die eine bestimmte registrierte Schriftdatei liest oder eine innerhalb eines als Suchpfad konfigurierten Verzeichnisses findet. Das Installieren einer Schrift mitapt-get install fonts-notooder das Ausführen vonfc-cachebewirkt für sich genommen nichts – die native Engine sieht diese Dateien nur, wenn Sie sie registrieren oder ihr Verzeichnis zum Suchpfad der Registry hinzufügen. - Chrome-Bridge (der HTML-zu-PDF-Renderer, der einen Headless-Browser
ansteuert): Dieser Pfad nutzt die installierten Schriften des Hosts über die
normale Schriftauffindung des Browsers, sodass
apt/apk-Schriftpakete undfontconfigdort von Bedeutung sind.
Wenn Sie allgemeine Anleitungen wie „installieren Sie diese System-Schriftpakete in Ihrem Dockerfile“ lesen, gelten diese für die Chrome-Bridge, nicht für die auf dieser Seite behandelte native Engine. Für die native Generierung bündeln Sie die Dateien und registrieren sie.
Schritt 1 — Die tatsächlichen Schriftdateien bündeln
Abschnitt betitelt „Schritt 1 — Die tatsächlichen Schriftdateien bündeln“Legen Sie die Schriftdateien in Ihren Anwendungsbaum, sodass sie versioniert sind
und mit jedem Build ausgeliefert werden. Ein üblicher Ort ist ein Verzeichnis
resources/fonts/.
your-app/├── resources/│ └── fonts/│ ├── DejaVuSans.ttf│ ├── DejaVuSans-B.ttf│ └── NotoSansCJK-Regular.ttc└── src/Benennen Sie die Dateien so, dass die Verzeichnissuche der Engine sie nach Familie
und Stil finden kann. Wenn Sie ein Verzeichnis registrieren (statt einer
bestimmten Datei) und später setFont('DejaVuSans', 'B', 12) aufrufen, sucht die
Engine in jedem konfigurierten Verzeichnis nach Dateien wie DejaVuSans-B.ttf,
DejaVuSansB.ttf oder DejaVuSans.ttf. Die Verzeichnissuche bildet diese
Kandidatennamen aus demselben einbuchstabigen Stilcode, den Sie an setFont
übergeben (B für fett, I für kursiv, BI für fett-kursiv), nicht aus einem
ausgeschriebenen Wort – die zuverlässige Form ist also
Family-<StyleCode>.ttf (zum Beispiel DejaVuSans-B.ttf oder
DejaVuSans-BI.ttf), nicht Family-Bold.ttf. Eine Datei namens
DejaVuSans-Bold.ttf wird von der Verzeichnissuche niemals gefunden; um eine
solche Datei zu verwenden, registrieren Sie sie explizit mit register() – was
die Schrift parst und sie unter der aus den eigenen name-Tabellen der Datei
gelesenen Familie und dem dort gelesenen Stil indexiert, sodass der
ausgeschriebene Dateiname keine Rolle mehr spielt (siehe Schritt 2).
Schritt 2 — Die Schriften bei der Engine registrieren
Abschnitt betitelt „Schritt 2 — Die Schriften bei der Engine registrieren“Sie haben zwei gleichwertige Wege, die Dateien sichtbar zu machen. Beide laufen
über NextPDF\Typography\FontRegistry, das
NextPDF\Contracts\FontRegistryInterface implementiert.
Registrieren Sie eine bestimmte Datei unter einem Alias, wenn Sie den exakten Schnitt kontrollieren:
use NextPDF\Typography\FontRegistry;
$registry = new FontRegistry();$registry->register(__DIR__ . '/../resources/fonts/DejaVuSans.ttf', alias: 'DejaVuSans');register(string $fontFile, string $alias = '', int $fontIndex = 0) akzeptiert
.ttf-, .otf- und .ttc-Dateien sowie Legacy-Type1-.pfb (das seine
begleitende .afm-Metrik aus demselben Pfad lädt); $fontIndex wählt eine
Unterschrift innerhalb einer TrueType-Collection (.ttc) aus. register() parst
die Datei und indexiert den Schnitt nach der aus den eigenen name-Tabellen
gelesenen Familie und dem dort gelesenen Stil, sodass der physische Dateiname nach
der Registrierung irrelevant ist. Der optionale $alias ist lediglich ein
zusätzlicher Nachschlagename für den Schnitt – er ist kein Stilcode und ändert
nicht, welchen Stil die Datei bereitstellt; übergeben Sie ihn, wenn Sie setFont()
mit einem anderen Namen als dem eingebetteten Familiennamen der Schrift aufrufen
möchten. Er gibt das geparste FontInfo zurück.
Registrieren Sie ein Verzeichnis, wenn die Engine Schnitte nach Namen aus einem von Ihnen kontrollierten Ordner auflösen soll:
$registry = new FontRegistry('/var/www/app/resources/fonts');// or, equivalently, after construction:$registry->addFontDirectory('/var/www/app/resources/fonts');Der FontRegistry-Konstruktor nimmt dieses Verzeichnis als sein erstes Argument,
und addFontDirectory() fügt weitere Suchpfade hinzu. Ein nacktes Document
stellt für den eigenständigen Fall ebenfalls addFontDirectory() bereit.
Um eine selbst befüllte Registry zu verwenden, bauen Sie Dokumente über
DocumentFactory, die genau diese Registry in jedes von ihr erzeugte Dokument
verdrahtet:
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() baut seine eigene interne Registry auf, sodass
ein auf einer separaten FontRegistry registrierter Schnitt für es unsichtbar
ist. Gehen Sie in der Produktion über DocumentFactory (oder die Factory Ihres
Frameworks), damit die befüllte Registry diejenige ist, die verwendet wird.
Framework-Konfiguration
Abschnitt betitelt „Framework-Konfiguration“Jede Framework-Integration stellt dieselben beiden Konzepte als Konfiguration
bereit, sodass Sie die Registry selten direkt anfassen. In der nextpdf.php des
Laravel-Pakets ist fonts_path (Standard NEXTPDF_FONTS_PATH, mit Rückfall auf
resource_path('fonts')) das Suchverzeichnis, und preload_fonts ist eine Liste
absoluter Schriftdatei-Pfade, die beim Worker-Boot geparst werden. Verweisen Sie
fonts_path auf das gebündelte Verzeichnis, und Ihre registrierten Schnitte
lösen sich automatisch auf.
Schritt 3 — Schriften in einem Docker-Image bereitstellen
Abschnitt betitelt „Schritt 3 — Schriften in einem Docker-Image bereitstellen“In einem Container müssen die Schriftdateien Teil der Image-Schicht sein, zur
Build-Zeit hineinkopiert. Da der Anwendungscode und die Schriften zusammen
ausgeliefert werden, wenn Sie sie unter resources/fonts/ bündeln, trägt ein
normales COPY . . sie bereits. Wenn Sie Schriften außerhalb des Build-Kontexts
halten, kopieren Sie sie explizit und stellen Sie sicher, dass der von Ihnen
registrierte Pfad mit dem Pfad innerhalb des Images übereinstimmt.
# 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"]Auf einem unveränderlichen oder schreibgeschützten Dateisystem (einem
readOnlyRootFilesystem-Container, einem Serverless-Image oder einem gehärteten
Host) werden die Schriftdateien zur Generierungszeit gelesen und niemals
geschrieben, sodass ein schreibgeschützter Mount in Ordnung ist. Der einzige
Schreibvorgang, den die Engine eventuell möchte, ist ihr Cache geparster
Schriften: Geben Sie diesem Verzeichnis entweder ein kleines beschreibbares
Volume oder wärmen und sperren Sie die Registry beim Boot (nächster Abschnitt),
sodass kein Schreibvorgang oder keine Registrierung zur Laufzeit versucht wird.
Schritt 4 — Aufwärmen und überprüfen
Abschnitt betitelt „Schritt 4 — Aufwärmen und überprüfen“Parsen Sie in einem langlaufenden Worker jeden Schnitt einmal beim Boot und sperren Sie dann die Registry, sodass keine Registrierung pro Anfrage stattfindet und eine Fehlkonfiguration lautstark fehlschlägt, statt stillschweigend zurückzufallen:
$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();Nach lock() werfen register(), addFontDirectory() und warmup() eine
Exception, was einen Fehler „falscher Pfad im Image“ in einen harten Boot-Fehler
verwandelt statt in eine Tofu-Seite in der Produktion.
Fügen Sie eine Deployment-Smoke-Prüfung hinzu, die eine Seite mit jedem benötigten Schnitt rendert. Die Header-Prüfung unten verifiziert nur, dass das Dokument Ausgabe erzeugt hat – sie beweist nicht, dass die Schrift geparst, eingebettet oder auch nur aufgelöst wurde. Ein Schnitt, den die Engine nicht finden kann, kann auf eine Standard-Basisschrift zurückfallen (und unter dem aktuellen, nicht-strikten Verhalten kann ein Konformitätsprofil stattdessen einen gebündelten Ersatz liefern), während dennoch ein gültiges, nicht-leeres PDF ausgegeben wird – selbst wo dieser Fallback geschieht, fängt diese Prüfung allein also die stille Verschlechterung nicht ab. Verlassen Sie sich nicht darauf, dass der Fallback auf jedem Pfad garantiert oder still ist; verifizieren Sie das eingebettete Programm direkt, wie unten gezeigt:
$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.');}Um das Deployment tatsächlich zum Scheitern zu bringen, wenn ein Schnitt fehlt,
prüfen Sie das ausgegebene PDF auf das eingebettete Schriftprogramm. Ein
registrierter Schnitt, der sich auflöst, trägt sein eigenes Font-Dictionary mit
einem eingebetteten Programm, sodass das Sicherstellen seiner Anwesenheit den Fall
abfängt, in dem sich der angeforderte Schnitt nie aufgelöst hat (worauf auch immer
die Engine zurückgefallen ist), den die Header-Prüfung verpasst. Welcher Schlüssel
das Programm hält, hängt vom Outline-Format ab: TrueType-Outlines (.ttf, .ttc)
verwenden /FontFile2, CFF/OpenType-Outlines (.otf mit PostScript-Outlines)
verwenden /FontFile3, und Legacy-Type1 (.pfb) verwendet /FontFile.
Wenn Sie nur ein formatunabhängiges Signal „irgendein Schriftprogramm
eingebettet“ benötigen, testen Sie auf /FontFile allein – weil /FontFile
ein Teilstring sowohl von /FontFile2 als auch von /FontFile3 ist, passt eine
nackte Teilstring-Prüfung bereits auf jeden Outline-Typ, und das Hinzufügen von
/FontFile2//FontFile3 als zusätzliche ||-Zweige ist redundant:
if (!str_contains($pdf, '/FontFile')) { throw new RuntimeException('No embedded font program found — face fell back.');}Ein nackter /FontFile-Teilstring kann die Outline-Typen allerdings nicht
auseinanderhalten. Um sie zu unterscheiden, passen Sie auf das exakte Token mit
einer Wortgrenze, sodass /FontFile nicht auch bei /FontFile2 oder
/FontFile3 auslöst:
$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.');}So oder so behandeln Sie dies nur als grobe Heuristik, nicht als zuverlässiges
Deploy-Gate. Eine rohe Byte-Suche über das serialisierte PDF ist aus mehreren
Gründen ungenau: Schriftprogramme können innerhalb von komprimierten
Objekt-Streams liegen (wo /FontFile* nie als Klartext-Bytes erscheint),
inkrementelle Updates können Objekte anhängen oder ersetzen, nicht
eingebettete oder Standard-14-Schriften tragen rechtmäßig überhaupt kein
Schriftprogramm, und Serialisierungsunterschiede (Objektreihenfolge,
Whitespace, Namenscodierung) können das Token verschieben oder verbergen.
Bestenfalls bestätigt sie, dass irgendein Schnitt ein Programm eingebettet hat –
niemals, dass sich der spezifische gewünschte Schnitt aufgelöst hat.
Für ein echtes Deploy-Gate verlassen Sie sich nicht auf die Byte-Suche. Parsen Sie
das ausgegebene PDF mit einem ordentlichen PDF-Parser oder Objekt-Inspektor und
stellen Sie sicher, dass das Font-Objekt für Ihren Zielschnitt ein eingebettetes
/FontFile//FontFile2//FontFile3-Programm trägt, oder verwenden Sie eine vom
Produkt bereitgestellte Schriftauflösungs-Zusicherung, falls eine für Ihre
Integration verfügbar ist. Die tokenbewussten Regexes oben sind für eine schnelle
lokale Plausibilitätsprüfung nützlich, aber eine strukturelle Inspektion ist das,
was das Deployment zum Scheitern bringen sollte. Die Einbettung und die
Font-Dictionary-Struktur sind in
TrueType-Schriftart einbetten und subsetten
beschrieben.
Grenzfälle & Stolperfallen
Abschnitt betitelt „Grenzfälle & Stolperfallen“createStandalone()hat seine eigene Registry. Ein auf einer separatenFontRegistryregistrierter Schnitt ist für ein eigenständiges Dokument nicht sichtbar. Verwenden SieDocumentFactory(oder die Framework-Factory), damit Ihre Registry die aktive ist.- Stildateien müssen als Dateien existieren. Die Engine synthetisiert weder
fett noch kursiv aus einem regulären Schnitt. Wenn Sie
setFont('DejaVuSans', 'B')aufrufen, sucht die Verzeichnissuche nachDejaVuSans-B.ttf,DejaVuSansB.ttfoderDejaVuSans.ttf(auch in Kleinschreibung und in.otf-Varianten) – sie bildet den Kandidaten aus dem buchstäblichen StilcodeB, sodass sie niemals nachDejaVuSans-Bold.ttfsucht. Eine Datei mit einem ausgeschriebenen Namen wieDejaVuSans-Bold.ttflöst sich nur auf, wenn Sie sie explizit mitregister()registrieren, was sie unabhängig vom Dateinamen nach der aus den eigenen name-Tabellen der Datei gelesenen Familie und dem dort gelesenen Stil indexiert; sich auf die Verzeichnissuche zu verlassen, um sie zu finden, erzeugt einen Fehlschlag, woraufhin die Engine auf eine Basisschrift zurückfallen kann (kein garantierter oder stets stiller Pfad) – die Verschlechterung, vor der diese Seite warnt. - Stream-Wrapper- und Remote-Pfade werden abgelehnt. Die Registry verweigert
Pfade, die ein URI-Schema oder ein Null-Byte enthalten. Registrieren Sie nur
lokale Dateien; für zur Laufzeit abgerufene Schriften verwenden Sie
registerFromBinary()mit den Rohbytes. - Eine gesperrte Registry ist unveränderlich. Sobald Sie
lock()aufrufen, wirft jedes spätereregister(),addFontDirectory()oderwarmup()eine Exception. Nachschlagemethoden bleiben verfügbar. Registrieren und wärmen Sie alles vor dem Sperren auf. - CJK-Collections sind groß. Registrieren Sie die richtige Unterschrift einer
.ttcmit$fontIndexund planen Sie für ein größeres eingebettetes Subset. Siehe die CJK-Hinweise im Embed-and-subset-Recipe.
Sicherheitshinweise
Abschnitt betitelt „Sicherheitshinweise“- Eine Schriftdatei ist nicht vertrauenswürdige Binäreingabe. Bündeln Sie nur Schriften aus Quellen, denen Sie vertrauen, und validieren Sie die Herkunft jedes von Endbenutzern angenommenen Schnitts.
- Das Sperren der Registry nach dem Aufwärmen entfernt eine Laufzeit-Mutations- Oberfläche und lässt einen Pfadfehler beim Boot fehlschlagen, statt die Ausgabe stillschweigend zu verschlechtern.
- Interpolieren Sie keine Benutzereingaben in einen registrierten Dateipfad. Registrieren Sie eine feste Menge gebündelter Schnitte; lassen Sie eine Anfrage keinen beliebigen Dateisystempfad wählen.
Konformität
Abschnitt betitelt „Konformität“Diese Anleitung erhebt keinen normativen Standardanspruch. Jedes gezeigte Symbol
ist verifizierte öffentliche Oberfläche: NextPDF\Typography\FontRegistry
(register(), addFontDirectory(), warmup(), lock(), das
Verzeichnis-Konstruktorargument), ihr Vertrag
NextPDF\Contracts\FontRegistryInterface,
NextPDF\Core\DocumentFactory::create() und NextPDF\Core\Document::setFont()
/ addFontDirectory(). Die Laravel-Schlüssel fonts_path und preload_fonts
sind die dokumentierte Konfiguration des Pakets nextpdf/laravel. Das Einbettungs-
und Subset-Tag-Verhalten mit seinen ISO-32000-2-Zitaten ist im unter „Siehe auch“
verlinkten Embed-and-subset-Recipe dokumentiert.
Siehe auch
Abschnitt betitelt „Siehe auch“- TrueType-Schriftart einbetten und subsetten: das API-Recipe zum Registrieren eines Schnitts und zum automatischen Subset beim Speichern.
- HTML in eine PDF-Seite rendern: der native HTML-Pfad, der Schriften über dieselbe Registry auflöst.
- Ein generiertes PDF aus einem Controller zurückgeben: ein per Factory gebautes Dokument in eine Framework-Antwort verdrahten.
- Laravel-Produktionsnutzung: die Schriftkonfiguration des Frameworks und das Aufwärmen beim Worker-Boot.