Aller au contenu

Contrats / Typographie

Le domaine de la typographie regroupe le contrat du registre de polices et les contrats de prétraitement du texte : FontRegistryInterface, TextPreprocessorInterface, ainsi que les objets valeur immuables TextPreprocessResult et TextSegment. Tous sont stable.

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

FontRegistryInterface est le magasin de polices conservé pendant toute la durée du processus. Il enregistre une police TrueType, OpenType, TTC ou PFB et renvoie les métadonnées FontInfo analysées. Le registre perdure au-delà des documents individuels, si bien qu’un worker analyse chaque police une seule fois. Il peut préchauffer un lot de polices au démarrage, puis se verrouiller afin que le trafic de production ne puisse plus le modifier. Un registre verrouillé lève LogicException sur register(), addFontDirectory() ou warmup(), tandis que les recherches restent disponibles. Le registre accepte aussi une police à partir de données binaires brutes via registerFromBinary(). Le pont @font-face utilise cette méthode pour enregistrer une police récupérée depuis une source distante ou une URI de données. Le registre ne stocke que des données PHP pures — aucun handle de ressource — il peut donc être partagé sans risque au sein d’un pool de workers.

Le moteur intègre chaque police qu’il utilise et en crée un sous-ensemble. Un programme de police intégré est transporté dans le PDF, si bien que le document s’affiche de la même façon dans n’importe quel lecteur, indépendamment des polices système installées — ISO 32000-2 §9. Un sous-ensemble de police n’embarque que les glyphes que le document référence réellement, ce qui est décisif pour le contenu CJK ou riche en Unicode — ISO 32000-2 §9. Le contrat du registre expose les métadonnées analysées consommées par les étapes de sous-ensemble et d’intégration.

TextPreprocessorInterface intercepte le texte avant qu’il n’entre dans la mise en page des glyphes, le sous-ensemble de police, la CMap ToUnicode et l’arbre de structure. Ce positionnement fournit la garantie de sécurité : un préprocesseur qui caviarde du contenu le supprime avant qu’il ne puisse atteindre le flux de contenu, le sous-ensemble de police ou les métadonnées. Le contrat définit deux invariants. Un préprocesseur ne doit pas introduire de caractères qui affectent la mise en page et doit préserver l’ordre de lecture logique ; sa responsabilité est la substitution de contenu, pas la mise en page. Le résultat est un TextPreprocessResult immuable qui contient une liste ordonnée de valeurs TextSegment. Un segment est soit transmis tel quel, soit caviardé. Pour un segment caviardé, le texte affiché dépend du mode de masquage : vide pour un rectangle noir, des astérisques correspondant à la longueur d’origine, ou une étiquette fixe. Le originalCharCount d’un segment est un indice de mesure non réversible, utilisé uniquement pour dimensionner un rectangle de caviardage. Il ne doit jamais servir à reconstruire le contenu d’origine.

TypeNatureMembres clésStabilitéDepuis
FontRegistryInterfaceinterfaceregister(), get(), has(), all(), addFontDirectory(), warmup(), lock(), isLocked(), registerBase14(), registerFromBinary(), memoryUsage()stable1.7.0
TextPreprocessorInterfaceinterfaceprocess(string): TextPreprocessResultstable1.9.0
TextPreprocessResultclasse final readonly$segments, hasRedactions(), getDisplayText()stable1.9.0
TextSegmentclasse final readonly$displayText, $isRedacted, $originalCharCount, $fillColorstable1.9.0

TextPreprocessResult et TextSegment figent la signature de leur constructeur et leurs propriétés publiques ; de nouvelles méthodes peuvent être ajoutées, mais les propriétés ne peuvent pas changer.

examples/04-text-and-fonts.php
<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();
$doc->addPage();
$doc->setFont('helvetica', 'B', 18);
$doc->cell(0, 12, 'Bold heading', newLine: true);
$doc->setFont('helvetica', '', 11);
$doc->multiCell(0, 7, 'Body text rendered with a registered font.');
$doc->save(__DIR__ . '/output/04-text-and-fonts.pdf');

setFont() résout la famille via FontRegistryInterface. Le document autonome utilise un registre privé. Un worker partage, lui, un registre unique (voir la page du document).

examples/contracts/typography-production.php
<?php
declare(strict_types=1);
require_once __DIR__ . '/../../vendor/autoload.php';
use NextPDF\Contracts\FontRegistryInterface;
use NextPDF\Contracts\TextPreprocessorInterface;
use NextPDF\Exception\NextPdfException;
use Psr\Log\LoggerInterface;
final readonly class FontWarmupService
{
public function __construct(
private FontRegistryInterface $fonts,
private TextPreprocessorInterface $preprocessor,
private LoggerInterface $logger,
) {}
/**
* Warm a font set at boot, then lock the registry.
*
* @param list<string> $fontFiles Absolute paths to font files.
*/
public function boot(array $fontFiles): void
{
try {
$this->fonts->warmup($fontFiles);
$this->fonts->lock();
} catch (NextPdfException $e) {
$this->logger->error('Font warmup failed', ['error' => $e->getMessage()]);
throw $e;
}
}
public function redact(string $text): string
{
$result = $this->preprocessor->process($text);
return $result->hasRedactions()
? $result->getDisplayText()
: $text;
}
}

warmup() puis lock() forment la séquence de démarrage d’un worker. Après lock(), toute mutation lève une exception. Les recherches restent disponibles pour le trafic.

  • Un registre verrouillé rejette toute méthode de mutation. Préchauffe et verrouille au démarrage ; n’appelle jamais register() pendant le traitement d’une requête.
  • registerFromBinary() écrit les octets de la police dans un fichier temporaire pour les analyser. Des données de police non fiables constituent une surface d’attaque pour l’analyseur — filtre-les via ExternalResourcePolicyInterface (voir la page de la politique de sécurité).
  • Un TextPreprocessor ne doit pas ajouter de sauts de ligne, de retours chariot ou de tabulations. Cela change la mise en page et rompt le premier invariant du contrat.
  • TextSegment::$originalCharCount n’est qu’un indice de largeur. L’utiliser pour déduire le contenu d’origine fait échouer le caviardage et viole le troisième invariant du contrat.
  • TextPreprocessResult::getDisplayText() renvoie une chaîne vide pour les segments à rectangle noir, et c’est intentionnel. Ne traite pas un segment vide comme un échec de prétraitement.

L’analyse des polices domine le coût de la première utilisation ; le registre l’amortit en la limitant à une seule fois par processus. Après le préchauffage, get() et has() sont des recherches dans une table en O(1). memoryUsage() renvoie un MemoryReport, ce qui permet à un worker de suivre le cache de polices par rapport à son budget. Le prétraitement du texte est linéaire par rapport à la longueur de l’entrée. La liste de segments ajoute une surcharge bornée, proportionnelle au nombre de correspondances de caviardage. Le performance_budget de 1500 ms de temps réel et 64 Mo de pic couvre le préchauffage d’un jeu de polices typique ainsi que le rendu du document. Le coût du sous-ensemble varie selon le nombre de glyphes réellement utilisés, pas selon la table de glyphes complète de la police. Le sous-ensemble réduit donc la taille de sortie et le coût de rendu pour le contenu CJK.

Le domaine de la typographie présente deux surfaces importantes pour la sécurité. La première est l’entrée des polices : registerFromBinary() analyse des octets arbitraires. Des données de police non fiables doivent passer par une ExternalResourcePolicyInterface qui borne la taille du fichier et le nombre de glyphes avant qu’elles n’atteignent l’analyseur. La seconde est le caviardage : TextPreprocessorInterface est placé avant la mise en page des glyphes, le sous-ensemble de police, la CMap ToUnicode et l’arbre de structure, précisément pour que le contenu caviardé n’entre jamais dans l’artefact rendu. Un caviardage implémenté comme une superposition au moment du tracé laisse fuir le texte d’origine dans le flux de contenu et le sous-ensemble. Le placement du contrat empêche cette catégorie de défaut. L’indice de mesure d’un segment est délibérément non réversible. Considère toute police ou tout texte fourni de l’extérieur comme non fiable.

AffirmationNormeClausePreuve
Chaque police utilisée par le document est intégrée, si bien que le document s’affiche sans dépendre des polices système.ISO 32000-2§9
La police intégrée est réduite au sous-ensemble des glyphes que le document référence.ISO 32000-2§9

Les deux clauses sont paraphrasées. NextPDF ne reproduit pas le texte normatif. PDF/A-4 impose l’intégration de chaque police. Cette conformité est documentée sur les pages d’extraction et d’accessibilité.