Usiamo i documenti PDF tutti i giorni, ma vi siete mai chiesti come sono fatti dentro, cioè come è strutturato un file PDF. Un po' di anni fa ho provato a smanettarci perché mi serviva creare appunto dei file PDF da un gestionale che avevo scritto. Ripubblico qui i miei appunti di allora per proporre una breve analisi di un documento PDF, evidenziandone la struttura e gli elementi costitutivi e affrontando quindi le problematiche relative alla gestione dei font, della grafica vettoriale e delle immagini raster.
Gli elementi di base
Un file PDF è sostanzialmente un file di testo, inteso come sequenza di caratteri e separatori di linea (ASCII 13 o ASCII 10), di tipo strutturato, ovvero in cui le informazioni assumono un particolare significato in quanto inserite in strutture che rispettano una particolare sintassi.
Gli elementi di base sono gli oggetti (obj), che all’occorrenza possono contenere sequenze (stream), coppie chiavi/valore (dictionary) o altro. Un oggetto può rappresentare una pagina, un’immagine, una sequenza grafica, ecc.
Ogni oggetto, racchiuso tra le parole chiavi obj e endobj è identificato da un numero e da una revisione. Per semplicità non prenderemo in considerazione le modifiche di file creati in precedenza quindi tutti i nostri oggetti avranno come numero di revisione 0 (zero).
2 0 obj
.. .. .. .. ..
endobj
Gli oggetti non devono necessariamente presentarsi nell'ordine numerico ed è possibile fare riferimento ad un oggetto “futuro”, ovvero non ancora definito; ciò risulta particolarmente utile e forse indispensabile in alcuni casi (ad esempio quando nel file è necessario indicare la lunghezza di un testo prima che il testo stesso sia stato inserito). Quando è necessario effettuare un riferimento ad un oggetto, basta indicare il suo numero e la revisione seguiti dal carattere “R”.
.. ..
/Parent 3 0 R
.. ..
In generale, ogni qualvolta si rende necessario utilizzare più volte lo stesso oggetto in più punti del documento, sia esso una immagine o una generica risorsa, ai fini dell’occupazione di memoria e della velocità di visualizzazione, conviene creare un oggetto che contenga la risorsa e utilizzare riferimenti a questo in tutti i punti in cui ricorre.
Le sequenze di dati sono racchiuse tra le parole chiave stream e endstream. Possono contenere qualsiasi sequenza di caratteri (anche quelli non stampabili) e servono a descrivere un testo, un’immagine o altro.
stream
.. sequenza di caratteri ..
endstream
I dizionari sono coppie variabile/valore racchiuse tra i delimitatori « e ». Vengono utilizzati per caratterizzare particolari oggetti, definendone gli attributi. Un valore può essere espresso con una costante numerica (la parte decimale prevede il punto e non la virgola), una stringa alfanumerica (delimitata da una coppia di parentesi tonde), con un ulteriore dizionario, con un riferimento ad un oggetto o con un array (delimitato da una coppia di parentesi quadre).
<< /Type /Page
/Parent 3 0 R
/Resources <<
/ProcSet 6 0 R >>
/MediaBox [0 0 612 792]
.. .. ..
>>
Un file PDF non è altro quindi che una opportuna sequenza di oggetti, costruiti rispettando una sintassi abbastanza semplice (facendo attenzione a maiuscole e minuscole), legati fra loro da riferimenti specifici, corredato da quanto necessario all’applicativo che legge il file (ad esempio Acrobat Reader) per sapere dove recuperare le informazioni ed in quale ordine.
Struttura di un file PDF
La struttura fisica di file PDF prevede 4 sezioni principali e può essere riassunta nel seguente schema:
HEADER |
BODY |
CROSS-REFERENCE TABLE |
TRAILER |
La sezione HEADER contiene informazioni utili al software Reader per identificare il tipo di file e lo standard PDF utilizzato. E’ rappresentata dalla sola prima riga del file ed è del tipo
%PDF-1.3
ove il simbolo % indica in genere una riga di commento e 1.3 indica che per leggere correttamente le informazioni contenute nel file è necessario Acrobat Reader 4.0 (piuttosto che 1.4 per il quale è necessario Acrobat Reader 5.0, piuttosto che 1.5 per Acrobat Reader 6.0 e così via).
Nel seguito, noi considereremo di operare sempre con formati compatibili con Acrobat Reader 4.0 ovvero con una specifica %PDF-1.3.
La sezione BODY contiene gli oggetti che saranno rappresentati sulle pagine e sui quali ci soffermeremo in seguito.
La sezione CROSS-REFERENCE TABLE è una tabella che riporta un riferimento ad ogni oggetto presente nella sezione BODY e alla sua eventuale revisione; in particolare indica quanti oggetti sono descritti e per ogni oggetto la posizione del primo carattere della definizione di un oggetto rispetto all’inizio del file e il numero di revisione a cui si riferisce.
xref
0 23
0000000019 65535 f
0000000009 00000 n
.. .. ..
0000000300 00000 n
0000000384 00000 n
La sezione TRAILER indica al Reader quanti oggetti sono presenti nella sezione BODY (/Size), qual è l’oggetto iniziale (/Root), quale oggetto contiene le informazioni generali del documento, quali autore, titolo, data di creazione (/Info), dove trovare la CROSS-REFERENCE TABLE ed inoltre segna la fine del file (%%EOF).
trailer
<< /Size 7
/Root 1 0 R
/Info 2 0 R
>>
startxref
408
%%EOF
Struttura logica di un documento PDF
La struttura logica di un documento PDF può essere riassunta nel seguente schema:
L’oggetto CATALOG rappresenta la radice dell’intero documento e deve essere quello a cui punta il riferimento /Root presente nella sezione TRAILER.
1 0 obj
<< /Type /Catalog
/Pages 3 0 R
/Outlines 20 0 R
>>
endobj
A sua volta, contiene un riferimento alla radice delle pagine (/Pages) e un riferimento alla radice della struttura ad albero che fa da indice (/Outlines), quella che, quando si apre un documento con Acrobat Reader, appare di solito a sinistra della pagina e permette di muoversi velocemente nel documento, e che noi per semplicità non analizzeremo.
L’oggetto PAGES rappresenta la radice delle pagine, indica il numero complessivo delle pagine del documento (/Count) e riporta un riferimento all’oggetto che contiene ogni pagina (/Kids).
3 0 obj
<< /Type /Pages
/Count 3
/Kids [4 0 R 8 0 R 10 0 R]
>>
endobj
L’oggetto PAGE riporta un riferimento alla propria radice (/Parent), un elenco delle risorse utilizzate nella pagina (/Resources, vedremo in seguito cosa sono), un array con le dimensioni del formato di stampa previsto (/MediaBox) ed infine un riferimento all’oggetto che contiene gli elementi da rappresentare sulla pagina (/Contents).
4 0 obj
<< /Type /Page
/Parent 3 0 R
/Resources << /ProcSet [/PDF /Text] >>
/MediaBox [0 0 595.2 842]
/Contents 5 0 R
>>
endobj
Se nel documento si vogliono riportare informazioni relative all’autore, all’applicativo che ha generato il file o la data di creazione, si può utilizzare il seguente oggetto (facendo attenzione alle parentesi che fanno parte della sintassi). Queste informazioni appaiono se dal Reader si visualizzano le proprietà del documento.
2 0 obj
<< /Title (titolo_del_documento)
/Author (autore_del_documento)
/Creator (applicativo_generatore)
/Producer (info_copyright)
/CreationDate (D:yyyymmddhhmmss+01'00')
>>
I tipi di dati e gli elementi della sintassi
Valori logici
I valori ammessi sono:
true
false
Valori numerici
Possono essere interi o reali e le rappresentazioni ammesse sono del tipo
123
43445
+17
.98
0
34.5
-3.62
+123.6
-4.
-.002
0.0
(non sono ammesse rappresentazioni in basi diverse, esadecimali o altro, e non sono ammesse rappresentazioni esponenziali tipo 6.02E23).
Stringhe di testo
Le stringhe sono delle sequenze di byte (0-255) ed è possibile rappresentarle in due diversi modi: come una sequenza di caratteri, racchiusa da parentesi ( ); come una sequenza di dati esadecimali, racchiusa tra parentesi angolari < >; Esempio:
(questa è una stringa)
<4E6F762073686D6F7A206B6120706F702E>
Array
le liste di dati (array), sono delle sequenze, di valori di qualsiasi tipo, racchiuse da parentesi quadre [];
[549 3.14 false (stringa) /Objname]
Le unità di misura
L’unità di misura dello standard PDF è 72 punti per pollice. Ovviamente con le opportune conversioni, è possibile utilizzarne altre (centimetri, millimetri e pollici). Per quanto riguarda le cifre decimali, lo standard prevede 3 cifre sul valore assoluto in unità di misura standard, e non dovrebbe essere necessario andare oltre.
Il sistema di coordinate
Il sistema di coordinate predefinite in documento PDF ha come unità di misura il punto, definito come 1/72 di pollice. L’origine degli assi è posta nell’angolo in basso a sinistra. In questo sistema, il normale foglio A4 (21×29.7 cm) ha dimensione 595.2 x 842 punti. Per default tutte le immagini, a meno di una esplicita traslazione/scalatura/rotazione, hanno dimensione 1×1 e sono poste con lo spigolo inferiore sinistro nell’origine (0,0).
Gli operatori
Per la definizione degli elementi contenuti in ogni pagina, la sintassi dello standard PDF prevede l’uso di alcuni operatori. Di seguito sono elencati solo alcuni (in fondo trovate il riferimento alle specifiche complete). In particolare, bisogna tener conto che gli operatori grafici descrivono solo un percorso (path) che verrà tracciato fisicamente soltanto quando verrà utilizzato un apposito operatore e che le componenti di colore vanno da 0 a 1 e non da 0 a 255.
Operatore | Descrizione |
---|---|
X Y m | Muove il punto di inserimento grafico alle coordinate (X,Y) |
X Y l | Aggiunge una linea dal punto corrente al punto (X,Y) alla path |
X w | Imposta la larghezza della linea a X punti |
h | Chiude una path |
n | Termina una path |
f | Colora la regione delimitata |
r g b RG | Imposta il colore per i contorni |
r g b rg | Imposta il colore per le aree |
gray G | Imposta un tono di grigio per i contorni |
gray g | Imposta un tono di grigio per le aree |
x1 y1 x2 y2 x3 y3 c | Aggiunge una curva Bezier alla path |
x y width height re | Aggiunge un rettangolo alla path |
W 0 0 H X Y cm | Matrice di trasformazione grafica di coordinate |
S | Traccia la path |
s | Chiude e traccia la path |
B | Traccia la path e colora la regione |
BT | Inizia una sequenza di testo |
ET | Termina una sequenza di testo |
space Tc | Imposta la spaziatura tra caratteri in millesimi di punto |
space Tw | Imposta la spaziatura tra parole in millesimi di punto |
scale Tz | Imposta la scalatura percentuale dei caratteri |
space TL | Imposta l’interlinea |
X Y Td | Imposta il punto di inserimento del testo |
fontname size Tf | Imposta il font e la dimensione del carattere |
string Tj | Visualizza la stringa con il font corrente |
/name Do | Esegue l’oggetto name |
Le path (percorsi)
Le path rappresentano dei tracciati normalmente invisibili, che diventano visibili solo a seguito di un opportuno comando. Spieghiamo meglio questo concetto con un esempio.
In un normale contesto grafico, se voglio tracciare una spezzata, costituta quindi da più linee, traccio fisicamente il primo tratto, poi il secondo, e così via. In un documento PDF, invece, preparo il primo tratto (ovvero costruisco un percorso invisibile che lo descrive), poi il secondo e così via e dopo l’ultimo tratto lancio un operatore che traccia fisicamente l’intera spezzata. Questo modo di operare è dovuto al fatto che il percorso (path) descritto potrebbe essere utilizzato non per tracciare fisicamente sulla pagina una spezzata, ma magari per delimitare (clippare) un’altra porzione di grafica successiva, racchiudere un testo, ecc.. .
Gli elementi di uso comune
Font e testo
Uno dei primi oggetti che analizziamo è l’oggetto necessario a descrivere un font. In questa fase vedremo come utilizzare uno dei 14 fonti TYPE1 predefiniti nel linguaggio PDF, ovvero font che il Reader conosce già e che non necessitano di particolari informazioni (viceversa, per altri tipi di font TrueType occorre fornire informazioni dettagliate, come ad esempio la larghezza in punti di ogni singolo carattere).
- CourierNew (.Italic, .Bold, .BoldItalic)
- Arial (.Italic, .Bold, .BoldItalic)
- TimesNewRoman (.Italic, .Bold, .BoldItalic)
- ZapfDingbats
- Symbol
Per poterne utilizzare uno, occorre creare un oggetto che ne descrivi le proprietà. Nell’esempio che segue, l’oggetto 7 contiene il font Arial con attributi Grassetto e Corsivo, al font viene dato il nome Fn1 e come tabella caratteri viene indicata quella ANSI.
7 0 obj
<< /Type /Font
/Subtype /Type1
/Name /Fn1
/BaseFont /Arial.BoldItalic
/Encoding /WinAnsiEncoding
>>
endobj
Quindi volendo scrivere il classico “Hello World” alla posizione di coordinate (100,400) con il font definito in precedenza di nome Fn1, basterà inserire in un oggetto la sequenza
% Scrive Hello World in Arial Bold Italic 24 punti BT /Fn1 24 Tf 100 400 Td (Hello World) Tj ET
Grafica vettoriale
Se vogliamo inserire degli elementi grafici, basterà inserire una sequenza del tipo
% Traccia un rettangolo rosso pieno con contorno blue .5 .75 1 rg 1 0 0 RG 200 300 50 75 re B % Traccia una linea con spessore 2 punti 2 w 150 250 m 150 350 l S
Immagini raster
Un altro oggetto di uso comune è quello necessario per rappresentare delle immagini di tipo raster (BMP). Per le immagini è possibile optare per due modalità: creare un oggetto che può essere richiamato ogni volta che la stessa immagine deve essere visualizzata, anche se con dimensioni diverse (ed è questo il caso che noi analizzeremo) o definire l’immagine all’interno della pagina senza la possibilità di poterla riutilizzare.
Ad esempio, se si definisce una immagine Img1, 10 pixel per 5, 24 bit colore (8 x 3 componenti colore RGB), utilizzando una rappresentazione esadecimale, sono necessari 150 elementi, ovvero 10 x 5 x 3.
12 0 obj << /Type /XObject /Subtype /Image /Name /Img1 /Width 10 /Height 5 /BitsPerComponent 8 /ColorSpace /DeviceRGB /Filter /ASCIIHexDecode" /Length 13 0 R >> stream 80 A1 2F .. .. %150 campioni esadecimali endstream endobj 13 0 obj 150 endobj
Notare come al posto della lunghezza dello stream è stato utilizzato un riferimento ad un oggetto (13 0) che contiene (solo) il valore 150.
Se vogliamo visualizzare l’immagine con l’angolo inferiore sinistro nel punto (100, 80), con una dimensione orizzontale 200 e una verticale di 300, sarà sufficiente inserire la sequenza
q % salva lo stato grafico corrente 200 0 0 300 100 80 cm % matrice di trasformazione di coordinate % per spostare/ingrandire l’immagine /Img1 Do % visualizza l’immagine Img1 Q % ripristina lo stato grafico precedente
Analogamente la sequenza
.. ..
/BitsPerComponent 8
/ColorSpace /DeviceGray
/Length 50
>>
stream
$-#etT .. .. %50 byte
endstream
endobj
individua un’immagine in toni di grigio (1 sola componente colore), rappresentata con una sequenza di 50 byte (10x5x1)
Le Form
Capita spesso che porzioni di documenti vadano sia ripetute all'interno di un documento, come ad esempio le intestazioni, i piè di pagina, i watermark (quelle scritte oblique sotto al testo tipo bozza, uso interno,…).
Lo standard PDF prevede l’uso di un particolare oggetto, appunto le form, (non hanno nulla a che fare con le form di una pagina web). Ad esempio tutto ciò che è contenuto nello stream dell’oggetto 13 con nome Frm1,
13 0 obj
<< /Type /XObject
/Subtype /Form
/FormType 1
/Name /Frm1
/BBox [0 0 595.2 842]
/Matrix [1 0 0 1 0 0]
/Length 14 0 R >>
stream
.. .. .. .. ..
endstream
endobj
può essere riprodotto, ovunque, con la sequenza
/Frm1 Do
E' possibile generare un PDF in modo semplice ?
Come abbiamo visto, gli elementi principali di un documento PDF sono gli oggetti.
Volendo scrivere un documento PDF da zero, carattere dopo carattere, abbiamo la necessità quindi di costruire un meccanismo che ci permetta di gestire la scrittura di ogni oggetto e contestualmente la gestione della sezione CROSS-REFERENCE TABLE, in cui, come già detto, va scritto un riferimento all’oggetto creato.
Non bisogna dimenticarsi inoltre che ci sono alcuni oggetti che in un ordine logico andrebbero prima di altri, per esempio la radice delle pagine /Pages, anche se contengono riferimenti alle pagine che saranno disponibili solo dopo che tutti gli oggetti saranno stati definiti.
Per risolvere i problemi evidenziati, si può pensare di utilizzare una struttura ed una numerazione degli oggetti tale che i riferimenti ad alcuni di essi siano fissi e conosciuti a priori e sfruttare la possibilità data dallo standard PDF che gli oggetti non siano necessariamente disposti in ordine di riferimento.
Così facendo, il documento viene costruito in modo tale che l’oggetto /Pages ( ed in modo simile gli altri oggetti principali) ha sempre lo stesso numero di riferimento, anche se viene fisicamente costruito dopo gli altri. In altre parole, la struttura tipo del documento generato è la seguente
1 0 obj Info
2 0 obj Catalog
3 0 obj Encoding
6 0 obj (//disponibile//)
7 0 obj (//disponibile//)
.. .. .. ..
.. .. .. .. ..
n-1 0 obj (//disponibile//)
n 0 obj (//disponibile//)
4 0 obj Pages
5 0 obj Resource
in questo modo il riferimento agli oggetti intermedi, ad esempio il /Parent presente in ogni oggetto /Page, è sempre noto, mentre gli altri possono essere costruiti a mano a mano che gli oggetti vengono creati e inseriti alla fine nella definizione degli oggetti 4 e 5.
L’oggetto Resources
Nella prima parte abbiamo accennato che ogni oggetto /Page contiene un riferimento ad un oggetto /Resources, un dictionary che descrive sostanzialmente il contenuto e le risorse utilizzate nel documento, ovvero l’elenco dei Font utilizzati e gli oggetti Form, e descrive il contenuto del documento. Ad esempio
4 0 obj
<< /Type /Pages
/Resources 5 0 R
.. ..
>>
endobj
5 0 obj
<< /Font <</Fnt1 6 0 R /Fnt2 7 0 R >>
/ProcSet %%[/%%PDF /Text /ImageB /ImageC /ImageI]
/XObject <</Img1 8 0 R /Frm1 9 0 R >>
>>
endobj
l’oggetto 5 0 precisa che il documento utilizza due font (Fnt1 e Fnt2) rappresentati rispettivamente dagli oggetti 6 0 e 7 0, un oggetto immagine Img1 (oggetto 8 0) ed un oggetto form (oggetto 9 0). Inoltre indica che all’interno del documento ci sono degli operatori standard (/PDF), degli oggetti di tipo testo (/Text), delle immagini in scala di grigio (/ImageB), delle immagini a colori RGB (/ImageC) e delle immagini a tavolozza indicizzata (/ImageI).
Conclusioni
Indipendentemente dal linguaggio che si vuole utilizzare, quindi è possibile scrivere su un file di testo le informazioni nella giusta sequenza per generare in proprio un documento PDF.
Per chi fosse interessato, da questo link
è possibile scaricare un progetto di esempio che non fa altro che implementare quanto fin qui abbiamo visto. Scritto volutamente in un linguaggio che fosse il più semplice possibile (VB.NET), il codice non fa uso di alcuna particolare funzione oltre il puro linguaggio basic. Per quanti volessero cimentarsi modificando il software proposto, potrebbe essere interessante ampliarne le funzioni o riscriverlo in un altro linguaggio.
Per approfondimenti, le specifiche del formato PDF 1.7 sono invece qui: https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/pdfreference1.7old.pdf
Commenti
Posta un commento