Les mises à jour incrémentielles PDF permettent à une application Delphi de modifier un document en ajoutant uniquement les objets modifiés, laissant intact chaque octet d'origine. losLab PDF Library implémente cela via AppendToStream, qui écrit uniquement la section incrémentielle définie par l'ISO 32000-1 §7.5.6, de sorte qu'une modification d'un seul signet dans un fichier de 2 Go coûte quelques kilo-octets de sortie au lieu d'une réécriture complète. Ce même mécanisme est la raison pour laquelle les documents signés peuvent être mis à jour sans invalider leurs signatures
Le problème résolu est concret. Une sauvegarde complète réécrit l'intégralité du fichier : chaque objet est resérialisé, chaque décalage de référence croisée (cross-reference offset) est recalculé, et la sortie n'a aucun rapport au niveau de l'octet avec l'entrée. Pour une facture de 40 Ko, c'est acceptable. Pour une archive numérisée de 2 Go où vous avez seulement corrigé une faute de frappe dans le titre du document, réécrire deux gigaoctets pour modifier vingt octets est absurde — et si le fichier portait une signature numérique, la réécriture l'a tout simplement détruite
Pourquoi la sauvegarde d'un PDF brise-t-elle sa signature numérique ?
Une signature numérique de PDF ne signe pas le contenu logique du document ; elle signe des plages d'octets du fichier physique. L'entrée /ByteRange du dictionnaire de signature enregistre exactement les zones du fichier couvertes par le condensé (digest) cryptographique. Toute opération de sauvegarde qui resérialise ces octets — même celle qui produit un document sémantiquement identique — modifie le condensé, et chaque validateur signalera la signature comme rompue. C'est voulu : la signature atteste des octets que le signataire a vus, non d'un modèle de document abstrait
Les mises à jour incrémentielles sont la porte de sortie fournie par la spécification PDF. Comme une sauvegarde incrémentielle ajoute de nouvelles données après le %%EOF d'origine et ne touche jamais aux plages d'octets signées, la signature existante continue d'être validée par rapport aux octets qu'elle couvre. Les validateurs classent ensuite les modifications ajoutées séparément — une deuxième signature, un remplissage de formulaire, une annotation — et décident s'il s'agit de modifications autorisées. Chaque flux de travail multi-signature dépend de cela : chaque signataire ajoute une section incrémentielle au-dessus de la précédente. Si vous concevez des pipelines de signature, l'article connexe sur la signature et la validation PAdES en Delphi explique en détail comment les plages d'octets de signature et les sections incrémentielles interagissent
Comment fonctionnent les mises à jour incrémentielles sous l'ISO 32000-1 §7.5.6
L'ISO 32000-1 §7.5.6 définit le modèle en three règles. Premièrement, le contenu du fichier d'origine est laissé entièrement intact — pas un seul octet ne bouge. Deuxièmement, les objets modifiés et nouvellement créés sont ajoutés après le dernier %%EOF, chacun avec le même numéro d'objet qu'auparavant (les objets modifiés obtiennent simplement une définition plus récente qui masque l'ancienne). Troisièmement, une nouvelle section de référence croisée (xref) et une fin de fichier (trailer) sont ajoutées ; l'entrée /Prev du trailer renvoie au décalage d'octets de la section de référence croisée précédente, formant une chaîne qu'un lecteur parcourt du plus récent au plus ancien pour résoudre chaque objet vers sa définition la plus récente
Deux propriétés utiles découlent de cette structure. Les mises à jour sont peu coûteuses par rapport à ce qui a changé, et non par rapport à la taille du document — le coût d'ajout est la taille des objets modifiés plus un petit surcoût xref/trailer. De plus, le fichier devient son propre historique des versions : chaque révision précédente est toujours physiquement présente, de sorte qu'un auditeur peut tronquer le fichier à n'importe quel %%EOF antérieur et récupérer exactement le document tel qu'il existait à ce moment-là. Pour les flux de travail de conformité qui doivent prouver à quoi ressemblait un document avant chaque modification, cette piste d'audit intégrée est souvent l'argument décisif en faveur des sauvegardes incrémentielles
Écrire une mise à jour incrémentielle avec AppendToStream
losLab PDF Library expose la sortie incrémentielle via AppendToStream(AppendMode: Integer; OutStream: TStream): Integer, qui renvoie 1 en cas de succès et 0 en cas d'échec. Le paramètre AppendMode sélectionne ce qui arrive dans le flux cible. Le mode 0 écrit un fichier complet : les octets source d'origine sont d'abord copiés dans le flux, puis la section incrémentielle est ajoutée. Le mode 1 écrit uniquement la section incrémentielle elle-même — le delta — et ignore entièrement les octets source. Le mode 2 écrit d'abord un préfixe fourni par l'appelant et enregistré via SetAppendInputFromString, puis ajoute la section de mise à jour par-dessus
var
Doc: TPDFlib;
Delta: TMemoryStream;
begin
Doc := TPDFlib.Create;
try
if Doc.LoadFromFile('contract.pdf', '') <= 0 then
Exit;
// Small edit: the kind of change that should not
// trigger a rewrite of the whole file
Doc.SetInformation(3, 'Amended 2026-07-04'); // key 3 = /Subject
Delta := TMemoryStream.Create;
try
// AppendMode = 1: write only the incremental section.
// Original bytes + Delta = a complete, valid PDF.
if Doc.AppendToStream(1, Delta) = 1 then
Delta.SaveToFile('contract.delta.bin');
finally
Delta.Free;
end;
finally
Doc.Free;
end;
end;
Le mode 1 est le plus intéressant pour la conception de systèmes. Comme le delta est autonome, vous pouvez le distribuer indépendamment de l'original : stocker les révisions sous forme de blobs distincts dans un stockage d'objets, répliquer uniquement les deltas sur un site distant, ou reconstruire n'importe quelle révision en concaténant le fichier de base avec sa chaîne d'incréments. La règle de reconstruction est une simple concaténation d'octets — le fichier d'origine d'abord, puis chaque delta dans l'ordre — car c'est exactement la disposition que le §7.5.6 prescrit pour un fichier mis à jour de manière incrémentielle
Comment la bibliothèque calcule-t-elle les décalages xref sans copier le fichier d'origine ?
Les entrées de référence croisée à l'intérieur d'une section incrémentielle doivent contenir des décalages d'octets absolus — des positions mesurées à partir du début du fichier complet, et non à partir du début du delta. Cela crée un problème pour le mode 1 : le programme d'écriture n'émet jamais les octets d'origine, pourtant chaque décalage qu'il enregistre doit faire comme s'ils étaient là. losLab PDF Library résout ce problème avec un adaptateur de flux interne, TPDFAppendSectionStream, qui présente un espace de coordonnées virtuel au sérialiseur. L'adaptateur est créé avec la longueur en octets du fichier d'origine comme décalage de base, signale sa position et sa taille comme étant cette base plus tout ce qui a été ajouté jusqu'à présent, et ne transmet que les octets nouvellement écrits au flux cible de l'appelant
La conséquence est que le mode 1 ne matérialise jamais de copie du document source — ni sur disque, ni en mémoire. L'implémentation naïve (écrire le fichier complet dans un tampon temporaire, puis couper la fin) générerait une copie temporaire de l'intégralité du PDF d'origine, ce qu'elle fait précisément pour éviter les gigaoctets d'entrée. Cette technique de virtualisation des décalages est très proche du décalage des références d'octets utilisé ailleurs dans la bibliothèque ; l'article sur la fusion rapide de PDF avec décalage de référence d'octets montre la même idée appliquée à la combinaison de documents, et le guide sur la fusion et division de grands PDF avec accès direct aux fichiers présente l'architecture d'E/S associée pour les fichiers qui ne tiennent pas facilement en RAM
Flux de sauvegardes complètes avec SaveToStream
La sortie incrémentielle ne représente que la moitié du streaming ; l'autre moitié concerne ce qui se passe lors d'une sauvegarde complète. SaveToStream dans losLab PDF Library pilote directement le sérialiseur de documents vers le flux cible, plutôt que de d'abord restituer l'intégralité du document dans une chaîne intermédiaire AnsiString puis d'écrire ce tampon en un seul appel. L'ancienne approche fonctionnait, mais elle signifiait que chaque sauvegarde complète conservait temporairement en mémoire une seconde copie complète de la sortie — inoffensif à 10 Mo, douloureux à 500 Mo, et bloquant pour des sorties de plusieurs gigaoctets sur des processus 32 bits. La sérialisation directe permet à la mémoire maximale de suivre les structures d'objets du document plutôt que sa longueur sérialisée
var
Doc: TPDFlib;
Output: TFileStream;
begin
Doc := TPDFlib.Create;
try
if Doc.LoadFromFile('archive.pdf', '') <= 0 then
Exit;
// ... edits that justify a full rewrite ...
Output := TFileStream.Create('archive-rewritten.pdf', fmCreate);
try
if Doc.SaveToStream(Output) = 0 then
Writeln('Save failed, error ', Doc.LastErrorCode);
finally
Output.Free;
end;
finally
Doc.Free;
end;
end;
Une leçon sur le mode de partage : quand AppendToFile renvoyait 0
Une régression dans ce domaine mérite d'être racontée car le schéma d'échec se généralise. AppendToFile(FileName) ajoute une mise à jour incrémentielle directement à un PDF existant sur le disque — l'appel naturel pour un flux de travail de piste d'audit sur place : charger un fichier, effectuer une modification, l'ajouter au même chemin. Dans la version 3.71.2, cette séquence exacte a commencé à renvoyer 0. La cause première résidait dans le chargeur, pas dans le programme d'écriture : pour prendre en charge la lecture à la demande de documents volumineux, LoadFromFile maintient le descripteur du fichier source ouvert pendant toute la durée de vie de l'objet document, et ce descripteur était ouvert avec fmShareDenyWrite. Lorsque AppendToFile tentait ensuite de réouvrir le même fichier en écriture, le mode de partage du chargeur lui-même le refusait, et l'API échouait avant d'écrire un seul octet
Le correctif a assoupli le mode de partage du chargeur en le passant à fmShareDenyNone, ce qui est sûr précisément en raison de la nature d'un ajout incrémentiel : il ajoute des octets strictement après la fin du fichier et ne réécrit jamais la zone desservie par le descripteur longue durée du lecteur. La leçon générale pour quiconque utilise cette bibliothèque — ou conçoit des chargeurs de flux similaires — est que les lecteurs passifs conservant les descripteurs et les rédacteurs écrivant dans le même fichier sont en tension, et le mode de partage choisi lors de l'ouverture est un contrat d'API, pas un détail d'implémentation. Si AppendToFile renvoie un jour 0 dans votre code, vérifiez d'abord si un autre élément de votre processus ne détient pas toujours le fichier cible avec un mode de partage restrictif
Les coûts réels : quand les mises à jour incrémentielles ne sont pas le bon outil
Les mises à jour incrémentielles échangent la taille du fichier contre l'efficacité d'écriture, et cet échange n'est pas toujours avantageux. Chaque révision ajoute ses objets modifiés tandis que les définitions obsolètes restent dans le fichier, de sorte qu'un document modifié des centaines de fois accumule des objets morts et une longue chaîne /Prev que chaque lecteur doit parcourir. Pire encore, le contenu « supprimé » ne disparaît pas : le texte supprimé dans la révision cinq est toujours physiquement présent dans les octets de la révision quatre, récupérable par quiconque tronque le fichier. Le caviardage, la désinfection ou toute suppression de contenu sensible exige donc une réécriture complète — une sauvegarde incrémentielle d'un caviardage est une fuite de données en plusieurs étapes
Une sauvegarde complète est également le bon choix lorsque l'objectif est le compactage (élimination des incréments accumulés et des objets inutilisés), lors de la modification de propriétés à l'échelle du document telles que le chiffrement — le chiffrement de nouveau touche chaque chaîne et flux, il n'y a donc plus rien d'« incrémentiel » dans la modification — ou lors de la production d'un livrable propre dont l'historique des modifications ne doit pas voyager avec le fichier. Une règle raisonnable : utiliser AppendToStream ou AppendToFile tant qu'un document est actif et en cours de modification, en particulier lorsqu'il porte des signatures ; utiliser une réécriture complète SaveToStream aux limites du cycle de vie, lorsque le document quitte votre système ou que son historique doit être aplati
Les mises à jour incrémentielles, la sortie de delta à décalage virtuel et la sérialisation directe vers le flux font toutes partie de la bibliothèque standard losLab PDF Library pour Delphi, C# et VB.NET ; la page du produit répertorie l'ensemble de l'interface API de sauvegarde et d'ajout aux côtés des fonctionnalités de signature et de grands fichiers abordées ci-dessus