Articol tehnic

Fluxuri de obiecte și actualizări incrementale în Delphi cu HotPDF

PDF 1.5 a introdus două structuri de stocare pe care formatul de fișier anterior nu avea cum să le exprime: fluxul de obiecte (object stream) și fluxul de referințe încrucișate (cross-reference stream). Un flux de obiecte este un singur container comprimat Flate, marcat /Type /ObjStm, care conține multe obiecte indirecte mici, împachetate cap la cap, în loc să fie împrăștiate prin corpul fișierului. Un flux de referințe încrucișate este tabelul de căutare al fișierului, rescris ca binar comprimat, cu câmpuri de lățime variabilă, în locul tabelului ASCII cu lățime fixă care încheia fiecare PDF până la versiunea 1.4. Cele două călătoresc împreună. Odată ce obiectele sunt pliate într-un flux, vechiul tabel text nu le mai poate adresa, așa că xref-ul binar trebuie să vină alături

Puneți asta lângă structura clasică și costul pe care îl elimină este ușor de văzut. Într-un fișier PDF 1.4, fiecare obiect indirect stă necomprimat în spatele propriului antet obj, iar tabelul din final consumă exact 20 de octeți ASCII per intrare, comprimarea fiind interzisă. Un document cu 200.000 de obiecte poartă aproximativ 4 MB de date de referințe încrucișate înainte ca măcar un singur glif să fie desenat, cu toate corpurile de dicționar necomprimate stivuite deasupra. PDF 1.5 atacă ambele cifre deodată: dicționarele se pliază în containere Flate, iar tabelul de 4 MB se micșorează la câteva sute de kiloocteți de binar. ISO 32000-1 definește cele două structuri în §7.5.7 și §7.5.8

Aspecturi de fișiere HotPDF paralele, comparând obiecte PDF 1.4 necomprimate și tabele xref ASCII cu fluxuri de obiecte PDF 1.5 comprimate și un flux xref binar
Dicționarele pliate și un stream xref binar colapsează megabați de overhead structural, în timp ce conținutul paginilor și datele de imagine păstrează compresia pe care o aveau deja — fișierele bogate în structură câștigă cel mai mult

Unde ajunge de fapt economia

Fluxurile de obiecte ating doar obiectele care nu sunt fluxuri, așa că ele comprimă structura, nu pixelii. Conținutul paginii era deja comprimat Flate înainte de 1.5, iar datele de imagine poartă propriile codecuri, motiv pentru care o broșură bogată în imagini abia se mișcă. Fișierele care se prăbușesc sunt cele bogate în structură: AcroForm-uri cu mii de dicționare de câmp, arbori de contur adânci, elemente de structură tagged-PDF. Acele obiecte sunt mici, numeroase și aproape identice unele cu altele, iar acea repetiție este exact ceea ce exploatează Flate odată ce ele stau într-un singur buffer, în loc să fie răspândite prin corp cu antete înfipte între ele

Este ușor de subestimat cât de mult dintr-un fișier vechi este overhead. O arhivă de formulare care a absorbit ani de editări poate cheltui cu mult peste jumătate din octeții ei pe antete de dicționar, padding de xref și revizii pe care niciun cititor nu le va privi vreodată. Cele două funcții de aici recuperează primele două dintre acestea. A treia, reviziile acumulate, cedează doar în fața compactării, odată ce fișierul nu mai trebuie să-și amintească propriul istoric

În HotPDF activați ambele prin două proprietăți, iar modul în care depind una de cealaltă contează mai mult decât ordinea în care le scrieți:

var
  Pdf: THotPDF;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := 'catalog-2026.pdf';
    Pdf.UseXRefStream := True;      // xref binar, condiție prealabilă pentru ObjStm
    Pdf.UseObjectStreams := True;   // împachetează obiectele în /Type /ObjStm
    Pdf.BeginDoc;
    Pdf.CurrentPage.SetFont('Arial', [], 11);
    Pdf.CurrentPage.TextOut(50, 760, 0, 'Compressed structure demo');
    Pdf.EndDoc;                     // emite containere XRefStm + ObjStm
  finally
    Pdf.Free;
  end;
end;

UseObjectStreams are nevoie ca UseXRefStream să fie setat la True. Un obiect comprimat este accesat printr-o intrare xref de tip 2, care înregistrează un număr de flux de obiecte plus un index, iar un rând clasic de text de 20 de octeți nu are unde să stocheze acea pereche. Așa că UseObjectStreams de unul singur nu face nimic vizibil; ambele flaguri, setate înainte de BeginDoc, reprezintă configurația care funcționează. Setați-le după BeginDoc și HotPDF s-a angajat deja la structura mai veche

De ce ambele sunt implicit dezactivate

HotPDF lasă ambele proprietăți pe False din start, iar motivul iese la iveală în integrările cu cod din aval mai vechi. Un cititor care înțelege doar PDF 1.4 nu anunță că nu poate gestiona obiecte comprimate. Întâlnește un flux xref, nu găsește niciunul dintre cuvintele-cheie de trailer pe care le așteaptă și raportează un tabel de referințe încrucișate deteriorat sau pur și simplu refuză să deschidă fișierul. Dacă rezultatul dumneavoastră ajunge la o poartă de fax îmbătrânită, o imprimantă hardware care rulează un interpretor înglobat sau un parser pe care cineva l-a scris după specificația 1.4 acum un deceniu, lăsați ambele flaguri dezactivate pentru acel canal și acceptați fișierul mai mare. Pentru stocare arhivistică și livrare web, unde fiecare vizualizator mainstream citește PDF 1.5 de douăzeci de ani, activarea lor este o compresie pe care o obțineți aproape gratuit

Există un efect de ordinul doi despre care merită să vă informați echipa de suport. Odată ce dicționarele sunt împachetate în fluxuri de obiecte, compararea a două fișiere generate octet cu octet încetează să mai însemne ceva, pentru că modificarea unui singur câmp poate re-comprima cu Flate un întreg container și poate amesteca tot ce urmează după el. Comparați astfel de fișiere după conținutul obiectelor, nu printr-o comparație binară

Actualizările incrementale și offset-urile de octeți pe care le protejează

O semnătură digitală acoperă un /ByteRange explicit: două porțiuni ale fișierului fizic, date ca offset-uri absolute de octeți, peste care s-a calculat digest-ul CMS. Rescrieți fișierul, chiar și în ceva care arată identic pe ecran, și toate acele offset-uri se mută. Digest-ul încetează să se potrivească, iar semnătura se citește ca fiind ruptă. Aceasta este exact problema pe care ISO 32000-1 §7.5.6 o rezolvă prin actualizări incrementale. Obiectele noi și modificate sunt adăugate după %%EOF-ul existent, apoi este scrisă o secțiune proaspătă de referințe încrucișate a cărei intrare /Prev indică înapoi către cea dinainte. Octeții originali nu sunt niciodată deranjați, așa că o revizie semnată rămâne verificabilă, iar Acrobat poate prezenta fiecare revizie semnată separat, în panoul de semnătură

HotPDF expune acest lucru printr-un punct de intrare propriu:

Trei revizii HotPDF doar-adăugare înlănțuite prin intrări xref Prev, cu rezumatele ByteRange originale încă validând
Reviziile adăugate se înlănțuiesc înapoi prin intrări Prev și nu ating niciodată octeții pe care o semnătură i-a digerat, astfel încât fiecare revizie semnată anterioară continuă să valideze în timp ce fișierul crește doar spre dreapta
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');  // adaugă doar delta

Două lucruri îi împiedică pe oameni. BeginIncrementalUpdate trebuie să primească numele fișierului original, deoarece secțiunea xref adăugată înregistrează offset-uri care au sens doar față de acei octeți originali exacți; îndreptați-l către o copie redenumită sau resalvată și offset-urile vor descrie un fișier care nu mai există. Iar salvarea este append-only prin construcție, astfel încât ieșirea este întotdeauna mai mare decât intrarea. Acea creștere nu este risipă de eliminat prin ajustare. Este chiar proprietatea care lasă intacte reviziile semnate anterioare

Modificarea unui fișier încărcat trece prin LoadFromFile

Dezvoltatorii care au întâlnit pentru prima dată HotPDF prin API-ul său de generare tind să lovească un anumit zid. BeginDoc deschide un document complet nou, ceea ce este instrumentul greșit atunci când intenționați să modificați unul care există deja. Editarea unui fișier existent trece în schimb prin apelurile pentru documente încărcate:

PageCount := Pdf.LoadFromFile('base.pdf');
Pdf.InsertPagesFromDocument(OtherDoc, '1-3', 5);  // paginile 1-3 după pagina 5
Pdf.MovePage(2, 5);
Pdf.SaveLoadedDocument('modified.pdf');

Amestecați cele două, iar simptomul este un fișier de ieșire care conține noul dumneavoastră conținut și nimic din original, pentru că BeginDoc a construit vesel un document nou, alături de cel pe care credeați că îl editați. Citiți LoadFromFile împreună cu SaveLoadedDocument ca un vocabular, și BeginDoc împreună cu EndDoc ca altul. O rutină care apelează la ambele pentru același fișier este aproape întotdeauna greșită

Două vocabulare de salvare HotPDF, în care BeginDoc creează un fișier nou, iar LoadFromFile cu SaveLoadedDocument editează fișierul existent
Perechea de generare construiește un document complet nou, în timp ce perechea de document-încărcat editează ceea ce este deja pe disc — amestecarea celor două este motivul pentru care editările se expediază ocazional fără nicio pagină originală

Când să compactați un fișier extins

Salvarea append-only poartă un cost lent. O sarcină nocturnă care ștampilează o linie de stare pe același PDF produce 365 de revizii de-a lungul unui an, iar fiecare revizie trage după ea o nouă secțiune xref. Când acel istoric și-a depășit utilitatea, iar nicio semnătură din fișier nu trebuie să supraviețuiască, puteți aplatiza totul reserializându-l pe calea documentului încărcat:

Pdf.LoadFromFile('stamped.pdf');
Pdf.SaveLoadedDocument('compacted.pdf');

Această resalvare este o rescriere completă. Aruncă intenționat reviziile anterioare și strică orice semnătură rămasă în fișier, așa că plasați-o în spatele aceleiași porți de politică pe care o aplicați oricărui alt pas distructiv. O regulă de producție care se dovedește solidă: compactați atunci când numărul de revizii depășește un prag, sau atunci când overhead-ul adăugat crește peste o anumită proporție din fișierul de bază, și nu compactați niciodată un document al cărui panou de semnătură conține ceva

Verificarea rezultatului înainte de livrare

Verificarea acestei perechi de funcții este plăcut de concretă. Deschideți rezultatul în Adobe Acrobat și confirmați trei puncte: proprietățile documentului raportează PDF 1.5 sau ulterior odată ce fluxurile de obiecte sunt activate; panoul de semnătură încă validează fiecare revizie semnată anterior după o actualizare incrementală; iar numărul de pagini și marcajele au trecut nevătămate printr-un ciclu de încărcare, modificare și salvare. Pentru rezultate arhivistice, treceți fișierul și prin veraPDF, deoarece un xref comprimat este exact genul de structură pe care un validator strict îl cercetează mai atent decât o va face vreodată un vizualizator îngăduitor. Dacă munca dumneavoastră implică și intrări foarte mari, metodele de inspecție din ghidul nostru despre Direct File API pentru fluxuri de lucru cu PDF-uri mari se împerechează în mod natural cu salvarea incrementală, iar mecanica semnăturilor din spatele intervalelor de octeți de mai sus este tratată în profunzime în articolul HotPDF despre semnăturile digitale și PAdES

Ambele funcții vin ca parte din HotPDF Delphi Component pentru Delphi și C++Builder, alături de API-urile de generare, formulare, criptare și semnare tratate în altă parte pe acest blog. Pagina de produs face legătura către referința completă a API-ului, dacă doriți să aliniați apelurile de mai sus cu propriul dumneavoastră flux de documente