L'economia della dimensione di un file PDF
Spec: ISO 32000-2, §7.5.7ISO 32000-2 §7.5.7
In sintesi
Sezione intitolata “In sintesi”Due PDF possono apparire pixel-per-pixel identici sullo schermo e differire di dieci volte su disco. La differenza non è quasi mai il contenuto che si vede; è come il file è stato assemblato sotto. Questa pagina è una panoramica di economia della dimensione: dove vanno davvero i byte di un PDF, e le quattro leve su cui un autore spende un budget di byte.
È il compagno perché i file sono grandi di Stream e filtri, che copre come un filtro decodifica. Questa resta sul budget.
Perché è importante
Sezione intitolata “Perché è importante”La dimensione del file è raramente una metrica di vanità. È banda su ogni download, archiviazione su ogni archivio, e latenza su ogni anteprima. Una fattura da 12 MB che avrebbe dovuto essere 400 KB non è un problema cosmetico quando se ne generano un milione al mese — è un conto trenta volte più grande.
La parte frustrante è che il gonfiore è di solito invisibile. Il documento si renderizza correttamente, si apre bene, si stampa bene. Nulla dice che lo stesso artefatto avrebbe potuto essere una frazione della dimensione, perché i byte sprecati sono strutturali, non visivi. Per trovarli si deve guardare al budget, non alla pagina.
In breve
Sezione intitolata “In breve”Si pensi a un PDF come a un budget che si spende su quattro voci di costo.
- Overhead per oggetto. Ogni oggetto indiretto porta un involucro
N G obj/endobj, ed è tracciato da una voce di cross-reference. Un documento con molte pagine ha migliaia di piccoli oggetti, e gli involucri si sommano. Gli object stream eliminano quell’involucro per un intero gruppo di oggetti; ogni oggetto impacchettato mantiene comunque la propria voce di cross-reference, ma come una compatta voce di type-2. - Byte delle immagini. Per qualsiasi documento con fotografie o scansioni, le immagini dominano, e la singola leva più grande è la scelta del filtro — un codec lossless contro un codec immagine lossy è la differenza tra megabyte e kilobyte.
- Byte dei font. Un font incorporato completo è centinaia di kilobyte di glifi che non si usano mai. Il subsetting mantiene solo i glifi che il documento disegna effettivamente.
- L’indice. La cross-reference table che permette a un lettore di trovare ogni oggetto può essa stessa essere uno stream compresso anziché testo in chiaro.
Si azzecchino tutte e quattro e il file è piccolo. Se ne sbaglia una e domina tutto il resto che si è fatto bene.
Come NextPDF lo affronta
Sezione intitolata “Come NextPDF lo affronta”Lo scrittore di NextPDF è un serializzatore in streaming a passaggio singolo: accoda i byte di ciascun oggetto man mano che vengono prodotti e registra una classica voce di cross-reference in-use per ciascuno. Quel valore predefinito è veloce, prevedibile, e produce un file byte-stabile — ma non è il layout più piccolo possibile, e NextPDF è onesto su questo.
Leva 1 — object stream (ObjStm)
Sezione intitolata “Leva 1 — object stream (ObjStm)”Un PDF è un grafo di oggetti indiretti. La maggior parte di essi sono piccoli
dizionari: nodi di pagina, dizionari di annotation, elementi dello structure
tree, voci di outline. Ciascuno paga una tassa fissa — le keyword obj /
endobj, i numeri di oggetto e di generazione, e una voce di cross-reference che
lo localizza. Su un documento con migliaia di piccoli oggetti, quella tassa è una
fetta significativa del file.
Un object stream raccoglie molti di quei piccoli oggetti non-stream in un unico
stream e li comprime insieme (Spec: ISO 32000-2, §7.5.7ISO 32000-2 §7.5.7).
L’involucro obj / endobj è eliminato per l’intero gruppo; i valori al suo
interno sono memorizzati uno dietro l’altro senza keyword per oggetto, poi
deflazionati come un unico blocco — il che comprime anche meglio, perché il
compressore deduplicante ora vede tutti quei dizionari simili in una volta. La
voce di cross-reference non sparisce — ogni oggetto impacchettato ne ha ancora
bisogno — ma si riduce a una compatta voce binaria di type-2 nel cross-reference
stream (più su questo nella Leva 2).
In NextPDF questo è fornito da ObjectStreamPacker, un post-processore
autonomo che prende un PDF a cross-reference-stream finito e riscrive gli oggetti
idonei in un unico /Type /ObjStm. Le regole che segue vengono dritte dallo
standard: un oggetto idoneo è un oggetto a generazione-zero, non-stream, dopo aver
escluso gli oggetti speciali che devono restare direttamente indirizzabili. La
§7.5.7 vieta di memorizzare un oggetto stream dentro un object stream, quindi gli
oggetti stream — contenuto, font, immagini — mantengono le proprie voci; e
ObjectStreamPacker declina inoltre il cross-reference stream stesso del
documento (è riscritto) e il dizionario /Encrypt, entrambi i quali devono
restare direttamente indirizzabili. Tutto il resto, a generazione-zero e
non-stream, è impacchettato.
| Edition | Availability |
|---|---|
| Core | Pieno supporto nel core open-source tramite ObjectStreamPacker. È opt-in: il valore predefinito dello scrittore a passaggio singolo emette classiche voci in-use, quindi l’output resta byte-identico a meno che non si abiliti il packing. Il packer è deterministico, con la propria baseline golden riproducibile. |
| Pro | Not in this edition |
| Enterprise | Not in this edition |
L’opt-in è una postura deliberata, non una limitazione che si nasconde. L’output predefinito è byte-stabile e coincide con le baseline golden esistenti; attivare il packing è un’opt-in a un layout diverso, più piccolo, ugualmente deterministico. Si sceglie il compromesso, e il motore non lo fa mai alle tue spalle.
Leva 2 — anche l’indice può essere uno stream
Sezione intitolata “Leva 2 — anche l’indice può essere uno stream”Una volta che gli oggetti vivono dentro un object stream, l’indice che punta ad
essi cambia forma. Un lettore trova un oggetto impacchettato attraverso una voce
di cross-reference compressa — una voce di type-2 che nomina l’object stream e
l’indice al suo interno (Spec: ISO 32000-2, §7.5.8.3ISO 32000-2 §7.5.8.3).
Poiché l’intera cross-reference è essa stessa uno stream /Type /XRef, l’indice
per migliaia di oggetti è impacchettato in binario e deflazionato anziché scritto
come righe di testo in chiaro. La mappa si riduce assieme al territorio.
ObjectStreamPacker ricostruisce esattamente questo: emette il singolo object
stream, poi un cross-reference stream riscritto che porta una compatta voce di
type-1 per ogni oggetto trattenuto e una voce di type-2 per ogni oggetto
impacchettato, preservando ogni numero di oggetto così che i riferimenti
esistenti restino validi.
Leva 3 — scelta del filtro immagine
Sezione intitolata “Leva 3 — scelta del filtro immagine”Per qualsiasi documento con immagini reali, questa leva fa impallidire le altre. I byte sono la stessa immagine; il codec è il budget. I nomi dei filtri vivono nel set di filtri standard (Spec: ISO 32000-2, §7.4ISO 32000-2 §7.4), e la scelta tra di essi è una decisione di dimensione:
- FlateDecode è lossless. Perfetto per line art, screenshot, e qualsiasi cosa con colore piatto — e rovinoso per una fotografia, dove lossless significa ogni byte dell’originale.
- DCTDecode è JPEG: lossy, e per le fotografie la scelta giusta con ampio margine, spesso una riduzione di dieci volte per un calo di qualità che nessuno nota.
- JPXDecode è JPEG 2000: compressione wavelet con una diversa curva qualità/dimensione, ma supporto disomogeneo e non permesso da alcuni profili di archiviazione.
Questo è esattamente dove come un filtro decodifica conta, e quello è il compito dell’articolo Stream e filtri. Il punto economico è più ristretto: una fotografia memorizzata lossless è la singola causa più comune di un PDF inutilmente enorme, e nessuna quantità di object-stream packing salverà un file il cui peso reale è una scansione non-JPEGgizzata.
Leva 4 — subsetting dei font
Sezione intitolata “Leva 4 — subsetting dei font”Un font incorporato è un programma. Uno completo può essere parecchie centinaia di kilobyte, perché porta ogni glifo che il progettista del carattere abbia mai disegnato — migliaia di caratteri attraverso scritture che non si useranno mai in questo documento. Il subsetting incorpora solo i glifi che il documento disegna effettivamente, trasformando quel programma in una piccola frazione di sé stesso. Una lettera di una pagina non ha bisogno dell’intero di un font con capacità CJK; ha bisogno delle poche dozzine di glifi che imposta. La meccanica di come i glifi sono selezionati e re-indicizzati è un argomento a sé — si veda Font: la parte difficile.
- Image bytesFor media-heavy files, the largest line item. The filter choice — lossless Flate vs a lossy image codec like DCT (or JPX in a lossy mode) — is the dominant size lever.
- Font bytesA full embedded font is mostly glyphs you never draw. Subsetting keeps only the used glyphs, often shrinking the program by an order of magnitude.
- Per-object overheadThousands of small dictionaries each pay an obj/endobj envelope plus a cross-reference entry. Object streams (ObjStm) drop the envelope for the whole group; each object keeps a compact type-2 cross-reference entry.
- The indexA compressed cross-reference stream with type-2 entries binary-packs and deflates the map of every object, replacing plaintext index rows.
Esempio pratico
Sezione intitolata “Esempio pratico”Non c’è alcuna API fabbricata da mostrare qui, perché le decisioni di dimensione più importanti sono prese prima che i byte raggiungano lo scrittore — e l’unica leva strutturale che NextPDF espone è un singolo opt-in. Concettualmente, il budget si legge così:
- Si forniscano le fotografie come JPEG così che finiscano sotto
DCTDecode, non ricodificate senza perdita. La vittoria più grande è una scelta sui dati sorgente, non un flag dello scrittore. - Si lasci che lo scrittore faccia il subset dei font incorporati così che solo i glifi disegnati vengano spediti.
- Per un documento con molti piccoli oggetti, si faccia opt-in all’object-stream
packing, che instrada il file finito attraverso
ObjectStreamPackerper raggruppare i piccoli oggetti non-stream e riscrivere il cross-reference stream.
Il percorso predefinito — classiche voci in-use, nessun ObjStm — è la baseline giusta: deterministica, byte-stabile, e facile da verificare. Il packing è l’aggiornamento ponderato quando è il numero di oggetti, non il peso delle immagini, ciò che gonfia il file.
Equivoco comune
Sezione intitolata “Equivoco comune”La trappola è ricorrere a un pulsante «comprimi il PDF» e aspettarsi che risolva
tutto. La compressione non è una leva; sono quattro, e non si sostituiscono l’una
all’altra. L’object-stream packing non può ridurre una fotografia — quello è il
compito del filtro immagine. Un JPEG perfetto non può compensare un font che si è
dimenticato di fare il subset. E nulla di tutto ciò aiuta se il gonfiore reale è
una scansione da 10 MB memorizzata senza perdita perché nessuno ha scelto
DCTDecode per essa.
Il secondo equivoco è che più piccolo sia sempre strettamente meglio. Non lo è. Gli object stream sono incompatibili con la linearizzazione — il layout fast-web-view che fissa la collocazione assoluta degli oggetti così che la prima pagina arrivi in streaming presto. E alcuni profili di archiviazione limitano quali filtri immagine siano persino permessi. La dimensione è un asse. Si scambia con lo streaming, la conformità di archiviazione, e la riproducibilità, e il punto giusto sulla curva dipende da a cosa serve il documento.
Limiti e confini
Sezione intitolata “Limiti e confini”Le quattro leve sono l’economia strutturale della dimensione del file. Non sono una garanzia universale di «rendilo più piccolo», e NextPDF non pretende di essere un ri-ottimizzatore per PDF in ingresso arbitrari.
ObjectStreamPacker è un’ottimizzazione best-effort che non rischia mai la
correttezza. Declina — restituendo l’input invariato — quando il file non è un
PDF a cross-reference-stream, quando è cifrato, quando porta una firma digitale
(ri-disporre gli oggetti sposterebbe i byte range che una firma protegge), quando
contiene già object stream, o quando non c’è alcun oggetto idoneo da impacchettare.
Emette esattamente un object stream per l’intero documento; non si divide in una
collezione /Extends, che è fuori ambito e conforme per le dimensioni di
documento che NextPDF produce.
Le leve delle immagini e dei font sono in gran parte decisioni sull’input. Lo
scrittore di NextPDF non transcodifica implicitamente una bitmap lossless in uno
stream immagine lossy — ricodificare una fotografia in DCTDecode o JPXDecode è
una decisione esplicita di codifica immagine a monte, non qualcosa che il
serializzatore fa alle tue spalle — né recupera glifi da un font che il chiamante
ha chiesto di incorporare per intero. Le vittorie di dimensione più grandi sono
fatte a monte del serializzatore di byte; il compito del motore è non sprecare il
budget che gli porti.
Documenti correlati
Sezione intitolata “Documenti correlati”- Stream e filtri — il compagno come un filtro decodifica; questa pagina lo complementa deliberatamente, non lo duplica.
- Cosa è davvero un PDF — il modello a oggetti indiretti il cui overhead per oggetto la leva degli object stream riduce.
- L’anatomia di un file PDF — la struttura di cross-reference che diventa uno stream compresso.
- Font: la parte difficile — come il subsetting seleziona e re-indicizza i glifi che un documento disegna.
Glossario
Sezione intitolata “Glossario”- Object stream (ObjStm) — un unico stream che contiene molti piccoli oggetti
indiretti non-stream, compressi insieme, così che l’involucro
obj/endobjsia eliminato per il gruppo. Ogni oggetto impacchettato mantiene comunque la propria voce di cross-reference, come una compatta voce di type-2. Opt-in in NextPDF. - Oggetto indiretto — un oggetto numerato nel grafo di un PDF, avvolto in un
involucro
obj/endobje tracciato dall’indice di cross-reference. L’involucro è l’overhead per oggetto. - Cross-reference stream — lo stream
/Type /XRefche indicizza ogni oggetto, impacchettato in binario e deflazionato anziché scritto come righe di testo in chiaro. - Voce compressa (type-2) — una voce di cross-reference che punta a un oggetto che vive dentro un object stream, nominando lo stream e l’indice al suo interno.
- Subsetting dei font — incorporare solo i glifi che un documento disegna effettivamente, anziché l’intero carattere, riducendo il programma del font incorporato.
- Filtro lossless contro lossy — un codec lossless (FlateDecode) riproduce ogni byte; un codec immagine lossy (DCTDecode, o JPXDecode in modalità lossy) scarta dettaglio impercettibile per un risultato molto più piccolo. (JPEG 2000 / JPX possono anche essere configurati lossless.) La scelta è la leva dominante su un file ricco di media.