Linking to MathJax Directly in Node
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 MathJax
variable 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 granular 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 the require 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 Synchronously section. 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 Linking example 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 the
cjs directory when used in a require() command, and the
mjs 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 and
mjs 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 cjs
and 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 a
MathDocument() 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 the The 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 use
require() to load js/util/asyncLoad/node.js rather than
js/util/asyncLoad/esm.js, and would replace all the import
commands 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, and
noundefined 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/tex
directory, 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 and
require 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 the
packages 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 CommonHTML (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 the
configmacros extension, add it to the packages
list, 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 the
MathDocument() 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 to
mathjax.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:
math (
string()) – The TeX, MathML, or AsciiMath expression to be converted.options (
OptionList()) –The options for the conversion. These can include:
format: the format of the input being passed (
'TeX','MathML', or'AsciiMath'). The default is the name of the first input jax of the MathDocument.display: Whether this should be typeset in display style or in-line style. The default is
true. For MathML input, this option is ignored, as the<math>tag’sdisplayattribute is used to specify the display style.end: The process state at which the conversion should end. The default is
STATE.LAST, meaning all render actions should be performed. The initial list of states is in thets/core/Mathitem.tsfile, though other files can augment that list.em: The em-size of the surrounding font in pixels. The default is 16.
ex: The ex-size of the surrounding font in pixels. The default is 8.
containerWidth: The width of the surrounding container element in pixels. The default is
null, meaning we consider the container to be infinitely wide.scale: The scaling factor to be applied to the output. The default is 1.
family: The name of the surrounding font (for when
mtextormerroruse the surrounding font). The default is an empty string.
- 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.
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 the
SerializedMmlVisitor() 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
<math xmlns="http://www.w3.org/1998/Math/MathML" data-latex="\sqrt{1-x^2}" display="block">
<msqrt data-latex="\sqrt{1-x^2}">
<mn data-latex="1">1</mn>
<mo data-latex="-">−</mo>
<msup data-latex="x^2">
<mi data-latex="x">x</mi>
<mn data-latex="2">2</mn>
</msup>
</msqrt>
</math>
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
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block">
<msqrt>
<mn>1</mn>
<mo>−</mo>
<msup>
<mi>x</mi>
<mn>2</mn>
</msup>
</msqrt>
</math>
Converting TeX to CHTML
This example puts together all the code blocks from The 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.
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 a
renderAction in the document options when the html
document is created. This is illustrated below, with changes from the
previous example highlighted.
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 called
removeLatex 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 calling
html.convert() rather than rendering an entire page with
html.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 used
chtml.font.loadDynamicFiles() 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 handling any asynchronous font file loading.
The example below shows how to accomplish that.
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.
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 the
mathjax-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 the
Using 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.
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() and
Package() class definitions, which are the heart of the
component framework. The next line initializes the
startup 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 the
input/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 mathtools and 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.
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/mathmaps
directory.
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 to
setupEngine() 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 call
html.renderPromise() rather than html.convertPromise().
This is illustrated with the example below, this time using SVG output rather than CHTML output.
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 the
fontCache 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 uses
html.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:
All the expressions will use the same ex-to-em factor, since MathJax can’t measure the font metrics of the surrounding font in node.
The handling of characters that are not in the MathJax fonts will be problematic, as MathJax can’t measure their sizes, so will have to make assumptions that probably aren’t right. The output will try to use a fixed-width font in order to try to reduce its error, but that can produce ugly results. Fortunately, the fonts in MathJax v4 have much greater coverage than in v3, so the problem should occur much less often.
The menu code relies on MathJax actually being active in the page, and when the page is pre-processed in this way, that will not be the case. So the menu is not available in pre-processed pages. That means your readers won’t be able to view or copy the math notation, change the renderer, or use any of the other features available in the MathJax contextual menu.
The expression explorer also relies on MathJax being present, and so that feature will not be available in pre-processed pages. You may wish to include the assistive-mml extension in order to help support users who require screen readers, as described below.
Finally, since MathJax doesn’t know the size of the screen that your user will be using when viewing your page, it is not able to do automatic line breaking of displayed equations, unless you configure the line-breaking width explicitly. In-line breaks will still be possible, since they are determined by the browser at run time.
To include the assistive-mml extension, add the following import
command
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 demos for additional examples of how to use MathJax from a node application. In particular, see the non-component-based examples for more illustrations of how to use MathJax modules directly in a node application, rather than using the pre-packaged components.