Le composant HotPDF peut rechercher et remplacer du texte dans un PDF existant depuis Delphi et C++Builder. SearchLoadedPageText et SearchLoadedDocumentText localisent chaque occurrence d'une chaîne avec une précision au niveau du glyphe, et ReplaceLoadedPageText et ReplaceLoadedDocumentText réécrivent les octets correspondants sur place — à condition que chaque caractère de remplacement puisse être ré-encodé via la police d'origine, une contrainte physique que cet article traite en toute franchise plutôt que de la masquer dans une note de bas de page
La demande derrière cette fonctionnalité est toujours banale. Une entreprise change de nom et trois mille factures archivées portent encore l'ancien nom. Un modèle de contrat a été envoyé avec la date d'expiration de l'année dernière. Un code produit a été retiré et chaque fiche technique qui le mentionne a besoin du code successeur à la place. Dans un traitement de texte, chacune de ces opérations prend trente secondes. Dans un PDF, c'est un problème réellement difficile, et en comprendre la raison fait toute la différence entre bien utiliser l'API et soumettre un rapport d'anomalie qui n'est en fait qu'une citation de la spécification
Pourquoi remplacer du texte dans un PDF est-il si difficile ?
Remplacer du texte dans un PDF est difficile car une page PDF ne contient pas de texte modifiable — elle contient des glyphes positionnés. Selon le modèle d'affichage de texte de l'ISO 32000-1 §9.4, un flux de contenu pilote des opérateurs comme Tj and TJ qui tracent des séquences de codes de caractères à des coordonnées établies par la matrice de texte. Ces codes ne sont pas Unicode ; ce sont des indices dans l'encodage déclaré par la police de la page, et le mappage vers des caractères lisibles peut résider dans une CMap /ToUnicode, un tableau de différences d'encodage ou une chaîne de mappage CID. Il n'y a pas d'objet paragraphe, pas de flux de texte, et aucune garantie qu'un mot visuel soit la base de stockage d'une seule chaîne
Le remplacement ajoute un deuxième niveau de difficulté en plus du décodage : vous devez savoir exactement quels octets du flux d'origine ont produit chaque glyphe, afin de pouvoir insérer de nouveaux octets précisément dans cette zone et rien d'autre. Un extracteur de texte peut se permettre d'ignorer les positions des octets une fois l'Unicode extrait. Un outil de remplacement ne le peut pas. C'est pourquoi HotPDF a divisé le travail sur deux versions — la v2.251.0 a construit la couche de suivi des décalages et de recherche, et la v2.252.0 a construit la couche de réécriture par-dessus
Trouver du texte : recherche au niveau des glyphes avec suivi des décalages d'octets
La fonction SearchLoadedDocumentText de HotPDF trouve chaque occurrence d'un terme en effectuant une correspondance avec la séquence de glyphes Unicode décodée de chaque page, et non avec les octets bruts du flux. Ainsi, une occurrence trouvée est valide quelle que soit la manière dont la police l'a encodée. L'infrastructure sous-jacente a été introduite dans la version 2.251.0 : le pré-analyseur (tokenizer) du flux de contenu enregistre une plage d'octets StartOfs/EndOfs pour chaque opérande de chaîne — y compris ses délimiteurs ( ) ou < > — et chaque glyphe décodé porte un triplet TokenIndex/ItemIndex/ByteOffset pointant vers l'opérande exact, l'élément du tableau TJ et l'unité d'octet qui l'ont produit. Le même interpréteur de glyphes pilote l'API d'extraction décrite dans l'extraction de texte à partir d'un PDF chargé en Delphi ; la recherche conserve simplement la provenance que l'extraction élimine
Chaque correspondance est renvoyée sous la forme d'un enregistrement THPDFTextMatch portant l'index de page, la plage de glyphes inclusive, l'origine X/Y de l'espace utilisateur et la largeur de la correspondance, l'indice du jeton et de l'élément d'origine, ainsi que le texte correspondant lui-même. C'est suffisant pour piloter une superposition de surbrillance, une interface utilisateur de révision ou l'étape de remplacement. Une recherche qui ne trouve rien renvoie un tableau vide plutôt que d'échouer, ce qui simplifie le modèle d'appel
var
Pdf: THotPDF;
Matches: THPDFTextMatchArray;
I: Integer;
begin
Pdf := THotPDF.Create(nil);
try
if Pdf.LoadFromFile('invoices-2025.pdf') > 0 then
begin
if Pdf.SearchLoadedDocumentText('Acme Corp', False, Matches) then
for I := 0 to Length(Matches) - 1 do
WriteLn(Format('page %d at (%.1f, %.1f): "%s"',
[Matches[I].PageIndex, Matches[I].X, Matches[I].Y,
Matches[I].Text]));
end;
finally
Pdf.Free;
end;
end;
Un choix de conception délibéré mérite d'être noté. Lorsque CaseSensitive est à False, la comparaison convertit la casse uniquement pour les caractères ASCII, par conception : la conversion complète de la casse Unicode se comporte différemment selon les chaînes d'outils de Delphi 5 à XE prises en charge par HotPDF, et une API de recherche qui trouve des correspondances différentes selon le compilateur qui a construit votre application est pire qu'une API dotée d'une limite documentée et prévisible. Pour les textes professionnels en alphabet latin — noms, codes, dates — la conversion ASCII couvre les cas pratiques
Remplacer du texte : encodage inverse et épissure chirurgicale
La fonction ReplaceLoadedDocumentText, ajoutée dans HotPDF v2.252.0, réécrit chaque occurrence d'un terme en exécutant le mécanisme de décodage à l'envers. La fonction HPDFEncodeUnicode est l'inverse du décodeur de codes de caractères : elle parcourt la même chaîne de stratégies en sens inverse — recherche dans les sections bfchar et bfrange de /ToUnicode, mappage CID du flux d'encodage, mappages d'identité Type0 et tables prédéfinies WinAnsi et MacRoman — pour reconvertir chaque caractère de remplacement en octets de code de caractère attendus par la police d'origine. Les octets ré-encodés sont ensuite sérialisés dans un littéral de chaîne ou une chaîne hexadécimale bien formés, reflétant les règles d'échappement propres au tokenizer afin qu'un cycle analyse → ré-sérialisation soit stable
L'épissure (splice) elle-même est chirurgicale plutôt que globale. Seule la plage d'octets de code couverte par la correspondance est remplacée dans l'opérande de chaîne ; les octets non correspondants du même opérande, les espaces blancs entre les jetons et chaque opérateur environnant sont préservés textuellement, octet par octet. Remplacer bca dans abcabc donne a + remplacement + bc, et non un opérande détruit. Les remplacements peuvent être plus courts ou plus longs que le terme recherché — le littéral est resérialisé et l'élément /Length du flux est mis à jour — et chaque flux /Contents d'une page multiflux est traité de manière isolée afin que la page reste bien formée
var
Pdf: THotPDF;
ReplaceCount: Integer;
begin
Pdf := THotPDF.Create(nil);
try
if Pdf.LoadFromFile('contract-draft.pdf') > 0 then
begin
if Pdf.ReplaceLoadedDocumentText('2025-12-31', '2026-12-31',
True, ReplaceCount) then
WriteLn(Format('%d operand rewrites performed', [ReplaceCount]));
Pdf.SaveLoadedDocument('contract-final.pdf');
end;
finally
Pdf.Free;
end;
end;
Notez ce que l'API ne fait pas : elle ne recompose pas la page. Le PDF n'a pas de redistribution (reflow), de sorte qu'un remplacement visuellement plus large que l'original occupera simplement plus d'espace horizontal et pourra chevaucher ce qui était dessiné à sa droite. Les substitutions de longueur identique ou proche — dates, chaînes de version, numéros de pièces, corrections de noms — sont idéales. Une reformulation globale doit être effectuée dans le document source, pas dans le PDF
Pourquoi ne pouvez-vous pas remplacer du texte par des caractères que le sous-ensemble de polices n'a jamais inclus ?
Vous ne pouvez pas remplacer du texte par un caractère que le sous-ensemble de polices intégré n'a jamais inclus, car la séquence d'octets qui permettrait de sélectionner ce caractère n'existe tout simplement pas dans les tables de mappage de la police. Lorsqu'un producteur de PDF intègre une police de sous-ensemble, sa CMap /ToUnicode et ses structures d'encodage ne couvrent que les glyphes réellement utilisés par le document d'origine. HPDFEncodeUnicode ne peut inverser qu'un mappage existant : si le document n'a jamais contenu la lettre E dans cette police, il n'y a pas de code de caractère vers lequel inverser E. C'est une propriété physique du fichier, pas une limite d'une bibliothèque particulière — aucun outil ne peut inventer un mappage de glyphes qui n'a jamais été intégré
HotPDF gère cet échec de manière conservatrice. Si un seul caractère du remplacement ne peut pas être ré-encodé, toute cette occurrence du terme recherché est ignorée — pas d'exception, pas de texte partiel illisible, et l'occurrence n'est tout simplement pas comptée dans ReplaceCount. Conséquence pratique : comparez ReplaceCount au nombre de correspondances d'une recherche préalable, et considérez tout écart comme un signal. Dans l'exemple de date ci-dessus, le chiffre 6 doit apparaître quelque part dans le texte du document avec cette même police pour que la réécriture réussisse — probable dans une facture, mais jamais garanti de manière générale. Lorsque les caractères dont vous avez besoin ne sont pas disponibles et que le but est de supprimer des informations sensibles plutôt que de les reformuler, la suppression réelle du contenu reste le meilleur outil ; voir la rédaction et la restructuration de PDF chargés en Delphi pour cette approche
var
Matches: THPDFTextMatchArray;
Expected, Replaced: Integer;
begin
Pdf.SearchLoadedDocumentText('Acme Corp', True, Matches);
Expected := Length(Matches);
Pdf.ReplaceLoadedDocumentText('Acme Corp', 'Apex Corp', True, Replaced);
if Replaced < Expected then
WriteLn(Format('%d occurrence(s) skipped: characters missing ' +
'from the font subset, or match spans multiple operands',
[Expected - Replaced]));
end;
Quels changements se produisent dans le fichier lors de la sauvegarde ?
Un flux /Contents remplacé est enregistré sans compression. Les flux compressés en FlateDecode sont décompressés pour édition, et lorsque HotPDF écrit les octets reconstruits, il supprime l'entrée /Filter du flux et met à jour /Length au lieu de recompresser. Le PDF résultant est entièrement valide et s'affiche normalement dans les visionneuses courantes ; le compromis est un fichier plus volumineux pour chaque flux édité. Pour un pipeline de traitement par lots qui traite des milliers de documents, prévoyez cette croissance ou exécutez une passe de compression distincte en aval. La manière dont les objets réécrits interagissent avec la structure de référence croisée du document lors de la sauvegarde est un sujet à part entière, traité dans les flux d'objets et les mises à jour incrémentielles dans HotPDF
Tout le reste du fichier est laissé intact. Les flux non modifiés conservent leur compression, les polices et les images ne sont pas réécrites, et l'épissure au niveau de l'opérande signifie que même les flux édités ne diffèrent de l'original qu'à l'endroit où une correspondance a été trouvée. Ce conservatisme est délibéré : plus une bibliothèque réécrit de parties d'un document chargé, plus elle risque d'interférer avec une particularité du producteur qu'elle n'avait pas anticipée
La recherche et le remplacement de texte rejoignent l'extraction, la rédaction et le rendu de pages dans la boîte à outils de documents chargés de HotPDF, le tout piloté par le même interpréteur de flux de contenu et disponible de Delphi 5 aux versions actuelles de RAD Studio, sans dépendance externe. La référence complète de l'API et le téléchargement d'essai se trouvent sur la page produit de HotPDF Component