temml 0.10.33 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/Temml-Asana.css +16 -1
- package/dist/Temml-Fira.css +16 -1
- package/dist/Temml-Latin-Modern.css +16 -1
- package/dist/Temml-Libertinus.css +16 -1
- package/dist/Temml-Local.css +16 -1
- package/dist/Temml-STIX2.css +16 -1
- package/dist/temml.cjs +495 -163
- package/dist/temml.d.ts +3 -0
- package/dist/temml.js +495 -163
- package/dist/temml.min.js +1 -1
- package/dist/temml.mjs +495 -163
- package/dist/temmlPostProcess.js +7 -6
- package/package.json +2 -2
- package/src/auto-render.js +263 -0
- package/src/buildMathML.js +20 -0
- package/src/environments/array.js +119 -82
- package/src/environments/cd.js +2 -0
- package/src/functions/label.js +1 -1
- package/src/linebreaking.js +1 -1
- package/src/macros.js +3 -1
- package/src/mathMLTree.js +5 -0
- package/src/postProcess.js +7 -6
- package/src/symbols.js +1 -1
- package/temml.js +6 -0
- package/contrib/auto-render/README.md +0 -89
- package/contrib/auto-render/auto-render.js +0 -128
- package/contrib/auto-render/dist/auto-render.js +0 -214
- package/contrib/auto-render/dist/auto-render.min.js +0 -1
- package/contrib/auto-render/splitAtDelimiters.js +0 -84
- package/contrib/auto-render/test/auto-render-spec.js +0 -234
- package/contrib/auto-render/test/auto-render.js +0 -214
- package/contrib/auto-render/test/test_page.html +0 -59
package/dist/temmlPostProcess.js
CHANGED
@@ -11,7 +11,7 @@
|
|
11
11
|
* https://mit-license.org/
|
12
12
|
*/
|
13
13
|
|
14
|
-
const version = "0.
|
14
|
+
const version = "0.11.00";
|
15
15
|
|
16
16
|
function postProcess(block) {
|
17
17
|
const labelMap = {};
|
@@ -27,15 +27,15 @@
|
|
27
27
|
// No need to write a number into the text content of the element.
|
28
28
|
// A CSS counter has done that even if this postProcess() function is not used.
|
29
29
|
|
30
|
-
// Find any \label that refers to an AMS eqn number.
|
30
|
+
// Find any \label that refers to an AMS automatic eqn number.
|
31
31
|
while (true) {
|
32
|
+
if (parent.tagName === "mtable") { break }
|
32
33
|
const labels = parent.getElementsByClassName("tml-label");
|
33
34
|
if (labels.length > 0) {
|
34
|
-
parent.
|
35
|
-
labelMap[
|
35
|
+
const id = parent.attributes.id.value;
|
36
|
+
labelMap[id] = String(i);
|
36
37
|
break
|
37
38
|
} else {
|
38
|
-
if (parent.tagName === "mtable") { break }
|
39
39
|
parent = parent.parentElement;
|
40
40
|
}
|
41
41
|
}
|
@@ -48,7 +48,8 @@
|
|
48
48
|
if (labels.length > 0) {
|
49
49
|
const tags = parent.getElementsByClassName("tml-tag");
|
50
50
|
if (tags.length > 0) {
|
51
|
-
|
51
|
+
const id = parent.attributes.id.value;
|
52
|
+
labelMap[id] = tags[0].textContent;
|
52
53
|
}
|
53
54
|
}
|
54
55
|
}
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "temml",
|
3
|
-
"version": "0.
|
3
|
+
"version": "0.11.00",
|
4
4
|
"description": "TeX to MathML conversion in JavaScript.",
|
5
5
|
"main": "dist/temml.js",
|
6
6
|
"engines": {
|
@@ -44,6 +44,6 @@
|
|
44
44
|
"minify": "terser test/temml.js -o site/assets/temml.min.js -c -m && terser contrib/mhchem/mhchem.js -o site/assets/mhchem.min.js -c -m",
|
45
45
|
"build": "rollup --config ./utils/rollupConfig.mjs && yarn minify && node utils/insertPlugins.js",
|
46
46
|
"docs": "node utils/buildDocs.js",
|
47
|
-
"dist": "yarn build && node ./utils/copyfiles.js
|
47
|
+
"dist": "yarn build && node ./utils/copyfiles.js"
|
48
48
|
}
|
49
49
|
}
|
@@ -0,0 +1,263 @@
|
|
1
|
+
import ParseError from "./ParseError.js"
|
2
|
+
import { postProcess } from "./postProcess.js";
|
3
|
+
|
4
|
+
const findEndOfMath = function(delimiter, text, startIndex) {
|
5
|
+
// Adapted from
|
6
|
+
// https://github.com/Khan/perseus/blob/master/src/perseus-markdown.jsx
|
7
|
+
let index = startIndex;
|
8
|
+
let braceLevel = 0;
|
9
|
+
|
10
|
+
const delimLength = delimiter.length;
|
11
|
+
|
12
|
+
while (index < text.length) {
|
13
|
+
const character = text[index];
|
14
|
+
|
15
|
+
if (braceLevel <= 0 && text.slice(index, index + delimLength) === delimiter) {
|
16
|
+
return index;
|
17
|
+
} else if (character === "\\") {
|
18
|
+
index++;
|
19
|
+
} else if (character === "{") {
|
20
|
+
braceLevel++;
|
21
|
+
} else if (character === "}") {
|
22
|
+
braceLevel--;
|
23
|
+
}
|
24
|
+
|
25
|
+
index++;
|
26
|
+
}
|
27
|
+
|
28
|
+
return -1;
|
29
|
+
};
|
30
|
+
|
31
|
+
const escapeRegex = function(string) {
|
32
|
+
return string.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&");
|
33
|
+
};
|
34
|
+
|
35
|
+
const amsRegex = /^\\(?:begin|(?:eq)?ref){/
|
36
|
+
|
37
|
+
const splitAtDelimiters = function(text, delimiters) {
|
38
|
+
let index;
|
39
|
+
const data = [];
|
40
|
+
|
41
|
+
const regexLeft = new RegExp(
|
42
|
+
"(" + delimiters.map((x) => escapeRegex(x.left)).join("|") + ")"
|
43
|
+
)
|
44
|
+
|
45
|
+
while (true) {
|
46
|
+
index = text.search(regexLeft);
|
47
|
+
if (index === -1) {
|
48
|
+
break;
|
49
|
+
}
|
50
|
+
if (index > 0) {
|
51
|
+
data.push({
|
52
|
+
type: "text",
|
53
|
+
data: text.slice(0, index)
|
54
|
+
});
|
55
|
+
text = text.slice(index); // now text starts with delimiter
|
56
|
+
}
|
57
|
+
// ... so this always succeeds:
|
58
|
+
const i = delimiters.findIndex((delim) => text.startsWith(delim.left));
|
59
|
+
index = findEndOfMath(delimiters[i].right, text, delimiters[i].left.length);
|
60
|
+
if (index === -1) {
|
61
|
+
break;
|
62
|
+
}
|
63
|
+
const rawData = text.slice(0, index + delimiters[i].right.length);
|
64
|
+
const math = amsRegex.test(rawData)
|
65
|
+
? rawData
|
66
|
+
: text.slice(delimiters[i].left.length, index);
|
67
|
+
data.push({
|
68
|
+
type: "math",
|
69
|
+
data: math,
|
70
|
+
rawData,
|
71
|
+
display: delimiters[i].display
|
72
|
+
});
|
73
|
+
text = text.slice(index + delimiters[i].right.length);
|
74
|
+
}
|
75
|
+
|
76
|
+
if (text !== "") {
|
77
|
+
data.push({
|
78
|
+
type: "text",
|
79
|
+
data: text
|
80
|
+
});
|
81
|
+
}
|
82
|
+
|
83
|
+
return data;
|
84
|
+
};
|
85
|
+
|
86
|
+
const defaultDelimiters = [
|
87
|
+
{ left: "$$", right: "$$", display: true },
|
88
|
+
{ left: "\\(", right: "\\)", display: false },
|
89
|
+
// LaTeX uses $…$, but it ruins the display of normal `$` in text:
|
90
|
+
// {left: "$", right: "$", display: false},
|
91
|
+
// $ must come after $$
|
92
|
+
|
93
|
+
// Render AMS environments even if outside $$…$$ delimiters.
|
94
|
+
{ left: "\\begin{equation}", right: "\\end{equation}", display: true },
|
95
|
+
{ left: "\\begin{equation*}", right: "\\end{equation*}", display: true },
|
96
|
+
{ left: "\\begin{align}", right: "\\end{align}", display: true },
|
97
|
+
{ left: "\\begin{align*}", right: "\\end{align*}", display: true },
|
98
|
+
{ left: "\\begin{alignat}", right: "\\end{alignat}", display: true },
|
99
|
+
{ left: "\\begin{alignat*}", right: "\\end{alignat*}", display: true },
|
100
|
+
{ left: "\\begin{gather}", right: "\\end{gather}", display: true },
|
101
|
+
{ left: "\\begin{gather*}", right: "\\end{gather*}", display: true },
|
102
|
+
{ left: "\\begin{CD}", right: "\\end{CD}", display: true },
|
103
|
+
// Ditto \ref & \eqref
|
104
|
+
{ left: "\\ref{", right: "}", display: false },
|
105
|
+
{ left: "\\eqref{", right: "}", display: false },
|
106
|
+
|
107
|
+
{ left: "\\[", right: "\\]", display: true }
|
108
|
+
];
|
109
|
+
|
110
|
+
const firstDraftDelimiters = {
|
111
|
+
"$": [
|
112
|
+
{ left: "$$", right: "$$", display: true },
|
113
|
+
{ left: "$`", right: "`$", display: false },
|
114
|
+
{ left: "$", right: "$", display: false }
|
115
|
+
],
|
116
|
+
"(": [
|
117
|
+
{ left: "\\[", right: "\\]", display: true },
|
118
|
+
{ left: "\\(", right: "\\)", display: false }
|
119
|
+
]
|
120
|
+
}
|
121
|
+
|
122
|
+
const amsDelimiters = [
|
123
|
+
{ left: "\\begin{equation}", right: "\\end{equation}", display: true },
|
124
|
+
{ left: "\\begin{equation*}", right: "\\end{equation*}", display: true },
|
125
|
+
{ left: "\\begin{align}", right: "\\end{align}", display: true },
|
126
|
+
{ left: "\\begin{align*}", right: "\\end{align*}", display: true },
|
127
|
+
{ left: "\\begin{alignat}", right: "\\end{alignat}", display: true },
|
128
|
+
{ left: "\\begin{alignat*}", right: "\\end{alignat*}", display: true },
|
129
|
+
{ left: "\\begin{gather}", right: "\\end{gather}", display: true },
|
130
|
+
{ left: "\\begin{gather*}", right: "\\end{gather*}", display: true },
|
131
|
+
{ left: "\\begin{CD}", right: "\\end{CD}", display: true },
|
132
|
+
{ left: "\\ref{", right: "}", display: false },
|
133
|
+
{ left: "\\eqref{", right: "}", display: false }
|
134
|
+
];
|
135
|
+
|
136
|
+
const delimitersFromKey = key => {
|
137
|
+
if (key === "$" || key === "(") {
|
138
|
+
return firstDraftDelimiters[key];
|
139
|
+
} else if (key === "$+" || key === "(+") {
|
140
|
+
const firstDraft = firstDraftDelimiters[key.slice(0, 1)];
|
141
|
+
return firstDraft.concat(amsDelimiters)
|
142
|
+
} else if (key === "ams") {
|
143
|
+
return amsDelimiters
|
144
|
+
} else if (key === "all") {
|
145
|
+
return (firstDraftDelimiters["("]).concat(firstDraftDelimiters["$"]).concat(amsDelimiters)
|
146
|
+
} else {
|
147
|
+
return defaultDelimiters
|
148
|
+
}
|
149
|
+
}
|
150
|
+
|
151
|
+
/* Note: optionsCopy is mutated by this method. If it is ever exposed in the
|
152
|
+
* API, we should copy it before mutating.
|
153
|
+
*/
|
154
|
+
const renderMathInText = function(text, optionsCopy) {
|
155
|
+
const data = splitAtDelimiters(text, optionsCopy.delimiters);
|
156
|
+
if (data.length === 1 && data[0].type === "text") {
|
157
|
+
// There is no formula in the text.
|
158
|
+
// Let's return null which means there is no need to replace
|
159
|
+
// the current text node with a new one.
|
160
|
+
return null;
|
161
|
+
}
|
162
|
+
|
163
|
+
const fragment = document.createDocumentFragment();
|
164
|
+
|
165
|
+
for (let i = 0; i < data.length; i++) {
|
166
|
+
if (data[i].type === "text") {
|
167
|
+
fragment.appendChild(document.createTextNode(data[i].data));
|
168
|
+
} else {
|
169
|
+
const span = document.createElement("span");
|
170
|
+
let math = data[i].data;
|
171
|
+
// Override any display mode defined in the settings with that
|
172
|
+
// defined by the text itself
|
173
|
+
optionsCopy.displayMode = data[i].display;
|
174
|
+
try {
|
175
|
+
if (optionsCopy.preProcess) {
|
176
|
+
math = optionsCopy.preProcess(math);
|
177
|
+
}
|
178
|
+
// Importing render() from temml.js would be a circular dependency.
|
179
|
+
// So call the global version.
|
180
|
+
// eslint-disable-next-line no-undef
|
181
|
+
temml.render(math, span, optionsCopy);
|
182
|
+
} catch (e) {
|
183
|
+
if (!(e instanceof ParseError)) {
|
184
|
+
throw e;
|
185
|
+
}
|
186
|
+
optionsCopy.errorCallback(
|
187
|
+
"Temml auto-render: Failed to parse `" + data[i].data + "` with ",
|
188
|
+
e
|
189
|
+
);
|
190
|
+
fragment.appendChild(document.createTextNode(data[i].rawData));
|
191
|
+
continue;
|
192
|
+
}
|
193
|
+
fragment.appendChild(span);
|
194
|
+
}
|
195
|
+
}
|
196
|
+
|
197
|
+
return fragment;
|
198
|
+
};
|
199
|
+
|
200
|
+
const renderElem = function(elem, optionsCopy) {
|
201
|
+
for (let i = 0; i < elem.childNodes.length; i++) {
|
202
|
+
const childNode = elem.childNodes[i];
|
203
|
+
if (childNode.nodeType === 3) {
|
204
|
+
// Text node
|
205
|
+
const frag = renderMathInText(childNode.textContent, optionsCopy);
|
206
|
+
if (frag) {
|
207
|
+
i += frag.childNodes.length - 1;
|
208
|
+
elem.replaceChild(frag, childNode);
|
209
|
+
}
|
210
|
+
} else if (childNode.nodeType === 1) {
|
211
|
+
// Element node
|
212
|
+
const className = " " + childNode.className + " ";
|
213
|
+
const shouldRender =
|
214
|
+
optionsCopy.ignoredTags.indexOf(childNode.nodeName.toLowerCase()) === -1 &&
|
215
|
+
optionsCopy.ignoredClasses.every((x) => className.indexOf(" " + x + " ") === -1);
|
216
|
+
|
217
|
+
if (shouldRender) {
|
218
|
+
renderElem(childNode, optionsCopy);
|
219
|
+
}
|
220
|
+
}
|
221
|
+
// Otherwise, it's something else, and ignore it.
|
222
|
+
}
|
223
|
+
};
|
224
|
+
|
225
|
+
export const renderMathInElement = function(elem, options) {
|
226
|
+
if (!elem) {
|
227
|
+
throw new Error("No element provided to render");
|
228
|
+
}
|
229
|
+
|
230
|
+
const optionsCopy = {};
|
231
|
+
|
232
|
+
// Object.assign(optionsCopy, option)
|
233
|
+
for (const option in options) {
|
234
|
+
if (Object.prototype.hasOwnProperty.call(options, option)) {
|
235
|
+
optionsCopy[option] = options[option];
|
236
|
+
}
|
237
|
+
}
|
238
|
+
|
239
|
+
if (optionsCopy.fences) {
|
240
|
+
optionsCopy.delimiters = delimitersFromKey(optionsCopy.fences);
|
241
|
+
} else {
|
242
|
+
optionsCopy.delimiters = optionsCopy.delimiters || defaultDelimiters
|
243
|
+
}
|
244
|
+
optionsCopy.ignoredTags = optionsCopy.ignoredTags || [
|
245
|
+
"script",
|
246
|
+
"noscript",
|
247
|
+
"style",
|
248
|
+
"textarea",
|
249
|
+
"pre",
|
250
|
+
"code",
|
251
|
+
"option"
|
252
|
+
];
|
253
|
+
optionsCopy.ignoredClasses = optionsCopy.ignoredClasses || [];
|
254
|
+
// eslint-disable-next-line no-console
|
255
|
+
optionsCopy.errorCallback = optionsCopy.errorCallback || console.error;
|
256
|
+
|
257
|
+
// Enable sharing of global macros defined via `\gdef` between different
|
258
|
+
// math elements within a single call to `renderMathInElement`.
|
259
|
+
optionsCopy.macros = optionsCopy.macros || {};
|
260
|
+
|
261
|
+
renderElem(elem, optionsCopy);
|
262
|
+
postProcess(elem);
|
263
|
+
};
|
package/src/buildMathML.js
CHANGED
@@ -263,16 +263,36 @@ const glue = _ => {
|
|
263
263
|
return new mathMLTree.MathNode("mtd", [], [], { padding: "0", width: "50%" })
|
264
264
|
}
|
265
265
|
|
266
|
+
const labelContainers = ["mrow", "mtd", "mtable", "mtr"];
|
267
|
+
const getLabel = parent => {
|
268
|
+
for (const node of parent.children) {
|
269
|
+
if (node.type && labelContainers.includes(node.type)) {
|
270
|
+
if (node.classes && node.classes[0] === "tml-label") {
|
271
|
+
const label = node.label
|
272
|
+
return label
|
273
|
+
} else {
|
274
|
+
const label = getLabel(node)
|
275
|
+
if (label) { return label }
|
276
|
+
}
|
277
|
+
} else if (!node.type) {
|
278
|
+
const label = getLabel(node)
|
279
|
+
if (label) { return label }
|
280
|
+
}
|
281
|
+
}
|
282
|
+
}
|
283
|
+
|
266
284
|
const taggedExpression = (expression, tag, style, leqno) => {
|
267
285
|
tag = buildExpressionRow(tag[0].body, style)
|
268
286
|
tag = consolidateText(tag)
|
269
287
|
tag.classes.push("tml-tag")
|
270
288
|
|
289
|
+
const label = getLabel(expression) // from a \label{} function.
|
271
290
|
expression = new mathMLTree.MathNode("mtd", [expression])
|
272
291
|
const rowArray = [glue(), expression, glue()]
|
273
292
|
rowArray[leqno ? 0 : 2].classes.push(leqno ? "tml-left" : "tml-right")
|
274
293
|
rowArray[leqno ? 0 : 2].children.push(tag)
|
275
294
|
const mtr = new mathMLTree.MathNode("mtr", rowArray, ["tml-tageqn"])
|
295
|
+
if (label) { mtr.setAttribute("id", label) }
|
276
296
|
const table = new mathMLTree.MathNode("mtable", [mtr])
|
277
297
|
table.style.width = "100%"
|
278
298
|
table.setAttribute("displaystyle", "true")
|