Cette leçon construit une page contenant une phrase et un titre documentaire. Elle explique les objets minimums qui permettent à un lecteur PDF de trouver la page, sa police et son contenu.
Prérequis : avoir installé les dépendances comme indiqué dans l'index. Aucune connaissance préalable de la syntaxe PDF n'est nécessaire.
Exécuter l'exemple
Depuis la racine d'un checkout du core :
php examples/hello-world.php
Ouvrir examples/hello-world.pdf. La page au format Letter porte la phrase « Super ! Le PDF est construit objet par objet. ». Le titre « Un PDF entièrement objet » est une métadonnée ; il apparaît dans les propriétés du document selon le lecteur.

Le code complet contient les imports et les commentaires qui correspondent aux étapes ci-dessous.
1. Donner un nom à une ressource de police
Type1Font(StandardFont::Helvetica) crée le dictionnaire de la police standard Helvetica. Le dictionnaire /Font des ressources associe cette instance au nom local /F1 :
$helvetica = new Type1Font(StandardFont::Helvetica);
$resources = new ResourceDictionary(
new Font(new DictionaryType(
new Entry(new NameType('F1'), new IndirectReference($helvetica)),
)),
);
Font est ici l'entrée /Font du dictionnaire de ressources. /F1 est un nom choisi pour ce document ; il peut changer si l'opérateur qui sélectionne la police utilise le même nom. /Helvetica identifie la police elle-même. La police standard n'est pas incorporée : le lecteur fournit sa représentation.
Une IndirectReference vise l'objet PHP. Il est donc possible de relier les objets avant de connaître leur numéro dans le PDF.
2. Décrire le texte par des instructions
Le contenu de page est une suite d'opérateurs. Leur nom PHP décrit l'instruction ; leur token est celui du format :
| Objet | Syntaxe produite | Effet dans cet exemple |
|---|---|---|
BeginTextObject |
BT |
Commencer l'objet texte et initialiser ses matrices. |
SetTextFontAndSize |
/F1 18 Tf |
Sélectionner la ressource /F1 et une taille de 18. |
MoveTextPosition |
72 720 Td |
Déplacer l'origine du texte depuis les matrices initiales. |
ShowText |
(Super ! …) Tj |
Afficher les glyphes correspondant aux octets de la chaîne. |
EndTextObject |
ET |
Terminer l'objet texte. |
Dans l'espace utilisateur par défaut de cette page, une unité vaut 1/72 de pouce. La position (72, 720) se situe à un pouce du bord gauche et à un pouce sous le haut de la page, dont la hauteur est 792. L'axe vertical augmente vers le haut.
ContentStreams contient les instructions et contrôle leur contexte. StreamObject enveloppe leur représentation dans un véritable flux PDF. Le writer calculera /Length à partir de ses octets.
Le texte affiché est ASCII. LiteralString n'effectue pas de conversion de police ou d'encodage : insérer du texte UTF-8 arbitraire dans Tj ne suffit pas à afficher des caractères Unicode. En revanche, le titre documentaire utilise TextString, adapté aux chaînes textuelles des métadonnées.
3. Relier contenu, page et catalogue
Le Rectangle construit avec quatre NumericType définit /MediaBox : [0 0 612 792], la surface de la page au format Letter. PageObject relie ce rectangle, les ressources et le flux de contenu. PageTreeNode::addKid() attache la page à l'arbre et établit sa propriété.
L'arbre produit /Kids, /Count et les références /Parent. Le DocumentCatalog contient /Type /Catalog et une référence /Pages vers la racine de cet arbre. Une page ne peut pas appartenir à deux parents.
4. Ajouter les informations et écrire
DocumentInformation contient l'entrée /Title. Une entrée /Info dans les ajouts du trailer référence ce dictionnaire. Le script fournit aussi explicitement la référence /Root vers le catalogue ; les exemples qui placent le catalogue directement dans FileBody peuvent laisser le writer la déterminer.
FileStructure assemble le header PDF 1.7, le corps et ces références. writeTo() parcourt les objets, attribue leurs numéros, écrit les flux et produit la table de références croisées avec les offsets réels. Chaque écriture dispose de son propre contexte d'identité.
Modifier pour comprendre
- Remplacer 18 par 24 augmente la taille de la police.
- Remplacer la position
(72, 720)par(72, 100)descend la phrase vers le bas de la page. - Remplacer
/F1par/TitleFontdans les ressources et dansSetTextFontAndSizeconserve le même rendu. - Modifier le
TextStringdu titre change les propriétés du document, sans changer la phrase imprimée sur la page.
Ce script ne réalise pas de retour à la ligne ni de calcul de marge automatique. Le tutoriel suivant montre explicitement les lignes et l'assemblage de plusieurs pages.
Dans la norme : objets et références (§7.3), structure du fichier (§7.5), catalogue et arbre de pages (§7.7.2-7.7.3), ressources (§7.8.3), espaces de coordonnées (§8.3.2), texte (§9.3-9.4), informations documentaires (§14.3.3).
Index des tutoriels · Suivant : pages et texte
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é : hello-world.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\Syntax\ContentStreamsResources\ContentStreams;
use PDF\Syntax\ContentStreamsResources\Operators\BeginTextObject;
use PDF\Syntax\ContentStreamsResources\Operators\EndTextObject;
use PDF\Syntax\ContentStreamsResources\Operators\MoveTextPosition;
use PDF\Syntax\ContentStreamsResources\Operators\SetTextFontAndSize;
use PDF\Syntax\ContentStreamsResources\Operators\ShowText;
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\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';
// Tutorial 01: docs/tutorials/01-first-document.md.
// This file builds a graph of PDF objects, then delegates writing to the writer.
// 1. Declare a font and give it a local name in page resources.
// /F1 is the name used by Tf; /Helvetica is the standard font name.
// The reference targets the font instance. Its number is assigned during writing.
$helvetica = new Type1Font(StandardFont::Helvetica);
$resources = new ResourceDictionary(
new Font(new DictionaryType(
new Entry(new NameType('F1'), new IndirectReference($helvetica)),
)),
);
// 2. Describe text with PDF instructions: BT, Tf, Td, Tj, ET.
// BT/ET enclose the text object. Tf selects /F1 at 18 units; Td moves
// the text origin to (72, 720). In this page space, y increases upwards
// and a unit defaults to 1/72 inch. Tj receives bytes, here in ASCII.
// ContentStreams retains operators; StreamObject turns them into a PDF stream.
$contents = new StreamObject(new ContentStreams(
new BeginTextObject(),
new SetTextFontAndSize(new NameType('F1'), new NumericType(18)),
new MoveTextPosition(new NumericType(72), new NumericType(720)),
new ShowText(new LiteralString('Super ! Le PDF est construit objet par objet.')),
new EndTextObject(),
));
// 3. Assemble the page: /MediaBox is a rectangle [x_min y_min x_max y_max].
// 612 x 792 corresponds to Letter size. The page connects resources and content.
$page = new PageObject(
new Rectangle(new NumericType(0), new NumericType(0), new NumericType(612), new NumericType(792)),
$resources,
$contents,
);
// 4. Attach the page to the /Pages tree, then connect its root to /Catalog.
// The graph calculates /Parent, /Kids and /Count; they are not filled manually.
$pages = (new PageTreeNode())->addKid($page);
$catalog = new DocumentCatalog($pages);
// 5. Add a document title in /Info. TextString explicitly encodes this Unicode
// value; this string does not represent the glyphs displayed by Tj.
$information = new DocumentInformation(new Title(new TextString('Un PDF entièrement objet')));
$file = new FileStructure(
new FileHeader('1.7'),
new FileBody($catalog),
new IndirectReference($catalog),
new DictionaryType(new Entry(new NameType('Info'), new IndirectReference($information))),
);
// 6. Write the file. The writer traverses references, assigns numbers,
// measures stream bytes and produces the xref table and trailer.
// The first argument selects the destination from the CLI command.
$path = $argv[1] ?? __DIR__ . '/hello-world.pdf';
$file->writeTo($path);
echo $path . PHP_EOL;
