Composez un article de dix-huit pages : le PDF pèse 76 974 octets, et le .synctex.gz qui atterrit à côté en pèse 159 347 — plus du double de ce qu'il décrit. Cette carte pléthorique, c'est SyncTeX, et elle ne fait qu'une seule chose : retenir quelle ligne de la source LaTeX est devenue quel rectangle sur quelle page. Cette page ouvre le fichier, exécute les deux sens à la main avec synctex view et synctex edit, explique pourquoi un clic atterrit sur une ligne plutôt que sur le mot visé, et se termine par la liste de contrôle à dérouler quand la recherche avant ne fait rien du tout.
Ce que -synctex=1 produit réellement
Avec -synctex=1, le moteur écrit un fichier supplémentaire à côté du PDF, sous le même nom de base : main.synctex.gz. Sans l'option, rien n'est écrit — c'est de loin l'oubli le plus fréquent dans une configuration SyncTeX. La valeur n'est pas un booléen mais un jeu de bits, détaillé tel quel dans man synctex : 0 ou absent, aucun fichier ; valeur positive, gzip ; valeur négative, texte brut non compressé ; le bit 2 conserve la compression mais retire le .gz du nom ; 4 active la prise en charge des formulaires pour pdfTeX ; 8 comprime davantage. Tout à la fois donne -synctex=15. Seul LuaTeX exige la forme à deux tirets, --synctex=1. Le mécanisme est présent de la même façon dans TeX Live et dans MiKTeX, et pdfLaTeX, XeLaTeX et LuaLaTeX produisent le même type de carte.
pdflatex -synctex=1 main.tex # writes main.synctex.gz
xelatex -synctex=1 main.tex
lualatex --synctex=1 main.tex # LuaTeX wants two dashes
pdflatex -synctex=-1 main.tex # writes main.synctex, plain text
pdflatex -synctex=2 main.tex # writes main.synctex -- still gzip inside!Ce bit 2 dissimule un petit piège. Le fichier produit par -synctex=2 s'appelle main.synctex, mais file y voit des données gzip. Se fier à l'extension et l'ouvrir dans less donne du bruit binaire et l'impression que SyncTeX a écrit un fichier corrompu. Pour simplement le lire, prenez -synctex=-1. Là où la ligne de commande est hors d'atteinte — une interface graphique qui compile d'un bouton —, la primitive TeX \synctex=1 en tête de source active également l'enregistrement. Mais cette voie ne donne jamais que la forme compressée : même \synctex=-1 produisait ici main.synctex.gz sous TeX Live 2024. Pour du texte brut, la ligne de commande est la seule porte.
| Valeur | Fichier écrit | Contenu |
|---|---|---|
(none) | — | rien n'est écrit ; aucun sens ne fonctionne |
-synctex=0 | — | comme sans option ; la désactivation explicite |
-synctex=1 | main.synctex.gz | compressé en gzip ; le choix courant |
-synctex=-1 | main.synctex | texte brut ; la forme de débogage |
-synctex=2 | main.synctex | nom d'apparence brute, contenu gzip — trompeur |
-synctex=15 | main.synctex | bits 1+2+4+8 : formulaires et compression renforcée compris |
Décompresser le .synctex.gz et lire ce qu'il contient
Le contenu est du texte ligne à ligne ; gunzip -c main.synctex.gz le rend directement lisible. Quatre sections le composent : preamble, content, postamble et post scriptum. Le préambule porte la version et la table Input:, qui numérote à partir de 1 chaque fichier ouvert par TeX. Non seulement votre main.tex, mais aussi article.cls, size10.clo, chaque .sty et main.aux y reçoivent une étiquette — c'est la moitié de l'explication de l'embonpoint de la carte. Viennent ensuite Magnification, Unit, X Offset et Y Offset, qui définissent le système de coordonnées : Unit:1 signifie que tous les nombres qui suivent sont en sp (scaled points, un 65536e de point), et X Offset:4736287 vaut exactement un pouce, la marge que TeX prend depuis toujours au coin supérieur gauche du papier.
$ gunzip -c main.synctex.gz # abridged; ... marks omitted lines
SyncTeX Version:1
Input:1:/home/you/doc/./main.tex
Input:2:/usr/local/texlive/2024/texmf-dist/tex/latex/base/article.cls
Input:3:/usr/local/texlive/2024/texmf-dist/tex/latex/base/size10.clo
...
Input:10:/home/you/doc/./chap.tex
Output:pdf
Magnification:1000
Unit:1
X Offset:4736287
Y Offset:4736287
Content:
!962
{1
[1,17:4736286,46220574:26673152,41484288,0
(1,4:8799518,8865054:22609920,655359,0
x1,4:9330359,8865054
k1,4:31409438,8865054:11586343
...
)
]
}1La section content est un relevé de boîtes imbriquées. {1 … }1 est une feuille, c'est-à-dire une page ; les crochets [ … ] une boîte verticale et les parenthèses ( … ) une boîte horizontale. Chaque ouvrant a la forme étiquette,ligne:x,y:largeur,hauteur,profondeur : ainsi (1,4:8799518,8865054:22609920,655359,0 signifie « une boîte horizontale née à la ligne 4 de l'étiquette 1, soit main.tex ». Dans l'exemple ci-dessus, la ligne 4 de main.tex était \section{Forward and inverse}. Le premier caractère d'une ligne nomme le type d'enregistrement : x la position courante, k un crénage, g du ressort, $ des mathématiques, f une référence de formulaire pdfTeX, v et h des boîtes verticales et horizontales vides, ! un décalage en octets permettant de se placer au milieu du fichier.
Consigner chaque page à cette finesse fait grossir le fichier. Pour l'article de dix-huit pages du début, la carte compressée pesait 159 347 octets et, décompressée, 638 962 octets — plus de huit fois le PDF, répartis sur 24 717 lignes. C'est pourquoi .synctex.gz n'est pas un livrable mais un fichier de travail régénérable : mettez-le dans le .gitignore et ajoutez-le aux @generated_exts de latexmk pour que le nettoyage l'emporte. La page de manuel synctex(5) est catégorique sur un point voisin : la structure ne doit pas être tenue pour publique, et personne d'autre que la commande synctex et la bibliothèque synctex_parser n'a besoin de l'analyser. La lire pour comprendre un problème, très bien ; en faire la base durable d'un outil maison, non.
# .latexmkrc -- keep -synctex=1 on every build, and clean the map up afterwards
$pdf_mode = 1;
$pdflatex = 'pdflatex -synctex=1 -interaction=nonstopmode -file-line-error %O %S';
@generated_exts = (@generated_exts, 'synctex.gz');Exécuter la recherche avant et la recherche inverse à la main
La recherche avant (source → PDF) est synctex view ; la recherche inverse (PDF → source) est synctex edit. Ce que l'éditeur et la visionneuse appellent derrière leurs boutons, c'est l'un de ces deux commandes ou son équivalent : quand la recherche inverse en LaTeX se dérègle, les lancer directement départage d'un coup les deux coupables possibles, une carte fautive ou une poignée de main défaillante entre éditeur et visionneuse. La recherche avant prend -i ligne:colonne:fichier et -o pdf, et répond par un numéro de page et un rectangle.
$ synctex view -i 5:1:main.tex -o main.pdf
This is SyncTeX command line utility, version 1.5
SyncTeX result begin
Output:main.pdf
Page:1
x:153.195526
y:156.585541
h:133.768356
v:158.522720
W:343.711060
H:8.855677
before:
offset:-1
middle:
after:
SyncTeX result endLe couple x et y est le point à montrer ; h, v, W, H sont le bord gauche, la ligne de base, la largeur et la hauteur du rectangle à surligner. L'unité est le point PDF : v:158.52 signifie 158,52 pt sous le haut de la page. La visionneuse prend ces nombres, fait défiler, et fait clignoter une bande de W sur H. L'autre sens se contente de renvoyer une coordonnée.
$ synctex edit -o 1:153.19:156.58:main.pdf
This is SyncTeX command line utility, version 1.5
SyncTeX result begin
Output:main.pdf
Input:/home/you/doc/./main.tex
Line:5
Column:-1
Offset:0
Context:
SyncTeX result endL'argument s'écrit -o page:x:y:pdf, et ce qui revient est un chemin de fichier absolu et un numéro de ligne. La visionneuse insère ces Input: et Line: dans la commande qui lance l'éditeur. Ce qui saute aux yeux, c'est Column:-1. Le format saurait représenter une colonne, mais les moteurs n'en écrivent pas : la recherche inverse est donc en pratique toujours à la ligne près. C'est pour cela que l'éditeur pose le curseur en début de ligne — ce n'est pas un défaut de configuration.
Pourquoi le saut atterrit sur une ligne et non sur le mot cliqué
Parce que l'unité de correspondance est une boîte composée. TeX transforme un paragraphe en une longue liste horizontale et ne la découpe en lignes qu'à la toute fin. Ce que SyncTeX retient, ce sont les boîtes obtenues et la ligne source qui a produit chacune — ni les mots, ni les caractères. La mesure rend l'asymétrie flagrante : douze mots courts placés sur douze lignes consécutives sans ligne blanche se réduisent à deux boîtes de ligne seulement. Interrogez la recherche avant sur les lignes source 5 à 12 l'une après l'autre : toutes répondent la même coordonnée.
# twelve short source lines 5..16, no blank line between them
$ for l in 5 6 7 8 9 10 11 12 13 14 15 16; do
> printf "src %-3s " $l; synctex view -i $l:1:res.tex -o res.pdf | grep "^v:"
> done
src 5 v:230.405960
src 6 v:230.405960
src 7 v:230.405960
src 8 v:230.405960
src 9 v:230.405960
src 10 v:230.405960
src 11 v:230.405960
src 12 v:230.405960
src 13 v:242.361130
src 14 v:242.361130
src 15 v:242.361130
src 16 v:242.361130Le point intéressant, c'est que le sens inverse est un peu plus fin. Promenez synctex edit de gauche à droite sur cette même boîte de ligne : il renvoie des lignes source différentes selon la position horizontale — et souvent plusieurs candidats par point, dont la visionneuse retient d'ordinaire le premier. La recherche avant est donc grossière, la recherche inverse est fine. À l'inverse : dans un paragraphe où une seule longue ligne source s'est repliée sur huit lignes composées, cliquer sur n'importe laquelle des huit renvoyait toujours la ligne 3, car il n'y avait jamais eu qu'une ligne source à retenir. Tomber à un mot de la cible dans une figure TikZ, dans le développement d'une macro compliquée ou au sein d'un tableau relève de la même granularité de boîte, pas d'un défaut.
# same typeset line (baseline v=230.4), scanning left to right
$ for x in 135 185 235 310 360 435 460; do
> printf "x=%-4s " $x; synctex edit -o 1:$x:229:res.pdf | grep "^Line:" | tr "\n" " "; echo
> done
x=135 Line:5
x=185 Line:5
x=235 Line:6 Line:7
x=310 Line:7 Line:8
x=360 Line:9 Line:10
x=435 Line:10 Line:11
x=460 Line:11 Line:12Il en découle une conséquence pratique. Écrivez la source sur une seule ligne immense et la résolution de SyncTeX retombe à un point unique pour tout le paragraphe. Coupez aux phrases, ou au moins aux frontières de propositions, et la recherche inverse recommence à viser juste. La façon d'écrire qui rend les diffs lisibles en gestion de versions et celle qui rend SyncTeX précis se trouvent être la même.
Pourquoi les numéros de ligne dérivent dès qu'on utilise \input
La réponse courte est que \input n'est pas en soi une cause de décalage. Chaque enregistrement porte une étiquette en plus du numéro de ligne, et l'étiquette indexe la table Input:. Un fichier enfant reçoit sa propre étiquette, et ses numéros de ligne sont ceux de ce fichier. Mesuré : un clic dans un chapitre inclus par \input{chap} renvoyait chap.tex comme Input: et le numéro de ligne interne comme Line:. Enchaînez vingt chapitres, rien ne s'additionne.
Il y a deux causes véritables. La première est une carte périmée. Un .synctex.gz est la photographie d'une compilation : ajoutez trois lignes en tête de chap.tex et lancez la recherche inverse sans recompiler, la carte répond encore Line:3 alors que le texte est passé à la ligne 6. Si le décalage vaut exactement le nombre de lignes insérées, c'est presque à coup sûr cela. La seconde est le chemin absolu. Ce qui atterrit dans Input: est le chemin complet au moment de la compilation : déplacer le projet, l'ouvrir via un lien symbolique, ou compiler dans un conteneur et consulter à l'extérieur envoient la visionneuse vers un chemin qui n'existe plus. Quand c'est le mauvais fichier qui s'ouvre — ou rien du tout — plutôt que la mauvaise ligne, c'est le suspect.
La commande de recherche inverse, visionneuse par visionneuse
La recherche inverse se configure du côté de la visionneuse. On lui remet un gabarit : lorsqu'on clique, insère ce numéro de ligne et ce nom de fichier, puis exécute cette commande. L'ennui, c'est que la syntaxe des substitutions varie d'une visionneuse à l'autre. zathura emploie des accolades — %{line} et %{input} ; Skim, %line et %file ; SumatraPDF et Okular, %l et %f. La plupart des réglages recopiés ailleurs échouent pour cette seule raison : la commande est juste, seuls les jetons ne correspondent pas.
| Visionneuse | Plateforme | Jetons de ligne et de fichier |
|---|---|---|
zathura | Linux / BSD | %{line} et %{input}, via set synctex-editor-command |
Skim | macOS | %line et %file, dans Preferences ▸ Sync ▸ Preset: Custom |
SumatraPDF | Windows | %l et %f, dans le champ inverse search sous Settings ▸ Options |
Okular | Linux / Windows | %l et %f, sous Configuration ▸ Éditeur (pour Kile, kile --line %l) |
Adobe Acrobat / Reader | toutes | aucune prise en charge de SyncTeX ; recherche inverse impossible |
# zathura (~/.config/zathura/zathurarc)
set synctex true
set synctex-editor-command "nvim --headless -c 'VimtexInverseSearch %{line} %{input}'"
# Skim -- Preferences > Sync > Preset: Custom
Command: nvim
Arguments: --headless -c "VimtexInverseSearch %line '%file'"
# SumatraPDF -- Settings > Options > inverse search command-line
cmd /c start /min "" nvim --headless -c "VimtexInverseSearch %l '%f'"
# Okular -- Settings > Configure Okular > Editor
kile --line %lDéclencher la recherche avant depuis l'éditeur est simple : dans TeXShop avec Skim, c'est Cmd-clic dans le PDF, et Maj-Cmd-clic pour l'autre sens. TeXstudio utilise Ctrl-clic, ou les entrées de menu « Aller au PDF » et « Aller à la source ». VS Code avec LaTeX Workshop utilise Ctrl/Cmd+Alt+J. Un piège propre à macOS mérite d'être nommé ici : le /usr/bin/vim livré avec macOS est compilé -clientserver, il n'existe donc aucun canal permettant à l'extérieur de rappeler l'éditeur, et l'extrait de configuration habituel ne fait silencieusement rien. Le remède est MacVim, le Vim de Homebrew, ou Neovim.
Ce qui se passe sur la route DVI (pLaTeX / upLaTeX → dvipdfmx)
D'abord la conclusion : avec les réglages par défaut, il n'y a rien à faire, et les coordonnées coïncident avec celles de la route directe vers le PDF. -synctex=1 se transmet au moteur (platex ou uplatex), pas au convertisseur. Le moteur écrit le .synctex.gz à côté du DVI, et son préambule indique Output:dvi et non Output:pdf. Lancer ensuite dvipdfmx ne touche pas du tout à cette carte — la comparaison du fichier avant et après avec cmp l'a montrée ici identique octet pour octet, et dvipdfmx n'a de toute façon aucune option -synctex. Au passage, le dvipdfmx de TeX Live 2024 est un lien symbolique vers xdvipdfmx : le même et unique binaire que le convertisseur employé pour XeTeX.
# the same source, both routes, same forward-search question
$ pdflatex -synctex=1 cmp.tex && synctex view -i 3:1:cmp.tex -o cmp.pdf
h:133.768356 v:137.554138
$ latex -synctex=1 cmp.tex && dvipdfmx cmp.dvi
$ synctex view -i 3:1:cmp.tex -o cmp.pdf
h:133.768372 v:137.554153
# the two agree to about 2e-5 pt -- nothing needs reconcilingÀ quoi sert alors synctex update ? Exactement à ce que dit son manuel — mettre à jour le fichier SyncTeX une fois qu'un filtre dvi/xdv vers pdf a été appliqué — et il n'est nécessaire que si cette conversion a reçu un grossissement ou un décalage. On donne à -m, -x et -y les valeurs passées au filtre. L'implémentation est le plus intéressant : synctex update ne réécrit pas la carte. Un lancement avec -x 20mm suivi d'une comparaison octet par octet a montré qu'il se contente d'ajouter un bloc gzip après la ligne finale Post scriptum:. Décompressé, ce bloc tient en une ligne : X Offset:20mm. Autrement dit, la quatrième section du format est l'endroit où un convertisseur en aval colle une correction du système de coordonnées, comme un pense-bête. Au quotidien, ptex2pdf ou latexmk exécute cette chaîne et la question ne se pose jamais.
Quand SyncTeX ne fait rien : quoi vérifier, dans l'ordre
Vérifiez d'abord qu'un .synctex.gz se trouve dans le même dossier que le PDF. Sinon, c'est que -synctex=1 manque à la compilation. Ce qu'on oublie facilement ici, c'est la commande de compilation fournie par l'éditeur. L'outil PDFLaTeX livré avec Kile, par exemple, n'inclut pas -synctex=1 dans ses options par défaut, et cette absence est la première cause de « j'ai tout configuré et rien ne se synchronise ». Cocher une case SyncTeX dans les préférences d'un éditeur ne modifie pas toujours la commande réellement exécutée.
- Y a-t-il une carte ? Cherchez le
.synctex.gzavecls. S'il manque, ajoutez-synctex=1à la commande de compilation — et tenez les réglages par défaut de l'éditeur pour suspects. - Le PDF et la carte ont-ils été séparés ?
-output-directoryne pose pas de problème, les deux atterrissent ensemble dans le dossier de sortie ; mais copier le PDF seul laisse la carte derrière et rien ne se produit. Mesuré ici : après avoir copié seulementmain.pdfhors debuild/,synctex views'est terminé en silence. - La carte est-elle périmée ? Avez-vous recompilé après avoir enregistré ? Si le décalage égale le nombre de lignes que vous venez d'insérer, c'est réglé. Faire tourner latexmk avec
-pvc, pour que chaque enregistrement recompile, supprime presque entièrement ce défaut. - Est-ce bien ce document que vous avez composé ? Compiler un fichier de chapitre isolément donne une carte qui décrit le PDF de ce chapitre, non celui du livre. Vérifiez que le réglage de fichier maître ou de document racine de votre éditeur pointe bien là où vous le croyez.
- La visionneuse gère-t-elle SyncTeX ? Adobe Acrobat/Reader ne sait pas faire de recherche inverse. Passez à Skim (macOS), SumatraPDF (Windows), ou Okular et zathura (Linux).
- Les jetons sont-ils les bons ? Confondre
%{line},%lineet%lest difficile à repérer précisément parce que le reste de la commande est juste. - Séparez le problème en ligne de commande. Lancez
synctex viewetsynctex editdirectement. S'ils répondent correctement, la carte est saine et le défaut est dans la poignée de main éditeur-visionneuse. Notez quesynctexrenvoie 0 même sans résultat : un script doit examiner la sortie, pas le code de retour.
Pour finir, la boucle qui fait de SyncTeX non pas un réglage mais une habitude de relecture. Lire le PDF, cliquer sur un mot qui gêne, atterrir dans la source, corriger, enregistrer, recompiler, puis revenir par la recherche avant à l'endroit que l'on vient de corriger. Quand cette boucle tourne rond, le temps passé à fouiller une longue source pour trouver où intervenir tombe à zéro. Le nom que Jérôme Laurens a donné à son travail — Synchronize TeXnology — sonne grandiose, mais ce qu'il apporte réellement tient à cela seul : ne plus jamais avoir à chercher.