HotPDF THotPDF.CompressDocument is één schakelaar die BeginDoc de kleinst mogelijke verliesvrije PDF laat produceren die het component kan schrijven: FlateDecode op het maximale niveau, een cross-reference stream met object streams, font subsetting en compacte font subsets die de behouden glyphs hernummeren achter een expliciete /CIDToGIDMap. EndDoc zet daarna uw eigen instellingen terug. Een testdocument van drie pagina's met Arial en SimSun zakte van 10,2 MB naar 20 KB met identieke rendering
Wat schakelt CompressDocument eigenlijk aan?
CompressDocument overrulet zes writer-instellingen, plus de object-stream-cap, voor één document en zet ze daarna allemaal terug. Bij BeginDoc, voordat de PDF-versie vastligt, neemt HotPDF uw waarden op en zet Compression op cmFlateDecode, CompressionLevel op clMaximum, zet EnableFontSubsetting en CompactFontSubsetting aan, en activeert UseXRefStream plus UseObjectStreams (ISO 32000-1 §7.5.7 en §7.5.8). Object streams hebben PDF 1.5 nodig, dus een oudere Version wordt naar 1.5 verhoogd als hij niet vastgezet is. PDF/A-1 verbiedt beide structuren, dus een PDF/A-1-document houdt zijn klassieke cross-reference tabel en krijgt alleen de Flate- en fontwerkzaamheden. Afbeeldingen blijven precies zoals u ze insloot
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.AutoLaunch := False;
Pdf.FileName := 'invoice-2026-1042.pdf';
Pdf.CompressDocument := True; // toegepast door BeginDoc, ongedaan gemaakt door EndDoc
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('Arial', [], 12);
Pdf.CurrentPage.TextOut(40, 40, 0, 'Invoice 2026-1042');
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
De terugzet gebeurt in de buitenste finally van EndDoc, dus een exceptie halverwege een rapport laat een langlevend component niet vastzitten op maximale compressie voor de volgende job. De property CompressDocument zelf blijft True; alleen de zes instellingen die hij leende gaan terug. Met de versie wordt voorzichtiger omgegaan. HotPDF draait zijn eigen verhoging naar 1.5 alleen terug als het document nog op 1.5 eindigt, dus als een andere functie het bestand tijdens de run naar 1.6 duwde (een ingesloten OpenType-font bijvoorbeeld), blijft de hogere versie staan, precies zoals zonder compressie
Waarom blijven font subsets groot zonder compactie?
Een klassieke TrueType-subset laat de outlines vallen die u nooit tekent maar houdt elke glyph ID op zijn plek, en juist die nummering houdt hem zwaar. De content stream toont CIDs die gelijk zijn aan de oorspronkelijke GIDs, dus de subset moet een loca-offset en een hmtx-entry vasthouden voor elke plek tot aan de hoogste glyph die hij behoudt, leeg of niet. Voor een Latijns lettertype is die overhead ruis. Voor een CJK-lettertype als SimSun, waarvan de ideografen diep in een zeer grote glyftabel zitten, slepen twee Chinese karakters tabellen mee die voor het hele font gemeten zijn. De font subset closure-regels voor gevormde glyphs bepalen welke glyphs overleven; compactie gaat over hoeveel de overlevenden kosten
CompactFontSubsetting hernummert de behouden glyphs in een dichte reeks beginnend bij nul en schrijft een /CIDToGIDMap-stream op de CIDFont, die ISO 32000-1 §9.7.4.2 definieert als een tabel van two-byte GIDs geïndexeerd op CID. Die tabel is de hele truc. Content streams, de /W-breedte-array en de ToUnicode CMap houden allemaal de oorspronkelijke CIDs aan, dus niets wat al geschreven is hoeft te veranderen; alleen de opslag van CID naar glyph verhuist naar de map. In de test die de functie motiveerde, ging SimSun met twee karakters van 24,8 KB fontgegevens naar 3,1 KB
Compactie heeft harde grenzen, en hij degradeert stilletjes in plaats van te falen. HotPDF bouwt compacte subsets alleen voor Type 0 TrueType-faces, zowel die gezet via SetFont met subsetting aan als het via RegisterUnicodeTTF geregistreerde face. Een simpel TrueType-font vindt zijn glyphs via de cmap binnen het fontprogramma, en hernummeren zou die breken, dus hij houdt de sparse subset. OpenType-CFF-faces hebben ook geen compact pad. Een compacte build die faalt, valt terug op de sparse subset in plaats van te gooien. De property staat standaard uit, dus bestaande uitvoer blijft byte-identiek, terwijl onder PDF/A het geregistreerde Unicode-face altijd een compacte subset krijgt
Pdf.EnableFontSubsetting := True;
Pdf.CompactFontSubsetting := True; // bruikbaar zonder CompressDocument
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('SimSun', [], 12);
Pdf.CurrentPage.TextOut(40, 40, 0, WideString('Total: '#$4E2D#$6587));
Pdf.EndDoc;
Hoe perst de packed writer de bestandsstructuur uit?
Als fonts en streams eenmaal klein zijn, worden de dictionaries en de cross-reference data de grootste resterende kostenpost, dus de object-stream writer achter CompressDocument snoeit ook die. De gids over object streams en incremental updates behandelt het containerformaat zelf; het compressiepad legt daar bovenop vier verfijningen:
- Compacte syntaxis volgens ISO 32000-1 §7.2.2: een spatie wordt alleen geschreven tussen twee tokens die anders als gewone tekens aan elkaar plakken, dus
/Type /Pagewordt/Type/Page - Cross-reference stream-velden krijgen elke breedte die §7.5.8.2 toelaat, dus een bestand onder 16 MB bewaart elke offset in 3 bytes in plaats van 4
- Tot 250 objecten gaan in elke object stream in plaats van de gebruikelijke 100, tenzij u via
ConfigureAdaptiveObjectStreamPackinguw eigen cap zet - Als het bestand niet versleuteld is, worden de Catalog en de Info-dictionary ook in object streams gepakt; versleutelde uitvoer houdt ze op het topniveau
De compacte syntaxis kwam met een valkuil die de moeite van kennen waard is als u de writer uitbreidt. Signing vult de handtekening in nadat het bestand geschreven is door de bytes te doorzoeken op de letterlijke placeholders /ByteRange ( en /Contents <, en compacte spelling zou die veranderen in /ByteRange( en /Contents<, wat de zoektocht nooit vindt. Signature-dictionaries (Type Sig of DocTimeStamp, FT Sig) en de encryption-dictionary houden daarom de gespatieerde lay-out. Een gerelateerd defect trof builds vóór v2.766.41: elke object-stream-save, CompressDocument inbegrepen, begon met twee %PDF--headerregels, dus upgrade als een strenge validator uw uitvoer vlagt
Kunt u een PDF comprimeren die al geladen is?
Ja, via de options-overload CompressLoadedDocument(Options, Info), die dezelfde verliesvrije stappen op een bestaand bestand zet. Met THPDFLoadedDocumentCompressionOptions.Default verwijdert hij ongebruikte paginabronnen, voegt hij identieke fonts en formulieren samen, subset hij ingesloten fonts met compacte subsets aan, hercomprimeert hij ongefilterde, Flate-, LZW-, ASCII- en RunLength-streams met Flate als het resultaat kleiner is, en laat hij de volgende save object streams gebruiken. HighRatioFlate staat standaard uit, en object streams worden overgeslagen voor PDF/A-1 en incremental saves. De overload van CompressLoadedDocument zonder parameters is de oudere, nauwere aanroep die alleen ongecomprimeerde streams met Flate comprimeert
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;
Op het geladen pad doen twee grenzen ertoe. Elke stap herschrijft bytes die een handtekening dekt, dus een document met handtekeningvelden wordt als geheel geweigerd: de aanroep geeft 0 terug, zet RefusedBySignaturePolicy en verandert niets, tenzij u AllowSignatureInvalidation zet, waarna Info.SignaturesInvalidated u vertelt wat u hebt opgegeven. Compactie is hier ook conservatiever dan op het aanmaakpad. HotPDF compact alleen fontprogramma's die uitsluitend door CIDFontType2-fonts met een Identity-/CIDToGIDMap gebruikt worden, waar CID gelijk is aan GID, en slaat programma's over met een bestaande map-stream, een /CIDSet of kleurglyftabellen als COLR, sbix, CBDT of SVG, want de compacte herbouw zou de kleurlagen laten vallen. Merk ook op dat Info.BytesSaved alleen de resource-, font- en streamstappen somt; de object-stream-winst komt tevoorschijn zodra het bestand geschreven wordt
Welke resultaten mag u in de praktijk verwachten?
De winst volgt hoeveel van een bestand ongecomprimeerde structuur en oversized fontgegevens is, niet hoeveel pagina's het heeft. Het voorbeeld van drie pagina's met Arial en SimSun kromp van 10,2 MB naar 20 KB bij generatie met CompressDocument, en van 10,2 MB naar 19,8 KB toen het ongecomprimeerde origineel geladen en door CompressLoadedDocument gehaald werd, met identieke rendering beide keren. Een PDF die al compact is beweegt nauwelijks: in de regressieset bespaarden zulke bestanden binnen -0,07% tot +0,06% van hun oorspronkelijke grootte. Fotzware bestanden winnen weinig, want geen van beide paden raakt beeldgegevens aan
Als u elke nacht dezelfde CJK-rapporten genereert, combineer dan compacte subsets met de persistente font subset cache op schijf zodat het subsetwerk niet per run herhaald wordt, en diff gecomprimeerde uitvoer op objectcontent in plaats van op bytes, want één veranderd veld re-Flate een hele object stream. De volledige property- en recordreferenties staan op de productpagina van de HotPDF Delphi PDF component