Skip to content

Commit d581791

Browse files
committed
Merge branch 'page-png'
2 parents 2c335fb + 01b2b47 commit d581791

File tree

2 files changed

+222
-14
lines changed

2 files changed

+222
-14
lines changed

bin/page2png

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
#! /usr/bin/env node
2+
3+
/************************************************************************
4+
*
5+
* page2png
6+
*
7+
* Reads an HTML5 file from stdin that contains math
8+
* and writes a new HTML5 document to stdout that
9+
* contains PNG versions of the math instead.
10+
*
11+
* ----------------------------------------------------------------------
12+
*
13+
* Copyright (c) 2014 The MathJax Consortium
14+
*
15+
* Licensed under the Apache License, Version 2.0 (the "License");
16+
* you may not use this file except in compliance with the License.
17+
* You may obtain a copy of the License at
18+
*
19+
* http://www.apache.org/licenses/LICENSE-2.0
20+
*
21+
* Unless required by applicable law or agreed to in writing, software
22+
* distributed under the License is distributed on an "AS IS" BASIS,
23+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
24+
* See the License for the specific language governing permissions and
25+
* limitations under the License.
26+
*/
27+
28+
var mjAPI = require("../lib/mj-page.js");
29+
var fs = require('fs');
30+
var jsdom = require('jsdom').jsdom;
31+
32+
var argv = require("yargs")
33+
.strict()
34+
.usage("Usage: page2png [options] < input.html > output.html",{
35+
preview: {
36+
boolean: true,
37+
describe: "make PNG into a Mathjax preview"
38+
},
39+
speech: {
40+
boolean: true,
41+
describe: "include speech text"
42+
},
43+
speechrules: {
44+
default: "mathspeak",
45+
describe: "ruleset to use for speech text (chromevox or mathspeak)"
46+
},
47+
speechstyle: {
48+
default: "default",
49+
describe: "style to use for speech text (default, brief, sbrief)"
50+
},
51+
linebreaks: {
52+
boolean: true,
53+
describe: "perform automatic line-breaking"
54+
},
55+
nodollars: {
56+
boolean: true,
57+
describe: "don't use single-dollar delimiters"
58+
},
59+
format: {
60+
default: "AsciiMath,TeX,MathML",
61+
describe: "input format(s) to look for"
62+
},
63+
eqno: {
64+
default: "none",
65+
describe: "equation number style (none, AMS, or all)"
66+
},
67+
img: {
68+
default: "",
69+
describe: "make external svg images with this name prefix"
70+
},
71+
dpi: {
72+
default: "144",
73+
describe: "the dpi for the PNG images"
74+
},
75+
font: {
76+
default: "TeX",
77+
describe: "web font to use"
78+
},
79+
ex: {
80+
default: 6,
81+
describe: "ex-size in pixels"
82+
},
83+
width: {
84+
default: 100,
85+
describe: "width of container in ex"
86+
}
87+
})
88+
.argv;
89+
90+
argv.format = argv.format.split(/ *, */);
91+
if (argv.font === "STIX") argv.font = "STIX-Web";
92+
mjAPI.config({MathJax: {SVG: {font: argv.font}}});
93+
mjAPI.start();
94+
95+
//
96+
// Process an HTML file:
97+
//
98+
function processHTML(html,callback) {
99+
var document = jsdom(html,null,{features:{FetchExternalResources: false}});
100+
var xmlns = getXMLNS(document);
101+
mjAPI.typeset({
102+
html: document.body.innerHTML,
103+
renderer: "PNG", dpi: argv.dpi,
104+
inputs: argv.format,
105+
equationNumbers: argv.eqno,
106+
singleDollars: !argv.nodollars,
107+
useGlobalCache: false,
108+
addPreview: argv.preview,
109+
speakText: argv.speech,
110+
speakRuleset: argv.speechrules.replace(/^chromevox$/i,"default"),
111+
speakStyle: argv.speechstyle,
112+
ex: argv.ex, width: argv.width,
113+
linebreaks: argv.linebreaks,
114+
xmlns:xmlns
115+
}, function (result) {
116+
document.body.innerHTML = result.html;
117+
document.head.appendChild(document.body.firstChild);
118+
if (argv.img !== "") {
119+
var img = document.getElementsByClassName("MathJax_PNG_IMG");
120+
for (var i = 0, m = img.length; i < m; i++) {
121+
var N = (i+1).toString(); while (N.length < 4) {N = "0"+N}
122+
var file = argv.img+N+".png";
123+
fs.writeFileSync(file,new Buffer(img[i].src.replace(/^.*?,/,""),"base64"));
124+
img[i].src = file;
125+
}
126+
}
127+
var HTML = "<!DOCTYPE html>\n"+document.documentElement.outerHTML.replace(/^(\n|\s)*/,"");
128+
callback(HTML);
129+
});
130+
}
131+
132+
//
133+
// Look up the MathML namespace from the <html> attributes
134+
//
135+
function getXMLNS(document) {
136+
var html = document.head.parentNode;
137+
for (var i = 0, m = html.attributes.length; i < m; i++) {
138+
var attr = html.attributes[i];
139+
if (attr.nodeName.substr(0,6) === "xmlns:" &&
140+
attr.nodeValue === "http://www.w3.org/1998/Math/MathML")
141+
{return attr.nodeName.substr(6)}
142+
}
143+
return "mml";
144+
}
145+
146+
//
147+
// Read the input file and collect the file contents
148+
// When done, process the HTML.
149+
//
150+
var html = [];
151+
process.stdin.on("readable",function (block) {
152+
var chunk = process.stdin.read();
153+
if (chunk) html.push(chunk.toString('utf8'));
154+
});
155+
process.stdin.on("end",function () {
156+
processHTML(html.join(""), function(html) {
157+
process.stdout.write(html);
158+
});
159+
});

lib/mj-page.js

Lines changed: 63 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@
2828
var http = require('http');
2929
var fs = require('fs');
3030
var path = require('path');
31+
var fmt = require('util').format;
3132
var jsdom = require("jsdom").jsdom;
33+
var exec = require('child_process').exec;
3234
var speech = require('speech-rule-engine');
3335

3436
var displayMessages = false; // don't log Message.Set() calls
@@ -50,6 +52,7 @@ var defaults = {
5052
inputs: ["AsciiMath","TeX","MathML"], // the inputs formats to support
5153
renderer: "SVG", // the output format
5254
// ("SVG", "NativeMML", "IMG", "PNG", or "None")
55+
dpi: 144, // dpi for png image
5356

5457
addPreview: false, // turn turn into a MathJax preview, and keep the jax
5558
removeJax: true, // remove MathJax <script> tags?
@@ -72,11 +75,14 @@ var STATE = {
7275
};
7376

7477
var MathJaxPath = "file://"+require.resolve('MathJax/unpacked/MathJax');
78+
var BatikRasterizerPath = path.resolve(__dirname,'..','batik/batik-rasterizer.jar');
7579
var MathJaxConfig; // configuration for when starting MathJax
7680
var MathJax; // filled in once MathJax is loaded
7781
var serverState = STATE.STOPPED; // nothing loaded yet
7882
var timer; // used to reset MathJax if it runs too long
7983

84+
var tmpfile = "/tmp/mj-single-svg"; // file name prefix to use for temp files
85+
8086
var document, window, content, html; // the DOM elements
8187

8288
var queue = []; // queue of typesetting requests of the form [data,callback]
@@ -535,6 +541,18 @@ function AdjustSVG() {
535541
//
536542
nodes = document.querySelectorAll(".MathJax_SVG[role=textbox], .MathJax_SVG_Display[role=textbox]");
537543
for (i = nodes.length-1; i >= 0; i--) nodes[i].setAttribute("role","math");
544+
//
545+
// Fix a problem with jsdom not setting width and height CSS.
546+
//
547+
nodes = document.querySelectorAll(".MathJax_SVG > span > span");
548+
for (i = nodes.length-1; i >= 0; i--) {
549+
var child = nodes[i].firstChild;
550+
SetWH(nodes[i],child.getAttribute("width"),child.getAttribute("height"));
551+
}
552+
//
553+
// Add speech text, if needed
554+
//
555+
if (data.speakText) {callback = GetSpeech()}
538556
}
539557
if (data.renderer === "SVG") {
540558
//
@@ -552,18 +570,6 @@ function AdjustSVG() {
552570
styles.innerHTML = STYLES;
553571
styles.id="MathJax_SVG_styles";
554572
content.insertBefore(styles,content.firstChild);
555-
//
556-
// Fix a problem with jsdom not setting width and height CSS.
557-
//
558-
nodes = document.querySelectorAll(".MathJax_SVG > span > span");
559-
for (i = nodes.length-1; i >= 0; i--) {
560-
var child = nodes[i].firstChild;
561-
SetWH(nodes[i],child.getAttribute("width"),child.getAttribute("height"));
562-
}
563-
//
564-
// Add speech text, if needed
565-
//
566-
if (data.speakText) {callback = GetSpeech()}
567573
}
568574
return callback;
569575
}
@@ -580,11 +586,12 @@ function GetSpeech() {
580586
if (!queue) {queue = MathJax.Callback.Queue()}
581587
return queue.Push(err.restart,window.Array(SPEAK,svg));
582588
}
589+
jax.speech = speech.processExpression(mml);
583590
svg.setAttribute("role","math");
584591
svg.setAttribute("aria-labelledby",id+"-Title "+id+"-Desc");
585592
for (var i = 0, m = svg.childNodes.length; i < m; i++)
586593
svg.childNodes[i].setAttribute("aria-hidden",true);
587-
var node = MathJax.HTML.Element("desc",{id:id+"-Desc"},[speech.processExpression(mml)]);
594+
var node = MathJax.HTML.Element("desc",{id:id+"-Desc"},[jax.speech]);
588595
svg.insertBefore(node,svg.firstChild);
589596
node = MathJax.HTML.Element("title",{id:id+"-Title"},["Equation"]);
590597
svg.insertBefore(node,svg.firstChild);
@@ -612,7 +619,7 @@ function AdjustMML() {
612619
//
613620
function MakeIMG() {
614621
if (data.renderer === "IMG") {
615-
var n = 0, nodes = document.getElementsByClassName("MathJax_SVG");
622+
var nodes = document.getElementsByClassName("MathJax_SVG");
616623
for (var i = nodes.length-1; i >= 0; i--) {
617624
var svg = nodes[i].getElementsByTagName("svg")[0];
618625
var w = svg.getAttribute("width"), h = svg.getAttribute("height");
@@ -626,6 +633,7 @@ function MakeIMG() {
626633
className: "MathJax_SVG_IMG",
627634
});
628635
SetWH(img,w,h); // work around jsdom bug with width and height CSS
636+
if (data.speakText) img.setAttribute("alt",MathJax.Hub.getJaxFor(nodes[i]).speech);
629637
nodes[i].parentNode.replaceChild(img,nodes[i]);
630638
}
631639
}
@@ -635,6 +643,47 @@ function MakeIMG() {
635643
// Make PNG images and attach them to IMG tags
636644
//
637645
function MakePNG() {
646+
if (data.renderer === "PNG") {
647+
var synch = MathJax.Callback(function () {}); // for synchronization with MathJax
648+
var batikCommand = fmt("java -jar %s -dpi %d '%s.svg'",BatikRasterizerPath,data.dpi,tmpfile);
649+
var tmpSVG = tmpfile+".svg", tmpPNG = tmpfile+".png";
650+
var nodes = document.getElementsByClassName("MathJax_SVG");
651+
var check = function (err) {if (err) {AddError(err.message); return true}}
652+
var PNG = function (i) {
653+
if (i < 0) return synch(); // signal everything is done
654+
var svg = nodes[i].getElementsByTagName("svg")[0];
655+
var w = svg.getAttribute("width"), h = svg.getAttribute("height");
656+
var css = svg.style.cssText+" width:"+w+"; height:"+h;
657+
svg.style.cssText = ""; // this will be handled by the <img> tag instead
658+
svg.setAttribute("xmlns","http://www.w3.org/2000/svg");
659+
svg = [
660+
'<?xml version="1.0" standalone="no"?>',
661+
'<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">',
662+
svg.outerHTML
663+
].join("\n");
664+
fs.writeFile(tmpSVG,svg,function (err) {
665+
if (check(err)) return PNG(i-1);
666+
exec(batikCommand, function (err,stdout,stderr) {
667+
if (check(err)) {fs.unlinkSync(tmpSVG); return PNG(i-1)}
668+
fs.readFile(tmpPNG,null,function (err,buffer) {
669+
if (!check(err)) {
670+
var img = MathJax.HTML.Element("img",{
671+
src:"data:image/png;base64,"+buffer.toString('base64'),
672+
style:{cssText:css}, className: "MathJax_PNG_IMG",
673+
});
674+
SetWH(img,w,h); // work around jsdom bug with width and height CSS
675+
if (data.speakText) img.setAttribute("alt",MathJax.Hub.getJaxFor(nodes[i]).speech);
676+
nodes[i].parentNode.replaceChild(img,nodes[i]);
677+
}
678+
fs.unlinkSync(tmpSVG); fs.unlinkSync(tmpPNG);
679+
PNG(i-1);
680+
});
681+
});
682+
});
683+
}
684+
PNG(nodes.length-1);
685+
}
686+
return synch;
638687
}
639688

640689
//

0 commit comments

Comments
 (0)