Aprite un PDF di 200 pagine da un link e il browser spesso resta vuoto finché non è arrivata la maggior parte del file. Fast Web View è il nome che Adobe Acrobat dà a un PDF linearizzato (ISO 32000-1, Allegato F): la prima pagina e un piccolo indice stanno all'inizio, così un visualizzatore dipinge la pagina uno in fretta e recupera le pagine successive con richieste HTTP Range. HotPDF e PDFium Component possono entrambi scrivere file linearizzati da Delphi
Nulla di tutto questo è un formato di file separato. Un PDF linearizzato è un normale PDF che qualunque lettore conforme apre senza alcuna gestione speciale; la differenza sta nell'ordine dei byte e in due strutture in più, il dizionario dei parametri di linearizzazione e l'hint stream. Due condizioni decidono se un lettore percepisce mai il beneficio. Il file deve essere linearizzato e restarlo, il che una modifica accodata annulla in silenzio, e il server web deve rispondere alle richieste di range di byte con 206 Partial Content. Su un disco locale, dove cercare la fine di un file non costa nulla, un PDF linearizzato si comporta esattamente come qualunque altro, così Fast Web View è soltanto un'ottimizzazione di rete, che ripaga con documenti grandi aperti direttamente da un URL
Cosa riorganizza un PDF linearizzato?
Un PDF linearizzato raccoglie tutto ciò che serve alla prima pagina in un blocco contiguo vicino all'inizio del file, davanti a ogni altra pagina. In un PDF ordinario gli oggetti possono stare in qualsiasi ordine, perché la tabella dei riferimenti incrociati alla fine mappa ogni numero di oggetto al suo offset di byte: un lettore salta alla coda, legge il puntatore startxref, carica la tabella e da lì può trovare qualsiasi cosa (la panoramica della struttura di un file PDF percorre quel layout di intestazione, corpo, xref e trailer). Il design è eccellente su disco e scarso in rete, dove la coda è esattamente la parte che arriva per ultima. Per disegnare la prima pagina un lettore convenzionale richiede l'oggetto pagina, il suo content stream e i font e le immagini che usa, e in un file non ordinato ognuno di questi può stare nell'ultimo megabyte
La linearizzazione corregge l'ordine. Dopo l'intestazione vengono il dizionario di linearizzazione e una piccola sezione di riferimenti incrociati della prima pagina, poi gli oggetti a livello di documento come il catalog, l'hint stream primario, e la prima pagina con tutte le sue risorse. Le pagine rimanenti seguono una per una, poi gli oggetti condivisi tra le pagine successive, e per ultimo la tabella dei riferimenti incrociati principale. Un lettore che ignora l'ottimizzazione parte comunque dalla fine e trova ogni oggetto attraverso la tabella principale, ed è per questo che un file linearizzato resta pienamente compatibile con il software più vecchio. La numerazione degli oggetti risulta così all'indietro in un editor esadecimale: il blocco frontale porta i numeri più alti, mentre tutto ciò che sta dietro è numerato a partire da 1
Il dizionario di linearizzazione e le tabelle di hint
Il primo oggetto dopo l'intestazione %PDF è il dizionario dei parametri di linearizzazione, e l'Allegato F richiede che stia entro i primi 1024 byte così un lettore in streaming lo trova nel pacchetto iniziale. La tabella F.1 elenca le sue voci: /L è la lunghezza dell'intero file in byte, /H l'offset e la lunghezza dell'hint stream primario, /O il numero di oggetto della prima pagina, /E l'offset in cui la prima pagina finisce, /N il numero di pagine, e /T l'offset della prima voce nella tabella dei riferimenti incrociati principale. La voce /L pesa più di quanto sembri. Quando non quadra con la dimensione reale del file, la tabella F.1 dice al lettore di trattare il documento come un PDF ordinario e di ignorare ogni hint al suo interno
L'hint stream è ciò che rende economico un salto alla pagina 40. Contiene una tabella di hint degli offset di pagina, che registra per ogni pagina dove iniziano i suoi oggetti e quanti byte coprono, e una tabella di hint degli oggetti condivisi che fa lo stesso per font, loghi e altre risorse usate su più pagine. Un lettore che vuole la pagina 40 consulta il suo intervallo di byte, richiede esattamente quella porzione e la renderizza appena arriva, tirando allo stesso modo qualsiasi oggetto condiviso che non abbia già. Due dettagli fanno inciampare i linearizzatori artigianali. Gli offset degli hint sono logici, calcolati come se l'hint stream stesso fosse assente, così un writer che memorizza offset fisici manda fuori tragitto ogni richiesta di range successiva della dimensione dell'oggetto hint. E un writer che segue il collegamento /Parent di una pagina mentre ne raccoglie le risorse risale l'albero delle pagine e trascina l'intero documento nella sezione della prima pagina. Entrambi i file continuano a dichiararsi linearizzati. Nessuno dei due carica più veloce
Perché Fast Web View ha bisogno della collaborazione del server?
Fast Web View funziona solo quando il server consegna a richiesta porzioni arbitrarie del file. Il lettore invia richieste HTTP con un'intestazione Range e si attende 206 Partial Content indietro; se il server non dichiara mai Accept-Ranges: bytes, o un proxy o una CDN davanti a lui collassano le richieste di range in trasferimenti completi, il lettore non può recuperare la pagina 40 da solo e scarica l'intero file invece. La struttura dentro il PDF è allora corretta e interamente sprecata, e questo è il guasto più spesso diagnosticato come linearizzazione rotta. Prima di ricompilare un documento, richiedete il primo kilobyte dell'URL esatto che il visualizzatore apre, per esempio con curl -r 0-1023, e confermate che lo stato sia 206. Gli host di file statici di solito ci riusciscono per impostazione predefinita, mentre gli endpoint applicativi che servono i PDF in streaming attraverso un proprio handler spesso no
Come verificare se un PDF ha il Fast Web View?
Verificate i byte invece di fidarvi dello strumento che ha prodotto il file, e controllate entrambe le metà della condizione: il file si dichiara linearizzato, e quella dichiarazione corrisponde ancora al file su disco. Tre controlli coprono quasi ogni caso, da un sguardo rapido su desktop a un gate automatizzato in una pipeline editoriale:
- Adobe Acrobat o Reader: aprite File > Properties; la scheda Description mostra
Fast Web View: YesoNonell'angolo in basso a destra. Acrobat riporta Yes solo quando la struttura è presente e aggiornata, così un file modificato dopo la linearizzazione mostra No - qpdf da riga di comando:
qpdf --check-linearizationvalida le tabelle di hint rispetto alle posizioni reali degli oggetti, eqpdf --show-linearizationfa il dump del dizionario e di ogni voce delle tabelle di hint - Il primo kilobyte del file: apritele in un editor di testo o esadecimale e cercate un oggetto contenente
/Linearized 1entro i primi 1024 byte; quando c'è, confrontate il suo valore/Lcon la dimensione del file riportata dal sistema operativo
# Valida il dizionario di linearizzazione e le tabelle di hint rispetto al layout
qpdf --check-linearization report-web.pdf
# Fa il dump di /L, /H, /O, /E, /N, /T e di ogni voce delle tabelle di hint
qpdf --show-linearization report-web.pdf
Il controllo con qpdf è quello che vale la pena cablare in una pipeline, perché riconcilia le tabelle di hint con le posizioni reali degli oggetti; un controllo che si limita a confermare che il dizionario esista benedirà un file i cui offset puntano ai byte sbagliati. Da Delphi, THotPDF.IsLoadedLinearized di HotPDF riporta se un documento caricato si dichiara linearizzato, scandendo i primi 1024 byte in cerca di /Linearized prima del parsing. PDFium Component risponde alla stessa domanda attraverso la class function TPdf.InspectAvailability, il cui campo Linearization restituisce plnLinearized, plnNotLinearized o plnUnknown senza nemmeno caricare il documento. Entrambi i flag riportano ciò che il file dichiara; il confronto sul /L dice se quella dichiarazione corrisponde ancora ai byte su disco:
uses
System.SysUtils, System.Classes, System.Math, System.StrUtils, PDFium;
type
TFastWebView = (fwvNo, fwvStale, fwvYes);
// Helper di vostra fabbricazione: PDFium decide se il file è linearizzato,
// e il confronto sul /L intercetta le modifiche accodate dopo
function FastWebViewState(const FileName: string): TFastWebView;
var
Stream: TFileStream;
Head: AnsiString;
Text: string;
P, Q: Integer;
begin
Result := fwvNo;
Stream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
try
if TPdf.InspectAvailability(Stream).Linearization <> plnLinearized then
Exit;
SetLength(Head, Min(Stream.Size, 1024)); // Allegato F: primi 1024 byte
Stream.Position := 0;
if Length(Head) > 0 then
Stream.ReadBuffer(Head[1], Length(Head));
Text := string(Head);
P := Pos('/Linearized', Text);
if P = 0 then
Exit;
P := PosEx('/L ', Text, P + Length('/Linearized'));
if P = 0 then
Exit;
Inc(P, 3);
Q := P;
while (Q <= Length(Text)) and CharInSet(Text[Q], ['0'..'9']) do
Inc(Q);
if StrToInt64Def(Copy(Text, P, Q - P), -1) = Stream.Size then
Result := fwvYes
else
Result := fwvStale; // linearizzato una volta, poi modificato
finally
Stream.Free;
end;
end;
Attivare il Fast Web View in Acrobat, qpdf e Delphi
Qualunque strumento che riscrive l'intero file in ordine da Allegato F può accendere il Fast Web View, e ciò che conta di più è che giri per ultimo. In Acrobat, abilitate Save As optimizes for Fast Web View sotto Preferences > Documents e poi usate File > Save As anziché Save, perché un Save semplice può accodare un aggiornamento incrementale. Su un server o in uno script di build, qpdf --linearize in.pdf out.pdf riscrive qualsiasi PDF in forma linearizzata e lascia l'input intatto. Il codice Delphi ha due strade, a seconda che si generi il documento o lo si riceva. HotPDF scrive direttamente output linearizzato quando THotPDF.LinearizeOutput è True; impostatelo con le altre opzioni di output prima di BeginDoc, e il layout da Allegato F viene calcolato e scritto dentro EndDoc:
qpdf --linearize report-final.pdf report-web.pdf
var
PDF: THotPDF;
begin
PDF := THotPDF.Create(nil);
try
PDF.AutoLaunch := False; // non aprire un viewer dopo EndDoc
PDF.FileName := 'catalog-web.pdf';
PDF.LinearizeOutput := True; // impostare prima di BeginDoc
PDF.BeginDoc; // la pagina 1 esiste dopo BeginDoc
PDF.CurrentPage.TextOut(72, 72, 0, 'Spring catalog');
PDF.AddPage;
PDF.CurrentPage.TextOut(72, 72, 0, 'Price list');
PDF.EndDoc; // il layout linearizzato viene scritto qui
finally
PDF.Free;
end;
end;
Per i file che esistono già, PDFium Component fornisce TPdf.SaveAsLinearized. Esegue il salvataggio PDFium ordinario in uno store intermedio, così le modifiche di pagina e i valori dei form passano dallo stesso percorso di qualsiasi altro salvataggio, e poi ristruttura quell'output secondo l'Allegato F prima di scrivere il target. Il metodo accetta gli stessi parametri TSaveOption e TPdfVersion di SaveAs, rifiuta saIncremental e qualsiasi versione sotto PDF 1.2, e il suo overload con nome di file scrive su un file temporaneo che sostituisce il target solo dopo che il salvataggio è riuscito. Poiché il writer ricostruisce il file dalla revisione corrente, è anche il modo di ripristinare il Fast Web View su un file che ha raccolto modifiche accodate. L'input cifrato richiede saRemoveSecurity: rinumerare gli oggetti invalida le chiavi per oggetto dello standard security handler (ISO 32000-1 §7.6.2), così la copia web viene scritta senza cifratura
var
Pdf: TPdf;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'manual-rev3.pdf'; // contiene modifiche accodate
Pdf.Active := True;
if not Pdf.SaveAsLinearized('manual-web.pdf', saNoIncremental) then
raise Exception.Create('Linearized save failed');
finally
Pdf.Free;
end;
end;
Gli aggiornamenti incrementali spengono il Fast Web View in silenzio
Un aggiornamento incrementale è la ragione più comune per cui un file linearizzato correttamente perde il Fast Web View in seguito. Quando uno strumento aggiunge una firma, compila un campo modulo o attacca una nota (sticky note) attraverso un salvataggio incrementale, lascia i byte originali intatti e accoda in fondo gli oggetti modificati, una nuova sezione di riferimenti incrociati e un nuovo trailer. Il dizionario di linearizzazione all'inizio descrive ancora il vecchio layout, il suo /L non corrisponde più alla dimensione del file, e un lettore conforme ripiega sul trattare il documento come un PDF ordinario, anche se la struttura linearizzata è ancora seduta intatta nella prima parte del file. HotPDF accoda esattamente questo tipo di revisione attraverso BeginIncrementalUpdate e SaveIncrementalUpdate, che è lo strumento giusto mentre un documento cambia ancora e il passo finale sbagliato per un file diretto a un server web:
// Accoda una revisione: veloce, tiene intatti i byte precedenti,
// e lascia il risultato senza Fast Web View
PDF.BeginIncrementalUpdate('report.pdf');
PDF.AddPage;
PDF.CurrentPage.TextOut(72, 760, 0, 'Addendum');
PDF.SaveIncrementalUpdate('report-rev2.pdf');
La regola praticabile è modificare in modo incrementale mentre il documento è in movimento, linearizzare una volta sola come passaggio finale, e lasciare il Fast Web View spento per i file che vengono inviati per email o scaricati prima che qualcuno li apra, dato che non guadagnano nulla dall'indice frontale più grande. I documenti firmati impongono una scelta vera: una riscrittura completa cambia i byte che il /ByteRange di ogni firma copre, e firmare dopo accoda un aggiornamento che riporta dritto indietro la discrepanza del /L, così per i contratti tenete la firma e accettate i download completi. Il lato di scrittura dell'Allegato F, incluso come gli offset convergono prima che il primo byte sia scritto e perché l'output linearizzato rinuncia agli object stream, è trattato in scrittura di PDF linearizzati con le tabelle di hint di HotPDF e linearizzazione Fast Web View con PDFium Component in Delphi. LinearizeOutput e IsLoadedLinearized viaggiano con HotPDF Delphi Component, mentre SaveAsLinearized e InspectAvailability fanno parte di PDFium Component per Delphi, C++Builder e Lazarus