- Pour modifier le contenu textuel d’un PDF sans outil PDF commercial, il faut décompresser les flux compressés, remonter l’encodage propre à chaque police et remplacer le texte d’origine et le texte de substitution de la même manière
- Beaucoup de PDF stockent le vrai texte du document non pas en clair, mais dans des flux compressés et des chaînes ressemblant à de l’hexadécimal, qu’on peut déplier vers une forme plus proche d’un format éditable avec
qpdf --qdf --object-streams=disable
- Les blocs de texte sont composés de commandes PDF comme
BT, Tf, Td, Tj, ET, et une valeur comme <002a004800570003003600480057> Tj n’est pas forcément une simple suite de points de code Unicode
- Si le PDF contient un mapping ToUnicode propre à la police, on peut vérifier avec
pdffonts que emb et uni valent yes, puis suivre une référence comme /ToUnicode 19 0 R pour trouver la table d’encodage
- Cette méthode est difficile à généraliser à tous les fichiers à cause de la complexité du standard PDF, mais sur certains PDF il est possible de retrouver l’encodage d’origine et de remplacer le texte par find-and-replace
Décompresser les flux de texte d’un PDF compressé
- En ouvrant un PDF dans un éditeur de texte, une partie de la structure peut être lisible, mais le vrai texte du document peut ne pas apparaître
- Beaucoup de PDF stockent les données textuelles sous forme compressée
- On peut décompresser les flux de texte avec
qpdf pour les inspecter
- Sur Mac, il existe une formule Homebrew
- Des outils équivalents sont aussi disponibles sur les distributions Linux
qpdf --qdf --object-streams=disable in.pdf out.pdf
- Après modification, on peut recompresser les flux
qpdf out-edited.pdf out-recompressed.pdf
- La commande de recompression a généré une erreur, mais le PDF obtenu restait lisible dans Preview
Trouver le texte dans les commandes PDF
- Même après décompression, le texte reste contenu dans des blocs de commandes PDF
- Un bloc de texte de base a la forme suivante
BT
/Font_0 12 Tf
288 720 Td
<002a004800570003003600480057> Tj
ET
- D’après la PDF Reference, ce bloc effectue les opérations suivantes dans l’ordre
- démarre un objet texte
- définit la police et sa taille à utiliser
- indique la position de départ sur la page
- dessine les glyphes de la chaîne à cet emplacement
- termine l’objet texte
Pourquoi le texte n’est pas directement lisible
- Une valeur comme
<002a004800570003003600480057> ressemble à une chaîne hexadécimale, mais ce n’est pas forcément une simple liste de points de code Unicode
- Le PDF propose de nombreuses façons de spécifier l’encodage, et peut inclure un encodage personnalisé dans le fichier
- Ces encodages peuvent le plus souvent être mappés vers des points de code Unicode
- Cette procédure suppose que le PDF en cours contient un encodage embarqué
- Le traitement des autres cas n’est pas abordé ici
Vérifier la police et le mapping Unicode
- L’encodage du texte dans un PDF est lié à une police précise
pdffonts affiche les polices du PDF ainsi que des informations liées à l’encodage
$ pdffonts sample.pdf
name type emb sub uni prob object ID
------------------------------------ ----------------- --- --- --- ---- ---------
CLDQZB+TrebuchetMS,Bold CID TrueType yes yes yes 9 0
YQBAIZ+TrebuchetMS CID TrueType yes yes yes 10 0
- Les champs à vérifier ici sont les suivants
emb : la police est embarquée dans le PDF
uni : un mapping Unicode est disponible
- Si les deux valent
yes, il y a des chances de pouvoir retrouver le mapping nécessaire
- Un identifiant de police comme
/Font_0 dans l’exemple peut, selon les cas, correspondre à un nom de police dans la sortie de pdffonts
- Dans le cas montré,
/Font_0 pointait vers CLDQZB+TrebuchetMS,Bold
Suivre la table de mapping ToUnicode
- Une fois le nom complet de la police utilisée pour le texte trouvé, il faut rechercher ce nom dans le PDF
- À un certain endroit, on peut trouver une référence comme celle-ci
/ToUnicode 19 0 R
- Cette valeur pointe vers l’ID d’objet de la table d’encodage
- En recherchant ensuite
19 0 obj, on a pu retrouver cette table
Remplacer le texte à l’aide de la table d’encodage
- La partie essentielle de la table de mapping peut ressembler à ceci
38 beginbfrange^M
<0036><0036><0053>^M
<0057><0057><0074>^M
<0044><0044><0061>^M
<0048><0048><0065>^M
<0050><0050><006D>^M
...
- Cet exemple mappe des plages de points d’encodage personnalisés vers des points Unicode
- Ici, chaque plage ne contient qu’un seul caractère
0036 est mappé vers le point Unicode 0053
- Si le format de la table diffère, on peut consulter le tutoriel sur les fichiers de mapping ToUnicode
- Pour automatiser la conversion, une table a été transformée en dictionnaire Python, puis de simples fonctions d’encodage/décodage ont été écrites
- Le flux final de remplacement se déroule ainsi
- identifier la valeur en encodage personnalisé du texte existant à remplacer
- encoder le texte de remplacement dans le même encodage personnalisé
- remplacer dans le PDF la valeur existante par find-and-replace
1 commentaires
Avis de Hacker News
La spécification PDF est assez rude. Ma curiosité préférée du moment, c’est qu’elle prend en charge tous les modes de fusion de calques de Photoshop lors du rendu d’éléments superposés.
Ma deuxième préférée, c’est que du contenu ajouté à la fin peut modifier du contenu précédent, si bien qu’il faut examiner de manière forensique toutes les différentes versions représentées dans un même fichier.
C’est aussi un exemple de la vacuité des DRM. La spécification prévoit un chiffrement par mot de passe, avec des mots de passe « propriétaire » et « utilisateur » distincts, ainsi que des champs de bits du type « impression interdite » ou « copie de texte interdite ». Mais pour lire le document, il faut bien le déchiffrer au final ; donc, si l’on ouvre un PDF chiffré avec le mot de passe « utilisateur » dans un outil non conforme et qu’on l’enregistre en version non chiffrée, on obtient un équivalent modifiable.
[1] Section “More than just transparency” de https://blog.adobe.com/en/publish/2022/01/31/20-years-of-tra...
[2] https://blog.didierstevens.com/2008/05/07/solving-a-little-p...
[3] Page 61 de https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandard...
[4] Par exemple, un script utilisant la bibliothèque pypdf
L’un des tutoriels de cracking célèbres de l’époque consistait à patcher la fonction qui vérifiait les permissions pour retirer les DRM du lecteur officiel.
Ce sujet revient périodiquement : la plupart des gens voient le PDF comme un format binaire difficile à percer, alors qu’en réalité ce n’est pas le cas.
Un PDF est un graphe d’objets de différents types, et ces types sont bien décrits dans la spécification officielle. J’ai le goût douloureux de la lire pour le plaisir.
Ce que je recommande toujours, c’est de faire comme l’auteur et de convertir le PDF en une version sans données compressées. Mon outil préféré est mutool :
mutool clean -d in.pdf out.pdfsuffit. Ensuite, en fouillant, on se rend compte avec surprise qu’on peut suivre bien plus de choses qu’on ne l’imaginait.L’article saute l’étape qui consiste à regarder l’objet page et à vérifier les ressources. C’est là que le nom de police utilisé dans le flux de contenu est relié au véritable sous-objet.
Un autre point important qui manque : la plupart des polices sont intégrées dans le PDF sous forme de sous-ensembles. Seuls les glyphes nécessaires sont conservés dans la police. Le réencodage semble généralement se produire à ce niveau, et ToUnicode est conservé pour permettre la copie de texte ou la recherche dans le PDF. C’est un plus pour l’utilisateur et, d’après mon expérience, c’est généralement présent et plutôt correct.
Dès le départ, la philosophie consistant à créer un format en lecture seule était mauvaise. La première fonctionnalité que les gens allaient demander était pourtant évidente, non ?
Je me demande s’il existe d’autres pistes pour bien gérer cela nativement, sans fournisseur cloud. En particulier pour les tableaux.
On peut faire comme ceci :
convertir en PostScript
a.psavecpdf2ps a.pdfmodifier directement le PostScript avec
vim a.psreconvertir en PDF avec
ps2pdf a.psLes PDF complexes qui contiennent par exemple du JavaScript intégré ou des animations peuvent ne plus fonctionner correctement après cet aller-retour. Mais pour un document ordinaire, cela marche plutôt bien. Supprimer un filigrane, changer des mots et des nombres est facile ; ajuster les espacements est plus difficile. Bien sûr, il faut connaître un peu PostScript.
Même lorsque la sortie était correcte, il fallait réordonner les pages, appliquer les codes-barres pour les machines de mise sous pli et le tri postal, puis générer des rapports. En général, on récupérait les adresses en extrayant le texte des pages avec Perl et d’autres outils ; le PS était globalement beaucoup plus facile à gérer que le PDF, mais il y avait aussi des cas très fragiles, par exemple quand des PDF « sécurisés » n’avaient pas de correspondance correcte des glyphes.
Dans le pire des cas, les clients envoyaient des PDF constitués de documents uniques ; une fois fusionnés, les polices sous-ensembles explosaient dans la sortie, remplissaient la mémoire de l’imprimante et la faisaient planter. Quand j’ai quitté ce poste, je ne crois pas qu’il existait d’outil utile pour unifier et fusionner des polices sous-ensembles issues de la même police d’origine. Cela devrait être possible et aurait été très utile, mais je n’avais ni le temps ni les connaissances pour m’y pencher.
Honnêtement, j’ai l’impression que les seuls à tirer profit de la complexité du PDF sont les auteurs de malwares.
Il me semble qu’un point important manque. À la fin d’un PDF se trouve une table qui stocke les décalages en octets de plusieurs objets dans le fichier : la table de références croisées.
Si l’on modifie le contenu du fichier, ces offsets changent généralement, ce qui casse le fichier. L’article semblait surtout s’intéresser au remplacement d’un chiffre par un autre, ce qui a pu laisser les positions inchangées.
Mais en général, si l’on ajoute, supprime ou modifie quelque chose au milieu du fichier, il faut recalculer la table xref ; utiliser une bibliothèque devient donc beaucoup plus simple que d’éditer le texte à la main.
qpdf semble reconstruire la structure PDF d’origine et essayer de conserver les numéros d’objets, mais les offsets sont entièrement recalculés.
Si les auteurs en parlaient un jour, j’aimerais lire pourquoi le format a été conçu ainsi. J’ai l’impression qu’il était au départ pensé comme un format entièrement textuel, puis qu’ils ont ensuite jugé le binaire plus efficace. Cela dit, l’obligation de donner une longueur fixe aux entrées de la table xref s’accorde assez bizarrement avec cette hypothèse.
mutool cleanpour la réparer. Comme elle peut être entièrement déduite du contenu, c’est sans danger.Tant qu’on préserve la longueur, on peut modifier le binaire comme on veut.
Un logiciel que j’utilisais autrefois s’authentifiait auprès d’un serveur, mais tout le traitement se faisait côté client. Le client exécutait directement du SQL sur le serveur, et le serveur vérifiait seulement que ce client ne dépassait pas le nombre de licences achetées.
On pouvait le passer dans un désassembleur, trouver la partie qui effectuait le contrôle, la remplacer directement par un JMP, puis remplir l’espace restant avec des NOP.
Il me semble qu’une étape importante manque quand on utilise le mode
--qdfde qpdf. Une fois l’édition terminée, il faut passer le fichier dans l’utilitaire fix-pdf pour recalculer tous les offsets d’objets et reconstruire la table de références croisées à la fin du fichier. Sauf si l’on n’a fait que remplacer en place, sans ajouter ni supprimer d’octets.Voici mon top 3 des faits amusants sur les PDF :
[0] Exemple : https://lab6.com/2
Si vous voulez continuer votre exploration du PDF et en apprendre davantage, vous pouvez lire ce document récent où j’en dissèque la structure :
https://pdfsyntax.dev/introduction_pdf_syntax.html
Ce que les gens oublient souvent à propos des PDF, c’est qu’à certains égards, ils sont plus proches d’un format d’image que d’un document Word. Dans l’édition de documents, les documents Word, les PDF et les images sont comme les projets DAW, le MIDI et le MP3 en musique, ou comme le code source Java, le bytecode JVM et le pur code machine x86 en logiciel.
L’objectif principal d’un fichier PDF est d’indiquer de façon parfaitement non ambiguë quoi afficher ou imprimer, avec beaucoup moins d’octets qu’une véritable image. Il exploite le fait que l’auteur du document connaît les motifs de sa structure, de sorte qu’une représentation correcte se compresse bien mieux qu’avec de vrais algorithmes de compression d’image. Par exemple, si l’on a accès à la police réelle, il vaut mieux dire « place ces caractères à ces coordonnées avec cet espacement » plutôt que d’inclure chaque occurrence de lettre comme une partie d’image et d’espérer que l’algorithme de compression réduise les répétitions. Savoir quel caractère appartient à quel mot, voire quel point de code Unicode correspond à quel glyphe de police, n’a fondamentalement pas d’importance si le seul objectif est de transmettre efficacement l’image du document.
Pour un document modifiable, la structure sémantique du contenu devient bien plus importante que sa simple représentation. Il importe de savoir si une rupture précise dans le texte correspond à plusieurs espaces, à la colonne suivante d’un tableau, ou à une mise en page étrange due à une image. Il importe aussi de savoir si le texte en bas de chaque page a été inséré plusieurs fois par l’auteur, ou saisi une seule fois puis défini comme pied de page. Si l’ajout d’un nouveau paragraphe doit modifier la mise en page, il importe de savoir que le dernier paragraphe de cette page est une note de bas de page et ne doit pas passer à la page suivante. Ce qui compte, c’est qu’un titre de section déplacé sur une autre page déclenche la mise à jour automatique de la table des matières, et non qu’il ne soit qu’un texte saisi manuellement par l’auteur.
Du point de vue d’une imprimante ou d’un écran, tout cela n’a aucune importance. Ils n’ont qu’à imprimer ou afficher ce qu’on leur demande. Dans un PDF, les notes de bas de page, les titres de section, les pieds de page et la table des matières n’ont pas besoin d’être spéciaux ; ils peuvent être simplement du texte mis en forme sans signification particulière. C’est pourquoi, dès qu’on utilise un PDF à d’autres fins que l’affichage ou l’impression, on ne peut pas atteindre une précision de 100 %. Bien sûr, des efforts existent pour y remédier, et les programmes générant des PDF peuvent y insérer librement les métadonnées qu’ils souhaitent, mais ce n’est pas obligatoire.
Ce n’est peut-être pas le modèle mental que les auteurs de PDF avaient en tête, mais c’est un point de vue utile pour comprendre pourquoi le PDF a cette forme.
Sorti sur une imprimante PostScript, il avait exactement le même aspect, et avec des polices Type 1, c’était parfois encore mieux.
Les personnes qui veulent faire cela passent à côté de l’essence du PDF. Le PDF est un format de description de page : il ne représente pas la structure du document, mais seulement les traces visibles sur la page.
Il ne faut pas essayer de modifier le PDF, mais modifier le document source qui l’a généré.
D’une manière ou d’une autre, envoyer simplement le document conçu pour être modifié est devenu quelque chose de « peu professionnel », et voilà où on en arrive.
C’est optionnel, mais recommandé pour l’accessibilité. PDF/UA en est un exemple. Voir les sections 14.7–14.8 de [1].
[1] https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandard...
On ne peut pas modifier directement le texte du PDF, car le maquettiste ne verrait pas cette modification. Les outils d’annotation d’Acrobat ne sont pas horribles, mais ils ne valent pas l’époque du papier et du crayon rouge. Sauf si l’on utilise l’outil « pencil » d’Acrobat. J’aimerais que cela s’améliore.
Il y a 20 ans, je travaillais dans un journal comme responsable des plaques d’impression. Nous avions deux « imprimantes » Kodak à un million de dollars pour les plaques, mais le mot imprimante n’est pas vraiment exact. Si je me souviens bien, l’émulsion de la plaque était exposée aux UV puis dissoute dans un bain chimique.
Le matériel Kodak tombait souvent en panne, et mon chef ouvrait directement les fichiers PostScript ou EPS pour corriger les en-têtes ou les passages erronés envoyés par le logiciel de mise en page. Ensuite, tout refonctionnait. À titre d’anecdote, notre énorme rotative offset allemande tournait sous Linux.
Je crois que mon chef s’appelait Bill. C’était quelqu’un de bien, qui m’avait emmené à un concert de Sigur Ros quand j’avais 17 ans. Voilà déjà deux histoires sans rapport avec le PDF.
Très bon article. Ces cinq dernières années environ, en créant DocSpring [1], j’ai beaucoup lu la spécification PDF, et j’ai pourtant encore l’impression de n’en avoir qu’effleuré la surface. qpdf est un excellent outil, et parmi mes autres outils préférés il y a RUPS [2], qui permet d’explorer en profondeur la structure d’un PDF.
[1] https://docspring.com
[2] https://github.com/itext/i7j-rups