Comment fonctionne la modification du texte d’un PDF : flux de contenu, encodages de police et pourquoi on ne modifie qu’une ligne à la fois
Une page PDF ne contient ni paragraphes ni zones de texte, seulement des instructions qui dessinent des glyphes avec une police à une position. Modifier une ligne supprime les anciennes instructions et en écrit de nouvelles au même endroit, ce qui décide de ce qui est modifiable ou non.

Une page PDF ne contient ni paragraphes ni zones de texte. Elle contient une liste d’instructions : à cette position, avec cette police et cette taille, dessiner ces glyphes. Changer le texte revient à supprimer les instructions qui dessinent cette ligne et à en écrire de nouvelles à la même position, avec la même police, la même taille et la même couleur. C’est pourquoi Modifier le PDF modifie une ligne à la fois et ne sait pas renvoyer le texte à la ligne : cela découle de la façon dont le texte est stocké.
À quoi ressemble une ligne de texte dans le fichier
Le contenu d’une page se trouve dans un flux de contenu, un petit programme fait d’opérateurs. Quatre opérateurs font l’essentiel du travail pour le texte : BT et ET encadrent un bloc de texte, Tf choisit la police et la taille, Td se déplace vers la position suivante et Tj dessine une chaîne. Avec une police standard, le cas le plus simple ressemble à ceci :
BT /F1 18 Tf 20 150 Td (Hello World) Tj ET
Cela signifie : choisir la police F1 à 18 points, se déplacer en x 20, y 150, et dessiner Hello World. Un PDF imprimé depuis Chrome est généralement différent : les parenthèses ne contiennent plus des caractères mais des numéros de glyphes de la police, et Tm fixe la position une seule fois. Voici la ligne Hello World from Georgia d’une page exportée par Chrome, où chaque groupe de quatre chiffres hexadécimaux est un numéro de glyphe :
BT /F4 24 Tf 1 0 0 -1 30 52 Tm <002b0048004f004f00520003003a00520055004f0047000300490055005200500003002a004800520055004a004c0044> Tj ET
002b est H, 0048 est e, 004f est l, 0003 est une espace, 003a est W, et ainsi de suite. Ces numéros n’ont de sens qu’à l’intérieur de cette police précise ; ce ne sont pas des codes Unicode. Quand vous copiez le texte, le lecteur retransforme les numéros en caractères grâce à une table de correspondance stockée ailleurs dans le fichier.
Deux choses en découlent. D’abord, rien dans le flux de contenu ne dit « ceci est un paragraphe » : un saut de ligne n’est qu’un autre Td ou Tm qui déplace la position vers la ligne suivante, et le nombre de lignes d’un paragraphe doit se déduire des positions. Ensuite, pour écrire un nouveau caractère dans une ligne, il faut le numéro de ce caractère dans cette police, et la police ne contient souvent que les glyphes utilisés à l’écriture du fichier. C’est le résultat du sous-ensemblage des polices.
Vous pouvez développer les flux avec qpdf :
qpdf --qdf --object-streams=disable in.pdf out.pdf
Ouvrez out.pdf dans un éditeur de texte et cherchez Tj pour voir chaque chaîne dessinée sur la page. Pour savoir ce que contient d’autre un PDF, voir Ce qu’il y a dans un PDF : objets, flux et table de références croisées.
Comment l’éditeur reconstruit une « ligne » à partir de glyphes
L’éditeur doit d’abord regrouper des glyphes épars en une ligne sur laquelle l’utilisateur peut cliquer. Les glyphes de même police, de même taille et de même couleur dont l’écart avec le glyphe précédent ne dépasse pas une taille de police forment un même segment (un groupe de glyphes contigus) ; un saut de ligne, une nouvelle police ou une nouvelle couleur y met fin. Le texte qui s’écrit de droite à gauche, ou à la verticale, n’est pas regroupé et ne peut pas être ouvert à la modification.
Deux conséquences s’observent directement. Une ligne qui mêle deux polices, comme une ligne en chinois contenant un mot anglais, est scindée en deux segments contigus sur lesquels on clique séparément. Après une modification, il se passe la même chose : si le nouveau texte utilise une autre police, la ligne devient deux segments, et modifier l’un laisse l’autre inchangé.
Modifier une ligne : supprimer l’ancien texte, puis écrire le nouveau
La suppression utilise le caviardage limité au texte : les glyphes situés dans la zone de la ligne sont retirés du flux de contenu, les images et les traits sont laissés tels quels, et aucun rectangle de couleur n’est dessiné sur l’ancien texte, si bien que la copie et la recherche ne le trouvent plus. Seul le contenu de la page change ; si le même texte figure aussi dans une annotation ou dans les propriétés du document, celles-ci ne sont pas touchées.
L’écriture ajoute une nouvelle instruction de texte à la fin du flux de contenu de la page, à partir de la position de la ligne de base de la ligne d’origine, en reprenant sa taille et sa couleur. Après avoir changé la ligne ci-dessus en Hello World 2, ce qui est ajouté à la fin de la page est :
q 1 0 0 1 22.5 148.92 cm 0 0 0 rg
/Span <</ActualText <feff00480065006c006c006f00200057006f0072006c006400200032>>> BDC
BT /F4 18 Tf 0 0 Td <002b0048004f004f00520003003a00520055004f00470003> Tj ET
BT /F9 18 Tf 102.18 0 Td (2) Tj ET
EMC
Q
Le premier BT utilise la police d’origine F4 et les numéros de glyphes d’origine, de sorte que Hello World est réutilisé exactement tel quel. Le « 2 » ne figure pas dans le sous-ensemble de la police d’origine ; le second BT démarre donc une nouvelle police Helvetica standard (F9) pour le dessiner. q et Q confinent la transformation de coordonnées et la couleur de ce bloc à lui-même ; l’éditeur enveloppe aussi le contenu d’origine dans une paire q/Q, afin qu’un état laissé déséquilibré à la fin de la page d’origine ne puisse pas affecter le nouveau texte. Voici à quoi ressemblait un segment ; les valeurs varient selon le fichier.
L’ajout en fin de flux a trois conséquences :
- Le nouveau texte est peint en dernier, par-dessus tout le reste de la page. Un texte qui se trouvait sous une image ou un bloc de couleur apparaît au-dessus après une modification.
- Un outil qui extrait le texte dans l’ordre du flux de contenu (comme
pdftotext -raw) lit la ligne modifiée en dernier sur cette page, tandis que l’extraction par position (pdftotext -layout) ne montre aucun changement. La page a le même aspect, mais l’ordre de lecture a changé. - L’éditeur associe un ActualText au nouveau texte, qui indique à un lecteur quels caractères réels les glyphes représentent. Un glyphe peut correspondre à plusieurs caractères, et le retrouver à partir du seul numéro de glyphe renvoie parfois un caractère de compatibilité (par exemple en lisant 文 comme la clé de Kangxi ⽂) ; ActualText garantit que ce que vous copiez est ce que vous avez saisi.
Quelle police dessine le nouveau texte
Réutiliser la police d’origine est l’option la plus sûre, car la forme des glyphes, la graisse et la largeur correspondent à ce qui l’entoure. La possibilité de la réutiliser dépend de la capacité de l’éditeur à savoir quel code un caractère a dans cette police :
- Une police composite comme celle ci-dessus (Type0, numéros de glyphes sur deux octets, encodage Identity-H) : on cherche le caractère dans la police pour obtenir son numéro de glyphe, et on l’utilise s’il est trouvé. S’il n’est pas trouvé, le caractère n’est pas dans le sous-ensemble.
- Une police simple à un octet (TrueType, Type1) avec l’encodage WinAnsi ou MacRoman : la correspondance entre caractère et octet est fixe, et les largeurs proviennent de la table des largeurs du fichier.
- Une police dotée d’un encodage personnalisé (Differences) ou qui s’appuie sur l’encodage intégré de la police : il n’existe pas de correspondance fiable, donc l’éditeur ne devine pas et passe à une police de repli, faute de quoi le texte serait illisible.
Quand un caractère n’est pas dans la police d’origine, une police de repli est utilisée. Les lettres latines simples emploient la plus proche parmi Helvetica, Times et Courier, des polices standard que tout lecteur possède, de sorte qu’il n’y a rien à intégrer. Tout le reste, comme les caractères chinois, les kana, le hangul, le thaï, les lettres latines accentuées, le cyrillique et le grec, utilise une police Noto libre : le serveur prend les caractères de la requête et découpe seulement ces glyphes dans la police complète avec la bibliothèque libre de sous-ensemblage HarfBuzz, généralement quelques Ko, et le navigateur les intègre à votre fichier. La forme régionale d’un caractère chinois suit la langue de l’interface : chinois simplifié, traditionnel, japonais ou coréen.
Les glyphes sont intégrés plutôt que de n’écrire qu’un nom de police, parce qu’une police référencée par son seul nom est remplacée par la police de repli que chaque lecteur choisit, et la graisse peut différer d’un lecteur à l’autre ; une fois intégrés, tous les lecteurs affichent les mêmes formes.
Pourquoi une seule ligne à la fois
L’éditeur n’a pas de moteur de mise en page. Chaque position de glyphe est fixée dans le fichier ; le nouveau texte est simplement disposé vers la droite depuis le point de départ à l’aide des largeurs de la police, sans logique de « retour à la ligne en bord de page », et rien de ce qui suit n’est déplacé. Si vous allongez une ligne, les caractères en trop continuent vers la droite, et ce qui dépasse du bord de la page n’est pas visible. Réécrire un paragraphe ou ajouter des lignes à un tableau demande une nouvelle mise en page, ce qui implique de revenir au fichier source à partir duquel le PDF a été créé.
Les largeurs ne sont en outre que les chasses propres à la police (de combien la position d’écriture avance après chaque glyphe), sans crénage ni substitution de glyphes. Le texte qui n’a pas besoin de mise en forme contextuelle des glyphes (choix et placement des glyphes selon le contexte : latin, cyrillique, grec et caractères chinois) est disposé correctement. Les marques de ton du thaï demandent un positionnement des glyphes pour se placer au bon endroit, ce que l’éditeur ne fait pas, elles peuvent donc être décalées. L’arabe demande des formes de glyphes dépendant du contexte et une mise en page de droite à gauche, l’hébreu demande une mise en page de droite à gauche et les écritures indiques demandent que consonnes et signes vocaliques soient combinés en glyphes ; tout cela requiert cette mise en forme contextuelle complète du texte, que l’éditeur ne fait pas actuellement, donc la saisie en est refusée plutôt que de produire de mauvais caractères.
Deux autres limites viennent du fichier lui-même. Le contenu d’une page numérisée est une seule image sans opérateurs de texte, il n’y a donc rien à supprimer ni à modifier. Après la modification d’un fichier portant une signature numérique, le contenu d’origine que la signature protégeait a changé, et la signature n’est plus valide.
Vérifier par vous-même
Vous pouvez constater les changements décrits ci-dessus dans un fichier téléchargé avec trois commandes :
pdftotext -raw edited.pdf - # extrait dans l’ordre du flux de contenu : l’ancien texte a disparu, la ligne modifiée vient en dernier
pdffonts edited.pdf # liste des polices : les polices d’origine réutilisées et le sous-ensemble de police nouvellement intégré
qpdf --qdf --object-streams=disable edited.pdf edited.qdf # développe le flux de contenu ; le bloc BT ... ET ajouté se trouve à la fin
Pour aller plus loin
- Pourquoi le sous-ensemblage des polices casse l’édition mais pas la lecture : pourquoi la police d’origine peut manquer d’un caractère, et ce qui se passe alors.
- Ce qu’il y a dans un PDF : objets, flux et table de références croisées : la vue d’ensemble des flux de contenu, des objets et de la structure du fichier.
- Modifier le texte d’un PDF en ligne : corriger sans le fichier source : comment l’utiliser, à quoi il convient et quand revenir au fichier source.