Aller au contenu

Polices personnalisées : le contrat d’extension FontRegistry

FontRegistryInterface est un contrat dont la durée de vie suit celle du processus, destiné à enregistrer et rechercher des polices. Enregistre une police à partir d’un chemin de fichier, d’un répertoire ou de données binaires brutes, puis verrouille le registre afin que les workers de production ne puissent plus le modifier.

Fenêtre de terminal
composer require nextpdf/core:^3

Le registre des polices est un singleton qui survit aux instances individuelles de Document. Il ne contient que des données PHP pures, sans ressource ni objet d’extension. Il peut donc être partagé sans risque entre plusieurs requêtes dans un worker à exécution longue.

Le contrat prend en charge trois modes d’enregistrement :

  • Depuis un fichier. register() analyse un fichier .ttf, .otf, .ttc ou .pfb et renvoie les métadonnées analysées. Pour une TrueType Collection, indique l’index de la sous-police.
  • Depuis un répertoire. addFontDirectory() ajoute un chemin de recherche que le moteur parcourt lorsqu’il résout une famille par son nom.
  • Depuis des données binaires. registerFromBinary() analyse des octets TrueType ou OpenType bruts. C’est le chemin utilisé par le pont @font-face pour les polices récupérées depuis des URI data: ou des sources distantes.

Pour amortir la latence de la première requête, appelle warmup() au démarrage du worker afin de pré-analyser un lot de polices. Appelle ensuite lock(). Après lock(), chaque méthode de mutation lève une LogicException. Ces méthodes sont register(), addFontDirectory(), warmup(), registerBase14() et registerFromBinary(). Les méthodes de recherche restent disponibles : get(), has(), all() et getSearchDirectories(). Ce verrou constitue le mécanisme de sécurité en production. Il garantit qu’aucune requête ne peut modifier l’ensemble de polices partagé.

Dans la plupart des cas, tu n’as pas à implémenter FontRegistryInterface. Le moteur fournit l’implémentation et tu l’appelles. Tu ne l’implémentes réellement que lorsque tu as besoin d’une stratégie de résolution de polices personnalisée, par exemple adossée à un magasin adressé par contenu. Dans les deux cas, le contrat reste la frontière.

NextPDF\Contracts\FontRegistryInterface (stable, depuis la 1.7.0) :

MéthodeRenvoieRôle
register(string $fontFile, string $alias, int $fontIndex)FontInfoAnalyse et enregistre un fichier de police. Lève une exception si le registre est verrouillé ou si le fichier ne peut pas être analysé.
registerFromBinary(string $fontData, string $alias)FontInfoEnregistre une police à partir d’octets TrueType ou OpenType bruts.
registerBase14(string $key, FontInfo $font)voidEnregistre une police standard Base 14 préconstruite.
addFontDirectory(string $directory)voidAjoute un répertoire de recherche de polices.
warmup(array $fontFiles)voidPré-analyse un lot de polices au démarrage du worker.
lock()voidFige le registre pour empêcher toute mutation ultérieure.
isLocked()boolIndique si le registre est verrouillé.
get(string $family, string $style)FontInfo | nullRecherche une police par famille et style.
has(string $key)boolVérifie si une clé d’enregistrement existe.
all()array<string, FontInfo>Renvoie toutes les polices enregistrées.
getSearchDirectories()list<string>Renvoie les répertoires de recherche dans l’ordre.
memoryUsage()MemoryReportIndique la consommation mémoire actuelle du registre.
<?php
declare(strict_types=1);
use NextPDF\Contracts\FontRegistryInterface;
/** @var FontRegistryInterface $fonts */
$info = $fonts->register('/srv/fonts/Inter-Regular.ttf', 'Inter');
if (!$fonts->has('inter')) {
throw new RuntimeException('Inter failed to register');
}

Cette routine de démarrage d’un worker préchauffe un ensemble de polices, verrouille le registre et observe chaque chargement pour le suivi des licences. Tous les types qu’elle utilise sont publics.

<?php
declare(strict_types=1);
use NextPDF\Contracts\FontRegistryInterface;
use NextPDF\Event\Content\FontLoadedEvent;
use NextPDF\Event\EventDispatcher;
use NextPDF\Event\ListenerProvider;
use Psr\Log\LoggerInterface;
final class FontWarmup
{
/** @param list<string> $fontFiles */
public function __construct(
private readonly FontRegistryInterface $fonts,
private readonly LoggerInterface $logger,
private readonly array $fontFiles,
) {}
public function boot(): EventDispatcher
{
$listeners = new ListenerProvider();
$listeners->addListener(
FontLoadedEvent::class,
function (FontLoadedEvent $event): void {
$this->logger->info('font.loaded', [
'family' => $event->family,
'style' => $event->style,
'type' => $event->fontType->name,
]);
},
);
if (!$this->fonts->isLocked()) {
$this->fonts->warmup($this->fontFiles);
$this->fonts->lock();
}
return new EventDispatcher($listeners);
}
}
  • Registre verrouillé. Toute mutation après lock() lève une LogicException. Vérifie toujours isLocked() avant un préchauffage conditionnel dans un worker recyclé.
  • L’enregistrement binaire n’est pas mis en cache par clé. registerFromBinary() écrit dans un fichier temporaire et l’analyse. Traite le FontInfo renvoyé comme le handle.
  • Index TTC. Pour une TrueType Collection, le troisième argument de register() sélectionne la sous-police. La valeur par défaut 0 sélectionne la première face.
  • Résolution de famille. get() renvoie null pour un couple famille/style inconnu. Ne suppose jamais un résultat non nul.

warmup() déplace le coût d’analyse de la première requête vers le démarrage. Les méthodes du registre travaillent sur des données PHP pures. Les recherches sont des lectures de map en temps constant. Appelle memoryUsage() pour dimensionner l’ensemble de polices résidant dans un worker par rapport à ton budget mémoire.

Une police enregistrée devient du contenu PDF intégrable. Valide la provenance de la police avant l’enregistrement. N’enregistre pas de données binaires contrôlées par un attaquant sans contrôle de taille et de format. Le hook FontLoadedEvent est le point pris en charge pour faire respecter la conformité de licence des polices et enregistrer les faces intégrées par un document.

Aucune revendication normative de signature ou d’archivage ne s’applique. L’intégration et le sous-ensemblage des polices se conforment au modèle de polices PDF 2.0 ; cette conformité relève du sous-ensembleur interne, pas de ce contrat.

NextPDF Enterprise ajoute l’attestation de licence de police et une politique de sous-ensemblage auditée par-dessus la même FontRegistryInterface. Ton code d’enregistrement reste inchangé d’une édition à l’autre, parce que le contrat est la frontière.

Le glossaire définit registre de polices, registre d’images et écouteur d’événements ; consulte le glossaire publié pour chaque définition canonique.