HotPDF THotPDF.CompressDocument jedan je prekidač koji natjera BeginDoc da proizvede najmanji besgubitni PDF koji komponenta zna zapisati: FlateDecode na maksimalnoj razini, cross-reference stream s object streamovima, font subsetting i kompaktni font subsetovi koji prenumeriraju zadržane glifove iza izričitog /CIDToGIDMapa. EndDoc zatim vraća vaše vlastite postavke. Testni dokument od tri stranice s Arialom i SimSunom pao je s 10,2 MB na 20 KB uz identično renderiranje
Što CompressDocument zapravo uključuje?
CompressDocument nadjačava šest postavki writera, plus strop object streamova, za jedan dokument i poslije sve vraća. Na BeginDoc, prije nego se verzija PDF-a smiri, HotPDF zapamti vaše vrijednosti i postavi Compression na cmFlateDecode, CompressionLevel na clMaximum, uključi EnableFontSubsetting i CompactFontSubsetting, te omogući UseXRefStream plus UseObjectStreams (ISO 32000-1 §7.5.7 i §7.5.8). Object streamovi traže PDF 1.5, pa se stariji Version podigne na 1.5 kad nije zaključan. PDF/A-1 zabranjuje obje strukture, pa PDF/A-1 dokument zadržava klasičnu cross-reference tablicu i dobiva samo Flate i fontovski posao. Slike ostaju toč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; // primjenjuje 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 događa u najvanjskom finally od EndDoc, pa iznimka usred izvješća ne ostavi dugovječnu komponentu zagavljenu na maksimalnoj kompresiji za sljedeći posao. Svojstvo CompressDocument samo ostaje True; vraćaju se samo šest postavki koje je posudilo. S verzijom se rukuje pažljivije. HotPDF poništi vlastito podizanje na 1.5 samo ako dokument i dalje završi na 1.5, pa kad ga je tijekom rada na 1.6 gurnula druga značajka (recimo ugrađeni OpenType font), viša verzija ostaje, točno kao da kompresije nije ni bilo
Zašto su font subsetovi bez kompakcije još veliki?
Klasični TrueType subset baca obrise koje nikad ne crtate, ali zadržava svaki glyph ID tamo gdje je bio, i baš to numeriranje čini ga teškim. Content stream prikazuje CID-ove jednake izvornim GID-ovima, pa subset mora držati loca offset i hmtx unos za svaki slot do najvišeg glifa koji zadrži, prazan ili ne. Za latinično lice taj je trošak šum. Za CJK lice poput SimSuna, čiji ideogrami sjede duboko u vrlo velikoj tablici glifova, dva kineska znaka vuku za sobom tablice dimenzionirane za cijeli font. Pravila zatvaranja font subseta za oblikovane glifove odlučuju koji glifovi prežive; kompakcija se tiče koliko preživjeli koštaju
CompactFontSubsetting prenumerira zadržane glifove u gust raspon koji kreće od nule i zapisuje /CIDToGIDMap stream na CIDFont, koji ISO 32000-1 §9.7.4.2 definira kao tablicu dvobajtnih GID-ova indeksiranih CID-om. Ta je tablica cijeli trik. Content streamovi, polje širina /W i ToUnicode CMap zadržavaju izvorne CID-ove, pa ništa što je već zapisano ne mora se mijenjati; u mapu se preseli samo pretraživanje od CID-a do glifa. U testu koji je motivirao značajku, SimSun s dva znaka pao je s 24,8 KB fontovskih podataka na 3,1 KB
Kompakcija ima čvrste granice, i tiho degradira umjesto da padne. HotPDF gradi kompaktne subsetove samo za Type 0 TrueType lica, i ona postavljena kroz SetFont s uključenim subsettingom i lice registrirano kroz RegisterUnicodeTTF. Jednostavni TrueType font glifove pronalazi kroz cmap unutar font programa, što bi prenumeriranje slomilo, pa zadržava rijetki subset. Ni OpenType-CFF lica nemaju kompaktan put. Kompaktna izgradnja koja padne vraća se na rijetki subset umjesto da baci. Svojstvo je po zadanom isključeno, pa postojeći izlaz ostaje bajtno identičan, dok pod PDF/A registrirano Unicode lice uvijek dobiva kompaktan subset
Pdf.EnableFontSubsetting := True;
Pdf.CompactFontSubsetting := True; // upotrebljivo bez CompressDocument
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('SimSun', [], 12);
Pdf.CurrentPage.TextOut(40, 40, 0, WideString('Total: '#$4E2D#$6587));
Pdf.EndDoc;
Kako spakovani writer stiska strukturu datoteke?
Kad su fontovi i streamovi mali, rječnici i cross-reference podaci postaju najveći preostali trošak, pa ih object-stream writer iza CompressDocument također obrađuje. Vodič kroz object streamove i inkrementalne updatee pokriva sam format spremnika; put kompresije dodaje četiri profinjenja na to:
- Kompaktna sintaksa po ISO 32000-1 §7.2.2: razmak se zapisuje samo između dva tokena koji bi inače sletjeli zajedno kao regularni znakovi, pa
/Type /Pagepostaje/Type/Page - Polja cross-reference streama uzimaju bilo koju širinu koju §7.5.8.2 dopušta, pa datoteka ispod 16 MB čuva svaki offset u 3 bajta umjesto 4
- Do 250 objekata ulazi u svaki object stream umjesto uobičajenih 100, osim ako svoj strop postavite kroz
ConfigureAdaptiveObjectStreamPacking - Kad datoteka nije šifrirana, i Catalog i Info rječnik pakuju se u object streamove; šifrirani ih izlaz drži na najvišoj razini
Kompaktna je sintaksa došla sa zamkom koju vrijedi znati ako proširujete writer. Potpisivanje dopunjuje potpis nakon što se datoteka zapiše, tražeći po bajtovima literalne placeholdere /ByteRange ( i /Contents <, a kompaktan bi ih pravopis pretvorio u /ByteRange( i /Contents<, što traženje nikad ne nađe. Rječnici potpisa (Type Sig ili DocTimeStamp, FT Sig) i encryption rječnik zato zadržavaju razmaknuti raspored. Povezani defekt zahvatio je buildove prije v2.766.41: svako spremanje s object streamovima, uključivo CompressDocument, počinjalo je s dvjema linijama %PDF- headera, pa nadogradite ako vas strogi validator označi izlaz
Možete li komprimirati PDF koji je već učitan?
Da, kroz options overload CompressLoadedDocument(Options, Info), koji na postojećoj datoteci vrti iste besgubitne korake. S THPDFLoadedDocumentCompressionOptions.Default uklanja neiskorištene resurse stranica, spaja identične fontove i forme, radi subsetove ugrađenih fontova s uključenim kompaktnim subsetovima, prekompresira nefiltrirane, Flate, LZW, ASCII i RunLength streamove s Flateom kad je rezultat manji, i natjera sljedeće spremanje da koristi object streamove. HighRatioFlate je po zadanom isključen, a object streamovi se preskaču za PDF/A-1 i inkrementalna spremanja. Overload bez parametara CompressLoadedDocument stariji je, uži poziv koji samo Flate-kompresira nekompresirane 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;
Dvije granice važe na učitanom putu. Svaki korak prepravlja bajtove koje potpis pokriva, pa se dokument s poljima potpisa odbija u cijelosti: poziv vrati 0, postavi RefusedBySignaturePolicy i ne promijeni ništa, osim ako postavite AllowSignatureInvalidation, nakon čega vam Info.SignaturesInvalidated kaže što ste time izgubili. Kompakcija je i konzervativnija ovdje nego na putu stvaranja. HotPDF komprimira samo font programe koje koriste isključivo CIDFontType2 fontovi s Identity /CIDToGIDMapom, gdje je CID jednak GID-u, a preskače programe s postojećim map streamom, s /CIDSetom ili tablicama kolornih glifova poput COLR, sbix, CBDT ili SVG, jer bi kompaktna ponovna izgradnja bacila slojeve boja. Zapamtite i da Info.BytesSaved zbraja samo korake resursa, fontova i streamova; dobitak object streamova pokazuje se kad se datoteka zapiše
Koje rezultate možete očekivati u praksi?
Dobici prate koliko datoteke čine nekompresirana struktura i pretjerano veliki fontovski podaci, ne koliko stranica ima. Uzorak od tri stranice s Arialom i SimSunom smanjio se s 10,2 MB na 20 KB kad je generiran s CompressDocumentom, i s 10,2 MB na 19,8 KB kad se nekompresirani original učitao i proveo kroz CompressLoadedDocument, uz identično renderiranje u oba slučaja. PDF koji je već kompaktan jedva se pomakne: u regresijskom skupu takve su se datoteke spremile unutar -0,07% do +0,06% izvorne veličine. Datoteke bogate fotografijama malo dobiju, jer nijedan put ne dira slikovne podatke
Ako svake noći generirate ista CJK izvješća, uparite kompaktne subsetove s trajnim cacheom font subseta na disku da se posao subsettinga ne ponavlja po runu, i diffajte komprimirane izlaze po sadržaju objekata umjesto po bajtovima, jer jedno promijenjeno polje ponovno Flate-a cijeli object stream. Potpune reference svojstava i zapisa su na stranici proizvoda HotPDF Delphi PDF komponente