PDF 1.5 introducerede to lagringsstrukturer, som det tidligere filformat ikke havde nogen måde at udtrykke på: object stream'en og cross-reference stream'en. En object stream er én Flate-komprimeret container, mærket /Type /ObjStm, der rummer mange små indirekte objekter pakket ende mod ende i stedet for at sprede dem ud gennem filens brødtekst. En cross-reference stream er filens opslagstabel omskrevet som komprimeret binærdata med felter af variabel bredde, i stedet for den faste ASCII-tabel med fast bredde, der afsluttede hver PDF op til version 1.4. De rejser sammen. Når først objekter er foldet ind i en stream, kan den gamle tekst-tabel ikke længere adressere dem, så den binære xref er nødt til at følge med
Sæt det op mod det klassiske layout, og omkostningen, det fjerner, er let at se. I en PDF 1.4-fil sidder hvert indirekte objekt ukomprimeret bag sin egen obj-header, og tabellen for enden bruger nøjagtigt 20 byte ASCII per post, kompression forbudt. Et dokument med 200.000 objekter bærer rundt regnet 4 MB cross-reference-data, før et eneste glyf er tegnet, med alle de ukomprimerede dictionary-legemer stablet oven på. PDF 1.5 angriber begge tal på én gang: dictionaries foldes ind i Flate-containere, og 4 MB-tabellen krymper til et par hundrede kilobyte binærdata. ISO 32000-1 definerer de to strukturer i §7.5.7 og §7.5.8
Hvor besparelsen rent faktisk lander
Object streams rører kun ved ikke-stream-objekter, så de komprimerer struktur, ikke pixels. Sideindhold var allerede Flate-komprimeret før 1.5, og billeddata bærer sine egne codecs, hvilket er grunden til, at en billedtung brochure næppe rykker sig. De filer, der kollapser, er de strukturtunge: AcroForms med tusindvis af felt-dictionaries, dybe outline-træer, tagged-PDF-strukturelementer. De objekter er små, talrige og næsten identiske med hinanden, og den gentagelse er præcis, hvad Flate udnytter, når de først sidder i én samlet buffer i stedet for spredt ud gennem brødteksten med headere kilet ind imellem
Det er let at undervurdere, hvor stor en del af en gammel fil der er overhead. Et formulararkiv, der har absorberet års redigeringer, kan bruge langt over halvdelen af sine bytes på dictionary-headere, xref-udfyldning og revisioner, som ingen læser nogensinde vil kigge på. De to funktioner her genvinder de første to af disse. Den tredje, akkumulerede revisioner, giver kun efter for komprimering, når filen ikke længere skal huske sin egen historie
I HotPDF slår man begge til gennem et par egenskaber, og hvordan de afhænger af hinanden, betyder mere end den rækkefølge, man skriver dem i:
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := 'catalog-2026.pdf';
Pdf.UseXRefStream := True; // binær xref, forudsætning for ObjStm
Pdf.UseObjectStreams := True; // pak objekter ind i /Type /ObjStm
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('Arial', [], 11);
Pdf.CurrentPage.TextOut(50, 760, 0, 'Compressed structure demo');
Pdf.EndDoc; // udsender XRefStm + ObjStm-containere
finally
Pdf.Free;
end;
end;
UseObjectStreams kræver, at UseXRefStream er sat til True. Et komprimeret objekt nås gennem en type-2-xref-post, som registrerer et object-stream-nummer plus et indeks, og en klassisk 20-byte tekstrække har ingen plads til at gemme det par. Så UseObjectStreams alene gør ingenting synligt; begge flag, sat før BeginDoc, er den konfiguration, der virker. Sætter man dem efter BeginDoc, har HotPDF allerede forpligtet sig til det ældre layout
Hvorfor begge som standard er slået fra
HotPDF lader begge egenskaber stå på False ud af boksen, og grunden viser sig i integrationer med gammel downstream-kode. En læser, der kun forstår PDF 1.4, annoncerer ikke, at den ikke kan håndtere komprimerede objekter. Den møder en xref stream, finder ingen af de trailer-nøgleord, den forventer, og rapporterer en beskadiget cross-reference-tabel eller nægter simpelthen at åbne filen. Hvis dit output strømmer ind i en aldrende faxgateway, en hardwareprinter, der kører en indlejret fortolker, eller en parser, nogen skrev mod 1.4-specifikationen for et årti siden, så hold begge flag slået fra for den kanal og lev med den større fil. For arkivlagring og web-levering, hvor enhver gængs viser har kunnet læse PDF 1.5 i tyve år, er det at slå dem til kompression, man får for næsten ingenting
Der er en andenordens-effekt, det er værd at fortælle dit supportteam om. Når først dictionaries er pakket ind i object streams, holder det op med at give mening at sammenligne to genererede filer byte for byte, fordi ændring af et enkelt felt kan Flate-komprimere en hel container igen og forskyde alt derefter. Diff sådanne filer efter objektindhold, ikke med en binær sammenligning
Inkrementelle opdateringer og de byte-offsets, de beskytter
En digital signatur dækker et eksplicit /ByteRange: to strækninger af den fysiske fil, angivet som absolutte byte-offsets, som CMS-digestet blev taget over. Omskriver man filen, selv til noget, der ser identisk ud på skærmen, flytter alle de offsets sig. Digestet holder op med at matche, og signaturen læses som brudt. Det er præcis det problem, ISO 32000-1 §7.5.6 løser med inkrementelle opdateringer. Nye og ændrede objekter tilføjes efter det eksisterende %%EOF, hvorefter et frisk cross-reference-afsnit skrives, hvis /Prev-post peger tilbage til det forrige. De oprindelige bytes forstyrres aldrig, så en signeret revision forbliver verificerbar, og Acrobat kan præsentere hver signeret revision for sig i signaturpanelet
HotPDF eksponerer dette gennem sit eget indgangspunkt:
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'); // tilføjer kun deltaet
To ting snubler folk over. BeginIncrementalUpdate skal modtage det oprindelige filnavn, fordi det tilføjede xref-afsnit registrerer offsets, der kun giver mening mod netop de oprindelige bytes; peger man den mod en omdøbt eller gemt-igen kopi, beskriver offsettene en fil, der ikke længere findes. Og gemningen er append-only af konstruktion, så outputtet er altid større end inputtet. Den vækst er ikke spild, der skal tunes væk. Det er den samme egenskab, der lader tidligere signerede revisioner blive intakte
At ændre en indlæst fil går gennem LoadFromFile
Udviklere, der først mødte HotPDF gennem dets genererings-API, har en tendens til at ramme en bestemt mur. BeginDoc åbner et splinternyt dokument, hvilket er det forkerte værktøj, når man har til hensigt at ændre et, der allerede findes. At redigere en eksisterende fil kører i stedet gennem de indlæste-dokument-kald:
PageCount := Pdf.LoadFromFile('base.pdf');
Pdf.InsertPagesFromDocument(OtherDoc, '1-3', 5); // side 1-3 efter side 5
Pdf.MovePage(2, 5);
Pdf.SaveLoadedDocument('modified.pdf');
Blander man de to, er symptomet en outputfil, der rummer dit nye indhold og intet af originalen, fordi BeginDoc muntert byggede et helt nyt dokument ved siden af det, du troede, du redigerede. Læs LoadFromFile med SaveLoadedDocument som ét ordforråd og BeginDoc med EndDoc som et andet. En rutine, der griber til begge mod den samme fil, er næsten altid forkert
Hvornår man skal komprimere en fil med tilføjelser
Append-only-gemning bærer en langsom omkostning. Et natligt job, der stempler én statuslinje på den samme PDF, producerer 365 revisioner i løbet af et år, og hver revision slæber et nyt xref-afsnit med sig. Når den historik har udspillet sin nytte, og ingen signatur i filen behøver at overleve, kan man flade det hele ud ved at reserialisere det gennem den indlæste-dokument-vej:
Pdf.LoadFromFile('stamped.pdf');
Pdf.SaveLoadedDocument('compacted.pdf');
Denne genlagring er en fuld omskrivning. Den kasserer med vilje de tidligere revisioner og ødelægger enhver signatur, der stadig er i filen, så placér den bag den samme politikport, man anvender på ethvert andet destruktivt trin. Én produktionsregel, der holder: komprimér, når revisionsantallet overstiger en tærskel, eller når det tilføjede overhead vokser forbi en vis andel af basisfilen, og komprimér aldrig et dokument, hvis signaturpanel har noget i sig
At tjekke outputtet, før det sendes af sted
At verificere dette par af funktioner er forfriskende konkret. Åbn resultatet i Adobe Acrobat, og bekræft tre punkter: dokumentegenskaberne rapporterer PDF 1.5 eller nyere, når object streams er slået til; signaturpanelet validerer stadig hver tidligere signeret revision efter en inkrementel opdatering; og sidetal og bogmærker kom uskadt gennem en indlæs-rediger-gem-cyklus. For arkivoutput, kør filen gennem veraPDF også, da en komprimeret xref er præcis den slags struktur, en streng validator gransker tættere, end en overbærende viser nogensinde vil. Hvis dit arbejde også involverer meget store inputs, kombinerer inspektionsmetoderne i vores gennemgang af Direct File API'et til store PDF-workflows sig naturligt med inkrementel gemning, og signaturmekanikken bag byte-områderne ovenfor behandles i dybden i artiklen om HotPDF digitale signaturer og PAdES
Begge funktioner leveres som en del af HotPDF Delphi Component til Delphi og C++Builder, ved siden af generings-, formular-, kryptering- og signerings-API'erne, der dækkes andre steder på denne blog. Produktsiden linker til den fulde API-reference, hvis du vil holde kaldene ovenfor op mod din egen dokumentpipeline