server/direct.rst
.. _node-direct:
################################### Linking to MathJax Directly in Node ###################################
The previous sections use the :ref:MathJax components <web-components> framework to manage most of the details of setting
up and using MathJax. That framework uses a global :js:data: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
:ref:require <tex-require> and :ref:autoload <tex-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 :ref:startup-component module
that performs those duties within the components framework, along with
the :js:data: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 :ref:node-preload
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 :ref:direct-tex2chtml-mixed
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.
.. _direct-basics:
First, :ref:get a copy of the MathJax code library <obtain-mathjax>.
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 :file: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:
.. code-block:: javascript
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 :js:data:mathjax object, which
contains the version number, a function for creating a
:js:class: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
:ref:node-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 :file:js/util/asyncLoad/node.js rather than
:file: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:
.. code-block:: javascript
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, :ref:tex-ams, :ref:tex-newcommand, and
:ref:tex-noundefined extensions. The names of these packages are
then added to the :js:data: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 :ref:extension-list for more about the TeX
extensions. Remember, you must load all the extensions explicitly
that you plan to use, and the :ref:tex-autoload and
:ref:tex-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:
.. code-block:: javascript
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:
.. code-block:: javascript
const adaptor = liteAdaptor({fontSize: EM}); RegisterHTMLHandler(adaptor);
Next, the examples create the input and output jax:
.. code-block:: javascript
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
:js:data:packages array to include the packages that we loaded
above. We also include a :js:meth: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
:ref:tex-configmacros extension, add it to the :js:data:packages
list, and include a :js:data: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
:js:class:MathDocument instance:
.. code-block:: javascript
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
:js:meth: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:
.. code-block:: javascript
const node = html.convert(process.argv[2] || '', { display: true, em: EM, ex: EX, containerWidth: WIDTH });
This takes the command-line argument from :data:process.argv[2]
and treats it as a TeX expression, converting it to CHTML output.
The :data: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.
.. js:method:: mathDocument.convert(math, [options])
:param string math: The TeX, MathML, or AsciiMath expression to be converted. :param OptionList options: 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's
:attr:`display` attribute is used to specify the display style.
* **end**: The process state at which the conversion should end.
The default is :data:`STATE.LAST`, meaning all render actions
should be performed. The initial list of states is in the
:file:`ts/core/Mathitem.ts` file, 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
``mtext`` or ``merror`` use the surrounding font). The
default is an empty string.
:returns: The DOM node containing the typeset version of the mathematics.
You could provide the :attr:display, :attr:ex, :attr:em, and
other values from command-line arguments, for example, though we use
the defaults in these examples.
Note that there is also
.. js:method:: mathDocument.convertPromise(math, [options])
taking the same arguments as :js:meth:mathDocument.convert() above,
and returning a promise that resolves when the conversion is complete,
passing the generated node as the argument to its :meth: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).
.. code-block:: javascript
// // 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).
In the examples below, the highlighted lines are the ones that differ from the explanations above, or from previous examples.
|btight|
:ref:direct-tex2mml
:ref:direct-tex2chtml
direct-text2chtml-removedirect-text2chtml-promisedirect-tex2chtml-font:ref:direct-tex2chtml-mixed
:ref:direct-tex2speech
:ref:direct-tex2svg-page
|etight|
.. _direct-tex2mml:
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.
.. code-block:: javascript :linenos: :emphasize-lines: 8, 9, 49-53, 63, 66-69 :caption: tex2mml.mjs
// // Load the modules needed for MathJax // import {mathjax} from '@mathjax/src/js/mathjax.js'; import {TeX} from '@mathjax/src/js/input/tex.js'; import {liteAdaptor} from '@mathjax/src/js/adaptors/liteAdaptor.js'; import {RegisterHTMLHandler} from '@mathjax/src/js/handlers/html.js'; import {SerializedMmlVisitor} from '@mathjax/src/js/core/MmlTree/SerializedMmlVisitor.js'; import {STATE} from '@mathjax/src/js/core/MathItem.js';
// // Import the needed TeX packages // 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';
// // The em and ex sizes and container width to use during the conversion // 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
// // Create DOM adaptor and register it for HTML documents // const adaptor = liteAdaptor({fontSize: EM}); RegisterHTMLHandler(adaptor);
// // Create input jax and a (blank) document using it // 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 html = mathjax.document('', { InputJax: tex, // // Other document options go here // });
// // Create a MathML serializer // const visitor = new SerializedMmlVisitor(); const toMathML = (node => visitor.visitTree(node, html));
// // Convert the math from the command line // const mml = html.convert(process.argv[2] || '', { display: true, em: EM, ex: EX, containerWidth: WIDTH, end: STATE.CONVERT // stop after conversion to MathML });
// // Output the resulting MathML // console.log(toMathML(mml));
Here, line 9 loads the :js:data: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
:js:class: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
.. code-block:: shell
node tex2mml.mjs '\sqrt{1-x^2}'
produces
.. code-block:: xml
<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 :attr:data-latex attributes indicating
the LaTeX that produced each node. If you don't want those
attributes, you can add
.. code-block:: javascript :linenos: :lineno-start: 66
// // Remove data-latex and data-latex-item attributes, if any. // mml.walkTree((node) => { const attributes = node.attributes; attributes.unset('data-latex'); attributes.unset('data-latex-item'); });
at line 66 just before the final output is produced. With this change, the output above becomes
.. code-block:: xml
<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>.. _direct-tex2chtml:
This example puts together all the code blocks from
:ref:direct-basics 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.
.. code-block:: javascript :linenos: :emphasize-lines: 56-59 :caption: tex2chtml.mjs
// // Load the modules needed for MathJax // 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';
// // Import the needed TeX packages // 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';
// // The em and ex sizes and container width to use during the conversion // 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
// // Create DOM adaptor and register it for HTML documents // const adaptor = liteAdaptor({fontSize: EM}); RegisterHTMLHandler(adaptor);
// // Create input and output jax and a (blank) document using them // 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 // }); const html = mathjax.document('', { InputJax: tex, OutputJax: chtml, // // Other document options go here // });
// // Load all the font data // await chtml.font.loadDynamicFiles();
// // Typeset the math from the command line // const node = html.convert(process.argv[2] || '', { display: true, em: EM, ex: EX, containerWidth: WIDTH });
// // 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)) }));
.. _direct-text2chtml-remove:
In the :ref:tex2mml <direct-tex2mml> example above, we saw that the
internal MathML contains :attr: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
:js:data:renderAction in the document options when the :data:html
document is created. This is illustrated below, with changes from the
previous example highlighted.
.. code-block:: javascript :linenos: :emphasize-lines: 46-58 :caption: tex2chtml-remove.mjs
// // Load the modules needed for MathJax // 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 {STATE} from '@mathjax/src/js/core/MathItem.js'; import '@mathjax/src/js/util/asyncLoad/esm.js';
// // Import the needed TeX packages // 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';
// // The em and ex sizes and container width to use during the conversion // 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
// // Create DOM adaptor and register it for HTML documents // const adaptor = liteAdaptor({fontSize: EM}); RegisterHTMLHandler(adaptor);
// // Create input and output jax and a (blank) document using them // const tex = new TeX({ packages: ['base', 'ams', 'newcommand', 'noundefined'], formatError(jax, err) {console.error(err.message); process.exit(1)}, }); const chtml = new CHTML({ fontURL: 'https://cdn.jsdelivr.net/npm/@mathjax/mathjax-newcm-font/chtml/woff2', }); const html = mathjax.document('', { InputJax: tex, OutputJax: chtml, renderActions: { removeLatex: [ STATE.CONVERT + 1, () => {}, (math, doc) => { math.root.walkTree(node => { const attributes = node.attributes; attributes.unset('data-latex'); attributes.unset('data-latex-item'); }); } ] }, });
// // Load all the font data // await chtml.font.loadDynamicFiles();
// // Typeset the math from the command line // const node = html.convert(process.argv[2] || '', { display: true, em: EM, ex: EX, containerWidth: WIDTH });
// // 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)) }));
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 :data:math.root, which is the internal MathML
representation of the expression. Since we are only calling
:meth:html.convert() rather than rendering an entire page with
:meth:html.render(), we don't need to provide a document-level
function for this action, and so use () => {} for that.
.. _direct-text2chtml-promise:
In these past two examples, we used
:meth: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 :js:func: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.
.. code-block:: javascript :linenos: :emphasize-lines: 37, 50, 55, 63 :caption: tex2chtml-promise.mjs
// // Load the modules needed for MathJax // 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';
// // Import the needed TeX packages // 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';
// // The em and ex sizes and container width to use during the conversion // 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
// // Create DOM adaptor and register it for HTML documents // const adaptor = liteAdaptor({fontSize: EM}); RegisterHTMLHandler(adaptor);
// // Create input and output jax and a (blank) document using them // const tex = new TeX({ packages: ['base', 'ams', 'newcommand', 'noundefined'], formatError(jax, err) {throw err}, }); const chtml = new CHTML({ fontURL: 'https://cdn.jsdelivr.net/npm/@mathjax/mathjax-newcm-font/chtml/woff2', }); const html = mathjax.document('', { InputJax: tex, OutputJax: chtml, });
// // Typeset the math from the command line // html.convertPromise(process.argv[2] || '', { display: true, em: EM, ex: EX, containerWidth: WIDTH }).then((node) => { // // 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)) })); }).catch((err) => console.error(err.message));
Here, we remove the :meth:chtml.font.loadDynamicFiles() call, and
replace :meth:html.convert() by :meth: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 :meth:then() call. Without
this, the :meth:html.convert() call could throw a :ref:MathJax retry <retry-error> error; it is the :meth:html.convertPromise()
function that traps and processes those errors as part of the handling
of asynchronous file loads.
The other change is that the :meth:formatError() function now throws
the error it receives, which is then trapped by the :meth:catch()
call following the :meth:html.convertPromise() function and reported
there. One could have used the original :meth:formatError(), but
this shows another approach to handling TeX errors.
.. _direct-tex2chtml-font:
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 :ref: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
.. code-block:: shell
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.
.. code-block:: javascript :linenos: :emphasize-lines: 19-22, 45, 46 :caption: tex2chtml-font.mjs
// // Load the modules needed for MathJax // 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';
// // Import the needed TeX packages // 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';
// // Import the desired font // import {MathJaxFiraFont} from '@mathjax/mathjax-fira-font/js/chtml.js';
// // The em and ex sizes and container width to use during the conversion // 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
// // Create DOM adaptor and register it for HTML documents // const adaptor = liteAdaptor({fontSize: EM}); RegisterHTMLHandler(adaptor);
// // Create input and output jax and a (blank) document using them // const tex = new TeX({ packages: ['base', 'ams', 'newcommand', 'noundefined'], formatError(jax, err) {throw err}, }); const chtml = new CHTML({ fontData: MathJaxFiraFont, fontURL: 'https://cdn.jsdelivr.net/npm/@mathjax/mathjax-fira-font/chtml/woff2', }); const html = mathjax.document('', { InputJax: tex, OutputJax: chtml, });
// // Typeset the math from the command line // html.convertPromise(process.argv[2] || '', { display: true, em: EM, ex: EX, containerWidth: WIDTH }).then((node) => { // // 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)) })); }).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.
.. _direct-tex2chtml-mixed:
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
:ref:node-preload 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.
.. code-block:: javascript :linenos: :emphasize-lines: 10-18, 20-28, 37-41, 54, 57, 58, 64 :caption: tex2chtml-mixed.mjs
// // Load the modules needed for MathJax // 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';
// // Load the component definitions // import {Loader} from '@mathjax/src/js/components/loader.js'; import {Package} from '@mathjax/src/js/components/package.js'; import '@mathjax/src/components/js/startup/init.js'; import '@mathjax/src/components/js/core/lib/core.js'; import '@mathjax/src/components/js/input/tex/tex.js'; import '@mathjax/src/components/js/output/chtml/chtml.js';
// // Record the pre-loaded component files // Loader.preLoaded( 'loader', 'startup', 'core', 'input/tex', 'output/chtml', );
// // The em and ex sizes and container width to use during the conversion // 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
// // Set up methods for loading dynamic files // MathJax.config.loader.require = (file) => import(file); mathjax.asyncLoad = (file) => import(Package.resolvePath(file));
// // Create DOM adaptor and register it for HTML documents // const adaptor = liteAdaptor({fontSize: EM}); RegisterHTMLHandler(adaptor);
// // Create input and output jax and a (blank) document using them // const tex = new TeX({ formatError(jax, err) {throw err}, ...(MathJax.config.tex || {}) }); const chtml = new CHTML({ ...(MathJax.config.output || {}), ...(MathJax.config.chtml || {}), fontURL: 'https://cdn.jsdelivr.net/npm/@mathjax/mathjax-newcm-font/chtml/woff2', }); const html = mathjax.document('', { InputJax: tex, OutputJax: chtml, ...(MathJax.config.options || {}) });
// // Typeset the math from the command line // html.convertPromise(process.argv[2] || '', { display: true, em: EM, ex: EX, containerWidth: WIDTH }).then((node) => { // // 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)) })); }).catch((err) => console.error(err.message));
Here, lines 10 through 18 load the component framework and definition
files. The first two lines obtain the :js:class:Loader and
:js:class:Package class definitions, which are the heart of the
component framework. The next line initializes the
:ref:startup-component 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 :file:tex2chtml-mixed.mjs file is
imported into another application, that would allow that application
to provide its own :js:data: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 :js:data: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 :js:data:tex.packages array prior
to instantiating the TeX input jax. For example
.. code-block:: javascript
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 :ref:tex-mathtools
and :ref:tex-physics TeX packages.
.. _direct-tex2speech:
One of MathJax's most important features is the ability to generate
speech strings from mathematical notation. MathJax uses the Speech Rule Engine <https://github.com/zorkow/speech-rule-engine>_ (SRE) to
perform this function.
The example below is based on the :ref:direct-tex2mml code described
above, with the changes highlighted.
.. code-block:: javascript :linenos: :emphasize-lines: 11-15, 73-77, 79-82 :caption: tex2speech.mjs
// // Load the modules needed for MathJax // import {mathjax} from '@mathjax/src/js/mathjax.js'; import {TeX} from '@mathjax/src/js/input/tex.js'; import {liteAdaptor} from '@mathjax/src/js/adaptors/liteAdaptor.js'; import {RegisterHTMLHandler} from '@mathjax/src/js/handlers/html.js'; import {SerializedMmlVisitor} from '@mathjax/src/js/core/MmlTree/SerializedMmlVisitor.js'; import {STATE} from '@mathjax/src/js/core/MathItem.js';
// // Import the speech-rule-engine // import '@mathjax/src/components/require.mjs'; import {setupEngine, engineReady, toSpeech} from 'speech-rule-engine/js/common/system.js';
// // Import the needed TeX packages // 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';
// // The em and ex sizes and container width to use during the conversion // 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
// // Create DOM adaptor and register it for HTML documents // const adaptor = liteAdaptor({fontSize: EM}); RegisterHTMLHandler(adaptor);
// // Create input jax and a (blank) document using it // 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 html = mathjax.document('', { InputJax: tex, // // Other document options go here // });
// // Create a MathML serializer // const visitor = new SerializedMmlVisitor(); const toMathML = (node => visitor.visitTree(node, html));
// // Convert the math from the command line // const mml = html.convert(process.argv[2] || '', { display: true, em: EM, ex: EX, containerWidth: WIDTH, end: STATE.CONVERT // stop after conversion to MathML });
// // Set up the speech engine to use English // const locale = process.argv[3] || 'en'; const modality = locale === 'nemeth' || locale === 'euro' ? 'braille' : 'speech'; await setupEngine({locale, modality}).then(() => engineReady());
// // Produce the speech for the converted MathML // console.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 :func: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,
.. code-block:: shell
node tex2speech.mjs '\frac{a}{b}'
would generate the phrase
.. code-block::
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,
.. code-block:: shell
node tex2speech.mjs '\frac{a}{b}' de
would generate the German phrase
.. code-block::
Anfang Bruch a durch b Ende Bruch
while
.. code-block:: shell
node tex2speech.mjs '\frac{a}{b}' nemeth
would generate the Braille code
.. code-block::
⠹⠁⠌⠃⠼
The available locales can be found in the :file:bundle/sre/mathmaps
directory.
There are several different speech rulesets that SRE can use,
including clearspeak, mathspeak, and chromvox rules. You can
add a :data:domain option to the list passed to
:func:setupEngine() in order to specify which of these rulesets to
use. The default is mathspeak.
.. _direct-tex2svg-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
:meth:html.renderPromise() rather than :meth:html.convertPromise().
This is illustrated with the example below, this time using SVG output rather than CHTML output.
.. code-block:: javascript :linenos: :emphasize-lines: 1, 8, 32-36, 43, 48-54, 57, 63-66, 68-75, 77-81 :caption: tex2svg-page.mjs
import fs from 'fs';
// // Load the modules needed for MathJax // import {mathjax} from '@mathjax/src/js/mathjax.js'; import {TeX} from '@mathjax/src/js/input/tex.js'; import {SVG} from '@mathjax/src/js/output/svg.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';
// // Import the needed TeX packages // 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';
// // The em and ex sizes to use during the conversion // const EM = 16; // size of an em in pixels const EX = 8; // size of an ex in pixels
// // Create DOM adaptor and register it for HTML documents // const adaptor = liteAdaptor({fontSize: EM}); RegisterHTMLHandler(adaptor);
// // Read the HTML file // const htmlfile = fs.readFileSync(process.argv[2] || 0, 'utf8');
// // Create input and output jax and a document using them on the HTML file // const tex = new TeX({ packages: ['base', 'ams', 'newcommand', 'noundefined'], formatError(jax, err) {console.error(err.message); return jax.formatError(err)}, // // Other TeX configuration goes here // }); const svg = new SVG({ fontCache: 'global', exFactor: EX / EM, // // Any output options go here // }); const html = mathjax.document(htmlfile, { InputJax: tex, OutputJax: svg, // // Other document options go here // });
// // Wait for the typesetting to finish // await html.renderPromise();
// // If no math was found on the page, remove the stylesheet and font cache (if any) // if (Array.from(html.math).length === 0) { adaptor.remove(svg.svgStyles); const cache = adaptor.elementById(adaptor.body(html.document), 'MJX-SVG-global-cache'); if (cache) adaptor.remove(cache); }
// // Output the resulting HTML // console.log(adaptor.doctype(html.document)); console.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
:data: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
:meth: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 :func:formatError() function on line 43 now logs the
error and then calls the default :meth: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
.. code-block:: javascript
import {AssistiveMmlHandler} from '@mathjax/src/js/a11y/assistive-mml.js';
and change line 31 to be
.. code-block:: javascript
AssistiveHandler(RegisterHTMLHandler(adaptor));
That will cause MathJax to include visually hidden MathML that can be read by screen readers.
See the MathJax node demos <https://github.com/mathjax/MathJax-demos-node#MathJax-demos-node>__
for additional examples of how to use MathJax from a node
application. In particular, see the non-component-based examples <https://github.com/mathjax/MathJax-demos-node/tree/master/direct#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.
|-----|