THotPDF.CompressDocument de HotPDF est un interrupteur unique qui fait produire à BeginDoc le plus petit PDF sans perte que le composant sache écrire : FlateDecode au niveau maximum, un flux de références croisées avec object streams, du font subsetting et des sous-ensembles de polices compacts qui renumérotent les glyphes gardés derrière un /CIDToGIDMap explicite. EndDoc remet ensuite vos propres réglages en place. Un document de test de trois pages en Arial et SimSun est tombé de 10.2 MB à 20 KB avec un rendu identique
Que déclenche réellement CompressDocument ?
CompressDocument passe outre six réglages de l'écrivain, plus le plafond des object streams, pour un seul document et les restaure tous ensuite. À BeginDoc, avant que la version PDF ne se fige, HotPDF enregistre vos valeurs et règle Compression sur cmFlateDecode, CompressionLevel sur clMaximum, active EnableFontSubsetting et CompactFontSubsetting, et met en route UseXRefStream plus UseObjectStreams (ISO 32000-1 §7.5.7 et §7.5.8). Les object streams exigent PDF 1.5, donc une Version plus ancienne est montée à 1.5 quand elle n'est pas verrouillée. PDF/A-1 interdit les deux structures, donc un document PDF/A-1 garde sa table de références croisées classique et ne reçoit que le travail Flate et polices. Les images sont laissées exactement comme vous les avez embarquées
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.AutoLaunch := False;
Pdf.FileName := 'invoice-2026-1042.pdf';
Pdf.CompressDocument := True; // appliqué par BeginDoc, annulé par EndDoc
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('Arial', [], 12);
Pdf.CurrentPage.TextOut(40, 40, 0, 'Invoice 2026-1042');
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
La restauration se produit dans le finally le plus externe de EndDoc, donc une exception à mi-parcours d'un rapport ne laisse pas un composant de longue vie coincé en compression maximum pour le job suivant. La propriété CompressDocument elle-même reste True ; seuls les six réglages qu'elle a empruntés rentrent chez eux. La version est traitée avec plus de soin. HotPDF annule sa propre montée à 1.5 seulement si le document finit encore à 1.5, donc quand une autre fonctionnalité a poussé le fichier à 1.6 pendant l'exécution (une police OpenType embarquée, disons), la version supérieure reste, exactement comme elle l'aurait fait sans compression
Pourquoi les sous-ensembles de polices restent-ils lourds sans compaction ?
Un sous-ensemble TrueType classique jette les contours que vous ne dessinez jamais mais garde chaque glyph ID là où il était, et cette numérotation est ce qui le rend lourd. Le flux de contenu montre des CIDs égaux aux GIDs d'origine, donc le sous-ensemble doit garder un décalage loca et une entrée hmtx pour chaque emplacement jusqu'au plus haut glyphe qu'il retient, vide ou pas. Pour une police latine, cette surcharge est un bruit de fond. Pour une police CJK comme SimSun, dont les idéogrammes logent au fond d'une très grande table de glyphes, deux caractères chinois traînent des tables dimensionnées pour toute la police. Les règles de fermeture de sous-ensemble de polices pour les glyphes façonnés décident quels glyphes survivent ; la compaction, elle, porte sur ce que coûtent les survivants
CompactFontSubsetting renumérote les glyphes gardés dans une plage dense commençant à zéro et écrit un flux /CIDToGIDMap sur le CIDFont, qu'ISO 32000-1 §9.7.4.2 définit comme une table de GIDs de deux octets indexée par CID. Cette table est toute l'astuce. Les flux de contenu, le tableau de largeurs /W et la CMap ToUnicode gardent tous les CIDs d'origine, donc rien de ce qui est déjà écrit n'a à changer ; seule la recherche du CID vers le glyphe déménage dans la map. Dans le test qui a motivé la fonctionnalité, SimSun avec deux caractères est passé de 24.8 KB de données de police à 3.1 KB
La compaction a des limites fermes, et elle se dégrade en silence plutôt que d'échouer. HotPDF bâtit des sous-ensembles compacts uniquement pour les polices Type 0 TrueType, à la fois celles réglées via SetFont avec subsetting actif et la police enregistrée via RegisterUnicodeTTF. Une police TrueType simple trouve ses glyphes par la cmap à l'intérieur du programme de police, ce que la renumérotation casserait, donc elle garde le sous-ensemble épars. Les polices OpenType-CFF n'ont pas de voie compacte non plus. Une construction compacte qui échoue retombe sur le sous-ensemble épars au lieu de lever. La propriété est désactivée par défaut, donc la sortie existante reste identique à l'octet près, tandis que sous PDF/A la police Unicode enregistrée reçoit toujours un sous-ensemble compact
Pdf.EnableFontSubsetting := True;
Pdf.CompactFontSubsetting := True; // utilisable sans CompressDocument
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('SimSun', [], 12);
Pdf.CurrentPage.TextOut(40, 40, 0, WideString('Total: '#$4E2D#$6587));
Pdf.EndDoc;
Comment l'écrivain tassé comprime-t-il la structure du fichier ?
Une fois polices et flux petits, les dictionnaires et les données de références croisées deviennent le plus gros coût restant, donc l'écrivain d'object streams derrière CompressDocument rogne ça aussi. Le guide des object streams et mises à jour incrémentales couvre le format du conteneur lui-même ; le chemin de compression ajoute quatre raffinements par-dessus :
- Syntaxe compacte selon ISO 32000-1 §7.2.2 : une espace n'est écrite qu'entre deux jetons qui sinon colleraient comme caractères ordinaires, donc
/Type /Pagedevient/Type/Page - Les champs du flux de références croisées prennent toute largeur que §7.5.8.2 permet, donc un fichier sous 16 MB range chaque décalage sur 3 octets au lieu de 4
- Jusqu'à 250 objets partent dans chaque object stream au lieu des 100 habituels, sauf si vous réglez votre propre plafond via
ConfigureAdaptiveObjectStreamPacking - Quand le fichier n'est pas chiffré, le Catalog et le dictionnaire Info sont eux aussi tassés dans des object streams ; la sortie chiffrée les garde au niveau supérieur
La syntaxe compacte est venue avec un piège à connaître si vous étendez l'écrivain. La signature remplit la signature après l'écriture du fichier en cherchant dans les octets les placeholders littéraux /ByteRange ( et /Contents <, et l'orthographe compacte transformerait ça en /ByteRange( et /Contents<, que la recherche ne trouve jamais. Les dictionnaires de signature (Type Sig ou DocTimeStamp, FT Sig) et le dictionnaire de chiffrement gardent donc la mise en page espacée. Un défaut apparenté a touché les builds antérieurs à la v2.766.41 : chaque sauvegarde avec object streams, CompressDocument comprise, commençait par deux lignes d'en-tête %PDF-, donc mettez à jour si un validateur strict signale votre sortie
Peut-on compresser un PDF déjà chargé ?
Oui, via la surcharge à options CompressLoadedDocument(Options, Info), qui fait tourner les mêmes étapes sans perte sur un fichier existant. Avec THPDFLoadedDocumentCompressionOptions.Default, elle retire les ressources de pages inutilisées, fusionne polices et formulaires identiques, fait des sous-ensembles des polices embarquées avec sous-ensembles compacts actifs, recomprime les flux non filtrés, Flate, LZW, ASCII et RunLength en Flate quand le résultat est plus petit, et fait que la prochaine sauvegarde emploie des object streams. HighRatioFlate est désactivé par défaut, et les object streams sont sautés pour PDF/A-1 et les sauvegardes incrémentales. La surcharge CompressLoadedDocument sans paramètre est l'appel plus ancien et plus étroit qui se contente de compresser en Flate les flux non compressés
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;
Deux frontières comptent sur la voie chargée. Chaque étape réécrit des octets qu'une signature couvre, donc un document porteur de champs de signature est refusé en bloc : l'appel renvoie 0, pose RefusedBySignaturePolicy et ne change rien, sauf si vous posez AllowSignatureInvalidation, après quoi Info.SignaturesInvalidated vous dit ce que vous avez abandonné. La compaction est aussi plus conservatrice ici que sur la voie de création. HotPDF ne compacte que les programmes de police employés uniquement par des polices CIDFontType2 avec une /CIDToGIDMap Identity, là où CID égale GID, et saute les programmes porteurs d'un flux de map existant, d'un /CIDSet ou de tables de glyphes couleur comme COLR, sbix, CBDT ou SVG, car la reconstruction compacte perdrait les couches de couleur. Notez aussi que Info.BytesSaved ne fait la somme que des étapes ressources, polices et flux ; le gain des object streams se voit quand le fichier est écrit
Quels résultats attendre en pratique ?
Les gains suivent la part du fichier qui est structure non compressée et données de police surdimensionnées, pas son nombre de pages. L'échantillon trois pages Arial et SimSun est passé de 10.2 MB à 20 KB quand il a été généré avec CompressDocument, et de 10.2 MB à 19.8 KB quand l'original non compressé a été chargé puis passé par CompressLoadedDocument, avec un rendu identique dans les deux cas. Un PDF déjà compact bouge à peine : dans le jeu de régression, de tels fichiers ont économisé entre -0.07% et +0.06% de leur taille d'origine. Les fichiers chargés en photos gagnent peu, car aucune des deux voies ne touche aux données d'image
Si vous générez les mêmes rapports CJK chaque nuit, mariez les sous-ensembles compacts au cache persistant de sous-ensembles de polices sur disque pour ne pas répéter le travail de subsetting à chaque exécution, et faites vos diffs de sorties compressées par contenu d'objet plutôt que par octets, car un seul champ changé re-Flate un object stream entier. Les références complètes des propriétés et des records sont sur la page produit du HotPDF Delphi PDF component