HotPDF THotPDF.CompressDocument je jedan prekidač koji od BeginDoc-a čini da komponenta napiše najmanji bezgubitnički PDF koji ume: FlateDecode na najvišem nivou, cross-reference stream sa object streamovima, font subsetting i kompaktne font podskupove koji zadržanim glifovima dodele nove, zbijene brojeve iza eksplicitnog /CIDToGIDMap-a. EndDoc potom vraća vaša sopstvena podešavanja. Test dokument od tri stranice sa Arial-om i SimSun-om pao je sa 10,2 MB na 20 KB uz identično renderovanje
Šta CompressDocument zapravo uključuje?
CompressDocument pregazi šest pisanih podešavanja, plus limit object streamova, za jedan dokument i potom ih sve vraća. Na BeginDoc-u, pre nego što se PDF verzija učvrsti, HotPDF zapamti vaše vrednosti i postavi Compression na cmFlateDecode, CompressionLevel na clMaximum, uključi EnableFontSubsetting i CompactFontSubsetting, i aktivira UseXRefStream plus UseObjectStreams (ISO 32000-1 §7.5.7 i §7.5.8). Object streamovi traže PDF 1.5, pa starija Version diže na 1.5 kad nije zaključana. PDF/A-1 zabranjuje obe strukture, pa PDF/A-1 dokument zadržava klasičnu cross-reference tabelu i dobija samo Flate i font posao. Slike ostaju tačno onakve kakve ste ih ugradili
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.AutoLaunch := False;
Pdf.FileName := 'invoice-2026-1042.pdf';
Pdf.CompressDocument := True; // primenjuje BeginDoc, poništava EndDoc
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('Arial', [], 12);
Pdf.CurrentPage.TextOut(40, 40, 0, 'Invoice 2026-1042');
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
Vraćanje se dešava u najspoljašnjem finally-ju EndDoc-a, pa izuzetak usred izveštaja ne ostavlja dugovečnu komponentu zaglavljenu na maksimalnoj kompresiji za sledeći posao. Svoje svojstvo CompressDocument ostaje True; vraća se samo šest podešavanja koja je pozajmila. Verzija se rešava pažljivije. HotPDF poništava sopstveno dizanje na 1.5 samo ako dokument i dalje završava na 1.5, pa kada je neka druga funkcija tokom rada gurnula fajl na 1.6 (recimo ugrađeni OpenType font), viša verzija ostaje, tačno kao što bi ostala i bez kompresije
Zašto su font podskupovi i dalje veliki bez kompakcije?
Klasični TrueType podskup baca konture koje nikada ne crtate, ali zadržava svaki glyph ID na njegovom mestu, i upravo to numerisanje ga drži teškim. Content stream pokazuje CID-ove jednake originalnim GID-ovima, pa podskup mora da drži loca offset i hmtx unos za svaki slot do najvišeg glifa koji zadrži, prazan ili ne. Za latinično pismo taj teret je šum. Za CJK pismo poput SimSun-a, čiji ideogrami sede duboko u vrlo velikoj tabeli glifova, dva kineska znaka vuku za sobom tabele dimenzionisane za ceo font. Pravila zatvaranja font podskupa za oblikovane glifove odlučuje koji glifovi prežive; kompakcija je pitanje koliko preživele koštaju
CompactFontSubsetting zadržanim glifovima dodeljuje zbijen opseg počevši od nule i upisuje /CIDToGIDMap stream na CIDFont, koji ISO 32000-1 §9.7.4.2 definiše kao tabelu dvobajtnih GID-ova indeksiranih CID-om. Ta tabela je ceo trik. Content streamovi, niz širina /W i ToUnicode CMap svi zadržavaju originalne CID-ove, pa ništa već upisano ne mora da se menja; samo pretraga od CID-a do glifa seli se u mapu. U testu koji je motivisao funkciju, SimSun sa dva znaka pao je sa 24,8 KB font podataka na 3,1 KB
Kompakcija ima čvrste granice, i tiho se povlači umesto da padne. HotPDF gradi kompaktne podskupove samo za Type 0 TrueType pisma, i ona postavljena kroz SetFont sa uključenim subsettingom i pismo registrovano kroz RegisterUnicodeTTF. Jednostavni TrueType font nalazi glifove kroz cmap unutar font programa, što bi ponovno numerisanje slomilo, pa zadržava raštrkani podskup. Ni OpenType-CFF pisma nemaju kompakt putanju. Kompaktni build koji omane vraća se na raštrkani podskup umesto da podigne grešku. Svojstvo je podrazumevano isključeno, pa postojeći izlaz ostaje bajt-identičan, dok pod PDF/A-om registrovano Unicode pismo uvek dobija kompaktan podskup
Pdf.EnableFontSubsetting := True;
Pdf.CompactFontSubsetting := True; // upotrebljivo i bez CompressDocument
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('SimSun', [], 12);
Pdf.CurrentPage.TextOut(40, 40, 0, WideString('Total: '#$4E2D#$6587));
Pdf.EndDoc;
Kako sabijeni pisac cedi strukturu fajla?
Kad fontovi i streamovi postanu mali, rečnici i cross-reference podaci postaju najveći preostali trošak, pa ih pisac object streamova iza CompressDocument-a takođe dotera. Vodič o object streamovima i inkrementalnim ispravkama pokriva sam format kontejnera; kompresiona putanja dodaje četiri usavršavanja preko toga:
- Kompaktna sintaksa po ISO 32000-1 §7.2.2: razmak se upisuje samo između dva tokena koja bi inače sastavljeni prošli kao obični znakovi, pa
/Type /Pagepostaje/Type/Page - Polja cross-reference streama uzimaju bilo koju širinu koju §7.5.8.2 dopušta, pa fajl ispod 16 MB čuva svaki offset u 3 bajta umesto 4
- Do 250 objekata ide u svaki object stream umesto uobičajenih 100, osim ako svoj limit postavite kroz
ConfigureAdaptiveObjectStreamPacking - Kad fajl nije šifrovan, i Catalog i Info rečnik pakuju se u object streamove; šifrovani ih izlaz drži na najvišem nivou
Kompaktna sintaksa je došla sa zamkom vrednom poznavanja ako proširujete pisca. Potpisivanje popunjava potpis posle upisa fajla tražeći po bajtovima literalne placeholdere /ByteRange ( i /Contents <, a kompaktan pravopis pretvorio bi ih u /ByteRange( i /Contents<, što pretraga nikada ne bi našla. Rečnici potpisa (Type Sig ili DocTimeStamp, FT Sig) i rečnik šifrovanja zato zadržavaju razmaknuti raspored. Srodni defekat pogođao je buildove pre v2.766.41: svako čuvanje kroz object streamove, CompressDocument uključeno, počinjalo je sa dve %PDF- header linije, pa nadogradite ako strogan validator označi vaš izlaz
Može li se komprimovati PDF koji je već učitan?
Može, kroz overload sa opcijama CompressLoadedDocument(Options, Info), koji na postojećem fajlu provodi iste bezgubitničke korake. Sa THPDFLoadedDocumentCompressionOptions.Default uklanja nekorišćene resurse stranica, spaja identične fontove i forme, radi font subsetting ugrađenih fontova sa uključenim kompaktnim podskupovima, ponovo komprimuje nefiltrirane, Flate, LZW, ASCII i RunLength streamove Flate-om kad je rezultat manji, i nameće sledećem čuvanju object streamove. HighRatioFlate je podrazumevano isključen, a object streamovi se preskaču za PDF/A-1 i inkrementalna čuvanja. Overload CompressLoadedDocument bez parametara je stariji, uži poziv koji samo Flate-komprimuje nekompromisovane streamove
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 učitanoj putanji bitne su dve granice. Svaki korak prepravlja bajtove koje potpis pokriva, pa se dokument sa poljima potpisa odbija kao celina: poziv vrati 0, postavi RefusedBySignaturePolicy i ništa ne menja, osim ako postavite AllowSignatureInvalidation, posle čega vam Info.SignaturesInvalidated kaže šta ste odustali. Kompakcija je ovde i konzervativnija nego na putanji kreiranja. HotPDF kompaktno samo font programe koje koriste isključivo CIDFontType2 fontovi sa Identity /CIDToGIDMap-om, gde je CID jednak GID-u, i preskače programe sa postojećim map streamom, /CIDSet-om, ili tabelama obojenih glifova poput COLR, sbix, CBDT ili SVG, jer bi kompaktan rebuild bacio slojeve boje. Primetite i da Info.BytesSaved sabira samo korake resursa, fontova i streamova; dobitak object streamova pokazuje se pri upisu fajla
Kakve rezultate možete očekivati u praksi?
Dobitak prati koliko fajla čini nekompresovana struktura i preveliki font podaci, a ne koliko stranica ima. Uzorak od tri stranice sa Arial-om i SimSun-om smanjen je sa 10,2 MB na 20 KB kad je generisan sa CompressDocument-om, i sa 10,2 MB na 19,8 KB kad je nekompresovani original učitan i proveden kroz CompressLoadedDocument, uz identično renderovanje u oba slučaja. PDF koji je već kompaktan jedva mrda: u regresionom skupu takvi fajlovi sačuvani su u rasponu od -0,07% do +0,06% prvobitne veličine. Fajlovi puni fotografija malo dobijaju, jer nijedna putanja ne dira slikove podatke
Ako svake noći generišete iste CJK izveštaje, uparite kompaktne podskupove sa trajnim font podskup kešom na disku da se posao subsettinga ne ponavlja pri svakom pokretanju, a komprimovane izlaze poredite po sadržaju objekata umesto po bajtovima, jer jedna promenjena vrednost ponovo Flate-komprimuje ceo object stream. Potpune reference svojstava i zapisa nalaze se na stranici proizvoda HotPDF Delphi PDF komponente