Search code examples
node.jsserver-side-renderingmathjax

Typesetting MathJax in a string of HTML server-side


I was pretty easily able to get npm's mathjax-full working, for parsing TeX to CommonHTML:

const MathJax = await require("mathjax-full").init({
    options: {
        enableAssistiveMml: true
    },
    loader: {
        source: {},
        load: ["adaptors/liteDOM", "tex-chtml"]
    },
    tex: {
        packages: ["base", "ams", "newcommand"]
    },
    chtml: {
        fontURL: "https://cdn.jsdelivr.net/npm/mathjax@3/es5/output/chtml/fonts/woff-v2"
    },
    startup: {
        typeset: false
    }
});

MathJax.tex2chtmlPromise("x^2-2x+1", {
    display: true,
    em: 16,
    ex: 8
}).then((node) => {
    var adaptor = MathJax.startup.adaptor;

    console.log(adaptor.outerHTML(node));
});

However, unlike typeset/typesetPromise, rather than a DOM node or string of HTML, this works with the TeX directly. Of course I could parse the page myself, finding MathJax delimiters (outside of code blocks) and passing the contents to tex2chtmlPromise, but this would have the potential of bugs or differences in behavior between a client-side preview using MathJax's typeset and the server-side rendered version.

I've looked around in the internals of the liteDOM adaptor quite a bit, but can't seem to find any way of setting the innerHTML of its body, if that would be the correct approach (which would allow me to just use typesetPromise normally).

Is there a recommended way to do what I'm trying to do, namely, take some HTML, and typeset it with MathJax without parsing for the delimiters myself?


Solution

  • The MathJax node demos repository includes examples of how to process an HTML page that should give you what you need. There are several different ways to load and call mathJax, so there are separate directories that illustrate each of them. You are using the "simple" approach, but may also want to look at the "component" and "direct" approaches. Look for files that end in -page.

    The main idea for the simple case is to use the document option in the startup section of your MathJax configuration. This allows you to provide a serialized HTML string to be used as the document to be processed. So in your case, you could change

        startup: {
            typeset: false
        }
    

    to

        startup: {
            typeset: false,
            document: html
        }
    

    where html is the HTML string to be processed. E.g.,

    html = `
    <!DOCTYPE html>
    <html>
    <head>
    <title>My Document</title>
    </head>
    <body>
    <p>
    Some math: \(E = mc^2\).
    </p>
    </body>
    </html>
    `;
    

    If you want the same invocation of your node app to be able to process multiple pages, then you will need to use the "direct" approach, as illustrated in direct/tex2chtml-page, and do these lines for each html file you want to process. You can reuse the same output jax, but should create a new InputJax and MathDocument for each page you process.