Ajouter des filigranes texte ou image et des arrière-plans aux pages
Tu veux afficher une mention « DRAFT » ou « CONFIDENTIAL » sur chaque page, ou placer un logo discret derrière le contenu. Cette recette montre comment superposer ces deux éléments sur les pages de NextPDF core avec la surface publique du document : setAlpha() pour la transparence, startTransform() / rotate() / stopTransform() pour un tampon diagonal, text() pour la mention, et image() pour un arrière-plan matriciel.
Un filigrane et un arrière-plan ne diffèrent que par une seule décision : l’ordre de dessin.
- Arrière-plan : dessine-le d’abord, puis écris le contenu de ta page par-dessus. La mention se place derrière le texte.
- Filigrane en surimpression : écris d’abord le contenu de ta page, puis dessine la mention par-dessus. La mention se place au-dessus.
NextPDF peint le contenu dans l’ordre où tu l’appelles ; ton ordre d’appel correspond donc à l’ordre des couches. Il n’existe pas de mode « arrière-plan » distinct. Tu choisis la couche en choisissant le moment où tu dessines.
Prérequis : une installation de NextPDF core (composer require nextpdf/core:^3), et, pour un arrière-plan image, un fichier matriciel lisible (PNG, JPEG ou WebP) sur le disque. Tout le pipeline s’exécute dans le processus, sans navigateur headless ni appel réseau.
Installation
Section intitulée « Installation »composer require nextpdf/core:^3Vue d’ensemble conceptuelle
Section intitulée « Vue d’ensemble conceptuelle »Chaque mention que tu ajoutes est du contenu de page ordinaire, dessiné via un état graphique. Trois éléments de la surface publique se combinent pour produire un filigrane :
-
Transparence.
setAlpha(float $alpha, BlendMode $mode = BlendMode::Normal)définit l’opacité de remplissage pour tout ce que tu dessines ensuite, de0.0(invisible) à1.0(opaque). Un filigrane se situe généralement entre0.1et0.3pour que le contenu en dessous reste lisible. Le mode de fusion provient de l’enumNextPDF\Graphics\BlendMode. Par exemple,BlendMode::Multiplyassombrit là où la mention recouvre le contenu. -
Rotation. Un tampon diagonal, c’est du texte tourné autour d’un point de pivot.
startTransform()sauvegarde l’état graphique,rotate(float $angle, float $x, float $y)tourne le système de coordonnées dans le sens antihoraire autour de($x, $y), etstopTransform()restaure l’état sauvegardé. Encadrer la mention dans un bloc de transformation empêche la rotation et l’alpha de s’appliquer au reste de la page. -
La mention elle-même.
text(float $x, float $y, string $text)écrit une chaîne à une position absolue, dans la police, la couleur et l’alpha courants.image(string $file, ?float $x, ?float $y, ?float $width, ?float $height)place une image matricielle : la brique de base d’un filigrane image ou d’un arrière-plan pleine page.
L’état graphique est restauré proprement parce que startTransform() et stopTransform() encadrent la modification. La valeur de setAlpha() persiste jusqu’à ce que tu la redéfinisses. Donc si du contenu ultérieur doit être totalement opaque, remets l’opacité à 1.0 après la mention. Le schéma le plus sûr, montré ci-dessous, dessine la mention dans son propre bloc de transformation et définit explicitement l’alpha du contenu de la page.
Le package fournit aussi les objets valeur NextPDF\Graphics\Watermark et NextPDF\Graphics\WatermarkPosition. Watermark est un conteneur de configuration immuable : texte, taille de police, angle, couleur, indicateur de surimpression, et préréglages de position tels que WatermarkPosition::Diagonal. Ils modélisent les paramètres d’un filigrane. Cette recette peint la mention avec les méthodes de dessin de page ci-dessus, de sorte que la sortie atteint directement le flux de contenu de la page.
Surface d’API
Section intitulée « Surface d’API »Toutes les méthodes ci-dessous sont publiques sur NextPDF\Core\Document et renvoient static, elles peuvent donc être chaînées.
setAlpha(float $alpha, BlendMode $mode = BlendMode::Normal): static: définit l’opacité de remplissage (0.0-1.0) et le mode de fusion pour le contenu suivant.startTransform(): static: sauvegarde l’état graphique (émetq).rotate(float $angle, float $x = 0, float $y = 0): static: fait pivoter le système de coordonnées de$angledegrés dans le sens antihoraire autour du pivot($x, $y).stopTransform(): static: restaure l’état sauvegardé parstartTransform()(émetQ), ce qui annule à la fois la rotation et la modification de l’alpha.setFont(string $family, string $style = '', float $size = 12.0): static: sélectionne la police de la mention. La famille Base-14helveticaest toujours disponible et ne nécessite aucun fichier de police.setTextColor(int $r, int $g = -1, int $b = -1): static: définit la couleur de la mention en composantes rouge, vert et bleu (ou une seule valeur de niveaux de gris).text(float $x, float $y, string $text): static: écrit la mention à une position absolue.image(string $file, ?float $x = null, ?float $y = null, ?float $width = null, ?float $height = null): static: place une image matricielle, la base d’un filigrane image ou d’un arrière-plan pleine page.getPageWidth(): float/getPageHeight(): float: lit la taille de la page courante en points pour que tu puisses centrer la mention.
Les types complémentaires se trouvent sous NextPDF\Graphics : l’enum BlendMode, l’objet valeur Color, et la paire de configuration Watermark / WatermarkPosition.
Exemple de code — Démarrage rapide
Section intitulée « Exemple de code — Démarrage rapide »Cet exemple écrit une page, dessine un tampon « DRAFT » diagonal et discret par-dessus le contenu, et enregistre le fichier. Il laisse de côté la gestion des erreurs pour montrer la forme des appels. L’exemple de production ci-dessous ajoute toutes les protections.
<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();$doc->addPage();
// Page content first, so the watermark lands on top of it.$doc->setFont('helvetica', '', 12);$doc->text(20.0, 40.0, 'Quarterly report: internal review copy.');
// Watermark second: a translucent, rotated stamp through the page center.$pivotX = $doc->getPageWidth() / 2.0;$pivotY = $doc->getPageHeight() / 2.0;
$doc->startTransform();$doc->setAlpha(0.15);$doc->setTextColor(150, 150, 150);$doc->setFont('helvetica', 'B', 72);$doc->rotate(45.0, $pivotX, $pivotY);$doc->text($pivotX - 110.0, $pivotY, 'DRAFT');$doc->stopTransform();
file_put_contents(__DIR__ . '/watermarked.pdf', $doc->getPdfData());Exemple de code — Production
Section intitulée « Exemple de code — Production »Ce programme autonome dessine un filigrane texte diagonal par-dessus le contenu généré. Puis, quand un chemin d’image est fourni via la variable d’environnement NEXTPDF_WATERMARK_IMAGE, il place cette image comme arrière-plan discret et centré sur une deuxième page. Il valide le chemin de l’image avant utilisation, intercepte les exceptions NextPDF les plus spécifiques et écrit le résultat vers un chemin contrôlé par le serveur. Remplace le contenu en mémoire par le tien et relie la sortie à ta réponse ou à ta couche de stockage.
<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;use NextPDF\Exception\ImageProcessingException;use NextPDF\Exception\NextPdfException;use NextPDF\Exception\PageLayoutException;
/** * Paint a translucent, rotated text stamp across the current page. * * The mark is bracketed in a transform block so the rotation and the alpha * change are undone together and never leak into later content. * * @param non-empty-string $mark The watermark text (for example "CONFIDENTIAL") */function paintTextWatermark(Document $doc, string $mark): void{ $pivotX = $doc->getPageWidth() / 2.0; $pivotY = $doc->getPageHeight() / 2.0;
// Estimate the mark width so the rotated text sits centered on the pivot. // Helvetica averages ~0.5 em per glyph; half the width offsets the origin. $fontSize = 64.0; $halfWidth = (\strlen($mark) * $fontSize * 0.5) / 2.0;
$doc->startTransform(); $doc->setAlpha(0.12); $doc->setTextColor(120, 120, 120); $doc->setFont('helvetica', 'B', $fontSize); $doc->rotate(45.0, $pivotX, $pivotY); $doc->text($pivotX - $halfWidth, $pivotY, $mark); $doc->stopTransform();}
/** * Place a raster image as a faint, full-page background behind later content. * * The image is drawn first and at low opacity; page content written after this * call sits over it. The path is validated by the caller before it arrives. * * @param non-empty-string $imagePath A readable raster image (PNG, JPEG, WebP) * * @throws ImageProcessingException If the file is missing, unreadable, or corrupt. * @throws PageLayoutException If the placement coordinates are rejected. */function paintImageBackground(Document $doc, string $imagePath): void{ $doc->startTransform(); $doc->setAlpha(0.08); // Cover the full page: origin at the top-left, sized to the page box. $doc->image( file: $imagePath, x: 0.0, y: 0.0, width: $doc->getPageWidth(), height: $doc->getPageHeight(), ); $doc->stopTransform();}
$doc = Document::createStandalone();$doc->setTitle('Watermark and background sample');
// Page 1: content first, then an overlay text watermark on top.$doc->addPage();$doc->setAlpha(1.0);$doc->setTextColor(0, 0, 0);$doc->setFont('helvetica', '', 12);$doc->text(20.0, 40.0, 'Quarterly report: internal review copy.');
try { paintTextWatermark($doc, 'CONFIDENTIAL');} catch (PageLayoutException $e) { // Raised if a coordinate or page state is rejected while placing the mark. throw new RuntimeException( sprintf('Watermark placement failed: %s', $e->getConstraint()), previous: $e, );}
// Page 2: an optional image background, then content over it.$imagePath = getenv('NEXTPDF_WATERMARK_IMAGE');
if ($imagePath !== false && $imagePath !== '') { // Validate the path before touching the image loader: reject NUL bytes, // require a real readable file, and resolve it to defeat path traversal. if (str_contains($imagePath, "\0")) { throw new RuntimeException('Image path must not contain NUL bytes.'); }
$resolved = realpath($imagePath);
if ($resolved === false || !is_file($resolved) || !is_readable($resolved)) { throw new RuntimeException( sprintf('Background image "%s" is not a readable file.', $imagePath), ); }
$doc->addPage();
try { paintImageBackground($doc, $resolved); } catch (ImageProcessingException $e) { // Raised when the file cannot be decoded as a supported raster format. throw new RuntimeException( sprintf( 'Background image rejected (%s, op "%s").', $e->getFormat(), $e->getOperation(), ), previous: $e, ); } catch (PageLayoutException $e) { throw new RuntimeException( sprintf('Background placement failed: %s', $e->getConstraint()), previous: $e, ); }
$doc->setAlpha(1.0); $doc->setTextColor(0, 0, 0); $doc->setFont('helvetica', '', 12); $doc->text(20.0, 40.0, 'Page two over a faint background.');}
try { $pdf = $doc->getPdfData();} catch (NextPdfException $e) { // Base of the NextPDF exception hierarchy: any output-stage failure. throw new RuntimeException( sprintf('Document output failed: %s', $e->getMessage()), previous: $e, );}
$out = getenv('NEXTPDF_COOKBOOK_OUTPUT');$path = $out !== false && $out !== '' ? $out : __DIR__ . '/watermarked.pdf';
if (file_put_contents($path, $pdf) === false) { throw new RuntimeException(sprintf('Could not write PDF to "%s".', $path));}
printf("Wrote %d-byte PDF to %s\n", strlen($pdf), $path);Sortie STDOUT attendue (la taille en octets dépend du build et de la présence éventuelle d’une image) :
Wrote <n>-byte PDF to <path>Cas limites & pièges
Section intitulée « Cas limites & pièges »- L’ordre des couches est l’ordre des appels. Un arrière-plan est du contenu dessiné avant le contenu de ta page. Un filigrane en surimpression est du contenu dessiné après. Aucun indicateur ne réordonne les couches ; déplace plutôt l’appel.
- L’alpha persiste jusqu’à sa réinitialisation.
setAlpha()change l’état pour tout ce qui est dessiné ensuite. Soit tu encadres la mention dansstartTransform()/stopTransform(), ce qui restaure l’alpha précédent, soit tu appellessetAlpha(1.0)avant le contenu opaque. L’exemple de production fait les deux. - Équilibre chaque bloc de transformation. Chaque
startTransform()a besoin d’unstopTransform()correspondant. Un bloc déséquilibré laisse la rotation ou l’alpha appliqués au contenu ultérieur, et unstopTransform()manquant crée un déséquilibre de l’état graphique que le writer rejette à la sortie. rotate()pivote en coordonnées utilisateur. Le pivot($x, $y)est en unités utilisateur mesurées depuis le coin supérieur gauche de la page, dans le même repère quetext(). Pour une diagonale passant par le centre, utilise le centre de la page (getPageWidth() / 2,getPageHeight() / 2).- Le texte pivoté nécessite un décalage de largeur manuel.
text()place l’origine de la chaîne ; il ne la centre pas pour toi. Soustrais environ la moitié de la largeur estimée du texte à la valeur X du pivot pour que la mention pivotée recouvre le centre, comme le fait la fonction utilitaire. - Les images s’adaptent à la boîte que tu passes.
image()étire l’image matricielle à lawidthet à laheightque tu indiques. Pour un arrière-plan pleine page, passe la largeur et la hauteur de la page ; pour un logo en coin, passe sa taille naturelle. Une dimension nulle ou négative déclenchePageLayoutException. image()rejette les URL et les octets NUL. Un cheminscheme://ou un octet NUL dans$filedéclenchePageLayoutExceptionavant tout décodage. Ne passe qu’un chemin local et validé.- La mention est du contenu visible. Un filigrane dessiné de cette manière est du vrai contenu de page, pas une annotation cachée. Quiconque a le fichier peut le lire. C’est un repère visuel, pas un contrôle d’accès.
Performances
Section intitulée « Performances »Un filigrane texte représente une poignée d’opérateurs de flux de contenu par page et ajoute un temps ou une mémoire négligeables. Un filigrane ou un arrière-plan image coûte le décodage de l’image matricielle plus les octets de l’image intégrée dans la sortie. Réutiliser la même image sur plusieurs pages réutilise le XObject décodé via le cache d’images, donc tu ne paies le coût du décodage qu’une seule fois. Dimensionne les images d’arrière-plan à leur boîte d’affichage avant de les intégrer. Une photo de 4000 px réduite dans une page letter stocke des octets que le lecteur ne voit jamais. Un filigrane texte typique sur une seule page reste largement dans un budget de 500 ms de temps réel et de 32 Mo de pic. Un arrière-plan image suit la taille décodée de l’image matricielle source.
Notes de sécurité
Section intitulée « Notes de sécurité »Le pipeline s’exécute dans le processus. Aucun octet du document ne quitte l’hôte et aucun appel réseau n’est effectué. Considère comme entrée non fiable tout chemin d’image qui provient de l’extérieur de ton code.
- Valide le chemin de l’image avant utilisation. Rejette les octets NUL, résous le chemin avec
realpath(), et confirmeis_file()etis_readable()avant d’appelerimage(), exactement comme le fait l’exemple de production. Cela bloque la traversée de chemin et rejette tôt les répertoires et les liens cassés. - N’interpole jamais un champ de requête dans un chemin. Dérive le chemin de l’image et le chemin de sortie de valeurs contrôlées par le serveur, pas d’un paramètre de requête. Cela t’empêche de lire ou d’écrire des fichiers en dehors du répertoire prévu.
- Traite les images non fiables comme une entrée hostile. Une image matricielle malformée déclenche
ImageProcessingExceptionplutôt que de corrompre le document, et le chargeur plafonne les dimensions de l’image pour résister aux entrées de type bombe de décompression. Intercepte l’exception et rejette le fichier importé. Ne réessaie pas aveuglément. - Un filigrane n’est pas un coffre à secrets. La mention est du contenu visible. N’encode pas d’identifiants, de jetons ou d’identifiants internes dans un filigrane ou un arrière-plan que tu renvoies à un client.
Conformité
Section intitulée « Conformité »Cette recette ne formule aucune revendication normative de conformité aux standards qui lui soit propre. Elle compose les primitives publiques d’alpha, de transformation, de texte et d’image. Chacune d’elles émet des opérateurs de flux de contenu PDF standard. L’état graphique est isolé avec les opérateurs q / Q qu’émettent startTransform() et stopTransform(), et la transparence est portée par un paramètre d’état graphique ExtGState. La sortie est structurellement neuve plutôt que stable à l’octet près, donc cette page déclare un profil de reproductibilité structural. Pour le détail des opérateurs de la surface de transformation et d’état graphique, consulte la référence du module Graphics.
Voir aussi
Section intitulée « Voir aussi »- Référence du module Graphics : toute la surface de chemin, de transformation, de couleur et d’état graphique derrière ces méthodes.
- Intégrer des images : charge, dimensionne et place des images matricielles, la brique de base d’un filigrane image ou d’un arrière-plan.
- Dégradés et transparence : la surface d’alpha et de mode de fusion en profondeur, y compris les remplissages translucides.
- Transformer l’espace de coordonnées : fais pivoter, mets à l’échelle et applique des translations au contenu avec des blocs de transformation équilibrés.
- Gestion des erreurs basée sur les exceptions : la hiérarchie d’exceptions NextPDF derrière
ImageProcessingException,PageLayoutExceptionetNextPdfException.