temml 0.12.2 → 0.13.2

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/src/Parser.js CHANGED
@@ -9,7 +9,7 @@ import { uSubsAndSups, unicodeSubRegEx } from "./unicodeSupOrSub"
9
9
  import { asciiFromScript } from "./asciiFromScript"
10
10
  import SourceLocation from "./SourceLocation";
11
11
  import { Token } from "./Token";
12
- import { isDelimiter } from "./functions/delimsizing"
12
+ import { isDelimiter } from "./functions/delimiter"
13
13
 
14
14
  // Pre-evaluate both modules as unicodeSymbols require String.normalize()
15
15
  import unicodeAccents from /*preval*/ "./unicodeAccents";
@@ -175,7 +175,7 @@ export default class Parser {
175
175
  * Parses an "expression", which is a list of atoms.
176
176
  *
177
177
  * `breakOnInfix`: Should the parsing stop when we hit infix nodes? This
178
- * happens when functions have higher precedence han infix
178
+ * happens when functions have higher precedence than infix
179
179
  * nodes in implicit parses.
180
180
  *
181
181
  * `breakOnTokenText`: The text of the token that the expression should end
@@ -783,7 +783,7 @@ export default class Parser {
783
783
  ) {
784
784
  const firstToken = this.fetch();
785
785
  const text = firstToken.text;
786
-
786
+ if (name === "argument to '\\left'") { return this.parseSymbol() }
787
787
  let result;
788
788
  // Try to parse an open brace or \begingroup
789
789
  if (text === "{" || text === "\\begingroup" || text === "\\toggle") {
@@ -816,6 +816,12 @@ export default class Parser {
816
816
  result = this.parseFunction(breakOnTokenText, name) || this.parseSymbol();
817
817
  if (result == null && text[0] === "\\" &&
818
818
  !Object.prototype.hasOwnProperty.call(implicitCommands, text )) {
819
+ if (this.settings.throwOnError) {
820
+ throw new ParseError("Unsupported function name: " + text, firstToken);
821
+ }
822
+ // For people getting dyanamically rendered math, it's better to
823
+ // show the unsupported command in red rather than panicking for every
824
+ // partially written expression.
819
825
  result = this.formatUnsupportedCmd(text);
820
826
  this.consume();
821
827
  }
@@ -924,7 +930,8 @@ export default class Parser {
924
930
  let symbol;
925
931
  if (symbols[this.mode][text]) {
926
932
  let group = symbols[this.mode][text].group;
927
- if (group === "bin" && binLeftCancellers.includes(this.prevAtomType)) {
933
+ if (group === "bin" &&
934
+ (binLeftCancellers.includes(this.prevAtomType) || this.prevAtomType === "")) {
928
935
  // Change from a binary operator to a unary (prefix) operator
929
936
  group = "open"
930
937
  }
package/src/Settings.js CHANGED
@@ -30,6 +30,7 @@ export default class Settings {
30
30
  : [Infinity, Infinity]
31
31
  )
32
32
  this.maxExpand = Math.max(0, utils.deflt(options.maxExpand, 1000)); // number
33
+ this.wrapDelimiterPairs = true; // boolean
33
34
  }
34
35
 
35
36
  /**
@@ -30,6 +30,7 @@ defineFunction({
30
30
  // That way, the arrow will be an overlay on the content.
31
31
  const phantom = new mathMLTree.MathNode("mphantom", [mml.buildGroup(group.body, style)])
32
32
  const arrow = new mathMLTree.MathNode("mrow", [phantom], ["tml-cancelto"])
33
+ arrow.style.color = style.color
33
34
  if (group.isCharacterBox && smalls.indexOf(group.body.body[0].text) > -1) {
34
35
  arrow.style.left = "0.1em"
35
36
  arrow.style.width = "90%"
@@ -61,6 +62,7 @@ defineFunction({
61
62
  dummyNode = new mathMLTree.MathNode("mphantom", [zeroWidthNode]) // Hide it.
62
63
  }
63
64
  const toNode = mml.buildGroup(group.to, style)
65
+ toNode.style.color = style.color
64
66
  const zeroWidthToNode = new mathMLTree.MathNode("mpadded", [toNode])
65
67
  if (!group.isCharacterBox || /[f∫∑]/.test(group.body.body[0].text)) {
66
68
  const w = new mathMLTree.MathNode("mspace", [])
@@ -18,7 +18,7 @@ const toHex = num => {
18
18
 
19
19
  // Colors from Tables 4.1 and 4.2 of the xcolor package.
20
20
  // Table 4.1 (lower case) RGB values are taken from chroma and xcolor.dtx.
21
- // Table 4.2 (Capitalizzed) values were sampled, because Chroma contains a unreliable
21
+ // Table 4.2 (Capitalized) values were sampled, because Chroma contains a unreliable
22
22
  // conversion from cmyk to RGB. See https://tex.stackexchange.com/a/537274.
23
23
  const xcolors = JSON.parse(`{
24
24
  "Apricot": "#ffb484",
@@ -26,7 +26,41 @@ export const delimiterSizes = {
26
26
  "\\Bigg": { mclass: "mord", size: 4 }
27
27
  };
28
28
 
29
- export const delimiters = [
29
+ export const leftToRight = {
30
+ "(": ")",
31
+ "\\lparen": "\\rparen",
32
+ "[": "]",
33
+ "\\lbrack": "\\rbrack",
34
+ "\\{": "\\}",
35
+ "\\lbrace": "\\rbrace",
36
+ "⦇": "⦈",
37
+ "\\llparenthesis": "\\rrparenthesis",
38
+ "\\lfloor": "\\rfloor",
39
+ "\u230a": "\u230b",
40
+ "\\lceil": "\\rceil",
41
+ "\u2308": "\u2309",
42
+ "\\langle": "\\rangle",
43
+ "\u27e8": "\u27e9",
44
+ "\\lAngle": "\\rAngle",
45
+ "\u27ea": "\u27eb",
46
+ "\\llangle": "\\rrangle",
47
+ "⦉": "⦊",
48
+ "\\lvert": "\\rvert",
49
+ "\\lVert": "\\rVert",
50
+ "\\lgroup": "\\rgroup",
51
+ "\u27ee": "\u27ef",
52
+ "\\lmoustache": "\\rmoustache",
53
+ "\u23b0": "\u23b1",
54
+ "\\llbracket": "\\rrbracket",
55
+ "\u27e6": "\u27e7",
56
+ "\\lBrace": "\\rBrace",
57
+ "\u2983": "\u2984"
58
+ };
59
+
60
+ export const leftDelimiterNames = new Set(Object.keys(leftToRight));
61
+ export const rightDelimiterNames = new Set(Object.values(leftToRight));
62
+
63
+ const delimiters = new Set([
30
64
  "(",
31
65
  "\\lparen",
32
66
  ")",
@@ -82,7 +116,7 @@ export const delimiters = [
82
116
  "\\llbracket",
83
117
  "\\rrbracket",
84
118
  "\u27e6",
85
- "\u27e6",
119
+ "\u27e7",
86
120
  "\\lBrace",
87
121
  "\\rBrace",
88
122
  "\u2983",
@@ -101,12 +135,12 @@ export const delimiters = [
101
135
  "\\updownarrow",
102
136
  "\\Updownarrow",
103
137
  "."
104
- ];
138
+ ]);
105
139
 
106
140
  // Export isDelimiter for benefit of parser.
107
- const dels = ["}", "\\left", "\\middle", "\\right"]
141
+ const dels = new Set(["}", "\\left", "\\middle", "\\right"]);
108
142
  export const isDelimiter = str => str.length > 0 &&
109
- (delimiters.includes(str) || delimiterSizes[str] || dels.includes(str))
143
+ (delimiters.has(str) || delimiterSizes[str] || dels.has(str));
110
144
 
111
145
  // Metrics of the different sizes. Found by looking at TeX's output of
112
146
  // $\bigl| // \Bigl| \biggl| \Biggl| \showlists$
@@ -119,11 +153,11 @@ function checkDelimiter(delim, context) {
119
153
  delim = delim.body[0]; // Unwrap the braces
120
154
  }
121
155
  const symDelim = checkSymbolNodeType(delim)
122
- if (symDelim && delimiters.includes(symDelim.text)) {
156
+ if (symDelim && delimiters.has(symDelim.text)) {
123
157
  // If a character is not in the MathML operator dictionary, it will not stretch.
124
158
  // Replace such characters w/characters that will stretch.
125
- if (["<", "\\lt"].includes(symDelim.text)) { symDelim.text = "⟨" }
126
- if ([">", "\\gt"].includes(symDelim.text)) { symDelim.text = "⟩" }
159
+ if (symDelim.text === "<" || symDelim.text === "\\lt") { symDelim.text = "⟨" }
160
+ if (symDelim.text === ">" || symDelim.text === "\\gt") { symDelim.text = "⟩" }
127
161
  return symDelim;
128
162
  } else if (symDelim) {
129
163
  throw new ParseError(`Invalid delimiter '${symDelim.text}' after '${context.funcName}'`, delim);
@@ -133,7 +167,16 @@ function checkDelimiter(delim, context) {
133
167
  }
134
168
 
135
169
  // / \
136
- const needExplicitStretch = ["\u002F", "\u005C", "\\backslash", "\\vert", "|"];
170
+ const needExplicitStretch = new Set(["\u002F", "\u005C", "\\backslash", "\u2216", "\\vert", "|"]);
171
+
172
+ const makeFenceMo = (delim, mode, form, isStretchy) => {
173
+ const text = delim === "." ? "" : delim;
174
+ const node = new mathMLTree.MathNode("mo", [mml.makeText(text, mode)]);
175
+ node.setAttribute("fence", "true");
176
+ node.setAttribute("form", form);
177
+ node.setAttribute("stretchy", isStretchy ? "true" : "false");
178
+ return node;
179
+ };
137
180
 
138
181
  defineFunction({
139
182
  type: "delimsizing",
@@ -184,9 +227,9 @@ defineFunction({
184
227
  },
185
228
  mathmlBuilder: (group) => {
186
229
  const children = [];
230
+ const delim = group.delim === "." ? "" : group.delim;
187
231
 
188
- if (group.delim === ".") { group.delim = "" }
189
- children.push(mml.makeText(group.delim, group.mode));
232
+ children.push(mml.makeText(delim, group.mode));
190
233
 
191
234
  const node = new mathMLTree.MathNode("mo", children);
192
235
 
@@ -199,7 +242,7 @@ defineFunction({
199
242
  // defaults.
200
243
  node.setAttribute("fence", "false");
201
244
  }
202
- if (needExplicitStretch.includes(group.delim) || group.delim.indexOf("arrow") > -1) {
245
+ if (needExplicitStretch.has(delim) || delim.indexOf("arrow") > -1) {
203
246
  // We have to explicitly set stretchy to true.
204
247
  node.setAttribute("stretchy", "true")
205
248
  }
@@ -212,7 +255,7 @@ defineFunction({
212
255
 
213
256
  function assertParsed(group) {
214
257
  if (!group.body) {
215
- throw new Error("Bug: The leftright ParseNode wasn't fully parsed.");
258
+ throw new Error("Bug: The delim ParseNode wasn't fully parsed.");
216
259
  }
217
260
  }
218
261
 
@@ -243,17 +286,10 @@ defineFunction({
243
286
  const delim = checkDelimiter(args[0], context);
244
287
 
245
288
  const parser = context.parser;
246
- // Parse out the implicit body
247
289
  ++parser.leftrightDepth;
248
- // parseExpression stops before '\\right' or `\\middle`
249
- let body = parser.parseExpression(false, null, true)
290
+ let body = parser.parseExpression(false, "\\right", true)
250
291
  let nextToken = parser.fetch()
251
292
  while (nextToken.text === "\\middle") {
252
- // `\middle`, from the ε-TeX package, ends one group and starts another group.
253
- // We had to parse this expression with `breakOnMiddle` enabled in order
254
- // to get TeX-compliant parsing of \over.
255
- // But we do not want, at this point, to end on \middle, so continue
256
- // to parse until we fetch a `\right`.
257
293
  parser.consume()
258
294
  const middle = parser.fetch().text
259
295
  if (!symbols.math[middle]) {
@@ -262,11 +298,10 @@ defineFunction({
262
298
  checkDelimiter({ type: "atom", mode: "math", text: middle }, { funcName: "\\middle" })
263
299
  body.push({ type: "middle", mode: "math", delim: middle })
264
300
  parser.consume()
265
- body = body.concat(parser.parseExpression(false, null, true))
301
+ body = body.concat(parser.parseExpression(false, "\\right", true))
266
302
  nextToken = parser.fetch()
267
303
  }
268
304
  --parser.leftrightDepth;
269
- // Check the next token
270
305
  parser.expect("\\right", false);
271
306
  const right = assertNodeType(parser.parseFunction(), "leftright-right");
272
307
  return {
@@ -274,39 +309,94 @@ defineFunction({
274
309
  mode: parser.mode,
275
310
  body,
276
311
  left: delim.text,
277
- right: right.delim
312
+ right: right.delim,
313
+ isStretchy: true
278
314
  };
279
315
  },
280
316
  mathmlBuilder: (group, style) => {
281
317
  assertParsed(group);
282
318
  const inner = mml.buildExpression(group.body, style);
283
319
 
284
- if (group.left === ".") { group.left = "" }
285
- const leftNode = new mathMLTree.MathNode("mo", [mml.makeText(group.left, group.mode)]);
286
- leftNode.setAttribute("fence", "true")
287
- leftNode.setAttribute("form", "prefix")
288
- if (group.left === "/" || group.left === "\u005C" || group.left.indexOf("arrow") > -1) {
289
- leftNode.setAttribute("stretchy", "true")
320
+ const leftNode = makeFenceMo(group.left, group.mode, "prefix", true);
321
+ inner.unshift(leftNode);
322
+
323
+ const rightNode = makeFenceMo(group.right, group.mode, "postfix", true);
324
+ if (group.body.length > 0) {
325
+ const lastElement = group.body[group.body.length - 1];
326
+ if (lastElement.type === "color" && !lastElement.isTextColor) {
327
+ rightNode.setAttribute("mathcolor", lastElement.color);
328
+ }
329
+ }
330
+ inner.push(rightNode);
331
+
332
+ return mml.makeRow(inner);
333
+ }
334
+ });
335
+
336
+ defineFunction({
337
+ type: "delimiter",
338
+ names: Array.from(leftDelimiterNames),
339
+ props: {
340
+ numArgs: 0,
341
+ allowedInText: true,
342
+ allowedInMath: true,
343
+ allowedInArgument: true
344
+ },
345
+ handler: ({ parser, funcName, token }) => {
346
+ if (parser.mode === "text") {
347
+ return {
348
+ type: "textord",
349
+ mode: "text",
350
+ text: funcName,
351
+ loc: token.loc
352
+ }
353
+ } else if (!parser.settings.wrapDelimiterPairs) {
354
+ // Treat this token as an ordinary symbol.
355
+ return {
356
+ type: "atom",
357
+ mode: "math",
358
+ family: "open",
359
+ loc: token.loc,
360
+ text: funcName
361
+ };
290
362
  }
291
- inner.unshift(leftNode)
363
+ // Otherwise, try to wrap a pair of delimiters with an <mrow>.
364
+ const rightDelim = leftToRight[funcName];
365
+ // Parse the inner expression, looking for the corresponding right delimiter.
366
+ const body = parser.parseExpression(false, rightDelim, false);
367
+ const nextToken = parser.fetch().text;
292
368
 
293
- if (group.right === ".") { group.right = "" }
294
- const rightNode = new mathMLTree.MathNode("mo", [mml.makeText(group.right, group.mode)]);
295
- rightNode.setAttribute("fence", "true")
296
- rightNode.setAttribute("form", "postfix")
297
- if (group.right === "\u2216" || group.right.indexOf("arrow") > -1) {
298
- rightNode.setAttribute("stretchy", "true")
369
+ if (nextToken !== rightDelim) {
370
+ // We were unable to find a matching right delimiter.
371
+ // Throw control back to renderToMathMLTree.
372
+ // It will reparse the entire expression with wrapDelimiterPairs set to false.
373
+ throw new ParseError("Unmatched delimiter");
299
374
  }
375
+ parser.consume();
376
+
377
+ return {
378
+ type: "delimiter",
379
+ mode: parser.mode,
380
+ body,
381
+ left: funcName,
382
+ right: rightDelim
383
+ };
384
+ },
385
+ mathmlBuilder: (group, style) => {
386
+ assertParsed(group);
387
+ const inner = mml.buildExpression(group.body, style);
388
+
389
+ const leftNode = makeFenceMo(group.left, group.mode, "prefix", false);
390
+ inner.unshift(leftNode);
391
+
392
+ const rightNode = makeFenceMo(group.right, group.mode, "postfix", false);
300
393
  if (group.body.length > 0) {
301
394
  const lastElement = group.body[group.body.length - 1];
302
395
  if (lastElement.type === "color" && !lastElement.isTextColor) {
303
- // \color is a switch. If the last element is of type "color" then
304
- // the user set the \color switch and left it on.
305
- // A \right delimiter turns the switch off, but the delimiter itself gets the color.
306
396
  rightNode.setAttribute("mathcolor", lastElement.color);
307
397
  }
308
398
  }
309
- inner.push(rightNode)
399
+ inner.push(rightNode);
310
400
 
311
401
  return mml.makeRow(inner);
312
402
  }
@@ -331,7 +421,7 @@ defineFunction({
331
421
  delim: delim.text
332
422
  };
333
423
  },
334
- mathmlBuilder: (group, style) => {
424
+ mathmlBuilder: (group) => {
335
425
  const textNode = mml.makeText(group.delim, group.mode);
336
426
  const middleNode = new mathMLTree.MathNode("mo", [textNode]);
337
427
  middleNode.setAttribute("fence", "true");
@@ -32,7 +32,7 @@ const mathmlBuilder = (group, style) => {
32
32
  break
33
33
  case "\\xcancel":
34
34
  node.setAttribute("notation", "updiagonalstrike downdiagonalstrike")
35
- node.classes.push("tml-xcancel")
35
+ node.children.push(new mathMLTree.MathNode("mrow", [], ["tml-cancel", "tml-xcancel"]))
36
36
  break
37
37
  // cancelto is handled in cancelto.js
38
38
  case "\\longdiv":
@@ -9,22 +9,37 @@ import { calculateSize } from "../units";
9
9
  const stylArray = ["display", "text", "script", "scriptscript"];
10
10
  const scriptLevel = { auto: -1, display: 0, text: 0, script: 1, scriptscript: 2 };
11
11
 
12
+ const adjustStyle = (functionSize, originalStyle) => {
13
+ // Figure out what style this fraction should be in based on the
14
+ // function used
15
+ let style = originalStyle;
16
+ if (functionSize === "display") { //\tfrac or \cfrac
17
+ // Get display style as a default.
18
+ // If incoming style is sub/sup, use style.text() to get correct size.
19
+ const newSize = style.level >= StyleLevel.SCRIPT ? StyleLevel.TEXT : StyleLevel.DISPLAY;
20
+ style = style.withLevel(newSize)
21
+ } else if (functionSize === "text" &&
22
+ style.level === StyleLevel.DISPLAY) {
23
+ // We're in a \tfrac but incoming style is displaystyle, so:
24
+ style = style.withLevel(StyleLevel.TEXT);
25
+ } else if (functionSize === "auto") {
26
+ style = style.incrementLevel()
27
+ } else if (functionSize === "script") {
28
+ style = style.withLevel(StyleLevel.SCRIPT);
29
+ } else if (functionSize === "scriptscript") {
30
+ style = style.withLevel(StyleLevel.SCRIPTSCRIPT)
31
+ }
32
+ return style;
33
+ };
34
+
12
35
  const mathmlBuilder = (group, style) => {
13
- // Track the scriptLevel of the numerator and denominator.
14
- // We may need that info for \mathchoice or for adjusting em dimensions.
15
- const childOptions = group.scriptLevel === "auto"
16
- ? style.incrementLevel()
17
- : group.scriptLevel === "display"
18
- ? style.withLevel(StyleLevel.TEXT)
19
- : group.scriptLevel === "text"
20
- ? style.withLevel(StyleLevel.SCRIPT)
21
- : style.withLevel(StyleLevel.SCRIPTSCRIPT);
36
+ style = adjustStyle(group.scriptLevel, style)
22
37
 
23
38
  // Chromium (wrongly) continues to shrink fractions beyond scriptscriptlevel.
24
39
  // So we check for levels that Chromium shrinks too small.
25
40
  // If necessary, set an explicit fraction depth.
26
- const numer = mml.buildGroup(group.numer, childOptions)
27
- const denom = mml.buildGroup(group.denom, childOptions)
41
+ const numer = mml.buildGroup(group.numer, style)
42
+ const denom = mml.buildGroup(group.denom, style)
28
43
  if (style.level === 3) {
29
44
  numer.style.mathDepth = "2"
30
45
  numer.setAttribute("scriptlevel", "2")
@@ -77,6 +92,7 @@ const mathmlBuilder = (group, style) => {
77
92
  defineFunction({
78
93
  type: "genfrac",
79
94
  names: [
95
+ "\\cfrac",
80
96
  "\\dfrac",
81
97
  "\\frac",
82
98
  "\\tfrac",
@@ -100,6 +116,7 @@ defineFunction({
100
116
  let scriptLevel = "auto";
101
117
 
102
118
  switch (funcName) {
119
+ case "\\cfrac":
103
120
  case "\\dfrac":
104
121
  case "\\frac":
105
122
  case "\\tfrac":
@@ -126,15 +143,10 @@ defineFunction({
126
143
  throw new Error("Unrecognized genfrac command");
127
144
  }
128
145
 
129
- switch (funcName) {
130
- case "\\dfrac":
131
- case "\\dbinom":
132
- scriptLevel = "display";
133
- break;
134
- case "\\tfrac":
135
- case "\\tbinom":
136
- scriptLevel = "text";
137
- break;
146
+ if (funcName === "\\cfrac" || funcName.startsWith("\\d")) {
147
+ scriptLevel = "display";
148
+ } else if (funcName.startsWith("\\t")) {
149
+ scriptLevel = "text";
138
150
  }
139
151
 
140
152
  return {
@@ -153,31 +165,6 @@ defineFunction({
153
165
  mathmlBuilder
154
166
  });
155
167
 
156
- defineFunction({
157
- type: "genfrac",
158
- names: ["\\cfrac"],
159
- props: {
160
- numArgs: 2
161
- },
162
- handler: ({ parser, funcName }, args) => {
163
- const numer = args[0];
164
- const denom = args[1];
165
-
166
- return {
167
- type: "genfrac",
168
- mode: parser.mode,
169
- continued: true,
170
- numer,
171
- denom,
172
- hasBarLine: true,
173
- leftDelim: null,
174
- rightDelim: null,
175
- scriptLevel: "display",
176
- barSize: null
177
- };
178
- }
179
- });
180
-
181
168
  // Infix generalized fractions -- these are not rendered directly, but replaced
182
169
  // immediately by one of the variants above.
183
170
  defineFunction({
@@ -148,8 +148,16 @@ export const binrelClass = (arg) => {
148
148
  const atom = arg.type === "ordgroup" && arg.body.length && arg.body.length === 1
149
149
  ? arg.body[0]
150
150
  : arg;
151
- if (atom.type === "atom" && (atom.family === "bin" || atom.family === "rel")) {
152
- return "m" + atom.family;
151
+ if (atom.type === "atom") {
152
+ // BIN args are sometimes changed to OPEN, so check the original family.
153
+ const family = arg.body.length > 0 && arg.body[0].text && symbols.math[arg.body[0].text]
154
+ ? symbols.math[arg.body[0].text].group
155
+ : atom.family
156
+ if (family === "bin" || family === "rel") {
157
+ return "m" + family;
158
+ } else {
159
+ return "mord";
160
+ }
153
161
  } else {
154
162
  return "mord";
155
163
  }
@@ -2,7 +2,7 @@
2
2
  import defineFunction, { ordargument } from "../defineFunction";
3
3
  import * as mathMLTree from "../mathMLTree";
4
4
  import * as mml from "../buildMathML";
5
- import { isDelimiter } from "./delimsizing"
5
+ import { isDelimiter } from "./delimiter"
6
6
 
7
7
  // Some helpers
8
8
 
@@ -3,7 +3,7 @@ import defineMacro from "../defineMacro";
3
3
  import * as mathMLTree from "../mathMLTree"
4
4
  import { spaceCharacter } from "./kern"
5
5
  import { ordTypes } from "./op"
6
- import { isDelimiter } from "./delimsizing"
6
+ import { isDelimiter } from "./delimiter"
7
7
 
8
8
  import * as mml from "../buildMathML"
9
9
 
@@ -22,6 +22,14 @@ const mathmlBuilder = (group, style) => {
22
22
  if ((node.type === "mrow" || node.type === "mpadded") && node.children.length === 1 &&
23
23
  node.children[0] instanceof mathMLTree.MathNode) {
24
24
  node = node.children[0]
25
+ } else if (node.type === "mrow" && node.children.length === 2 &&
26
+ node.children[0] instanceof mathMLTree.MathNode &&
27
+ node.children[1] instanceof mathMLTree.MathNode &&
28
+ node.children[1].type === "mspace" && !node.children[1].attributes.width &&
29
+ node.children[1].children.length === 0) {
30
+ // This is a workaround for a Firefox bug that applies spacing to
31
+ // an <mi> with mathvariant="normal".
32
+ node = node.children[0];
25
33
  }
26
34
  switch (node.type) {
27
35
  case "mi":
@@ -41,8 +41,8 @@ defineFunctionBuilders({
41
41
  node.setAttribute("mathvariant", "normal")
42
42
  if (text.text.length === 1) {
43
43
  // A Firefox bug will apply spacing here, but there should be none. Fix it.
44
- node = new mathMLTree.MathNode("mpadded", [node])
45
- node.setAttribute("lspace", "0")
44
+ const mspace = new mathMLTree.MathNode("mspace", [])
45
+ node = new mathMLTree.MathNode("mrow", [node, mspace])
46
46
  }
47
47
  }
48
48
  return node
package/src/functions.js CHANGED
@@ -16,7 +16,7 @@ import "./functions/char";
16
16
  import "./functions/color";
17
17
  import "./functions/cr";
18
18
  import "./functions/def";
19
- import "./functions/delimsizing";
19
+ import "./functions/delimiter";
20
20
  import "./functions/enclose";
21
21
  import "./functions/environment";
22
22
  import "./functions/envTag";
@@ -26,16 +26,12 @@ import { DocumentFragment } from "./tree"
26
26
  * much of this module.
27
27
  */
28
28
 
29
- const openDelims = "([{⌊⌈⟨⟮⎰⟦⦃"
30
- const closeDelims = ")]}⌋⌉⟩⟯⎱⟦⦄"
31
-
32
29
  export default function setLineBreaks(expression, wrapMode, isDisplayMode) {
33
30
  const mtrs = [];
34
31
  let mrows = [];
35
32
  let block = [];
36
33
  let numTopLevelEquals = 0
37
34
  let i = 0
38
- let level = 0
39
35
  while (i < expression.length) {
40
36
  while (expression[i] instanceof DocumentFragment) {
41
37
  expression.splice(i, 1, ...expression[i].children) // Expand the fragment.
@@ -58,13 +54,10 @@ export default function setLineBreaks(expression, wrapMode, isDisplayMode) {
58
54
  }
59
55
  block.push(node);
60
56
  if (node.type && node.type === "mo" && node.children.length === 1 &&
57
+ !(node.attributes.form && node.attributes.form === "prefix") && // unary operators
61
58
  !Object.prototype.hasOwnProperty.call(node.attributes, "movablelimits")) {
62
59
  const ch = node.children[0].text
63
- if (openDelims.indexOf(ch) > -1) {
64
- level += 1
65
- } else if (closeDelims.indexOf(ch) > -1) {
66
- level -= 1
67
- } else if (level === 0 && wrapMode === "=" && ch === "=") {
60
+ if (wrapMode === "=" && ch === "=") {
68
61
  numTopLevelEquals += 1
69
62
  if (numTopLevelEquals > 1) {
70
63
  block.pop()
@@ -73,7 +66,7 @@ export default function setLineBreaks(expression, wrapMode, isDisplayMode) {
73
66
  mrows.push(element)
74
67
  block = [node];
75
68
  }
76
- } else if (level === 0 && wrapMode === "tex" && ch !== "∇") {
69
+ } else if (wrapMode === "tex") {
77
70
  // Check if the following node is a \nobreak text node, e.g. "~""
78
71
  const next = i < expression.length - 1 ? expression[i + 1] : null;
79
72
  let glueIsFreeOfNobreak = true;
package/src/macros.js CHANGED
@@ -318,7 +318,6 @@ const dotsByToken = {
318
318
  "\\iint": "\\dotsi",
319
319
  "\\iiint": "\\dotsi",
320
320
  "\\iiiint": "\\dotsi",
321
- "\\idotsint": "\\dotsi",
322
321
  // Symbols whose definition starts with \DOTSX:
323
322
  "\\DOTSX": "\\dotsx"
324
323
  };
@@ -400,7 +399,7 @@ defineMacro("\\cdots", function(context) {
400
399
  defineMacro("\\dotsb", "\\cdots");
401
400
  defineMacro("\\dotsm", "\\cdots");
402
401
  defineMacro("\\dotsi", "\\!\\cdots");
403
- defineMacro("\\idotsint", "\\dotsi");
402
+ defineMacro("\\idotsint", "\\int\\!\\cdots\\!\\int");
404
403
  // amsmath doesn't actually define \dotsx, but \dots followed by a macro
405
404
  // starting with \DOTSX implies \dotso, and then \extra@ detects this case
406
405
  // and forces the added `\,`.
@@ -695,6 +694,7 @@ defineMacro("\\upomega", "\\up@greek{\\omega}");
695
694
  // cmll package
696
695
  defineMacro("\\invamp", '\\mathbin{\\char"214b}')
697
696
  defineMacro("\\parr", '\\mathbin{\\char"214b}')
697
+ defineMacro("\\upand", '\\mathbin{\\char"214b}') // STIX package
698
698
  defineMacro("\\with", '\\mathbin{\\char"26}')
699
699
  defineMacro("\\multimapinv", '\\mathrel{\\char"27dc}')
700
700
  defineMacro("\\multimapboth", '\\mathrel{\\char"29df}')
package/src/parseNode.js CHANGED
@@ -34,7 +34,7 @@ export function assertSymbolNodeType(node) {
34
34
  * returns null.
35
35
  */
36
36
  export function checkSymbolNodeType(node) {
37
- if (node && (node.type === "atom" ||
37
+ if (node && (node.type === "atom" || node.type === "delimiter" ||
38
38
  Object.prototype.hasOwnProperty.call(NON_ATOMS, node.type))) {
39
39
  return node;
40
40
  }