Index des tutoriels · Précédent : pages et texte · Suivant : plan du document
Cette leçon dessine des rectangles, une courbe de Bézier et un segment pointillé. Elle applique ensuite une translation et une rotation au même rectangle local. Le script complet est graphics.php.
Exécuter l'exemple
Utiliser PHP 8.3 ou supérieur et les dépendances installées par composer install. L'assemblage du document est celui du premier tutoriel. Depuis la racine d'un checkout du core :
php examples/graphics.php graphics.pdf
Le premier argument désigne la sortie. Sans argument, le fichier est examples/graphics.pdf. Son répertoire parent doit déjà exister.
Ce que l'on obtient
Une page A4 avec deux rectangles bleus, l'un rempli et l'autre tracé ; une courbe orange et sa corde pointillée ; deux rectangles bleu clair en bas, dont l'un est tourné de 30 degrés. Les légendes restent noires et horizontales.

1. Construire un tracé, puis le peindre
Un tracé commence avec un point courant ou un rectangle. Les opérateurs de construction ajoutent sa géométrie ; un opérateur de peinture décide ensuite du rendu et termine ce tracé.
| Objet | Opérateur | Fonction |
|---|---|---|
AppendRectangle |
re |
Ajoute un rectangle avec x, y, largeur et hauteur. |
MoveTo |
m |
Commence un sous-tracé en un point. |
LineTo |
l |
Ajoute un segment depuis le point courant. |
CurveTo |
c |
Ajoute une courbe avec deux points de contrôle et un point final. |
FillPathNonzero |
f |
Remplit selon la règle nonzero winding. |
StrokePath |
S |
Trace le contour. |
FillAndStrokePathNonzero |
B |
Remplit et trace le contour. |
Le premier rectangle associe re et f, le second re et S. Le rectangle du bas utilise B. La courbe part de (54, 410), utilise les points de contrôle (175, 500) et (405, 320), puis arrive à (541, 410). Les points de contrôle orientent la courbe sans devenir nécessairement des points qu'elle traverse.
Après f, S ou B, le tracé est terminé. Pour peindre à nouveau la même forme, il faut la reconstruire. Le validateur de ContentStreams refuse aussi de glisser un changement de couleur ou un Q au milieu d'un tracé inachevé : les réglages précèdent la géométrie, puis la peinture la termine.
Référence : ISO 32000-1, 8.5.2–8.5.3 pour la construction et la peinture des tracés.
2. Régler deux couleurs indépendantes
SetNonstrokingRGBColor (rg) choisit la couleur utilisée par les remplissages, et par le texte dans son mode de rendu par défaut. SetStrokingRGBColor (RG) choisit celle du contour. La casse des opérateurs PDF compte.
Les trois composantes DeviceRGB vont de 0 à 1. SetLineWidth (w) règle la largeur du contour. SetLineCapStyle(LineCapStyle::Round) (J) arrondit les extrémités de la courbe. SetLineDashPattern (d) reçoit un LineDashPattern : [4 4] alterne quatre unités peintes et quatre unités vides, avec une phase initiale de zéro.
Les couleurs DeviceRGB sont adaptées à cette démonstration ; leur rendu exact dépend du lecteur et du dispositif. Un besoin de gestion des couleurs demande un espace de couleur approprié, par exemple ICCBased, et ses ressources explicites.
Référence : 8.4.3–8.4.4 pour les paramètres d'état graphique, 8.6.4.3 et 8.6.8 pour DeviceRGB et les opérateurs de couleur.
3. Isoler un dessin avec q et Q
SaveGraphicsState (q) empile l'état graphique courant. RestoreGraphicsState (Q) restaure l'état sauvegardé : matrice de transformation, couleurs, largeur du trait et autres paramètres. Chaque forme du script est encadrée par cette paire.
Cet encadrement explique pourquoi les pointillés ne s'appliquent pas aux rectangles suivants et pourquoi leurs remplissages ne colorent pas les légendes. Il protège aussi les textes de la rotation appliquée à la dernière forme.
La géométrie du tracé courant ne fait pas partie de cet état sauvegardé. Q n'est donc pas une instruction permettant de terminer ou d'annuler un tracé. Le script peint chaque tracé avant de restaurer l'état, et équilibre toutes les paires q/Q.
Référence : 8.4.2 et 8.4.4.
4. Dessiner dans un repère local
ConcatenateMatrix (cm) reçoit un Matrix de six NumericType : [a b c d e f]. Pour un point local (x, y), la transformation de cet exemple donne :
x' = a × x + c × y + e
y' = b × x + d × y + f
La page utilise ici les coordonnées habituelles, avec une unité égale à 1/72 de pouce et l'origine en bas à gauche. Le rectangle local est centré en (0, 0) et mesure 100 × 80 unités ; sa construction commence donc en (-50, -40).
[1 0 0 1 130 205]place son centre en(130, 205)sans rotation.[0.8660254 0.5 -0.5 0.8660254 415 205]applique une rotation de 30 degrés, puis place son centre en(415, 205). Les valeurs sontcos(30°)etsin(30°)arrondies.
cm concatène sa matrice à la matrice courante. Son résultat dépend donc de l'état déjà en place, et l'ordre de plusieurs transformations compte. Chaque exemple repart de l'état de la page grâce à q et Q. La rotation ne modifie pas /Rotate de la page : elle transforme seulement le dessin de ce bloc.
Référence : 8.3.2–8.3.3 pour les repères et matrices, 8.4.4 pour cm.
À essayer
- Remplacer
StrokePathparFillAndStrokePathNonzeropour ajouter un remplissage à un rectangle. - Changer les deux points de contrôle de la courbe en gardant les points de début et de fin.
- Utiliser
[2 0 0 2 e f]dans un bloc isolé : la taille du rectangle et sa largeur de contour sont transformées ensemble. - Ajouter une deuxième paire
q/Qdans un bloc pour expérimenter des états imbriqués.
Le placement et les marges sont explicites. Un tracé peut dépasser la page si ses coordonnées ou sa transformation le placent hors du papier ; vérifier le PDF après une modification des matrices.
Index des tutoriels · Précédent : pages et texte · Suivant : plan du document
Voir le résultat PDF
Ce fichier a été généré avec le code de cette documentation. Toutes ses pages sont reproduites ci-dessous ; ouvrir le PDF pour utiliser ses liens et ses signets.
Utiliser le lecteur PDF du navigateur
Ce lecteur dépend du navigateur. Si le document ne s’affiche pas, utiliser « Ouvrir le PDF » ou « Télécharger ».
Code source complet
Code de roukmoute/pdf · Consulter la source au commit documenté ↗
Lire le script commenté : graphics.php
<?php
declare(strict_types=1);
use PDF\Document\DocumentInformation;
use PDF\Document\Fonts\StandardFont;
use PDF\Document\Fonts\Type1Font;
use PDF\Document\Information\Title;
use PDF\Document\ResourceDictionary;
use PDF\Document\Resources\Font;
use PDF\FileStructure;
use PDF\Graphics\CoordinateSystems\Matrix;
use PDF\Graphics\GraphicsState\LineCapStyle;
use PDF\Graphics\GraphicsState\LineDashPattern;
use PDF\Syntax\ContentStreamsResources\ContentStreams;
use PDF\Syntax\ContentStreamsResources\Operators\AppendRectangle;
use PDF\Syntax\ContentStreamsResources\Operators\BeginTextObject;
use PDF\Syntax\ContentStreamsResources\Operators\ConcatenateMatrix;
use PDF\Syntax\ContentStreamsResources\Operators\CurveTo;
use PDF\Syntax\ContentStreamsResources\Operators\EndTextObject;
use PDF\Syntax\ContentStreamsResources\Operators\FillAndStrokePathNonzero;
use PDF\Syntax\ContentStreamsResources\Operators\FillPathNonzero;
use PDF\Syntax\ContentStreamsResources\Operators\LineTo;
use PDF\Syntax\ContentStreamsResources\Operators\MoveTextPosition;
use PDF\Syntax\ContentStreamsResources\Operators\MoveTo;
use PDF\Syntax\ContentStreamsResources\Operators\RestoreGraphicsState;
use PDF\Syntax\ContentStreamsResources\Operators\SaveGraphicsState;
use PDF\Syntax\ContentStreamsResources\Operators\SetLineCapStyle;
use PDF\Syntax\ContentStreamsResources\Operators\SetLineDashPattern;
use PDF\Syntax\ContentStreamsResources\Operators\SetLineWidth;
use PDF\Syntax\ContentStreamsResources\Operators\SetNonstrokingRGBColor;
use PDF\Syntax\ContentStreamsResources\Operators\SetStrokingRGBColor;
use PDF\Syntax\ContentStreamsResources\Operators\SetTextFontAndSize;
use PDF\Syntax\ContentStreamsResources\Operators\ShowText;
use PDF\Syntax\ContentStreamsResources\Operators\StrokePath;
use PDF\Syntax\DataStructure\Rectangle;
use PDF\Syntax\DataStructure\TextString;
use PDF\Syntax\DocumentStructure\DocumentCatalog;
use PDF\Syntax\DocumentStructure\PageTree\PageObject;
use PDF\Syntax\DocumentStructure\PageTree\PageTreeNode;
use PDF\Syntax\FileStructure\FileBody;
use PDF\Syntax\FileStructure\FileHeader;
use PDF\Syntax\Object\ArrayType;
use PDF\Syntax\Object\DictionaryObject\Entry;
use PDF\Syntax\Object\DictionaryType;
use PDF\Syntax\Object\IndirectReference;
use PDF\Syntax\Object\NameType;
use PDF\Syntax\Object\NumericType;
use PDF\Syntax\Object\StreamObject;
use PDF\Syntax\Object\StringObject\LiteralString;
require dirname(__DIR__) . '/vendor/autoload.php';
$helvetica = new Type1Font(StandardFont::Helvetica);
$resources = new ResourceDictionary(
new Font(new DictionaryType(
new Entry(new NameType('F1'), new IndirectReference($helvetica)),
)),
);
// 1. Drawing uses paper coordinates: origin at the lower left,
// one user-space unit is 1/72 inch here.
$contents = new ContentStreams(
new BeginTextObject(),
new SetTextFontAndSize(new NameType('F1'), new NumericType(24)),
new MoveTextPosition(new NumericType(54), new NumericType(778)),
new ShowText(new LiteralString('Traces, couleurs et matrices')),
new EndTextObject(),
new BeginTextObject(),
new SetTextFontAndSize(new NameType('F1'), new NumericType(11)),
new MoveTextPosition(new NumericType(54), new NumericType(750)),
new ShowText(new LiteralString('Chaque forme est une suite d\'operateurs PDF explicites.')),
new EndTextObject(),
// 2. q saves the state, rg selects the fill color.
// re constructs the rectangle; f fills it and ends the path.
new SaveGraphicsState(),
new SetNonstrokingRGBColor(new NumericType(0.12), new NumericType(0.36), new NumericType(0.64)),
new AppendRectangle(new NumericType(54), new NumericType(546), new NumericType(220), new NumericType(134)),
new FillPathNonzero(),
new RestoreGraphicsState(),
// RG and w set the outline. Its color is independent of the fill.
new SaveGraphicsState(),
new SetStrokingRGBColor(new NumericType(0.12), new NumericType(0.36), new NumericType(0.64)),
new SetLineWidth(new NumericType(3)),
new AppendRectangle(new NumericType(321), new NumericType(546), new NumericType(220), new NumericType(134)),
new StrokePath(),
new RestoreGraphicsState(),
new BeginTextObject(),
new SetTextFontAndSize(new NameType('F1'), new NumericType(11)),
new MoveTextPosition(new NumericType(54), new NumericType(524)),
new ShowText(new LiteralString('re + f : remplir')),
new EndTextObject(),
new BeginTextObject(),
new SetTextFontAndSize(new NameType('F1'), new NumericType(11)),
new MoveTextPosition(new NumericType(321), new NumericType(524)),
new ShowText(new LiteralString('re + S : tracer le contour')),
new EndTextObject(),
// 3. The Bézier curve begins at m and uses two control points.
// Settings precede the path; S ends it before the matching Q.
new SaveGraphicsState(),
new SetStrokingRGBColor(new NumericType(0.82), new NumericType(0.34), new NumericType(0.12)),
new SetLineWidth(new NumericType(5)),
new SetLineCapStyle(LineCapStyle::Round),
new MoveTo(new NumericType(54), new NumericType(410)),
new CurveTo(
new NumericType(175),
new NumericType(500),
new NumericType(405),
new NumericType(320),
new NumericType(541),
new NumericType(410),
),
new StrokePath(),
new RestoreGraphicsState(),
// The dashed line shows the curve's chord using a separate state.
new SaveGraphicsState(),
new SetStrokingRGBColor(new NumericType(0.58), new NumericType(0.62), new NumericType(0.67)),
new SetLineWidth(new NumericType(1)),
new SetLineDashPattern(new LineDashPattern(
new ArrayType(new NumericType(4), new NumericType(4)),
new NumericType(0),
)),
new MoveTo(new NumericType(54), new NumericType(410)),
new LineTo(new NumericType(541), new NumericType(410)),
new StrokePath(),
new RestoreGraphicsState(),
new BeginTextObject(),
new SetTextFontAndSize(new NameType('F1'), new NumericType(11)),
new MoveTextPosition(new NumericType(54), new NumericType(356)),
new ShowText(new LiteralString('m + c + S : courbe ; m + l + S : segment pointille')),
new EndTextObject(),
// 4. cm concatenates [a b c d e f] with the current transformation matrix.
// [1 0 0 1 130 205] translates the origin to (130, 205).
new SaveGraphicsState(),
new ConcatenateMatrix(new Matrix(
new NumericType(1),
new NumericType(0),
new NumericType(0),
new NumericType(1),
new NumericType(130),
new NumericType(205),
)),
new SetNonstrokingRGBColor(new NumericType(0.87), new NumericType(0.93), new NumericType(0.98)),
new SetStrokingRGBColor(new NumericType(0.12), new NumericType(0.36), new NumericType(0.64)),
new SetLineWidth(new NumericType(2)),
new AppendRectangle(new NumericType(-50), new NumericType(-40), new NumericType(100), new NumericType(80)),
new FillAndStrokePathNonzero(),
new RestoreGraphicsState(),
// cos(30°) ≈ 0.8660254 and sin(30°) = 0.5: rotation, then translation.
// The same local rectangle is drawn in a second coordinate system.
new SaveGraphicsState(),
new ConcatenateMatrix(new Matrix(
new NumericType(0.8660254),
new NumericType(0.5),
new NumericType(-0.5),
new NumericType(0.8660254),
new NumericType(415),
new NumericType(205),
)),
new SetNonstrokingRGBColor(new NumericType(0.87), new NumericType(0.93), new NumericType(0.98)),
new SetStrokingRGBColor(new NumericType(0.12), new NumericType(0.36), new NumericType(0.64)),
new SetLineWidth(new NumericType(2)),
new AppendRectangle(new NumericType(-50), new NumericType(-40), new NumericType(100), new NumericType(80)),
new FillAndStrokePathNonzero(),
new RestoreGraphicsState(),
// Q restored matrix and colors: these labels are horizontal, black
// and once again expressed in page coordinates.
new BeginTextObject(),
new SetTextFontAndSize(new NameType('F1'), new NumericType(11)),
new MoveTextPosition(new NumericType(54), new NumericType(117)),
new ShowText(new LiteralString('cm : translation')),
new EndTextObject(),
new BeginTextObject(),
new SetTextFontAndSize(new NameType('F1'), new NumericType(11)),
new MoveTextPosition(new NumericType(321), new NumericType(117)),
new ShowText(new LiteralString('cm : rotation de 30 degres')),
new EndTextObject(),
);
// 5. The document graph and writing follow the first tutorial.
$page = new PageObject(
new Rectangle(new NumericType(0), new NumericType(0), new NumericType(595.28), new NumericType(841.89)),
$resources,
new StreamObject($contents),
);
$pages = (new PageTreeNode())->addKid($page);
$catalog = new DocumentCatalog($pages);
$information = new DocumentInformation(new Title(new TextString('Tracés, couleurs et transformations')));
$file = new FileStructure(
new FileHeader('1.7'),
new FileBody($catalog),
new IndirectReference($catalog),
new DictionaryType(new Entry(new NameType('Info'), new IndirectReference($information))),
);
$path = $argv[1] ?? __DIR__ . '/graphics.pdf';
$file->writeTo($path);
echo $path . PHP_EOL;
