85 lines
2.8 KiB
JavaScript
85 lines
2.8 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* render_math.js — Batch LaTeX-math renderer for the IHUFT MkDocs build.
|
|
*
|
|
* Reads a JSON array of math expressions from stdin and writes a JSON array
|
|
* of rendered HTML strings to stdout. Each input item has the shape:
|
|
*
|
|
* { "tex": "<LaTeX source>", "display": <bool> }
|
|
*
|
|
* Each output item has the shape:
|
|
*
|
|
* { "ok": true, "html": "<rendered HTML>" } // success
|
|
* { "ok": false, "error": "<KaTeX error msg>" } // failure
|
|
*
|
|
* Invoked by hooks/math_prerender.py. All math in a page is rendered in a
|
|
* single subprocess call so that the per-page overhead of spawning Node is
|
|
* amortised across all equations on the page.
|
|
*
|
|
* KaTeX produces both an HTML representation (for visual rendering) and an
|
|
* MathML representation that includes the original LaTeX source in an
|
|
* `<annotation encoding="application/x-tex">` element. This means the LaTeX
|
|
* source travels with the rendered math and can be recovered by users for
|
|
* copy-and-paste, without any extra plumbing.
|
|
*/
|
|
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
// Resolve KaTeX from the local node_tools tree regardless of cwd.
|
|
const KATEX_PATH = path.resolve(__dirname, '..', 'node_tools', 'node_modules', 'katex');
|
|
const katex = require(KATEX_PATH);
|
|
|
|
// Common macros the IHUFT proposal relies on but that KaTeX does not bundle
|
|
// by default. Extend this object as new macros appear in the source.
|
|
const MACROS = {
|
|
// None at present. KaTeX already supports \tag, \boxed, \aligned,
|
|
// \begin{aligned}, \operatorname, \mathrm, \mathfrak, \mathcal,
|
|
// \mathbb, \mathbf, \longleftrightarrow, \longrightarrow, \Vert,
|
|
// \hookrightarrow, \blacksquare, etc., out of the box.
|
|
};
|
|
|
|
function renderItem(item) {
|
|
try {
|
|
const html = katex.renderToString(item.tex, {
|
|
displayMode: !!item.display,
|
|
output: 'htmlAndMathml',
|
|
throwOnError: false,
|
|
strict: 'ignore',
|
|
trust: true,
|
|
macros: MACROS,
|
|
});
|
|
return { ok: true, html: html };
|
|
} catch (err) {
|
|
return { ok: false, error: String(err && err.message || err) };
|
|
}
|
|
}
|
|
|
|
function main() {
|
|
let raw;
|
|
try {
|
|
raw = fs.readFileSync(0, 'utf-8');
|
|
} catch (err) {
|
|
console.error('render_math.js: failed to read stdin:', err.message);
|
|
process.exit(2);
|
|
}
|
|
|
|
let items;
|
|
try {
|
|
items = JSON.parse(raw);
|
|
} catch (err) {
|
|
console.error('render_math.js: invalid JSON on stdin:', err.message);
|
|
process.exit(2);
|
|
}
|
|
|
|
if (!Array.isArray(items)) {
|
|
console.error('render_math.js: expected a JSON array of math items');
|
|
process.exit(2);
|
|
}
|
|
|
|
const results = items.map(renderItem);
|
|
process.stdout.write(JSON.stringify(results));
|
|
}
|
|
|
|
main();
|