O que um PDF realmente é
ISO 32000-2 §7 Evidence: Standard-backed
Visão geral
Seção intitulada “Visão geral”Um PDF não é uma descrição de página que por acaso está em um arquivo. É um pequeno banco de dados em grafo com uma impressora acoplada. Esta página descreve as quatro partes presentes em todo PDF — header, body, tabela de referências cruzadas, trailer — e como o NextPDF as grava para que um leitor encontre cada objeto sem adivinhar.
Por que isso importa
Seção intitulada “Por que isso importa”A maioria dos bugs de PDF não é bug de renderização. São bugs de estrutura: um byte offset que aponta um caractere além do objeto correto, um trailer que nomeia o root errado, uma entrada de referência cruzada que discorda de onde o objeto de fato está. Nenhum deles muda a aparência de uma página até que um leitor siga um caminho diferente pelo arquivo e avance além do fim dele.
Se você tratar um PDF como opaco, essas falhas parecem aleatórias. Se você conhece o modelo de objetos, elas parecem exatamente o que são: um número que não corresponde a uma posição. Entender o formato é a diferença entre “o PDF está corrompido” e “o offset do objeto 14 está defasado porque o writer o mediu antes de finalizar o comprimento do stream.”
A versão resumida
Seção intitulada “A versão resumida”Um PDF tem quatro partes, na ordem do arquivo:
- Um header — uma linha que nomeia a versão (
%PDF-2.0). - Um body — uma sequência de objetos indiretos numerados: dicionários, streams, arrays, números, strings, nomes.
- Uma tabela de referências cruzadas (ou, no PDF 2.0, um stream de referências cruzadas) — um mapeamento do número do objeto para o byte offset, de modo que qualquer objeto possa ser alcançado sem varrer o arquivo.
- Um trailer — um pequeno dicionário que nomeia o objeto root do documento e aponta para onde a seção de referências cruzadas começa.
Um leitor não lê um PDF do início ao fim. Ele lê a última linha primeiro, encontra startxref, vai para a seção de referências cruzadas e a usa como índice no body. O formato foi construído para ser lido de trás para frente. Esse único fato explica a maior parte do seu design.
Como o NextPDF aborda isso
Seção intitulada “Como o NextPDF aborda isso”O NextPDF constrói um PDF do mesmo modo como o formato é lido: primeiro o objeto, depois o offset registrado, e por último a tabela gravada.
Todo objeto indireto recebe um número a partir de um único registro (src/Core/ObjectRegistry.php). O registro atribui números sequenciais por meio de allocate() e, depois que os bytes de um objeto são gravados no buffer de saída, registra o byte offset por meio de register(). Os offsets nunca são adivinhados de antemão. Eles são observados a partir de BinaryBuffer::getOffset() no momento em que o cabeçalho do objeto é emitido. É por isso que uma entrada de referência cruzada do NextPDF não pode divergir do objeto que descreve: o offset é exatamente a posição em que o buffer de fato estava.
Quando o body está completo, uma estratégia de serialização específica da versão (src/Writer/PdfSerializationStrategy.php) grava a seção de referências cruzadas e o trailer:
Pdf20StreamStrategyemite um stream comprimido de referências cruzadas (/Type /XRef) — o padrão do PDF 2.0.Pdf17TableStrategyePdf14TableStrategyemitem uma tabela tradicional de referências cruzadas de 20 bytes, mais um dicionário de trailer separado — exigido pelos perfis PDF/A que requerem a estrutura de arquivo mais antiga.
A estratégia é escolhida pelo perfil de saída, não inferida. Independentemente dela, os bytes finais têm o mesmo formato: a seção de referências cruzadas, depois startxref, depois o byte offset, depois %%EOF. Essa cauda é o que um leitor encontra primeiro.
- Step 1 of 4: ISO 32000-2 §7.5.5 — %%EOF and startxref at the file end
- Step 2 of 4: ISO 32000-2 §7.5.4 / §7.5.8 — the cross-reference section maps object number to offset
- Step 3 of 4: ISO 32000-2 §7.5.5 — the trailer names /Root, the document catalog
- Step 4 of 4: ISO 32000-2 §7.3.10 — each indirect object is reached at its recorded offset
O que a evidência diz
Seção intitulada “O que a evidência diz”A estrutura de quatro partes não é uma convenção do NextPDF; é a cláusula de estrutura de arquivo da Spec: ISO 32000-2, §7.5 ISO 32000-2 §7.5 . O padrão define um PDF como um header, um body de objetos, uma tabela de referências cruzadas e um trailer, e estabelece que um leitor deve fazer parsing a partir do fim do arquivo. A última linha é %%EOF, e as duas linhas anteriores são a palavra-chave startxref e o byte offset para a seção de referências cruzadas.
Um objeto indireto é definido como um número de objeto e um número de geração, separados por espaço em branco, seguidos pelo valor do objeto delimitado pelas palavras-chave obj e endobj. A combinação de número de objeto e número de geração identifica o objeto de forma única; uma referência indireta a ele é escrita como o número do objeto, o número de geração e a palavra-chave R. O ObjectRegistry do NextPDF espelha isso exatamente: um número sequencial, geração 0 para objetos recém-gravados e um offset registrado.
A partir do PDF 1.5, objetos também podem residir dentro de um object stream, onde são armazenados sem as palavras-chave obj/endobj e devem ter geração zero. O stream de referências cruzadas (/Type /XRef,
Spec: ISO 32000-2, §7.5.8 ISO 32000-2 §7.5.8 ) é o mecanismo do PDF 2.0
que indexa tanto os objetos comuns quanto os objetos comprimidos.
O CrossReferenceStream do NextPDF o constrói com um array de larguras de campo /W e
compressão FlateDecode.
Exemplo prático
Seção intitulada “Exemplo prático”Este é o formato do body de um PDF mínimo e do seu trailer. Os números na seção de referências cruzadas são byte offsets. Eles precisam estar exatamente corretos, e é por isso que o NextPDF os registra a partir do buffer em vez de calculá-los.
%PDF-2.01 0 obj<< /Type /Catalog /Pages 2 0 R >>endobj2 0 obj<< /Type /Pages /Kids [3 0 R] /Count 1 >>endobj3 0 obj<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] >>endobjxref0 40000000000 65535 f0000000009 00000 n0000000058 00000 n0000000122 00000 ntrailer<< /Size 4 /Root 1 0 R >>startxref196%%EOFUm leitor abre isso de baixo para cima: %%EOF, depois startxref 196; então vai até o byte 196, onde xref começa, lê que o objeto 1 reside no byte 9, segue /Root 1 0 R até o catálogo e percorre a árvore de páginas a partir daí. O objeto 0 é sempre a cabeça da free-list com geração 65535 — uma peculiaridade herdada do design mais antigo do formato, reproduzida fielmente porque os leitores a esperam.
Equívoco comum
Seção intitulada “Equívoco comum”A armadilha é acreditar que um PDF é lido de cima para baixo como código-fonte. Não é. Os objetos no body podem estar em qualquer ordem. Os números de objeto não precisam ser sequenciais no arquivo, e um leitor nunca conta com isso. O único índice autoritativo é a seção de referências cruzadas, e a única forma de encontrá-la é o trailer no fim. Um PDF com um body perfeitamente válido e um único número errado em startxref é ilegível. Um PDF com objetos gravados em uma ordem embaralhada, mas com uma tabela de referências cruzadas correta, está correto. A posição em si não importa; a posição registrada é tudo.
Limites e fronteiras
Seção intitulada “Limites e fronteiras”Esta página descreve a estrutura de arquivo, não o conteúdo de página. Como as marcações chegam a uma página — content streams, operadores gráficos, exibição de texto — é um tópico separado. Ela também não cobre o que acontece quando um arquivo é alterado depois de ser gravado. Esse é o papel dos incremental updates, em que o writer anexa uma segunda seção de referências cruzadas e o trailer encadeia para trás.
O NextPDF é um writer. O comportamento descrito aqui é como ele serializa um documento que ele próprio construiu. Ele não é um parser de PDF de uso geral nem uma ferramenta de reparo. Ele não promete ler, reconstruir ou recuperar um arquivo arbitrário de terceiros com uma tabela de referências cruzadas danificada. A garantia é estreita e deliberada. Os arquivos que o NextPDF grava têm offsets correspondentes, porque são medidos, não previstos.
Mini-FAQ
Seção intitulada “Mini-FAQ”Por que existem números de geração se arquivos novos sempre usam 0? Os números de geração existem para o reuso de objetos entre atualizações. Um arquivo recém-gravado tem todos os objetos na geração 0. Gerações diferentes de zero aparecem apenas quando um arquivo foi atualizado incrementalmente e um número de objeto é reaproveitado.
Dois objetos podem ter o mesmo número? Em uma única seção de referências cruzadas, não. Ao longo de incremental updates, um arquivo pode conter fisicamente várias cópias do mesmo número de objeto. A entrada de referência cruzada mais recente prevalece. Esse é o tema da próxima página.
A ordem dos objetos no arquivo importa para a saída? Não. O NextPDF grava os objetos em uma ordem determinística para builds reproduzíveis, mas um leitor resolve tudo por meio da seção de referências cruzadas, então a ordem física não tem significado semântico.
Documentos relacionados
Seção intitulada “Documentos relacionados”- Incremental updates e por que eles importam — o que acontece quando um PDF gravado é alterado: seções anexadas e um trailer encadeado.
- Streams and filters — como os objetos de stream do body são comprimidos e codificados.
- PDF 2.0: o que mudou — como a estrutura de arquivo difere entre a 1.7 e a baseline 2.0 que o NextPDF tem como alvo.
Glossário
Seção intitulada “Glossário”- Objeto indireto — um objeto numerado no body, escrito como
N G obj … endobj, ondeNé o número do objeto eGo número de geração. - Referência indireta — um ponteiro para um objeto indireto, escrito
N G R. - Tabela de referências cruzadas (xref) — o índice do número do objeto para o byte offset. No PDF 2.0 isso costuma ser um stream de referências cruzadas (
/Type /XRef) em vez da clássica tabela de texto de 20 bytes por entrada. - Trailer — o dicionário no fim de uma seção de referências cruzadas que nomeia
/Root(o catálogo do documento) e/Size, e é encontrado por meio do offset destartxref. - Object stream — um objeto de stream que ele próprio contém outros objetos indiretos (comprimidos juntos); os membros não têm
obj/endobje têm geração zero. - Catálogo do documento — o objeto nomeado por
/Root; o ponto de entrada para a árvore de páginas e todo o restante do documento.