HotPDF:s THotPDF.CompressDocument är en enskild omkopplare som får BeginDoc att producera den minsta förlustfria PDF komponenten kan skriva: FlateDecode på högsta nivån, en korsreferensström med objektströmmar, fontsubsetting och kompakta fontsubsets som omnumrerar de behållna glyferna bakom en explicit /CIDToGIDMap. EndDoc sätter sedan tillbaka dina egna inställningar. Ett tresidigt testdokument med Arial och SimSun föll från 10.2 MB till 20 KB med identisk rendering
Vad slår CompressDocument egentligen på?
CompressDocument åsidosätter sex skrivarinställningar, plus objektströmsgränsen, för ett dokument och återställer dem alla efteråt. Vid BeginDoc, innan PDF-versionen fastställs, noterar HotPDF dina värden och sätter Compression till cmFlateDecode, CompressionLevel till clMaximum, slår på EnableFontSubsetting och CompactFontSubsetting, och aktiverar UseXRefStream plus UseObjectStreams (ISO 32000-1 §7.5.7 och §7.5.8). Objektströmmar kräver PDF 1.5, så en äldre Version höjs till 1.5 när den inte är låst. PDF/A-1 förbjuder båda strukturerna, så ett PDF/A-1-dokument behåller sin klassiska korsreferenstabell och får bara Flate- och fontarbetet. Bilder lämnas exakt som du bäddade in dem
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.AutoLaunch := False;
Pdf.FileName := 'invoice-2026-1042.pdf';
Pdf.CompressDocument := True; // appliceras av BeginDoc, görs ogjord av EndDoc
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('Arial', [], 12);
Pdf.CurrentPage.TextOut(40, 40, 0, 'Invoice 2026-1042');
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
Återställningen sker i det yttersta finally-blocket i EndDoc, så ett undantag halvvägs genom en rapport lämnar inte en långlivad komponent fastlåst på maximal komprimering inför nästa jobb. Egenskapen CompressDocument själv förblir True; bara de sex inställningar den lånade går tillbaka. Versionen hanteras med större omsorg. HotPDF gör sin höjning till 1.5 ogjord bara om dokumentet fortfarande slutar på 1.5, så när en annan funktion knuffat filen till 1.6 under körningen (ett inbäddat OpenType-teckensnitt, säg), stannar den högre versionen kvar, exakt som den skulle ha gjort utan komprimering
Varför förblir fontsubsets stora utan kompaktering?
En klassisk TrueType-subset släpper de konturer du aldrig ritar men behåller varje glyph-ID där det ligger, och den numreringen är det som håller den tung. Innehållsströmmen visar CIDs som är lika med de ursprungliga GID:erna, så subseten måste behålla en loca-offset och en hmtx-post för varje plats upp till den högsta glyf den behåller, tom eller ej. För ett latinskt teckensnitt är den omkostnaden brus. För ett CJK-teckensnitt som SimSun, vars ideografier ligger djupt inne i en mycket stor glyftabell, släpar två kinesiska tecken med sig tabeller dimensionerade för hela fonten. Reglerna för fontsubsets closure för formade glyfer avgör vilka glyfer som överlever; kompakteringen handlar om hur mycket överlevarna kostar
CompactFontSubsetting omnumrerar de behållna glyferna till ett tätt intervall som börjar på noll och skriver en /CIDToGIDMap-ström på CIDFont:en, som ISO 32000-1 §9.7.4.2 definierar som en tabell av tvåbyte-GID:er indexerade med CID. Den tabellen är hela tricket. Innehållsströmmar, breddarrayen /W och ToUnicode-CMap:en behåller alla de ursprungliga CID:erna, så inget som redan skrivits behöver ändras; bara uppslagningen från CID till glyf flyttas in i mappen. I testet som motiverade funktionen gick SimSun med två tecken från 24.8 KB fontdata till 3.1 KB
Kompakteringen har fasta gränser, och den degraderar tyst i stället för att fallera. HotPDF bygger kompakta subsets bara för Type 0 TrueType-teckensnitt, både de som sätts genom SetFont med subsetting påslagen och det teckensnitt som registreras genom RegisterUnicodeTTF. Ett enkelt TrueType-teckensnitt hittar sina glyfer via cmap:en inuti fontprogrammet, vilket omnumreringen skulle bryta, så det behåller den glesa subseten. OpenType-CFF-teckensnitt har ingen kompakt väg heller. Ett kompakt bygge som misslyckas faller tillbaka till den glesa subseten i stället för att kasta. Egenskapen är avslagen som standard, så befintlig utdata förblir byte-identisk, medan det registrerade Unicode-teckensnittet under PDF/A alltid får en kompakt subset
Pdf.EnableFontSubsetting := True;
Pdf.CompactFontSubsetting := True; // användbar utan CompressDocument
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('SimSun', [], 12);
Pdf.CurrentPage.TextOut(40, 40, 0, WideString('Total: '#$4E2D#$6587));
Pdf.EndDoc;
Hur klämmer den packade skrivaren åt filstrukturen?
När teckensnitten och strömmarna är små blir ordböckerna och korsreferensdatan den största återstående kostnaden, så objektströmsskrivaren bakom CompressDocument stryker även där. Guiden om objektströmmar och inkrementella uppdateringar täcker själva behållarformatet; komprimeringsvägen lägger fyra förfiningar ovanpå:
- Kompakt syntax enligt ISO 32000-1 §7.2.2: ett blanksteg skrivs bara mellan två token som annars skulle löpa ihop som vanliga tecken, så
/Type /Pageblir/Type/Page - Korsreferensströmmens fält tar vilken bredd §7.5.8.2 tillåter, så en fil under 16 MB lagrar varje offset i 3 byte i stället för 4
- Upp till 250 objekt går i varje objektström i stället för de vanliga 100, såvida du inte sätter egen gräns genom
ConfigureAdaptiveObjectStreamPacking - När filen inte är krypterad packas Catalog och Info-ordboken in i objektströmmarna också; krypterad utdata behåller dem på toppnivån
Den kompakta syntaxen kom med en fälla värd att känna till om du bygger ut skrivaren. Signeringen fyller i signaturen efter att filen skrivits genom att söka i bytena efter de literala platshållarna /ByteRange ( och /Contents <, och kompakt stavning skulle förvandla dem till /ByteRange( och /Contents<, vilket sökningen aldrig hittar. Signaturordböcker (Type Sig eller DocTimeStamp, FT Sig) och krypteringsordboken behåller därför den mellanslagna layouten. En relaterad defekt drabbade byggen före v2.766.41: varje objektströmssparning, CompressDocument inkluderad, började med två %PDF--huvudrader, så uppgradera om en strikt validerare flaggar din utdata
Kan du komprimera en PDF som redan är inläst?
Ja, genom options-överloaden CompressLoadedDocument(Options, Info), som kör samma förlustfria steg på en befintlig fil. Med THPDFLoadedDocumentCompressionOptions.Default tar den bort oanvända sidresurser, slår ihop identiska teckensnitt och formulär, gör subsets av inbäddade teckensnitt med kompakta subsets påslagna, komprimerar om ofiltrerade Flate-, LZW-, ASCII- och RunLength-strömmar med Flate när resultatet är mindre, och låter nästa sparande använda objektströmmar. HighRatioFlate är avslagen som standard, och objektströmmar hoppas över för PDF/A-1 och inkrementella sparanden. Den parameterlösa CompressLoadedDocument-överloaden är det äldre, smalare anropet som bara Flate-komprimerar okomprimerade strömmar
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;
Två gränser spelar roll på den inlästa vägen. Varje steg skriver om byte som en signatur täcker, så ett dokument med signaturfält vägras som helhet: anropet returnerar 0, sätter RefusedBySignaturePolicy och ändrar ingenting, om du inte sätter AllowSignatureInvalidation, varefter Info.SignaturesInvalidated talar om vad du gav upp. Kompakteringen är också mer konservativ här än på skapandevägen. HotPDF kompakterar bara fontprogram som enbart används av CIDFontType2-teckensnitt med en Identity-/CIDToGIDMap, där CID är lika med GID, och hoppar över program med en befintlig map-ström, en /CIDSet eller färgglyftabeller som COLR, sbix, CBDT eller SVG, eftersom det kompakta återbygget skulle tappa färglagren. Notera också att Info.BytesSaved summerar bara resurs-, font- och strömstegen; objektströmsvinsten dyker upp när filen skrivs
Vilka resultat ska du vänta dig i praktiken?
Vinsterna följer hur stor del av en fil som är okomprimerad struktur och överdimensionerad fontdata, inte hur många sidor den har. Det tresidiga Arial- och SimSun-exemplet krympte från 10.2 MB till 20 KB när det genererades med CompressDocument, och från 10.2 MB till 19.8 KB när den okomprimerade originalfilen lästes in och kördes genom CompressLoadedDocument, med identisk rendering åt båda hållen. En PDF som redan är kompakt rör sig knappt: i regressionsmängden sparade sådana filer inom -0.07% till +0.06% av sin ursprungliga storlek. Fototunga filer vinner lite, eftersom ingen av vägarna rör bilddata
Om du genererar samma CJK-rapporter varje natt, para ihop kompakta subsets med den beständiga fontsubset-cachen på disk så att subsetarbetet inte upprepas per körning, och diffa komprimerade utdata efter objektinnehåll i stället för byte, eftersom ett ändrat fält Flate-om en hel objektström. Fullständiga egenskaps- och postreferenser finns på produktsidan för HotPDF Delphi PDF component