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