MathJax, Macros et packages. Commandes Tex simplifiées

Logo

Introduction

Si vous n’êtes pas habitué aux documents Latex et avez commencé à publier des documents scientifiques uniquement en utilisant le langage HTML et la bibliothèque javascript MathJax, vous êtes rapidement confronté à des syntaxes lourdes et répétitives, exemple :

$$ \dfrac{dH}{dp} = \left( \dfrac{\partial H}{\partial T} \right)_p \dfrac{dT}{dp} + \left( \dfrac{\partial H}{\partial p} \right)_T $$
<div class="cmath">
  $$
      \dfrac{dH}{dp} = \left( \dfrac{\partial H}{\partial T} \right)_p \dfrac{dT}{dp}
                      + \left( \dfrac{\partial H}{\partial p} \right)_T
  $$
</div>

Dans l’exemple ci-dessus, on veut simplifier et automatiser les dérivées normales et partielles à l’aide de commandes de raccourci.

Autre exemple, nous voulons automatiser et normaliser l’écriture des unités et créer des raccourcis pour les constantes physiques utilisées dans plusieurs pages :

$$ \begin{align} g &= 9.81 \ \mathrm{m\!\cdot\!s\small{^{-2}}} \\ R &= 8.3145 \ \mathrm{J\!\cdot\!mol\small{^{-1}}\!\cdot\!K\small{^{-1}}} \end{align}$$
<div class="cmath">
  $$
    \begin{align} 
      g &= 9.81 \ \mathrm{m\!\cdot\!s\small{^{-2}}}   \\ 
      R &= 8.3145 \ \mathrm{J\!\cdot\!mol\small{^{-1}}\!\cdot\!K\small{^{-1}}}
    \end{align}
  $$
</div>

Pour y parvenir, la bibliothèque javascript MathJax supporte les commandes de macros Tex (\def, \newcommand, \let…).

Les configurations des chargements de MathJax v2 et v3 sont données en annexe. Dans cet article, le code Tex est encapsulé dans des balises HTML div, ins et span ayant la classe cmath. Les délimiteurs sont :

  • Mode bloc : $$ Tex code $$, \[ Tex code \]
  • Mode "Inline" : ## Tex code ##, \( Tex code \)
<div class="cmath"> $$  Tex code $$ </div>
<span class="cmath"> ... \( Tex code \) ... </span>

Nouveau sur MathJax et Tex ? Un guide pratique est disponible sur ce portail : SQLPAC | MathJax - Tex : guide pratique, aide-mémoire

Pré-requis

Les macros sont activées par défaut dans MathJax 2 et MathJax 3.

Avec MathJax 3, si une configuration minimaliste a été choisie, les macros doivent être activées dans l’objet window.MathJax en ajoutant les packages newcommand et configmacros. Le package configmacros n’est nécessaire que s’il est prévu ultérieurement de définir des macros dans l’objet window.MathJax object :

<script>
  window.MathJax = {
   tex: {
     …,
     packages: ['base','newcommand','configmacros','noerrors']
   },
   …,
   loader: {
     load: ['input/tex-base','output/chtml','[tex]/newcommand','[tex]/configmacros','[tex]/noerrors']
   }
</script>
<script src="[path_to_mathjax]/startup.js" id="MathJax-script">

Une macro basique

\( \def \Cg { 9.81 \ \mathrm{m\!\cdot\!s\small{^{-2}}} } \)

Initier une balise "cachée" pour stocker les définitions des macros en utilisant CSS, ici la balise ins avec la classe cmath :

ins[class*="cmath"] {
    position: fixed; top: 10px; height: 0px; width: 0px;
    color: transparent; background-color: transparent;
}

Ne pas utiliser la propriété CSS display: none, sinon la nouveauté "Lazy mode" de MathJax v3.2 ne fonctionnera pas, elle utilise l’objet IntersectionObserver.

Un raccourci Cg est créé pour la constante de gravitation ##g##. Dans une balise "cachée", avec \def :

<ins class="cmath">
  \( \def \Cg { 9.81 \ \mathrm{m\!\cdot\!s\small{^{-2}}} }   % Gravitational constant \)
</ins>

Dans le code Tex, appeler simplement \Cg :

<div class="cmath">$$ \Cg{} $$</div>
$$ \Cg{} $$

Au lieu d’utiliser une balise "cachée", les macros peuvent être définies dans l’objet window.MathJax avant d’appeler MathJax, les antislashes sont échappés :

MathJax v2
window.MathJax = {
 …,
 TeX : {
  Macros : {
   Cg : "9.81 \ \mathrm{m\!\cdot\!s\small{^{-2}}}"
  }
 }
}
MathJax v3
window.MathJax = {
 tex : {
  …,
  macros : {
   Cg : "9.81 \ \mathrm{m\!\cdot\!s\small{^{-2}}}"
  }
 }
 …
}

Cet exemple est très basique, les valeurs et les unités sont codées en dur. Bien entendu, les macros gèrent les arguments.

Macros avec arguments

Définissons 2 macros dvr et dvp respectivement pour les dérivées régulières et partielles : 2 arguments dans les macros.

\( \def \dvr #1#2 { \dfrac{d#1}{d#2} } \) \( \def \dvp #1#2 { \dfrac{\partial #1}{\partial #2} } \)
<ins class="cmath">
  \( \def \dvr #1#2 { \dfrac{d#1}{d#2} }   % Regular derivative \)
  \( \def \dvp #1#2 { \dfrac{\partial #1}{\partial #2} }   % Partial derivative \)
</ins>

L’équation de l’introduction est maintenant simplement écrite et même plus lisible à condition de bien connaître ses propres macros :

<div class="cmath">
  $$ 
    \dvr{H}{p} = \left( \dvp{H}{T} \right)_p \dvr{T}{p} +  \left( \dvp{H}{p} \right)_T
  $$
</div>
$$ \dvr{H}{p} = \left( \dvp{H}{T} \right)_p \dvr{T}{p} + \left( \dvp{H}{p} \right)_T $$

Le nombre maximum d’arguments dans la commande \def est de 9 : de #1 à #9.

Par défaut, en définissant \def \dvp #1#2#3 { … }, la syntaxe \dvp{ }{ }{ } est attendue dans le code Tex. Dans une syntaxe alternative, le premier argument est entre crochets tandis que les autres sont des caractères simples :

\( \def \drv[#1]#2 { \dfrac{d#1}{d#2} } % Derivative) \)
<ins class="cmath">
  \( \def \drv[#1]#2 { \dfrac{d#1}{d#2} }   % Derivative) \)
</ins>
<div class="cmath">$$  \drv[T]{p}  $$</div>
$$ \drv[T]{p} $$

La commande \def ne comprend pas les arguments optionnels. Pour les arguments optionnels, utiliser \newcommand à la place, mais le support reste partiel, seul un argument optionnel est autorisé : ce point est abordé dans les prochaines sections.

\def ne vérifie pas si la commande existe déjà, la définition est écrasée. Des contrôles d’existence sont possibles :

\( \ifx \dv \undefined \def \dvr #1#2 { \dfrac{d#1}{d#2} }   % Regular derivative \fi \)

Définir des macros avec \newcommand

Au lieu de la commande \def pour définir des macros, \newcommand peut être préférable. Quelles sont les différences ?

  • \newcommand échoue si la commande est déjà définie.
  • \newcommand fournit une syntaxe pratique pour spécifier le nombre d’arguments et un argument facultatif (un seul).
  • \newcommand définit les "long" commands, \def définit des commandes courtes à moins que \long\def ne soit utilisé.

Pour résumer la syntaxe de base de \newcommand :

\newcommand {\cmd}[number of arguments] { ...#1...#2... }

L’équation démo avec \newcommand est maintenant :

\( \newcommand {\dvr}[2] { \dfrac{d#1}{d#2} } % Regular derivative \) \( \newcommand {\dvp}[2] { \dfrac{\partial{#1}}{\partial{#2}} } % Partial derivative \)
<ins class="cmath">
   \( \newcommand {\dvr}[2] { \dfrac{d#1}{d#2} }   % Regular derivative \)
   \( \newcommand {\dvp}[2] { \dfrac{\partial{#1}}{\partial{#2}} }   % Partial derivative \)            
</ins>
<div class="cmath">
  $$ 
    \dvr{H}{p} = \left( \dvp{H}{T} \right)_p \dvr{T}{p} +  \left( \dvp{H}{p} \right)_T
  $$
</div>
$$ \dvr{H}{p} = \left( \dvp{H}{T} \right)_p \dvr{T}{p} + \left( \dvp{H}{p} \right)_T $$

Pour définir les macros dans l’objet window.MathJax :

MathJax v2
window.MathJax {
 …,
 TeX : {
  Macros : {
    dvr: ["{ \\dfrac{d#1}{d#2} }",2],
    dvp: ["{ \\dfrac{\\partial{#1}}{\\partial{#2}} }",2]
  }
 }
}
MathJax v3
window.MathJax {
 tex : {
  …,
  macros : {
    dvr: ["{ \\dfrac{d#1}{d#2} }",2],
    dvp: ["{ \\dfrac{\\partial{#1}}{\\partial{#2}} }",2]
  }
 }
 …
}

Argument optionnel

Avec \newcommand, on peut définir un argument optionnel, et un seul.

La syntaxe est alors :

\newcommand {\cmd}[number of arguments][default value optional argument] { ...#1...#2... }
  • L’argument optionnel est toujours #1.
  • Pour définir une valeur de l’argument facultatif lors de l’appel de la commande : \cmd[value]{}....
<ins class="cmath">
  \( \newcommand {\seq}[2][n] { S_{#1} = #2_0 + #2_1 + \cdots + #2_{#1}}   % Sequences \) 
</ins>
<div class="cmath">
  $$
     \seq{u}    \\
     \seq[n-1]{u} \\
     \seq[j]{a}
  $$
</div>
\( \newcommand {\seq}[2][n] { S_{#1} = #2_0 + #2_1 + \cdots + #2_{#1}} % Sequences \)
$$\displaylines { \seq{u} \\ \seq[n-1]{u} \\ \seq[j]{a} }$$

Combinaison de macros

Les macros peuvent être combinées et imbriquées. L’ordre de définition n’a pas d’importance, l’évaluation n’est pas réalisée à l’étape de définition.

Dans l’exemple ci-dessous, les grandes parenthèses gauche et droite sont automatisées à l’aide de macros :

\( \newcommand {\bdvr}[2] { \left( \dvr{#1}{#2} \right) } % Regular derivative Big parenthesis \) \( \newcommand {\bdvp}[2] { \left( \dvp{#1}{#2} \right) } % Partial derivative Big parenthesis \) \( \newcommand {\bp}[1] { \left( #1 \right) } % Big parenthesis \)
<ins class="cmath">
   \( \newcommand {\dvr}[2] { \dfrac{d#1}{d#2} }   % Regular derivative \)
   \( \newcommand {\dvp}[2] { \dfrac{\partial{#1}}{\partial{#2}} }   % Partial derivative \)
   \( \newcommand {\bdvr}[2] { \left( \dvr{#1}{#2} \right)    % Regular derivative Big parenthesis \)
   \( \newcommand {\bdvp}[2] { \left( \dvp{#1}{#2} \right) }   % Partial derivative Big parenthesis \)
   \( \newcommand {\bp}[1] { \left( #1 \right) }   % Big parenthesis \)            
</ins>
<div class="cmath">
  $$ 
    \dvr{H}{p} = \bdvp{H}{T}_p \dvr{T}{p} +  \bdvp{H}{p}_T
  $$
</div>

ou, en utilisant la macro bp :

<div class="cmath">
  $$ 
    \dvr{H}{p} = \bp{\dvp{H}{T}}_p \dvr{T}{p} +  \bp{\dvp{H}{p}}_T
  $$
</div>
$$ \dvr{H}{p} = \bp{\dvp{H}{T}}_p \dvr{T}{p} + \bp{\dvp{H}{p}}_T $$

Sachant comment combiner les macros, comment définir un argument optionnel, la mise en forme des unités et les définitions de constantes physiques deviennent plus simples : 3 macros

  • \unit : pour formater l’unité dans son ensemble (font, espace).
  • \per : pour formater puissance moins x, par défaut ##-1##.
  • \pt : pour formater le séparateur point dans l’unité.
\( \newcommand {\unit}[1] {\ \mathrm{#1}} % Unit format (font, space) \) \( \newcommand {\pt}{\!\cdot\!} % Unit dot separator formatting \) \( \newcommand {\per}[1][1]{\small{^{-#1}}} % Minus x power \)
<ins class="cmath">
  \( \newcommand {\unit}[1] {\ \mathrm{#1}}   % Unit format (font, space) \)
  \( \newcommand {\pt}{\!\cdot\!}   % Unit dot separator formatting \)
  \( \newcommand {\per}[1][1]{\small{^{-#1}}}   % Minus x power \)            
</ins>

Les 2 constantes ##R## et ##g## sont maintenant codées et affichées avec ces macros réutilisables :

<div class="cmath">
  $$
   \begin{align}
      R &= 8.3145 \unit{J \pt mol\per \pt K\per} \\
      g &= 9.81 \unit{m \pt s\per[2]}
   \end{align}
  $$ 
</div>
$$\begin{align} R &= 8.3145 \unit{J \pt mol\per \pt K\per} \\ g &= 9.81 \unit{m \pt s\per[2]} \end{align} $$

On peut maintenant décider de définir les constantes physiques appelées dans plusieurs pages aussi à l’aide de macros, de plus une macro uacc est créée pour l’unité d'accélération (##\unit{m \pt s\per[2]}##), unité utilisée dans de nombreuses pages :

\( \newcommand {\uacc}{ \unit{m \pt s\per[2]} } % Acceleration unit \) \( \newcommand {\CR}{ 8.3145 \unit{J \pt mol\per \pt K\per} } % Ideal gas constant \) \( \newcommand {\Cg}{ 9.81 \uacc} % Gravitational constant \)
<ins class="cmath">
   \( \newcommand {\uacc}{ \unit{m \pt s\per[2]} }   % Acceleration unit \)
   \( \newcommand {\CR}{ 8.3145 \unit{J \pt mol\per \pt K\per} }   % Ideal gas constant \)
   \( \newcommand {\Cg}{ 9.81 \uacc}     % Gravitational constant \)
</ins>
<div class="cmath">
  $$
   \begin{align}
      R &= \CR \\
      g &= \Cg
   \end{align}
  $$ 
</div>
$$\begin{align} R &= \CR \\ g &= \Cg \end{align} $$

Si les macros sont définies dans l’objet window.MathJax :

MathJax v2
window.MathJax = {
 …,
 TeX : {
  Macros : {
   unit: ["{ \\ \\mathrm{#1} }",1],
   pt:   ["{ \\!\\cdot\\! }"],
   per:  ["{ \\small{^{-#1}} }",1,"1"],
   uacc: ["{ \\unit{m \\pt s\\per[2]} }"],
   CR:   ["{ 8.3145 \\unit{J \\pt mol\\per \\pt K\\per} }"],
   Cg:   ["{ 9.81 \\uacc }"]    
  }
 }
}
MathJax v3
window.MathJax = {
 tex : {
  …,
  macros : {
   unit: ["{ \\ \\mathrm{#1} }",1],
   pt:   ["{ \\!\\cdot\\! }"],
   per:  ["{ \\small{^{-#1}} }",1,"1"],
   uacc: ["{ \\unit{m \\pt s\\per[2]} }"],
   CR:   ["{ 8.3145 \\unit{J \\pt mol\\per \\pt K\\per} }"],
   Cg:   ["{ 9.81 \\uacc }"]    
  }
 }
 …
}

Remarque: dans la macro per, le troisième argument est la valeur par défaut de l’argument optionnel de la macro et il doit être une chaîne de caractères (string). Lorsque les valeurs par défaut ne sont pas typées en string (integer, float, etc.), les définitions de macros échouent avec MathJax.

Construction et chargement de librairies de macros

Dans les sections précédentes, les macros sont définies dans la page HTML ou dans l’objet window.MathJax :

HTML
<ins class="cmath">
   \( \newcommand {\dvr}[2] { \dfrac{d#1}{d#2} } \)
   \( \newcommand {\dvp}[2] { \dfrac{\partial{#1}}{\partial{#2}} } \)
</ins>
window.MathJax - MathJax v3
window.MathJax {
 tex : {
  macros : {
    dvr: ["{ \\dfrac{d#1}{d#2} }",2],
    dvp: ["{ \\dfrac{\\partial{#1}}{\\partial{#2}} }",2]
  }
 }
}

Dans l’option HTML, les commandes sont codées en dur dans chaque page qui a besoin des macros. Avec l’option window.MathJax, selon comment MathJax est appelé (appel dans la page ou avec une fonction globale) : soit les macros sont codées en dur dans la page, soit toutes les macros sont chargées même celles qui ne sont pas nécessaires.

Une solution plus robuste consiste à séparer les macros par fichier et par fonctionnalités, une sorte de bibliothèque : par exemple, 1 fichier pour les macros qui gèrent les unités, 1 fichier pour les macros qui gèrent les dérivées/intégrales, etc. Dans la page, seules les librairies nécessaires sont chargées. Comment y parvenir ?

Avec PHP, c’est simple mais cela nécessite un serveur Web : inclure simplement les fichiers nécessaires. Les fichiers contiendront le code HTML qui encapsule les définitions des macros.

<body>
  <?php
  require_once('./lib/macros-units.inc');
  require_once('./lib/macros-derivatives.inc');
?>
  …
</body>
./lib/macros-units.inc
<ins class="cmath">
  \( \newcommand {\unit}[1] {\ \mathrm{#1}}  % Unit format (font, etc.) \)
  \( \newcommand {\pt}{\!\cdot\!}  % Unit dot separator formatting \)
  \( \newcommand {\per}[1][1]{\small{^{-#1}}}   % Minus x power \)
  \( \newcommand {\uacc}{ \unit{m \pt s\per[2]} }  % Acceleration unit \)
  \( \newcommand {\CR}{ 8.3145 \unit{J \pt mol\per \pt K\per} }   % Ideal gas constant R \)
  \( \newcommand {\Cg}{ 9.81 \uacc} }   % Gravitational constant \)
  …
</ins>

Avec Javascript, un tout petit peu plus délicat :

Les définitions des macros doivent être chargées juste après que window.MathJax ne soit instancié et avant que MathJax ne démarre. Utiliser l’attribut defer pour garantir l’ordre de séquence, le mode async n’étant alors plus approprié.
<head>
 <script>
    window.MathJax = { tex: …, loader: …, … }
 </script>

 <script src="./lib/macros-units.js" defer></script>
 <script src="./lib/macros-derivatives.js" defer></script>
 
 <script src="[path_mathjax]/tex-chtml.js" defer></script>
</head>
./lib/macros-units.js (MathJax v3)
macros = {
  unit: ["{ \\ \\mathrm{#1} }",1],
  pt:   ["{ \\!\\cdot\\! }"],
  per:  ["{ \\small{^{-#1}} }",1,"1"],
  uacc: ["{ \\unit{m \\pt s\\per[2]} }"],
  CR:   ["{ 8.3145 \\unit{J \\pt mol\\per \\pt K\\per} }"],
  Cg:   ["{ 9.81 \\uacc }"]
};

window.MathJax = window.MathJax || {};

m = window.MathJax;

m.tex = m.tex || {};
m.tex.macros = m.tex.macros || {};

m.tex.macros = {...m.tex.macros,...macros};
Pour MathJax v2, remplacer m.tex par m.Tex et m.tex.macros par m.Tex.Macros.

Les packages MathJax v3

Plus flexible que la version 2, MathJax v3 fournit de nouveaux packages contenant des macros très utiles : packages physics, braket. Plus de packages à venir dans les prochaines versions v3. Consulter régulièrement les prochaines versions, un nouveau package peut correspondre à ses besoins : pas besoin de réinventer la roue.

Les macros créées ci-dessus pour les dérivées normales et partielles ont des équivalents analogues dans le package physics inclus dans MathJax 3.

Le package physics n’est pas chargé par défaut, pour le charger (la sortie est allégée par souci de concision) :

window.MathJax = {
  tex: { …, packages: {'[+]': ['noerrors','physics']} },
  …,
  loader: { load: ['[tex]/noerrors','[tex]/physics',…] }        
}

L’équation démo de cet article est alors la suivante en utilisant le package physics : (doc: CTAN - The Physics package) :

<div class="cmath">
  $$
    \dv{H}{p} = \left( \pdv{H}{T} \right)_p \dv{T}{p} + \left( \pdv{H}{p} \right)_T
  $$
</div>

Les dérivées normales et partielles sont respectivement gérées avec les macros \dv et \pdv dans ce package.

Et pourquoi ne pas appliquer la macro bp créée précédemment pour gérer les parenthèses larges :-) ?

<div class="cmath">
  $$
    \dv{H}{p} = \bp { \pdv{H}{T} }_p \dv{T}{p} + \bp { \pdv{H}{p} }_T
  $$
</div>
$$ \dv{H}{p} = \bp { \pdv{H}{T} }_p \dv{T}{p} + \bp { \pdv{H}{p} }_T $$

Un autre exemple, le package mhchem pour les équations de chimie avec sa macro \ce :

<div class="cmath"> $$ \ce{SO4^2- + Ba^2+ -> BaSO4 v} $$ </div>
$$ \ce{SO4^2- + Ba^2+ -> BaSO4 v} $$

Annexe - Chargement de MathJax, configuration de base

MathJax v2
<head>
 <script>
    window.MathJax = {
      tex2jax : {
        inlineMath : [ ['##','##'], ["\\(","\\)"] ],
        displayMath: [ ['$$','$$'], ["\\[","\\]"] ],
        processEscapes: true,
        processClass: 'cmath|dmath'
      },
      displayAlign: "left"
    };
 </script>
 <script async="true" 
    src="[path_mathax]/MathJax.js?config=TeX-AMS_CHTML">
 </script>
</head>
MathJax v3.2
<head>
 <script>
    window.MathJax = {
      tex: {
        inlineMath: [ ['##','##'], ["\\(","\\)"] ],
        displayMath: [ ['$$','$$'], ["\\[","\\]"] ],
        processEscapes: true,
        packages: {'[+]': ['noerrors']}
      },
      chtml: {
        displayAlign: "left"
      },
      options: {
        processHtmlClass: 'cmath|dmath',
        ignoreHtmlClass: 'tex2jax_ignore'
      },
      loader: {
        load: ['input/tex','output/chtml','[tex]/noerrors','ui/lazy']
      }
    };
 </script>
 <script async="true"
        src="[path_mathjax]/startup.js"></script>
</head>