temml 0.9.1
Sign up to get free protection for your applications and to get access to all the features.
- package/LICENSE +21 -0
- package/README.md +44 -0
- package/contrib/auto-render/README.md +89 -0
- package/contrib/auto-render/auto-render.js +128 -0
- package/contrib/auto-render/dist/auto-render.js +217 -0
- package/contrib/auto-render/dist/auto-render.min.js +1 -0
- package/contrib/auto-render/splitAtDelimiters.js +84 -0
- package/contrib/auto-render/test/auto-render-spec.js +234 -0
- package/contrib/auto-render/test/auto-render.js +217 -0
- package/contrib/auto-render/test/test_page.html +59 -0
- package/contrib/mhchem/README.md +26 -0
- package/contrib/mhchem/mhchem.js +1705 -0
- package/contrib/mhchem/mhchem.min.js +1 -0
- package/contrib/physics/README.md +20 -0
- package/contrib/physics/physics.js +131 -0
- package/contrib/texvc/README.md +23 -0
- package/contrib/texvc/texvc.js +61 -0
- package/dist/Temml-Asana.css +201 -0
- package/dist/Temml-Latin-Modern.css +216 -0
- package/dist/Temml-Libertinus.css +214 -0
- package/dist/Temml-Local.css +194 -0
- package/dist/Temml-STIX2.css +203 -0
- package/dist/Temml.woff2 +0 -0
- package/dist/temml.cjs +13122 -0
- package/dist/temml.js +11225 -0
- package/dist/temml.min.js +1 -0
- package/dist/temml.mjs +13120 -0
- package/dist/temmlPostProcess.js +70 -0
- package/package.json +34 -0
- package/src/Lexer.js +121 -0
- package/src/MacroExpander.js +437 -0
- package/src/Namespace.js +107 -0
- package/src/ParseError.js +64 -0
- package/src/Parser.js +977 -0
- package/src/Settings.js +49 -0
- package/src/SourceLocation.js +29 -0
- package/src/Style.js +144 -0
- package/src/Token.js +40 -0
- package/src/buildMathML.js +235 -0
- package/src/constants.js +25 -0
- package/src/defineEnvironment.js +25 -0
- package/src/defineFunction.js +69 -0
- package/src/defineMacro.js +11 -0
- package/src/domTree.js +185 -0
- package/src/environments/array.js +791 -0
- package/src/environments/cd.js +252 -0
- package/src/environments.js +8 -0
- package/src/functions/accent.js +127 -0
- package/src/functions/accentunder.js +38 -0
- package/src/functions/arrow.js +204 -0
- package/src/functions/cancelto.js +36 -0
- package/src/functions/char.js +33 -0
- package/src/functions/color.js +253 -0
- package/src/functions/cr.js +46 -0
- package/src/functions/def.js +259 -0
- package/src/functions/delimsizing.js +304 -0
- package/src/functions/enclose.js +193 -0
- package/src/functions/envTag.js +38 -0
- package/src/functions/environment.js +59 -0
- package/src/functions/font.js +123 -0
- package/src/functions/genfrac.js +333 -0
- package/src/functions/hbox.js +29 -0
- package/src/functions/horizBrace.js +32 -0
- package/src/functions/href.js +90 -0
- package/src/functions/html.js +95 -0
- package/src/functions/includegraphics.js +131 -0
- package/src/functions/kern.js +75 -0
- package/src/functions/label.js +29 -0
- package/src/functions/lap.js +75 -0
- package/src/functions/math.js +40 -0
- package/src/functions/mathchoice.js +41 -0
- package/src/functions/mclass.js +201 -0
- package/src/functions/multiscript.js +91 -0
- package/src/functions/not.js +46 -0
- package/src/functions/op.js +338 -0
- package/src/functions/operatorname.js +139 -0
- package/src/functions/ordgroup.js +9 -0
- package/src/functions/phantom.js +73 -0
- package/src/functions/pmb.js +31 -0
- package/src/functions/raise.js +68 -0
- package/src/functions/ref.js +28 -0
- package/src/functions/relax.js +16 -0
- package/src/functions/rule.js +52 -0
- package/src/functions/sizing.js +64 -0
- package/src/functions/smash.js +66 -0
- package/src/functions/sqrt.js +31 -0
- package/src/functions/styling.js +58 -0
- package/src/functions/supsub.js +135 -0
- package/src/functions/symbolsOp.js +53 -0
- package/src/functions/symbolsOrd.js +102 -0
- package/src/functions/symbolsSpacing.js +53 -0
- package/src/functions/tag.js +8 -0
- package/src/functions/text.js +75 -0
- package/src/functions/tip.js +63 -0
- package/src/functions/toggle.js +13 -0
- package/src/functions/verb.js +33 -0
- package/src/functions.js +57 -0
- package/src/linebreaking.js +159 -0
- package/src/macros.js +708 -0
- package/src/mathMLTree.js +175 -0
- package/src/parseNode.js +42 -0
- package/src/parseTree.js +40 -0
- package/src/postProcess.js +57 -0
- package/src/replace.js +225 -0
- package/src/stretchy.js +66 -0
- package/src/svg.js +110 -0
- package/src/symbols.js +972 -0
- package/src/tree.js +50 -0
- package/src/unicodeAccents.js +16 -0
- package/src/unicodeScripts.js +119 -0
- package/src/unicodeSupOrSub.js +108 -0
- package/src/unicodeSymbolBuilder.js +31 -0
- package/src/unicodeSymbols.js +320 -0
- package/src/units.js +109 -0
- package/src/utils.js +109 -0
- package/src/variant.js +103 -0
- package/temml.js +181 -0
@@ -0,0 +1,791 @@
|
|
1
|
+
import defineEnvironment from "../defineEnvironment";
|
2
|
+
import { parseCD } from "./cd";
|
3
|
+
import defineFunction from "../defineFunction";
|
4
|
+
import mathMLTree from "../mathMLTree";
|
5
|
+
import { StyleLevel } from "../constants"
|
6
|
+
import ParseError from "../ParseError";
|
7
|
+
import { assertNodeType, assertSymbolNodeType } from "../parseNode";
|
8
|
+
import { checkSymbolNodeType } from "../parseNode";
|
9
|
+
|
10
|
+
import * as mml from "../buildMathML";
|
11
|
+
|
12
|
+
// Helper functions
|
13
|
+
function getHLines(parser) {
|
14
|
+
// Return an array. The array length = number of hlines.
|
15
|
+
// Each element in the array tells if the line is dashed.
|
16
|
+
const hlineInfo = [];
|
17
|
+
parser.consumeSpaces();
|
18
|
+
let nxt = parser.fetch().text;
|
19
|
+
if (nxt === "\\relax") {
|
20
|
+
parser.consume();
|
21
|
+
parser.consumeSpaces();
|
22
|
+
nxt = parser.fetch().text;
|
23
|
+
}
|
24
|
+
while (nxt === "\\hline" || nxt === "\\hdashline") {
|
25
|
+
parser.consume();
|
26
|
+
hlineInfo.push(nxt === "\\hdashline");
|
27
|
+
parser.consumeSpaces();
|
28
|
+
nxt = parser.fetch().text;
|
29
|
+
}
|
30
|
+
return hlineInfo;
|
31
|
+
}
|
32
|
+
|
33
|
+
const validateAmsEnvironmentContext = context => {
|
34
|
+
const settings = context.parser.settings;
|
35
|
+
if (!settings.displayMode) {
|
36
|
+
throw new ParseError(`{${context.envName}} can be used only in display mode.`);
|
37
|
+
}
|
38
|
+
}
|
39
|
+
|
40
|
+
const getTag = (group, style, rowNum) => {
|
41
|
+
let tag
|
42
|
+
const tagContents = group.tags.shift()
|
43
|
+
if (tagContents) {
|
44
|
+
// The author has written a \tag or a \notag in this row.
|
45
|
+
if (tagContents.body) {
|
46
|
+
tag = mml.buildExpressionRow(tagContents.body, style)
|
47
|
+
tag.classes = ["tml-tag"]
|
48
|
+
} else {
|
49
|
+
// \notag. Return an empty span.
|
50
|
+
tag = new mathMLTree.MathNode("mtext", [], [])
|
51
|
+
return tag
|
52
|
+
}
|
53
|
+
} else if (group.envClasses.includes("multline") &&
|
54
|
+
((group.leqno && rowNum !== 0) || (!group.leqno && rowNum !== group.body.length - 1))) {
|
55
|
+
// A multiline that does not receive a tag. Return an empty cell.
|
56
|
+
tag = new mathMLTree.MathNode("mtext", [], [])
|
57
|
+
return tag
|
58
|
+
} else {
|
59
|
+
// AMS automatcally numbered equaton.
|
60
|
+
// Insert a class so the element can be populated by a post-processor.
|
61
|
+
tag = new mathMLTree.MathNode("mtext", [], ["tml-eqn"])
|
62
|
+
}
|
63
|
+
return tag
|
64
|
+
}
|
65
|
+
|
66
|
+
/**
|
67
|
+
* Parse the body of the environment, with rows delimited by \\ and
|
68
|
+
* columns delimited by &, and create a nested list in row-major order
|
69
|
+
* with one group per cell. If given an optional argument scriptLevel
|
70
|
+
* ("text", "display", etc.), then each cell is cast into that scriptLevel.
|
71
|
+
*/
|
72
|
+
function parseArray(
|
73
|
+
parser,
|
74
|
+
{
|
75
|
+
cols, // [{ type: string , align: l|c|r|null }]
|
76
|
+
envClasses, // align(ed|at|edat) | array | cases | cd | small | multline
|
77
|
+
addEqnNum, // boolean
|
78
|
+
singleRow, // boolean
|
79
|
+
emptySingleRow, // boolean
|
80
|
+
maxNumCols, // number
|
81
|
+
leqno // boolean
|
82
|
+
},
|
83
|
+
scriptLevel
|
84
|
+
) {
|
85
|
+
parser.gullet.beginGroup();
|
86
|
+
if (!singleRow) {
|
87
|
+
// \cr is equivalent to \\ without the optional size argument (see below)
|
88
|
+
// TODO: provide helpful error when \cr is used outside array environment
|
89
|
+
parser.gullet.macros.set("\\cr", "\\\\\\relax");
|
90
|
+
}
|
91
|
+
if (addEqnNum) {
|
92
|
+
parser.gullet.macros.set("\\tag", "\\env@tag{\\text{#1}}");
|
93
|
+
parser.gullet.macros.set("\\notag", "\\env@notag");
|
94
|
+
parser.gullet.macros.set("\\nonumber", "\\env@notag")
|
95
|
+
}
|
96
|
+
|
97
|
+
// Start group for first cell
|
98
|
+
parser.gullet.beginGroup();
|
99
|
+
|
100
|
+
let row = [];
|
101
|
+
const body = [row];
|
102
|
+
const rowGaps = [];
|
103
|
+
const tags = [];
|
104
|
+
let rowTag;
|
105
|
+
const hLinesBeforeRow = [];
|
106
|
+
|
107
|
+
// Test for \hline at the top of the array.
|
108
|
+
hLinesBeforeRow.push(getHLines(parser));
|
109
|
+
|
110
|
+
// eslint-disable-next-line no-constant-condition
|
111
|
+
while (true) {
|
112
|
+
// Parse each cell in its own group (namespace)
|
113
|
+
let cell = parser.parseExpression(false, singleRow ? "\\end" : "\\\\");
|
114
|
+
|
115
|
+
if (addEqnNum && !rowTag) {
|
116
|
+
// Check if the author wrote a \tag{} inside this cell.
|
117
|
+
for (let i = 0; i < cell.length; i++) {
|
118
|
+
if (cell[i].type === "envTag" || cell[i].type === "noTag") {
|
119
|
+
// Get the contents of the \text{} nested inside the \env@Tag{}
|
120
|
+
rowTag = cell[i].type === "envTag"
|
121
|
+
? cell.splice(i, 1)[0].body.body[0]
|
122
|
+
: { body: null };
|
123
|
+
break
|
124
|
+
}
|
125
|
+
}
|
126
|
+
}
|
127
|
+
parser.gullet.endGroup();
|
128
|
+
parser.gullet.beginGroup();
|
129
|
+
|
130
|
+
cell = {
|
131
|
+
type: "ordgroup",
|
132
|
+
mode: parser.mode,
|
133
|
+
body: cell
|
134
|
+
};
|
135
|
+
row.push(cell);
|
136
|
+
const next = parser.fetch().text;
|
137
|
+
if (next === "&") {
|
138
|
+
if (maxNumCols && row.length === maxNumCols) {
|
139
|
+
if (envClasses.includes("array")) {
|
140
|
+
if (parser.settings.strict) {
|
141
|
+
throw new ParseError("Too few columns " + "specified in the {array} column argument.",
|
142
|
+
parser.nextToken)
|
143
|
+
}
|
144
|
+
} else if (maxNumCols === 2) {
|
145
|
+
throw new ParseError("The split environment accepts no more than two columns",
|
146
|
+
parser.nextToken);
|
147
|
+
} else {
|
148
|
+
throw new ParseError("The equation environment accepts only one column",
|
149
|
+
parser.nextToken)
|
150
|
+
}
|
151
|
+
}
|
152
|
+
parser.consume();
|
153
|
+
} else if (next === "\\end") {
|
154
|
+
// Arrays terminate newlines with `\crcr` which consumes a `\cr` if
|
155
|
+
// the last line is empty. However, AMS environments keep the
|
156
|
+
// empty row if it's the only one.
|
157
|
+
// NOTE: Currently, `cell` is the last item added into `row`.
|
158
|
+
if (row.length === 1 && cell.body.length === 0 && (body.length > 1 || !emptySingleRow)) {
|
159
|
+
body.pop();
|
160
|
+
}
|
161
|
+
if (hLinesBeforeRow.length < body.length + 1) {
|
162
|
+
hLinesBeforeRow.push([]);
|
163
|
+
}
|
164
|
+
break;
|
165
|
+
} else if (next === "\\\\") {
|
166
|
+
parser.consume();
|
167
|
+
let size;
|
168
|
+
// \def\Let@{\let\\\math@cr}
|
169
|
+
// \def\math@cr{...\math@cr@}
|
170
|
+
// \def\math@cr@{\new@ifnextchar[\math@cr@@{\math@cr@@[\z@]}}
|
171
|
+
// \def\math@cr@@[#1]{...\math@cr@@@...}
|
172
|
+
// \def\math@cr@@@{\cr}
|
173
|
+
if (parser.gullet.future().text !== " ") {
|
174
|
+
size = parser.parseSizeGroup(true);
|
175
|
+
}
|
176
|
+
rowGaps.push(size ? size.value : null);
|
177
|
+
|
178
|
+
tags.push(rowTag)
|
179
|
+
|
180
|
+
// check for \hline(s) following the row separator
|
181
|
+
hLinesBeforeRow.push(getHLines(parser));
|
182
|
+
|
183
|
+
row = [];
|
184
|
+
rowTag = null;
|
185
|
+
body.push(row);
|
186
|
+
} else {
|
187
|
+
throw new ParseError("Expected & or \\\\ or \\cr or \\end", parser.nextToken);
|
188
|
+
}
|
189
|
+
}
|
190
|
+
|
191
|
+
// End cell group
|
192
|
+
parser.gullet.endGroup();
|
193
|
+
// End array group defining \cr
|
194
|
+
parser.gullet.endGroup();
|
195
|
+
|
196
|
+
tags.push(rowTag)
|
197
|
+
|
198
|
+
return {
|
199
|
+
type: "array",
|
200
|
+
mode: parser.mode,
|
201
|
+
body,
|
202
|
+
cols,
|
203
|
+
rowGaps,
|
204
|
+
hLinesBeforeRow,
|
205
|
+
envClasses,
|
206
|
+
addEqnNum,
|
207
|
+
scriptLevel,
|
208
|
+
tags,
|
209
|
+
leqno
|
210
|
+
};
|
211
|
+
}
|
212
|
+
|
213
|
+
// Decides on a scriptLevel for cells in an array according to whether the given
|
214
|
+
// environment name starts with the letter 'd'.
|
215
|
+
function dCellStyle(envName) {
|
216
|
+
return envName.slice(0, 1) === "d" ? "display" : "text"
|
217
|
+
}
|
218
|
+
|
219
|
+
const alignMap = {
|
220
|
+
c: "center ",
|
221
|
+
l: "left ",
|
222
|
+
r: "right "
|
223
|
+
};
|
224
|
+
|
225
|
+
const glue = group => {
|
226
|
+
const glueNode = new mathMLTree.MathNode("mtd", [])
|
227
|
+
glueNode.style = { padding: "0", width: "50%" }
|
228
|
+
if (group.envClasses.includes("multline")) {
|
229
|
+
glueNode.style.width = "7.5%"
|
230
|
+
}
|
231
|
+
return glueNode
|
232
|
+
}
|
233
|
+
|
234
|
+
const mathmlBuilder = function(group, style) {
|
235
|
+
const tbl = [];
|
236
|
+
const numRows = group.body.length
|
237
|
+
const hlines = group.hLinesBeforeRow;
|
238
|
+
|
239
|
+
for (let i = 0; i < numRows; i++) {
|
240
|
+
const rw = group.body[i];
|
241
|
+
const row = [];
|
242
|
+
const cellLevel = group.scriptLevel === "text"
|
243
|
+
? StyleLevel.TEXT
|
244
|
+
: group.scriptLevel === "script"
|
245
|
+
? StyleLevel.SCRIPT
|
246
|
+
: StyleLevel.DISPLAY
|
247
|
+
|
248
|
+
for (let j = 0; j < rw.length; j++) {
|
249
|
+
const mtd = new mathMLTree.MathNode(
|
250
|
+
"mtd",
|
251
|
+
[mml.buildGroup(rw[j], style.withLevel(cellLevel))]
|
252
|
+
)
|
253
|
+
|
254
|
+
if (group.envClasses.includes("multline")) {
|
255
|
+
const align = i === 0 ? "left" : i === numRows - 1 ? "right" : "center"
|
256
|
+
mtd.setAttribute("columnalign", align)
|
257
|
+
if (align !== "center") {
|
258
|
+
mtd.style.textAlign = "-webkit-" + align
|
259
|
+
}
|
260
|
+
}
|
261
|
+
row.push(mtd)
|
262
|
+
}
|
263
|
+
if (group.addEqnNum) {
|
264
|
+
row.unshift(glue(group));
|
265
|
+
row.push(glue(group));
|
266
|
+
const tag = getTag(group, style.withLevel(cellLevel), i)
|
267
|
+
if (group.leqno) {
|
268
|
+
row[0].children.push(tag)
|
269
|
+
row[0].style.textAlign = "-webkit-left"
|
270
|
+
} else {
|
271
|
+
row[row.length - 1].children.push(tag)
|
272
|
+
row[row.length - 1].style.textAlign = "-webkit-right"
|
273
|
+
}
|
274
|
+
}
|
275
|
+
const mtr = new mathMLTree.MathNode("mtr", row, [])
|
276
|
+
// Write horizontal rules
|
277
|
+
if (i === 0 && hlines[0].length > 0) {
|
278
|
+
if (hlines[0].length === 2) {
|
279
|
+
mtr.classes.push("tml-top-double")
|
280
|
+
} else {
|
281
|
+
mtr.classes.push(hlines[0][0] ? "tml-top-dashed" : "tml-top-solid")
|
282
|
+
}
|
283
|
+
}
|
284
|
+
if (hlines[i + 1].length > 0) {
|
285
|
+
if (hlines[i + 1].length === 2) {
|
286
|
+
mtr.classes.push("tml-hline-double")
|
287
|
+
} else {
|
288
|
+
mtr.classes.push(hlines[i + 1][0] ? "tml-hline-dashed" : "tml-hline-solid")
|
289
|
+
}
|
290
|
+
}
|
291
|
+
tbl.push(mtr);
|
292
|
+
}
|
293
|
+
let table = new mathMLTree.MathNode("mtable", tbl)
|
294
|
+
if (group.envClasses.length > 0) {
|
295
|
+
table.classes = group.envClasses.map(e => "tml-" + e)
|
296
|
+
}
|
297
|
+
if (group.scriptLevel === "display") { table.setAttribute("displaystyle", "true") }
|
298
|
+
|
299
|
+
if (group.addEqnNum || group.envClasses.includes("multline")) {
|
300
|
+
table.style.width = "100%"
|
301
|
+
}
|
302
|
+
|
303
|
+
// Column separator lines and column alignment
|
304
|
+
let align = "";
|
305
|
+
|
306
|
+
if (group.cols && group.cols.length > 0) {
|
307
|
+
const cols = group.cols;
|
308
|
+
let prevTypeWasAlign = false;
|
309
|
+
let iStart = 0;
|
310
|
+
let iEnd = cols.length;
|
311
|
+
|
312
|
+
while (cols[iStart].type === "separator") {
|
313
|
+
iStart += 1
|
314
|
+
}
|
315
|
+
while (cols[iEnd - 1].type === "separator") {
|
316
|
+
iEnd -= 1
|
317
|
+
}
|
318
|
+
|
319
|
+
if (cols[0].type === "separator") {
|
320
|
+
const sep = cols[1].type === "separator"
|
321
|
+
? "0.15em double"
|
322
|
+
: cols[0].separator === "|"
|
323
|
+
? "0.06em solid "
|
324
|
+
: "0.06em dashed "
|
325
|
+
for (const row of table.children) {
|
326
|
+
row.children[0].style.borderLeft = sep
|
327
|
+
}
|
328
|
+
}
|
329
|
+
let iCol = group.addEqnNum ? 0 : -1
|
330
|
+
for (let i = iStart; i < iEnd; i++) {
|
331
|
+
if (cols[i].type === "align") {
|
332
|
+
const colAlign = alignMap[cols[i].align];
|
333
|
+
align += colAlign
|
334
|
+
iCol += 1
|
335
|
+
for (const row of table.children) {
|
336
|
+
if (colAlign.trim() !== "center" && iCol < row.children.length) {
|
337
|
+
row.children[iCol].style.textAlign = "-webkit-" + colAlign.trim()
|
338
|
+
}
|
339
|
+
}
|
340
|
+
prevTypeWasAlign = true;
|
341
|
+
} else if (cols[i].type === "separator") {
|
342
|
+
// MathML accepts only single lines between cells.
|
343
|
+
// So we read only the first of consecutive separators.
|
344
|
+
if (prevTypeWasAlign) {
|
345
|
+
const sep = cols[i + 1].type === "separator"
|
346
|
+
? "0.15em double"
|
347
|
+
: cols[i].separator === "|"
|
348
|
+
? "0.06em solid"
|
349
|
+
: "0.06em dashed"
|
350
|
+
for (const row of table.children) {
|
351
|
+
if (iCol < row.children.length) {
|
352
|
+
row.children[iCol].style.borderRight = sep
|
353
|
+
}
|
354
|
+
}
|
355
|
+
}
|
356
|
+
prevTypeWasAlign = false
|
357
|
+
}
|
358
|
+
}
|
359
|
+
if (cols[cols.length - 1].type === "separator") {
|
360
|
+
const sep = cols[cols.length - 2].type === "separator"
|
361
|
+
? "0.15em double"
|
362
|
+
: cols[cols.length - 1].separator === "|"
|
363
|
+
? "0.06em solid"
|
364
|
+
: "0.06em dashed"
|
365
|
+
for (const row of table.children) {
|
366
|
+
row.children[row.children.length - 1].style.borderRight = sep
|
367
|
+
row.children[row.children.length - 1].style.paddingRight = "0.4em"
|
368
|
+
}
|
369
|
+
}
|
370
|
+
}
|
371
|
+
if (group.addEqnNum) {
|
372
|
+
// allow for glue cells on each side
|
373
|
+
align = "left " + (align.length > 0 ? align : "center ") + "right "
|
374
|
+
}
|
375
|
+
if (align) {
|
376
|
+
table.setAttribute("columnalign", align.trim())
|
377
|
+
}
|
378
|
+
|
379
|
+
if (group.envClasses.includes("small")) {
|
380
|
+
// A small array. Wrap in scriptstyle.
|
381
|
+
table = new mathMLTree.MathNode("mstyle", [table])
|
382
|
+
table.setAttribute("scriptlevel", "1")
|
383
|
+
}
|
384
|
+
|
385
|
+
return table
|
386
|
+
};
|
387
|
+
|
388
|
+
// Convenience function for align, align*, aligned, alignat, alignat*, alignedat, split.
|
389
|
+
const alignedHandler = function(context, args) {
|
390
|
+
if (context.envName.indexOf("ed") === -1) {
|
391
|
+
validateAmsEnvironmentContext(context);
|
392
|
+
}
|
393
|
+
const cols = [];
|
394
|
+
const res = parseArray(
|
395
|
+
context.parser,
|
396
|
+
{
|
397
|
+
cols,
|
398
|
+
addEqnNum: context.envName === "align" || context.envName === "alignat",
|
399
|
+
emptySingleRow: true,
|
400
|
+
envClasses: ["jot", "abut"], // set row spacing & provisional column spacing
|
401
|
+
maxNumCols: context.envName === "split" ? 2 : undefined,
|
402
|
+
leqno: context.parser.settings.leqno
|
403
|
+
},
|
404
|
+
"display"
|
405
|
+
);
|
406
|
+
|
407
|
+
// Determining number of columns.
|
408
|
+
// 1. If the first argument is given, we use it as a number of columns,
|
409
|
+
// and makes sure that each row doesn't exceed that number.
|
410
|
+
// 2. Otherwise, just count number of columns = maximum number
|
411
|
+
// of cells in each row ("aligned" mode -- isAligned will be true).
|
412
|
+
//
|
413
|
+
// At the same time, prepend empty group {} at beginning of every second
|
414
|
+
// cell in each row (starting with second cell) so that operators become
|
415
|
+
// binary. This behavior is implemented in amsmath's \start@aligned.
|
416
|
+
let numMaths;
|
417
|
+
let numCols = 0;
|
418
|
+
if (args[0] && args[0].type === "ordgroup") {
|
419
|
+
let arg0 = "";
|
420
|
+
for (let i = 0; i < args[0].body.length; i++) {
|
421
|
+
const textord = assertNodeType(args[0].body[i], "textord");
|
422
|
+
arg0 += textord.text;
|
423
|
+
}
|
424
|
+
numMaths = Number(arg0);
|
425
|
+
numCols = numMaths * 2;
|
426
|
+
}
|
427
|
+
const isAligned = !numCols;
|
428
|
+
res.body.forEach(function(row) {
|
429
|
+
if (!isAligned) {
|
430
|
+
// Case 1
|
431
|
+
const curMaths = row.length / 2;
|
432
|
+
if (numMaths < curMaths) {
|
433
|
+
throw new ParseError(
|
434
|
+
"Too many math in a row: " + `expected ${numMaths}, but got ${curMaths}`,
|
435
|
+
row[0]
|
436
|
+
);
|
437
|
+
}
|
438
|
+
} else if (numCols < row.length) {
|
439
|
+
// Case 2
|
440
|
+
numCols = row.length;
|
441
|
+
}
|
442
|
+
});
|
443
|
+
|
444
|
+
// Adjusting alignment.
|
445
|
+
// In aligned mode, we add one \qquad between columns;
|
446
|
+
// otherwise we add nothing.
|
447
|
+
for (let i = 0; i < numCols; ++i) {
|
448
|
+
let align = "r";
|
449
|
+
if (i % 2 === 1) {
|
450
|
+
align = "l";
|
451
|
+
}
|
452
|
+
cols[i] = {
|
453
|
+
type: "align",
|
454
|
+
align: align
|
455
|
+
};
|
456
|
+
}
|
457
|
+
if (context.envName === "split") {
|
458
|
+
// Append no more classes
|
459
|
+
} else if (context.envName.indexOf("ed") > -1) {
|
460
|
+
res.envClasses.push("aligned") // Sets justification
|
461
|
+
} else if (isAligned) {
|
462
|
+
res.envClasses[1] = context.envName === "align*"
|
463
|
+
? "align-star"
|
464
|
+
: "align" // Sets column spacing & justification
|
465
|
+
} else {
|
466
|
+
res.envClasses.push("aligned") // Sets justification
|
467
|
+
}
|
468
|
+
return res;
|
469
|
+
};
|
470
|
+
|
471
|
+
// Arrays are part of LaTeX, defined in lttab.dtx so its documentation
|
472
|
+
// is part of the source2e.pdf file of LaTeX2e source documentation.
|
473
|
+
// {darray} is an {array} environment where cells are set in \displaystyle,
|
474
|
+
// as defined in nccmath.sty.
|
475
|
+
defineEnvironment({
|
476
|
+
type: "array",
|
477
|
+
names: ["array", "darray"],
|
478
|
+
props: {
|
479
|
+
numArgs: 1
|
480
|
+
},
|
481
|
+
handler(context, args) {
|
482
|
+
// Since no types are specified above, the two possibilities are
|
483
|
+
// - The argument is wrapped in {} or [], in which case Parser's
|
484
|
+
// parseGroup() returns an "ordgroup" wrapping some symbol node.
|
485
|
+
// - The argument is a bare symbol node.
|
486
|
+
const symNode = checkSymbolNodeType(args[0]);
|
487
|
+
const colalign = symNode ? [args[0]] : assertNodeType(args[0], "ordgroup").body;
|
488
|
+
const cols = colalign.map(function(nde) {
|
489
|
+
const node = assertSymbolNodeType(nde);
|
490
|
+
const ca = node.text;
|
491
|
+
if ("lcr".indexOf(ca) !== -1) {
|
492
|
+
return {
|
493
|
+
type: "align",
|
494
|
+
align: ca
|
495
|
+
};
|
496
|
+
} else if (ca === "|") {
|
497
|
+
return {
|
498
|
+
type: "separator",
|
499
|
+
separator: "|"
|
500
|
+
};
|
501
|
+
} else if (ca === ":") {
|
502
|
+
return {
|
503
|
+
type: "separator",
|
504
|
+
separator: ":"
|
505
|
+
};
|
506
|
+
}
|
507
|
+
throw new ParseError("Unknown column alignment: " + ca, nde);
|
508
|
+
});
|
509
|
+
const res = {
|
510
|
+
cols,
|
511
|
+
envClasses: ["array"],
|
512
|
+
maxNumCols: cols.length
|
513
|
+
};
|
514
|
+
return parseArray(context.parser, res, dCellStyle(context.envName));
|
515
|
+
},
|
516
|
+
mathmlBuilder
|
517
|
+
});
|
518
|
+
|
519
|
+
// The matrix environments of amsmath builds on the array environment
|
520
|
+
// of LaTeX, which is discussed above.
|
521
|
+
// The mathtools package adds starred versions of the same environments.
|
522
|
+
// These have an optional argument to choose left|center|right justification.
|
523
|
+
defineEnvironment({
|
524
|
+
type: "array",
|
525
|
+
names: [
|
526
|
+
"matrix",
|
527
|
+
"pmatrix",
|
528
|
+
"bmatrix",
|
529
|
+
"Bmatrix",
|
530
|
+
"vmatrix",
|
531
|
+
"Vmatrix",
|
532
|
+
"matrix*",
|
533
|
+
"pmatrix*",
|
534
|
+
"bmatrix*",
|
535
|
+
"Bmatrix*",
|
536
|
+
"vmatrix*",
|
537
|
+
"Vmatrix*"
|
538
|
+
],
|
539
|
+
props: {
|
540
|
+
numArgs: 0
|
541
|
+
},
|
542
|
+
handler(context) {
|
543
|
+
const delimiters = {
|
544
|
+
matrix: null,
|
545
|
+
pmatrix: ["(", ")"],
|
546
|
+
bmatrix: ["[", "]"],
|
547
|
+
Bmatrix: ["\\{", "\\}"],
|
548
|
+
vmatrix: ["|", "|"],
|
549
|
+
Vmatrix: ["\\Vert", "\\Vert"]
|
550
|
+
}[context.envName.replace("*", "")];
|
551
|
+
// \hskip -\arraycolsep in amsmath
|
552
|
+
let colAlign = "c";
|
553
|
+
const payload = {
|
554
|
+
envClasses: [],
|
555
|
+
cols: []
|
556
|
+
};
|
557
|
+
if (context.envName.charAt(context.envName.length - 1) === "*") {
|
558
|
+
// It's one of the mathtools starred functions.
|
559
|
+
// Parse the optional alignment argument.
|
560
|
+
const parser = context.parser;
|
561
|
+
parser.consumeSpaces();
|
562
|
+
if (parser.fetch().text === "[") {
|
563
|
+
parser.consume();
|
564
|
+
parser.consumeSpaces();
|
565
|
+
colAlign = parser.fetch().text;
|
566
|
+
if ("lcr".indexOf(colAlign) === -1) {
|
567
|
+
throw new ParseError("Expected l or c or r", parser.nextToken);
|
568
|
+
}
|
569
|
+
parser.consume();
|
570
|
+
parser.consumeSpaces();
|
571
|
+
parser.expect("]");
|
572
|
+
parser.consume();
|
573
|
+
payload.cols = [];
|
574
|
+
}
|
575
|
+
}
|
576
|
+
const res = parseArray(context.parser, payload, "text")
|
577
|
+
res.cols = new Array(res.body[0].length).fill({ type: "align", align: colAlign })
|
578
|
+
return delimiters
|
579
|
+
? {
|
580
|
+
type: "leftright",
|
581
|
+
mode: context.mode,
|
582
|
+
body: [res],
|
583
|
+
left: delimiters[0],
|
584
|
+
right: delimiters[1],
|
585
|
+
rightColor: undefined // \right uninfluenced by \color in array
|
586
|
+
}
|
587
|
+
: res;
|
588
|
+
},
|
589
|
+
mathmlBuilder
|
590
|
+
});
|
591
|
+
|
592
|
+
defineEnvironment({
|
593
|
+
type: "array",
|
594
|
+
names: ["smallmatrix"],
|
595
|
+
props: {
|
596
|
+
numArgs: 0
|
597
|
+
},
|
598
|
+
handler(context) {
|
599
|
+
const payload = { type: "small" };
|
600
|
+
const res = parseArray(context.parser, payload, "script");
|
601
|
+
res.envClasses = ["small"];
|
602
|
+
return res;
|
603
|
+
},
|
604
|
+
mathmlBuilder
|
605
|
+
});
|
606
|
+
|
607
|
+
defineEnvironment({
|
608
|
+
type: "array",
|
609
|
+
names: ["subarray"],
|
610
|
+
props: {
|
611
|
+
numArgs: 1
|
612
|
+
},
|
613
|
+
handler(context, args) {
|
614
|
+
// Parsing of {subarray} is similar to {array}
|
615
|
+
const symNode = checkSymbolNodeType(args[0]);
|
616
|
+
const colalign = symNode ? [args[0]] : assertNodeType(args[0], "ordgroup").body;
|
617
|
+
const cols = colalign.map(function(nde) {
|
618
|
+
const node = assertSymbolNodeType(nde);
|
619
|
+
const ca = node.text;
|
620
|
+
// {subarray} only recognizes "l" & "c"
|
621
|
+
if ("lc".indexOf(ca) !== -1) {
|
622
|
+
return {
|
623
|
+
type: "align",
|
624
|
+
align: ca
|
625
|
+
};
|
626
|
+
}
|
627
|
+
throw new ParseError("Unknown column alignment: " + ca, nde);
|
628
|
+
});
|
629
|
+
if (cols.length > 1) {
|
630
|
+
throw new ParseError("{subarray} can contain only one column");
|
631
|
+
}
|
632
|
+
let res = {
|
633
|
+
cols,
|
634
|
+
envClasses: ["small"]
|
635
|
+
};
|
636
|
+
res = parseArray(context.parser, res, "script");
|
637
|
+
if (res.body.length > 0 && res.body[0].length > 1) {
|
638
|
+
throw new ParseError("{subarray} can contain only one column");
|
639
|
+
}
|
640
|
+
return res;
|
641
|
+
},
|
642
|
+
mathmlBuilder
|
643
|
+
});
|
644
|
+
|
645
|
+
// A cases environment (in amsmath.sty) is almost equivalent to
|
646
|
+
// \def
|
647
|
+
// \left\{\begin{array}{@{}l@{\quad}l@{}} … \end{array}\right.
|
648
|
+
// {dcases} is a {cases} environment where cells are set in \displaystyle,
|
649
|
+
// as defined in mathtools.sty.
|
650
|
+
// {rcases} is another mathtools environment. It's brace is on the right side.
|
651
|
+
defineEnvironment({
|
652
|
+
type: "array",
|
653
|
+
names: ["cases", "dcases", "rcases", "drcases"],
|
654
|
+
props: {
|
655
|
+
numArgs: 0
|
656
|
+
},
|
657
|
+
handler(context) {
|
658
|
+
const payload = {
|
659
|
+
cols: [],
|
660
|
+
envClasses: ["cases"]
|
661
|
+
};
|
662
|
+
const res = parseArray(context.parser, payload, dCellStyle(context.envName));
|
663
|
+
return {
|
664
|
+
type: "leftright",
|
665
|
+
mode: context.mode,
|
666
|
+
body: [res],
|
667
|
+
left: context.envName.indexOf("r") > -1 ? "." : "\\{",
|
668
|
+
right: context.envName.indexOf("r") > -1 ? "\\}" : ".",
|
669
|
+
rightColor: undefined
|
670
|
+
};
|
671
|
+
},
|
672
|
+
mathmlBuilder
|
673
|
+
});
|
674
|
+
|
675
|
+
// In the align environment, one uses ampersands, &, to specify number of
|
676
|
+
// columns in each row, and to locate spacing between each column.
|
677
|
+
// align gets automatic numbering. align* and aligned do not.
|
678
|
+
// The alignedat environment can be used in math mode.
|
679
|
+
defineEnvironment({
|
680
|
+
type: "array",
|
681
|
+
names: ["align", "align*", "aligned", "split"],
|
682
|
+
props: {
|
683
|
+
numArgs: 0
|
684
|
+
},
|
685
|
+
handler: alignedHandler,
|
686
|
+
mathmlBuilder
|
687
|
+
});
|
688
|
+
|
689
|
+
// alignat environment is like an align environment, but one must explicitly
|
690
|
+
// specify maximum number of columns in each row, and can adjust where spacing occurs.
|
691
|
+
defineEnvironment({
|
692
|
+
type: "array",
|
693
|
+
names: ["alignat", "alignat*", "alignedat"],
|
694
|
+
props: {
|
695
|
+
numArgs: 1
|
696
|
+
},
|
697
|
+
handler: alignedHandler,
|
698
|
+
mathmlBuilder
|
699
|
+
});
|
700
|
+
|
701
|
+
// A gathered environment is like an array environment with one centered
|
702
|
+
// column, but where rows are considered lines so get \jot line spacing
|
703
|
+
// and contents are set in \displaystyle.
|
704
|
+
defineEnvironment({
|
705
|
+
type: "array",
|
706
|
+
names: ["gathered", "gather", "gather*"],
|
707
|
+
props: {
|
708
|
+
numArgs: 0
|
709
|
+
},
|
710
|
+
handler(context) {
|
711
|
+
if (context.envName !== "gathered") {
|
712
|
+
validateAmsEnvironmentContext(context);
|
713
|
+
}
|
714
|
+
const res = {
|
715
|
+
cols: [],
|
716
|
+
envClasses: ["jot", "abut"],
|
717
|
+
addEqnNum: context.envName === "gather",
|
718
|
+
emptySingleRow: true,
|
719
|
+
leqno: context.parser.settings.leqno
|
720
|
+
};
|
721
|
+
return parseArray(context.parser, res, "display");
|
722
|
+
},
|
723
|
+
mathmlBuilder
|
724
|
+
});
|
725
|
+
|
726
|
+
defineEnvironment({
|
727
|
+
type: "array",
|
728
|
+
names: ["equation", "equation*"],
|
729
|
+
props: {
|
730
|
+
numArgs: 0
|
731
|
+
},
|
732
|
+
handler(context) {
|
733
|
+
validateAmsEnvironmentContext(context);
|
734
|
+
const res = {
|
735
|
+
addEqnNum: context.envName === "equation",
|
736
|
+
emptySingleRow: true,
|
737
|
+
singleRow: true,
|
738
|
+
maxNumCols: 1,
|
739
|
+
envClasses: ["align"],
|
740
|
+
leqno: context.parser.settings.leqno
|
741
|
+
};
|
742
|
+
return parseArray(context.parser, res, "display");
|
743
|
+
},
|
744
|
+
mathmlBuilder
|
745
|
+
});
|
746
|
+
|
747
|
+
defineEnvironment({
|
748
|
+
type: "array",
|
749
|
+
names: ["multline", "multline*"],
|
750
|
+
props: {
|
751
|
+
numArgs: 0
|
752
|
+
},
|
753
|
+
handler(context) {
|
754
|
+
validateAmsEnvironmentContext(context);
|
755
|
+
const res = {
|
756
|
+
addEqnNum: context.envName === "multline",
|
757
|
+
maxNumCols: 1,
|
758
|
+
envClasses: ["jot", "multline"],
|
759
|
+
leqno: context.parser.settings.leqno
|
760
|
+
};
|
761
|
+
return parseArray(context.parser, res, "display");
|
762
|
+
},
|
763
|
+
mathmlBuilder
|
764
|
+
});
|
765
|
+
|
766
|
+
defineEnvironment({
|
767
|
+
type: "array",
|
768
|
+
names: ["CD"],
|
769
|
+
props: {
|
770
|
+
numArgs: 0
|
771
|
+
},
|
772
|
+
handler(context) {
|
773
|
+
validateAmsEnvironmentContext(context);
|
774
|
+
return parseCD(context.parser);
|
775
|
+
},
|
776
|
+
mathmlBuilder
|
777
|
+
});
|
778
|
+
|
779
|
+
// Catch \hline outside array environment
|
780
|
+
defineFunction({
|
781
|
+
type: "text", // Doesn't matter what this is.
|
782
|
+
names: ["\\hline", "\\hdashline"],
|
783
|
+
props: {
|
784
|
+
numArgs: 0,
|
785
|
+
allowedInText: true,
|
786
|
+
allowedInMath: true
|
787
|
+
},
|
788
|
+
handler(context, args) {
|
789
|
+
throw new ParseError(`${context.funcName} valid only within array environment`);
|
790
|
+
}
|
791
|
+
});
|