HotPDF THotPDF.CompressDocument je jediný prepínač, ktorý prinúti BeginDoc vyrobiť najmenší bezstratový PDF, aký komponent dokáže zapísať: FlateDecode na maximálnej úrovni, cross-reference stream s object streamami, font subsetting a kompaktné subsety fontov, ktoré prečíslujú ponechané glyfy za explicitným /CIDToGIDMap. EndDoc potom vráti vaše vlastné nastavenia. Trojstranový testovací dokument s Arial a SimSun klesol z 10,2 MB na 20 kB pri identickom vykreslení
Čo CompressDocument vlastne zapína?
CompressDocument prebije šesť nastavení zapisovača, plus strop object streamov, pre jeden dokument a potom všetky obnoví. Pri BeginDoc, skôr než sa PDF verzia ustáli, si HotPDF zapamätá vaše hodnoty a nastaví Compression na cmFlateDecode, CompressionLevel na clMaximum, zapne EnableFontSubsetting a CompactFontSubsetting a povolí UseXRefStream plus UseObjectStreams (ISO 32000-1 §7.5.7 a §7.5.8). Object streamy potrebujú PDF 1.5, takže staršia Version sa zvýši na 1.5, keď nie je zamknutá. PDF/A-1 obom štruktúram zakazuje, takže dokument PDF/A-1 si nechá klasickú cross-reference tabuľku a získa len Flate a font prácu. Obrázky ostanú presne tak, ako ste ich vložili
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.AutoLaunch := False;
Pdf.FileName := 'invoice-2026-1042.pdf';
Pdf.CompressDocument := True; // aplikuje BeginDoc, ruší EndDoc
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('Arial', [], 12);
Pdf.CurrentPage.TextOut(40, 40, 0, 'Invoice 2026-1042');
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
Obnova prebieha vo najvnejšom finally metódy EndDoc, takže výnimka v polovici reportu nenechá dlho žijúci komponent zaseknutý na maximálnej kompresii pre ďalšiu úlohu. Samotná vlastnosť CompressDocument ostane True; vracajú sa len šesť nastavení, ktoré požičala. S verziou sa narába o pozornejšie. HotPDF ruší vlastné zvýšenie na 1.5, len keď dokument stále končí na 1.5, takže keď iná funkcia posunula súbor počas behu na 1.6 (povedzme vložený OpenType font), vyššia verzia ostane, presne ako by ostala bez kompresie
Prečo sú subsety fontov bez kompaktizácie stále veľké?
Klasický TrueType subset zahodí obrysy, ktoré nikdy nekreslíte, ale každé glyph ID nechá tam, kde bolo, a práve to číslovanie ho drží ťažké. Content stream ukazuje CID rovné pôvodným GID, takže subset musí držať offset loca a záznam hmtx pre každý slot až po najvyšší glyf, ktorého si drží, či je prázdny alebo nie. Pri latinskom písme je tá réžia hluk. Pri CJK písme ako SimSun, ktorého ideografy sedia hlboko vo veľmi veľkej glyfovej tabuľke, dva čínske znaky ťahajú so sebou tabuľky dimenzované na celý font. O tom, ktoré glyfy prežijú, rozhodujú pravidlá uzáveru subsetu pre tvarované glyfy; kompaktizácia je o tom, koľko stoja prežívší
CompactFontSubsetting prečísluje ponechané glyfy do hustého rozsahu začínajúceho nulou a zapíše na CIDFont stream /CIDToGIDMap, ktorý ISO 32000-1 §9.7.4.2 definuje ako tabuľku dvojbajtových GID indexovaných CID. Tá tabuľka je celý trik. Content streamy, šírkové pole /W aj ToUnicode CMap si držia pôvodné CID, takže nič už zapísané sa nemusí meniť; do mapy sa presunie len vyhľadanie z CID na glyf. V teste, ktorý funkciu motivoval, klesol SimSun s dvoma znakmi z 24,8 kB fontových dát na 3,1 kB
Kompaktizácia má pevné hranice a degraduje potichu namiesto toho, aby padla. HotPDF stavia kompaktné subsety len pre písma Type 0 TrueType, a to tak tie nastavené cez SetFont so zapnutým subsettingom, ako aj písmo zaregistrované cez RegisterUnicodeTTF. Jednoduchý TrueType font nachádza svoje glyfy cez cmap vnútri font programu, čo by prečíslovanie rozbilo, takže si drží riedky subset. Ani písma OpenType-CFF nemajú kompaktnú cestu. Kompaktné zostavenie, ktoré zlyhá, spadne späť na riedky subset namiesto vyhodenia chyby. Vlastnosť je predvolene vypnutá, takže existujúci výstup ostanú bajtovo identický, zatiaľ čo pod PDF/A dostane zaregistrované Unicode písmo kompaktný subset vždy
Pdf.EnableFontSubsetting := True;
Pdf.CompactFontSubsetting := True; // použiteľné aj bez CompressDocument
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('SimSun', [], 12);
Pdf.CurrentPage.TextOut(40, 40, 0, WideString('Total: '#$4E2D#$6587));
Pdf.EndDoc;
Ako zhutnený zapisovač vymiesti štruktúru súboru?
Keď sú fonty a streamy malé, slovníky a cross-reference dáta sa stanú najväčšou zostávajúcou položkou, takže object-stream zapisovač za CompressDocument oreže aj tie. Formát kontajnera samotný popisuje sprievodca object streamami a inkrementálnymi aktualizáciami; kompresná cesta pridáva štyri vylepšenia navyše:
- Kompaktná syntax podľa ISO 32000-1 §7.2.2: medzera sa zapisuje len medzi dva tokeny, ktoré by inak stiekli dokopy ako obyčajné znaky, takže
/Type /Pagesa stane/Type/Page - Polia cross-reference streamu zaberú ľubovoľnú šírku, ktorú dovoľuje §7.5.8.2, takže súbor pod 16 MB ukladá každý offset v 3 bajtoch namiesto 4
- Do každého object streamu vchádza až 250 objektov namiesto obvyklých 100, pokiaľ si vlastný strop nenastavíte cez
ConfigureAdaptiveObjectStreamPacking - Keď súbor nie je šifrovaný, Catalog aj Info slovník sa pribalia do object streamov tiež; šifrovaný výstup ich necháva na najvyššej úrovni
Kompaktná syntax si priniesla pascu, ktorú stoja za poznanie, keď zapisovač rozširujete. Podpisovanie doplní podpis až po zapísaní súboru tak, že v bajtoch hľadá literálne zástupné znaky /ByteRange ( a /Contents <, a kompaktné hláskovanie by z nich urobilo /ByteRange( a /Contents<, čo hľadanie nikdy nenájde. Slovníky podpisov (Type Sig alebo DocTimeStamp, FT Sig) aj šifrovací slovník si preto držia rozmedzené rozloženie. Súvisiaci defekt postihol zostavenia pred v2.766.41: každé uloženie s object streamami, vrátane CompressDocument, začínalo dvoma hlavičkovými riadkami %PDF-, takže urobte upgrade, keď prísny validator označí váš výstup
Dá sa komprimovať PDF, ktoré je už načítané?
Áno, cez overload s options CompressLoadedDocument(Options, Info), ktorý prevedie tie isté bezstratové kroky na existujúcom súbore. S THPDFLoadedDocumentCompressionOptions.Default odstráni nepoužívané zdroje strán, zlúči identické fonty a formuláre, subsetuje vložené fonty so zapnutými kompaktnými subsetmi, prekomprimuje nefiltrované, Flate, LZW, ASCII a RunLength streamy cez Flate, keď je výsledok menší, a prinúti ďalšie uloženie použiť object streamy. HighRatioFlate je predvolene vypnuté a object streamy sa preskočia pri PDF/A-1 a inkrementálnych ukladaniach. Overload CompressLoadedDocument bez parametrov je staršie, užšie volanie, ktoré cez Flate komprimuje len nekomprimované streamy
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;
Na načítanej ceste majú význam dve hranice. Každý krok prepisuje bajty, ktoré kryje podpis, takže dokument s podpisovými poliami sa odmietne ako celok: volanie vráti 0, nastaví RefusedBySignaturePolicy a nič nezmení, pokiaľ nenastavíte AllowSignatureInvalidation, po čom vám Info.SignaturesInvalidated povie, čím ste prišli. Kompaktizácia je tu tiež konzervatívnejšia než na ceste tvorenia. HotPDF kompaktizuje len font programy používané výhradne fontmi CIDFontType2 s Identity /CIDToGIDMap, kde CID sa rovná GID, a preskočí programy s existujúcim map streamom, s /CIDSet alebo s farebnými glyfovými tabuľkami ako COLR, sbix, CBDT či SVG, lebo kompaktná prestavba by zahodila farebné vrstvy. Všimnite si tiež, že Info.BytesSaved sčítava len kroky zdrojov, fontov a streamov; zisk object streamov sa ukáže, keď sa súbor zapisuje
Akých výsledkov sa dať čakať v praxi?
Úspory sledujú, koľko súboru tvorí nekomprimovaná štruktúra a nafúkané fontové dáta, nie to, koľko má strán. Trojstranová vzorka Arial a SimSun sa scvrkla z 10,2 MB na 20 kB pri generovaní s CompressDocument a z 10,2 MB na 19,8 kB, keď sa nekomprimovaný originál načítal a prebehol cez CompressLoadedDocument, v oboch prípadoch s identickým vykreslením. PDF, ktoré je už kompaktné, sa len tak nepohne: v regresnej sade sa také súbory uložili v rozsahu -0,07 % až +0,06 % pôvodnej veľkosti. Súbory plné fotiek získajú málo, lebo žiadna z ciest nesiahne na obrazové dáta
Ak každú noc generujete tie isté CJK reporty, skombinujte kompaktné subsety s trvalou cache subsetov fontov na disku, aby sa subsettingová práca neopakovala pri každom behu, a porovnávajte komprimované výstupy podľa obsahu objektov namiesto bajtov, keďže jedna zmenená hodnota re-Flate celý object stream. Úplné referencie vlastností a recordov sú na produktovej stránke HotPDF Delphi PDF component