Salta ai contenuti
getnextpdf.com

Un PDF è un contenitore: file incorporati e dati associati

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

La maggior parte delle persone immagina un PDF come una pila di pagine. Quella è la parte che si vede. Ma un PDF è anche un contenitore, e può trasportare interi altri file al suo interno — un foglio di calcolo, un payload XML, il documento sorgente originale — raggruppati nello stesso unico file che si consegna a qualcun altro.

Questa pagina spiega come funziona: l’embedded-file stream che memorizza i byte, il name tree che li elenca, e l’unica chiave che decide se un allegato è semplicemente lì o significa davvero qualcosa.

Un allegato non tipizzato e uno tipizzato sembrano identici a un essere umano. Entrambi sono un file che viaggia dentro un PDF e — in questo motore — entrambi sono associati al documento. La differenza è che uno dei due dice a una macchina a cosa serve, e l’altro lascia la relazione in bianco affinché la macchina la indovini.

Quella differenza è tutta la partita per una e-fattura ibrida. Una piattaforma fiscale non legge la pagina della fattura; legge l’XML che si è incorporato. Se quell’XML è allegato come un blob indifferenziato anziché come i dati della fattura per il documento visibile, un lettore conforme non ha modo affidabile di sapere che è il payload da elaborare. La pagina sembra perfetta. La fattura viene respinta. Il fallimento arriva giorni dopo, con un pagamento sospeso alle spalle.

Azzeccare la relazione, nello strato che produce il file, è molto più economico che scoprirla una fattura respinta alla volta.

  • Un PDF può incorporare i byte di qualsiasi file come embedded file stream (Spec: ISO 32000-2, §7.11.4). Lo stream porta i dati più un piccolo parameters dictionary: dimensione originale, date e un checksum.
  • I file incorporati sono catalogati nel name tree EmbeddedFiles, così che un lettore possa enumerarli per nome senza scandire l’intero documento.
  • Un file associato va un passo oltre: dichiara una AFRelationship (Spec: ISO 32000-2, §7.11.3) — uno di otto valori standard (Source, Data, Alternative, Supplement, EncryptedPayload, FormData, Schema, Unspecified), o un valore personalizzato — che dice come il file si relaziona al contenuto a cui è allegato.
  • Quella relazione tipizzata è il meccanismo dietro le e-fatture ibride (ZUGFeRD / Factur-X) e gli allegati PDF/A-3 (Spec: ISO 19005-3, PDF/A-3).
  • NextPDF supporta le primitive grezze del contenitore nel core: embedFile() e embedFileFromString() con una relazione esplicita. Le edizioni Advanced aggiungono l’embedder dedicato per e-fatture EN 16931 / ZUGFeRD / Factur-X sopra queste primitive.

Lo si pensi come due strati impilati uno sull’altro.

Lo strato inferiore è l’archiviazione. Un embedded file stream (Spec: ISO 32000-2, §7.11.4) è costituito dai byte del file originale avvolti in un oggetto stream PDF, con un parameters dictionary che registra la dimensione originale, la data di modifica e un checksum dei dati non compressi. Lo stream è raggiunto attraverso un dizionario di file specification il cui dizionario /EF punta all’embedded file stream — lo stream stesso non porta /EF. Un lettore può estrarre di nuovo il file byte-per-byte. Per rendere questi file individuabili, il document catalog contiene un name tree EmbeddedFiles — una mappa ordinata da un nome a ciascuna file specification — così che un lettore possa elencare «ecco i 3 file dentro questo PDF» senza percorrere ogni pagina.

Lo strato superiore è il significato. Di per sé, un file incorporato è semplicemente presente. Il meccanismo dei file associati (Spec: ISO 32000-2, §14.13) allega un file a qualcosa — l’intero documento, una pagina, un oggetto grafico — e lo marca con una AFRelationship. ISO 32000-2 definisce un piccolo vocabolario di otto valori standard (Spec: ISO 32000-2, §7.11.3), e permette anche valori personalizzati; ciascun valore standard risponde a una domanda precisa:

AFRelationshipCosa afferma riguardo al file
SourceQuesto è il materiale sorgente da cui è stato generato il contenuto visibile (per esempio, il documento originale di videoscrittura).
DataQuesti sono dati strutturati legati al contenuto visibile — il caso canonico è l’XML della fattura dietro una pagina di fattura renderizzata.
AlternativeQuesta è una rappresentazione alternativa dello stesso contenuto (per esempio, una versione audio o video).
SupplementQuesto è materiale supplementare che estende il contenuto ma non ne fa parte.
EncryptedPayloadIl file incorporato è un payload cifrato che il PDF avvolge come un blob opaco.
FormDataIl file è dati di modulo (FDF, XFDF, o un payload di modulo XML).
SchemaIl file è uno schema che descrive la struttura di un file Data (per esempio, un XSD per dati XML o un JSON Schema).
UnspecifiedLa relazione è deliberatamente non dichiarata. Onesto, ma non dice nulla a una macchina.

Oltre a questi otto, lo standard permette anche valori di relazione personalizzati specifici dell’applicazione, così che il vocabolario sia estensibile anziché fisso.

Un file associato è definito da due cose che lavorano insieme, non da una sola chiave. L’associazione /AF vincola la file specification a una parte del documento; la chiave AFRelationship nella file specification poi dichiara la relazione semantica. La voce /AF sul punto di associazione (il document catalog, una pagina o un oggetto) è un array — quell’array contiene uno o più dizionari di file specification, di solito come riferimenti indiretti; /AF non è un singolo riferimento. Un file associato a livello di documento è la file specification elencata nell’array /AF del document catalog, che porta la sua AFRelationship. Si marchi quel foglio di calcolo Unspecified e lo si è associato al documento ma non si è detto nulla a una macchina sul perché. Si marchi lo stesso foglio di calcolo Data e si è detto a ogni lettore conforme cos’è e a cosa serve. I byte sono gli stessi. La semantica no.

Per questo il caso della e-fattura non è «allegare un file XML». È «incorporare questo XML come il file associato Data per questo documento, dentro un contenitore PDF/A-3 conforme» — con la validità della fattura e l’accettazione legale che restano controlli separati che il contenitore non esegue. Il flusso ha quattro fasi, e l’ordine è ciò che lo mantiene corretto.

  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).
How a typed attachment becomes a hybrid file end to end: the engine stores the bytes, registers the file by name, declares the relationship, and the archival profile permits it all to ride inside one conforming archival document.

Quella quarta fase è il motivo per cui PDF/A-3 esiste come profilo distinto. I profili di archiviazione precedenti limitavano ciò che poteva essere incorporato; PDF/A-3 (Spec: ISO 19005-3, PDF/A-3) è la parte che permette a file di qualsiasi formato di viaggiare dentro un documento di archiviazione conforme. Permette il payload incorporato — non valida quel payload né conferisce uno stato legale. Senza di esso, la fattura ibrida — un unico file che è sia la pagina che una persona legge sia i dati che un sistema fiscale analizza — non potrebbe affatto essere un documento di archiviazione PDF/A conforme; se la fattura sia valida e legalmente accettata resta una questione separata. L’embedder dedicato per e-fatture che le edizioni Advanced aggiungono è la cucitura di comodità esattamente su questo: incorpora il payload, imposta la relazione su Data e lo registra correttamente, così che non si debba assemblare a mano l’impiantistica del contenitore. La meccanica più profonda di fatturazione e archiviazione vive sulle due pagine vicine collegate qui sotto; questa pagina riguarda il contenitore su cui entrambe poggiano.

Un programma piccolo e completo. Le due chiamate che contano sono la differenza tra un file associato non tipizzato e uno tipizzato — e la relazione è un argomento esplicito che si dovrebbe impostare. In questo motore, entrambe le chiamate producono un file associato: embedFile() e embedFileFromString() registrano sempre la file specification nell’array /AF del document catalog, quindi l’unica cosa che la relazione cambia è cosa significa l’associazione. Il valore predefinito è Unspecified, che associa il file ma non dice nulla a una macchina sul perché; per il payload di una e-fattura lo si imposta su Data così che un lettore possa trovarlo.

<?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 relazione '/Data' è inequivocabile. Il primo allegato — lasciato Unspecified — è associato comunque, solo senza un significato dichiarato. Per entrambe le chiamate il motore scrive l’embedded file stream, aggiunge il file al name tree EmbeddedFiles, elenca la sua file specification nell’array /AF del document catalog, e registra la relazione che si è dichiarato — non ne sceglie una al posto dell’utente. Questo motore non ha una modalità solo-name-tree: ogni file incorporato in questo modo è un file associato al documento, quindi la relazione è l’unica leva che si controlla.

Si dà spesso per scontato che «incorporato» e «associato» siano due parole per la stessa cosa. Non lo sono. Incorporato riguarda l’archiviazione — i byte sono dentro il PDF. Associato riguarda il vincolo — la file specification è elencata in un array /AF su una parte del documento, e porta una AFRelationship. Nel modello astratto del PDF un file può essere incorporato nel name tree senza mai essere associato; il percorso embedFile() di NextPDF non lo lascia lì — scrive sempre l’associazione /AF — quindi per questo motore la domanda aperta non è mai se un file è associato ma cosa dice la relazione.

Una seconda trappola: presumere che un lettore «capirà» quale allegato è la fattura. Un lettore conforme non dovrebbe indovinare. Cerca il file la cui relazione dice Data. Si lasci la relazione Unspecified e si è associato il payload pur non dicendo nulla di utile alla macchina sul suo ruolo.

Il meccanismo del contenitore è potente in un modo su cui vale la pena essere onesti: embedFile() legge qualunque percorso il processo PHP possa leggere. Questa è la funzionalità — ed è anche il confine. Il motore allega i byte che gli vengono dati; non decide, e non può decidere, per l’utente se un percorso sia uno che si intendeva esporre.

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

embedFile() legge qualsiasi percorso a cui il processo PHP ha accesso e ne incorpora i byte alla lettera. Validare che il percorso sia sicuro e intenzionale — non un valore controllato dall’utente, una traversal o un segreto al di fuori dell’ambito del documento — è responsabilità dell’integratore. Questo è un contratto di sicurezza documentato, non una svista: il motore non indovinerà silenziosamente quali percorsi sono legittimi, perché quella decisione appartiene alla tua applicazione, che conosce il confine di fiducia che il motore non può vedere. Si facciano passare i byte influenzati da un attaccante attraverso una stringa con embedFileFromString() così che lo strato dei percorsi non sia mai in gioco.

ProNot in this edition
EnterpriseNot in this edition

Due ulteriori limiti che vale la pena dichiarare chiaramente:

  • Incorporare non è validare. Il motore porta i byte che gli si dà. Se l’XML incorporato sia un payload di fattura conforme è una questione separata, a cui risponde un validatore — si veda la pagina sulla fatturazione.
  • Un allegato tipizzato non è di per sé un file di archiviazione conforme. Rendere il file ibrido un documento PDF/A-3 legale richiede la modalità di archiviazione e un controllo di conformità indipendente — si veda la pagina sull’archiviazione.
  • Fatture ed e-fatturazione — il caso d’uso che questo meccanismo rende possibile: un PDF ibrido che porta una fattura leggibile dalla macchina come suo file associato Data.
  • Archiviazione e PDF/A — perché il contenitore è un file PDF/A-3 e cosa la conformità promette e cosa non promette.
  • L’anatomia di un file PDF — dove si trovano il name tree e il document catalog nella struttura del file.
  • Stream e filtri — come i byte di un file incorporato sono memorizzati e compressi dentro un oggetto stream.
  • Embedded file stream — un oggetto stream PDF che contiene i byte di un file esterno, con un parameters dictionary che registra la sua dimensione originale, le date e un checksum (ISO 32000-2 §7.11.4).
  • EmbeddedFiles name tree — la mappa ordinata nel document catalog che elenca i file incorporati per nome, così che un lettore possa enumerare gli allegati senza scandire l’intero documento.
  • File associato — un file incorporato vincolato a una parte del documento da un’associazione /AF (sul document catalog, una pagina o un oggetto) e che porta una AFRelationship che dichiara come si relaziona a quel contenuto; il caso a livello di documento — la file specification nell’array /AF del catalog — è quello su cui questa pagina si concentra (ISO 32000-2 §14.13.3).
  • AFRelationship — la chiave della file specification il cui valore nomina la relazione (ISO 32000-2 §7.11.3). Assume uno di otto valori standard (Source, Data, Alternative, Supplement, EncryptedPayload, FormData, Schema, Unspecified) o un valore personalizzato; Data è il valore che usa il payload di una e-fattura ibrida.
  • PDF/A-3 — il profilo di archiviazione ISO 19005-3 che permette di incorporare file di qualsiasi formato, abilitando un documento ibrido conforme.
  • Fattura ibrida — un unico file PDF che è sia una pagina leggibile dall’uomo sia un payload di fattura incorporato leggibile dalla macchina.