MathJax 3 - Méthodes javascript avancées de rendu

Logo

Introduction

Comment rendre les équations MathJax après :

  • une entrée utilisateur, une injection de code tex brut
  • du contenu ajouté dynamiquement

2 fonctions simples à connaître avec MathJax 3 pour du contenu plus interactif : typesetPromise, tex2chtmlPromise.

Contenu ajouté dynamiquement après que Mathjax ait traité la page. typesetPromise, typeset

Lorsque du contenu est ajouté dynamiquement après que MathJax a traité une page, comment rendre les nouvelles équations insérées ?

Dans l’exemple ci-dessous :

<div class="m-exercice" data-title="Exercice 1">
  <div id="exercise-1">Calculer la dérivée de \(f(x) = \dfrac{x+2}{x+4} \)</div>
  <button id="display-solution-1">Solution</button>
</div>
Calculer la dérivée de \(f(x) = \dfrac{x+2}{x+4} \)

en cliquant sur le bouton "Solution", la fonction display_solution est appelée. Cette fonction extrait la solution d’un fichier externe avec la méthode fetch (SQLPAC - Javascript : Import de blocs HTML, fetch).

./include/solutions.inc
<div id="solution-1">
$$
 \begin{align*}
    \left (\dfrac{f}{g} \right)' = \dfrac{f'g -g'f}{g^2}
  
    \implies f'(x) &= \dfrac{(x+4) - (x+2)}{(x+4)^2}  \\
                   &= \dfrac{2}{(x+4)^2}
 \end{align*}
$$
</div>

Fonction display_solution :

b = document.getElementById('display-solution-1');
b.addEventListener('click', display_solution);

display_solution = ()=> {  
   fetch(`./include/solutions.inc`)
   .then ( (r) => { return r.text();  } )
   .then ( (s) => {
      p= new DOMParser();
            
      d = p.parseFromString(s,'text/html') ;
            
      se1 = d.getElementById('solution-1');
            
      b = document.getElementById('display-solution-1');
      b.removeEventListener('click', display_solution);
      b.addEventListener('click', () => { render_solution('solution-1'); } );
      b.innerHTML='Render';
            
      document.querySelector('main > div[data-title*="Exerci"][data-title$=" 1"]')
      .insertBefore(se1, b);  
    });
};

La solution est insérée, ok, mais dans le format brut, MathJax a déjà traité la page :

$$\begin{align*}
  \left (\dfrac{f}{g} \right)' = \dfrac{f'g -g'f}{g^2}
  \implies f'(x) &= \dfrac{(x+4) - (x+2)}{(x+4)^2} \\
                 &= \dfrac{2}{(x+4)^2}
\end{align*}$$

Pour exécuter MathJax sur du nouveau contenu ajouté, utiliser la fonction typesetPromise :

if (window.MathJax) {
  node = document.getElementById('solution-1');
  MathJax.typesetPromise([node]).then(() => {});
}

Si aucun nœud en particulier n’est donné en argument (tableau de nœuds), la page entière est à nouveau traitée par MathJax. Les nœuds déjà formatés sont évidemment écartés.

Dans l’exemple ci-dessus, typesetPromise est lancée dans la fonction render_solution, fonction attachée au bouton lorsque son libellé est changé en "Render" après que la solution soit chargée dans le DOM.

render_solution = (p)=> {
      if (window.MathJax) {
        node = document.getElementById(p);
        MathJax.typesetPromise([node]).then(() => {
             document.getElementById('display-solution-1').style='display:none;';
         });
      }
};

Évidemment dans une page concrète, la fonction typesetPromise est appelée juste après l’ajout du nouveau contenu. L’utilisateur n’a pas à cliquer sur un bouton "Render".

Une autre fonction est disponible pour composer du nouveau contenu Math : typeset

if (window.MathJax) {
  node = document.getElementById(p);
  MathJax.typeset([node]);
}

typeset, typesetPromise, quelle est la différence ? La première est utilisée quand aucune extension ou dépendance (require, mhchem, autres packages…) n’a à être auto-chargée pour traiter le bloc. Lorsque des extensions sont susceptibles d’être chargées dynamiquement, la fonction asynchrone typesetPromise est incontournable.

Si l’autonumérotation des équations Tex/AMS est active, pour réinitialiser/recalculer la numérotation des équations :

MathJax.texReset([start])

Le recalcul de la numérotation des équations n’est pas souvent nécessaire : les contenus ajoutés sont généralement des exercices etc…, pas des démonstrations de base.

Saisie utilisateur, code tex brut. tex2chtmlPromise

Dans un champ textarea d’un formulaire, l’utilisateur écrit le code Tex de l’équation et clique sur le bouton "Générer" pour obtenir le rendu de l’équation avec MathJax :

Le formulaire est simple et le rendu MathJax de l’équation est affiché dans un bloc div juste en dessous du formulaire :

          <form id="equation-input">
            <textarea name="tex-code">\int_{x_0}^{\infty} \frac{x^2}{2} dx</textarea>
            <button>Générer</button>
          </form>
          <div id="equation-result">
          </div>

Quand le DOM est chargé, la fonction display_equation est attachée à l’action du formulaire (submit) :

display_equation = ()=> {
   
};

f = document.getElementById('equation-input');
f.addEventListener('submit', (e)=> { e.preventDefault(); display_equation(); } );

Dans la fonction display_equation :

  • Le code Tex saisi est récupéré du formulaire.
  • La fonction MathJax.tex2chtmlPromise est appelée avec le code Tex en argument. Cette fonction retourne en promesse le nœud résultant MathJax.
  • Le nœud est alors ajouté dans la bloc div du résultat.
  • Pour d’"obscures" raisons, les fonctions MathJax.startup.document.clear() et MathJax.startup.document.updateDocument() doivent être appelées à la fin.
display_equation = ()=> {
   tex = document.querySelector('textarea[name="tex-code"]').value;
   
   d=document.getElementById('equation-result');
   
   MathJax.tex2chtmlPromise(tex).then((node) => {
        d.innerHTML='';
        d.append(node);
        MathJax.startup.document.clear();
        MathJax.startup.document.updateDocument();
    });       
};