Apri un PDF che ha attraversato qualche anno di uso in produzione e spesso troverai più contabilità che contenuto: migliaia di piccoli oggetti dizionario, ciascuno preceduto dal proprio header obj, più una tabella cross-reference che costa 20 byte ASCII per oggetto. In un caso di supporto che abbiamo analizzato, un archivio polizze da 58 MB spendeva meno della metà dei suoi byte in vero contenuto pagina; il resto era overhead strutturale e revisioni obsolete. HotPDF, libreria PDF VCL nativa per Delphi e C++Builder, espone i due meccanismi di formato file che attaccano entrambe le metà del problema: object stream per archiviazione compatta e incremental update per modifiche append-only che non distruggono ciò che esisteva prima.
Come object stream e xref stream ridisegnano il file
Fino a PDF 1.4, ogni oggetto indiretto risiede non compresso nel body, e la tabella cross-reference alla fine è una struttura testuale a larghezza fissa: esattamente 20 byte per voce, senza compressione. Un documento con 200.000 oggetti porta quindi circa 4 MB di soli dati xref, prima ancora di disegnare un singolo glifo. PDF 1.5 ha introdotto due sostituti, definiti in ISO 32000-1 §7.5.7 e §7.5.8: object stream (/Type /ObjStm), che raccolgono oggetti non-stream in un unico contenitore compresso Flate, e cross-reference stream, che memorizzano la tabella di lookup stessa come binario compresso con campi a larghezza variabile.
I risparmi sono maggiori proprio dove i generatori ingenui fanno più danni: documenti ricchi di form con migliaia di dizionari campo, alberi outline profondi ed elementi di struttura tagged-PDF. Quegli oggetti sono minuscoli e molto ripetitivi singolarmente, quindi diventano input Flate ideale una volta impacchettati insieme. I content stream delle pagine erano già comprimibili prima di PDF 1.5, quindi gli object stream riducono poco i file dominati da immagini; riducono in modo drastico i file dominati dalla struttura.
In HotPDF le due funzionalità si attivano con una coppia di proprietà, e la dipendenza d'ordine conta:
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := 'catalog-2026.pdf';
Pdf.UseXRefStream := True; // binary xref, prerequisite for ObjStm
Pdf.UseObjectStreams := True; // pack objects into /Type /ObjStm
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('Arial', [], 11);
Pdf.CurrentPage.TextOut(50, 760, 0, 'Compressed structure demo');
Pdf.EndDoc; // emits XRefStm + ObjStm containers
finally
Pdf.Free;
end;
end;
UseObjectStreams richiede che UseXRefStream sia True, perché un oggetto compresso è indirizzato da una voce xref di tipo 2 (numero dell'object stream più indice), e le voci di tipo 2 non possono essere espresse in una classica tabella testuale da 20 byte. Impostare solo UseObjectStreams non ti compra nulla; impostarle entrambe prima di BeginDoc è la configurazione funzionante.
Il confine di compatibilità a PDF 1.4
Entrambe le proprietà sono False di default per un motivo che morde i team con integrazioni legacy. Un reader limitato alla semantica PDF 1.4 non segnala "oggetti compressi non supportati" quando incontra uno xref stream; di solito segnala una tabella cross-reference danneggiata o rifiuta del tutto il file, perché il layout con keyword trailer che si aspetta non c'è. Se il tuo output alimenta un vecchio fax gateway, una stampante hardware con interprete embedded o un parser downstream scritto contro PDF 1.4, lascia disattivati entrambi i flag per quel canale e accetta il file più grande. Per canali di archiviazione e distribuzione web, dove ogni viewer mainstream gestisce PDF 1.5 da vent'anni, abilitarli è compressione quasi gratuita.
Vale la pena segnalare al supporto un effetto operativo: una volta che i dizionari sono impacchettati in object stream, il diff byte-level tra due file generati diventa privo di significato, perché una modifica a un campo può rieseguire il deflate di un intero contenitore. Le indagini di supporto dovrebbero confrontare i documenti logicamente, per contenuto degli oggetti, non con un diff binario.
Perché esistono gli incremental update: offset, firme, audit trail
Una firma digitale in PDF copre un /ByteRange esplicito: due intervalli del file fisico, misurati in offset byte assoluti, su cui è stato calcolato il digest CMS. Riscrivi il file, anche con contenuto visivamente identico, e ogni offset si sposta; il digest non combacia più e la firma risulta rotta. Questa è la ragione concreta per cui ISO 32000-1 §7.5.6 definisce gli incremental update: gli oggetti cambiati e aggiunti vengono accodati dopo il %%EOF esistente, seguiti da una nuova sezione cross-reference il cui /Prev punta alla precedente. I byte originali non vengono mai toccati, quindi una revisione firmata in precedenza resta verificabile, e Acrobat può mostrare separatamente ogni revisione firmata nel pannello firme.
HotPDF incapsula questo come entry point dedicato:
Pdf.BeginIncrementalUpdate('contract-signed.pdf');
Pdf.AddPage;
Pdf.CurrentPage.SetFont('Arial', [], 10);
Pdf.CurrentPage.TextOut(50, 760, 0, 'Addendum recorded 2026-06-11');
Pdf.SaveIncrementalUpdate('contract-updated.pdf'); // appends the delta only
Due dettagli sono facili da sbagliare. Primo, BeginIncrementalUpdate deve ricevere il nome del file originale; la sezione xref accodata memorizza offset validi solo rispetto a quegli esatti byte originali. Secondo, il salvataggio è append-only per definizione, quindi l'output è sempre più grande dell'input. Non è un difetto da ottimizzare via: è la proprietà che mantiene intatte le revisioni firmate precedenti.
Modificare documenti caricati: LoadFromFile, non BeginDoc
Una trappola separata cattura gli sviluppatori che hanno imparato HotPDF tramite l'API di generazione. BeginDoc avvia un nuovo documento; è la chiamata sbagliata quando l'obiettivo è modificare un documento esistente. La chirurgia documentale passa dal percorso dei documenti caricati:
PageCount := Pdf.LoadFromFile('base.pdf');
Pdf.InsertPagesFromDocument(OtherDoc, '1-3', 5); // pages 1-3 after page 5
Pdf.MovePage(2, 5);
Pdf.SaveLoadedDocument('modified.pdf');
Il sintomo della confusione tra i due modelli è un file che contiene solo il tuo nuovo contenuto e nulla dell'originale, perché BeginDoc ha felicemente creato un documento fresco accanto a quello che pensavi di modificare. In review, tratta LoadFromFile + SaveLoadedDocument come un vocabolario accoppiato e BeginDoc + EndDoc come un altro; una procedura che li usa entrambi per lo stesso documento è quasi sempre un bug.
Contenere la crescita: quando compattare un file accodato
Il salvataggio append-only ha un costo nel lungo periodo. Un job notturno che timbra una riga di stato sullo stesso PDF produce 365 revisioni l'anno, e ogni revisione si trascina dietro una nuova sezione xref. Quando la cronologia delle revisioni ha servito il suo scopo, e purché nessuna firma debba sopravvivere, il file può essere compattato riserializzandolo attraverso il percorso loaded-document:
Pdf.LoadFromFile('stamped.pdf');
Pdf.SaveLoadedDocument('compacted.pdf');
Il risalvataggio è una riscrittura completa, quindi scarta deliberatamente le revisioni precedenti e invaliderà qualunque firma nel file; mettilo dietro lo stesso controllo di policy usato prima di qualsiasi operazione distruttiva. Una regola di produzione ragionevole che abbiamo visto funzionare: compatta quando il conteggio revisioni supera una soglia o quando l'overhead accodato supera una percentuale del file base, e non compattare mai file il cui pannello firme non è vuoto.
Controllare il risultato prima della consegna
La verifica per questa coppia di funzionalità è piacevolmente meccanica. Apri l'output in Adobe Acrobat e controlla tre cose: le proprietà documento riportano PDF 1.5 o successivo quando gli object stream sono abilitati; il pannello firme valida ancora ogni revisione firmata in precedenza dopo un incremental update; page count e segnalibri sono sopravvissuti a un ciclo load-modify-save. Per canali archivistici, passa il file anche in veraPDF, perché le strutture xref compresse sono esattamente il tipo di dettaglio che un parser rigoroso esamina con più attenzione di un viewer permissivo. Se elabori anche input molto grandi, le tecniche di ispezione nella nostra guida alla Direct File API per workflow PDF di grandi dimensioni si combinano bene con il salvataggio incrementale, e la meccanica delle firme citata sopra è trattata in profondità nell'articolo HotPDF su firme digitali e PAdES.
FAQ
Abilitare gli object stream romperà i reader PDF più vecchi?
I reader che implementano solo PDF 1.4 non possono analizzare xref stream e di solito riportano il file come danneggiato. Mantieni UseXRefStream e UseObjectStreams al default False per canali che alimentano interpreti legacy, e abilitali per canali moderni di visualizzazione e archivio.
Un incremental update mantiene valida la firma digitale?
Sì, è il suo scopo: i nuovi oggetti vengono accodati dopo i byte firmati, quindi il /ByteRange firmato continua a produrre il digest corretto. Una riscrittura completa, inclusa la compattazione load-and-resave, rompe ogni firma esistente anche quando il contenuto visibile è invariato.
Perché il file continua a crescere dopo salvataggi ripetuti?
Il salvataggio incrementale accoda un delta a ogni salvataggio e non recupera mai spazio. Compatta occasionalmente con LoadFromFile più SaveLoadedDocument quando cronologia revisioni e firme non devono più essere preservate.
Dove andare dopo
Entrambe le funzionalità fanno parte del set standard di HotPDF Component, accanto alle API di generazione, form, crittografia e firma mostrate altrove in questo blog. La pagina prodotto collega la documentazione completa dell'API se vuoi mappare le chiamate sopra sulla tua pipeline documentale.