Ga naar inhoud
getnextpdf.com

Een PDF is een container: ingebedde bestanden en bijbehorende gegevens

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

De meeste mensen stellen zich een PDF voor als een stapel pagina’s. Dat is het deel dat je ziet. Maar een PDF is ook een container, en hij kan hele andere bestanden in zich dragen — een spreadsheet, een XML-payload, het oorspronkelijke brondocument — gebundeld in hetzelfde enkele bestand dat je aan iemand anders overhandigt.

Deze pagina legt uit hoe dat werkt: de embedded-file stream die de bytes opslaat, de name tree die ze opsomt, en de ene sleutel die bepaalt of een bijlage daar alleen maar staat of werkelijk iets betekent.

Een ongetypeerde bijlage en een getypeerde zien er voor een mens identiek uit. Beide zijn een bestand dat binnen in een PDF meereist en — in deze engine — zijn beide met het document geassocieerd. Het verschil is dat een van beide een machine vertelt waarvoor het dient, en de andere de relatie blanco laat zodat de machine moet gokken.

Dat verschil is bij een hybride e-factuur de hele inzet. Een belastingplatform leest jouw factuurpagina niet; het leest de XML die je hebt ingebed. Als die XML is bijgevoegd als een ongedifferentieerde blob in plaats van als de factuurgegevens voor het zichtbare document, heeft een conforme reader geen betrouwbare manier om te weten dat het de te verwerken payload is. De pagina ziet er perfect uit. De factuur wordt afgewezen. Het falen komt dagen later, met een geblokkeerde betaling erachter.

De relatie goed krijgen, in de laag die het bestand produceert, is veel goedkoper dan het ontdekken per afgewezen factuur.

  • Een PDF kan de bytes van elk bestand inbedden als een embedded file stream (Spec: ISO 32000-2, §7.11.4). De stream draagt de gegevens plus een kleine parameters dictionary: oorspronkelijke grootte, data en een checksum.
  • Ingebedde bestanden worden gecatalogiseerd in de EmbeddedFiles name tree, zodat een reader ze op naam kan opsommen zonder het hele document te scannen.
  • Een bijbehorend bestand gaat een stap verder: het declareert een AFRelationship (Spec: ISO 32000-2, §7.11.3) — een van acht standaardwaarden (Source, Data, Alternative, Supplement, EncryptedPayload, FormData, Schema, Unspecified), of een aangepaste waarde — die zegt hoe het bestand zich verhoudt tot de inhoud waaraan het is bijgevoegd.
  • Die getypeerde relatie is het mechanisme achter hybride e-facturen (ZUGFeRD / Factur-X) en PDF/A-3-bijlagen (Spec: ISO 19005-3, PDF/A-3).
  • NextPDF ondersteunt de ruwe container-primitieven in core: embedFile() en embedFileFromString() met een expliciete relatie. Advanced edities voegen de speciale EN 16931 / ZUGFeRD / Factur-X e-factuur-embedder bovenop deze primitieven toe.

Zie het als twee lagen op elkaar gestapeld.

De onderste laag is opslag. Een embedded file stream (Spec: ISO 32000-2, §7.11.4) is de bytes van het oorspronkelijke bestand verpakt in een PDF stream-object, met een parameters dictionary die de oorspronkelijke grootte, wijzigingsdatum en een checksum van de niet-gecomprimeerde gegevens vastlegt. De stream wordt bereikt via een file specification dictionary waarvan de /EF-dictionary naar de embedded file stream wijst — de stream zelf draagt geen /EF. Een reader kan het bestand er byte voor byte weer uithalen. Om deze bestanden vindbaar te maken houdt de document catalog een EmbeddedFiles name tree bij — een gesorteerde afbeelding van een naam naar elke file specification — zodat een viewer “hier zijn de 3 bestanden in deze PDF” kan opsommen zonder elke pagina te doorlopen.

De bovenste laag is betekenis. Op zichzelf is een ingebed bestand slechts aanwezig. Het associated-files-mechanisme (Spec: ISO 32000-2, §14.13) bindt een bestand aan iets — het hele document, een pagina, een grafisch object — en bestempelt het met een AFRelationship. ISO 32000-2 definieert een kleine woordenschat van acht standaardwaarden (Spec: ISO 32000-2, §7.11.3), en staat ook aangepaste waarden toe; elke standaardwaarde beantwoordt een precieze vraag:

AFRelationshipWat het beweert over het bestand
SourceDit is het bronmateriaal waarvan de zichtbare inhoud is gegenereerd (bijvoorbeeld het oorspronkelijke tekstverwerkerdocument).
DataDit zijn gestructureerde gegevens die aan de zichtbare inhoud zijn gekoppeld — het canonieke geval is de factuur-XML achter een gerenderde factuurpagina.
AlternativeDit is een alternatieve weergave van dezelfde inhoud (bijvoorbeeld een audio- of videoversie).
SupplementDit is aanvullend materiaal dat de inhoud uitbreidt maar er geen deel van uitmaakt.
EncryptedPayloadHet ingebedde bestand is een versleutelde payload die de PDF als een ondoorzichtige blob omhult.
FormDataHet bestand is formuliergegevens (FDF, XFDF of een XML-formulierpayload).
SchemaHet bestand is een schema dat de structuur van een Data-bestand beschrijft (bijvoorbeeld een XSD voor XML-gegevens of een JSON Schema).
UnspecifiedDe relatie is met opzet niet vermeld. Eerlijk, maar het vertelt een machine niets.

Naast deze acht staat de standaard ook applicatiespecifieke aangepaste relatiewaarden toe, zodat de woordenschat uitbreidbaar is in plaats van vast.

Een bijbehorend bestand wordt bepaald door twee dingen die samenwerken, niet door één sleutel alleen. De /AF-associatie bindt de file specification aan een deel van het document; de AFRelationship-sleutel in de file specification vermeldt vervolgens de semantische relatie. De /AF-vermelding op het associatiepunt (de document catalog, een pagina of een object) is een array — die array bevat een of meer file specification dictionaries, meestal als indirecte referenties; /AF is geen enkele referentie. Een bijbehorend bestand op documentniveau is de file specification die wordt vermeld in de /AF-array van de document catalog, met zijn AFRelationship. Markeer dat spreadsheet als Unspecified en je hebt het met het document geassocieerd maar een machine niets verteld over waarom. Markeer hetzelfde spreadsheet als Data en je hebt elke conforme reader verteld wat het is en waarvoor het dient. De bytes zijn hetzelfde. De semantiek niet.

Dit is waarom het e-factuurgeval niet “voeg een XML-bestand bij” is. Het is “bed deze XML in als het Data-bijbehorende bestand voor dit document, binnen een conforme PDF/A-3-drager” — waarbij factuurgeldigheid en juridische acceptatie afzonderlijke controles blijven die de drager niet uitvoert. De flow heeft vier fasen, en de volgorde is wat hem correct houdt.

  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).
Hoe een getypeerde bijlage van begin tot eind een hybride bestand wordt: de engine slaat de bytes op, registreert het bestand op naam, declareert de relatie, en het archiveringsprofiel staat toe dat dit alles binnen één conform archiveringsdocument meereist.

Die vierde fase is waarom PDF/A-3 als een apart profiel bestaat. Eerdere archiveringsprofielen beperkten wat ingebed mocht worden; PDF/A-3 (Spec: ISO 19005-3, PDF/A-3) is het deel dat bestanden van elk formaat toestaat om binnen een conform archiveringsdocument mee te reizen. Het staat de ingebedde payload toe — het valideert die payload niet en verleent geen juridische status. Zonder het zou de hybride factuur — één bestand dat zowel de pagina is die een persoon leest als de gegevens die een belastingsysteem parseert — helemaal geen conform archief-PDF/A-document kunnen zijn; of de factuur geldig en juridisch geaccepteerd is, blijft een afzonderlijke vraag. De speciale e-factuur-embedder die Advanced edities toevoegen is de gemaksnaad over precies dit: hij bedt de payload in, zet de relatie op Data en registreert die correct, zodat je het container-leidingwerk niet met de hand in elkaar zet. De diepere factuur- en archiveringsmechaniek staat op de twee naburige pagina’s die hieronder worden gelinkt; deze pagina gaat over de container waarop ze beide staan.

Een klein, compleet programma. De twee aanroepen die ertoe doen zijn het verschil tussen een ongetypeerd bijbehorend bestand en een getypeerd — en de relatie is een expliciet argument dat je hoort te zetten. In deze engine produceren beide aanroepen een bijbehorend bestand: embedFile() en embedFileFromString() registreren de file specification altijd in de /AF-array van de document catalog, zodat het enige wat de relatie verandert is wat de associatie betekent. Hij valt standaard terug op Unspecified, wat het bestand associeert maar een machine niets vertelt over waarom; voor een e-factuur-payload zet je hem op Data zodat een reader hem kan vinden.

<?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();

De '/Data'-relatie is onmiskenbaar. De eerste bijlage — gelaten op Unspecified — is evengoed geassocieerd, alleen zonder een vermelde betekenis. Voor beide aanroepen schrijft de engine de embedded file stream, voegt het bestand toe aan de EmbeddedFiles name tree, vermeldt zijn file specification in de /AF-array van de document catalog en legt de relatie vast die je hebt opgegeven — hij kiest er geen voor je. Deze engine heeft geen modus met alleen-name-tree: elk bestand dat je op deze manier inbedt is een document-geassocieerd bestand, dus de relatie is de enige hendel die je beheert.

De veelgehoorde aanname is dat “embedded” en “associated” twee woorden voor hetzelfde zijn. Dat zijn ze niet. Embedded gaat over opslag — de bytes zitten in de PDF. Associated gaat over binding — de file specification staat in een /AF-array op een deel van het document, en draagt een AFRelationship. In het abstracte PDF-model kan een bestand in de name tree worden ingebed zonder ooit geassocieerd te zijn; het embedFile()-pad van NextPDF laat het daar niet bij — het schrijft altijd de /AF-associatie — dus voor deze engine is de open vraag nooit of een bestand geassocieerd is maar wat de relatie zegt.

Een tweede valkuil: aannemen dat een viewer “wel uitvogelt” welke bijlage de factuur is. Een conforme reader hoort niet te gokken. Hij zoekt naar het bestand waarvan de relatie Data zegt. Laat de relatie Unspecified en je hebt de payload geassocieerd terwijl je de machine niets nuttigs over zijn rol hebt verteld.

Het containermechanisme is krachtig op een manier die het waard is om eerlijk over te zijn: embedFile() leest welk pad het PHP-proces ook maar kan lezen. Dat is de functie — en het is ook de grens. De engine voegt de bytes toe die hem worden gegeven; hij beslist niet, en kan niet beslissen, voor jou of een pad er een is dat je bedoelde bloot te leggen.

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

Nog twee grenzen die het waard zijn om duidelijk te benoemen:

  • Inbedden is niet valideren. De engine draagt de bytes die je hem geeft. Of de ingebedde XML een conforme factuur-payload is, is een afzonderlijke vraag, beantwoord door een validator — zie de factuurpagina.
  • Een getypeerde bijlage is op zichzelf geen conform archiveringsbestand. Het hybride bestand een wettelijk PDF/A-3-document maken vereist de archiveringsmodus en een onafhankelijke conformiteitscontrole — zie de archiveringspagina.
  • Facturen en e-facturatie — de use case die dit mechanisme mogelijk maakt: een hybride PDF die een machineleesbare factuur draagt als zijn Data-bijbehorende bestand.
  • Archivering en PDF/A — waarom de drager een PDF/A-3-bestand is en wat conformiteit wel en niet belooft.
  • De anatomie van een PDF-bestand — waar de name tree en de document catalog in de bestandsstructuur zitten.
  • Streams en filters — hoe de bytes van een ingebed bestand worden opgeslagen en gecomprimeerd binnen een stream-object.
  • Embedded file stream — een PDF stream-object dat de bytes van een extern bestand bevat, met een parameters dictionary die zijn oorspronkelijke grootte, data en een checksum vastlegt (ISO 32000-2 §7.11.4).
  • EmbeddedFiles name tree — de gesorteerde afbeelding in de document catalog die ingebedde bestanden op naam opsomt, zodat een reader bijlagen kan opsommen zonder het hele document te scannen.
  • Bijbehorend bestand — een ingebed bestand dat aan een deel van het document is gebonden door een /AF-associatie (op de document catalog, een pagina of een object) en dat een AFRelationship draagt die vermeldt hoe het zich verhoudt tot die inhoud; het geval op documentniveau — de file specification in de /AF-array van de catalog — is degene waar deze pagina om draait (ISO 32000-2 §14.13.3).
  • AFRelationship — de file-specification-sleutel waarvan de waarde de relatie benoemt (ISO 32000-2 §7.11.3). Hij neemt een van acht standaardwaarden aan (Source, Data, Alternative, Supplement, EncryptedPayload, FormData, Schema, Unspecified) of een aangepaste waarde; Data is de waarde die een hybride e-factuur-payload gebruikt.
  • PDF/A-3 — het ISO 19005-3-archiveringsprofiel dat toestaat dat bestanden van elk formaat worden ingebed, waardoor een conform hybride document mogelijk wordt.
  • Hybride factuur — één PDF-bestand dat zowel een menselijk leesbare pagina is als een machineleesbare ingebedde factuur-payload.