Rimuovendo le descrizioni delle pagine si rimane con un sottile livello di struttura che nessuno stampa, ma da cui dipende ogni lettore, indicizzatore e sistema di archiviazione. Un oggetto pagina non sa nulla del capitolo a cui appartiene, dell'autore che lo ha scritto o della nota a piè di pagina che rimanda a un altro punto. Questa conoscenza vive a un livello superiore, in tre strutture collegate al catalogo del documento: i flussi di metadati, l'albero di outline (i segnalibri) e gli array di annotazioni di ciascuna pagina. Queste strutture condividono una caratteristica che le rende facili da sbagliare: nessuna di esse lascia segni visibili sulla pagina, quindi un file può essere visualizzato perfettamente ma risultare privo di segnalibri, in contrasto con il proprio campo autore o contenere un collegamento che punta a un oggetto pagina non più esistente
Questo è il livello che una libreria PDF espone come proprietà del documento, API dei segnalibri e chiamate di link o annotazione, ed è lo stesso livello letto dai crawler dei motori di ricerca per determinare l'argomento del documento. Il modello a oggetti sottostante è trattato nella guida approfondita alla struttura dei documenti PDF. In questa sede, l'attenzione è rivolta esclusivamente a ciò che dipende dal catalogo
Tutte e tre le strutture si collegano al catalogo. Un catalogo completo che le collega tra loro si presenta così:
1 0 obj
<< /Type /Catalog
/Pages 2 0 R
/Outlines 3 0 R
/Names << /EmbeddedFiles 4 0 R >>
/Metadata 5 0 R
>>
endobj
Quattro voci, quattro sottosistemi indipendenti. /Pages è il documento visibile; /Outlines è l'albero dei segnalibri; /Metadata punta al flusso XMP; /Names raggiunge il dizionario dei nomi a livello di documento, che tra le altre cose contiene gli allegati incorporati. Ognuna di queste voci è opzionale, e un lettore che non ne trova nessuna mostrerà comunque le pagine. Questa opzionalità è esattamente il motivo per cui il livello di navigazione è il primo a deteriorarsi quando un file viene modificato con strumenti che comprendono solo le pagine
Due archivi di metadati in disaccordo
Il formato PDF conserva i metadati del documento in due punti contemporaneamente, e i problemi sorgono quando questi dicono cose diverse. Il meccanismo originale è il dizionario delle informazioni del documento (Info dictionary), a cui fa riferimento la voce /Info nel trailer: un insieme piatto di coppie chiave-valore per /Title, /Author, /Subject, /Keywords, /Creator, /Producer e le due date. È semplice e viene letto da ogni visualizzatore. Il PDF 2.0 depreca la maggior parte di questo dizionario a favore del secondo meccanismo, il flusso di metadati XMP
XMP è un documento XML autonomo, scritto in RDF, memorizzato come un flusso (stream) che il catalogo raggiunge tramite /Metadata e contrassegnato con /Type /Metadata /Subtype /XML. A differenza del dizionario Info, sepolto all'interno della struttura a oggetti del PDF, un pacchetto XMP è progettato per essere estratto e analizzato in modo indipendente da strumenti che non sanno nulla del formato PDF. Ecco un esempio rappresentativo di pacchetto:
5 0 obj
<< /Type /Metadata /Subtype /XML /Length 1235 >>
stream
<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>
<x:xmpmeta xmlns:x="adobe:ns:meta/">
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<rdf:Description rdf:about=""
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:xmp="http://ns.adobe.com/xap/1.0/"
xmlns:pdf="http://ns.adobe.com/pdf/1.3/">
<dc:title><rdf:Alt><rdf:li xml:lang="x-default">Quarterly Report</rdf:li></rdf:Alt></dc:title>
<dc:creator><rdf:Seq><rdf:li>A. Author</rdf:li></rdf:Seq></dc:creator>
<xmp:CreateDate>2026-06-16T10:46:27+08:00</xmp:CreateDate>
<xmp:CreatorTool>Reporting Service 4.2</xmp:CreatorTool>
<pdf:Producer>losLab PDF Library</pdf:Producer>
</rdf:Description>
</rdf:RDF>
</x:xmpmeta>
<?xpacket end="w"?>
endstream
endobj
Tre dettagli in questo blocco stabiliscono se i metadati sopravvivono al contatto con i veri strumenti di elaborazione. Le istruzioni di elaborazione xpacket non sono una decorazione: inquadrano il pacchetto in modo che un estrattore possa trovarlo all'interno di un flusso di byte più grande, e un software di scrittura che omette la chiusura <?xpacket end="w"?> produce un file che si apre correttamente ma che fa fallire i validatori più rigidi. Anche i tipi di dati delle proprietà sono importanti. dc:title è un'alternativa linguistica racchiusa in rdf:Alt, mentre dc:creator è un elenco ordinato che richiede rdf:Seq; definire l'uno o l'altro come un semplice nodo di testo è l'errore XMP più comune, tollerato da molti visualizzatori fino a quando non si incontra quello che non lo accetta. I prefissi dei namespace sono convenzionali, ma gli URI a cui si associano sono normativi: un parser si basa sull'URI, non sul prefisso
La regola fondamentale con due archivi è che devono concordare. Se /Info indica come autore una persona e dc:creator ne nomina un'altra, si è distribuito un documento che risponde alla stessa domanda in due modi diversi, e quale risposta prevarrà dipenderà da quale campo viene letto dallo strumento in uso. Una libreria di solito li scrive entrambi al posto tuo, ma nel momento in cui ne modifichi uno a mano, o unisci file provenienti da generatori diversi, i due inizieranno a differire. Considera il dizionario Info come compatibilità legacy e l'XMP come la fonte di verità, e rigenera entrambi a partire da un unico insieme di valori anziché aggiornarli in modo indipendente. Per il PDF/A questo diventa un requisito di conformità: lo standard ISO 19005 impone l'uso di XMP e vieta qualsiasi proprietà Info che contraddica la sua controparte XMP
L'albero di outline dietro il pannello dei segnalibri
Quello che un visualizzatore mostra come pannello dei segnalibri è, all'interno del file, un albero di dizionari doppiamente collegati chiamato outline del documento. Il catalogo punta a un dizionario di outline radice (root outline) tramite la chiave /Outlines; la radice punta al primo e all'ultimo dei suoi elementi di primo livello; e ogni elemento è collegato ai suoi vicini e al suo genitore. Non esiste un array di segnalibri da nessuna parte. L'intera struttura viene ricostruita seguendo i riferimenti, ed è proprio per questo che un singolo collegamento interrotto può far sparire un intero ramo dal pannello senza generare alcun errore
8 0 obj % the outline root
<< /Type /Outlines /Count 4 /First 9 0 R /Last 9 0 R >>
endobj
9 0 obj % top-level: a chapter
<< /Title (Chapter 1: Results)
/Parent 8 0 R /Count 2
/First 12 0 R /Last 15 0 R >>
endobj
12 0 obj % first child
<< /Title (Introduction)
/Parent 9 0 R /Next 15 0 R
/Dest [3 0 R /XYZ 72 720 0] >>
endobj
15 0 obj % second child, last sibling
<< /Title (Methodology)
/Parent 9 0 R /Prev 12 0 R
/Dest [3 0 R /Fit] >>
endobj
Leggendo i collegamenti, gli invarianti diventano evidenti. Ogni elemento punta all'indietro verso il proprio /Parent. Gli elementi di pari livello (siblings) formano una catena tramite /Prev e /Next, con il primo elemento che omette /Prev e l'ultimo che omette /Next. Un genitore indica il primo e l'ultimo dei suoi figli tramite /First e /Last, e i figli intermedi sono raggiungibili solo scorrendo la catena degli elementi di pari livello. Se se ne sbaglia uno, il fallimento è silenzioso: un /Next non valido tronca un capitolo, un genitore il cui /Last non termina correttamente la catena lascia gli elementi orfani, e il visualizzatore renderizza solo ciò che riesce a raggiungere
Il campo /Count contiene un'informazione sullo stato che spesso sorprende. Sulla radice e su qualsiasi elemento espanso, indica il numero di discendenti attualmente visibili; su un elemento compresso, è un numero negativo il cui valore assoluto rappresenta il numero di discendenti che apparirebbero in caso di espansione. Di conseguenza, /Count non è un dato strutturale fisso dell'albero, bensì lo stato salvato (aperto o chiuso) del pannello, e un generatore che lo imposta come valore fisso positivo riaprirà ogni ramo che l'autore intendeva lasciare chiuso
Ogni elemento trova la sua utilità puntando a una posizione. La chiave /Title è ciò che il pannello mostra, mentre /Dest indica dove si atterra con un clic. Una destinazione può essere definita inline all'interno dell'elemento, come mostrato sopra, o tramite un nome che si risolve attraverso il dizionario dei nomi del documento; questa seconda opzione é preferibile quando molti segnalibri e collegamenti puntano allo stesso punto, poiché consente di correggere un eventuale spostamento del target in un unico posto. Una libreria di solito nasconde questo albero dietro un gestore della radice dell'outline e metodi per aggiungere elementi figlio; in HotPDF, il documento espone una proprietà OutlineRoot di tipo THPDFDocOutlineObject e gestisce automaticamente i collegamenti /Prev, /Next, /Parent e /Count mentre si aggiungono elementi. Conviene sfruttare questa funzionalità, poiché mantenere manualmente questi invarianti durante le modifiche è proprio la causa principale per cui gli outline si corrompono
Destinazioni: la sintassi di dove conduce un clic
Sia i segnalibri che le annotazioni di collegamento puntano a destinazioni, e una destinazione è qualcosa in più di un semplice numero di pagina. Si tratta di un array che indica un oggetto pagina e specifica, attraverso un verbo nella seconda posizione, come il visualizzatore debba inquadrarla. Il tipo più comune e più abusato è /XYZ, nella forma [pagina /XYZ left top zoom]. I suoi tre operandi sono indipendenti e ciascuno può essere null per indicare "lascia questo valore così com'è impostato nel lettore". Pertanto, [pagina /XYZ null null null] salta alla pagina senza modificare la posizione di scorrimento o lo zoom, che solitamente è ciò che si desidera da un link di tipo "vai alla pagina". I numeri sono nello spazio utente predefinito, misurati a partire dall'angolo inferiore sinistro con l'asse y che cresce verso l'alto, lo stesso sistema di coordinate utilizzato dal contenuto della pagina. Gli sviluppatori abituati al layout dello schermo misurano spesso istintivamente dall'alto, inviando così il lettore alla parte sbagliata della pagina
La famiglia /Fit scambia il posizionamento preciso con la resilienza. [pagina /Fit] adatta l'intera pagina alla finestra, [pagina /FitH top] adatta la larghezza della pagina a partire da un determinato bordo superiore e [pagina /FitR l b r t] ingrandisce un rettangolo fino a riempire la visualizzazione. Poiché queste opzioni calcolano la scala in base alla geometria della pagina piuttosto che a coordinate fisse, una destinazione /Fit si comporta in modo corretto anche dopo il ridimensionamento della pagina, mentre una destinazione /XYZ con uno zoom fisso può mostrare al lettore solo i margini del documento. Per un indice, l'uso di /FitH con la coordinata superiore della sezione resiste nel tempo molto meglio rispetto a /XYZ con uno zoom ipotizzato
Annotazioni: tutto ciò che è interattivo e non fa parte del contenuto della pagina
Un'annotazione è un oggetto che si sovrappone alla pagina senza far parte del suo flusso di contenuti (content stream). Collegamenti, note adesive, evidenziazioni, widget di moduli, icone di file allegati, timbri: sono tutte annotazioni, elencate nell'array /Annots della pagina su cui si trovano. Rimuovere un'annotazione da tale array la rimuove dalla pagina anche se il contenuto sottostante rimane intatto. Questo é il punto chiave: le annotazioni costituiscono un livello di editing separato dagli elementi grafici su cui poggiano
Ogni annotazione condivide una struttura di base. La chiave /Subtype specifica il tipo di annotazione, /Rect ne definisce il rettangolo di delimitazione (bounding box) nelle coordinate della pagina e /Contents contiene il testo che funge anche da descrizione per l'accessibilità. L'annotazione di tipo collegamento (link) merita uno studio approfondito poiché si presenta in due forme: una semplice destinazione o un'azione
12 0 obj % link to a destination
<< /Type /Annot /Subtype /Link
/Rect [100 200 300 250]
/Border [0 0 0]
/Dest [5 0 R /XYZ null null null] >>
endobj
13 0 obj % link that runs an action
<< /Type /Annot /Subtype /Link
/Rect [50 50 200 100]
/Border [0 0 0]
/A << /Type /Action /S /URI /URI (https://www.example.com) >> >>
endobj
La chiave /Rect definisce l'area attiva del collegamento; facendo clic al suo interno, il lettore viene inviato alla destinazione, riutilizzando la stessa sintassi vista per gli outline. L'istruzione /Border [0 0 0] svolge un ruolo importante poiché rimuove il fastidioso rettangolo che i visualizzatori disegnano per impostazione predefinita attorno ai link. La seconda forma sostituisce il semplice /Dest con un'azione /A, il cui sottotipo /S ne specifica il comportamento: /GoTo all'interno del file corrente, /GoToR per fare riferimento a un altro file, /URI per un indirizzo web e /Launch per avviare un programma esterno. Quest'ultima opzione richiede cautela: un'azione /Launch che avvia un file eseguibile è il comportamento che rende i PDF un potenziale vettore di malware, pertanto i visualizzatori conformi alle specifiche la bloccano o mostrano avvisi ben visibili, con il risultato che il link non funziona per la maggior parte dei lettori. È preferibile ricorrere a /URI e /GoTo, evitando l'uso di /Launch
Le annotazioni di markup (come le evidenziazioni e le note adesive) e le annotazioni di forma (come /Square) introducono un'ulteriore complicazione: il loro aspetto sullo schermo non è implicito nel loro tipo. Un visualizzatore renderizza una propria versione a meno che non si fissi l'aspetto grafico tramite un flusso di aspetto (appearance stream), definito dall'elemento /AP, che fa riferimento a un XObject modulo contenente le istruzioni grafiche di disegno. Omettendo questo flusso, lo stesso elemento evidenziato può apparire diverso in due lettori differenti, o prima e dopo una modifica del documento. Se l'aspetto grafico esatto è importante ai fini del documento, è fondamentale fornire la chiave /AP. Per inciso, anche gli allegati dei file riutilizzano questo stesso meccanismo: un flusso di file incorporato e un dizionario di specifica del file, mostrati come un'annotazione di tipo /FileAttachment o tramite l'albero dei nomi /EmbeddedFiles sotto la chiave /Names del catalogo
Dove questo livello si corrompe e come accorgersene
Il problema più frequente in tutto questo è rappresentato dai riferimenti orfani (dangling references). I segnalibri smettono di apparire se il catalogo non contiene la voce /Outlines o se una catena di elementi di pari livello si interrompe a metà dell'albero; i metadati vengono ignorati se al flusso XMP mancano le marcature /Type /Metadata /Subtype /XML o se l'involucro xpacket è malformato. In tutti questi casi il contenuto della pagina è intatto, quindi una rapida apertura del documento sembrerà corretta e il difetto emergerà solo in un pannello di navigazione che nessuno ha controllato
Due semplici abitudini consentono di individuare la maggior parte degli errori. Apri il file finale in un visualizzatore reale e scorri il pannello dei segnalibri e alcuni collegamenti di prova, in modo da testare il grafo dei riferimenti come farà un lettore. Successivamente, leggi i metadati con uno strumento esterno e verifica che il dizionario Info e il pacchetto XMP concordino, un disallineamento che nessun clic sui link potrà mai rivelare. Generando questo livello tramite una libreria che gestisce internamente i riferimenti dei link, la maggior parte di queste insidie viene evitata in partenza. Il Componente HotPDF per Delphi e C++Builder espone le strutture di outline, annotazioni e metadati attraverso API a livello di documento, consentendo di descrivere la gerarchia dei segnalibri e dei collegamenti lasciando alla libreria il compito di connettere i riferimenti. Per quanto riguarda il modello a oggetti a cui queste strutture sono collegate, la panoramica tecnica sulla struttura dei file PDF tratta del catalogo e della tabella di riferimento incrociato (cross-reference table) da cui dipendono