Provisionner les polices en production
En un coup d’œil
Section intitulée « En un coup d’œil »Ton PDF s’affiche correctement sur ton portable, puis part vers un conteneur et en ressort sous forme de rangée de cases vides — le glyphe « tofu » — ou avec des accents et des caractères non latins manquants. La cause est presque toujours la même : la police que tu as sélectionnée n’est pas présente dans l’image déployée.
Le moteur NextPDF natif, en processus, résout les polices à partir de fichiers de
polices que le registre de polices peut lire. Il ne découvre pas automatiquement
les polices de l’OS ni de fontconfig — les fichiers de polices installés par l’OS
n’aident que si tu enregistres explicitement ces fichiers ou ajoutes leur
répertoire conteneur au chemin de recherche du FontRegistry. Un conteneur
construit à partir d’une image de base minimale n’a aucune police installée par
apt/apk, et même quand il en a, le moteur natif les ignore tant que tu ne
pointes pas le registre vers leurs fichiers. La solution consiste à empaqueter les
vrais fichiers de polices à l’intérieur de ton application ou de ton image et à les
enregistrer auprès du moteur. Le registre lit les fichiers TrueType (.ttf),
OpenType (.otf) et TrueType Collection (.ttc) ; le Type1 hérité (.pfb) est
aussi accepté mais rarement nécessaire pour de nouveaux travaux.
Avant de commencer, confirme que ces éléments sont en place :
- NextPDF core est installé.
- Tu disposes des vrais fichiers de polices que tu comptes utiliser, et tu as le droit de les embarquer. Les droits d’embarquement relèvent de ta responsabilité — voir Embarquer et sous-ensembler une police TrueType.
- Ton build peut copier ces fichiers dans l’artefact déployé.
C’est un mode d’emploi d’exploitation. Le code est minimal ; le travail réside dans le build et l’agencement du système de fichiers. Pour la mécanique au niveau de l’API d’enregistrement et de sous-ensemblage d’une seule fonte, lis la recette embed-and-subset liée ci-dessus. Cette page couvre la mise des fichiers sur la machine et le pointage du moteur vers eux.
Pourquoi le moteur natif ne trouve pas automatiquement les polices de l’OS
Section intitulée « Pourquoi le moteur natif ne trouve pas automatiquement les polices de l’OS »Il existe deux chemins de rendu distincts, et l’histoire des polices diffère entre eux.
- Moteur natif en processus (le défaut,
Document/writeHtml) : le moteur n’appelle pas le système de polices du système d’exploitation nifontconfigpour la découverte. Il résout une fonte via le registre de polices, qui lit un fichier de police précis que tu as enregistré ou trouve un fichier dans un répertoire que tu as configuré comme chemin de recherche. Installer une police avecapt-get install fonts-notoou exécuterfc-cachene fait rien en soi — le moteur natif ne voit ces fichiers que si tu les enregistres ou ajoutes leur répertoire au chemin de recherche du registre. - Pont Chrome (le moteur de rendu HTML-vers-PDF qui pilote un navigateur
headless) : ce chemin utilise bien les polices installées de l’hôte via la
découverte de polices normale du navigateur, donc les paquets de polices
apt/apketfontconfigcomptent là.
Si tu lis des conseils généraux du type « installe ces paquets de polices système dans ton Dockerfile », ils s’appliquent au pont Chrome, pas au moteur natif couvert sur cette page. Pour la génération native, empaquette les fichiers et enregistre-les.
Étape 1 — Empaqueter les vrais fichiers de polices
Section intitulée « Étape 1 — Empaqueter les vrais fichiers de polices »Mets les fichiers de polices à l’intérieur de l’arborescence de ton application pour
qu’ils soient versionnés et livrés avec chaque build. Un emplacement conventionnel
est un répertoire resources/fonts/.
your-app/├── resources/│ └── fonts/│ ├── DejaVuSans.ttf│ ├── DejaVuSans-B.ttf│ └── NotoSansCJK-Regular.ttc└── src/Nomme les fichiers pour que la recherche par répertoire du moteur puisse les
retrouver par famille et par style. Quand tu enregistres un répertoire (plutôt
qu’un fichier précis) et que tu appelles ensuite setFont('DejaVuSans', 'B', 12),
le moteur cherche des fichiers tels que DejaVuSans-B.ttf, DejaVuSansB.ttf ou
DejaVuSans.ttf dans chaque répertoire configuré. La recherche par répertoire
construit ces noms candidats à partir du même code de style à une lettre que tu
passes à setFont (B pour gras, I pour italique, BI pour gras-italique), et
non d’un mot écrit en toutes lettres — donc la forme fiable est
Family-<StyleCode>.ttf (par exemple DejaVuSans-B.ttf ou DejaVuSans-BI.ttf),
pas Family-Bold.ttf. Un fichier nommé DejaVuSans-Bold.ttf n’est jamais
trouvé par la recherche par répertoire ; pour utiliser un tel fichier,
enregistre-le explicitement avec register() — ce qui analyse la police et
l’indexe sous la famille et le style lus dans ses propres tables de noms, si bien
que le nom de fichier écrit en toutes lettres n’a plus d’importance (voir l’étape 2).
Étape 2 — Enregistrer les polices auprès du moteur
Section intitulée « Étape 2 — Enregistrer les polices auprès du moteur »Tu as deux moyens équivalents de rendre les fichiers visibles. Les deux passent par
NextPDF\Typography\FontRegistry, qui implémente
NextPDF\Contracts\FontRegistryInterface.
Enregistre un fichier précis sous un alias quand tu contrôles la fonte exacte :
use NextPDF\Typography\FontRegistry;
$registry = new FontRegistry();$registry->register(__DIR__ . '/../resources/fonts/DejaVuSans.ttf', alias: 'DejaVuSans');register(string $fontFile, string $alias = '', int $fontIndex = 0) accepte les
fichiers .ttf, .otf et .ttc, plus le Type1 hérité .pfb (qui charge ses
métriques .afm compagnes depuis le même chemin) ; $fontIndex sélectionne une
sous-police à l’intérieur d’une TrueType Collection (.ttc). register() analyse
le fichier et indexe la fonte par la famille et le style lus dans ses propres tables
de noms, donc le nom de fichier physique est sans importance une fois enregistré.
L’$alias optionnel n’est qu’un nom de recherche supplémentaire pour la fonte — ce
n’est pas un code de style et il ne change pas le style fourni par le fichier ;
passe-le quand tu veux appeler setFont() avec un nom autre que le nom de famille
embarqué de la police. Il renvoie le FontInfo analysé.
Enregistre un répertoire quand tu veux que le moteur résolve les fontes par nom depuis un dossier que tu contrôles :
$registry = new FontRegistry('/var/www/app/resources/fonts');// or, equivalently, after construction:$registry->addFontDirectory('/var/www/app/resources/fonts');Le constructeur de FontRegistry prend ce répertoire comme premier argument, et
addFontDirectory() ajoute d’autres chemins de recherche. Un Document nu expose
aussi addFontDirectory() pour le cas autonome.
Pour utiliser un registre que tu as toi-même peuplé, construis les documents via
DocumentFactory, qui câble exactement ce registre dans chaque document qu’il crée :
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() construit son propre registre interne, donc une
fonte que tu as enregistrée sur un FontRegistry séparé lui est invisible. En
production, passe par DocumentFactory (ou la fabrique de ton framework) pour que
le registre peuplé soit celui en service.
Configuration par framework
Section intitulée « Configuration par framework »Chaque intégration de framework expose les deux mêmes concepts sous forme de
configuration, si bien que tu touches rarement le registre directement. Dans le
nextpdf.php du paquet Laravel, fonts_path (par défaut NEXTPDF_FONTS_PATH, avec
repli sur resource_path('fonts')) est le répertoire de recherche, et
preload_fonts est une liste de chemins absolus de fichiers de polices analysés au
démarrage du worker. Pointe fonts_path vers le répertoire que tu as empaqueté et
tes fontes enregistrées se résolvent automatiquement.
Étape 3 — Provisionner les polices dans une image Docker
Section intitulée « Étape 3 — Provisionner les polices dans une image Docker »Dans un conteneur, les fichiers de polices doivent faire partie de la couche
d’image, copiés au moment du build. Comme le code applicatif et les polices sont
livrés ensemble quand tu les empaquettes sous resources/fonts/, un simple
COPY . . les emporte déjà. Si tu gardes les polices hors du contexte de build,
copie-les explicitement et assure-toi que le chemin que tu enregistres correspond au
chemin à l’intérieur de l’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"]Sur un système de fichiers immuable ou en lecture seule (un conteneur
readOnlyRootFilesystem, une image serverless ou un hôte durci), les fichiers de
polices sont lus au moment de la génération et jamais écrits, donc un montage en
lecture seule convient. La seule écriture que le moteur peut vouloir est son cache
de polices analysées : soit tu donnes à ce répertoire un petit volume inscriptible,
soit tu chauffes et verrouilles le registre au démarrage (section suivante) pour
qu’aucune écriture ni aucun enregistrement à l’exécution ne soit tenté.
Étape 4 — Chauffer et vérifier
Section intitulée « Étape 4 — Chauffer et vérifier »Dans un worker à longue durée de vie, analyse chaque fonte une fois au démarrage, puis verrouille le registre pour qu’aucun enregistrement par requête n’ait lieu et qu’une mauvaise configuration échoue bruyamment au lieu de retomber silencieusement :
$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();Après lock(), register(), addFontDirectory() et warmup() lèvent une
exception, ce qui transforme une erreur de « mauvais chemin dans l’image » en échec
de démarrage net plutôt qu’en page de tofu en production.
Ajoute une vérification de fumée de déploiement qui rend une page avec chaque fonte requise. Le contrôle d’en-tête ci-dessous vérifie seulement que le document a produit une sortie — il ne prouve pas que la police a été analysée, embarquée ni même résolue. Une fonte que le moteur ne trouve pas peut retomber sur une police de base standard (et, sous le comportement non strict actuel, un profil de conformité peut au contraire fournir un substitut embarqué) tout en émettant un PDF valide et non vide — donc même là où ce repli se produit, ce contrôle seul ne détectera pas la dégradation silencieuse. Ne te fie pas à ce que le repli soit garanti ou silencieux sur chaque chemin ; vérifie directement le programme embarqué, comme montré ci-dessous :
$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.');}Pour réellement faire échouer le déploiement quand une fonte est absente, cherche
dans le PDF émis le programme de police embarqué. Une fonte enregistrée qui se
résout porte son propre dictionnaire de police avec un programme embarqué ; affirmer
sa présence détecte donc le cas où la fonte demandée ne s’est jamais résolue (quel
que soit le repli du moteur) que le contrôle d’en-tête manque. La clé qui contient
le programme dépend du format des contours : les contours TrueType (.ttf, .ttc)
utilisent /FontFile2, les contours CFF/OpenType (.otf à contours PostScript)
utilisent /FontFile3, et le Type1 hérité (.pfb) utilise /FontFile.
S’il te suffit d’un signal agnostique au format « un programme de police quelconque
est embarqué », teste /FontFile seul — parce que /FontFile est une
sous-chaîne de /FontFile2 comme de /FontFile3, un simple test de sous-chaîne
correspond déjà à tout type de contour, et ajouter /FontFile2//FontFile3 comme
branches || supplémentaires est redondant :
if (!str_contains($pdf, '/FontFile')) { throw new RuntimeException('No embedded font program found — face fell back.');}Une simple sous-chaîne /FontFile ne peut cependant pas distinguer les types de
contours. Pour les distinguer, fais correspondre le jeton exact avec une limite de
mot pour que /FontFile ne se déclenche pas aussi sur /FontFile2 ou
/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.');}Dans tous les cas, considère cela comme une heuristique grossière seulement, pas
comme une barrière de déploiement fiable. Une recherche d’octets bruts dans le PDF
sérialisé est inexacte pour plusieurs raisons : les programmes de police peuvent
vivre dans des flux d’objets compressés (où /FontFile* n’apparaît jamais en
octets bruts), les mises à jour incrémentales peuvent ajouter ou remplacer des
objets, les polices non embarquées ou standard-14 ne portent légitimement aucun
programme de police, et les différences de sérialisation (ordre des objets,
espaces, encodage des noms) peuvent déplacer ou masquer le jeton. Au mieux, elle
confirme qu’une fonte a embarqué un programme — jamais que la fonte précise que
tu voulais s’est résolue.
Pour une vraie barrière de déploiement, ne te fie pas à la recherche d’octets.
Analyse le PDF émis avec un véritable analyseur PDF ou un inspecteur d’objets et
affirme que l’objet de police de la fonte cible porte un programme embarqué
/FontFile//FontFile2//FontFile3, ou utilise une assertion de résolution de
police fournie par le produit si ton intégration en propose une. Les expressions
régulières sensibles au jeton ci-dessus sont utiles pour un contrôle de cohérence
local rapide, mais c’est une inspection structurelle qui devrait faire échouer le
déploiement. L’embarquement et la structure du dictionnaire de police sont décrits
dans
Embarquer et sous-ensembler une police TrueType.
Cas limites et pièges
Section intitulée « Cas limites et pièges »createStandalone()a son propre registre. Une fonte enregistrée sur unFontRegistryséparé n’est pas visible d’un document autonome. UtiliseDocumentFactory(ou la fabrique du framework) pour que ton registre soit celui qui est actif.- Les fichiers de style doivent exister en tant que fichiers. Le moteur ne
synthétise pas le gras ni l’italique à partir d’une fonte régulière. Si tu appelles
setFont('DejaVuSans', 'B'), la recherche par répertoire chercheDejaVuSans-B.ttf,DejaVuSansB.ttfouDejaVuSans.ttf(en minuscules et avec les variantes.otfaussi) — elle forme le candidat à partir du code de style littéralB, donc elle ne cherche jamaisDejaVuSans-Bold.ttf. Un fichier au nom écrit en toutes lettres commeDejaVuSans-Bold.ttfne se résout que lorsque tu l’enregistres explicitement avecregister(), qui l’indexe par la famille et le style lus dans ses propres tables de noms quel que soit le nom de fichier ; compter sur la recherche par répertoire pour le trouver produit un échec, après quoi le moteur peut retomber sur une police de base (pas un chemin garanti ni toujours silencieux) — la dégradation contre laquelle cette page met en garde. - Les chemins via stream-wrapper et distants sont rejetés. Le registre refuse
les chemins contenant un schéma d’URI ou un octet nul. Enregistre uniquement des
fichiers locaux ; pour des polices récupérées à l’exécution, utilise
registerFromBinary()avec les octets bruts. - Un registre verrouillé est immuable. Une fois que tu appelles
lock(), toutregister(),addFontDirectory()ouwarmup()ultérieur lève une exception. Les méthodes de recherche restent disponibles. Enregistre et chauffe tout avant de verrouiller. - Les collections CJK sont volumineuses. Enregistre la bonne sous-police d’un
.ttcavec$fontIndex, et prévois un budget pour un sous-ensemble embarqué plus grand. Voir les notes CJK dans la recette embed-and-subset.
Notes de sécurité
Section intitulée « Notes de sécurité »- Un fichier de police est une entrée binaire non fiable. N’empaquette que des polices de sources de confiance, et valide la provenance de toute fonte acceptée de la part d’utilisateurs finaux.
- Verrouiller le registre après le préchauffage supprime une surface de mutation à l’exécution et fait échouer une erreur de chemin au démarrage plutôt que de dégrader silencieusement la sortie.
- N’interpole pas d’entrée utilisateur dans un chemin de fichier enregistré. Enregistre un ensemble fixe de fontes empaquetées ; ne laisse pas une requête choisir un chemin de système de fichiers arbitraire.
Conformité
Section intitulée « Conformité »Ce guide ne fait aucune revendication normative de standard. Chaque symbole montré
est une surface publique vérifiée : NextPDF\Typography\FontRegistry (register(),
addFontDirectory(), warmup(), lock(), l’argument répertoire du constructeur),
son contrat NextPDF\Contracts\FontRegistryInterface,
NextPDF\Core\DocumentFactory::create() et NextPDF\Core\Document::setFont() /
addFontDirectory(). Les clés Laravel fonts_path et preload_fonts sont la
configuration documentée du paquet nextpdf/laravel. Le comportement
d’embarquement et de marquage de sous-ensemble, avec ses citations ISO 32000-2, est
documenté dans la recette embed-and-subset liée sous Voir aussi.
Voir aussi
Section intitulée « Voir aussi »- Embarquer et sous-ensembler une police TrueType : la recette au niveau de l’API pour enregistrer une fonte et le sous-ensemblage automatique à l’enregistrement.
- Rendre du HTML vers une page PDF : le chemin HTML natif, qui résout les polices via le même registre.
- Renvoyer un PDF généré depuis un contrôleur : câbler un document construit par fabrique dans une réponse de framework.
- Utilisation en production avec Laravel : la configuration des polices du framework et le préchauffage au démarrage du worker.