HotPDF THotPDF.CompressDocument e un singur switch care face ca BeginDoc să producă cel mai mic PDF lossless pe care componenta îl poate scrie: FlateDecode la nivelul maxim, un cross-reference stream cu object stream-uri, font subsetting și subseturi de fonturi compacte care renumără glyph-urile păstrate în spatele unui /CIDToGIDMap explicit. EndDoc vă pune apoi înapoi setările proprii. Un document de test de trei pagini cu Arial și SimSun a scăzut de la 10.2 MB la 20 KB cu randare identică
Ce activează de fapt CompressDocument?
CompressDocument suprascrie șase setări de scriere, plus plafonul de object stream-uri, pentru un singur document și le restituie pe toate după. La BeginDoc, înainte ca versiunea PDF să se așeze, HotPDF își notează valorile voastre și setează Compression pe cmFlateDecode, CompressionLevel pe clMaximum, pornește EnableFontSubsetting și CompactFontSubsetting, și activează UseXRefStream plus UseObjectStreams (ISO 32000-1 §7.5.7 și §7.5.8). Object stream-urile cer PDF 1.5, deci un Version mai vechi e ridicat la 1.5 când nu e încuiat. PDF/A-1 interzice ambele structuri, deci un document PDF/A-1 își păstrează tabelul clasic de cross-reference și primește doar partea de Flate și de fonturi. Imaginile rămân întocmai cum le-ați încorporat
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.AutoLaunch := False;
Pdf.FileName := 'invoice-2026-1042.pdf';
Pdf.CompressDocument := True; // aplicat de BeginDoc, anulat de EndDoc
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('Arial', [], 12);
Pdf.CurrentPage.TextOut(40, 40, 0, 'Invoice 2026-1042');
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
Restituirea are loc în finally-ul cel mai exterior al lui EndDoc, deci o excepție la jumătatea unui raport nu lasă o componentă de lungă durată blocată pe compresie maximă pentru jobul următor. Proprietatea CompressDocument rămâne ea însăși True; doar cele șase setări împrumutate se întorc. Versiunea e tratată cu mai multă grijă. HotPDF anulează propria lui ridicare la 1.5 doar dacă documentul se termină în continuare la 1.5, deci când o altă funcționalitate a împins fișierul la 1.6 în timpul rulării (un font OpenType încorporat, să zicem), versiunea mai mare rămâne, exact cum ar fi rămas fără compresie
De ce subseturile de fonturi rămân mari fără compactare?
Un subset TrueType clasic aruncă contururile pe care nu le desenați vreodată, dar păstrează fiecare glyph ID acolo unde era, iar numerotarea aceea e cea care-l ține greu. Content stream-ul arată CID-uri egale cu GID-urile originale, deci subsetul trebuie să țină un offset loca și o intrare hmtx pentru fiecare slot până la cel mai mare glyph pe care îl reține, gol sau nu. Pentru un font latin cheltuiala asta e zgomot. Pentru un font CJK precum SimSun, ale cărui ideograme stau adânc într-un tabel de glyph-uri foarte mare, două caractere chinezești trag după ele tabele dimensionate pentru tot fontul. Regulile de închidere a subseturilor de fonturi pentru glyph-uri modelate decid ce glyph-uri supraviețuiesc; compactarea ține de cât costă supraviețuitorii
CompactFontSubsetting renumără glyph-urile păstrate într-un interval dens pornind de la zero și scrie un stream /CIDToGIDMap pe CIDFont, pe care ISO 32000-1 §9.7.4.2 îl definește ca un tabel de GID-uri pe doi octeți indexate după CID. Tabelul acela e tot trucul. Content stream-urile, tabloul de lățimi /W și CMap-ul ToUnicode păstrează cu toții CID-urile originale, deci nimic din ce e deja scris nu trebuie schimbat; doar căutarea de la CID la glyph se mută în hartă. În testul care a motivat funcționalitatea, SimSun cu două caractere a coborât de la 24.8 KB de date de font la 3.1 KB
Compactarea are limite ferme și se degradează în tăcere în loc să eșueze. HotPDF construiește subseturi compacte doar pentru fonturi Type 0 TrueType, atât cele setate prin SetFont cu subsetting pornit, cât și fontul înregistrat prin RegisterUnicodeTTF. Un font TrueType simplu își găsește glyph-urile prin cmap-ul din interiorul programului de font, pe care renumărarea l-ar rupe, deci păstrează subsetul rar. Fonturile OpenType-CFF n-au nici ele cale compactă. O construcție compactă care eșuează cade pe subsetul rar în loc să ridice. Proprietatea e oprită din fabrică, deci output-ul existent rămâne identic octet cu octet, în timp ce sub PDF/A fontul Unicode înregistrat primește oricum un subset compact
Pdf.EnableFontSubsetting := True;
Pdf.CompactFontSubsetting := True; // utilizabil și fără CompressDocument
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('SimSun', [], 12);
Pdf.CurrentPage.TextOut(40, 40, 0, WideString('Total: '#$4E2D#$6587));
Pdf.EndDoc;
Cum strânge scriitorul împachetat structura fișierului?
Odată ce fonturile și stream-urile sunt mici, dicționarele și datele de cross-reference devin cea mai mare cheltuială rămasă, deci scriitorul de object stream-uri din spatele lui CompressDocument le rade și pe acestea. Ghidul object stream-uri și actualizări incrementale acoperă formatul de container în sine; calea de compresie adaugă peste el patru rafinări:
- Sintaxă compactă conform ISO 32000-1 §7.2.2: un spațiu e scris doar între două token-uri care altfel s-ar lipi ca caractere regulate, deci
/Type /Pagedevine/Type/Page - Câmpurile stream-ului de cross-reference iau orice lățime pe care §7.5.8.2 o permite, deci un fișier sub 16 MB stochează fiecare offset pe 3 octeți în loc de 4
- Până la 250 de obiecte intră în fiecare object stream în loc de cele obișnuite 100, cu excepția cazului în care vă setați propriul plafon prin
ConfigureAdaptiveObjectStreamPacking - Când fișierul nu e criptat, Catalogul și dicționarul Info sunt împachetate și ele în object stream-uri; output-ul criptat le ține la nivelul de sus
Sintaxa compactă a venit cu o capcană care merită știută dacă extindeți scriitorul. Semnarea completează semnătura după ce fișierul e scris, căutând în octeți placeholder-ele literale /ByteRange ( și /Contents <, iar ortografia compactă le-ar transforma în /ByteRange( și /Contents<, pe care căutarea nu le găsește niciodată. Dicționarele de semnătură (Type Sig sau DocTimeStamp, FT Sig) și dicționarul de criptare păstrează deci aranjamentul cu spații. Un defect înrudit a afectat build-urile de dinainte de v2.766.41: fiecare salvare cu object stream-uri, CompressDocument inclusă, începea cu două linii de header %PDF-, deci faceți upgrade dacă un validator strict vă semnalează output-ul
Puteți comprima un PDF deja încărcat?
Da, prin overload-ul cu opțiuni CompressLoadedDocument(Options, Info), care rulează aceleași pași lossless pe un fișier existent. Cu THPDFLoadedDocumentCompressionOptions.Default, el elimină resursele de pagină nefolosite, îmbină fonturile și formularele identice, subsetează fonturile încorporate cu subseturi compacte pornite, recompresă stream-urile nefiltrate, Flate, LZW, ASCII și RunLength cu Flate când rezultatul e mai mic, și face ca următoarea salvare să folosească object stream-uri. HighRatioFlate e oprit din fabrică, iar object stream-urile sunt sărite pentru PDF/A-1 și salvările incrementale. Overload-ul fără parametri CompressLoadedDocument e apelul mai vechi și mai îngust care doar comprima Flate stream-urile necompresate
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;
Două limite contează pe calea documentului încărcat. Fiecare pas rescrie octeți pe care o semnătură îi acoperă, deci un document cu câmpuri de semnătură e refuzat ca întreg: apelul întoarce 0, setează RefusedBySignaturePolicy și nu schimbă nimic, cu excepția cazului în care setați AllowSignatureInvalidation, după care Info.SignaturesInvalidated vă spune la ce ați renunțat. Compactarea e de asemenea mai conservatoare aici decât pe calea de creare. HotPDF compactează doar programele de font folosite exclusiv de fonturi CIDFontType2 cu /CIDToGIDMap Identity, unde CID egalează GID, și sare peste programele cu un stream de hartă existent, un /CIDSet sau tabele de glyph-uri colorate precum COLR, sbix, CBDT sau SVG, pentru că reconstrucția compactă ar arunca straturile de culoare. Rețineți de asemenea că Info.BytesSaved însumează doar pașii de resurse, fonturi și stream-uri; câștigul de object stream-uri apare când fișierul e scris
Ce rezultate trebuie să vă așteptați în practică?
Câștigurile urmăresc cât dintr-un fișier e structură necompresată și date de font supradimensionate, nu câte pagini are. Eșantionul de trei pagini cu Arial și SimSun s-a micșorat de la 10.2 MB la 20 KB când a fost generat cu CompressDocument, și de la 10.2 MB la 19.8 KB când originalul necompresat a fost încărcat și trecut prin CompressLoadedDocument, cu randare identică pe ambele căi. Un PDF deja compact abia se mișcă: în setul de regresie, asemenea fișiere s-au salvat între -0.07% și +0.06% din dimensiunea lor originală. Fișierele pline de poze câștigă puțin, pentru că niciuna dintre căi nu atinge datele de imagine
Dacă generați în fiecare noapte aceleași rapoarte CJK, combinați subseturile compacte cu cache-ul persistent de subseturi de fonturi pe disc, ca munca de subsetting să nu se repete per rulare, și faceți diff la output-urile compresate după conținutul de obiect, nu după octeți, fiindcă un singur câmp schimbat re-Flatează un object stream întreg. Referințele complete de proprietăți și înregistrări sunt pe pagina de produs a componentei HotPDF Delphi PDF