-
Notifications
You must be signed in to change notification settings - Fork 0
/
app.js
266 lines (266 loc) · 11.9 KB
/
app.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
"use strict";
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
if (typeof b !== "function" && b !== null)
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
exports.__esModule = true;
exports.MathMlReplacer = exports.MathMLNow = void 0;
var mjAPI = require("mathjax-node-sre");
var stream = require("stream");
mjAPI.config({
MathJax: {
// traditional MathJax configuration
menuSettings: {
texHints: false,
semantics: true
}
}
});
/**
* Replace unusual unicode characters with their HTML entities
* @param rawStr
*/
function replaceWithHTMLEntities(rawStr) {
return rawStr.replace(/[\u00A0-\u9999]/gim, function (i) {
return "&#" + i.charCodeAt(0) + ";";
});
}
/**
* Generate a promise that resolves to a string of HTML that will display the inputted
* maths equation in a way understood by all browsers
* @param mathString The string representation of the maths equation you wish to display
* @param options The MathMLNowOptions object that will control the behaviour of the rendered equation
* @param id The ID number to use (should be incremented if called multiple times on the same page for unique ids)
*/
function MathMLNow(mathString, options, id) {
//Default font size is 18
if (!options.fontSize)
options.fontSize = 18;
//Default vertical whitespace margin is 0%
if (!options.verticalMarginPercent)
options.verticalMarginPercent = 0;
//Default horizontal whitespace margin is 0%
if (!options.horizontalMarginPercent)
options.horizontalMarginPercent = 0;
//Default id is 1
id = id || 1;
return mjAPI.typeset({
math: mathString,
format: options.formatName,
mmlNode: true,
svgNode: true,
speakText: true
}).then(function (data) {
var mml = data.mmlNode;
var svg = data.svgNode;
//MathJax likes to make its content a relative size - but this isn't valid HTML, and breaks SVG2PNG
//So we convert back to flat pixels
var height = Math.ceil(Number.parseFloat(data.height) * 11 *
options.fontSize / 18);
var verticalMargin = Math.round(height * (options.verticalMarginPercent / 100));
var heightWithMargin = height + verticalMargin * 2;
var width = Math.ceil(Number.parseFloat(data.width) * 10.5 *
options.fontSize / 18);
var horizontalMargin = Math.round(width * (options.horizontalMarginPercent / 100));
var widthWithMargin = width + horizontalMargin * 2;
svg.setAttribute("height", height.toString());
svg.setAttribute("width", width.toString());
svg.removeAttribute("style");
//Color the SVG
if (options.fontColor)
svg.setAttribute("color", options.fontColor);
//Center the SVG
if (!!horizontalMargin)
svg.setAttribute("x", horizontalMargin.toString());
if (!!verticalMargin)
svg.setAttribute("y", verticalMargin.toString());
//Set the ID for the SVG label
var titleId = "MathJax-SVG-" + id + "-Title";
svg.setAttribute("aria-labelledby", titleId);
svg.querySelector("title").id = titleId;
//Scaling and coloring the MathML requires a <mstyle> element
var mstyle = mml.ownerDocument.createElementNS("http://www.w3.org/1998/Math/MathML", "mstyle");
mstyle.setAttribute("mathsize", (Math.floor(options.fontSize)).toString() + "pt");
if (options.fontColor)
mstyle.setAttribute("mathcolor", options.fontColor);
//Move the math nodes into the style node
var mathChildNodes = Array.from(mml.childNodes);
mathChildNodes.forEach(function (value) {
mstyle.appendChild(value);
});
mml.appendChild(mstyle);
//create our <svg>, <switch> and <foreignObject> elements
var parentSvg = mml.ownerDocument.createElementNS("http://www.w3.org/2000/svg", "svg");
var switchElem = mml.ownerDocument.createElementNS("http://www.w3.org/2000/svg", "switch");
var foreignObject = mml.ownerDocument.createElementNS("http://www.w3.org/2000/svg", "foreignObject");
//If MathML is supported, we'll show the MathML - otherwise, we'll show the SVG backup
//We use <switch> and requiredExtensions to do this
foreignObject.setAttribute("requiredExtensions", "http://www.w3.org/1998/Math/MathML");
foreignObject.setAttribute("height", heightWithMargin.toString());
foreignObject.setAttribute("width", widthWithMargin.toString());
foreignObject.appendChild(mml);
switchElem.appendChild(foreignObject);
switchElem.appendChild(svg);
parentSvg.appendChild(switchElem);
parentSvg.setAttribute("height", heightWithMargin.toString());
parentSvg.setAttribute("width", widthWithMargin.toString());
parentSvg.setAttribute("role", "presentation");
return replaceWithHTMLEntities(parentSvg.outerHTML);
});
}
exports.MathMLNow = MathMLNow;
/**
* A Gulp-style replacer function that will rewrite large chunks of text (like a HTML page),
* replacing instances of $$[Math string]$$ with the corresponding MathMLNow
*/
var MathMlReplacer = /** @class */ (function (_super) {
__extends(MathMlReplacer, _super);
/**
* A Gulp-style replacer function that will rewrite large chunks of text (like a HTML page),
* replacing instances of $$[Math string]$$ with the corresponding MathML
* @param options The MathMLNowOptions object that will control the behaviour of the rendered equation
*/
function MathMlReplacer(options) {
var _this = _super.call(this, { objectMode: true }) || this;
_this.state = 1;
_this.options = options || { formatName: "TeX" };
_this.options.formatName = options.formatName || "TeX";
return _this;
}
/**
* Like the normal JavaScript string replacer, but with an async callback function
* Solution taken from https://stackoverflow.com/a/33631886/7077589
* @param str The stream to replace
* @param re The regex to do matches with
* @param callback The async function to apply to the regex matches
*/
MathMlReplacer.prototype.replaceAsync = function (str, re, callback) {
// http://es5.github.io/#x15.5.4.11
str = String(str);
var parts = [];
var i = 0;
if (re instanceof RegExp) {
//Regex search function - could have many matches
if (re.global)
re.lastIndex = i;
var m = void 0;
while (m = re.exec(str)) {
var args = m.concat([m.index.toString(), m.input]);
parts.push(str.slice(i, m.index), callback.apply(null, args));
i = re.lastIndex;
if (!re.global)
break; // for non-global regexes only take the first match
if (m[0].length == 0)
re.lastIndex++;
}
}
else {
//This is a string search function - it only has one match
re = String(re);
i = str.indexOf(re);
parts.push(str.slice(0, i), callback.apply(null, [re, i, str]));
i += re.length;
}
parts.push(str.slice(i));
return Promise.all(parts).then(function (strings) {
return strings.join("");
});
};
/**
* Apply MathMLNow to a vinyl file
* @param file The file to assign our result to
* @param data The string data we read from the file
* @param enc The file encoding the file was initially in
* @param callback The function to call when we are done
*/
MathMlReplacer.prototype.rewriteFile = function (file, data, enc, callback) {
var _this = this;
//First, replace the ones with all four properties, and then down from there
this.replaceAsync(data, /\$\$(.+?)\|\|(\d+)\|\|(\d+)\|\|(\d+)\|\|(\w+)\$\$/g, function (match, math, fontSize, vMargin, hMargin, fontColor) {
var indiviualOptions = Object.create(_this.options);
indiviualOptions.fontSize = Number(fontSize);
indiviualOptions.verticalMarginPercent = Number(vMargin);
indiviualOptions.horizontalMarginPercent = Number(hMargin);
indiviualOptions.fontColor = fontColor;
return MathMLNow(math, indiviualOptions, _this.state++);
}).then(function (data) {
return _this.replaceAsync(data, /\$\$(.+?)\|\|(\d+)\|\|(\d+)\|\|(\d+)\$\$/g, function (match, math, fontSize, vMargin, hMargin) {
var indiviualOptions = Object.create(_this.options);
indiviualOptions.fontSize = Number(fontSize);
indiviualOptions.verticalMarginPercent = Number(vMargin);
indiviualOptions.horizontalMarginPercent = Number(hMargin);
return MathMLNow(math, indiviualOptions, _this.state++);
});
}).then(function (data) {
return _this.replaceAsync(data, /\$\$(.+?)\|\|(\d+)\|\|(\d+)\$\$/g, function (match, math, fontSize, vMargin) {
var indiviualOptions = Object.create(_this.options);
indiviualOptions.fontSize = Number(fontSize);
indiviualOptions.verticalMarginPercent = Number(vMargin);
return MathMLNow(math, indiviualOptions, _this.state++);
});
}).then(function (data) {
return _this.replaceAsync(data, /\$\$(.+?)\|\|(\d+)\$\$/g, function (match, math, fontSize) {
var indiviualOptions = Object.create(_this.options);
indiviualOptions.fontSize = Number(fontSize);
return MathMLNow(math, indiviualOptions, _this.state++);
});
}).then(function (data) {
return _this.replaceAsync(data, /\$\$(.+?)\$\$/g, function (match, math) {
return MathMLNow(math, _this.options, _this.state++);
});
}).then(function (processedTemp) {
//All done! Write to the file now!
file.contents = Buffer.from(processedTemp, enc);
callback(null, file);
})["catch"](function (reason) {
//If there was a fail, pass the reason why up the chain
callback(reason);
});
};
/**
* Reads a stream into memory so that we can run Regex on it
* @param stream The stream to read from
* @param enc The encoding of the stream
* @param callback A callback function to run when we are done
*/
MathMlReplacer.prototype.streamToString = function (stream, enc, callback) {
var chunks = [];
stream.on('data', function (chunk) {
chunks.push(chunk.toString(enc));
});
stream.on('end', function () {
callback(chunks.join(''));
});
};
/**
* @inheritdoc
*/
MathMlReplacer.prototype._transform = function (file, enc, callback) {
var _this = this;
if (file.isNull()) {
callback(null, file);
}
else if (file.isBuffer()) {
var data = file.contents.toString(enc);
this.rewriteFile(file, data, enc, callback);
}
else if (file.isStream()) {
this.streamToString(file.contents, enc, function (fileContents) {
_this.rewriteFile(file, fileContents, enc, callback);
});
}
};
return MathMlReplacer;
}(stream.Transform));
exports.MathMlReplacer = MathMlReplacer;