Le script complet produit un PDF 2.0 avec un titre bilingue, une déclaration de l'auteur, un rapport incorporé et des métadonnées distinctes pour le document, une partie documentaire et une séquence de dessin. Le rectangle coloré utilise la compensation du point noir. Ces informations sont écrites dans les objets PDF correspondants ; elles ne se limitent pas aux phrases visibles sur la page.
Exécuter l'exemple
Depuis un checkout du core 0.7.1, avec ses dépendances installées :
php examples/pdf20-metadata-declarations.php metadata.pdf
L’exemple fournit Courier avec des métriques explicites : FirstChar/LastChar, largeurs ASCII et FontDescriptor. PDF 2.0 ne permet plus d’omettre ces entrées pour les quatorze polices standard. Cette construction n’incorpore pas de programme de fonte ; un profil exigeant l’incorporation demande une fonte explicitement incorporée.
PHP 8.3 ou supérieur suffit. Ce script utilise les objets natifs du core ; aucun convertisseur d'images ou de fontes n'est nécessaire. L'aperçu et le téléchargement ci-dessous proviennent du fichier réellement généré.
Construire un paquet XMP avec des valeurs explicites
XmpPacket sérialise des propriétés RDF dans un paquet XML UTF-8. XmpName
associe séparément préfixe, URI d'espace de noms et nom local ; XmlText contrôle
les caractères XML et échappe le texte. Le BOM UTF-8 du marqueur XMP ne transforme
pas ce paquet en chaîne textuelle PDF : il s'agit du contenu d'un flux XML.
$title = new XmpLanguageAlternative(
new XmpName('dc', new MetadataUri('http://purl.org/dc/elements/1.1/'), 'title'),
new XmlText('PDF 2.0 metadata and document parts'),
new XmpLocalizedText('fr', new XmlText('Métadonnées PDF 2.0 et parties de document')),
);
$metadata = new MetadataStream(new XmpPacket($title, $review));
$catalogMetadata = new MetadataEntry($metadata);
XmpLanguageAlternative écrit un rdf:Alt dont x-default est la première
valeur. Les langues sont distinctes, y compris lorsque leur casse diffère.
MetadataStream fournit /Type /Metadata et /Subtype /XML, puis
MetadataEntry établit sa référence indirecte. Les flux XML existent depuis
PDF 1.4 ; le fichier de cet exemple choisit explicitement PDF 2.0 pour ses autres
mécanismes. Les propriétés ou préfixes contradictoires sont refusés.
Déclarer une propriété du document sans inventer une certification
Une Declaration contient l'URI de la spécification ou du profil revendiqué.
Declarations écrit le tableau non ordonné pdfd:declarations, dans l'espace de
noms exact http://pdfa.org/declarations/. Chaque déclaration peut contenir un
tableau non ordonné de ClaimData : auteur de la revendication, date explicite,
qualifications et URL du rapport.
$review = new Declarations(new Declaration(
new MetadataUri('https://example.org/pdf/reviewed'),
new ClaimData(
new XmlText('PDFToolkit example'),
new ClaimDate('2026-10-14'),
claimReport: new ClaimReport('#ef=review.txt'),
),
));
L'identifiant example.org décrit ici un processus de démonstration. La présence
de cette déclaration ne prouve pas une conformité PDF/A, PDF/UA ou WTPDF. Le core
n'ajoute aucune revendication de ce type. DeclarationsPdfaExtensionSchema permet
de fournir explicitement la description du vocabulaire exigée dans un contexte
PDF/A ; cette description ne rend pas, à elle seule, un document conforme à PDF/A.
Le fragment #ef=review.txt désigne un rapport incorporé par son nom. L'exemple
fournit ses octets, son type MIME et sa date source, l'ajoute à l'arbre
Names.EmbeddedFiles et l'associe avec AFRelationship /Supplement. Le rapport
devient ainsi accessible aux lecteurs qui affichent les pièces jointes. Les URI
et les noms de fichiers ne déclenchent aucun chargement externe.
Donner à chaque métadonnée son propriétaire
La déclaration du catalogue porte sur le document ; elle ne s'étend pas à ses fichiers incorporés ou référencés. Le flux XMP d'une partie documentaire décrit cette partie, tandis qu'un autre flux décrit uniquement la séquence du rectangle. Les trois flux gardent des identités et des vocabulaires distincts.
La séquence de dessin utilise une MetadataPropertyList sous
Resources.Properties.ColourInfo, puis /Sample /ColourInfo BDC ... EMC.
La liste est nommée pour que sa référence indirecte vers le flux XML reste hors
de la syntaxe du contenu. Une propriété Metadata placée directement dans un
opérande de contenu est refusée. Les références sont résolues dans les ressources
du vrai propriétaire, y compris lorsque le contenu appartient à une révision
déjà émise.
MetadataResourceLocation exprime aussi les emplacements précisés par la note
d'application : programme de fonte incorporé plutôt que descripteur de fonte,
flux de motif répétitif, dictionnaire de shading plutôt que son enveloppe de
motif, élément de structure, annotation, groupe de contenu optionnel ou flux 3D.
Le choix appartient à l'application qui connaît la ressource décrite.
Séparer DPM, parties de document et état graphique
DocumentPart porte ici un dictionnaire DocumentPartMetadata avec Job
(Proof 42) et un flux XMP sous Metadata. DPM et XMP sont deux mécanismes
distincts. DPM contient des données applicatives typées ; ses clés et les noms
de niveaux suivent la syntaxe XML Name. Ses valeurs sont des chaînes textuelles
ou dates, tableaux, dictionnaires, booléens et nombres.
DocumentPartRoot relie le catalogue au nœud. La page référence sa partie via
DocumentPartEntry, puis setPageRange() définit les bornes de cette même page.
Le constructeur d'arbre gère les parents ; la validation du graphe contrôle les
liens réciproques, l'ordre des pages, l'absence de chevauchement, la couverture
des pages et les niveaux déclarés. DPM n'est pas hérité implicitement d'un parent.
Enfin, UseBlackPointCompensation(BlackPointCompensation::On) écrit
UseBlackPtComp /ON dans une ressource GraphicsStateParameters, sélectionnée
par gs. Les deux autres valeurs exactes sont /OFF et /Default. La couleur
du rectangle appartient à un espace CalRGB ; la compensation intervient dans
les conversions de couleur CIE réalisées par le processeur PDF 2.0. Son apparence
dépend du profil de sortie et du lecteur, et /Default leur laisse le choix.
Inspecter les preuves et les sources
tools/validate_pdf20_metadata.py vérifie les XML avec un parseur indépendant,
les tableaux RDF, les identités AF et DPart, les types MIME et dates source,
l'état BPC et les opérateurs du contenu avec pypdf. qpdf contrôle le fichier.
Les tests natifs couvrent notamment les caractères XML invalides, les dates
impossibles, les propriétés manquantes et les remplacements incrémentaux
incohérents. Le rendu permet de vérifier séparément la page visible.
Le constructeur de paquets n'analyse aucun XML fourni par l'utilisateur. Une application qui construit manuellement un flux XML brut reste responsable de son contenu ; le graphe contrôle ses références et son type PDF. Cette leçon ne constitue pas une validation générale de toutes les règles ISO 32000-2.
Sources conservées : PDF Declarations, pages 7–10, compensation du point noir, page 4, fichiers associés, pages 7 et 12–13 et emplacements des métadonnées, pages 4–10. Les parties de document sont définies dans ISO 32000-2, pages physiques 850–853. La provenance du bundle est l'accès sponsorisé de la PDF Association.
Parcours des tutoriels · Guide PDF 2.0
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é : pdf20-metadata-declarations.php
<?php
declare(strict_types=1);
use PDF\Document\Files as Files;
use PDF\Document\Metadata as Metadata;
use PDF\Document\Metadata\Declarations as Declarations;
use PDF\Document\Parts as Parts;
use PDF\Document\ResourceDictionary;
use PDF\Document\Resources;
use PDF\FileStructure;
use PDF\Graphics\ColourSpaces\CalRGB;
use PDF\Graphics\ColourSpaces\WhitePoint;
use PDF\Graphics\GraphicsState\BlackPointCompensation;
use PDF\Graphics\GraphicsState\GraphicsStateParameters;
use PDF\Graphics\GraphicsState\UseBlackPointCompensation;
use PDF\Syntax\ContentStreamsResources\ContentStreams;
use PDF\Syntax\ContentStreamsResources\Operators as Op;
use PDF\Syntax\DataStructure\Date;
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\Filters\FlateDecode;
use PDF\Syntax\Object\DictionaryObject\Entry;
use PDF\Syntax\Object\DictionaryType;
use PDF\Syntax\Object\IndirectReference;
use PDF\Syntax\Object\IntegerType;
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';
// PDF Declarations are explicit author assertions. This demonstration identifier
// asserts a local review workflow; it does not claim PDF/A, PDF/UA or WTPDF conformance.
$review = new Declarations\Declarations(new Declarations\Declaration(
new Metadata\MetadataUri('https://example.org/pdf/reviewed'),
new Declarations\ClaimData(new Metadata\XmlText('PDFToolkit example'), new Declarations\ClaimDate('2026-10-14'), claimReport: new Declarations\ClaimReport('#ef=review.txt')),
));
$title = new Metadata\XmpLanguageAlternative(new Metadata\XmpName('dc', new Metadata\MetadataUri('http://purl.org/dc/elements/1.1/'), 'title'), new Metadata\XmlText('PDF 2.0 metadata and document parts'), new Metadata\XmpLocalizedText('fr', new Metadata\XmlText('Métadonnées PDF 2.0 et parties de document')));
$documentMetadata = new Metadata\MetadataStream(new Metadata\XmpPacket($title, $review));
// Object-level declarations remain separate: the catalog claim does not extend
// to this embedded report. Names/EmbeddedFiles makes the report easy to discover.
$report = new Files\EmbeddedFileStream("Example review report. This is not a certification.\n", new NameType('text/plain'), modificationDate: new Date(new DateTimeImmutable('2026-10-14T00:00:00Z')), filters: new FlateDecode());
$reportFile = new Files\FileSpecification(new LiteralString('review.txt'), new Files\FileSpecificationOptions(embedded: new Files\EmbeddedFileBindings(new Files\EmbeddedFileBinding(Files\EmbeddedFileKey::File, $report))), new Files\AssociatedFileRelationship(Files\AFRelationship::Supplement));
$attachments = new Files\EmbeddedFiles(new Files\EmbeddedFileAttachment(new LiteralString('review.txt'), $reportFile));
$associated = new Files\AssociatedFiles(new IndirectReference($reportFile));
// DPM is a typed PDF dictionary describing a workflow. Metadata is a different,
// XMP stream entry. Both may coexist on the same document-part node.
$part = new Parts\DocumentPart(new Parts\DocumentPartMetadata(new Entry(new NameType('Job'), new TextString('Proof 42'))), new Metadata\MetadataEntry(new Metadata\MetadataStream(new Metadata\XmpPacket(new Metadata\XmpTextProperty(new Metadata\XmpName('job', new Metadata\MetadataUri('urn:example:job'), 'description'), new Metadata\XmlText('One-page proof'))))), new Files\AssociatedFilesEntry($associated));
$partRoot = new Parts\DocumentPartRoot($part, new IntegerType(0), new NameType('Proof'));
$sequenceMetadata = new Metadata\MetadataStream(new Metadata\XmpPacket(new Metadata\XmpTextProperty(new Metadata\XmpName('sample', new Metadata\MetadataUri('urn:example:sample'), 'purpose'), new Metadata\XmlText('Calibrated colour demonstration'))));
$state = new GraphicsStateParameters(new UseBlackPointCompensation(BlackPointCompensation::On));
// PDF 2.0 requires explicit metrics and a descriptor even for standard fonts.
$font = require __DIR__ . '/fixtures/pdf20-courier-font.php';
$resources = new ResourceDictionary(
new Resources\Font(new DictionaryType(new Entry(new NameType('F1'), new IndirectReference($font)))),
new Resources\ExtGState(new DictionaryType(new Entry(new NameType('BpcOn'), new IndirectReference($state)))),
new Resources\ColorSpace(new DictionaryType(new Entry(new NameType('Calibrated'), new CalRGB(new WhitePoint(new NumericType(0.9505), new NumericType(1), new NumericType(1.089)))))),
new Resources\Properties(new DictionaryType(new Entry(new NameType('ColourInfo'), new Metadata\MetadataPropertyList($sequenceMetadata)))),
);
$operators = [];
foreach (['PDF 2.0: metadata and document parts', 'Catalog XMP: a bilingual title and explicit review declaration.', 'A named property list attaches XMP to this colour sample.', 'The page belongs to one DPart with separate DPM and XMP.', 'The review report is an associated file and a named attachment.', 'BPC is ON for the calibrated colour conversion; its display', 'depends on the PDF processor and its output colour profile.'] as $index => $line) {
$operators[] = new Op\BeginTextObject();
$operators[] = new Op\SetTextFontAndSize(new NameType('F1'), new NumericType($index === 0 ? 20 : 11));
$operators[] = new Op\MoveTextPosition(new NumericType(48), new NumericType(780 - 30 * $index));
$operators[] = new Op\ShowText(new LiteralString($line));
$operators[] = new Op\EndTextObject();
}
$operators = [...$operators, new Op\SaveGraphicsState(), new Op\BeginMarkedContentWithProperties(new NameType('Sample'), new NameType('ColourInfo')), new Op\SetGraphicsStateParameters(new NameType('BpcOn')), new Op\SetNonstrokingColorSpace(new NameType('Calibrated')), new Op\SetNonstrokingColor(new NumericType(0.1), new NumericType(0.3), new NumericType(0.6)), new Op\AppendRectangle(new NumericType(48), new NumericType(440), new NumericType(496), new NumericType(100)), new Op\FillPathNonzero(), new Op\EndMarkedContent(), new Op\RestoreGraphicsState()];
$page = new PageObject(new Rectangle(new NumericType(0), new NumericType(0), new NumericType(595), new NumericType(842)), $resources, new StreamObject(new ContentStreams(...$operators), filters: new FlateDecode()), new Parts\DocumentPartEntry($part));
$pages = (new PageTreeNode())->addKid($page);
$part->setPageRange($page);
$catalog = new DocumentCatalog($pages, new Metadata\MetadataEntry($documentMetadata), $partRoot->catalogEntry(), new Files\AssociatedFilesEntry($associated), new Entry(new NameType('Names'), new DictionaryType($attachments->nameDictionaryEntry())));
$output = $argv[1] ?? __DIR__ . '/pdf20-metadata-declarations.pdf';
(new FileStructure(new FileHeader('2.0'), new FileBody($catalog)))->writeTo($output);
echo $output . PHP_EOL;
