Ir al contenido
getnextpdf.com

Un PDF es un contenedor: archivos incrustados y datos asociados

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

La mayoría imagina un PDF como una pila de páginas. Esa es la parte que se ve. Pero un PDF es también un contenedor, y puede transportar otros archivos completos dentro de sí —una hoja de cálculo, una carga útil XML, el documento de origen original— agrupados en el mismo archivo único que se entrega a otra persona.

Esta página explica cómo funciona: el flujo de archivo incrustado que almacena los bytes, el árbol de nombres que los lista, y la única clave que decide si un adjunto simplemente está ahí o realmente significa algo.

Un adjunto sin tipo y uno con tipo parecen idénticos a una persona. Ambos son un archivo que viaja dentro de un PDF y —en este motor— ambos están asociados al documento. La diferencia es que uno de ellos le dice a una máquina para qué sirve, y el otro deja la relación en blanco para que la máquina la adivine.

Esa diferencia lo es todo para una factura electrónica híbrida. Una plataforma tributaria no lee la página de su factura; lee el XML que usted incrustó. Si ese XML se adjunta como un bloque indiferenciado en lugar de como los datos de la factura del documento visible, un lector conforme no tiene forma fiable de saber que es la carga útil que debe procesar. La página se ve perfecta. La factura se rechaza. El fallo llega días después, con un pago retenido detrás.

Acertar con la relación, en la capa que produce el archivo, es mucho más barato que descubrirlo de factura rechazada en factura rechazada.

  • Un PDF puede incrustar los bytes de cualquier archivo como un flujo de archivo incrustado (Spec: ISO 32000-2, §7.11.4). El flujo transporta los datos más un pequeño diccionario de parámetros: tamaño original, fechas y una suma de comprobación.
  • Los archivos incrustados se catalogan en el árbol de nombres EmbeddedFiles, de modo que un lector pueda enumerarlos por nombre sin escanear todo el documento.
  • Un archivo asociado va un paso más allá: declara una AFRelationship (Spec: ISO 32000-2, §7.11.3) —uno de ocho valores estándar (Source, Data, Alternative, Supplement, EncryptedPayload, FormData, Schema, Unspecified), o un valor personalizado— que indica cómo se relaciona el archivo con el contenido al que se adjunta.
  • Esa relación tipada es el mecanismo detrás de las facturas electrónicas híbridas (ZUGFeRD / Factur-X) y los adjuntos PDF/A-3 (Spec: ISO 19005-3, PDF/A-3).
  • NextPDF admite en el núcleo las primitivas de contenedor en bruto: embedFile() y embedFileFromString() con una relación explícita. Las ediciones Advanced añaden el incrustador de facturas electrónicas EN 16931 / ZUGFeRD / Factur-X dedicado, encima de estas primitivas.

Imagínelo como dos capas apiladas una sobre otra.

La capa inferior es el almacenamiento. Un flujo de archivo incrustado (Spec: ISO 32000-2, §7.11.4) son los bytes del archivo original envueltos en un objeto de flujo PDF, con un diccionario de parámetros que registra el tamaño original, la fecha de modificación y una suma de comprobación de los datos sin comprimir. Al flujo se llega a través de un diccionario de especificación de archivo cuyo diccionario /EF apunta al flujo de archivo incrustado; el flujo en sí no lleva /EF. Un lector puede extraer el archivo de nuevo byte por byte. Para que estos archivos sean localizables, el catálogo del documento contiene un árbol de nombres EmbeddedFiles —un mapa ordenado de un nombre a cada especificación de archivo— de modo que un lector pueda listar «aquí están los 3 archivos dentro de este PDF» sin recorrer cada página.

La capa superior es el significado. Por sí mismo, un archivo incrustado simplemente está presente. El mecanismo de archivos asociados (Spec: ISO 32000-2, §14.13) adjunta un archivo a algo —el documento completo, una página, un objeto gráfico— y lo marca con una AFRelationship. ISO 32000-2 define un pequeño vocabulario de ocho valores estándar (Spec: ISO 32000-2, §7.11.3), y permite también valores personalizados; cada valor estándar responde a una pregunta precisa:

AFRelationshipQué afirma sobre el archivo
SourceEste es el material de origen a partir del cual se generó el contenido visible (por ejemplo, el documento de procesador de textos original).
DataEstos son datos estructurados ligados al contenido visible; el caso canónico es el XML de factura detrás de una página de factura renderizada.
AlternativeEsta es una representación alternativa del mismo contenido (por ejemplo, una versión de audio o vídeo).
SupplementEste es material complementario que amplía el contenido pero no forma parte de él.
EncryptedPayloadEl archivo incrustado es una carga útil cifrada que el PDF envuelve como un bloque opaco.
FormDataEl archivo son datos de formulario (FDF, XFDF o una carga útil de formulario XML).
SchemaEl archivo es un esquema que describe la estructura de un archivo Data (por ejemplo, un XSD para datos XML o un JSON Schema).
UnspecifiedLa relación se deja deliberadamente sin indicar. Honesto, pero no le dice nada a una máquina.

Más allá de estos ocho, el estándar también permite valores de relación personalizados específicos de la aplicación, de modo que el vocabulario es extensible en lugar de fijo.

Un archivo asociado lo definen dos cosas que trabajan juntas, no una sola clave. La asociación /AF vincula la especificación de archivo a una parte del documento; la clave AFRelationship en la especificación de archivo después enuncia la relación semántica. La entrada /AF en el punto de asociación (el catálogo del documento, una página o un objeto) es un arreglo —ese arreglo contiene una o más diccionarios de especificación de archivo, normalmente como referencias indirectas; /AF no es una referencia única—. Un archivo asociado a nivel de documento es la especificación de archivo listada en el arreglo /AF del catálogo del documento, con su AFRelationship. Marque esa hoja de cálculo como Unspecified y la habrá asociado al documento pero no le habrá dicho nada a una máquina sobre el porqué. Marque la misma hoja de cálculo como Data y le habrá dicho a todo lector conforme qué es y para qué sirve. Los bytes son los mismos. La semántica no.

Por eso el caso de la factura electrónica no es «adjuntar un archivo XML». Es «incrustar este XML como el archivo asociado Data de este documento, dentro de un portador PDF/A-3 conforme», quedando la validez de la factura y la aceptación legal como comprobaciones separadas que el portador no realiza. El flujo tiene cuatro etapas, y el orden es lo que lo mantiene correcto.

  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.

Esa cuarta etapa es la razón por la que PDF/A-3 existe como un perfil distinto. Los perfiles de archivado anteriores restringían lo que podía incrustarse; PDF/A-3 (Spec: ISO 19005-3, PDF/A-3) es la parte que permite que archivos de cualquier formato viajen dentro de un documento de archivado conforme. Permite la carga útil incrustada; no valida esa carga útil ni confiere estatus legal. Sin él, la factura híbrida —un único archivo que es a la vez la página que una persona lee y los datos que un sistema tributario analiza— no podría ser en absoluto un documento de archivado PDF/A conforme; si la factura es válida y legalmente aceptada sigue siendo una cuestión separada. El incrustador de facturas electrónicas dedicado que las ediciones Advanced añaden es la costura de conveniencia justo sobre esto: incrusta la carga útil, fija la relación en Data y la registra correctamente, de modo que usted no monte a mano la fontanería del contenedor. La mecánica más profunda de facturación y archivado vive en las dos páginas vecinas enlazadas abajo; esta página trata del contenedor sobre el que ambas se apoyan.

Un programa pequeño y completo. Las dos llamadas que importan son la diferencia entre un archivo asociado sin tipo y uno con tipo, y la relación es un argumento explícito que debería establecer. En este motor, ambas llamadas producen un archivo asociado: embedFile() y embedFileFromString() siempre registran la especificación de archivo en el arreglo /AF del catálogo del documento, de modo que lo único que cambia la relación es qué significa la asociación. Su valor predeterminado es Unspecified, que asocia el archivo pero no le dice nada a una máquina sobre el porqué; para la carga útil de una factura electrónica se establece en Data para que un lector pueda encontrarla.

<?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 relación '/Data' es inconfundible. El primer adjunto —dejado como Unspecified— está asociado igualmente, solo que sin un significado declarado. Para ambas llamadas el motor escribe el flujo de archivo incrustado, añade el archivo al árbol de nombres EmbeddedFiles, lista su especificación de archivo en el arreglo /AF del catálogo del documento y registra la relación que usted enunció: no elige una por usted. Este motor no tiene un modo de solo árbol de nombres: cada archivo que incruste de esta forma es un archivo asociado al documento, de modo que la relación es la única palanca que usted controla.

La suposición frecuente es que «incrustado» y «asociado» son dos palabras para lo mismo. No lo son. Incrustado tiene que ver con el almacenamiento: los bytes están dentro del PDF. Asociado tiene que ver con el vínculo: la especificación de archivo está listada en un arreglo /AF de una parte del documento, y lleva una AFRelationship. En el modelo abstracto de PDF un archivo puede incrustarse en el árbol de nombres sin estar nunca asociado; la ruta embedFile() de NextPDF no lo deja ahí —siempre escribe la asociación /AF—, de modo que para este motor la pregunta abierta nunca es si un archivo está asociado, sino qué dice la relación.

Una segunda trampa: suponer que un lector «averiguará» qué adjunto es la factura. Un lector conforme no debe adivinar. Busca el archivo cuya relación dice Data. Deje la relación en Unspecified y habrá asociado la carga útil mientras no le dice a la máquina nada útil sobre su papel.

El mecanismo de contenedor es potente de una forma sobre la que conviene ser honesto: embedFile() lee cualquier ruta que el proceso PHP pueda leer. Esa es la función, y es también la frontera. El motor adjunta los bytes que se le dan; no decide por usted, ni puede hacerlo, si una ruta es una que usted pretendía exponer.

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

Dos límites más que conviene enunciar con claridad:

  • Incrustar no es validar. El motor transporta los bytes que usted le da. Si el XML incrustado es una carga útil de factura conforme es una cuestión separada, que responde un validador; consulte la página de facturación.
  • Un adjunto con tipo no es por sí solo un archivo de archivado conforme. Hacer del archivo híbrido un documento legal PDF/A-3 requiere el modo de archivado y una comprobación de conformidad independiente; consulte la página de archivado.
  • Facturas y facturación electrónica: el caso de uso que este mecanismo hace posible: un PDF híbrido que transporta una factura legible por máquina como su archivo asociado Data.
  • Archivado y PDF/A: por qué el portador es un archivo PDF/A-3 y qué promete y qué no promete la conformidad.
  • La anatomía de un archivo PDF: dónde se sitúan el árbol de nombres y el catálogo del documento en la estructura del archivo.
  • Flujos y filtros: cómo se almacenan y comprimen dentro de un objeto de flujo los bytes de un archivo incrustado.
  • Flujo de archivo incrustado: un objeto de flujo PDF que contiene los bytes de un archivo externo, con un diccionario de parámetros que registra su tamaño original, fechas y una suma de comprobación (ISO 32000-2 §7.11.4).
  • Árbol de nombres EmbeddedFiles: el mapa ordenado en el catálogo del documento que lista los archivos incrustados por nombre, de modo que un lector pueda enumerar los adjuntos sin escanear todo el documento.
  • Archivo asociado: un archivo incrustado vinculado a una parte del documento por una asociación /AF (en el catálogo del documento, una página o un objeto) y que lleva una AFRelationship que enuncia cómo se relaciona con ese contenido; el caso a nivel de documento —la especificación de archivo en el arreglo /AF del catálogo— es el que centra esta página (ISO 32000-2 §14.13.3).
  • AFRelationship: la clave de especificación de archivo cuyo valor nombra la relación (ISO 32000-2 §7.11.3). Toma uno de ocho valores estándar (Source, Data, Alternative, Supplement, EncryptedPayload, FormData, Schema, Unspecified) o un valor personalizado; Data es el valor que usa la carga útil de una factura electrónica híbrida.
  • PDF/A-3: el perfil de archivado ISO 19005-3 que permite incrustar archivos de cualquier formato, habilitando un documento híbrido conforme.
  • Factura híbrida: un único archivo PDF que es a la vez una página legible por humanos y una carga útil de factura incrustada legible por máquina.