HotPDF THotPDF.CompressDocument je jediný přepínač, který donutí BeginDoc vyrobit nejmenší bezztrátové PDF, jaké komponenta umí napsat: FlateDecode na maximální úrovni, cross-reference stream s object streams, subsetting fontů a kompaktní podmnožiny fontů, které přečíslují ponechané glyfy za explicitním /CIDToGIDMap. EndDoc pak vrátí vaše vlastní nastavení. Testovací dokument o třech stránkách s Arial a SimSun se smrskl z 10.2 MB na 20 KB při identickém vykreslení
Co CompressDocument doopravdy zapíná?
CompressDocument přebije šest nastavení writeru, plus strop object streamů, pro jeden dokument a potom je všem vrátí. V BeginDoc, dřív než se usadí PDF verze, si HotPDF poznamená 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 streams potřebují PDF 1.5, takže starší Version se zvedne na 1.5, pokud není uzamčená. PDF/A-1 obě struktury zakazuje, takže PDF/A-1 dokument si nechává klasickou cross-reference tabulku a dostane jen práci s Flate a fonty. Obrázky zůstanou přesně takové, jaké jste je 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;
Restaurace probíhá v nejvnejším finally v EndDoc, takže výjimka v půlce reportu nenechá dlouho žijící komponentu zaseknutou na maximální komprimaci pro příští job. Vlastnost CompressDocument sama zůstává True; vrátí se jen šest nastavení, která si půjčila. S verzí se zachází pozorněji. HotPDF ruší vlastní zvednutí na 1.5 jen tehdy, pokud dokument stále končí na 1.5, takže když jiná funkce posunula soubor na 1.6 během běhu (řekněme vložený OpenType font), vyšší verze zůstane, úplně jako by bez komprimace
Proč jsou podmnožiny fontů bez kompakce pořád velké?
Klasická TrueType podmnožina zahodí obrysy, které nikdy nekreslíte, ale každé glyph ID nechá tam, kde bylo, a právě tohle číslování ji drží těžkou. Content stream ukazuje CIDy rovné původním GIDům, takže podmnožina musí držet offset loca a položku hmtx pro každý slot až po nejvyšší ponechaný glyf, prázdný nebo ne. U latinského řezu je ta režie šum. U CJK řezu jako SimSun, jehož ideografy sedí hluboko ve velmi velké tabulce glyfů, dva čínské znaky vláčejí tabulky dimenzované na celý font. Pravidla uzávěru podmnožiny fontů pro tvarované glyfy rozhodují, kteří glyfové přežijí; kompakce je o tom, kolik stojí přeživší
CompactFontSubsetting přečísluje ponechané glyfy do hustého rozsahu začínajícího nulou a na CIDFont zapíše stream /CIDToGIDMap, který ISO 32000-1 §9.7.4.2 definuje jako tabulku dvoubajtových GIDů indexovanou podle CID. Tahle tabulka je celý trik. Content streamy, pole šířek /W i ToUnicode CMap si nechávají původní CIDy, takže nic už napsaného se nemusí měnit; do mapy se přesune jen vyhledávání z CIDu na glyf. V testu, který funkci motivoval, se SimSun se dvěma znaky smrskl z 24.8 KB fontových dat na 3.1 KB
Kompakce má tvrdé meze a spíš se potichu degraduje, než selže. HotPDF staví kompaktní podmnožiny jen pro řezy Type 0 TrueType, jak pro ty nastavené přes SetFont se zapnutým subsettingem, tak pro řez registrovaný přes RegisterUnicodeTTF. Jednoduchý TrueType font hledá glyfy přes cmap uvnitř font programu, což by přečíslování rozbilo, takže si nechává řídkou podmnožinu. Řezy OpenType-CFF kompaktní cestu nemají taky. Kompaktní sestava, která selže, sestoupí na řídkou podmnožinu místo vyhození výjimky. Vlastnost je defaultně vypnutá, takže stávající výstup zůstává bajtově identický, zatímco pod PDF/A dostane registrovaný Unicode řez vždy kompaktní podmnožinu
Pdf.EnableFontSubsetting := True;
Pdf.CompactFontSubsetting := True; // použitelné i bez CompressDocument
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('SimSun', [], 12);
Pdf.CurrentPage.TextOut(40, 40, 0, WideString('Total: '#$4E2D#$6587));
Pdf.EndDoc;
Jak packed writer vymačká strukturu souboru?
Jakmile jsou fonty a streamy malé, slovníky a cross-reference data se stanou největší zbývající položkou, takže object-stream writer za CompressDocument nařeže i ty. Průvodce object streams a inkrementálními aktualizacemi popisuje samotný formát kontejneru; kompresní cesta přidává čtyři vylepšení navrch:
- Kompaktní syntaxe podle ISO 32000-1 §7.2.2: mezera se zapíše jen mezi dva tokeny, které by jinak srostly jako běžné znaky, takže
/Type /Pagese stane/Type/Page - Pole cross-reference streamu nabírají libovolnou šířku, kterou §7.5.8.2 dovoluje, takže soubor pod 16 MB ukládá každý offset do 3 bajtů místo 4
- Až 250 objektů jde do každého object streamu místo obvyklých 100, pokud si vlastní strop nenastavíte přes
ConfigureAdaptiveObjectStreamPacking - Když soubor není zašifrovaný, Catalog i Info slovník se sbalí do object streamů taky; šifrovaný výstup je nechává na nejvyšší úrovni
Kompaktní syntaxe přišla s pastí, kterou stojí za znát, pokud writer rozšiřujete. Podepisování doplňuje podpis až po zapsání souboru tím, že v bajtech hledá literální placeholdery /ByteRange ( a /Contents <, a kompaktní hláskování by z nich udělalo /ByteRange( a /Contents<, které hledání nikdy nenajde. Slovníky podpisů (Type Sig nebo DocTimeStamp, FT Sig) a šifrovací slovník si proto nechávají rozestoupanou podobu. Související vada postihla buildy před v2.766.41: každé uložení s object streams, CompressDocument nevyjímaje, začínalo dvěma řádky hlavičky %PDF-, takže upgradujte, pokud přísný validator váš výstup označí
Dá se zkomprimovat PDF, které už je načtené?
Ano, přes overload s options CompressLoadedDocument(Options, Info), který pustí tytéž bezztrátové kroky nad existujícím souborem. S THPDFLoadedDocumentCompressionOptions.Default odstraní nepoužívané zdroje stránek, sloučí identické fonty a formuláře, podmnožinuje vložené fonty s kompaktními podmnožinami, znovu zkomprimuje nefiltrované, Flate, LZW, ASCII i RunLength streamy Flate, když je výsledek menší, a přepne příští uložení na object streams. HighRatioFlate je defaultně vypnuté a object streams se přeskočí u PDF/A-1 a inkrementálních uložení. Overload CompressLoadedDocument bez parametrů je starší, užší volání, které jen zkomprimuje nekomprimované streamy přes Flate
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čtené cestě záleží na dvou mezích. Každý krok přepisuje bajty, které kryje podpis, takže dokument s podpisovými poli se odmítne v celku: volání vrátí 0, nastaví RefusedBySignaturePolicy a nic nezmění, pokud nenastavíte AllowSignatureInvalidation, po čemž Info.SignaturesInvalidated řekne, čeho jste se vzdali. Kompakce je tady taky konzervativnější než na cestě tvorby. HotPDF kompaktuje jen font programy používané výhradně fonty CIDFontType2 s Identity /CIDToGIDMap, kde se CID rovná GID, a přeskočí programy s existujícím map streamem, s /CIDSet nebo s tabulkami barevných glyfů jako COLR, sbix, CBDT či SVG, protože kompaktní přestavba by barevné vrstvy zahodila. Všimněte si také, že Info.BytesSaved sčítá jen kroky se zdroji, fonty a streamy; zisk z object streamů se ukáže, až se soubor zapíše
Jakých výsledků se v praxi dočkáte?
Zisky sledují, jak velká část souboru je nekomprimovaná struktura a nafouknutá fontová data, ne kolik má stránek. Vzorek o třech stránkách s Arial a SimSun se smrskl z 10.2 MB na 20 KB, když se generoval s CompressDocument, a z 10.2 MB na 19.8 KB, když se nekomprimovaný originál načetl a pustil přes CompressLoadedDocument, obojí při identickém vykreslení. PDF, které už je kompaktní, se sotva pohně: v regresní sadě si takové soubory polepšily v rozmezí -0.07 % až +0.06 % původní velikosti. Soubory plné fotek si přidají málo, protože ani jedna cesta nezasahuje do obrazových dat
Pokud generujete tytéž CJK reporty každou noc, spojte kompaktní podmnožiny s perzistentní cache podmnožin fontů na disku, aby se subsetting práce neopakoval při každém běhu, a diffujte komprimované výstupy podle obsahu objektů, ne bajtů, protože jedna změněná položka znovu zFlateuje celý object stream. Kompletní reference vlastností a recordů najdete na stránce produktu HotPDF Delphi PDF component