temml 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (117) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +44 -0
  3. package/contrib/auto-render/README.md +89 -0
  4. package/contrib/auto-render/auto-render.js +128 -0
  5. package/contrib/auto-render/dist/auto-render.js +217 -0
  6. package/contrib/auto-render/dist/auto-render.min.js +1 -0
  7. package/contrib/auto-render/splitAtDelimiters.js +84 -0
  8. package/contrib/auto-render/test/auto-render-spec.js +234 -0
  9. package/contrib/auto-render/test/auto-render.js +217 -0
  10. package/contrib/auto-render/test/test_page.html +59 -0
  11. package/contrib/mhchem/README.md +26 -0
  12. package/contrib/mhchem/mhchem.js +1705 -0
  13. package/contrib/mhchem/mhchem.min.js +1 -0
  14. package/contrib/physics/README.md +20 -0
  15. package/contrib/physics/physics.js +131 -0
  16. package/contrib/texvc/README.md +23 -0
  17. package/contrib/texvc/texvc.js +61 -0
  18. package/dist/Temml-Asana.css +201 -0
  19. package/dist/Temml-Latin-Modern.css +216 -0
  20. package/dist/Temml-Libertinus.css +214 -0
  21. package/dist/Temml-Local.css +194 -0
  22. package/dist/Temml-STIX2.css +203 -0
  23. package/dist/Temml.woff2 +0 -0
  24. package/dist/temml.cjs +13122 -0
  25. package/dist/temml.js +11225 -0
  26. package/dist/temml.min.js +1 -0
  27. package/dist/temml.mjs +13120 -0
  28. package/dist/temmlPostProcess.js +70 -0
  29. package/package.json +34 -0
  30. package/src/Lexer.js +121 -0
  31. package/src/MacroExpander.js +437 -0
  32. package/src/Namespace.js +107 -0
  33. package/src/ParseError.js +64 -0
  34. package/src/Parser.js +977 -0
  35. package/src/Settings.js +49 -0
  36. package/src/SourceLocation.js +29 -0
  37. package/src/Style.js +144 -0
  38. package/src/Token.js +40 -0
  39. package/src/buildMathML.js +235 -0
  40. package/src/constants.js +25 -0
  41. package/src/defineEnvironment.js +25 -0
  42. package/src/defineFunction.js +69 -0
  43. package/src/defineMacro.js +11 -0
  44. package/src/domTree.js +185 -0
  45. package/src/environments/array.js +791 -0
  46. package/src/environments/cd.js +252 -0
  47. package/src/environments.js +8 -0
  48. package/src/functions/accent.js +127 -0
  49. package/src/functions/accentunder.js +38 -0
  50. package/src/functions/arrow.js +204 -0
  51. package/src/functions/cancelto.js +36 -0
  52. package/src/functions/char.js +33 -0
  53. package/src/functions/color.js +253 -0
  54. package/src/functions/cr.js +46 -0
  55. package/src/functions/def.js +259 -0
  56. package/src/functions/delimsizing.js +304 -0
  57. package/src/functions/enclose.js +193 -0
  58. package/src/functions/envTag.js +38 -0
  59. package/src/functions/environment.js +59 -0
  60. package/src/functions/font.js +123 -0
  61. package/src/functions/genfrac.js +333 -0
  62. package/src/functions/hbox.js +29 -0
  63. package/src/functions/horizBrace.js +32 -0
  64. package/src/functions/href.js +90 -0
  65. package/src/functions/html.js +95 -0
  66. package/src/functions/includegraphics.js +131 -0
  67. package/src/functions/kern.js +75 -0
  68. package/src/functions/label.js +29 -0
  69. package/src/functions/lap.js +75 -0
  70. package/src/functions/math.js +40 -0
  71. package/src/functions/mathchoice.js +41 -0
  72. package/src/functions/mclass.js +201 -0
  73. package/src/functions/multiscript.js +91 -0
  74. package/src/functions/not.js +46 -0
  75. package/src/functions/op.js +338 -0
  76. package/src/functions/operatorname.js +139 -0
  77. package/src/functions/ordgroup.js +9 -0
  78. package/src/functions/phantom.js +73 -0
  79. package/src/functions/pmb.js +31 -0
  80. package/src/functions/raise.js +68 -0
  81. package/src/functions/ref.js +28 -0
  82. package/src/functions/relax.js +16 -0
  83. package/src/functions/rule.js +52 -0
  84. package/src/functions/sizing.js +64 -0
  85. package/src/functions/smash.js +66 -0
  86. package/src/functions/sqrt.js +31 -0
  87. package/src/functions/styling.js +58 -0
  88. package/src/functions/supsub.js +135 -0
  89. package/src/functions/symbolsOp.js +53 -0
  90. package/src/functions/symbolsOrd.js +102 -0
  91. package/src/functions/symbolsSpacing.js +53 -0
  92. package/src/functions/tag.js +8 -0
  93. package/src/functions/text.js +75 -0
  94. package/src/functions/tip.js +63 -0
  95. package/src/functions/toggle.js +13 -0
  96. package/src/functions/verb.js +33 -0
  97. package/src/functions.js +57 -0
  98. package/src/linebreaking.js +159 -0
  99. package/src/macros.js +708 -0
  100. package/src/mathMLTree.js +175 -0
  101. package/src/parseNode.js +42 -0
  102. package/src/parseTree.js +40 -0
  103. package/src/postProcess.js +57 -0
  104. package/src/replace.js +225 -0
  105. package/src/stretchy.js +66 -0
  106. package/src/svg.js +110 -0
  107. package/src/symbols.js +972 -0
  108. package/src/tree.js +50 -0
  109. package/src/unicodeAccents.js +16 -0
  110. package/src/unicodeScripts.js +119 -0
  111. package/src/unicodeSupOrSub.js +108 -0
  112. package/src/unicodeSymbolBuilder.js +31 -0
  113. package/src/unicodeSymbols.js +320 -0
  114. package/src/units.js +109 -0
  115. package/src/utils.js +109 -0
  116. package/src/variant.js +103 -0
  117. 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
+ });