Article technique

Flux d’objets et mises à jour incrémentales dans Delphi avec HotPDF

Ouvrez un PDF qui a traversé quelques années de production et vous trouverez souvent plus de comptabilité que de contenu : des milliers de petits objets dictionnaire, chacun précédé de son propre en-tête obj, plus une table de références croisées qui coûte 20 octets ASCII par objet. Dans un cas de support que nous avons analysé, une archive de police de 58 Mo consacrait moins de la moitié de ses octets au contenu réel des pages ; le reste était surcharge structurelle et révisions périmées. HotPDF, bibliothèque PDF VCL native pour Delphi et C++Builder, expose les deux mécanismes de format fichier qui attaquent ces deux moitiés du problème : les flux d'objets pour le stockage compact, et les mises à jour incrémentales pour les modifications append-only qui ne détruisent pas ce qui précédait.

Comment object streams et xref streams remodèlent le fichier

Jusqu'à PDF 1.4, chaque objet indirect est placé non compressé dans le corps du fichier, et la table de références croisées de fin est une structure texte à largeur fixe : exactement 20 octets par entrée, sans compression possible. Un document de 200 000 objets transporte donc environ 4 Mo de données xref seules, avant même qu'un glyphe soit dessiné. PDF 1.5 a introduit deux remplacements, définis dans ISO 32000-1 §7.5.7 et §7.5.8 : les object streams (/Type /ObjStm), qui rassemblent les objets non flux dans un conteneur unique compressé Flate, et les cross-reference streams, qui stockent la table de recherche elle-même comme binaire compressé à champs de largeur variable.

Les gains sont les plus importants là où les générateurs naïfs font le plus mal : documents chargés en formulaires, avec des milliers de dictionnaires de champs, arborescences de signets profondes et éléments de structure Tagged PDF. Ces objets sont individuellement minuscules et très répétitifs, ce qui en fait une entrée Flate idéale une fois groupés. Les flux de contenu de page étaient déjà compressibles avant PDF 1.5 ; les object streams réduisent donc peu les fichiers dominés par les images, mais réduisent fortement les fichiers dominés par la structure.

Dans HotPDF, les deux fonctions s'activent par une paire de propriétés, et la dépendance d'ordre compte :

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 exige que UseXRefStream soit True, car un objet compressé est adressé par une entrée xref de type 2, numéro de flux d'objets plus index, et les entrées de type 2 ne peuvent tout simplement pas être exprimées dans une table texte classique de 20 octets. Définir UseObjectStreams seul ne vous apporte rien ; définir les deux avant BeginDoc est la configuration fonctionnelle.

La frontière de compatibilité à PDF 1.4

Les deux propriétés valent False par défaut pour une raison qui mord les équipes avec intégrations héritées. Un lecteur limité à la sémantique PDF 1.4 ne dit pas « objets compressés non pris en charge » quand il rencontre un xref stream ; il signale généralement une table de références croisées endommagée ou refuse le fichier, parce que l'agencement de mots-clés trailer qu'il attend n'est pas là. Si votre sortie alimente une vieille passerelle fax, une imprimante matérielle avec interpréteur embarqué, ou un parseur aval écrit pour PDF 1.4, laissez les deux flags désactivés pour ce canal et acceptez le fichier plus gros. Pour les canaux d'archivage et de livraison web, où tous les lecteurs courants gèrent PDF 1.5 depuis deux décennies, les activer revient presque à une compression gratuite.

Un effet opérationnel mérite d'être signalé au support : une fois les dictionnaires rangés dans des object streams, le diff binaire de deux fichiers générés n'a plus de sens, car un changement d'un champ peut regonfler tout un conteneur. Les investigations de support doivent comparer les documents logiquement, par contenu d'objet, plutôt qu'avec un diff binaire.

Pourquoi les mises à jour incrémentales existent : offsets, signatures, piste d'audit

Une signature numérique dans un PDF couvre un /ByteRange explicite : deux plages du fichier physique, mesurées en offsets absolus, sur lesquelles le digest CMS a été calculé. Réécrivez le fichier, même avec un contenu visuellement identique, et chaque offset se décale ; le digest ne correspond plus et la signature est déclarée cassée. C'est la raison concrète pour laquelle ISO 32000-1 §7.5.6 définit les mises à jour incrémentales : les objets changés et ajoutés sont placés après le %%EOF existant, suivis d'une nouvelle section de références croisées dont l'entrée /Prev pointe vers la précédente. Les octets originaux ne sont jamais touchés ; une révision signée reste donc vérifiable, et Acrobat peut afficher chaque révision signée séparément dans le panneau de signatures.

HotPDF encapsule cela par un point d'entrée dédié :

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 détails se trompent facilement ici. Premièrement, BeginIncrementalUpdate doit recevoir le nom du fichier original ; la section xref ajoutée stocke des offsets qui ne sont valides que relativement à ces octets originaux exacts. Deuxièmement, l'enregistrement est append-only par définition ; la sortie est donc toujours plus grande que l'entrée. Ce n'est pas un défaut à optimiser : c'est la propriété qui maintient les révisions signées antérieures intactes.

Modifier des documents chargés : LoadFromFile, pas BeginDoc

Un piège distinct attend les développeurs qui ont appris HotPDF par l'API de génération. BeginDoc démarre un nouveau document ; c'est le mauvais appel lorsque l'objectif est de modifier un document existant. La chirurgie documentaire passe par le chemin document chargé :

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');

Le symptôme du mélange des deux modèles est un fichier qui contient seulement votre nouveau contenu et rien de l'original, parce que BeginDoc a parfaitement créé un document neuf à côté de celui que vous pensiez modifier. En revue de code, traitez LoadFromFile + SaveLoadedDocument comme un vocabulaire apparié, et BeginDoc + EndDoc comme un autre ; une procédure qui utilise les deux pour le même document est presque toujours boguée.

Contenir la croissance : quand compacter un fichier append-only

L'enregistrement append-only a un coût à long terme. Un job nocturne qui tamponne une ligne d'état sur le même PDF produit 365 révisions par an, et chaque révision entraîne une nouvelle section xref. Une fois l'historique de révisions devenu inutile, et à condition qu'aucune signature ne doive survivre, le fichier peut être compacté en le resérialisant par le chemin document chargé :

Pdf.LoadFromFile('stamped.pdf');
Pdf.SaveLoadedDocument('compacted.pdf');

Le réenregistrement est une réécriture complète, ce qui signifie qu'il supprime volontairement les révisions antérieures et invalidera toute signature présente dans le fichier ; placez-le donc derrière le même contrôle de politique que toute opération destructive. Une règle de production raisonnable que nous avons vue fonctionner : compacter quand le nombre de révisions franchit un seuil ou quand la surcharge ajoutée dépasse un pourcentage du fichier de base, et ne jamais compacter les fichiers dont le panneau de signatures n'est pas vide.

Contrôler le résultat avant livraison

La vérification de cette paire de fonctions est agréablement mécanique. Ouvrez la sortie dans Adobe Acrobat et contrôlez trois points : les propriétés du document signalent PDF 1.5 ou ultérieur quand les object streams sont activés ; le panneau de signatures valide encore chaque révision signée antérieure après une mise à jour incrémentale ; le nombre de pages et les signets ont survécu à un cycle charger-modifier-enregistrer. Pour les canaux d'archivage, passez aussi le fichier dans veraPDF, car les structures xref compressées sont précisément le genre de détail qu'un parseur strict examine plus soigneusement qu'un lecteur tolérant. Si vous traitez aussi de très grandes entrées, les techniques d'inspection de notre parcours de la Direct File API pour grands workflows PDF se combinent bien avec l'enregistrement incrémental, et les mécaniques de signature évoquées plus haut sont détaillées dans l'article HotPDF sur les signatures numériques et PAdES.

FAQ

L'activation des object streams casse-t-elle les anciens lecteurs PDF ?

Les lecteurs qui n'implémentent que PDF 1.4 ne peuvent pas parser les xref streams et signalent généralement le fichier comme endommagé. Gardez UseXRefStream et UseObjectStreams à leur valeur par défaut False pour les canaux qui alimentent des interpréteurs hérités, et activez-les pour les canaux modernes de lecture et d'archivage.

Une mise à jour incrémentale garde-t-elle ma signature numérique valide ?

Oui, c'est son objectif : les nouveaux objets sont ajoutés après les octets signés, de sorte que le digest du /ByteRange signé reste correct. Une réécriture complète, y compris une compaction par chargement et réenregistrement, casse chaque signature existante même lorsque le contenu visible est inchangé.

Pourquoi mon fichier continue-t-il de grossir après des enregistrements répétés ?

L'enregistrement incrémental ajoute un delta à chaque sauvegarde et ne récupère jamais l'espace. Compactez occasionnellement avec LoadFromFile plus SaveLoadedDocument lorsque l'historique de révisions et les signatures n'ont plus besoin d'être préservés.

Suite logique

Les deux fonctions font partie du jeu standard du HotPDF Component, avec les API de génération, formulaire, chiffrement et signature montrées ailleurs sur ce blog. La page produit renvoie à la documentation complète de l'API si vous voulez mapper les appels ci-dessus sur votre propre pipeline documentaire.