Scrivere PDF da zero (01): Hello, World——Costruire un PDF minimo utilizzabile
Scrivere PDF da zero (01): Hello, World——Costruire un PDF minimo utilizzabile
Obiettivo della serie: Comprendere il PDF come un formato di file leggibile—iniziare con un esempio minimo "funzionante" e poi espandere gradualmente a grafica, più pagine, compressione e riutilizzo delle risorse.
Indice della serie
- Parte 01 (questo articolo): Scrivere a mano un PDF minimo (1 pagina + 1 riga di testo) e completarlo con uno strumento in un PDF standard apribile
- Parte 02: Disegnare linee/rettangoli nel flusso di contenuto (comprendere percorsi, tratti, riempimenti)
- Parte 03: PDF multipagina (come si sviluppa l'albero delle pagine)
- Parte 04: Più vicino al mondo reale (flussi compressi, riutilizzo delle risorse, strutture opzionali, ecc.)
Perché è importante comprendere la struttura sottostante del PDF?
Il PDF (Portable Document Format, formato di documento portatile) è uno dei linguaggi di descrizione della pagina più popolari oggi. A differenza di HTML/CSS, che separano "contenuto e presentazione, e sono riadattabili (Reflowable)", il PDF enfatizza di più la formattazione fissa, ciò che vedi è ciò che ottieni—indipendentemente dal dispositivo su cui viene aperto, il layout rimane coerente.
Comprendere la struttura sottostante del PDF ha diversi vantaggi pratici:
- Debugging dei problemi di generazione PDF: Quando si genera un PDF con una libreria di codice e si verifica un errore, comprendere la struttura sottostante consente di localizzare rapidamente il problema
- Elaborazione automatizzata: Operazioni come l'estrazione di testo in batch, la fusione di documenti, l'aggiunta di filigrane, ecc., richiedono una comprensione della struttura per essere eseguite con precisione
- Audit di sicurezza: Comprendere quali contenuti possono essere incorporati nel PDF (JavaScript, allegati, moduli, ecc.) aiuta nell'analisi della sicurezza
- Apprendimento del design dei formati di file: Il design "oggetto grafico + accesso casuale" del PDF è un esempio classico, meritevole di studio
Preparativi
In questo articolo scriveremo un hello-broken.pdf "strutturalmente incompleto ma logicamente corretto", quindi utilizzeremo pdftk per completare automaticamente le strutture chiave e generare hello.pdf.
- Strumenti necessari: pdftk (strumento da riga di comando gratuito, supporta Windows/macOS/Linux)
- File di output:
hello-broken.pdf(scritto a mano),hello.pdf(aperto dopo la riparazione)
Concetti chiave: La struttura a tre livelli del PDF
Comprendere il PDF è fondamentale per stabilire un modello mentale a tre livelli:

1. Livello oggetti (Contenuto del documento)
Un documento PDF è composto da molti oggetti, che sono collegati tra loro tramite riferimenti indiretti (come 2 0 R) per formare un grafo. Tipi di oggetti comuni:
| Tipo | Esempio | Descrizione |
|---|---|---|
| Nome | /Page | Nome che inizia con / |
| Intero/Reale | 50, 36.0 | Valore numerico |
| Stringa | (Hello, World!) | Racchiusa tra parentesi tonde |
| Array | [0 0 612 792] | Collezione ordinata |
| Dizionario | << /Type /Page >> | Collezione di coppie chiave-valore |
| Riferimento indiretto | 2 0 R | Riferisce all'oggetto 2 (numero di generazione 0) |
| Flusso | stream...endstream | Dati binari (come istruzioni di disegno, immagini) |
2. Livello contenuto (Contenuto della pagina)
La sequenza di istruzioni che "disegna testo/grafica sulla pagina", di solito scritta in stream ... endstream. Il formato è: operandi prima, operatori dopo.
/F0 36 Tf ← Operando: /F0, 36 Operatore: Tf (imposta il font)
(Hello, World!) Tj ← Operando: stringa Operatore: Tj (disegna testo)
3. Livello struttura del file (Struttura del file)
Permette al lettore di accedere casualmente a qualsiasi oggetto senza dover leggere dall'inizio alla fine:
| Elemento | Funzione |
|---|---|
%PDF-1.x | Intestazione del file, identifica la versione PDF |
xref | Tabella di riferimento incrociato: numero oggetto → offset in byte |
trailer | Dizionario finale: punta all'oggetto radice /Root |
startxref | Indica la posizione di inizio della tabella xref |
%%EOF | Marcatura di fine file |
Quali oggetti sono necessari per un PDF minimo?
Un PDF "minimo ma in grado di visualizzare testo" ha la seguente relazione di riferimento tra oggetti:

Elenco degli oggetti minimi:
| Oggetto | Funzione | Campi chiave |
|---|---|---|
| Catalogo | Oggetto radice, ingresso del documento | /Type /Catalog, /Pages |
| Pagine | Albero delle pagine | /Type /Pages, /Kids, /Count |
| Pagina | Singola pagina | /Type /Page, /MediaBox, /Resources, /Contents, /Parent |
| Risorse | Contenitore delle risorse | /Font (dizionario dei font) |
| Font | Definizione del font | /Type /Font, /BaseFont, /Subtype |
| Contenuti | Flusso di contenuto | Flusso di istruzioni di disegno |
Pratica: Scrivere hello-broken.pdf
Crea un file hello-broken.pdf e incolla completamente il seguente contenuto:
%PDF-1.0
1 0 obj
<< /Type /Pages
/Count 1
/Kids [2 0 R]
>>
endobj
2 0 obj
<< /Type /Page
/MediaBox [0 0 612 792]
/Resources 3 0 R
/Parent 1 0 R
/Contents [4 0 R]
>>
endobj
3 0 obj
<< /Font
<< /F0
<< /Type /Font
/BaseFont /Times-Italic
/Subtype /Type1 >>
>>
>>
endobj
4 0 obj
<< >>
stream
1. 0. 0. 1. 50. 700. cm
BT
/F0 36. Tf
(Hello, World!) Tj
ET
endstream
endobj
5 0 obj
<< /Type /Catalog
/Pages 1 0 R
>>
endobj
xref
0 6
trailer
<< /Size 6
/Root 5 0 R
>>
startxref
0
%%EOF
Perché questo file è "rotto"?
Abbiamo intenzionalmente omesso o riempito in modo errato i seguenti contenuti:
| Voce mancante/errata | Descrizione |
|---|---|
Offset xref | Non è stato fornito l'offset reale in byte di ogni oggetto |
startxref | È stato impostato a 0, non è la posizione reale di xref |
/Length | Il flusso di contenuto non ha dichiarato la lunghezza |
| Marcatura binaria | Manca la riga di intestazione di identificazione binaria |
Queste sono informazioni chiave necessarie per il lettore; la loro mancanza può portare a impossibilità di apertura o apertura solo in modalità tollerante.
Dettagli delle istruzioni chiave del flusso di contenuto
Il flusso di contenuto si trova tra stream ... endstream nell'oggetto 4 0 obj, spiegato riga per riga:
1. 0. 0. 1. 50. 700. cm ← Imposta la matrice di trasformazione (nota che 1. rappresenta il numero decimale 1.0)
BT ← Inizio dell'oggetto di testo
/F0 36. Tf ← Seleziona il font F0, dimensione 36pt
(Hello, World!) Tj ← Disegna la stringa
ET ← Fine dell'oggetto di testo
Operatore di matrice di trasformazione cm
1 0 0 1 50 700 cm è una matrice di trasformazione a 6 elementi [a b c d e f], corrispondente a:
| a b 0 | | 1 0 0 |
| c d 0 | = | 0 1 0 |
| e f 1 | | 50 700 1 |
Quando a=1, b=0, c=0, d=1, questa è una matrice di traslazione pura, che sposta l'origine del sistema di coordinate (cioè il punto (0,0) delle operazioni di disegno successive) a (50, 700). Se non si sposta, l'origine predefinita è nell'angolo in basso a sinistra della pagina.
Operatori di testo
| Operatore | Significato | Esempio |
|---|---|---|
BT | Inizio testo, inizia l'oggetto di testo | BT |
ET | Fine testo, termina l'oggetto di testo | ET |
Tf | Imposta font e dimensione | /F0 36 Tf |
Tj | Disegna stringa | (Hello!) Tj |
Utilizzare pdftk per riparare in un PDF apribile
Esegui nel terminale nella directory in cui si trova hello-broken.pdf:
pdftk hello-broken.pdf output hello.pdf
Apri hello.pdf con qualsiasi lettore PDF, dovresti vedere apparire sulla pagina "Hello, World!" (font Times-Italic, 36pt, situato nell'angolo in alto a sinistra della pagina).
Cosa ha completato pdftk?
| Voce completata | Descrizione |
|---|---|
| Riga di marcatura binaria | Aggiunta una riga di caratteri non stampabili dopo %PDF-1.0, assicurando che venga riconosciuto come file binario |
/Length | Calcolata e aggiunta la lunghezza in byte per il flusso di contenuto |
Tabella xref | Calcolato l'offset in byte di ogni oggetto e riempito |
startxref | Riempito con la posizione reale di inizio della tabella xref |
Perché sono necessari xref / trailer / startxref?
Obiettivo principale: accesso casuale
Immagina un PDF di 500 pagine; senza xref, il lettore deve analizzare dall'inizio per visualizzare la pagina 450—questo è troppo lento.
Con xref, il lettore può:
- Leggere prima
startxref→ trovare la posizione di xref - Leggere
trailer→ trovare l'oggetto radice/Root - Seguire il percorso dall'oggetto radice → saltare direttamente all'oggetto della pagina 450
- Controllare l'offset in byte di quell'oggetto tramite xref → cercare direttamente e leggere
La complessità temporale scende da O(n) a O(1).
Esercizio di questo articolo
Ti consigliamo di modificare realmente hello-broken.pdf, quindi ripararlo nuovamente con pdftk e osservare gli effetti:
| Esercizio | Contenuto modificato | Punti di osservazione |
|---|---|---|
| A | Cambiare (Hello, World!) in un'altra frase in inglese | Cambiamento del testo |
| B | Cambiare 36 in 12 o 72 | Cambiamento della dimensione del font |
| C | Cambiare 50 700 in 50 100 | Spostamento verso il basso (l'origine del sistema di coordinate PDF è in basso a sinistra) |
| D | Cambiare /Times-Italic in /Helvetica o /Courier | Cambiamento del font |
| E | Cambiare /MediaBox [0 0 612 792] in [0 0 595 842] | Cambiamento della carta da US Letter a A4 |
Suggerimento: L'origine del sistema di coordinate PDF è nell'angolo in basso a sinistra della pagina, con l'asse Y che va verso l'alto.
(50, 700)significa 50pt dalla sinistra e 700pt dal fondo.
Domande frequenti
D: Perché utilizzare font Type1 incorporati invece di TrueType?
R: I 14 font standard Type1 (Times, Helvetica, Courier, ecc.) devono essere incorporati nel lettore PDF, non è necessario includere i file dei font, è la soluzione più semplice. Nella realtà, è spesso necessario incorporare i font per garantire coerenza tra piattaforme.
D: Cosa significano questi numeri in /MediaBox [0 0 612 792]?
R: L'unità è il punto (1 punto = 1/72 di pollice). 612 × 792 punti = 8.5 × 11 pollici = carta US Letter. A4 è 595 × 842 punti.
D: Cosa significa il numero di generazione (come il 0 in 2 0 R)?
R: Viene utilizzato per aggiornamenti incrementali. Quando un oggetto viene modificato, il numero di generazione aumenta di 1. Negli PDF appena creati, tutti gli oggetti hanno solitamente un numero di generazione di 0.
Prossimo articolo in arrivo
Nella Parte 02 continueremo a utilizzare il metodo "scrivere a mano il flusso di contenuto", aggiungendo le più basilari operazioni sui percorsi grafici:
m(moveto),l(lineto): definire percorsiS(stroke): tracciaturare(rectangle),f(fill): disegnare un rettangolo e riempirlo
Ti permetterà di disegnare sulla stessa pagina: testo del titolo + una linea di separazione orizzontale + un rettangolo, passando da "saper scrivere" a "saper disegnare forme".
