Aller au contenu
getnextpdf.com

Un PDF est un conteneur : fichiers incorporés et données associées

Spec: ISO 32000-2, §7.11.4Spec: ISO 32000-2, §14.13Spec: ISO 19005-3, PDF/A-3

La plupart des gens imaginent un PDF comme une pile de pages. C’est la partie que tu vois. Mais un PDF est aussi un conteneur, et il peut transporter d’autres fichiers entiers en son sein — un tableur, une charge utile XML, le document source d’origine — regroupés dans le même fichier unique que tu remets à quelqu’un d’autre.

Cette page explique comment cela fonctionne : le flux de fichier incorporé qui stocke les octets, l’arbre de noms qui les répertorie, et la seule clé qui décide si une pièce jointe est simplement posée là ou si elle signifie réellement quelque chose.

Une pièce jointe non typée et une pièce jointe typée sont identiques pour un humain. Toutes deux sont un fichier voyageant à l’intérieur d’un PDF et — dans ce moteur — toutes deux sont associées au document. La différence, c’est que l’une d’elles indique à une machine à quoi elle sert, tandis que l’autre laisse la relation vierge pour que la machine devine.

Cette différence est tout l’enjeu pour une facture électronique hybride. Une plateforme fiscale ne lit pas la page de ta facture ; elle lit le XML que tu as incorporé. Si ce XML est joint comme un bloc indifférencié plutôt que comme les données de facture du document visible, un lecteur conforme n’a aucun moyen fiable de savoir que c’est la charge utile à traiter. La page a l’air parfaite. La facture est rejetée. L’échec arrive des jours plus tard, avec un paiement bloqué derrière lui.

Bien établir la relation, dans la couche qui produit le fichier, revient bien moins cher que de la découvrir une facture rejetée à la fois.

  • Un PDF peut incorporer les octets de n’importe quel fichier sous forme de flux de fichier incorporé (Spec: ISO 32000-2, §7.11.4). Le flux porte les données plus un petit dictionnaire de paramètres : taille d’origine, dates et somme de contrôle.
  • Les fichiers incorporés sont catalogués dans l’arbre de noms EmbeddedFiles, de sorte qu’un lecteur peut les énumérer par nom sans parcourir tout le document.
  • Un fichier associé va un pas plus loin : il déclare une AFRelationship (Spec: ISO 32000-2, §7.11.3) — l’une de huit valeurs standard (Source, Data, Alternative, Supplement, EncryptedPayload, FormData, Schema, Unspecified), ou une valeur personnalisée — précisant comment le fichier se rapporte au contenu auquel il est joint.
  • Cette relation typée est le mécanisme derrière les factures électroniques hybrides (ZUGFeRD / Factur-X) et les pièces jointes PDF/A-3 (Spec: ISO 19005-3, PDF/A-3).
  • NextPDF prend en charge les primitives brutes du conteneur dans le core : embedFile() et embedFileFromString() avec une relation explicite. Les éditions Advanced ajoutent par-dessus ces primitives l’incorporateur de factures électroniques EN 16931 / ZUGFeRD / Factur-X dédié.

Considère cela comme deux couches empilées l’une sur l’autre.

La couche inférieure est le stockage. Un flux de fichier incorporé (Spec: ISO 32000-2, §7.11.4) correspond aux octets du fichier d’origine enveloppés dans un objet flux PDF, avec un dictionnaire de paramètres enregistrant la taille d’origine, la date de modification et une somme de contrôle des données non compressées. On accède au flux via un dictionnaire de spécification de fichier dont le dictionnaire /EF pointe vers le flux de fichier incorporé — le flux lui-même ne porte pas /EF. Un lecteur peut ressortir le fichier octet pour octet. Pour rendre ces fichiers repérables, le catalogue du document détient un arbre de noms EmbeddedFiles — une carte triée d’un nom vers chaque spécification de fichier — afin qu’un lecteur puisse lister « voici les 3 fichiers à l’intérieur de ce PDF » sans parcourir chaque page.

La couche supérieure est le sens. À elle seule, une pièce incorporée est simplement présente. Le mécanisme des fichiers associés (Spec: ISO 32000-2, §14.13) attache un fichier à quelque chose — le document entier, une page, un objet graphique — et l’estampille d’une AFRelationship. ISO 32000-2 définit un petit vocabulaire de huit valeurs standard (Spec: ISO 32000-2, §7.11.3), et permet aussi des valeurs personnalisées ; chaque valeur standard répond à une question précise :

AFRelationshipCe qu’elle affirme à propos du fichier
SourceC’est le matériau source à partir duquel le contenu visible a été généré (par exemple, le document de traitement de texte d’origine).
DataCe sont des données structurées liées au contenu visible — le cas canonique étant le XML de facture derrière une page de facture rendue.
AlternativeC’est une représentation alternative du même contenu (par exemple, une version audio ou vidéo).
SupplementC’est un matériau supplémentaire qui étend le contenu mais n’en fait pas partie.
EncryptedPayloadLe fichier incorporé est une charge utile chiffrée que le PDF enveloppe comme un bloc opaque.
FormDataLe fichier est constitué de données de formulaire (FDF, XFDF ou une charge utile de formulaire XML).
SchemaLe fichier est un schéma décrivant la structure d’un fichier Data (par exemple, un XSD pour des données XML ou un JSON Schema).
UnspecifiedLa relation n’est délibérément pas indiquée. Honnête, mais cela ne dit rien à une machine.

Au-delà de ces huit, la norme permet également des valeurs de relation personnalisées spécifiques à l’application, de sorte que le vocabulaire est extensible plutôt que figé.

Un fichier associé est défini par deux choses qui fonctionnent ensemble, pas une clé seule. L’association /AF lie la spécification de fichier à une partie du document ; la clé AFRelationship dans la spécification de fichier énonce alors la relation sémantique. L’entrée /AF au point d’association (le catalogue du document, une page ou un objet) est un tableau — ce tableau contient une ou plusieurs spécifications de fichier, généralement sous forme de références indirectes ; /AF n’est pas une référence unique. Un fichier associé au niveau document est la spécification de fichier listée dans le tableau /AF du catalogue du document, portant son AFRelationship. Marque ce tableur Unspecified et tu l’as associé au document mais tu n’as rien dit à une machine sur le pourquoi. Marque le même tableur Data et tu as dit à chaque lecteur conforme ce qu’il est et à quoi il sert. Les octets sont les mêmes. La sémantique, non.

C’est pourquoi le cas de la facture électronique n’est pas « joins un fichier XML ». C’est « incorpore ce XML comme le fichier associé Data de ce document, à l’intérieur d’un porteur PDF/A-3 conforme » — la validité de la facture et l’acceptation légale demeurant des contrôles distincts que le porteur n’effectue pas. Le flux comporte quatre étapes, et c’est l’ordre qui le maintient correct.

  1. Store the bytesThe file is wrapped in an embedded file stream with its size, dates, and a checksum (ISO 32000-2 §7.11.4).
  2. Register it by nameThe file specification is added to the EmbeddedFiles name tree so a reader can enumerate attachments without scanning the document.
  3. Declare the relationshipAn AFRelationship value (one of the eight standard values such as Source or Data) marks how the file relates to the content, associated at the document level (ISO 32000-2 §14.13.3).
  4. Make it archivalA PDF/A-3 carrier permits the embedded payload to ride inside one conforming archival PDF/A document; invoice validity and legal acceptance remain separate checks (ISO 19005-3).
Comment une pièce jointe typée devient un fichier hybride de bout en bout : le moteur stocke les octets, enregistre le fichier par nom, déclare la relation, et le profil d'archivage permet à le tout de voyager à l'intérieur d'un seul document d'archivage conforme.

Cette quatrième étape explique pourquoi PDF/A-3 existe comme profil distinct. Les profils d’archivage antérieurs restreignaient ce qui pouvait être incorporé ; PDF/A-3 (Spec: ISO 19005-3, PDF/A-3) est la partie qui permet à des fichiers de n’importe quel format de voyager à l’intérieur d’un document d’archivage conforme. Il permet la charge utile incorporée — il ne valide pas cette charge utile et ne confère pas de statut légal. Sans lui, la facture hybride — un fichier qui est à la fois la page qu’une personne lit et les données qu’un système fiscal analyse — ne pourrait pas être du tout un document d’archivage PDF/A conforme ; savoir si la facture est valide et légalement acceptée demeure une question distincte. L’incorporateur de factures électroniques dédié que les éditions Advanced ajoutent est la couture de commodité par-dessus exactement cela : il incorpore la charge utile, fixe la relation à Data et l’enregistre correctement, pour que tu n’assembles pas à la main la plomberie du conteneur. Les mécaniques plus profondes de la facturation et de l’archivage vivent sur les deux pages voisines liées ci-dessous ; cette page porte sur le conteneur sur lequel elles reposent toutes deux.

Un petit programme complet. Les deux appels qui comptent sont la différence entre un fichier associé non typé et un fichier associé typé — et la relation est un argument explicite que tu devrais définir. Dans ce moteur, les deux appels produisent un fichier associé : embedFile() et embedFileFromString() enregistrent toujours la spécification de fichier dans le tableau /AF du catalogue du document, de sorte que la seule chose que la relation change est ce que l’association signifie. Elle a pour valeur par défaut Unspecified, qui associe le fichier mais ne dit rien à une machine sur le pourquoi ; pour une charge utile de facture électronique, tu la fixes à Data afin qu’un lecteur puisse la trouver.

<?php
declare(strict_types=1);
use NextPDF\Core\Document;
use NextPDF\Navigation\AFRelationship;
$document = Document::createStandalone();
$document->addPage();
$document->setFont('helvetica', 'B', 16);
$document->cell(0, 12, 'Invoice INV-2026-0042', newLine: true);
// An UNTYPED associated file: the bytes are embedded AND the file spec is
// added to the document catalog's /AF array, but the relationship says
// nothing about why. A reader can open it; a machine cannot tell its role.
// The relationship is left Unspecified (its default); the second argument is
// the human-readable description. embedFile accepts the AFRelationship enum.
$document->embedFile(
'/srv/invoices/INV-2026-0042-source.docx',
'Original source document',
AFRelationship::Unspecified,
);
// A TYPED associated file: the invoice XML is declared as the DATA behind
// the visible page. This is the relationship a hybrid e-invoice reader
// looks for — the same intent the dedicated e-invoice embedder sets.
// embedFileFromString takes the data, a filename, a description, and a
// relationship as a PDF-name string ('/Data').
$invoiceXml = $generateCiiXml(); // your ERP authors this; the engine never does
$document->embedFileFromString(
$invoiceXml,
'factur-x.xml',
'Factur-X invoice data',
'/Data',
);
$bytes = $document->getPdfData();

La relation '/Data' est sans équivoque. La première pièce jointe — laissée Unspecified — est tout de même associée, simplement sans signification énoncée. Pour les deux appels, le moteur écrit le flux de fichier incorporé, ajoute le fichier à l’arbre de noms EmbeddedFiles, liste sa spécification de fichier dans le tableau /AF du catalogue du document et enregistre la relation que tu as énoncée — il n’en choisit pas une à ta place. Ce moteur n’a aucun mode arbre-de-noms-seul : chaque fichier que tu incorpores de cette manière est un fichier associé au document, de sorte que la relation est le seul levier que tu contrôles.

L’hypothèse fréquente est que « incorporé » et « associé » sont deux mots pour la même chose. Ils ne le sont pas. Incorporé concerne le stockage — les octets sont à l’intérieur du PDF. Associé concerne la liaison — la spécification de fichier est listée dans un tableau /AF sur une partie du document, et elle porte une AFRelationship. Dans le modèle PDF abstrait, un fichier peut être incorporé dans l’arbre de noms sans jamais être associé ; le chemin embedFile() de NextPDF ne le laisse pas ainsi — il écrit toujours l’association /AF — de sorte que, pour ce moteur, la question ouverte n’est jamais si un fichier est associé mais ce que la relation dit.

Un second piège : supposer qu’un lecteur va « deviner » quelle pièce jointe est la facture. Un lecteur conforme n’est pas censé deviner. Il cherche le fichier dont la relation dit Data. Laisse la relation Unspecified et tu as associé la charge utile tout en ne disant à la machine rien d’utile sur son rôle.

Le mécanisme de conteneur est puissant d’une manière qui mérite d’être dite honnêtement : embedFile() lit n’importe quel chemin que le processus PHP peut lire. C’est la fonctionnalité — et c’est aussi la frontière. Le moteur attache les octets qu’on lui donne ; il ne décide pas, et ne peut pas décider, à ta place si un chemin est un chemin que tu avais l’intention d’exposer.

Embedding a file from a caller-supplied path — edition availability
EditionAvailability
Core

embedFile() reads any path the PHP process has access to and embeds its bytes verbatim. Validating that the path is safe and intended — not a user-controlled value, a traversal, or a secret outside the document’s scope — is the integrator’s responsibility. This is a documented security contract, not an oversight: the engine will not silently guess which paths are legitimate, because that guess belongs to your application, which knows the trust boundary the engine cannot see. Pass attacker-influenced bytes through a string with embedFileFromString() so the path layer is never in play.

ProNot in this edition
EnterpriseNot in this edition

Deux limites supplémentaires méritent d’être énoncées clairement :

  • Incorporer n’est pas valider. Le moteur transporte les octets que tu lui donnes. Savoir si le XML incorporé est une charge utile de facture conforme est une question distincte, à laquelle répond un validateur — vois la page de facturation.
  • Une pièce jointe typée n’est pas, à elle seule, un fichier d’archivage conforme. Faire de la facture hybride un document PDF/A-3 légal nécessite le mode d’archivage et un contrôle de conformité indépendant — vois la page d’archivage.
  • Factures et facturation électronique — le cas d’usage que ce mécanisme rend possible : un PDF hybride qui transporte une facture lisible par machine comme son fichier associé Data.
  • Archivage et PDF/A — pourquoi le porteur est un fichier PDF/A-3 et ce que la conformité promet et ne promet pas.
  • L’anatomie d’un fichier PDF — où se situent l’arbre de noms et le catalogue du document dans la structure du fichier.
  • Flux et filtres — comment les octets d’un fichier incorporé sont stockés et compressés à l’intérieur d’un objet flux.
  • Flux de fichier incorporé — un objet flux PDF contenant les octets d’un fichier externe, avec un dictionnaire de paramètres enregistrant sa taille d’origine, ses dates et une somme de contrôle (ISO 32000-2 §7.11.4).
  • Arbre de noms EmbeddedFiles — la carte triée du catalogue du document qui liste les fichiers incorporés par nom, pour qu’un lecteur puisse énumérer les pièces jointes sans parcourir tout le document.
  • Fichier associé — un fichier incorporé lié à une partie du document par une association /AF (sur le catalogue du document, une page ou un objet) et portant une AFRelationship qui énonce comment il se rapporte à ce contenu ; le cas au niveau document — la spécification de fichier dans le tableau /AF du catalogue — est celui sur lequel cette page se concentre (ISO 32000-2 §14.13.3).
  • AFRelationship — la clé de spécification de fichier dont la valeur nomme la relation (ISO 32000-2 §7.11.3). Elle prend l’une de huit valeurs standard (Source, Data, Alternative, Supplement, EncryptedPayload, FormData, Schema, Unspecified) ou une valeur personnalisée ; Data est la valeur qu’utilise une charge utile de facture électronique hybride.
  • PDF/A-3 — le profil d’archivage ISO 19005-3 qui permet d’incorporer des fichiers de n’importe quel format, rendant possible un document hybride conforme.
  • Facture hybride — un seul fichier PDF qui est à la fois une page lisible par un humain et une charge utile de facture incorporée lisible par machine.