HotPDF THotPDF.CompressDocument er én enkelt kontakt, der får BeginDoc til at producere den mindste tabsfri PDF, komponenten kan skrive: FlateDecode på maksimalt niveau, en cross-reference stream med object streams, font subsetting og kompakte font-undersæt, der omnummererer de beholdte glyffer bag et eksplicit /CIDToGIDMap. EndDoc lægger derefter dine egne indstillinger tilbage. Et testdokument på tre sider med Arial og SimSun faldt fra 10,2 MB til 20 KB med identisk rendering
Hvad slår CompressDocument egentlig til?
CompressDocument tilsidesætter seks writer-indstillinger plus object-stream-grænsen for ét dokument og gendanner dem alle bagefter. Ved BeginDoc, før PDF-versionen falder på plads, noterer HotPDF dine værdier og sætter Compression til cmFlateDecode, CompressionLevel til clMaximum, slår EnableFontSubsetting og CompactFontSubsetting til og aktiverer UseXRefStream plus UseObjectStreams (ISO 32000-1 §7.5.7 og §7.5.8). Object streams kræver PDF 1.5, så en ældre Version hæves til 1.5, når den ikke er låst. PDF/A-1 forbyder begge strukturer, så et PDF/A-1-dokument beholder sin klassiske cross-reference-tabel og får kun Flate- og font-arbejdet. Billeder efterlades præcis, som du indlejrede dem
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.AutoLaunch := False;
Pdf.FileName := 'invoice-2026-1042.pdf';
Pdf.CompressDocument := True; // anvendes af BeginDoc, fortrydes af EndDoc
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('Arial', [], 12);
Pdf.CurrentPage.TextOut(40, 40, 0, 'Invoice 2026-1042');
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
Gendannelsen sker i den yderste finally i EndDoc, så en exception midt i en rapport ikke efterlader en langlivet komponent fastlåst på maksimal komprimering til næste job. Egenskaben CompressDocument selv forbliver True; kun de seks indstillinger, den lånte, går tilbage. Versionen håndteres med mere omtanke. HotPDF fortryder kun sin egen hævning til 1.5, hvis dokumentet stadig slutter på 1.5, så når en anden funktion har skubbet filen til 1.6 under kørslen (en indlejret OpenType-font, for eksempel), forbliver den højere version, præcis som den ville have gjort uden komprimering
Hvorfor er font-undersæt stadig store uden komprimering?
Et klassisk TrueType-undersæt dropper de konturer, du aldrig tegner, men beholder hvert glyph ID, hvor det lå, og det er nummereringen, der gør det tungt. Content streamen viser CIDs, der svarer til de oprindelige GIDs, så undersættet skal beholde en loca-offset og et hmtx-indslag for hver plads op til den højeste glyf, den beholder, tom eller ej. For en latinsk skrifttype er det overhead støj. For en CJK-skrifttype som SimSun, hvis ideografer ligger dybt i en meget stor glyftabel, slæber to kinesiske tegn tabeller i størrelse til hele fonten med sig. Reglerne for font subset closure for formede glyffer afgør, hvilke glyffer der overlever; komprimering handler om, hvor meget de overlevende koster
CompactFontSubsetting omnummererer de beholdte glyffer til et tæt interval, der starter ved nul, og skriver en /CIDToGIDMap-stream på CIDFonten, som ISO 32000-1 §9.7.4.2 definerer som en tabel over to-byte GIDs indekseret efter CID. Den tabel er hele tricket. Content streams, /W-bredde-arrayet og ToUnicode CMap beholder alle de oprindelige CIDs, så intet, der allerede er skrevet, behøver at ændres; kun opslaget fra CID til glyf flytter ind i mappen. I testen, der motiverede funktionen, gik SimSun med to tegn fra 24,8 KB fontdata til 3,1 KB
Komprimering har skrappe grænser, og den degraderer i stilhed frem for at fejle. HotPDF bygger kompakte undersæt kun til Type 0 TrueType-faces, både dem, der sættes gennem SetFont med subsetting slået til, og face'en registreret gennem RegisterUnicodeTTF. En simpel TrueType-font finder sine glyffer gennem cmap'en inde i fontprogrammet, hvilket omnummerering ville ødelægge, så den beholder det tynde undersæt. OpenType-CFF-faces har heller ingen kompakt sti. Et kompakt build, der fejler, falder tilbage til det tynde undersæt i stedet for at rejse en exception. Egenskaben er slået fra som standard, så eksisterende output forbliver byte-identisk, mens den registrerede Unicode-face under PDF/A altid får et kompakt undersæt
Pdf.EnableFontSubsetting := True;
Pdf.CompactFontSubsetting := True; // kan bruges uden CompressDocument
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('SimSun', [], 12);
Pdf.CurrentPage.TextOut(40, 40, 0, WideString('Total: '#$4E2D#$6587));
Pdf.EndDoc;
Hvordan presser den pakkede writer filstrukturen?
Når fonts og streams først er små, bliver dictionaries og cross-reference-data den største resterende omkostning, så object-stream-writeren bag CompressDocument trimmer dem også. Guiden om object streams og incremental updates dækker containerformatet selv; komprimeringsstien tilføjer fire forfinelser oven på:
- Kompakt syntaks efter ISO 32000-1 §7.2.2: et mellemrum skrives kun mellem to tokens, der ellers ville løbe sammen som almindelige tegn, så
/Type /Pagebliver til/Type/Page - Cross-reference-stream-felter tager enhver bredde, §7.5.8.2 tillader, så en fil under 16 MB gemmer hver offset i 3 bytes i stedet for 4
- Op til 250 objekter går i hver object stream i stedet for de sædvanlige 100, medmindre du sætter din egen grænse gennem
ConfigureAdaptiveObjectStreamPacking - Når filen ikke er krypteret, pakkes Catalog og Info-dictionaryen også ind i object streams; krypteret output beholder dem på topniveau
Den kompakte syntaks kom med en fælde, der er værd at kende, hvis du udvider writeren. Signering udfylder signaturen, efter filen er skrevet, ved at søge bytesene efter literal-pladsholderne /ByteRange ( og /Contents <, og kompakt stavning ville gøre dem til /ByteRange( og /Contents<, som søgningen aldrig finder. Signature-dictionaries (Type Sig eller DocTimeStamp, FT Sig) og encryption-dictionaryen beholder derfor det mellemrumslagte layout. En relateret defekt ramte builds før v2.766.41: hver object-stream-gemning, CompressDocument inkluderet, begyndte med to %PDF--headerlinjer, så opgradér, hvis en streng validator markerer dit output
Kan du komprimere en PDF, der allerede er indlæst?
Ja, gennem options-overloaden CompressLoadedDocument(Options, Info), som kører de samme tabsfri trin på en eksisterende fil. Med THPDFLoadedDocumentCompressionOptions.Default fjerner den ubrugte sideressourcer, merger identiske fonts og formularer, laver undersæt af indlejrede fonts med kompakte undersæt slået til, komprimerer ufiltrerede, Flate-, LZW-, ASCII- og RunLength-streams om med Flate, når resultatet er mindre, og får næste gemning til at bruge object streams. HighRatioFlate er slået fra som standard, og object streams springes over ved PDF/A-1 og inkrementelle gemninger. Den parameterløse CompressLoadedDocument-overload er det ældre, smallere kald, der kun Flate-komprimerer ukomprimerede streams
var
Doc: THotPDF;
Options: THPDFLoadedDocumentCompressionOptions;
Info: THPDFLoadedDocumentCompressionInfo;
begin
Doc := THotPDF.Create(nil);
try
Doc.AutoLaunch := False;
Doc.LoadFromFile('quarterly-report.pdf');
Options := THPDFLoadedDocumentCompressionOptions.Default;
Doc.CompressLoadedDocument(Options, Info);
if Info.RefusedBySignaturePolicy then
Writeln(Format('Left untouched: %d signature fields', [Info.SignatureCount]))
else
begin
Writeln(Format('Compact fonts: %d, stream bytes saved: %d',
[Info.Fonts.CompactSubsetFontCount, Info.BytesSaved]));
Doc.SaveLoadedDocument('quarterly-report-compact.pdf');
end;
finally
Doc.Free;
end;
end;
To grænser betyder noget på den indlæste sti. Hvert trin omskriver bytes, som en signatur dækker, så et dokument med signaturfelter afvises som helhed: kaldet returnerer 0, sætter RefusedBySignaturePolicy og ændrer ingenting, medmindre du sætter AllowSignatureInvalidation, hvorefter Info.SignaturesInvalidated fortæller dig, hvad du gav afkald på. Komprimeringen er også mere konservativ her end på oprettelsesstien. HotPDF komprimerer kun fontprogrammer, der udelukkende bruges af CIDFontType2-fonts med et Identity /CIDToGIDMap, hvor CID svarer til GID, og springer programmer over med en eksisterende map-stream, en /CIDSet eller farveglyf-tabeller som COLR, sbix, CBDT eller SVG, fordi det kompakte genopbyg ville droppe farvelagene. Bemærk også, at Info.BytesSaved kun summerer ressource-, font- og stream-trinnene; gevinsten fra object streams viser sig, når filen skrives
Hvilke resultater kan du forvente i praksis?
Gevinsten følger, hvor stor en del af en fil er ukomprimeret struktur og overdimensionerede fontdata, ikke hvor mange sider den har. Prøven på tre sider med Arial og SimSun skrumpede fra 10,2 MB til 20 KB, da den blev genereret med CompressDocument, og fra 10,2 MB til 19,8 KB, da originalen uden komprimering blev indlæst og kørt gennem CompressLoadedDocument, med identisk rendering begge veje. En PDF, der allerede er kompakt, flytter sig næsten ikke: i regressionssættet sparede sådanne filer inden for -0,07 % til +0,06 % af deres oprindelige størrelse. Fototunge filer vinder lidt, fordi ingen af stierne rører billeddata
Hvis du genererer de samme CJK-rapporter hver nat, så par kompakte undersæt med den vedvarende font subset cache på disken, så subsetting-arbejdet ikke gentages pr. kørsel, og diff komprimerede outputs efter objektindhold frem for bytes, da ét ændret felt re-Flater en hel object stream. Den fulde reference til egenskaber og records ligger på HotPDF Delphi PDF component-produktsiden