Skip to content

Instantly share code, notes, and snippets.

@dpvc
Last active August 6, 2020 18:49
Show Gist options
  • Save dpvc/2cf897746549c0f92ceff40ca6a0d034 to your computer and use it in GitHub Desktop.
Save dpvc/2cf897746549c0f92ceff40ca6a0d034 to your computer and use it in GitHub Desktop.
MathJax filter for sag's produce with TikZ
#! /usr/bin/env node
/*************************************************************************
*
* tikz-filter
*
* Uses MathJax v3 to convert math to svg within TikZ svg output.
*
* ----------------------------------------------------------------------
*
* Copyright (c) 2020 The MathJax Consortium
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
//
// Load the packages needed for MathJax
//
require('mathjax-full/js/util/asyncLoad/node.js');
const {mathjax} = require('mathjax-full/js/mathjax.js');
const {TeX} = require('mathjax-full/js/input/tex.js');
const {SVG} = require('mathjax-full/js/output/svg.js');
const {RegisterHTMLHandler} = require('mathjax-full/js/handlers/html.js');
const {liteAdaptor} = require('mathjax-full/js/adaptors/liteAdaptor.js');
const {AllPackages} = require('mathjax-full/js/input/tex/AllPackages.js');
//
// Get the command-line arguments
//
var argv = require('yargs')
.demand(0).strict()
.usage('$0 [options] infile.svg > outfile.svg')
.options({
fontPaths: {
boolean: true,
default: false,
describe: 'use svg paths not cached paths'
},
em: {
default: 16,
describe: 'em-size in pixels'
},
packages: {
default: AllPackages.sort().join(', '),
describe: 'the packages to use, e.g. "base, ams"'
}
})
.argv;
//
// Read the SVG file
//
const svgfile = require('fs').readFileSync(argv._[0], 'utf8');
//
// Create DOM adaptor and register it for HTML documents
//
const adaptor = liteAdaptor({fontSize: argv.em});
const handler = RegisterHTMLHandler(adaptor);
//
// Patch MathJax 3.0.5 SVG bug:
//
if (mathjax.version === '3.0.5') {
const {SVGWrapper} = require('mathjax-full/js/output/svg/Wrapper.js');
const CommonWrapper = SVGWrapper.prototype.__proto__;
SVGWrapper.prototype.unicodeChars = function (text, variant) {
if (!variant) variant = this.variant || 'normal';
return CommonWrapper.unicodeChars.call(this, text, variant);
}
}
//
// Create an HTML document using the svg file
//
const html = mathjax.document(svgfile, {
InputJax: new TeX({packages: argv.packages.split(/\s*,\s*/)}),
OutputJax: new SVG({fontCache: (argv.fontPaths ? 'none' : 'local')})
});
//
// Process the SVG diagram
//
async function processSVG() {
const adaptor = html.adaptor;
//
// For each element with class PTX-math...
//
for (const ptx of adaptor.getElements(['.PTX-math'], html.document)) {
//
// Get the LaTeX and typeset it using MathJax
//
const math = adaptor.getAttribute(ptx, 'data-text');
const mjx = await mathjax.handleRetriesFor(() => html.convert(math));
const svg = adaptor.firstChild(mjx);
//
// Transfer the x, y, width, height from the bounding box rectangle
// to the typeset math to scale and position the math
//
const rect = adaptor.firstChild(ptx);
for (const {name, value} of adaptor.allAttributes(rect)) {
adaptor.setAttribute(svg, name, value);
}
//
// Remove unneeded attributes
//
adaptor.removeAttribute(svg, 'style');
adaptor.removeAttribute(svg, 'role');
adaptor.removeAttribute(svg, 'focusable');
//
// Replace the <g> with the typeset math
//
adaptor.replace(svg, ptx);
}
}
//
// Process the SVG and output the result
//
processSVG()
.then(() => console.log(adaptor.innerHTML(adaptor.body(html.document))))
.catch((err) => console.error(err));
#! /usr/bin/env node
/*************************************************************************
*
* tikz-html
*
* Uses MathJax v3 to convert math to svg and braille titles
* within TikZ svg output, with checkboxes to control which is shown.
*
* ----------------------------------------------------------------------
*
* Copyright (c) 2020 The MathJax Consortium
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
//
// Load the packages needed for MathJax
//
require('mathjax-full/js/util/asyncLoad/node.js');
const {mathjax} = require('mathjax-full/js/mathjax.js');
const {TeX} = require('mathjax-full/js/input/tex.js');
const {SVG} = require('mathjax-full/js/output/svg.js');
const {RegisterHTMLHandler} = require('mathjax-full/js/handlers/html.js');
const {liteAdaptor} = require('mathjax-full/js/adaptors/liteAdaptor.js');
const {STATE} = require('mathjax-full/js/core/MathItem.js');
const {AllPackages} = require('mathjax-full/js/input/tex/AllPackages.js');
//
// Get the command-line arguments
//
var argv = require('yargs')
.demand(0).strict()
.usage('$0 [options] infile.svg > outfile.svg')
.options({
braille: {
boolean: true,
default: true,
describe: 'produce braille output'
},
svg: {
boolean: true,
default: true,
describe: 'produce svg output'
},
fontPaths: {
boolean: true,
default: false,
describe: 'use svg paths not cached paths'
},
em: {
default: 16,
describe: 'em-size in pixels'
},
packages: {
default: AllPackages.sort().join(', '),
describe: 'the packages to use, e.g. "base, ams"'
}
})
.argv;
//
// Load SRE if needed for speech or braille
//
const {sreReady} = (argv.braille ? require('mathjax-full/js/a11y/sre.js') : {sreReady: Promise.resolve()});
//
// Read the SVG file
//
const svgfile = require('fs').readFileSync(argv._[0], 'utf8');
//
// Create DOM adaptor and register it for HTML documents
//
const adaptor = liteAdaptor({fontSize: argv.em});
const handler = RegisterHTMLHandler(adaptor);
//
// Patch MathJax 3.0.5 SVG bug:
//
if (mathjax.version === '3.0.5') {
const {SVGWrapper} = require('mathjax-full/js/output/svg/Wrapper.js');
const CommonWrapper = SVGWrapper.prototype.__proto__;
SVGWrapper.prototype.unicodeChars = function (text, variant) {
if (!variant) variant = this.variant || 'normal';
return CommonWrapper.unicodeChars.call(this, text, variant);
}
}
//
// Create a MathML serializer
//
const {SerializedMmlVisitor} = require('mathjax-full/js/core/MmlTree/SerializedMmlVisitor.js');
const visitor = new SerializedMmlVisitor();
const toMathML = (node => visitor.visitTree(node, html));
//
// Create an HTML document using the svg file
//
const html = mathjax.document(svgfile, {
InputJax: new TeX({packages: argv.packages.split(/\s*,\s*/)}),
OutputJax: new SVG({fontCache: (argv.fontPaths ? 'none' : 'local')})
});
//
// Function for adding SVG output for labels
//
async function addSVG(ptx, math, rect) {
if (!argv.svg) return;
//
// Typeset to SVG
//
const mjx = await mathjax.handleRetriesFor(() => html.convert(math));
const svg = adaptor.firstChild(mjx);
//
// Transfer the x, y, width, height from the bounding box rectangle
// to the typeset math to scale and position the math
//
for (const {name, value} of adaptor.allAttributes(rect)) {
adaptor.setAttribute(svg, name, value);
}
//
// Remove unneeded attributes
//
adaptor.removeAttribute(svg, 'style');
adaptor.removeAttribute(svg, 'role');
adaptor.removeAttribute(svg, 'focusable');
//
// Replace the <rect> with the typeset math
//
adaptor.append(ptx, svg);
}
//
// Function for adding braille output for labels
//
async function addBraille(ptx, math, rect) {
if (!argv.braille) return;
//
// Add the braille title
//
const mml = await mathjax.handleRetriesFor(() => html.convert(math, {end: STATE.COMPILED}));
const braille = SRE.toSpeech(toMathML(mml));
const g = adaptor.node('g', {class: 'ptx-braille'}, [adaptor.node('text', {}, [adaptor.text(braille)])]);
//
// Move the braile into place
//
const x = parseFloat(adaptor.getAttribute(rect, 'x'));
const y = parseFloat(adaptor.getAttribute(rect, 'y')) + 6;
adaptor.setAttribute(g, 'transform', `translate(${x}, ${y}) scale(.6)`);
adaptor.append(ptx, g);
}
//
// Process the SVG diagram
//
async function processSVG() {
//
// For each element with class PTX-math or PTX-text...
//
for (const ptx of adaptor.getElements(['.PTX-math', '.PTX-text'], html.document)) {
//
// Mark the container as containing MathJax output (for CSS selection of alternatives)
//
adaptor.addClass(ptx, 'PTX-MJX');
//
// Get the text or LaTeX and typeset it using MathJax
//
let math = adaptor.textContent(adaptor.remove(adaptor.firstChild(ptx)));
if (adaptor.hasClass(ptx, 'PTX-text')) {
math = '\\hbox{' + math + '}';
}
//
// Get the bounding box rectangle
//
const rect = adaptor.remove(adaptor.firstChild(ptx));
//
// Add various output formats
//
await addSVG(ptx, math, rect);
await addBraille(ptx, math, rect);
}
}
//
// Process the SVG and output the result
//
(async function () {
if (argv.braille) {
SRE.setupEngine({modality: 'braille', locale: 'nemeth'});
await sreReady();
}
await processSVG();
console.log([
'<!DOCTYPE html>',
'<head>',
'<meta charset="utf-8" />',
'<title>Sample SVG with MathJax output</title>',
'<style>',
' body.no-svg .PTX-MJX > svg {display: none}',
' body.no-braille .PTX-MJX > g.ptx-braille {display: none}',
'</style>',
'</head>',
`<body class="no-${argv.svg ? 'braille' : 'svg'}">`,
'<label>Show:',
'<select onchange="document.body.className = this.value">',
argv.svg ? '<option value="no-braille">SVG</option>' : '',
argv.braille ? '<option value="no-svg">Braille</option>' : '',
'</select>',
'<br>',
adaptor.innerHTML(adaptor.body(html.document)),
'</body>',
'</html>'
].join('\n'));
})().catch((err) => console.error(err));
@dpvc
Copy link
Author

dpvc commented Aug 6, 2020

This filter converts an SVG file produces from dvisvgm from a TikZ diagram to one that has the math properly typeset by MathJax.

@dpvc
Copy link
Author

dpvc commented Aug 6, 2020

I've added tikz-html that produces an html file containing an svg that has multiple versions of the math (svg and braille in this case) with a selector that allows you to show one or the other.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment