Programmation en Lua dans LaTeX : introduction

par Damien Mégy   latex lua lualatex maths

Table des matières

Introduction

Comment mettre du texte en Comic Sans MS avec Lualatex ?

Short answer : don't

Le changement de la police de caractères (et la gestion des caractères unicode) est souvent la principale raison pour laquelle les gens s'intéressent à Lualatex.

Ce document ne parle absolument pas de cela. Il est centré sur l'utilisation du langage Lua à l'intérieur de documents LuaLaTex, et des nombreuses utilisations que peuvent en avoir des étudiants ou enseignants en mathématiques, afin d'éviter de programmer en Tex, ce qui est interdit par les conventions de Genève.

Voici néanmoins un exemple minimal de changement de police en Lualatex :

\documentclass{article}
\usepackage{fontspec} % remplace fontenc.
\begin{document}
\setmainfont{Comic Sans MS}
Quelle indignité...
\setmainfont{Papyrus}
Je vous demande de vous arrêter !
\end{document}

Quels packages mettre dans un préambule lualatex ?

il y a de nombreuses ressources disponibles en ligne. Voici néanmoins un exemple minimaliste de liste de packages à charger pour écrire des maths en français avec lualatex:

\usepackage{fontspec} % remplace fontenc.
\usepackage{polyglossia} % remplace babel
\usepackage{unicode-math} % remplace mathtools,
% qui lui-même remplaçait amsmath et amssymb

Le langage de programmation Lua

LuaLatex ne sert pas qu'à utiliser des polices de caractères immondes dans ses documents. Il permet d'intégrer du code Lua à son document.

Lua (https://lua.org https://fr.wikipedia.org/wiki/Lua) est un langage de programmation inventé en 1993 par John Lua. Il est très léger, performant, facile à embarquer dans d'autres logiciels. Sa syntaxe ressemble à celle de Python, sauf qu'il ne vous embête pas avec les maudits niveaux d'indentation : en effet, Lua utilise simplement le mot-clef end pour fermer les blocs d'instructions. Ceci est donc du code Lua valide:

	a=1 for k=0,10 do
a = a+1
	end b=a/2
assert(b==6)

Non pas que ce soit conseillé, évidemment.

Pour utiliser Lua avec Luatex, il n'y a rien à installer, tout est déjà installé par TexLive sur linux/mac ou Miktex sur Windows. (Inutile, donc, d'installer Lua à partir de l'adresse officielle. Si on le fait, il y aura de toute façon deux installations diférentes sur le système, celle pour lualatex, et l'installation normale.)

Ce document n'est PAS un cours de Lua !

Cependant il est conçu pour permettre d'apprendre les bases de Lua sur le tas, ainsi que pour servir d'aide-mémoire Lua raisonnable (à l'auteur du billet).

Exécution de code Lua dans un document TeX lors de la compilation avec la commande \directlua

On execute du code Lua dans un document Latex à l'aide de la commande \directlua. Par exemple, écrire \directlua{a= 2^5 + 127}} affecte la valeur 159 à la variable a.

Rien ne s'affiche dans le document compilé car on n'a pas précisé qu'il fallait afficher quoi que ce soit.

Importation et exécution de fichiers Lua externes

Si l'on a du code Lua dans un fichier monfichier.lua, on peut l'importer et l'executer de la façon suivante : \directlua{dofile("monfichier.lua").

Ceci permet de séparer le code Lua du code Latex et peut éviter bien des ennuis liés à l'interaction entre Lua et Latex.

Sortie de texte vers Latex avec tex.print() et tex.sprint()

Pour que Lua affiche quelque chose dans le code Latex, on utilise tex.print() ou sa variante tex.sprint() qui n'imprime pas de saut de ligne à la fin.

La chaîne de caractères imprimée est ensuite traitée par le moteur Latex.

(Il est également possible d'écrire dans la console, ou dans les logs, ce qui peut parfois être utile.)

Considérons le nombre $6$.
Son double vaut $\directlua{tex.sprint(6*2)}$.
Son triple vaut $\directlua{tex.sprint(6*3)}$.

Nous avons accès à un certain nombre de libraries standard de Lua, dont math. Les calculs peuvent se faire directement à l'intérieur du mode mathématique.

L'exponentielle de $6$ vaut $\directlua{tex.sprint(math.exp(6))}$.
Son sinus (radians!) vaut $\directlua{tex.sprint(math.sin(6))}$.
La racine carrée de $1000$, arrondie à l'entier inférieur, est
$\directlua{tex.sprint(math.floor(math.sqrt(1000)))}$.

Nous avons aussi accès aux instructions conditionnelles et de répétition, par exemple :

Les dix premiers nombres impairs positifs sont :
\directlua{for i=0,9 do tex.sprint(" ", (2*i+1)) end}.

La factorielle de $5$ est :
\directlua{local result=1; for k=1,5 do result = result * k end tex.sprint(result)}.

La double factorielle de $5$, notée $5!!$, est :
\directlua{local n=5; local result=1; while n>0 do  result = result * n; n = n-2 end tex.sprint(result)}.

La puissance de $2$ immédiatement supérieure à $100$ est :
\directlua{local n=1; while n <= 100 do n = n * 2 end tex.sprint(n)}.

On peut aller à la ligne dans \directlua:

La somme des entiers consécutifs jusqu'à $10$ vaut
$\directlua{
local somme=0
for i=1,10 do
somme = somme + i
end
tex.sprint(somme)
}$

Cependant, ceci provoque parfois des erreurs un peu inattendues, voir les exemples dans la section suivante.

Échappement des caractères

\directlua{tex.print("\emph{Test raté !}")} ne marche pas, mais c'est assez logique, ça ne marcherait a priori pas non plus dans aucun autre langage de programmation : il faut « échapper » l'antislash, qui a un rôle spécial dans une chaîne de caractère (il sert à encoder des caractères spéciaux).

Pour cela, on le double, donc on écrit:

\directlua{tex.print("\\emph{Test réussi !}")}

Gestion des erreurs

Un exemple

Considérons le code latex suivant :

\directlua{a=1}
\directlua{b = c = 1}
\directlua{d=3}
La variable a vaut \directlua{tex.sprint(a)},
la variable b vaut \directlua{tex.sprint(b)},
la variable c vaut \directlua{tex.sprint(c)},
la variable d vaut \directlua{tex.sprint(d)}.

Si l'on compile le document, la compilation n'échoue pas, un document pdf est produit. Dans ce pdf, on voit ceci : « La variable a vaut 1, la variable b vaut , la variable c vaut , la variable d vaut 3.»

Explication

  • La première instruction est valide et la valeur 1 est affectée à a
  • La deuxième instruction b=c=1 n'est pas du Lua valide. À partir de l'instruction invalide, tout le reste du code Lua dans le bloc \directlua{} est ignoré, un message d'erreur est produit dans la console:
[2] [3][\directlua]:1: unexpected symbol near '='.
l.129 \directlua{b = c = 1}
The lua interpreter ran into a problem, so the
remainder of this lua chunk will be ignored.

Ensuite, la compilation LaTeX continue.

  • Le troisième bloc directlua est valide, la valeur 3 est affectée à la variable d.
  • Lua imprime ensuite les variables dans Latex, ça ne le gêne pas que des variables n'existent pas, elles valent nil, il n'y a pas d'erreur Lua. ENfin, LaTeX met en page le texte.

Conclusion

Pas d'erreur de compilation, pdf produit avec contenu qui n'est pas forcément celui attendu, une seule erreur Lua qu'il faut penser à aller chercher dans la console.

Passage de valeurs de TeX vers Lua, premières macros

Avant d'être interprété comme du code Lua, l'argument de la commande est analysé et « développé » par Tex. On peut donc faire des choses comme ceci :

\def\exposant{6}
10 puissance \exposant{} vaut :
\directlua{tex.print(math.tointeger(10^\exposant))}.

\newcommand{\luaprint}[1]{\directlua{tex.sprint(#1)}}

Puis : $2\times 3^4 = \luaprint{2*3^4}$.

\newcommand{\roundsqrt}[1]{\directlua{tex.sprint(math.round(math.sqrt(#1)))}}
L'entier le plus proche de $\sqrt{26}$ est $\roundsqrt{26}$.
L'entier le plus proche de $\sqrt{35}$ est $\roundsqrt{35}$.

Point d'étape

Jusqu'ici , on voit que Lua a un fort potentiel pour remplacer certaines macros pénibles à écrire en Latex ou qui nécessitaient énormément de packages ad-hoc : pgffor ou multido, ifthenelse, etc etc, avec à chaque fois une syntaxe à retenir et la nécessité de se tenir au courant pour savoir si tel ou tel package n'a pas été remplacé par tel autre. Maintenant, on peut tout simplement coder en Lua.

Pouvoir passer des valeurs de LaTeX vers Lua peut être bien pratique ou au contraire provoquer des problèmes difficiles à régler, voir plus bas.

Problèmes lié à l'interprétation préalable par le moteur Latex

TeX qui mange des espaces, qui traite les sauts de ligne comme des espaces etc

Considérons le code latex suivant :

\def\macro{Hello}
\directlua{tex.sprint("\macro World!")}

Il produit «HelloWorld!», sans espace.

Explication : l'espace entre les deux mots a disparu car la macro latex mange l'espace qui suit. L'interpréteur Lua voit donc \code{tex.sprint("HelloWorld!")} et renvoie HelloWorld! au moteur LaTeX.

Pire, le code suivant produit l'erreur un peu étonnante malformed number near '1b' :

\def\macro{1}
\directlua{
a = \macro
b = 2
}

La raison est que Tex ignore les sauts de ligne et les espaces avant et après, donc l'interpréteur Lua voit a=1b=2, ce qui n'est pas du code Lua valide. L'interpréteur Lua affiche une erreur dans la console et ignore tout ce qui suit dans le bloc de code \directlua. La compilation LaTeX continue ensuite.

Problèmes avec les commentaires Lua

Le code suivant compile, produit un pdf, ne produit pas d'erreurs LaTeX ni Lua, mais n'affiche rien du tout dans le PDF:

\directlua{
a = 1 -- un commentaire
tex.sprint(a)
}

La raison est que Tex ignore les sauts de ligne, ce qui fait que Lua voit uniquement ceci :

a = 1 -- un commentaire tex.sprint(a)

et n'affiche donc pas la variable.

Conclusion : on ne peut pas utiliser de commentaires Lua dans un bloc directlua. (Sauf à la fin de la dernière ligne...)

Commentaires et caractères spéciaux Latex

Le code LaTeX suivant ne marche pas : 1729 est congru à \directlua{tex.print(1729%57)} modulo 57.

Cette fois, le problème est le caractère % qui est interprété par Tex avant d'être interprété comme du Lua, et provoque la neutralisation de tout le reste de la ligne.

Une conséquence un peu crispante est que le code suivant est ... correct :

\directlua{
a = 1 % un commentaire
tex.sprint(a)
}

Bien sûr, il est déconseillé de faire cela car le bloc de texte Lua n'est plus du Lua valide : il le devient uniquement après interprétation par le moteur latex.

Pour gérer ce genre de problèmes, la meilleure solution est d'utiliser un package spécialement créé pour cela :

Solution : utilisation du package «luacode»

Ceci peut être résolu en utilisant le package luacode qui fournit plusieurs environnements, dont \begin{luacode*}. L'étoile est importante !

Le code latex suivant fonctionne correctement :

1729 est congru à
\begin{luacode*}
tex.print(1729 % 57)
-- marche !
\end{luacode*}
 modulo 57.

Conclusion

Cet article a présenté l'intégration de code Lua dans un document LaTeX (compilé avec le moteur lualatex), soit avec la fonction de base \directlua{}, soit avec l'environnement \begin{luacode*} fourni par le package luacode, ainsi que les principaux pièges à éviter. Dans un prochain article, nous passerons en revue une longue liste d'exemples d'utilisation des fonctionnalités de Lua dans un document LaTeX, qui peuvent être particulièrement utiles pour l'écriture de documents scientifiques ou pédagogiques.