Inserite un report scansionato da 80 MB dietro un link, apritelo in un browser e osservate cosa succede: il visualizzatore rimane su una schermata vuota finché non arriva una gran parte di quei byte, per poi mostrare la prima pagina tutta in una volta. Passando alla pagina 40, in un file strutturato male, l'intero download potrebbe riavviarsi. La parte frustrante è che il lettore voleva solo la prima pagina. La linearizzazione è la risposta strutturale a questo problema. Riorganizza un PDF in modo che un visualizzatore possa visualizzare la pagina iniziale da un piccolo prefisso del file e recuperare il resto su richiesta, motivo per cui Adobe commercializza questa funzionalità come "Fast Web View" (Visualizzazione web rapida).
Nulla di tutto questo rappresenta un formato di file diverso. Un PDF linearizzato è un normale PDF che un lettore conforme aprirà senza alcuna gestione speciale. Il trucco risiede interamente nel modo in cui i byte sono ordinati e in due strutture aggiuntive che il file contiene. Lo standard ISO 32000-1 specifica l'intera disposizione nell'Allegato F, e una volta compreso il layout, questo comportamento smette di sembrare magia e inizia ad apparire come un deliberato compromesso tra l'ordine del file e la latenza della prima visualizzazione.
Cosa riorganizza effettivamente la linearizzazione
Un normale PDF può distribuire i suoi oggetti in quasi qualsiasi ordine. La tabella dei riferimenti incrociati alla fine del file è ciò che rende possibile questo funzionamento: un lettore si sposta alla fine, legge il puntatore startxref, carica la tabella xref e da lì può individuare ogni oggetto tramite il suo offset. Questo design è eccellente per i file locali, dove lo spostamento alla fine non costa nulla, ma è inefficiente per un file trasmesso in streaming su una rete, dove la fine è proprio la parte che arriva per ultima. Per visualizzare la prima pagina, un lettore convenzionale ha bisogno dell'oggetto pagina, del suo stream di contenuti, dei font a cui fa riferimento e di qualsiasi immagine che disegna; in un file non ordinato, questi elementi possono trovarsi ovunque, incluso l'ultimo megabyte.
La linearizzazione corregge l'ordine. Gli oggetti necessari per visualizzare la prima pagina vengono raggruppati in un blocco contiguo all'inizio del file, subito dopo una piccola sezione di intestazione, in modo da arrivare presto nel flusso di byte. Tutto il resto, ovvero le pagine rimanenti e le risorse condivise, segue in una sequenza prevedibile. Una seconda tabella completa dei riferimenti incrociati si trova ancora alla fine per i lettori che ignorano l'ottimizzazione, ma un file linearizzato posiziona all'inizio anche i riferimenti incrociati della prima pagina e i parametri necessari per un lettore in streaming. Il lettore non deve più raggiungere la fine del file prima di poter visualizzare qualcosa.
Il set di oggetti della prima pagina e il dizionario dei parametri di linearizzazione
Il primissimo oggetto in un file linearizzato, dopo l'intestazione %PDF, è il dizionario dei parametri di linearizzazione. È ciò che un lettore in streaming cerca per determinare se l'ottimizzazione è presente e come utilizzarla. Il dizionario registra la lunghezza dell'intero file, l'offset di byte in cui inizia la sezione principale dei riferimenti incrociati, il numero dell'oggetto della prima pagina, nonché la posizione e la lunghezza del flusso di suggerimenti (hint stream) successivo. Con questi numeri, il lettore sa, fin dai primi kilobyte, quanto deve scaricare per mostrare la prima pagina e dove cercare l'indice che gli consente di saltare altrove.
L'Allegato F è rigoroso su ciò che si intende per "prima pagina". La sezione della prima pagina deve contenere l'oggetto pagina stesso, i suoi flussi di contenuti e le risorse a cui tali flussi fanno riferimento, in modo che la pagina sia autosufficiente una volta scaricato questo prefisso. Le risorse condivise, come un font utilizzato in ogni pagina o un logo che si ripete in un'intestazione, vengono gestite in modo speciale: appaiono abbastanza presto per servire la prima pagina, ma vengono contrassegnate come condivise affinché il lettore non debba scaricarle nuovamente quando successivamente visualizzerà la pagina 30. Questa distinzione tra oggetti privati della pagina e oggetti condivisi è la parte che la maggior parte degli "ottimizzatori" artigianali sbaglia, e questo errore produce un file che dichiara di essere linearizzato ma che in realtà continua a bloccarsi.
Flussi di suggerimento (hint streams): l'indice che rende veloci i salti di pagina
Mostrare rapidamente la prima pagina è solo metà del valore. L'altra metà consiste nel saltare a una pagina qualsiasi senza scaricare tutto ciò che si trova nel mezzo, ed è proprio questo che offrono i flussi di suggerimento. Un file linearizzato contiene una tabella di suggerimenti per l'offset di pagina e una tabella di suggerimenti per gli oggetti condivisi, memorizzate come uno stream a cui fa riferimento il dizionario dei parametri. La tabella degli offset di pagina registra, per ogni pagina, dove iniziano i suoi oggetti nel file e quanto durano. La tabella degli oggetti condivisi fa lo stesso per le risorse utilizzate in più pagine.
Grazie a queste tabelle, un lettore che richiede la pagina 40 non analizza il file in modo sequenziale. Consulta la tabella dei suggerimenti per conoscere l'intervallo di byte occupato dalla pagina 40, richiede al server esattamente quell'intervallo e visualizza la pagina non appena arrivano quei byte, recuperando eventuali risorse condivise non ancora in suo possesso tramite lo stesso meccanismo. Il flusso di suggerimenti è, in effetti, una mappa ad accesso casuale sovrapposta al documento, ed è il motivo per cui un file di 500 pagine ben linearizzato risulta reattivo su una connessione lenta, mentre un file non ottimizzato delle stesse dimensioni non lo è.
Perché il server deve cooperare
La linearizzazione presuppone che il protocollo di trasporto possa fornire porzioni arbitrarie del file, ed è opportuno verificare questa ipotesi prima di attribuire al formato la responsabilità di scarse prestazioni. Il meccanismo utilizzato è il byte-serving HTTP: il lettore invia richieste di intervalli di byte (range requests) e il server risponde con codici 206 Partial Content. Se il server non dichiara Accept-Ranges: bytes, o se un proxy o una CDN a monte convertono le range requests in trasferimenti completi, il lettore non ha modo di recuperare la pagina 40 in isolamento e deve scaricare l'intero file. In questo caso, la struttura interna del PDF è perfettamente corretta ma del tutto sprecata.
Questo è l'errore che più spesso viene diagnosticato erroneamente come "la linearizzazione non funziona". Il file è corretto; è il percorso di distribuzione a non esserlo. Prima di ricompilare un documento, verificate con una richiesta condizionale che l'host restituisca effettivamente contenuti parziali per l'URL richiesto dal lettore. Molti host statici lo fanno per impostazione predefinita, mentre molti server applicativi e livelli di cache non configurati correttamente non lo fanno.
Gli aggiornamenti incrementali compromettono silenziosamente la linearizzazione
Ecco il vincolo che sorprende chi genera correttamente file linearizzati e poi si chiede perché l'ottimizzazione svanisca. La linearizzazione dipende da un singolo layout accuratamente ordinato con il suo indice all'inizio. Un aggiornamento incrementale viola questo principio per progettazione. Quando uno strumento aggiunge una firma, compila un campo modulo o aggiunge un'annotazione tramite un salvataggio incrementale, non riscrive il file. Aggiunge invece gli oggetti modificati, una nuova sezione di riferimento incrociato e un nuovo trailer alla fine del file, lasciando intatti i byte originali. Questa aggiunta alla fine è proprio lo scopo degli aggiornamenti incrementali: è veloce e conserva la versione precedente per scopi di audit o validazione della firma.
L'effetto collaterale è che il file ora contiene i dati di riferimento incrociato più recenti alla fine, dopo il blocco della prima pagina posizionato con cura, e il dizionario dei parametri di linearizzazione all'inizio descrive un layout che non corrisponde più al file. Un lettore conforme rileva questa discrepanza e tratta il documento come un normale PDF non linearizzato. La funzione Fast Web View svanisce, anche se la struttura linearizzata originale si trova ancora nella prima metà del file. Se si aggiungono diversi aggiornamenti, ciascuno di essi accumula un'altra revisione alla fine e il divario tra l'indice obsoleto all'inizio e lo stato reale del file si allarga.
Se il vostro flusso di lavoro richiede sia le modifiche sia Fast Web View, la regola deriva direttamente dalla struttura: modificate in modo incrementale mentre il documento è in fase di lavorazione, quindi linearizzate nuovamente una sola volta alla fine. Una riscrittura completa è ciò che ripristina il layout. In termini di HotPDF, ciò significa che una modifica in corso passa attraverso BeginIncrementalUpdate e SaveIncrementalUpdate, che aggiungono una variazione (delta), mentre la fase finale carica l'intero documento e lo serializza da zero con LoadFromFile seguito da SaveLoadedDocument, che elimina le vecchie revisioni accumulate ed emette un unico layout pulito. Lo stesso compromesso si presenta con gli stream di oggetti: abilitare UseObjectStreams insieme a UseXRefStream comprime i riferimenti incrociati e raggruppa gli oggetti strettamente, migliorando le dimensioni del file. Tuttavia, come ogni scelta strutturale, questa operazione deve essere applicata durante la riscrittura finale anziché essere aggiunta a una revisione successiva.
// In-flight edits: append a delta, keep prior revisions intact.
// This leaves the file NOT linearized.
Pdf.BeginIncrementalUpdate('report.pdf');
Pdf.AddPage;
Pdf.CurrentPage.TextOut(72, 760, 0, 'Addendum');
Pdf.SaveIncrementalUpdate('report.pdf');
// Finishing step: full re-serialization produces one clean layout,
// dropping the stacked revisions. Re-run your linearizer on the output.
Pdf.LoadFromFile('report.pdf');
Pdf.SaveLoadedDocument('report-final.pdf');
HotPDF non espone una routine di linearizzazione con una singola chiamata, quindi l'approccio pratico consiste nel generare un file pulito e completamente riscritto e poi eseguire su di esso un ottimizzatore dedicato. Gli strumenti da riga di comando gestiscono direttamente la riorganizzazione. qpdf riscrive un file in forma linearizzata con un singolo flag:
qpdf --linearize report-final.pdf report-web.pdf
Come verificare se un file è linearizzato
Non fidatevi del nome del file o dello strumento che dichiara di averlo generato; verificate i byte. Il controllo più diretto è all'inizio del file: apritelo e cercate il dizionario dei parametri di linearizzazione come primo oggetto dopo l'intestazione, contrassegnato dalla chiave /Linearized. Una scorciatoia per l'utente consiste nell'aprire la finestra Proprietà documento di Acrobat, che indica "Visualizzazione Web rapida: Sì" solo quando la struttura è realmente presente e aggiornata.
Per i controlli automatizzati tramite script, qpdf segnala sia la presenza sia l'integrità della struttura, un aspetto importante poiché un file può contenere un dizionario di linearizzazione che non riflette più il suo layout effettivo, esattamente lo stato in cui si trova dopo un aggiornamento incrementale:
# Reports "File is linearized" and validates hint tables against the layout
qpdf --check report-web.pdf
# Dumps the linearization parameters and hint data in detail
qpdf --show-linearization report-web.pdf
La fase di validazione è quella che fa davvero la differenza. Un controllo che si limita a confermare l'esistenza del dizionario accetterà senza problemi un file il cui indice punta a offset errati; una verifica che riconcilia le tabelle di suggerimento con le posizioni reali degli oggetti è ciò che vi garantisce che l'ottimizzazione funzionerà correttamente con le range requests di un lettore reale.
La linearizzazione rimane un'opzione consigliata per qualsiasi documento di grandi dimensioni distribuito via web, in particolare per i lettori su dispositivi mobili con connessioni instabili, al costo di una percentuale minima di aumento delle dimensioni del file per l'indice caricato all'inizio. I due aspetti chiave da tenere a mente sono che la struttura interna del PDF e il byte-serving lato server devono essere entrambi corretti, e che qualsiasi modifica successiva annulla l'ottimizzazione finché il file non viene riscritto. Considerate la re-linearizzazione come l'ultima fase della pipeline, da eseguire solo dopo aver stabilizzato qualsiasi altra modifica. Il comportamento dei riferimenti incrociati, degli stream di oggetti e degli aggiornamenti incrementali qui descritto fa parte del modello strutturale implementato dal componente HotPDF per Delphi e C++Builder; per ulteriori informazioni sul layout dei file, consultate come è strutturato un PDF, e per il flusso di lavoro relativo agli aggiornamenti incrementali e ai file di grandi dimensioni nel codice, consultate elaborazione di file PDF di grandi dimensioni in Delphi.