Le script complet génère une page PDF 2.0 avec police incorporée, métadonnées Unicode et CSV incorporé. Le CSV est associé au document et à la page ; une liste de propriétés MCAF nommée relie le contenu marqué au fichier. Ce sont de véritables objets PDF, pas seulement un nom de fichier dessiné sur la page.

Exécuter l'exemple

Depuis un checkout du core 0.6.1 avec ses dépendances de développement verrouillées :

php examples/pdf20-associated-files.php pdf20-associated-files.pdf

PHP 8.3 ou supérieur et le module optionnel pdftoolkit/pdf-fonts sont utilisés. Le checkout installe ce module pour les exemples ; l'installation normale du core ne le fait pas. La construction exécute le script et rend ses pages. L'aperçu ci-dessous provient de ce PDF réel ; le télécharger permet d'inspecter les fichiers associés dans un lecteur compatible.

Encoder séparément le texte visible et les métadonnées

Les métadonnées de dictionnaire utilisent TextString avec Utf8TextStringEncoding, qui écrit un BOM UTF-8 avant la valeur encodée. Ce mécanisme de chaîne textuelle appartient à PDF 2.0. L’exemple conserve l’entrée dépréciée Info.Title uniquement pour démontrer cet encodage et ne prétend pas garantir une conformité PDF 2.0 complète. Il permet des métadonnées hors PDFDocEncoding sans supposer que leurs octets UTF-8 sont déjà des codes utilisables par une police.

Le texte visible des pages suit une autre construction. L'exemple lit la police de démonstration Noto Sans avec le convertisseur optionnel, incorpore son programme PDF, sélectionne une ressource de police et fournit des codes CID aux opérateurs de texte. La CMap ToUnicode relie ces codes à Unicode. Ces objets restent nécessaires même lorsque les métadonnées du fichier utilisent UTF-8.

Fournir et décrire le CSV

L'application fournit les octets CSV à EmbeddedFileStream, avec le nom MIME text/csv et la date source 2026-10-14T00:00:00Z. Son nom PDF est F (donnees.csv) avec le nom Unicode préféré UF, données.csv. EF.F et EF.UF référencent le même flux. EmbeddedFileBindings relie une référence indirecte du flux au dictionnaire EF de la spécification de fichier. Le nom et la description restent des valeurs PDF explicites ; le core ne charge pas de fichier externe et n'interprète pas ses lignes CSV.

AssociatedFileRelationship ajoute AFRelationship /Data, pour déclarer le rapport entre le CSV et la présentation PDF. La relation exprime une sémantique, pas une instruction d'exécution du fichier. La taille et la somme de contrôle décrivent les octets sans certifier leur origine ni leur innocuité.

Relier le document, la page et le contenu marqué

AssociatedFiles contient les spécifications de fichiers et AssociatedFilesEntry écrit /AF sur le catalogue et la page. Ces propriétaires réutilisent la même spécification au lieu de fabriquer des copies indépendantes de la pièce jointe. Un arbre de noms peut rendre le fichier accessible séparément de son association avec ces propriétaires.

L'association du contenu marqué utilise un dictionnaire nommé dans Resources.Properties contenant /MCAF sous AFData, puis /AF /AFData BDC … EMC dans le flux de contenu. Le modèle EC3 corrigé maintient la liste de propriétés comme dictionnaire et garde les références indirectes de flux hors de la syntaxe de contenu. Un tableau direct après BDC, une association vide ou un fichier sans AFRelationship échouent à la validation native des associations prises en charge ; DP/MP ne peuvent pas remplacer la séquence.

Choisir le catalogue PDF 2.0

L'exemple utilise le schéma CatalogDictionaryTable29 propre à l'édition et un FileHeader PDF 2.0 explicite. Le catalogue exige Type /Catalog et sa référence vers l'arbre des pages ; l'édition sélectionnée conserve EC3 séparément de la version produite. Les métadonnées UTF-8 et AF/MCAF nécessitent PDF 2.0 et sont contrôlés selon la version effective, y compris les déclarations du catalogue et les objets effectifs des révisions.

Le tableau 29 ISO1 décrit un nœud de pages ; il ne doit pas remplacer ce catalogue ISO2. Un nouvel en-tête seul n'implémente pas toutes les règles PDF 2.0.

Inspecter le PDF et garder les limites visibles

Ouvrir le document téléchargé dans un lecteur gérant les fichiers incorporés et associés. Inspecter /AF du catalogue et de la page, AFRelationship /Data, les octets du flux incorporé, le dictionnaire nommé MCAF, l'en-tête PDF 2.0 et les métadonnées UTF-8. Comparer le texte visible avec la police incorporée et la correspondance ToUnicode. Les contrôles indépendants examinent ces octets et références, pas seulement le succès d'une commande de génération.

Le package optionnel pdf-reader documente toujours une lecture PDF 1.0–1.7. L'exemple est validé par des outils externes pour PDF 2.0. Cette leçon ne chiffre pas le fichier et n'implémente pas R=6, les espaces de noms natifs, DSS/horodatages, parties documentaires ni la conformité ISO 32000-2 complète.

Références

Le guide PDF 2.0 explique les éditions, l'applicabilité et les mécanismes restants. Pages physiques de source : chaînes textuelles 131–132 ; catalogue 113–117 ; fichiers 149–152 ; fichiers associés 853–856 ; tableau MCAF 409a corrigé 1015. La copie fournie est privée ; utiliser l'accès sponsorisé officiel et l'erratum public 374.

Parcours des tutoriels · Couverture

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-associated-files : 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-associated-files.php
<?php

declare(strict_types=1);

use PDF\Document\Files\AFRelationship;
use PDF\Document\Files\AssociatedFileRelationship;
use PDF\Document\Files\AssociatedFiles;
use PDF\Document\Files\AssociatedFilesPropertyList;
use PDF\Document\Files\BeginAssociatedFilesMarkedContent;
use PDF\Document\Files\EmbeddedFileBinding;
use PDF\Document\Files\EmbeddedFileBindings;
use PDF\Document\Files\EmbeddedFileKey;
use PDF\Document\Files\EmbeddedFileStream;
use PDF\Document\Files\FileSpecification;
use PDF\Document\Files\FileSpecificationOptions;
use PDF\Document\Files\FileSpecificationName;
use PDF\Document\Fonts\CIDFontType2;
use PDF\Document\Fonts\CIDToGIDMap;
use PDF\Document\Fonts\CIDWidths;
use PDF\Document\Fonts\FontDescriptorFlags;
use PDF\Document\Fonts\FontFile2;
use PDF\Document\Fonts\ToUnicodeCMap;
use PDF\Document\Fonts\Type0Font;
use PDF\Document\ResourceDictionary;
use PDF\Document\Resources\Font;
use PDF\Document\Resources\Properties;
use PDF\FileStructure;
use PDF\Fonts\TrueType\CharacterMapFactory;
use PDF\Fonts\TrueType\CIDFontProgram;
use PDF\Fonts\TrueType\FontDescriptorFactory;
use PDF\Fonts\TrueType\FontProgram;
use PDF\Fonts\TrueType\TextEncoder;
use PDF\Fonts\TrueType\UnicodeText;
use PDF\Specification\Iso32000_2\Dictionary\CatalogDictionaryTable29;
use PDF\Specification\Iso32000_2\Dictionary\CatalogDictionaryTable29\AFEntry;
use PDF\Specification\Iso32000_2\Dictionary\CatalogDictionaryTable29\PagesEntry;
use PDF\Specification\Iso32000_2\Dictionary\CatalogDictionaryTable29\TypeEntry;
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\DataStructure\Utf8TextStringEncoding;
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\FileStructure\PdfVersion;
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\NameType;
use PDF\Syntax\Object\NumericType;
use PDF\Syntax\Object\StreamObject;
use PDF\Syntax\Object\StringObject\LiteralString;

require dirname(__DIR__) . '/vendor/autoload.php';

// Text strings in metadata and filenames use PDF2's UTF8 BOM. This is not a
// font encoding: text-showing operators below still receive explicit CID codes.
$utf8 = new Utf8TextStringEncoding();
$title = new TextString('PDF 2.0 : fichiers associés', $utf8);
$csv = "produit,quantite,prix_unitaire\nPapier,2,3.50\nEnveloppes,4,1.20\n";
$payload = new EmbeddedFileStream(
    $csv,
    new NameType('text/csv'),
    modificationDate: new Date(new DateTimeImmutable('2026-10-14T00:00:00+00:00')),
    filters: new FlateDecode(),
);
$specification = new FileSpecification(
    new LiteralString('donnees.csv'),
    new FileSpecificationOptions(
        embedded: new EmbeddedFileBindings(new EmbeddedFileBinding(EmbeddedFileKey::File, $payload), new EmbeddedFileBinding(EmbeddedFileKey::UnicodeFile, $payload)),
        description: new TextString('Données source de cette page.', $utf8),
        additionalNames: new FileSpecificationName(EmbeddedFileKey::UnicodeFile, new TextString('données.csv', $utf8)),
    ),
    new AssociatedFileRelationship(AFRelationship::Data),
);
// The same indirect identity is associated with both the document and its page.
// /AF expresses a relationship; it is distinct from the EmbeddedFiles name tree.
$files = new AssociatedFiles(new IndirectReference($specification));

$lines = [
    new UnicodeText('PDF 2.0 : fichiers associés'),
    new UnicodeText('Un fichier CSV accompagne ce document et cette page.'),
    new UnicodeText('Sa relation est Data ; ses octets et sa date sont explicites.'),
    new UnicodeText('Les chaînes de métadonnées utilisent un UTF-8 avec marqueur.'),
    new UnicodeText('Le texte visible utilise une police incorporée et une CMap.'),
];
// Font conversion remains optional and outside the core. Its distributed fixture
// supplies a complete font program; each PDF font/CID/Unicode object is explicit.
$fontBytes = file_get_contents(dirname(__DIR__) . '/vendor/pdftoolkit/pdf-fonts/examples/assets/fonts/NotoSans-Regular.ttf');
if ($fontBytes === false) {
    throw new RuntimeException('Unable to read the bundled demonstration font.');
}
$program = new CIDFontProgram(new FontProgram($fontBytes));
$fontFile = new FontFile2($program->bytes, new FlateDecode());
$repertoire = new UnicodeText(implode('', array_map(static fn (UnicodeText $line): string => $line->utf8, $lines)));
$map = (new CharacterMapFactory($program))->create($fontFile, $repertoire);
$encoder = new TextEncoder($map);
$descriptor = (new FontDescriptorFactory($program))->create($fontFile, new FontDescriptorFlags(4), new NumericType(80));
$descendant = new CIDFontType2($descriptor, new CIDToGIDMap($map, new FlateDecode()), new CIDWidths($map));
$font = new Type0Font($descendant, new ToUnicodeCMap($map, new FlateDecode()));
// MCAF associates a content sequence through a named property dictionary.
$resources = new ResourceDictionary(
    new Font(new DictionaryType(new Entry(new NameType('F1'), new IndirectReference($font)))),
    new Properties(new DictionaryType(new Entry(new NameType('AFData'), new AssociatedFilesPropertyList($files)))),
);
$operators = [new BeginAssociatedFilesMarkedContent(new NameType('AFData')), new Op\BeginTextObject(), new Op\SetTextFontAndSize(new NameType('F1'), new NumericType(20)), new Op\MoveTextPosition(new NumericType(48), new NumericType(748))];
foreach ($lines as $index => $line) {
    if ($index !== 0) {
        $operators[] = new Op\SetTextFontAndSize(new NameType('F1'), new NumericType(12));
        $operators[] = new Op\MoveTextPosition(new NumericType(0), new NumericType($index === 1 ? -42 : -28));
    }
    $operators[] = new Op\ShowText($encoder->encode($line));
}
$operators[] = new Op\EndTextObject();
$operators[] = new Op\EndMarkedContent();
$page = new PageObject(
    new Rectangle(new NumericType(0), new NumericType(0), new NumericType(595), new NumericType(842)),
    $resources,
    new StreamObject(new ContentStreams(...$operators)),
    new Entry(new NameType('AF'), $files),
);
$pages = (new PageTreeNode())->addKid($page);
// This definition belongs to ISO32000-2 Table29, separately from ISO32000-1 Table28.
$catalog = new CatalogDictionaryTable29(new TypeEntry(new NameType('Catalog')), new PagesEntry(new IndirectReference($pages)), new AFEntry($files));
// Info is retained for a simple UTF8 string demonstration. It is deprecated in
// PDF2.0; this example does not claim complete PDF2.0 or archival-profile conformance.
$info = new DictionaryType(new Entry(new NameType('Title'), $title));
$file = new FileStructure(new FileHeader(PdfVersion::PDF20), new FileBody($catalog), trailerEntries: new DictionaryType(new Entry(new NameType('Info'), new IndirectReference($info))));
$path = $argv[1] ?? __DIR__ . '/pdf20-associated-files.pdf';
$file->writeTo($path);
echo $path . PHP_EOL;

Source de ce guide au commit documenté ↗