temml 0.9.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.
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
+ });