Aller au contenu
getnextpdf.com

L’anatomie d’un fichier PDF

Ouvre n’importe quel PDF dans un éditeur de texte brut et la première chose que tu vois est rassurante : un en-tête %PDF-1.x ou %PDF-2.0. La dernière chose que tu vois est %%EOF. Tout ce qui se trouve entre ces deux lignes est une petite machine bien rangée pour retrouver des objets par numéro. Cette page est une dissection. Nous ouvrons le fichier, nommons chaque organe et montrons comment ils se connectent.

C’est le pendant structurel de deux pages voisines. Ce qu’est vraiment un PDF traite le fichier comme un graphe d’objets ; les mises à jour incrémentales couvrent la façon dont il grandit au fil du temps. Cette page reste au plus près des octets — les régions physiques qu’un analyseur parcourt, dans l’ordre où elles reposent sur le disque.

Tu n’as presque jamais besoin de cela pour utiliser un PDF. Tu en as besoin le jour où l’un d’eux tourne mal. Un fichier s’ouvre dans un visualiseur mais pas dans un autre ; un validateur signale une « table de références croisées endommagée » ; un document signé cesse soudain de se vérifier. Aucun de ces phénomènes n’est un mystère dès que tu sais lire l’anatomie. Ce sont un nombre qui ne correspond plus à une position, une région au mauvais endroit, ou une queue qui pointe vers rien.

Connaître la disposition transforme « le PDF est corrompu » en un diagnostic sur lequel tu peux agir. C’est la différence entre hausser les épaules devant une boîte noire et désigner l’octet exact qui ment.

Un PDF conforme comporte quatre parties physiques, dans cet ordre de fichier (Spec: ISO 32000-2, §7.5.1) :

  1. Un en-tête — une ligne, %PDF-2.0, qui nomme la version.
  2. Un corps — l’essentiel du fichier : une suite d’objets indirects numérotés.
  3. Une section de références croisées — un index qui associe le numéro d’objet au décalage d’octets où réside cet objet. Les PDF classiques utilisent une table textuelle ; PDF 2.0 utilise un flux xref compressé.
  4. Un trailer — un petit dictionnaire qui nomme le point d’entrée, suivi de startxref, d’un décalage, et de %%EOF.

La subtilité : un lecteur ne commence pas par le haut. Il commence par le bas, lit startxref pour trouver l’index, et utilise cet index pour atteindre directement n’importe quel objet. Le fichier est écrit de l’avant vers l’arrière mais lu de l’arrière vers l’avant.

Parcourons les quatre régions dans l’ordre, les octets sous les yeux.

L’en-tête est une ligne. NextPDF écrit %PDF-2.0, et par convention une seconde ligne de commentaire faite d’octets à bit de poids fort pour que les outils de transfert naïfs traitent le fichier comme binaire, et non comme texte. C’est cette seconde ligne qui fait qu’un PDF ouvert en texte brut affiche un petit charabia juste après la version.

Le corps est l’endroit où vit le document. Chaque objet indirect est un numéro, une génération, le mot-clé obj, une valeur, et endobj (Spec: ISO 32000-2, §7.3.10). La valeur est l’une de quelques formes — mais deux portent presque tout le poids :

  • Un dictionnaire, << /Key value … >>, est une table associant des noms à des valeurs. L’arbre des pages, le catalogue, les descripteurs de police : tous des dictionnaires.
  • Un flux est un dictionnaire suivi de stream, d’un bloc d’octets arbitraires, et de endstream. Le contenu des pages, les polices intégrées et les images sont des flux, presque toujours compressés. (Leurs filtres sont une histoire à part, racontée dans flux et filtres.)

Les objets se pointent les uns les autres par référence indirecte2 0 R signifie « objet 2, génération 0 ». C’est le câblage qui transforme une liste plate d’objets en graphe.

La section de références croisées est la partie que la plupart des gens ne se représentent jamais correctement. Dans la forme classique, c’est du texte brut : le mot-clé xref, puis des sous-sections de lignes de 20 octets à largeur fixe (Spec: ISO 32000-2, §7.5.4). Chaque ligne est un décalage d’octets à dix chiffres, une génération à cinq chiffres, et un seul drapeau — n pour en usage, f pour libre — complété à exactement vingt octets pour qu’un lecteur puisse se positionner sur n’importe quelle entrée par le seul calcul. PDF 2.0 remplace cela par un flux de références croisées : le même index, mais binaire et compressé à l’intérieur d’un objet flux marqué /Type /XRef (Spec: ISO 32000-2, §7.5.8). Plus petit, et capable de décrire des objets empaquetés à l’intérieur de flux d’objets.

Le trailer est la table des matières du fichier (Spec: ISO 32000-2, §7.5.5). Il nomme /Root — le catalogue du document, l’unique objet auquel tout le reste est suspendu — et /Size, le nombre d’objets. Vient ensuite la poignée de main qui rend possible la lecture à rebours : startxref, un décalage d’octets sur sa propre ligne, et %%EOF. Un lecteur se positionne à la fin, lit ce décalage, saute droit vers la section de références croisées, et c’est parti.

  1. HeaderOne line, %PDF-2.0, naming the version. A binary-marker comment usually follows.
  2. BodyNumbered indirect objects — dictionaries and streams — referenced by N G R.
  3. Cross-reference sectionA text table of 20-byte entries, or a compressed /Type /XRef stream in PDF 2.0.
  4. TrailerNames /Root and /Size, then startxref + offset + %%EOF.
  5. Read orderA reader starts at %%EOF, follows startxref to the index, then reaches each object directly.
The four physical regions of a PDF in file order, and the path a reader actually takes through them — starting at the trailer and working inward via the cross-reference section.

Il existe une cinquième région qu’un fichier de longue durée fait croître : une mise à jour incrémentale. Une modification n’est pas écrite sur place. Les objets modifiés, une nouvelle section de références croisées et un nouveau trailer sont ajoutés après le premier %%EOF, et ce nouveau trailer porte /Prev — le décalage de la section de références croisées précédente (Spec: ISO 32000-2, §7.5.6). Les sections forment une chaîne arrière ; pour tout numéro d’objet, l’entrée la plus récente l’emporte. Comme les octets d’origine ne bougent jamais, le contrôle cryptographique sur la plage d’octets qu’une signature couvre réellement tient toujours après une mise à jour. Une mise à jour incrémentale ultérieure peut tout de même changer ce qu’un validateur rapporte sur le document dans son ensemble — si les modifications postérieures à la signature sont autorisées, et ce que la signature est réputée certifier — mais elle ne peut pas altérer les octets signés eux-mêmes. Cette propriété est tout le sujet des mises à jour incrémentales.

Voici l’anatomie entière dans un fichier minimal. Les nombres sous xref sont des décalages d’octets, et ils doivent être exacts — pointe un caractère après le début d’un objet et un lecteur strict abandonne.

%PDF-2.0
1 0 obj
<< /Type /Catalog /Pages 2 0 R >>
endobj
2 0 obj
<< /Type /Pages /Kids [3 0 R] /Count 1 >>
endobj
3 0 obj
<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] >>
endobj
xref
0 4
0000000000 65535 f
0000000009 00000 n
0000000058 00000 n
0000000115 00000 n
trailer
<< /Size 4 /Root 1 0 R >>
startxref
186
%%EOF

Lis-le comme le fait un analyseur. Dernière ligne : %%EOF. Au-dessus : startxref 186, donc positionne-toi à l’octet 186, où commence xref. La table indique que l’objet 1 réside à l’octet 9. Le /Root 1 0 R du trailer pointe là — le catalogue — et depuis le catalogue tu parcours /Pages jusqu’à l’arbre des pages et trouves l’unique page. L’objet 0 est toujours la tête de la liste des objets libres, avec la génération 65535, un fossile de la toute première conception du format que tout lecteur s’attend encore à voir.

Le piège est de lire un PDF comme une histoire — de haut en bas, dans l’ordre. Ce n’est pas une histoire ; c’est un index muni d’un pointeur arrière. Les numéros d’objets n’ont pas à être séquentiels dans le fichier, les objets peuvent apparaître dans n’importe quel ordre physique, et un lecteur ne s’appuie jamais sur leur position. La seule carte qui fasse autorité est la section de références croisées, et la seule façon de trouver cette carte est le décalage startxref à la toute fin.

La conséquence surprend. Un PDF doté d’un corps impeccable mais d’un seul chiffre erroné dans startxref est illisible — le lecteur ne peut pas trouver l’index. Un PDF dont les objets sont dans un ordre brouillé mais qui a une section de références croisées correcte est parfaitement valide. La position physique ne porte aucun sens. La position enregistrée en porte tout.

Cette page décrit la structure physique, pas le contenu des pages. La manière dont les marques apparaissent sur une page — opérateurs de flux de contenu, affichage du texte, état graphique — est un sujet distinct. Elle décrit aussi un fichier bien formé. Les PDF du monde réel sont fréquemment un peu cassés et ne survivent que parce que des visualiseurs indulgents reconstruisent la table de références croisées en cherchant les mots-clés obj. Ce sauvetage est un comportement de visualiseur, et non quelque chose que le format garantit.

Reading and repairing arbitrary third-party PDFs — edition availability
EditionAvailability
CoreNextPDF est un générateur. Il enregistre chaque décalage depuis le tampon de sortie au moment où chaque objet est émis, si bien que les fichiers qu’il produit ont une section de références croisées qui correspond au corps par construction.
ProAnalyser, reconstruire ou réparer une table de références croisées endommagée dans un fichier que NextPDF n’a pas écrit est hors périmètre dans toutes les éditions.
EnterprisePour inspecter la structure d’un fichier existant, utilise un analyseur ou un validateur dédié ; NextPDF garantit la correction de ce qu’il écrit, et non de ce qu’il lit.

Pourquoi un PDF a-t-il une ligne de marqueur binaire après l’en-tête ? Certains anciens outils de transfert dénaturaient un fichier qu’ils croyaient être du texte brut. Le commentaire à bit de poids fort fait paraître le fichier sans ambiguïté binaire, si bien qu’il survit au trajet sans modification.

Le flux xref n’est-il qu’une table en plus petit ? Pour l’essentiel, oui, avec un pouvoir supplémentaire. Au-delà d’être compressé, un flux xref peut décrire des objets stockés à l’intérieur de flux d’objets — des entrées que la table textuelle classique de 20 octets n’a aucun moyen d’exprimer.

Puis-je avoir à la fois une table et un flux dans un même fichier ? Une révision unique utilise l’un ou l’autre. Mais un fichier hybride peut associer une table classique pour les anciens lecteurs et un flux de références croisées pour les nouveaux, si bien que chaque type de lecteur trouve un index qu’il comprend.

  • En-tête — la première ligne, %PDF-2.0, qui nomme la version ; généralement suivie d’un commentaire marqueur binaire.
  • Objet indirect — un objet numéroté dans le corps, écrit N G obj … endobj, où N est le numéro d’objet et G la génération.
  • Dictionnaire — une table << /Key value … >> associant des noms à des valeurs ; la forme d’objet la plus courante.
  • Flux — un dictionnaire plus un bloc d’octets entre stream et endstream, utilisé pour le contenu, les polices et les images.
  • Table de références croisées (xref) — l’index associant le numéro d’objet au décalage d’octets ; une table textuelle de 20 octets par entrée classiquement, un flux /Type /XRef en PDF 2.0.
  • Trailer — le dictionnaire qui nomme /Root et /Size, localisé via le décalage startxref à la fin du fichier.
  • Mise à jour incrémentale — des objets modifiés, une nouvelle section de références croisées et un nouveau trailer ajoutés après %%EOF, avec /Prev chaînant vers la section précédente.