Cos'è davvero un PDF
ISO 32000-2 §7 Evidence: Standard-backed
In sintesi
Sezione intitolata “In sintesi”Un PDF non è una descrizione di pagina finita per caso dentro un file. È un piccolo database a grafo con una stampante collegata. Questa pagina descrive le quattro parti presenti in ogni PDF — intestazione, corpo, tabella dei riferimenti incrociati, trailer — e il modo in cui NextPDF le scrive, così che un lettore possa trovare ogni oggetto senza tirare a indovinare.
Perché è importante
Sezione intitolata “Perché è importante”La maggior parte dei difetti nei PDF non sono difetti di rendering. Sono difetti di struttura: un offset in byte che punta un carattere oltre l’oggetto a cui dovrebbe puntare, un trailer che indica la radice sbagliata, una voce di riferimenti incrociati che non concorda con la posizione reale dell’oggetto. Nessuno di questi cambia l’aspetto di una pagina finché un lettore non percorre il file in modo diverso e prova a leggere oltre la fine.
Se si tratta un PDF come una scatola nera, questi errori sembrano casuali. Se si conosce il modello a oggetti, appaiono esattamente per ciò che sono: un numero che non corrisponde a una posizione. Saper leggere il formato fa la differenza tra «il PDF è corrotto» e «l’offset dell’oggetto 14 è obsoleto perché lo scrittore l’ha misurato prima che la lunghezza del flusso fosse definitiva.»
In breve
Sezione intitolata “In breve”Un PDF è composto da quattro parti, nell’ordine in cui compaiono nel file:
- Un’intestazione — una riga che indica la versione (
%PDF-2.0). - Un corpo — una sequenza di oggetti indiretti numerati: dizionari, flussi, array, numeri, stringhe, nomi.
- Una tabella di riferimenti incrociati (oppure, in PDF 2.0, un flusso di riferimenti incrociati) — una corrispondenza dal numero di oggetto all’offset in byte, così che ogni oggetto sia raggiungibile senza scorrere il file.
- Un trailer — un piccolo dizionario che indica l’oggetto radice del documento e punta al punto in cui inizia la sezione dei riferimenti incrociati.
Un lettore non legge un PDF dall’inizio alla fine. Legge prima l’ultima riga, trova startxref, salta alla sezione dei riferimenti incrociati e la usa come indice nel corpo. Il formato è progettato per essere letto a ritroso. Questo fatto, da solo, spiega gran parte della sua progettazione.
Come lo affronta NextPDF
Sezione intitolata “Come lo affronta NextPDF”NextPDF costruisce un PDF nello stesso ordine in cui il formato viene letto: prima l’oggetto, poi l’offset registrato, infine la tabella, scritta per ultima.
A ogni oggetto indiretto viene assegnato un numero da un unico registro (src/Core/ObjectRegistry.php). Il registro assegna numeri sequenziali tramite allocate() e, dopo che i byte di un oggetto sono stati scritti nel buffer di output, registra l’offset in byte tramite register(). Gli offset non vengono mai indovinati in anticipo. Vengono osservati da BinaryBuffer::getOffset() nel momento in cui viene emessa l’intestazione dell’oggetto. Per questo una voce di riferimenti incrociati di NextPDF non può scostarsi dall’oggetto che descrive: l’offset è esattamente la posizione effettiva nel buffer.
Quando il corpo è completo, una strategia di serializzazione specifica per la versione (src/Writer/PdfSerializationStrategy.php) scrive la sezione dei riferimenti incrociati e il trailer:
Pdf20StreamStrategyemette un flusso di riferimenti incrociati compresso (/Type /XRef) — il valore predefinito in PDF 2.0.Pdf17TableStrategyePdf14TableStrategyemettono una tabella di riferimenti incrociati tradizionale da 20 byte più un dizionario trailer separato — richiesto dai profili PDF/A che impongono la struttura del file più datata.
La strategia è scelta dal profilo di output, non dedotta. Qualunque sia la strategia, i byte finali hanno la stessa forma: la sezione dei riferimenti incrociati, poi startxref, poi l’offset in byte, poi %%EOF. Quella coda è ciò che un lettore trova per primo.
- 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
Cosa dicono le evidenze
Sezione intitolata “Cosa dicono le evidenze”La struttura in quattro parti non è una convenzione di NextPDF; è la clausola sulla struttura del file di Spec: ISO 32000-2, §7.5 ISO 32000-2 §7.5 . Lo standard definisce un PDF come un’intestazione, un corpo di oggetti, una tabella di riferimenti incrociati e un trailer, e stabilisce che un lettore dovrebbe analizzarlo a partire dalla fine del file. L’ultima riga è %%EOF; le due righe precedenti sono la parola chiave startxref e l’offset in byte verso la sezione dei riferimenti incrociati.
Un oggetto indiretto è definito da un numero di oggetto e un numero di generazione, separati da spazi, seguiti dal valore dell’oggetto racchiuso tra le parole chiave obj ed endobj. La combinazione di numero di oggetto e numero di generazione identifica l’oggetto in modo univoco; un riferimento indiretto a tale oggetto è scritto come il numero di oggetto, il numero di generazione e la parola chiave R. L’ObjectRegistry di NextPDF rispecchia esattamente questo schema: un numero sequenziale, generazione 0 per gli oggetti appena scritti e un offset registrato.
A partire da PDF 1.5, gli oggetti possono anche risiedere all’interno di un flusso di oggetti, dove sono memorizzati senza le parole chiave obj/endobj e devono avere generazione zero. Il flusso di riferimenti incrociati (/Type /XRef,
Spec: ISO 32000-2, §7.5.8 ISO 32000-2 §7.5.8 ) è il meccanismo PDF 2.0
che indicizza sia gli oggetti ordinari sia quelli compressi.
Il CrossReferenceStream di NextPDF lo costruisce usando un array di larghezze di campo /W e
la compressione FlateDecode.
Esempio pratico
Sezione intitolata “Esempio pratico”Questa è la forma di un corpo PDF minimo e del suo trailer. I numeri nella sezione dei riferimenti incrociati sono offset in byte. Devono essere esatti al byte: per questo NextPDF li registra dal buffer invece di calcolarli.
%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%%EOFUn lettore lo apre dal fondo: %%EOF, poi startxref 196, quindi si posiziona al byte 196 dove inizia xref, legge che l’oggetto 1 risiede al byte 9, segue /Root 1 0 R fino al catalogo e percorre da lì l’albero delle pagine. L’oggetto 0 è sempre la testa della lista libera con generazione 65535 — una stranezza ereditata dalla progettazione originaria del formato, riprodotta fedelmente perché i lettori se l’aspettano.
Equivoco comune
Sezione intitolata “Equivoco comune”La trappola sta nel credere che un PDF venga letto dall’alto verso il basso come il codice sorgente. Non è così. Il corpo può contenere gli oggetti in qualsiasi ordine. I numeri di oggetto non devono essere sequenziali nel file, e un lettore non fa mai affidamento sul fatto che lo siano. L’unico indice autorevole è la sezione dei riferimenti incrociati, e l’unico modo per trovarla è il trailer alla fine. Un PDF con un corpo perfettamente valido e un solo numero errato in startxref è illeggibile. Un PDF con gli oggetti scritti in ordine sparso ma con una tabella di riferimenti incrociati corretta va benissimo. La posizione di per sé non conta nulla; la posizione registrata è tutto.
Limiti e confini
Sezione intitolata “Limiti e confini”Questa pagina descrive la struttura del file, non il contenuto delle pagine. Come i segni vengono collocati su una pagina — flussi di contenuto, operatori grafici, visualizzazione del testo — è un argomento a parte. Non tratta nemmeno ciò che accade quando un file viene modificato dopo essere stato scritto. Questo è il compito degli aggiornamenti incrementali, in cui viene aggiunta una seconda sezione di riferimenti incrociati e il trailer si concatena a ritroso.
NextPDF è uno scrittore. Il comportamento descritto qui è il modo in cui serializza un documento che ha costruito. Non è un parser PDF generico né uno strumento di riparazione. Non promette di leggere, ricostruire o recuperare un file arbitrario di terze parti con una tabella di riferimenti incrociati danneggiata. La garanzia è ristretta e voluta. I file che NextPDF scrive hanno offset coerenti, perché sono misurati, non stimati in anticipo.
Mini-FAQ
Sezione intitolata “Mini-FAQ”Perché esistono i numeri di generazione, se i nuovi file usano sempre 0? I numeri di generazione esistono per riutilizzare gli oggetti tra un aggiornamento e l’altro. In un file appena scritto, ogni oggetto è alla generazione 0. Le generazioni diverse da zero compaiono solo quando un file è stato aggiornato in modo incrementale e un numero di oggetto viene riciclato.
Due oggetti possono avere lo stesso numero? All’interno di una singola sezione di riferimenti incrociati, no. Tra più aggiornamenti incrementali, un file può contenere fisicamente più copie dello stesso numero di oggetto. Vince la voce di riferimenti incrociati più recente. È l’argomento della pagina successiva.
L’ordine degli oggetti nel file conta per l’output? No. NextPDF scrive gli oggetti in un ordine deterministico per ottenere build riproducibili, ma un lettore risolve ogni cosa attraverso la sezione dei riferimenti incrociati, quindi l’ordine fisico non ha rilevanza semantica.
Documenti correlati
Sezione intitolata “Documenti correlati”- Aggiornamenti incrementali e perché sono importanti — cosa accade quando un PDF già scritto viene modificato: sezioni aggiunte in coda e trailer concatenato.
- Flussi e filtri — come gli oggetti flusso del corpo vengono compressi e codificati.
- PDF 2.0: cosa è cambiato — come la struttura di file differisce tra la 1.7 e la baseline 2.0 a cui punta NextPDF.
Glossario
Sezione intitolata “Glossario”- Oggetto indiretto — un oggetto numerato nel corpo, scritto come
N G obj … endobj, doveNè il numero di oggetto eGil numero di generazione. - Riferimento indiretto — un puntatore a un oggetto indiretto, scritto come
N G R. - Tabella di riferimenti incrociati (xref) — l’indice dal numero di oggetto all’offset in byte. In PDF 2.0 si tratta di solito di un flusso di riferimenti incrociati (
/Type /XRef) anziché della classica tabella testuale da 20 byte per voce. - Trailer — il dizionario alla fine di una sezione di riferimenti incrociati che indica
/Root(il catalogo del documento) e/Size, ed è individuato tramite l’offsetstartxref. - Flusso di oggetti — un oggetto flusso che a sua volta contiene altri oggetti indiretti (compressi insieme); i membri non hanno
obj/endobje hanno generazione zero. - Catalogo del documento — l’oggetto indicato da
/Root; il punto di ingresso all’albero delle pagine e a tutto il resto del documento.