Pular para o conteúdo
getnextpdf.com

Um PDF é um contêiner: arquivos embutidos e dados associados

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

A maioria das pessoas imagina um PDF como uma pilha de páginas. Essa é a parte que você vê. Mas um PDF também é um contêiner, e pode carregar outros arquivos inteiros dentro de si — uma planilha, um payload XML, o documento de origem original — empacotados no mesmo arquivo único que você entrega a outra pessoa.

Esta página explica como isso funciona: o stream de arquivo embutido que armazena os bytes, a name tree que os lista, e a única chave que decide se um anexo está apenas ali parado ou de fato significa algo.

Um anexo sem tipo e um com tipo parecem idênticos para um humano. Ambos são um arquivo viajando dentro de um PDF e — neste motor — ambos estão associados ao documento. A diferença é que um deles diz a uma máquina para que serve, e o outro deixa a relação em branco para a máquina adivinhar.

Essa diferença é tudo o que importa para uma e-fatura híbrida. Uma plataforma fiscal não lê a página da sua fatura; ela lê o XML que você embutiu. Se esse XML estiver anexado como um blob indiferenciado em vez de como os dados da fatura para o documento visível, um leitor conforme não tem como saber de forma confiável que ele é o payload a processar. A página parece perfeita. A fatura é rejeitada. A falha chega dias depois, com um pagamento retido por trás.

Acertar a relação, na camada que produz o arquivo, é muito mais barato do que descobri-la uma fatura rejeitada por vez.

  • Um PDF pode embutir os bytes de qualquer arquivo como um stream de arquivo embutido (Spec: ISO 32000-2, §7.11.4). O stream carrega os dados mais um pequeno dicionário de parâmetros: tamanho original, datas e um checksum.
  • Os arquivos embutidos são catalogados na name tree EmbeddedFiles, de modo que um leitor pode enumerá-los por nome sem varrer o documento inteiro.
  • Um arquivo associado vai um passo além: ele declara uma AFRelationship (Spec: ISO 32000-2, §7.11.3) — um de oito valores padrão (Source, Data, Alternative, Supplement, EncryptedPayload, FormData, Schema, Unspecified), ou um valor customizado — dizendo como o arquivo se relaciona com o conteúdo ao qual está anexado.
  • Essa relação tipada é o mecanismo por trás de e-faturas híbridas (ZUGFeRD / Factur-X) e de anexos PDF/A-3 (Spec: ISO 19005-3, PDF/A-3).
  • O NextPDF suporta as primitivas brutas do contêiner no core: embedFile() e embedFileFromString() com uma relação explícita. As edições Advanced adicionam o embedder dedicado de e-fatura EN 16931 / ZUGFeRD / Factur-X em cima dessas primitivas.

Pense nisso como duas camadas empilhadas uma sobre a outra.

A camada inferior é o armazenamento. Um stream de arquivo embutido (Spec: ISO 32000-2, §7.11.4) são os bytes do arquivo original embrulhados em um objeto stream do PDF, com um dicionário de parâmetros registrando o tamanho original, a data de modificação e um checksum dos dados descomprimidos. O stream é alcançado por meio de um dicionário de especificação de arquivo cujo dicionário /EF aponta para o stream de arquivo embutido — o stream em si não carrega /EF. Um leitor pode extrair o arquivo de volta byte a byte. Para tornar esses arquivos localizáveis, o catálogo do documento mantém uma name tree EmbeddedFiles — um mapa ordenado de um nome até cada especificação de arquivo — de modo que um visualizador pode listar “aqui estão os 3 arquivos dentro deste PDF” sem percorrer cada página.

A camada superior é o significado. Por si só, um arquivo embutido está apenas presente. O mecanismo de arquivos associados (Spec: ISO 32000-2, §14.13) anexa um arquivo a algo — o documento inteiro, uma página, um objeto gráfico — e o carimba com uma AFRelationship. A ISO 32000-2 define um pequeno vocabulário de oito valores padrão (Spec: ISO 32000-2, §7.11.3), e também permite valores customizados; cada valor padrão responde a uma pergunta precisa:

AFRelationshipO que ele afirma sobre o arquivo
SourceEste é o material de origem a partir do qual o conteúdo visível foi gerado (por exemplo, o documento original do processador de texto).
DataEstes são dados estruturados ligados ao conteúdo visível — o caso canônico é o XML da fatura por trás de uma página de fatura renderizada.
AlternativeEsta é uma representação alternativa do mesmo conteúdo (por exemplo, uma versão em áudio ou vídeo).
SupplementEste é material suplementar que estende o conteúdo, mas não faz parte dele.
EncryptedPayloadO arquivo embutido é um payload criptografado que o PDF embrulha como um blob opaco.
FormDataO arquivo é dados de formulário (FDF, XFDF, ou um payload de formulário XML).
SchemaO arquivo é um schema descrevendo a estrutura de um arquivo Data (por exemplo, um XSD para dados XML ou um JSON Schema).
UnspecifiedA relação deliberadamente não é declarada. Honesto, mas não diz nada a uma máquina.

Além desses oito, a norma também permite valores de relação customizados específicos da aplicação, de modo que o vocabulário é extensível em vez de fixo.

Um arquivo associado é definido por duas coisas trabalhando juntas, não por uma chave isolada. A associação /AF vincula a especificação de arquivo a uma parte do documento; a chave AFRelationship na especificação de arquivo então declara a relação semântica. A entrada /AF no ponto de associação (o catálogo do documento, uma página ou um objeto) é um array — esse array contém uma ou mais especificações de arquivo, geralmente como referências indiretas; /AF não é uma referência única. Um arquivo associado em nível de documento é a especificação de arquivo listada no array /AF do catálogo do documento, carregando sua AFRelationship. Marque aquela planilha como Unspecified e você a terá associado ao documento, mas terá dito a uma máquina nada sobre o porquê. Marque a mesma planilha como Data e você terá dito a todo leitor conforme o que ela é e para que serve. Os bytes são os mesmos. A semântica não.

É por isso que o caso da e-fatura não é “anexar um arquivo XML”. É “embutir este XML como o arquivo associado Data deste documento, dentro de um portador PDF/A-3 conforme” — com a validade da fatura e a aceitação legal permanecendo verificações separadas que o portador não realiza. O fluxo tem quatro estágios, e a ordem é o que o mantém correto.

  1. Armazenar os bytesO arquivo é embrulhado em um stream de arquivo embutido com seu tamanho, datas e um checksum (ISO 32000-2 §7.11.4).
  2. Registrar por nomeA especificação de arquivo é adicionada à name tree EmbeddedFiles para que um leitor possa enumerar anexos sem varrer o documento.
  3. Declarar a relaçãoUm valor AFRelationship (um dos oito valores padrão, como Source ou Data) marca como o arquivo se relaciona com o conteúdo, associado em nível de documento (ISO 32000-2 §14.13.3).
  4. Torná-lo de arquivamentoUm portador PDF/A-3 permite que o payload embutido viaje dentro de um único documento de arquivamento PDF/A conforme; a validade da fatura e a aceitação legal permanecem verificações separadas (ISO 19005-3).
Como um anexo tipado se torna um arquivo híbrido de ponta a ponta: o motor armazena os bytes, registra o arquivo por nome, declara a relação, e o perfil de arquivamento permite que tudo isso viaje dentro de um único documento de arquivamento conforme.

Esse quarto estágio é o motivo de o PDF/A-3 existir como um perfil distinto. Os perfis de arquivamento anteriores restringiam o que podia ser embutido; o PDF/A-3 (Spec: ISO 19005-3, PDF/A-3) é a parte que permite arquivos de qualquer formato viajarem dentro de um documento de arquivamento conforme. Ele permite o payload embutido — não valida esse payload nem confere status legal. Sem ele, a fatura híbrida — um arquivo que é tanto a página que uma pessoa lê quanto os dados que um sistema fiscal faz parsing — não poderia ser um documento de arquivamento PDF/A conforme de jeito nenhum; se a fatura é válida e legalmente aceita continua sendo uma questão separada. O embedder dedicado de e-fatura que as edições Advanced adicionam é a costura de conveniência sobre exatamente isto: ele embute o payload, define a relação como Data e o registra corretamente, de modo que você não monta o encanamento do contêiner à mão. A mecânica mais profunda de faturamento e arquivamento vive nas duas páginas vizinhas linkadas abaixo; esta página é sobre o contêiner sobre o qual ambas se apoiam.

Um programa pequeno e completo. As duas chamadas que importam são a diferença entre um arquivo associado sem tipo e um com tipo — e a relação é um argumento explícito que você deveria definir. Neste motor, ambas as chamadas produzem um arquivo associado: embedFile() e embedFileFromString() sempre registram a especificação de arquivo no array /AF do catálogo do documento, de modo que a única coisa que a relação muda é o que a associação significa. Ela tem como padrão Unspecified, que associa o arquivo, mas não diz nada a uma máquina sobre o porquê; para um payload de e-fatura você a define como Data para que um leitor possa encontrá-lo.

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

A relação '/Data' é inconfundível. O primeiro anexo — deixado Unspecified — está associado da mesma forma, só que sem um significado declarado. Para ambas as chamadas, o motor escreve o stream de arquivo embutido, adiciona o arquivo à name tree EmbeddedFiles, lista sua especificação de arquivo no array /AF do catálogo do documento, e registra a relação que você declarou — ele não escolhe uma por você. Este motor não tem um modo somente-name-tree: todo arquivo que você embute desta forma é um arquivo associado ao documento, de modo que a relação é a única alavanca que você controla.

A suposição frequente é que “embutido” e “associado” são duas palavras para a mesma coisa. Não são. Embutido é sobre armazenamento — os bytes estão dentro do PDF. Associado é sobre vinculação — a especificação de arquivo está listada em um array /AF em uma parte do documento, e ela carrega uma AFRelationship. No modelo PDF abstrato, um arquivo pode ser embutido na name tree sem nunca ser associado; o caminho embedFile() do NextPDF não o deixa assim — ele sempre escreve a associação /AF — então, para este motor, a questão em aberto nunca é se um arquivo está associado, mas o que a relação diz.

Uma segunda armadilha: presumir que um visualizador “vai descobrir” qual anexo é a fatura. Um leitor conforme não deve adivinhar. Ele procura o arquivo cuja relação diz Data. Deixe a relação como Unspecified e você terá associado o payload sem dizer à máquina nada de útil sobre seu papel.

O mecanismo do contêiner é poderoso de um jeito que vale ser honesto a respeito: embedFile() lê qualquer caminho que o processo PHP possa ler. Esse é o recurso — e também é a fronteira. O motor anexa os bytes que recebe; ele não decide, e não pode decidir, por você se um caminho é um que você pretendia expor.

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

embedFile() lê qualquer caminho ao qual o processo PHP tem acesso e embute seus bytes literalmente. Validar que o caminho é seguro e pretendido — não um valor controlado pelo usuário, um traversal, ou um segredo fora do escopo do documento — é responsabilidade do integrador. Este é um contrato de segurança documentado, não um descuido: o motor não vai silenciosamente adivinhar quais caminhos são legítimos, porque essa adivinhação pertence à sua aplicação, que conhece a fronteira de confiança que o motor não consegue ver. Passe bytes influenciados por um atacante por meio de uma string com embedFileFromString() para que a camada de caminho nunca entre em jogo.

ProNot in this edition
EnterpriseNot in this edition

Dois outros limites valem ser declarados de forma direta:

  • Embutir não é validar. O motor carrega os bytes que você lhe dá. Se o XML embutido é um payload de fatura conforme é uma questão separada, respondida por um validador — veja a página de faturamento.
  • Um anexo tipado não é, por si só, um arquivo de arquivamento conforme. Tornar o arquivo híbrido um documento PDF/A-3 legal exige o modo de arquivamento e uma verificação de conformidade independente — veja a página de arquivamento.
  • Faturas e faturamento eletrônico — o caso de uso que este mecanismo torna possível: um PDF híbrido que carrega uma fatura legível por máquina como seu arquivo associado Data.
  • Arquivamento e PDF/A — por que o portador é um arquivo PDF/A-3 e o que a conformidade promete e não promete.
  • A anatomia de um arquivo PDF — onde a name tree e o catálogo do documento ficam na estrutura do arquivo.
  • Streams e filtros — como os bytes de um arquivo embutido são armazenados e comprimidos dentro de um objeto stream.
  • Stream de arquivo embutido — um objeto stream do PDF que contém os bytes de um arquivo externo, com um dicionário de parâmetros registrando seu tamanho original, datas e um checksum (ISO 32000-2 §7.11.4).
  • Name tree EmbeddedFiles — o mapa ordenado no catálogo do documento que lista os arquivos embutidos por nome, de modo que um leitor pode enumerar anexos sem varrer o documento inteiro.
  • Arquivo associado — um arquivo embutido vinculado a uma parte do documento por uma associação /AF (no catálogo do documento, em uma página ou em um objeto) e carregando uma AFRelationship que declara como ele se relaciona com esse conteúdo; o caso em nível de documento — a especificação de arquivo no array /AF do catálogo — é o que esta página centraliza (ISO 32000-2 §14.13.3).
  • AFRelationship — a chave da especificação de arquivo cujo valor nomeia a relação (ISO 32000-2 §7.11.3). Ela assume um de oito valores padrão (Source, Data, Alternative, Supplement, EncryptedPayload, FormData, Schema, Unspecified) ou um valor customizado; Data é o valor que um payload de e-fatura híbrida usa.
  • PDF/A-3 — o perfil de arquivamento ISO 19005-3 que permite embutir arquivos de qualquer formato, viabilizando um documento híbrido conforme.
  • Fatura híbrida — um único arquivo PDF que é tanto uma página legível por humano quanto um payload de fatura embutido legível por máquina.