Le PDF 1.5 a introduit deux structures de stockage que le format de fichier précédent n'avait aucun moyen d'exprimer : le flux d'objets (object stream) et le flux de références croisées (cross-reference stream). Un flux d'objets est un conteneur compressé par Flate, étiqueté /Type /ObjStm, qui contient de nombreux petits objets indirects emballés bout à bout au lieu de les disperser dans le corps du fichier. Un flux de références croisées est la table de recherche du fichier réécrite sous forme binaire compressée avec des champs de largeur variable, à la place de la table ASCII de largeur fixe qui clôturait chaque PDF jusqu'à la version 1.4. Ils voyagent ensemble. Une fois les objets pliés dans un flux, l'ancienne table de texte ne peut plus les adresser, le flux xref binaire doit donc l'accompagner
Opposez cela à la disposition classique et le coût qu'elle supprime est facile à voir. Dans un fichier PDF 1.4, chaque objet indirect se trouve non compressé derrière son propre en-tête obj, et la table à la fin (tail) dépense exactement 20 octets d'ASCII par entrée, compression interdite. Un document contenant 200 000 objets transporte environ 4 Mo de données de référence croisée avant qu'un seul glyphe ne soit dessiné, avec tous les corps de dictionnaire non compressés empilés par-dessus. Le PDF 1.5 attaque les deux nombres en même temps : les dictionnaires se plient dans des conteneurs Flate, et la table de 4 Mo se réduit à quelques centaines de kilo-octets de binaire. L'ISO 32000-1 définit les deux structures au §7.5.7 et au §7.5.8
Où l'économie atterrit réellement
Les flux d'objets ne touchent que les objets hors flux (non-stream objects), ils compressent donc la structure, pas les pixels. Le contenu de la page était déjà compressé par Flate avant la version 1.5, et les données d'image portent leurs propres codecs, c'est pourquoi une brochure riche en images (image-heavy) bouge à peine. Les fichiers qui s'effondrent (collapse) sont ceux dont la structure est lourde : les AcroForms avec des milliers de dictionnaires de champs, les arborescences de contours (outline trees) profondes, les éléments de structure PDF balisés (tagged-PDF). Ces objets sont minuscules, nombreux et presque identiques les uns aux autres, et cette répétition est exactement ce que Flate exploite une fois qu'ils se trouvent dans un seul tampon (buffer) plutôt que dispersés dans le corps avec des en-têtes coincés entre eux
Il est facile de sous-estimer la quantité de surcharge (overhead) que représente un ancien fichier. Une archive de formulaire qui a absorbé des années d'éditions peut dépenser bien plus de la moitié de ses octets sur les en-têtes de dictionnaire, le remplissage (padding) xref et les révisions qu'aucun lecteur ne regardera jamais. Les deux fonctionnalités présentées ici récupèrent les deux premiers d'entre eux. La troisième, les révisions accumulées, ne cède qu'au compactage (compaction), une fois que le fichier n'a plus à se souvenir de sa propre histoire
Dans HotPDF, vous les activez tous les deux via une paire de propriétés, et la façon dont ils dépendent l'un de l'autre compte plus que l'ordre dans lequel vous les écrivez :
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := 'catalog-2026.pdf';
Pdf.UseXRefStream := True; // binary xref, prerequisite for ObjStm
Pdf.UseObjectStreams := True; // pack objects into /Type /ObjStm
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('Arial', [], 11);
Pdf.CurrentPage.TextOut(50, 760, 0, 'Compressed structure demo');
Pdf.EndDoc; // emits XRefStm + ObjStm containers
finally
Pdf.Free;
end;
end;
UseObjectStreams nécessite que UseXRefStream soit défini sur True. Un objet compressé est atteint via une entrée xref de type 2, qui enregistre un numéro de flux d'objets plus un index, et une ligne de texte classique de 20 octets n'a pas la place de stocker cette paire. Donc, UseObjectStreams seul ne fait rien de visible ; les deux drapeaux (flags), définis avant BeginDoc, constituent la configuration qui fonctionne. Définissez-les après BeginDoc et HotPDF s'est déjà engagé dans l'ancienne disposition (layout)
Pourquoi les deux sont désactivés par défaut
HotPDF laisse les deux propriétés sur False par défaut, et la raison apparaît dans les intégrations avec de l'ancien code en aval (downstream code). Un lecteur qui ne comprend que le PDF 1.4 n'annonce pas qu'il ne peut pas gérer les objets compressés. Il rencontre un flux xref, ne trouve aucun des mots-clés de trailer qu'il attend et signale une table de références croisées endommagée ou refuse simplement d'ouvrir le fichier. Si votre sortie s'écoule (flows) dans une passerelle de fax vieillissante, une imprimante matérielle exécutant un interpréteur intégré, ou un analyseur (parser) que quelqu'un a écrit contre la spécification 1.4 il y a dix ans, gardez les deux drapeaux désactivés pour ce canal et acceptez le fichier plus volumineux. Pour le stockage d'archives et la livraison Web, où chaque visionneuse grand public lit le PDF 1.5 depuis vingt ans, les activer est une compression que vous obtenez pour presque rien
Il y a un effet de second ordre dont il vaut la peine de parler à votre équipe de support. Une fois que les dictionnaires sont emballés dans des flux d'objets, la comparaison (comparing) de deux fichiers générés octet par octet ne signifie plus rien, car la modification d'un seul champ peut re-Flate un conteneur entier et mélanger tout ce qui le suit. Comparez (Diff) de tels fichiers par le contenu de l'objet, et non avec une comparaison binaire
Mises à jour incrémentielles et décalages d'octets (byte offsets) qu'elles protègent
Une signature numérique couvre un /ByteRange explicite : deux portées (spans) du fichier physique, données sous forme de décalages d'octets absolus, sur lesquelles le résumé CMS (CMS digest) a été pris. Réécrivez le fichier, même en quelque chose qui semble identique à l'écran, et ces décalages se déplacent tous. Le résumé ne correspond plus et la signature est lue comme étant cassée. C'est le problème précis que l'ISO 32000-1 §7.5.6 résout avec les mises à jour incrémentielles. Les objets nouveaux et modifiés sont ajoutés après le %%EOF existant, puis une nouvelle section de référence croisée est écrite dont l'entrée /Prev pointe vers celle qui la précède. Les octets d'origine ne sont jamais perturbés, de sorte qu'une révision signée reste vérifiable et qu'Acrobat peut présenter chaque révision signée seule dans le panneau des signatures
HotPDF expose cela via son propre point d'entrée :
Pdf.BeginIncrementalUpdate('contract-signed.pdf');
Pdf.AddPage;
Pdf.CurrentPage.SetFont('Arial', [], 10);
Pdf.CurrentPage.TextOut(50, 760, 0, 'Addendum recorded 2026-06-11');
Pdf.SaveIncrementalUpdate('contract-updated.pdf'); // appends the delta only
Deux choses font trébucher (trip people up) les gens. BeginIncrementalUpdate doit recevoir le nom du fichier d'origine, car la section xref ajoutée enregistre des décalages qui n'ont de sens que par rapport à ces octets d'origine exacts ; pointez-le vers une copie renommée ou ré-enregistrée et les décalages décrivent un fichier qui n'existe plus. Et la sauvegarde est par construction un ajout uniquement (append-only), la sortie est donc toujours plus grande que l'entrée. Cette croissance n'est pas un gaspillage (waste) à éliminer. C'est la même propriété qui laisse intactes les révisions signées antérieures
La modification d'un fichier chargé passe par LoadFromFile
Les développeurs qui ont rencontré HotPDF pour la première fois via son API de génération ont tendance à se heurter à un mur particulier. BeginDoc ouvre un tout nouveau document, ce qui est le mauvais outil lorsque vous avez l'intention de modifier un document qui existe déjà. L'édition d'un fichier existant passe plutôt par les appels de document chargé (loaded-document) :
PageCount := Pdf.LoadFromFile('base.pdf');
Pdf.InsertPagesFromDocument(OtherDoc, '1-3', 5); // pages 1-3 after page 5
Pdf.MovePage(2, 5);
Pdf.SaveLoadedDocument('modified.pdf');
Mélangez les deux et le symptôme est un fichier de sortie qui contient votre nouveau contenu et rien de l'original, car BeginDoc a allègrement construit un nouveau document à côté de celui que vous croyiez éditer. Lisez LoadFromFile avec SaveLoadedDocument comme un vocabulaire et BeginDoc avec EndDoc comme un autre. Une routine qui recherche les deux pour le même fichier est presque toujours fausse
Quand compacter un fichier ajouté (appended file)
La sauvegarde par ajout uniquement (append-only) a un coût lent. Un travail nocturne qui appose une ligne d'état sur le même PDF produit 365 révisions sur une année, et chaque révision remorque (tows) une nouvelle section xref derrière elle. Lorsque cet historique a survécu à son utilité et qu'aucune signature dans le fichier ne doit survivre, vous pouvez aplatir l'ensemble (flatten the whole thing) en le resérialisant (re-serializing) via le chemin du document chargé :
Pdf.LoadFromFile('stamped.pdf');
Pdf.SaveLoadedDocument('compacted.pdf');
Ce ré-enregistrement est une réécriture complète. Il jette les révisions antérieures à dessein (on purpose) et brise toute signature encore dans le fichier, alors placez-le derrière la même porte de politique (policy gate) que vous appliquez à toute autre étape destructrice. Une règle de production qui tient la route (holds up) : compactez lorsque le nombre de révisions dépasse un seuil, ou lorsque la surcharge (overhead) ajoutée dépasse une certaine part du fichier de base, et ne compactez jamais un document dont le panneau de signature contient quoi que ce soit
Vérification de la sortie avant son expédition (ships)
La vérification de cette paire de fonctionnalités est d'une concrétisation rafraîchissante. Ouvrez le résultat dans Adobe Acrobat et confirmez trois points : les propriétés du document signalent un PDF 1.5 ou supérieur une fois que les flux d'objets sont activés ; le panneau des signatures valide toujours chaque révision précédemment signée après une mise à jour incrémentielle ; et le nombre de pages et les signets ont traversé un cycle de chargement, de modification et de sauvegarde sans dommage. Pour les sorties d'archives, passez également le fichier par veraPDF, car un xref compressé est précisément le type de structure qu'un validateur strict scrute de plus près qu'un visualiseur indulgent ne le fera jamais. Si votre travail implique également des entrées très volumineuses, les méthodes d'inspection dans notre présentation de l'API Direct File pour les flux de travail PDF volumineux se marient naturellement avec l'enregistrement incrémentiel, et les mécanismes de signature derrière les plages d'octets ci-dessus sont traités en profondeur dans l'article sur les signatures numériques HotPDF et PAdES
Les deux fonctionnalités sont fournies dans le cadre du Composant HotPDF pour Delphi et C++Builder, à côté des API de génération, de formulaire, de chiffrement et de signature couvertes ailleurs sur ce blog. La page produit renvoie à la référence complète de l'API si vous souhaitez aligner (line up) les appels ci-dessus sur votre propre pipeline de documents