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.

Résultat PDF pdf20-metadata-declarations : page 1 sur 1
Page 1 sur 1
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;

Source de ce guide au commit documenté ↗