Linking to MathJax Directly in Node — MathJax 4.0 documentation (original) (raw)

The previous sections use the MathJax components framework to manage most of the details of setting up and using MathJax. That framework uses a global MathJaxvariable to configure MathJax, and to store the functions for typesetting and converting math in web pages.

It is possible, however, to bypass the components layer and link to the MathJax modules directly. This provides the lowest-level access to the MathJax code, and while it is more complicated than using components, it gives you the greatest control over the details of MathJax. In this approach, you trade off ease of configuration and use for more direct and granual command over MathJax’s operations.

When you import MathJax code directly, the MathJax loader and startup modules are not used, and since those underlie the dynamic loading of MathJax code on the fly, that ability is more restricted in this setting. Some modules rely on that ability, however; in particular therequire and autoload TeX extensions, and the MathJax menu code, all depend on the component infrastructure, and so can not be used when importing the MathJax modules directly.

With the direct approach, you must load all the MathJax code that you will be using explicitly, and you will need to instantiate and configure the MathJax objects (like the input and output jax, and the MathDocument object) by hand, as the startup module that performs those duties within the components framework, along with the MathJax configuration variable, are not used when you load MathJax modules directly.

Note

With a little care, it is possible to mix components and direct loading of modules. This is illustrated in the Using Components Synchronouslysection. Although that shows how to use MathJax synchronously, those techniques can be used for asynchronous processing as well. This is also illustrated in the Mixing Components and Direct Linkingexample below.

Finally, MathJax v4 introduced new fonts that include many more characters than the original MathJax TeX fonts, and these have been broken into smaller pieces so that, in web pages, your readers don’t have to download the entire font and its data for characters that may never be used. Font ranges are downloaded dynamically when needed, but when you use direct access to MathJax, rather than the components framework, that dynamic loading takes a bit more work. You either have to preload the ranges that you will need, or make provisions for loading the ranges yourself when they are needed. Both these approaches are illustrated in the examples below.


The Basics of Linking Directly to MathJax

First, get a copy of the MathJax code library. Here, we will assume you have used npm or pnpm to install the@mathjax/src@4 package. You will need to use @mathjax/src, not just mathjax, since the latter only includes the bundled component files, not the individual MathJax modules that you will be importing directly.

The MathJax source code for v3 and earlier consisted of ES5 javascript in CommonJS modules. As of version 4, MathJax is compiled into both ES5 CommonJS modules and the more modern ES6 Modules. These are stored in the cjs and mjs directories, respectively, of the@mathjax/src node package. The MathJax package.json file is set up so that references to @mathjax/src/js will access thecjs directory when used in a require() command, and themjs directory when used in an import command. The examples below will use import commands, but you can change them to the corresponding require() commands without altering the file paths.

The original source code for MathJax is in Typescript, which is a form of javascript that has additional information about the types of data stored in variables, used for function arguments and return values, and so on. Those Typescript files are compiled into the cjs andmjs directories when MathJax is built. The ts directory holds the Typescript files, and those contain comments describing the functions and objects they include. You can refer to them to see what can be imported from each of the compiled files in the cjsand mjs directories.

Most of the examples below begin with import commands like the following:

import {mathjax} from '@mathjax/src/js/mathjax.js'; import {TeX} from '@mathjax/src/js/input/tex.js'; import {CHTML} from '@mathjax/src/js/output/chtml.js'; import {liteAdaptor} from '@mathjax/src/js/adaptors/liteAdaptor.js'; import {RegisterHTMLHandler} from '@mathjax/src/js/handlers/html.js'; import '@mathjax/src/js/util/asyncLoad/esm.js';

These load the objects and classes needed to use MathJax’s internal structures.

The first obtains the mathjax object, which contains the version number, a function for creating aMathDocument() instance that handles the typesetting and conversion for a document, a function for handling asynchronous actions by MathJax, and a function for loading external files dynamically, among other things.

The next two lines load the class constructors for the input and output jax. The fourth loads the LiteDOM adaptor that implements a simple DOM replacement for use within node applications (since node doesn’t have a built-in DOM like browsers do). See theThe DOM Adaptor section for more details about the DOM adaptors available in MathJax.

The fifth line loads a function that registers the code for handling HTML documents, which is currently the only format MathJax understands (but we hope to extend this to other formats like Markdown in the future).

The last line tells MathJax to use import() commands to load external files, when needed. In a CommonJS module, you would userequire() to load js/util/asyncLoad/node.js rather thanjs/util/asyncLoad/esm.js, and would replace all the importcommands by corresponding require() calls.

Most of the examples also load a number of TeX extensions:

import '@mathjax/src/js/input/tex/base/BaseConfiguration.js'; import '@mathjax/src/js/input/tex/ams/AmsConfiguration.js'; import '@mathjax/src/js/input/tex/newcommand/NewcommandConfiguration.js'; import '@mathjax/src/js/input/tex/noundefined/NoUndefinedConfiguration.js';

Here, we load the base, ams, newcommand, andnoundefined extensions. The names of these packages are then added to the packages array of the options passed to the TeX input jax constructor when it is instantiated later on. The MathJax TeX extensions are in subdirectories of the ts/input/texdirectory, so you can look there for the configuration files that you can load. See The TeX/LaTeX Extension List for more about the TeX extensions. Remember, you must load all the extensions explicitly that you plan to use, and the autoload andrequire extensions can’t be used, as they rely on the component framework.

Since node applications don’t have a fully functional DOM (the LiteDOM is very minimal), MathJax can’t determine the font metrics like the em- and ex-sizes, or the font in use, or the width of container elements, as it can in a browser with a full DOM. Thus the next lines define default values for these:

const EM = 16; // size of an em in pixels const EX = 8; // size of an ex in pixels const WIDTH = 80 * EM; // width of container for linebreaking

Then the examples create a DOM adaptor and inform MathJax that is should recognize HTML documents:

const adaptor = liteAdaptor({fontSize: EM}); RegisterHTMLHandler(adaptor);

Next, the examples create the input and output jax:

const tex = new TeX({ packages: ['base', 'ams', 'newcommand', 'noundefined'], formatError(jax, err) {console.error(err.message); process.exit(1)}, // // Other TeX configuration goes here // }); const chtml = new CHTML({ fontURL: 'https://cdn.jsdelivr.net/npm/@mathjax/mathjax-newcm-font/chtml/woff2', // // Any output options go here // });

Here, we create a TeX input jax instance and configure thepackages array to include the packages that we loaded above. We also include a formatError() function that will report the error and then stop the program from running. Since we are only going to process one equation in these examples, that makes it easy to tell when the TeX input is faulty.

Next, we create a CommomHTML (CHTML) output jax, and configure the URL where the font files will be found. If you are hosting your own copy of MathJax, you should replace this URL with the actual URL of where you have placed the font files on your server.

You can include any additional configuration options for the input and output jax, as well, in the indicated locations. For example, if you wanted to predefine some TeX macros, you could load theconfigmacros extension, add it to the packageslist, and include a macros block to the options for the TeX input jax that defines the needed macros.

Similar commands could be used to create an MathML or AsciiMath input jax, or an SVG output jax.

Once we have the input and output jax, we create theMathDocument() instance:

const html = mathjax.document('', { InputJax: tex, OutputJax: chtml, // // Other document options go here // });

This specifies the input and output jax, and any other document options that you need to set. The content of the document is blank due to the empty string as the first argument tomathjax.document(). Alternatively, you can pass a serialized HTML string, or an actual DOM object or document fragment to create a MathDocument to handle the given content.

After the document is created, we can use it to convert TeX expressions into CHTML output. The heart of this process is a command like the following:

const node = html.convert(process.argv[2] || '', { display: true, em: EM, ex: EX, containerWidth: WIDTH });

This takes the command-line argument from process.argv[2]and treats it as a TeX expression, converting it to CHTML output. The display property indicates that it should be typeset as a displayed equation rather than in-line (though you could make that a command-line argument as well), and uses the em- and ex-sizes and container width values defined earlier.

mathDocument.convert(_math_[, _options_])

Arguments:

Returns:

The DOM node containing the typeset version of the mathematics.

You could provide the display, ex, em, and other values from command-line arguments, for example, though we use the defaults in these examples.

Note that there is also

mathDocument.convertPromise(_math_[, _options_])

taking the same arguments as mathDocument.convert() above, and returning a promise that resolves when the conversion is complete, passing the generated node as the argument to its then()method. This function handles any asynchronous file loads, like those needed for dynamic font ranges. Some of the examples below use this function for that purpose.

Finally, we output a JSON object that contains the serialized HTML output along with the CSS stylesheet contents for the expression (this will not be a minimal stylesheet, as, for example, it includes all the web-font definitions, even if those fonts aren’t used).

// // Generate a JSON object with the CHTML output and needed CSS // console.log(JSON.stringify({ math: adaptor.outerHTML(node), css: adaptor.cssText(chtml.styleSheet(html)) }));

Some examples generate other output (for instance, MathML code, or a complete HTML page).


The Examples

In the examples below, the highlighted lines are the ones that differ from the explanations above, or from previous examples.


Converting TeX to MathML

This example combines the ideas from the previous sections into a complete example. In this case, it converts a LaTeX expression into a corresponding MathML one.

tex2mml.mjs

1// 2// Load the modules needed for MathJax 3// 4import {mathjax} from '@mathjax/src/js/mathjax.js'; 5import {TeX} from '@mathjax/src/js/input/tex.js'; 6import {liteAdaptor} from '@mathjax/src/js/adaptors/liteAdaptor.js'; 7import {RegisterHTMLHandler} from '@mathjax/src/js/handlers/html.js'; 8import {SerializedMmlVisitor} from '@mathjax/src/js/core/MmlTree/SerializedMmlVisitor.js'; 9import {STATE} from '@mathjax/src/js/core/MathItem.js'; 10 11// 12// Import the needed TeX packages 13// 14import '@mathjax/src/js/input/tex/base/BaseConfiguration.js'; 15import '@mathjax/src/js/input/tex/ams/AmsConfiguration.js'; 16import '@mathjax/src/js/input/tex/newcommand/NewcommandConfiguration.js'; 17import '@mathjax/src/js/input/tex/noundefined/NoUndefinedConfiguration.js'; 18 19// 20// The em and ex sizes and container width to use during the conversion 21// 22const EM = 16; // size of an em in pixels 23const EX = 8; // size of an ex in pixels 24const WIDTH = 80 * EM; // width of container for linebreaking 25 26// 27// Create DOM adaptor and register it for HTML documents 28// 29const adaptor = liteAdaptor({fontSize: EM}); 30RegisterHTMLHandler(adaptor); 31 32// 33// Create input jax and a (blank) document using it 34// 35const tex = new TeX({ 36 packages: ['base', 'ams', 'newcommand', 'noundefined'], 37 formatError(jax, err) {console.error(err.message); process.exit(1)}, 38 // 39 // Other TeX configuration goes here 40 // 41}); 42const html = mathjax.document('', { 43 InputJax: tex, 44 // 45 // Other document options go here 46 // 47}); 48 49// 50// Create a MathML serializer 51// 52const visitor = new SerializedMmlVisitor(); 53const toMathML = (node => visitor.visitTree(node, html)); 54 55// 56// Convert the math from the command line 57// 58const mml = html.convert(process.argv[2] || '', { 59 display: true, 60 em: EM, 61 ex: EX, 62 containerWidth: WIDTH, 63 end: STATE.CONVERT // stop after conversion to MathML 64}); 65 66// 67// Output the resulting MathML 68// 69console.log(toMathML(mml));

Here, line 9 loads the STATE variable that is used in line 63 to stop the conversion process after the LaTeX is compiled into the internal MathML format.

Lines 49 through 53 create a MathML serializer using theSerializedMmlVisitor() loaded in line 8. This is used in line 69 to convert the internal MathML to a string form for output.

Running this command as

node tex2mml.mjs '\sqrt{1-x^2}'

produces

1 − x 2

Note that the MathML includes data-latex attributes indicating the LaTeX that produced each node. If you don’t want those attributes, you can add

66// 67// Remove data-latex and data-latex-item attributes, if any. 68// 69mml.walkTree((node) => { 70 const attributes = node.attributes; 71 attributes.unset('data-latex'); 72 attributes.unset('data-latex-item'); 73});

at line 66 just before the final output is produced. With this change, the output above becomes

1 − x 2

Converting TeX to CHTML

This example puts together all the code blocks fromThe Basics of Linking Directly to MathJax section in order to give an illustration of the complete process. The only new lines are 56 through 59, which cause all the font data to be loaded before the conversion is performed, thus avoiding the need to have to handle dynamically loaded font ranges.

tex2chtml.mjs

1// 2// Load the modules needed for MathJax 3// 4import {mathjax} from '@mathjax/src/js/mathjax.js'; 5import {TeX} from '@mathjax/src/js/input/tex.js'; 6import {CHTML} from '@mathjax/src/js/output/chtml.js'; 7import {liteAdaptor} from '@mathjax/src/js/adaptors/liteAdaptor.js'; 8import {RegisterHTMLHandler} from '@mathjax/src/js/handlers/html.js'; 9import '@mathjax/src/js/util/asyncLoad/esm.js'; 10 11// 12// Import the needed TeX packages 13// 14import '@mathjax/src/js/input/tex/base/BaseConfiguration.js'; 15import '@mathjax/src/js/input/tex/ams/AmsConfiguration.js'; 16import '@mathjax/src/js/input/tex/newcommand/NewcommandConfiguration.js'; 17import '@mathjax/src/js/input/tex/noundefined/NoUndefinedConfiguration.js'; 18 19// 20// The em and ex sizes and container width to use during the conversion 21// 22const EM = 16; // size of an em in pixels 23const EX = 8; // size of an ex in pixels 24const WIDTH = 80 * EM; // width of container for linebreaking 25 26// 27// Create DOM adaptor and register it for HTML documents 28// 29const adaptor = liteAdaptor({fontSize: EM}); 30RegisterHTMLHandler(adaptor); 31 32// 33// Create input and output jax and a (blank) document using them 34// 35const tex = new TeX({ 36 packages: ['base', 'ams', 'newcommand', 'noundefined'], 37 formatError(jax, err) {console.error(err.message); process.exit(1)}, 38 // 39 // Other TeX configuration goes here 40 // 41}); 42const chtml = new CHTML({ 43 fontURL: 'https://cdn.jsdelivr.net/npm/@mathjax/mathjax-newcm-font/chtml/woff2', 44 // 45 // Any output options go here 46 // 47}); 48const html = mathjax.document('', { 49 InputJax: tex, 50 OutputJax: chtml, 51 // 52 // Other document options go here 53 // 54}); 55 56// 57// Load all the font data 58// 59await chtml.font.loadDynamicFiles(); 60 61// 62// Typeset the math from the command line 63// 64const node = html.convert(process.argv[2] || '', { 65 display: true, 66 em: EM, 67 ex: EX, 68 containerWidth: WIDTH 69}); 70 71// 72// Generate a JSON object with the CHTML output and needed CSS 73// 74console.log(JSON.stringify({ 75 math: adaptor.outerHTML(node), 76 css: adaptor.cssText(chtml.styleSheet(html)) 77}));

Removing LaTeX Attributes from CHTML

In the tex2mml example above, we saw that the internal MathML contains data-latex attributes that indicate the LaTeX commands that produce each MathML node. Those attributes are retained in the CHTML output. If you want to remove them, we can use a similar idea to the one above, but since we don’t have direct access to the MathML representation in this case, we need to hook into the MathJax rendering pipeline in order to remove the attributes before the CHTML output is created. That can be done by configuring arenderAction in the document options when the htmldocument is created. This is illustrated below, with changes from the previous example highlighted.

tex2chtml-remove.mjs

1// 2// Load the modules needed for MathJax 3// 4import {mathjax} from '@mathjax/src/js/mathjax.js'; 5import {TeX} from '@mathjax/src/js/input/tex.js'; 6import {CHTML} from '@mathjax/src/js/output/chtml.js'; 7import {liteAdaptor} from '@mathjax/src/js/adaptors/liteAdaptor.js'; 8import {RegisterHTMLHandler} from '@mathjax/src/js/handlers/html.js'; 9import {STATE} from '@mathjax/src/js/core/MathItem.js'; 10import '@mathjax/src/js/util/asyncLoad/esm.js'; 11 12// 13// Import the needed TeX packages 14// 15import '@mathjax/src/js/input/tex/base/BaseConfiguration.js'; 16import '@mathjax/src/js/input/tex/ams/AmsConfiguration.js'; 17import '@mathjax/src/js/input/tex/newcommand/NewcommandConfiguration.js'; 18import '@mathjax/src/js/input/tex/noundefined/NoUndefinedConfiguration.js'; 19 20// 21// The em and ex sizes and container width to use during the conversion 22// 23const EM = 16; // size of an em in pixels 24const EX = 8; // size of an ex in pixels 25const WIDTH = 80 * EM; // width of container for linebreaking 26 27// 28// Create DOM adaptor and register it for HTML documents 29// 30const adaptor = liteAdaptor({fontSize: EM}); 31RegisterHTMLHandler(adaptor); 32 33// 34// Create input and output jax and a (blank) document using them 35// 36const tex = new TeX({ 37 packages: ['base', 'ams', 'newcommand', 'noundefined'], 38 formatError(jax, err) {console.error(err.message); process.exit(1)}, 39}); 40const chtml = new CHTML({ 41 fontURL: 'https://cdn.jsdelivr.net/npm/@mathjax/mathjax-newcm-font/chtml/woff2', 42}); 43const html = mathjax.document('', { 44 InputJax: tex, 45 OutputJax: chtml, 46 renderActions: { 47 removeLatex: [ 48 STATE.CONVERT + 1, 49 () => {}, 50 (math, doc) => { 51 math.root.walkTree(node => { 52 const attributes = node.attributes; 53 attributes.unset('data-latex'); 54 attributes.unset('data-latex-item'); 55 }); 56 } 57 ] 58 }, 59}); 60 61// 62// Load all the font data 63// 64await chtml.font.loadDynamicFiles(); 65 66// 67// Typeset the math from the command line 68// 69const node = html.convert(process.argv[2] || '', { 70 display: true, 71 em: EM, 72 ex: EX, 73 containerWidth: WIDTH 74}); 75 76// 77// Generate a JSON object with the CHTML output and needed CSS 78// 79console.log(JSON.stringify({ 80 math: adaptor.outerHTML(node), 81 css: adaptor.cssText(chtml.styleSheet(html)) 82}));

Here, lines 46 through 58 define a render action calledremoveLatex that occurs right after the conversion to MathML (indicated by the STATE.CONVERT + 1), and that performs the tree-walking on math.root, which is the internal MathML representation of the expression. Since we are only callinghtml.convert() rather than rendering an entire page withhtml.render(), we don’t need to provide a document-level function for this action, and so use () => {} for that.

Loading Font Ranges Dynamically

In these past two examples, we usedchtml.font.loadDynamciFiles() to load all the font data, so that dynamic loading would not need to occur during the conversion process. The font data in MathJax version 4 is much more extensive than in v3, due to the more expansive character coverage of the v4 fonts, so loading all the data can be time-consuming, especially when most of the data will never be used.

Instead, we can let MathJax load the data as needed. Because loading the data is asynchronous, this requires that we handle the asynchronous nature of those file loads during the conversion process. This is done via the mathDocument.convertPromise()function, which returns a promise that is resolved when the conversion process completes, after handlign any asynchronous font file loading. The example below shows how to accomplish that.

tex2chtml-promise.mjs

1// 2// Load the modules needed for MathJax 3// 4import {mathjax} from '@mathjax/src/js/mathjax.js'; 5import {TeX} from '@mathjax/src/js/input/tex.js'; 6import {CHTML} from '@mathjax/src/js/output/chtml.js'; 7import {liteAdaptor} from '@mathjax/src/js/adaptors/liteAdaptor.js'; 8import {RegisterHTMLHandler} from '@mathjax/src/js/handlers/html.js'; 9import '@mathjax/src/js/util/asyncLoad/esm.js'; 10 11// 12// Import the needed TeX packages 13// 14import '@mathjax/src/js/input/tex/base/BaseConfiguration.js'; 15import '@mathjax/src/js/input/tex/ams/AmsConfiguration.js'; 16import '@mathjax/src/js/input/tex/newcommand/NewcommandConfiguration.js'; 17import '@mathjax/src/js/input/tex/noundefined/NoUndefinedConfiguration.js'; 18 19// 20// The em and ex sizes and container width to use during the conversion 21// 22const EM = 16; // size of an em in pixels 23const EX = 8; // size of an ex in pixels 24const WIDTH = 80 * EM; // width of container for linebreaking 25 26// 27// Create DOM adaptor and register it for HTML documents 28// 29const adaptor = liteAdaptor({fontSize: EM}); 30RegisterHTMLHandler(adaptor); 31 32// 33// Create input and output jax and a (blank) document using them 34// 35const tex = new TeX({ 36 packages: ['base', 'ams', 'newcommand', 'noundefined'], 37 formatError(jax, err) {throw err}, 38}); 39const chtml = new CHTML({ 40 fontURL: 'https://cdn.jsdelivr.net/npm/@mathjax/mathjax-newcm-font/chtml/woff2', 41}); 42const html = mathjax.document('', { 43 InputJax: tex, 44 OutputJax: chtml, 45}); 46 47// 48// Typeset the math from the command line 49// 50html.convertPromise(process.argv[2] || '', { 51 display: true, 52 em: EM, 53 ex: EX, 54 containerWidth: WIDTH 55}).then((node) => { 56 // 57 // Generate a JSON object with the CHTML output and needed CSS 58 // 59 console.log(JSON.stringify({ 60 math: adaptor.outerHTML(node), 61 css: adaptor.cssText(chtml.styleSheet(html)) 62 })); 63}).catch((err) => console.error(err.message));

Here, we remove the chtml.font.loadDynamicFiles() call, and replace html.convert() by html.convertPromise(), so that if a font file needs to be loaded, that will be properly handled, putting the rest of the code in its then() call. Without this, the html.convert() call could throw a MathJax retry error; it is the html.convertPromise()function that traps and processes those errors as part of the handling of asynchronous file loads.

The other change is that the formatError() function now throws the error it receives, which is then trapped by the catch()call following the html.convertPromise() function and reported there. One could have used the original formatError(), but this shows another approach to handling TeX errors.

Specifying The Font to Use

The examples so far, other than the first one, have all used the default font, which is mathjax-newcm, based on the New Computer Modern font. MathJax v4 provides a number of other fonts, however (see the MathJax Font Support section for details), and you can use any of these to replace the default font.

In the example below, we use the mathjax-fira font, which is a sans-serif font. First, install the font using

pnpm install @mathjax/mathjax-fira-font

(or use npm instead of pnpm), and then modify the previous example as indicated in the highlighted lines below.

tex2chtml-font.mjs

1// 2// Load the modules needed for MathJax 3// 4import {mathjax} from '@mathjax/src/js/mathjax.js'; 5import {TeX} from '@mathjax/src/js/input/tex.js'; 6import {CHTML} from '@mathjax/src/js/output/chtml.js'; 7import {liteAdaptor} from '@mathjax/src/js/adaptors/liteAdaptor.js'; 8import {RegisterHTMLHandler} from '@mathjax/src/js/handlers/html.js'; 9import '@mathjax/src/js/util/asyncLoad/esm.js'; 10 11// 12// Import the needed TeX packages 13// 14import '@mathjax/src/js/input/tex/base/BaseConfiguration.js'; 15import '@mathjax/src/js/input/tex/ams/AmsConfiguration.js'; 16import '@mathjax/src/js/input/tex/newcommand/NewcommandConfiguration.js'; 17import '@mathjax/src/js/input/tex/noundefined/NoUndefinedConfiguration.js'; 18 19// 20// Import the desired font 21// 22import {MathJaxFiraFont} from '@mathjax/mathjax-fira-font/js/chtml.js'; 23 24// 25// The em and ex sizes and container width to use during the conversion 26// 27const EM = 16; // size of an em in pixels 28const EX = 8; // size of an ex in pixels 29const WIDTH = 80 * EM; // width of container for linebreaking 30 31// 32// Create DOM adaptor and register it for HTML documents 33// 34const adaptor = liteAdaptor({fontSize: EM}); 35RegisterHTMLHandler(adaptor); 36 37// 38// Create input and output jax and a (blank) document using them 39// 40const tex = new TeX({ 41 packages: ['base', 'ams', 'newcommand', 'noundefined'], 42 formatError(jax, err) {throw err}, 43}); 44const chtml = new CHTML({ 45 fontData: MathJaxFiraFont, 46 fontURL: 'https://cdn.jsdelivr.net/npm/@mathjax/mathjax-fira-font/chtml/woff2', 47}); 48const html = mathjax.document('', { 49 InputJax: tex, 50 OutputJax: chtml, 51}); 52 53// 54// Typeset the math from the command line 55// 56html.convertPromise(process.argv[2] || '', { 57 display: true, 58 em: EM, 59 ex: EX, 60 containerWidth: WIDTH 61}).then((node) => { 62 // 63 // Generate a JSON object with the CHTML output and needed CSS 64 // 65 console.log(JSON.stringify({ 66 math: adaptor.outerHTML(node), 67 css: adaptor.cssText(chtml.styleSheet(html)) 68 })); 69}).catch((err) => console.error(err.message));

Here, line 22 imports the Fira font class, which is passed to the CHTML output jax at line 45. Line 46 now needs to point to themathjax-fira-font directory on the CDN.

The earlier examples could be modified in a similar way, as well.


Mixing Components and Direct Linking

One of the drawbacks to using direct loading of MathJax modules is that you don’t have the MathJax component framework to work with, which means you can’t use \require{} or autoloaded TeX components, for example. It is possible to use both together, however, by importing the needed component definitions. This is done in theUsing Components Synchronously section to show how to handle synchronous typesetting, but this technique also can be used more generally with the promise-based commands, as illustrated in the example below.

tex2chtml-mixed.mjs

1// 2// Load the modules needed for MathJax 3// 4import {mathjax} from '@mathjax/src/js/mathjax.js'; 5import {TeX} from '@mathjax/src/js/input/tex.js'; 6import {CHTML} from '@mathjax/src/js/output/chtml.js'; 7import {liteAdaptor} from '@mathjax/src/js/adaptors/liteAdaptor.js'; 8import {RegisterHTMLHandler} from '@mathjax/src/js/handlers/html.js'; 9 10// 11// Load the component definitions 12// 13import {Loader} from '@mathjax/src/js/components/loader.js'; 14import {Package} from '@mathjax/src/js/components/package.js'; 15import '@mathjax/src/components/js/startup/init.js'; 16import '@mathjax/src/components/js/core/lib/core.js'; 17import '@mathjax/src/components/js/input/tex/tex.js'; 18import '@mathjax/src/components/js/output/chtml/chtml.js'; 19 20// 21// Record the pre-loaded component files 22// 23Loader.preLoaded( 24 'loader', 'startup', 25 'core', 26 'input/tex', 27 'output/chtml', 28); 29 30// 31// The em and ex sizes and container width to use during the conversion 32// 33const EM = 16; // size of an em in pixels 34const EX = 8; // size of an ex in pixels 35const WIDTH = 80 * EM; // width of container for linebreaking 36 37// 38// Set up methods for loading dynamic files 39// 40MathJax.config.loader.require = (file) => import(file); 41mathjax.asyncLoad = (file) => import(Package.resolvePath(file)); 42 43// 44// Create DOM adaptor and register it for HTML documents 45// 46const adaptor = liteAdaptor({fontSize: EM}); 47RegisterHTMLHandler(adaptor); 48 49// 50// Create input and output jax and a (blank) document using them 51// 52const tex = new TeX({ 53 formatError(jax, err) {throw err}, 54 ...(MathJax.config.tex || {}) 55}); 56const chtml = new CHTML({ 57 ...(MathJax.config.output || {}), 58 ...(MathJax.config.chtml || {}), 59 fontURL: 'https://cdn.jsdelivr.net/npm/@mathjax/mathjax-newcm-font/chtml/woff2', 60}); 61const html = mathjax.document('', { 62 InputJax: tex, 63 OutputJax: chtml, 64 ...(MathJax.config.options || {}) 65}); 66 67// 68// Typeset the math from the command line 69// 70html.convertPromise(process.argv[2] || '', { 71 display: true, 72 em: EM, 73 ex: EX, 74 containerWidth: WIDTH 75}).then((node) => { 76 // 77 // Generate a JSON object with the CHTML output and needed CSS 78 // 79 console.log(JSON.stringify({ 80 math: adaptor.outerHTML(node), 81 css: adaptor.cssText(chtml.styleSheet(html)) 82 })); 83}).catch((err) => console.error(err.message));

Here, lines 10 through 18 load the component framework and definition files. The first two lines obtain the Loader() andPackage() class definitions, which are the heart of the component framework. The next line initializes thestartup component, which sets up some of the needed component configuration. The next line loads the core component definition, and the final two lines load the input and output jax component definitions for the ones we will be using.

Lines 20 through 28 register the pre-loaded components with the loader so that it won’t try to load them again.

Lines 37 through 41 define the methods needed for dynamic loading of files. The first tells MathJax to use import() to load files, and the second tells MathJax to use Package.resolvePath() to process file names before importing them. That allows references like[tex]/cancel or [mathjax-fira]/chtml/dynamic/calligraphic to be resolved to their full URLs before they are loaded.

Lines 54, 57, 58, and 64 incorporate the appropriate MathJax configuration blocks into the options used for creating the input and output jax and the math document. The component files initialize some of these values, and if the tex2chtml-mixed.mjs file is imported into another application, that would allow that application to provide its own MathJax configuration object, just like in a web page, and its configuration would be incorporated into the creation of the MathJax objects here.

Note that the import commands that loaded the TeX packages (base, ams, newcommand, and noundefined) have been removed, as the input/tex component loads those (and several others) itself. Note also that the packages array has been removed, as theinput/tex component defines that itself, and already includes the packages that it loads.

With these changes, the LaTeX being processed can now use\require, and macros that autoload extensions will work as well. So this gives you the best of both worlds: the convenience of MathJax components, and the control of direct imports.

If you want to preload additional TeX packages, you can import them and then push their names onto the tex.packages array prior to instantiating the TeX input jax. For example

import '@mathjax/src/components/js/input/tex/extensions/mathtools/mathtools.js'; import '@mathjax/src/components/js/input/tex/extensions/physics/physics.js'; MathJax.config.tex.packages.push('mathtools', 'physics'); Loader.preLoaded('[tex]/mathtools', '[tex]/physics');

could be added at line 19 in order to include the mathtoolsand physics TeX packages.


Generating Speech Strings without Typesetting

One of MathJax’s most important features is the ability to generate speech strings from mathematical notation. MathJax uses the Speech Rule Engine (SRE) to perform this function.

The example below is based on the Converting TeX to MathML code described above, with the changes highlighted.

tex2speech.mjs

1// 2// Load the modules needed for MathJax 3// 4import {mathjax} from '@mathjax/src/js/mathjax.js'; 5import {TeX} from '@mathjax/src/js/input/tex.js'; 6import {liteAdaptor} from '@mathjax/src/js/adaptors/liteAdaptor.js'; 7import {RegisterHTMLHandler} from '@mathjax/src/js/handlers/html.js'; 8import {SerializedMmlVisitor} from '@mathjax/src/js/core/MmlTree/SerializedMmlVisitor.js'; 9import {STATE} from '@mathjax/src/js/core/MathItem.js'; 10 11// 12// Import the speech-rule-engine 13// 14import '@mathjax/src/components/require.mjs'; 15import {setupEngine, engineReady, toSpeech} from 'speech-rule-engine/js/common/system.js'; 16 17// 18// Import the needed TeX packages 19// 20import '@mathjax/src/js/input/tex/base/BaseConfiguration.js'; 21import '@mathjax/src/js/input/tex/ams/AmsConfiguration.js'; 22import '@mathjax/src/js/input/tex/newcommand/NewcommandConfiguration.js'; 23import '@mathjax/src/js/input/tex/noundefined/NoUndefinedConfiguration.js'; 24 25// 26// The em and ex sizes and container width to use during the conversion 27// 28const EM = 16; // size of an em in pixels 29const EX = 8; // size of an ex in pixels 30const WIDTH = 80 * EM; // width of container for linebreaking 31 32// 33// Create DOM adaptor and register it for HTML documents 34// 35const adaptor = liteAdaptor({fontSize: EM}); 36RegisterHTMLHandler(adaptor); 37 38// 39// Create input jax and a (blank) document using it 40// 41const tex = new TeX({ 42 packages: ['base', 'ams', 'newcommand', 'noundefined'], 43 formatError(jax, err) {console.error(err.message); process.exit(1)}, 44 // 45 // Other TeX configuration goes here 46 // 47}); 48const html = mathjax.document('', { 49 InputJax: tex, 50 // 51 // Other document options go here 52 // 53}); 54 55// 56// Create a MathML serializer 57// 58const visitor = new SerializedMmlVisitor(); 59const toMathML = (node => visitor.visitTree(node, html)); 60 61// 62// Convert the math from the command line 63// 64const mml = html.convert(process.argv[2] || '', { 65 display: true, 66 em: EM, 67 ex: EX, 68 containerWidth: WIDTH, 69 end: STATE.CONVERT // stop after conversion to MathML 70}); 71 72// 73// Set up the speech engine to use English 74// 75const locale = process.argv[3] || 'en'; 76const modality = locale === 'nemeth' || locale === 'euro' ? 'braille' : 'speech'; 77await setupEngine({locale, modality}).then(() => engineReady()); 78 79// 80// Produce the speech for the converted MathML 81// 82console.log(toSpeech(toMathML(mml)));

Here, line 15 loads the functions needed for speech generation. Because SRE uses require() to load its dependencies when used from node, line 14 makes that available before loading SRE.

Line 75 gets the locale (i.e., the language) to use for the speech, and line 76 determines whether we are generating speech or Braille strings. Line 77 sets up SRE for the proper locale and modality, and waits for the needed files to be loaded.

Finally, line 82 uses the toSpeech() function from SRE to convert the MathML generated from the TeX into the needed speech or Braille string.

When this node application is run from the command line, the first command line argument is the LaTeX string to convert, while the output is the corresponding speech string. For example,

node tex2speech.mjs '\frac{a}{b}'

would generate the phrase

StartFraction a Over b EndFraction

SRE can generate speech in several languages; here, the default language is English, but the second command-line argument can be used to specify a different language locale. For example,

node tex2speech.mjs '\frac{a}{b}' de

would generate the German phrase

Anfang Bruch a durch b Ende Bruch

while

node tex2speech.mjs '\frac{a}{b}' nemeth

would generate the Braille code

The available locales can be found in the bundle/sre/mathmapsdirectory.

There are several different speech rulesets that SRE can use, including clearspeak, mathspeak, and chromvox rules. You can add a domain option to the list passed tosetupEngine() in order to specify which of these rulesets to use. The default is mathspeak.


Pre-processing a Complete Page

All of the previous examples convert a single mathematical expression at a time, but you may wish to preprocess an entire web page, rather than individual expressions. In that case, you would load the page’s text and use that when creating the math document, and then callhtml.renderPromise() rather than html.convertPromise().

This is illustrated with the example below, this time using SVG output rather than CHTML output.

tex2svg-page.mjs

1import fs from 'fs'; 2 3// 4// Load the modules needed for MathJax 5// 6import {mathjax} from '@mathjax/src/js/mathjax.js'; 7import {TeX} from '@mathjax/src/js/input/tex.js'; 8import {SVG} from '@mathjax/src/js/output/svg.js'; 9import {liteAdaptor} from '@mathjax/src/js/adaptors/liteAdaptor.js'; 10import {RegisterHTMLHandler} from '@mathjax/src/js/handlers/html.js'; 11import '@mathjax/src/js/util/asyncLoad/esm.js'; 12 13// 14// Import the needed TeX packages 15// 16import '@mathjax/src/js/input/tex/base/BaseConfiguration.js'; 17import '@mathjax/src/js/input/tex/ams/AmsConfiguration.js'; 18import '@mathjax/src/js/input/tex/newcommand/NewcommandConfiguration.js'; 19import '@mathjax/src/js/input/tex/noundefined/NoUndefinedConfiguration.js'; 20 21// 22// The em and ex sizes to use during the conversion 23// 24const EM = 16; // size of an em in pixels 25const EX = 8; // size of an ex in pixels 26 27// 28// Create DOM adaptor and register it for HTML documents 29// 30const adaptor = liteAdaptor({fontSize: EM}); 31RegisterHTMLHandler(adaptor); 32 33// 34// Read the HTML file 35// 36const htmlfile = fs.readFileSync(process.argv[2] || 0, 'utf8'); 37 38// 39// Create input and output jax and a document using them on the HTML file 40// 41const tex = new TeX({ 42 packages: ['base', 'ams', 'newcommand', 'noundefined'], 43 formatError(jax, err) {console.error(err.message); return jax.formatError(err)}, 44 // 45 // Other TeX configuration goes here 46 // 47}); 48const svg = new SVG({ 49 fontCache: 'global', 50 exFactor: EX / EM, 51 // 52 // Any output options go here 53 // 54}); 55const html = mathjax.document(htmlfile, { 56 InputJax: tex, 57 OutputJax: svg, 58 // 59 // Other document options go here 60 // 61}); 62 63// 64// Wait for the typesetting to finish 65// 66await html.renderPromise(); 67 68// 69// If no math was found on the page, remove the stylesheet and font cache (if any) 70// 71if (Array.from(html.math).length === 0) { 72 adaptor.remove(svg.svgStyles); 73 const cache = adaptor.elementById(adaptor.body(html.document), 'MJX-SVG-global-cache'); 74 if (cache) adaptor.remove(cache); 75} 76 77// 78// Output the resulting HTML 79// 80console.log(adaptor.doctype(html.document)); 81console.log(adaptor.outerHTML(adaptor.root(html.document)));

Here, line 1 loads the node fs library that is used in line 36 to read the HTML file that is to be processed (either the one given as the first command-line argument, or from standard input when the script is run as a filter).

Line 8 loads the SVG output jax rather than the CHTML one, and lines 48 through 54 instantiate the output jax. We set thefontCache to global so that all the SVG path data will be stored in one place and can be shared among all the expressions on the page rather than having individual copies for each expression. We also set up the ex-to-em factor, since we can’t measure that directory using the LiteDOM in node. Line 57 uses the SVG output jax that we just created rather than the CHTML one from previous examples.

Line 66 is the key change from the previous examples, which now useshtml.renderPromise() to process the entire page, rather than just process a single expression. We use the promise-based function so that we handle any font data files that need to be loaded.

Lines 72 to 75 check to see that there is actually math that was processed on the page, and if not, it removes the stylesheet and font-cache <svg> element that it added to the page, leaving the page essentially untouched.

Lines 80 and 81 produce the final output for the pre-processed page.

Finally, the formatError() function on line 43 now logs the error and then calls the default formatError() function, so that the error will appear within the final HTML document as it would in a web page.

Note that when pre-processing a page, there are a number of limitations:

To include the assistive-mml extension, add the following importcommand

import {AssistiveMmlHandler} from '@mathjax/src/js/a11y/assistive-mml.js';

and change line 31 to be

AssistiveHandler(RegisterHTMLHandler(adaptor));

That will cause MathJax to include visually hidden MathML that can be read by screen readers.


More Examples

See the MathJax node demosfor additional examples of how to use MathJax from a nodeapplication. In particular, see the non-component-based examplesfor more illustrations of how to use MathJax modules directly in anode application, rather than using the pre-packaged components.