temml 0.10.34 → 0.11.1
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.cjs +4757 -4265
- package/dist/temml.d.ts +3 -0
- package/dist/temml.js +3107 -2615
- package/dist/temml.min.js +1 -1
- package/dist/temml.mjs +4757 -4265
- package/dist/temmlPostProcess.js +2 -1
- package/package.json +2 -2
- package/src/ParseError.js +1 -1
- package/src/auto-render.js +263 -0
- package/src/environments/array.js +81 -34
- package/src/environments/borderTree.js +139 -0
- package/src/functions/bordermatrix.js +42 -0
- package/src/functions.js +1 -0
- package/src/linebreaking.js +1 -1
- package/src/postProcess.js +2 -1
- package/temml.js +7 -1
- 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.01";
|
15
15
|
|
16
16
|
function postProcess(block) {
|
17
17
|
const labelMap = {};
|
@@ -72,6 +72,7 @@
|
|
72
72
|
mtext.appendChild(document.createTextNode(str));
|
73
73
|
const math = document.createElementNS("http://www.w3.org/1998/Math/MathML", "math");
|
74
74
|
math.appendChild(mtext);
|
75
|
+
ref.textContent = '';
|
75
76
|
ref.appendChild(math);
|
76
77
|
});
|
77
78
|
}
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "temml",
|
3
|
-
"version": "0.
|
3
|
+
"version": "0.11.01",
|
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
|
}
|
package/src/ParseError.js
CHANGED
@@ -27,7 +27,7 @@ class ParseError {
|
|
27
27
|
if (start === input.length) {
|
28
28
|
error += " at end of input: ";
|
29
29
|
} else {
|
30
|
-
error += " at position " + (start + 1) + ": ";
|
30
|
+
error += " at position " + (start + 1) + ": \n";
|
31
31
|
}
|
32
32
|
|
33
33
|
// Underline token in question using combining underscores
|
@@ -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
|
+
};
|
@@ -1,5 +1,6 @@
|
|
1
1
|
import defineEnvironment from "../defineEnvironment";
|
2
2
|
import { parseCD } from "./cd";
|
3
|
+
import { bordermatrixParseTree } from "./borderTree.js"
|
3
4
|
import defineFunction from "../defineFunction";
|
4
5
|
import mathMLTree from "../mathMLTree";
|
5
6
|
import { Span } from "../domTree"
|
@@ -103,6 +104,7 @@ function parseArray(
|
|
103
104
|
},
|
104
105
|
scriptLevel
|
105
106
|
) {
|
107
|
+
const endToken = envClasses && envClasses.includes("bordermatrix") ? "}" : "\\end"
|
106
108
|
parser.gullet.beginGroup();
|
107
109
|
if (!singleRow) {
|
108
110
|
// \cr is equivalent to \\ without the optional size argument (see below)
|
@@ -176,7 +178,7 @@ function parseArray(
|
|
176
178
|
}
|
177
179
|
}
|
178
180
|
parser.consume();
|
179
|
-
} else if (next ===
|
181
|
+
} else if (next === endToken) {
|
180
182
|
endRow()
|
181
183
|
// Arrays terminate newlines with `\crcr` which consumes a `\cr` if
|
182
184
|
// the last line is empty. However, AMS environments keep the
|
@@ -213,7 +215,7 @@ function parseArray(
|
|
213
215
|
body.push(row);
|
214
216
|
beginRow();
|
215
217
|
} else {
|
216
|
-
throw new ParseError("Expected & or \\\\ or \\cr or
|
218
|
+
throw new ParseError("Expected & or \\\\ or \\cr or " + endToken, parser.nextToken);
|
217
219
|
}
|
218
220
|
}
|
219
221
|
|
@@ -346,23 +348,44 @@ const mathmlBuilder = function(group, style) {
|
|
346
348
|
})
|
347
349
|
}
|
348
350
|
}
|
349
|
-
|
351
|
+
|
352
|
+
// Check for \hphantom \from \bordermatrix
|
353
|
+
let mustSquashRow = true
|
354
|
+
for (let j = 0; j < mtr.children.length; j++) {
|
355
|
+
const child = mtr.children[j].children[0];
|
356
|
+
if (!(child && child.type === "mpadded" && child.attributes.height === "0px")) {
|
357
|
+
mustSquashRow = false
|
358
|
+
break
|
359
|
+
}
|
360
|
+
}
|
361
|
+
if (mustSquashRow) {
|
362
|
+
// All the cell contents are \hphantom. Squash the padding.
|
363
|
+
for (let j = 0; j < mtr.children.length; j++) {
|
364
|
+
mtr.children[j].style.paddingTop = "0"
|
365
|
+
mtr.children[j].style.paddingBottom = "0"
|
366
|
+
}
|
367
|
+
}
|
368
|
+
|
369
|
+
tbl.push(mtr)
|
350
370
|
}
|
351
371
|
|
352
|
-
if (group.
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
for (let
|
359
|
-
|
360
|
-
|
361
|
-
tbl[i].children[j].style.paddingBottom = pad
|
362
|
-
}
|
372
|
+
if (group.arraystretch && group.arraystretch !== 1) {
|
373
|
+
// In LaTeX, \arraystretch is a factor applied to a 12pt strut height.
|
374
|
+
// It defines a baseline to baseline distance.
|
375
|
+
// Here, we do an approximation of that approach.
|
376
|
+
const pad = String(1.4 * group.arraystretch - 0.8) + "ex"
|
377
|
+
for (let i = 0; i < tbl.length; i++) {
|
378
|
+
for (let j = 0; j < tbl[i].children.length; j++) {
|
379
|
+
tbl[i].children[j].style.paddingTop = pad
|
380
|
+
tbl[i].children[j].style.paddingBottom = pad
|
363
381
|
}
|
364
382
|
}
|
365
|
-
|
383
|
+
}
|
384
|
+
|
385
|
+
let sidePadding
|
386
|
+
let sidePadUnit
|
387
|
+
if (group.envClasses.length > 0) {
|
388
|
+
sidePadding = group.envClasses.includes("abut")
|
366
389
|
? "0"
|
367
390
|
: group.envClasses.includes("cases")
|
368
391
|
? "0"
|
@@ -371,13 +394,14 @@ const mathmlBuilder = function(group, style) {
|
|
371
394
|
: group.envClasses.includes("cd")
|
372
395
|
? "0.25"
|
373
396
|
: "0.4" // default side padding
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
397
|
+
sidePadUnit = "em"
|
398
|
+
}
|
399
|
+
if (group.arraycolsep) {
|
400
|
+
const arraySidePad = calculateSize(group.arraycolsep, style)
|
401
|
+
sidePadding = arraySidePad.number.toFixed(4)
|
402
|
+
sidePadUnit = arraySidePad.unit
|
403
|
+
}
|
404
|
+
if (sidePadding) {
|
381
405
|
const numCols = tbl.length === 0 ? 0 : tbl[0].children.length
|
382
406
|
|
383
407
|
const sidePad = (j, hand) => {
|
@@ -399,7 +423,18 @@ const mathmlBuilder = function(group, style) {
|
|
399
423
|
tbl[i].children[j].style.paddingRight = `${sidePad(j, 1)}${sidePadUnit}`
|
400
424
|
}
|
401
425
|
}
|
426
|
+
}
|
427
|
+
if (group.envClasses.length === 0) {
|
428
|
+
// Set zero padding on side of the matrix
|
429
|
+
for (let i = 0; i < tbl.length; i++) {
|
430
|
+
tbl[i].children[0].style.paddingLeft = "0em"
|
431
|
+
if (tbl[i].children.length === tbl[0].children.length) {
|
432
|
+
tbl[i].children[tbl[i].children.length - 1].style.paddingRight = "0em"
|
433
|
+
}
|
434
|
+
}
|
435
|
+
}
|
402
436
|
|
437
|
+
if (group.envClasses.length > 0) {
|
403
438
|
// Justification
|
404
439
|
const align = group.envClasses.includes("align") || group.envClasses.includes("alignat")
|
405
440
|
for (let i = 0; i < tbl.length; i++) {
|
@@ -425,14 +460,6 @@ const mathmlBuilder = function(group, style) {
|
|
425
460
|
}
|
426
461
|
}
|
427
462
|
}
|
428
|
-
} else {
|
429
|
-
// Set zero padding on side of the matrix
|
430
|
-
for (let i = 0; i < tbl.length; i++) {
|
431
|
-
tbl[i].children[0].style.paddingLeft = "0em"
|
432
|
-
if (tbl[i].children.length === tbl[0].children.length) {
|
433
|
-
tbl[i].children[tbl[i].children.length - 1].style.paddingRight = "0em"
|
434
|
-
}
|
435
|
-
}
|
436
463
|
}
|
437
464
|
|
438
465
|
let table = new mathMLTree.MathNode("mtable", tbl)
|
@@ -672,7 +699,7 @@ defineEnvironment({
|
|
672
699
|
mathmlBuilder
|
673
700
|
});
|
674
701
|
|
675
|
-
// The matrix environments of amsmath
|
702
|
+
// The matrix environments of amsmath build on the array environment
|
676
703
|
// of LaTeX, which is discussed above.
|
677
704
|
// The mathtools package adds starred versions of the same environments.
|
678
705
|
// These have an optional argument to choose left|center|right justification.
|
@@ -732,6 +759,10 @@ defineEnvironment({
|
|
732
759
|
const res = parseArray(context.parser, payload, "text")
|
733
760
|
res.cols = new Array(res.body[0].length).fill({ type: "align", align: colAlign })
|
734
761
|
const [arraystretch, arraycolsep] = arrayGaps(context.parser.gullet.macros)
|
762
|
+
res.arraystretch = arraystretch
|
763
|
+
if (arraycolsep && !(arraycolsep === 6 && arraycolsep === "pt")) {
|
764
|
+
res.arraycolsep = arraycolsep
|
765
|
+
}
|
735
766
|
return delimiters
|
736
767
|
? {
|
737
768
|
type: "leftright",
|
@@ -739,15 +770,31 @@ defineEnvironment({
|
|
739
770
|
body: [res],
|
740
771
|
left: delimiters[0],
|
741
772
|
right: delimiters[1],
|
742
|
-
rightColor: undefined
|
743
|
-
arraystretch,
|
744
|
-
arraycolsep
|
773
|
+
rightColor: undefined // \right uninfluenced by \color in array
|
745
774
|
}
|
746
775
|
: res;
|
747
776
|
},
|
748
777
|
mathmlBuilder
|
749
778
|
});
|
750
779
|
|
780
|
+
defineEnvironment({
|
781
|
+
type: "array",
|
782
|
+
names: ["bordermatrix"],
|
783
|
+
props: {
|
784
|
+
numArgs: 0
|
785
|
+
},
|
786
|
+
handler(context) {
|
787
|
+
const payload = { cols: [], envClasses: ["bordermatrix"] }
|
788
|
+
const res = parseArray(context.parser, payload, "text")
|
789
|
+
res.cols = new Array(res.body[0].length).fill({ type: "align", align: "c" })
|
790
|
+
res.envClasses = [];
|
791
|
+
res.arraystretch = 1
|
792
|
+
if (context.envName === "matrix") { return res}
|
793
|
+
return bordermatrixParseTree(res, context.delimiters)
|
794
|
+
},
|
795
|
+
mathmlBuilder
|
796
|
+
});
|
797
|
+
|
751
798
|
defineEnvironment({
|
752
799
|
type: "array",
|
753
800
|
names: ["smallmatrix"],
|
@@ -0,0 +1,139 @@
|
|
1
|
+
|
2
|
+
const ordGroup = (body) => {
|
3
|
+
return {
|
4
|
+
"type": "ordgroup",
|
5
|
+
"mode": "math",
|
6
|
+
"body": body,
|
7
|
+
"semisimple": true
|
8
|
+
}
|
9
|
+
}
|
10
|
+
|
11
|
+
const phantom = (body, type) => {
|
12
|
+
return {
|
13
|
+
"type": type,
|
14
|
+
"mode": "math",
|
15
|
+
"body": ordGroup(body)
|
16
|
+
}
|
17
|
+
}
|
18
|
+
|
19
|
+
/*
|
20
|
+
* A helper for \bordermatrix.
|
21
|
+
* parseArray() has parsed the tokens as if the environment
|
22
|
+
* was \begin{matrix}. That parse tree is this function’s input.
|
23
|
+
* Here, we rearrange the parse tree to get one that will
|
24
|
+
* result in TeX \bordermatrix.
|
25
|
+
* The final result includes a {pmatrix}, which is the bottom
|
26
|
+
* half of a <mover> element. The top of the <mover> contains
|
27
|
+
* the \bordermatrix headings. The top section also contains the
|
28
|
+
* contents of the bottom {pmatrix}. Those elements are hidden via
|
29
|
+
* \hphantom, but they ensure that column widths are the same top and
|
30
|
+
* bottom.
|
31
|
+
*
|
32
|
+
* We also create a left {matrix} with a single column that contains
|
33
|
+
* elements shifted out of the matrix. The left {matrix} also
|
34
|
+
* contains \vphantom copies of the other {pmatrix} elements.
|
35
|
+
* As before, this ensures consistent row heights of left and main.
|
36
|
+
*/
|
37
|
+
|
38
|
+
export const bordermatrixParseTree = (matrix, delimiters) => {
|
39
|
+
const body = matrix.body
|
40
|
+
body[0].shift() // dispose of top left cell
|
41
|
+
|
42
|
+
// Create an array for the left column
|
43
|
+
const leftColumnBody = new Array(body.length - 1).fill().map(() => [])
|
44
|
+
for (let i = 1; i < body.length; i++) {
|
45
|
+
// The visible part of the cell
|
46
|
+
leftColumnBody[i - 1].push(body[i].shift())
|
47
|
+
// A vphantom with contents from the pmatrix, to set minimum cell height
|
48
|
+
const phantomBody = [];
|
49
|
+
for (let j = 0; j < body[i].length; j++) {
|
50
|
+
phantomBody.push(structuredClone(body[i][j]))
|
51
|
+
}
|
52
|
+
leftColumnBody[i - 1].push(phantom(phantomBody, "vphantom"))
|
53
|
+
}
|
54
|
+
|
55
|
+
// Create an array for the top row
|
56
|
+
const topRowBody = new Array(body.length).fill().map(() => [])
|
57
|
+
for (let j = 0; j < body[0].length; j++) {
|
58
|
+
topRowBody[0].push(structuredClone(body[0][j]))
|
59
|
+
}
|
60
|
+
// Copy the rest of the pmatrix, but squashed via \hphantom
|
61
|
+
for (let i = 1; i < body.length; i++) {
|
62
|
+
for (let j = 0; j < body[0].length; j++) {
|
63
|
+
topRowBody[i].push(phantom(structuredClone(body[i][j]).body, "hphantom"))
|
64
|
+
}
|
65
|
+
}
|
66
|
+
|
67
|
+
// Squash the top row of the main {pmatrix}
|
68
|
+
for (let j = 0; j < body[0].length; j++) {
|
69
|
+
body[0][j] = phantom(structuredClone(body[0][j]).body, "hphantom")
|
70
|
+
}
|
71
|
+
|
72
|
+
// Now wrap the arrays in the proper parse nodes.
|
73
|
+
|
74
|
+
const leftColumn = {
|
75
|
+
type: "array",
|
76
|
+
mode: "math",
|
77
|
+
body: leftColumnBody,
|
78
|
+
cols: [{ type: "align", align: "c" }],
|
79
|
+
rowGaps: new Array(leftColumnBody.length - 1).fill(null),
|
80
|
+
hLinesBeforeRow: new Array(leftColumnBody.length + 1).fill().map(() => []),
|
81
|
+
envClasses: [],
|
82
|
+
scriptLevel: "text",
|
83
|
+
arraystretch: 1,
|
84
|
+
labels: new Array(leftColumnBody.length).fill(""),
|
85
|
+
arraycolsep: { "number": 0.04, unit: "em" }
|
86
|
+
}
|
87
|
+
|
88
|
+
const topRow = {
|
89
|
+
type: "array",
|
90
|
+
mode: "math",
|
91
|
+
body: topRowBody,
|
92
|
+
cols: new Array(topRowBody.length).fill({ type: "align", align: "c" }),
|
93
|
+
rowGaps: new Array(topRowBody.length - 1).fill(null),
|
94
|
+
hLinesBeforeRow: new Array(topRowBody.length + 1).fill().map(() => []),
|
95
|
+
envClasses: [],
|
96
|
+
scriptLevel: "text",
|
97
|
+
arraystretch: 1,
|
98
|
+
labels: new Array(topRowBody.length).fill(""),
|
99
|
+
arraycolsep: null
|
100
|
+
}
|
101
|
+
|
102
|
+
const topWrapper = {
|
103
|
+
type: "styling",
|
104
|
+
mode: "math",
|
105
|
+
scriptLevel: "text", // Must set this explicitly.
|
106
|
+
body: [topRow] // Default level is "script".
|
107
|
+
}
|
108
|
+
|
109
|
+
const container = {
|
110
|
+
type: "leftright",
|
111
|
+
mode: "math",
|
112
|
+
body: [matrix],
|
113
|
+
left: delimiters ? delimiters[0] : "(",
|
114
|
+
right: delimiters ? delimiters[1] : ")",
|
115
|
+
rightColor: undefined
|
116
|
+
}
|
117
|
+
|
118
|
+
const base = {
|
119
|
+
type: "op", // The base of a TeX \overset
|
120
|
+
mode: "math",
|
121
|
+
limits: true,
|
122
|
+
alwaysHandleSupSub: true,
|
123
|
+
parentIsSupSub: true,
|
124
|
+
symbol: false,
|
125
|
+
stack: true,
|
126
|
+
suppressBaseShift: true,
|
127
|
+
body: [container]
|
128
|
+
}
|
129
|
+
|
130
|
+
const mover = {
|
131
|
+
type: "supsub", // We're using the MathML equivalent
|
132
|
+
mode: "math", // of TeX \overset.
|
133
|
+
base: base, // That keeps the {pmatrix} aligned with
|
134
|
+
sup: topWrapper, // the math centerline.
|
135
|
+
sub: null
|
136
|
+
}
|
137
|
+
|
138
|
+
return ordGroup([leftColumn, mover])
|
139
|
+
}
|
@@ -0,0 +1,42 @@
|
|
1
|
+
import defineFunction from "../defineFunction"
|
2
|
+
import environments from "../environments"
|
3
|
+
|
4
|
+
// \bordermatrix from TeXbook pp 177 & 361
|
5
|
+
// Optional argument from Herbert Voß, Math mode, p 20
|
6
|
+
// Ref: https://tug.ctan.org/obsolete/info/math/voss/mathmode/Mathmode.pdf
|
7
|
+
|
8
|
+
defineFunction({
|
9
|
+
type: "bordermatrix",
|
10
|
+
names: ["\\bordermatrix", "\\matrix"],
|
11
|
+
props: {
|
12
|
+
numArgs: 0,
|
13
|
+
numOptionalArgs: 1
|
14
|
+
},
|
15
|
+
handler: ({ parser, funcName }, args, optArgs) => {
|
16
|
+
// Find out if the author has defined custom delimiters
|
17
|
+
let delimiters = ["(", ")"]
|
18
|
+
if (funcName === "\\bordermatrix" && optArgs[0] && optArgs[0].body) {
|
19
|
+
const body = optArgs[0].body
|
20
|
+
if (body.length === 2 && body[0].type === "atom" && body[1].type === "atom") {
|
21
|
+
if (body[0].family === "open" && body[1].family === "close") {
|
22
|
+
delimiters = [body[0].text, body[1].text]
|
23
|
+
}
|
24
|
+
}
|
25
|
+
}
|
26
|
+
// consume the opening brace
|
27
|
+
parser.consumeSpaces()
|
28
|
+
parser.consume()
|
29
|
+
|
30
|
+
// Pass control to the environment handler in array.js.
|
31
|
+
const env = environments["bordermatrix"];
|
32
|
+
const context = {
|
33
|
+
mode: parser.mode,
|
34
|
+
envName: funcName.slice(1),
|
35
|
+
delimiters,
|
36
|
+
parser
|
37
|
+
}
|
38
|
+
const result = env.handler(context)
|
39
|
+
parser.expect("}", true)
|
40
|
+
return result
|
41
|
+
}
|
42
|
+
});
|