temml 0.13.1 → 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/dist/temml.cjs CHANGED
@@ -215,6 +215,7 @@ class Settings {
215
215
  : [Infinity, Infinity]
216
216
  );
217
217
  this.maxExpand = Math.max(0, deflt(options.maxExpand, 1000)); // number
218
+ this.wrapDelimiterPairs = true; // boolean
218
219
  }
219
220
 
220
221
  /**
@@ -996,8 +997,7 @@ defineSymbol(math, textord, "\u2135", "\\aleph", true);
996
997
  defineSymbol(math, textord, "\u2200", "\\forall", true);
997
998
  defineSymbol(math, textord, "\u210f", "\\hbar", true);
998
999
  defineSymbol(math, textord, "\u2203", "\\exists", true);
999
- // is actually a unary operator, not binary. But this works.
1000
- defineSymbol(math, bin, "\u2207", "\\nabla", true);
1000
+ defineSymbol(math, open, "\u2207", "\\nabla", true);
1001
1001
  defineSymbol(math, textord, "\u266d", "\\flat", true);
1002
1002
  defineSymbol(math, textord, "\u2113", "\\ell", true);
1003
1003
  defineSymbol(math, textord, "\u266e", "\\natural", true);
@@ -1090,7 +1090,7 @@ defineSymbol(math, mathord, "\u2295", "\\Earth");
1090
1090
 
1091
1091
  // AMS Negated Binary Relations
1092
1092
  defineSymbol(math, rel, "\u226e", "\\nless", true);
1093
- // Symbol names preceeded by "@" each have a corresponding macro.
1093
+ // Symbol names preceded by "@" each have a corresponding macro.
1094
1094
  defineSymbol(math, rel, "\u2a87", "\\lneq", true);
1095
1095
  defineSymbol(math, rel, "\u2268", "\\lneqq", true);
1096
1096
  defineSymbol(math, rel, "\u2268\ufe00", "\\lvertneqq");
@@ -2001,16 +2001,12 @@ for (let i = 0; i < 10; i++) {
2001
2001
  * much of this module.
2002
2002
  */
2003
2003
 
2004
- const openDelims = "([{⌊⌈⟨⟮⎰⟦⦃";
2005
- const closeDelims = ")]}⌋⌉⟩⟯⎱⟦⦄";
2006
-
2007
2004
  function setLineBreaks(expression, wrapMode, isDisplayMode) {
2008
2005
  const mtrs = [];
2009
2006
  let mrows = [];
2010
2007
  let block = [];
2011
2008
  let numTopLevelEquals = 0;
2012
2009
  let i = 0;
2013
- let level = 0;
2014
2010
  while (i < expression.length) {
2015
2011
  while (expression[i] instanceof DocumentFragment) {
2016
2012
  expression.splice(i, 1, ...expression[i].children); // Expand the fragment.
@@ -2033,13 +2029,10 @@ function setLineBreaks(expression, wrapMode, isDisplayMode) {
2033
2029
  }
2034
2030
  block.push(node);
2035
2031
  if (node.type && node.type === "mo" && node.children.length === 1 &&
2032
+ !(node.attributes.form && node.attributes.form === "prefix") && // unary operators
2036
2033
  !Object.prototype.hasOwnProperty.call(node.attributes, "movablelimits")) {
2037
2034
  const ch = node.children[0].text;
2038
- if (openDelims.indexOf(ch) > -1) {
2039
- level += 1;
2040
- } else if (closeDelims.indexOf(ch) > -1) {
2041
- level -= 1;
2042
- } else if (level === 0 && wrapMode === "=" && ch === "=") {
2035
+ if (wrapMode === "=" && ch === "=") {
2043
2036
  numTopLevelEquals += 1;
2044
2037
  if (numTopLevelEquals > 1) {
2045
2038
  block.pop();
@@ -2048,7 +2041,7 @@ function setLineBreaks(expression, wrapMode, isDisplayMode) {
2048
2041
  mrows.push(element);
2049
2042
  block = [node];
2050
2043
  }
2051
- } else if (level === 0 && wrapMode === "tex" && ch !== "∇") {
2044
+ } else if (wrapMode === "tex") {
2052
2045
  // Check if the following node is a \nobreak text node, e.g. "~""
2053
2046
  const next = i < expression.length - 1 ? expression[i + 1] : null;
2054
2047
  let glueIsFreeOfNobreak = true;
@@ -3070,7 +3063,7 @@ function assertSymbolNodeType(node) {
3070
3063
  * returns null.
3071
3064
  */
3072
3065
  function checkSymbolNodeType(node) {
3073
- if (node && (node.type === "atom" ||
3066
+ if (node && (node.type === "atom" || node.type === "delimiter" ||
3074
3067
  Object.prototype.hasOwnProperty.call(NON_ATOMS, node.type))) {
3075
3068
  return node;
3076
3069
  }
@@ -4252,6 +4245,7 @@ defineMacro("\\upomega", "\\up@greek{\\omega}");
4252
4245
  // cmll package
4253
4246
  defineMacro("\\invamp", '\\mathbin{\\char"214b}');
4254
4247
  defineMacro("\\parr", '\\mathbin{\\char"214b}');
4248
+ defineMacro("\\upand", '\\mathbin{\\char"214b}'); // STIX package
4255
4249
  defineMacro("\\with", '\\mathbin{\\char"26}');
4256
4250
  defineMacro("\\multimapinv", '\\mathrel{\\char"27dc}');
4257
4251
  defineMacro("\\multimapboth", '\\mathrel{\\char"29df}');
@@ -7223,6 +7217,7 @@ defineFunction({
7223
7217
  // That way, the arrow will be an overlay on the content.
7224
7218
  const phantom = new MathNode("mphantom", [buildGroup$1(group.body, style)]);
7225
7219
  const arrow = new MathNode("mrow", [phantom], ["tml-cancelto"]);
7220
+ arrow.style.color = style.color;
7226
7221
  if (group.isCharacterBox && smalls.indexOf(group.body.body[0].text) > -1) {
7227
7222
  arrow.style.left = "0.1em";
7228
7223
  arrow.style.width = "90%";
@@ -7254,6 +7249,7 @@ defineFunction({
7254
7249
  dummyNode = new MathNode("mphantom", [zeroWidthNode]); // Hide it.
7255
7250
  }
7256
7251
  const toNode = buildGroup$1(group.to, style);
7252
+ toNode.style.color = style.color;
7257
7253
  const zeroWidthToNode = new MathNode("mpadded", [toNode]);
7258
7254
  if (!group.isCharacterBox || /[f∫∑]/.test(group.body.body[0].text)) {
7259
7255
  const w = new MathNode("mspace", []);
@@ -7312,7 +7308,7 @@ const toHex = num => {
7312
7308
 
7313
7309
  // Colors from Tables 4.1 and 4.2 of the xcolor package.
7314
7310
  // Table 4.1 (lower case) RGB values are taken from chroma and xcolor.dtx.
7315
- // Table 4.2 (Capitalizzed) values were sampled, because Chroma contains a unreliable
7311
+ // Table 4.2 (Capitalized) values were sampled, because Chroma contains a unreliable
7316
7312
  // conversion from cmyk to RGB. See https://tex.stackexchange.com/a/537274.
7317
7313
  const xcolors = JSON.parse(`{
7318
7314
  "Apricot": "#ffb484",
@@ -7871,7 +7867,41 @@ const delimiterSizes = {
7871
7867
  "\\Bigg": { mclass: "mord", size: 4 }
7872
7868
  };
7873
7869
 
7874
- const delimiters = [
7870
+ const leftToRight = {
7871
+ "(": ")",
7872
+ "\\lparen": "\\rparen",
7873
+ "[": "]",
7874
+ "\\lbrack": "\\rbrack",
7875
+ "\\{": "\\}",
7876
+ "\\lbrace": "\\rbrace",
7877
+ "⦇": "⦈",
7878
+ "\\llparenthesis": "\\rrparenthesis",
7879
+ "\\lfloor": "\\rfloor",
7880
+ "\u230a": "\u230b",
7881
+ "\\lceil": "\\rceil",
7882
+ "\u2308": "\u2309",
7883
+ "\\langle": "\\rangle",
7884
+ "\u27e8": "\u27e9",
7885
+ "\\lAngle": "\\rAngle",
7886
+ "\u27ea": "\u27eb",
7887
+ "\\llangle": "\\rrangle",
7888
+ "⦉": "⦊",
7889
+ "\\lvert": "\\rvert",
7890
+ "\\lVert": "\\rVert",
7891
+ "\\lgroup": "\\rgroup",
7892
+ "\u27ee": "\u27ef",
7893
+ "\\lmoustache": "\\rmoustache",
7894
+ "\u23b0": "\u23b1",
7895
+ "\\llbracket": "\\rrbracket",
7896
+ "\u27e6": "\u27e7",
7897
+ "\\lBrace": "\\rBrace",
7898
+ "\u2983": "\u2984"
7899
+ };
7900
+
7901
+ const leftDelimiterNames = new Set(Object.keys(leftToRight));
7902
+ new Set(Object.values(leftToRight));
7903
+
7904
+ const delimiters = new Set([
7875
7905
  "(",
7876
7906
  "\\lparen",
7877
7907
  ")",
@@ -7927,7 +7957,7 @@ const delimiters = [
7927
7957
  "\\llbracket",
7928
7958
  "\\rrbracket",
7929
7959
  "\u27e6",
7930
- "\u27e6",
7960
+ "\u27e7",
7931
7961
  "\\lBrace",
7932
7962
  "\\rBrace",
7933
7963
  "\u2983",
@@ -7946,12 +7976,12 @@ const delimiters = [
7946
7976
  "\\updownarrow",
7947
7977
  "\\Updownarrow",
7948
7978
  "."
7949
- ];
7979
+ ]);
7950
7980
 
7951
7981
  // Export isDelimiter for benefit of parser.
7952
- const dels = ["}", "\\left", "\\middle", "\\right"];
7982
+ const dels = new Set(["}", "\\left", "\\middle", "\\right"]);
7953
7983
  const isDelimiter = str => str.length > 0 &&
7954
- (delimiters.includes(str) || delimiterSizes[str] || dels.includes(str));
7984
+ (delimiters.has(str) || delimiterSizes[str] || dels.has(str));
7955
7985
 
7956
7986
  // Metrics of the different sizes. Found by looking at TeX's output of
7957
7987
  // $\bigl| // \Bigl| \biggl| \Biggl| \showlists$
@@ -7964,11 +7994,11 @@ function checkDelimiter(delim, context) {
7964
7994
  delim = delim.body[0]; // Unwrap the braces
7965
7995
  }
7966
7996
  const symDelim = checkSymbolNodeType(delim);
7967
- if (symDelim && delimiters.includes(symDelim.text)) {
7997
+ if (symDelim && delimiters.has(symDelim.text)) {
7968
7998
  // If a character is not in the MathML operator dictionary, it will not stretch.
7969
7999
  // Replace such characters w/characters that will stretch.
7970
- if (["<", "\\lt"].includes(symDelim.text)) { symDelim.text = "⟨"; }
7971
- if ([">", "\\gt"].includes(symDelim.text)) { symDelim.text = "⟩"; }
8000
+ if (symDelim.text === "<" || symDelim.text === "\\lt") { symDelim.text = "⟨"; }
8001
+ if (symDelim.text === ">" || symDelim.text === "\\gt") { symDelim.text = "⟩"; }
7972
8002
  return symDelim;
7973
8003
  } else if (symDelim) {
7974
8004
  throw new ParseError(`Invalid delimiter '${symDelim.text}' after '${context.funcName}'`, delim);
@@ -7978,7 +8008,16 @@ function checkDelimiter(delim, context) {
7978
8008
  }
7979
8009
 
7980
8010
  // / \
7981
- const needExplicitStretch = ["\u002F", "\u005C", "\\backslash", "\\vert", "|"];
8011
+ const needExplicitStretch = new Set(["\u002F", "\u005C", "\\backslash", "\u2216", "\\vert", "|"]);
8012
+
8013
+ const makeFenceMo = (delim, mode, form, isStretchy) => {
8014
+ const text = delim === "." ? "" : delim;
8015
+ const node = new MathNode("mo", [makeText(text, mode)]);
8016
+ node.setAttribute("fence", "true");
8017
+ node.setAttribute("form", form);
8018
+ node.setAttribute("stretchy", isStretchy ? "true" : "false");
8019
+ return node;
8020
+ };
7982
8021
 
7983
8022
  defineFunction({
7984
8023
  type: "delimsizing",
@@ -8029,9 +8068,9 @@ defineFunction({
8029
8068
  },
8030
8069
  mathmlBuilder: (group) => {
8031
8070
  const children = [];
8071
+ const delim = group.delim === "." ? "" : group.delim;
8032
8072
 
8033
- if (group.delim === ".") { group.delim = ""; }
8034
- children.push(makeText(group.delim, group.mode));
8073
+ children.push(makeText(delim, group.mode));
8035
8074
 
8036
8075
  const node = new MathNode("mo", children);
8037
8076
 
@@ -8044,7 +8083,7 @@ defineFunction({
8044
8083
  // defaults.
8045
8084
  node.setAttribute("fence", "false");
8046
8085
  }
8047
- if (needExplicitStretch.includes(group.delim) || group.delim.indexOf("arrow") > -1) {
8086
+ if (needExplicitStretch.has(delim) || delim.indexOf("arrow") > -1) {
8048
8087
  // We have to explicitly set stretchy to true.
8049
8088
  node.setAttribute("stretchy", "true");
8050
8089
  }
@@ -8057,7 +8096,7 @@ defineFunction({
8057
8096
 
8058
8097
  function assertParsed(group) {
8059
8098
  if (!group.body) {
8060
- throw new Error("Bug: The leftright ParseNode wasn't fully parsed.");
8099
+ throw new Error("Bug: The delim ParseNode wasn't fully parsed.");
8061
8100
  }
8062
8101
  }
8063
8102
 
@@ -8088,17 +8127,10 @@ defineFunction({
8088
8127
  const delim = checkDelimiter(args[0], context);
8089
8128
 
8090
8129
  const parser = context.parser;
8091
- // Parse out the implicit body
8092
8130
  ++parser.leftrightDepth;
8093
- // parseExpression stops before '\\right' or `\\middle`
8094
- let body = parser.parseExpression(false, null, true);
8131
+ let body = parser.parseExpression(false, "\\right", true);
8095
8132
  let nextToken = parser.fetch();
8096
8133
  while (nextToken.text === "\\middle") {
8097
- // `\middle`, from the ε-TeX package, ends one group and starts another group.
8098
- // We had to parse this expression with `breakOnMiddle` enabled in order
8099
- // to get TeX-compliant parsing of \over.
8100
- // But we do not want, at this point, to end on \middle, so continue
8101
- // to parse until we fetch a `\right`.
8102
8134
  parser.consume();
8103
8135
  const middle = parser.fetch().text;
8104
8136
  if (!symbols.math[middle]) {
@@ -8107,11 +8139,10 @@ defineFunction({
8107
8139
  checkDelimiter({ type: "atom", mode: "math", text: middle }, { funcName: "\\middle" });
8108
8140
  body.push({ type: "middle", mode: "math", delim: middle });
8109
8141
  parser.consume();
8110
- body = body.concat(parser.parseExpression(false, null, true));
8142
+ body = body.concat(parser.parseExpression(false, "\\right", true));
8111
8143
  nextToken = parser.fetch();
8112
8144
  }
8113
8145
  --parser.leftrightDepth;
8114
- // Check the next token
8115
8146
  parser.expect("\\right", false);
8116
8147
  const right = assertNodeType(parser.parseFunction(), "leftright-right");
8117
8148
  return {
@@ -8119,35 +8150,90 @@ defineFunction({
8119
8150
  mode: parser.mode,
8120
8151
  body,
8121
8152
  left: delim.text,
8122
- right: right.delim
8153
+ right: right.delim,
8154
+ isStretchy: true
8123
8155
  };
8124
8156
  },
8125
8157
  mathmlBuilder: (group, style) => {
8126
8158
  assertParsed(group);
8127
8159
  const inner = buildExpression(group.body, style);
8128
8160
 
8129
- if (group.left === ".") { group.left = ""; }
8130
- const leftNode = new MathNode("mo", [makeText(group.left, group.mode)]);
8131
- leftNode.setAttribute("fence", "true");
8132
- leftNode.setAttribute("form", "prefix");
8133
- if (group.left === "/" || group.left === "\u005C" || group.left.indexOf("arrow") > -1) {
8134
- leftNode.setAttribute("stretchy", "true");
8135
- }
8161
+ const leftNode = makeFenceMo(group.left, group.mode, "prefix", true);
8136
8162
  inner.unshift(leftNode);
8137
8163
 
8138
- if (group.right === ".") { group.right = ""; }
8139
- const rightNode = new MathNode("mo", [makeText(group.right, group.mode)]);
8140
- rightNode.setAttribute("fence", "true");
8141
- rightNode.setAttribute("form", "postfix");
8142
- if (group.right === "\u2216" || group.right.indexOf("arrow") > -1) {
8143
- rightNode.setAttribute("stretchy", "true");
8164
+ const rightNode = makeFenceMo(group.right, group.mode, "postfix", true);
8165
+ if (group.body.length > 0) {
8166
+ const lastElement = group.body[group.body.length - 1];
8167
+ if (lastElement.type === "color" && !lastElement.isTextColor) {
8168
+ rightNode.setAttribute("mathcolor", lastElement.color);
8169
+ }
8144
8170
  }
8171
+ inner.push(rightNode);
8172
+
8173
+ return makeRow(inner);
8174
+ }
8175
+ });
8176
+
8177
+ defineFunction({
8178
+ type: "delimiter",
8179
+ names: Array.from(leftDelimiterNames),
8180
+ props: {
8181
+ numArgs: 0,
8182
+ allowedInText: true,
8183
+ allowedInMath: true,
8184
+ allowedInArgument: true
8185
+ },
8186
+ handler: ({ parser, funcName, token }) => {
8187
+ if (parser.mode === "text") {
8188
+ return {
8189
+ type: "textord",
8190
+ mode: "text",
8191
+ text: funcName,
8192
+ loc: token.loc
8193
+ }
8194
+ } else if (!parser.settings.wrapDelimiterPairs) {
8195
+ // Treat this token as an ordinary symbol.
8196
+ return {
8197
+ type: "atom",
8198
+ mode: "math",
8199
+ family: "open",
8200
+ loc: token.loc,
8201
+ text: funcName
8202
+ };
8203
+ }
8204
+ // Otherwise, try to wrap a pair of delimiters with an <mrow>.
8205
+ const rightDelim = leftToRight[funcName];
8206
+ // Parse the inner expression, looking for the corresponding right delimiter.
8207
+ const body = parser.parseExpression(false, rightDelim, false);
8208
+ const nextToken = parser.fetch().text;
8209
+
8210
+ if (nextToken !== rightDelim) {
8211
+ // We were unable to find a matching right delimiter.
8212
+ // Throw control back to renderToMathMLTree.
8213
+ // It will reparse the entire expression with wrapDelimiterPairs set to false.
8214
+ throw new ParseError("Unmatched delimiter");
8215
+ }
8216
+ parser.consume();
8217
+
8218
+ return {
8219
+ type: "delimiter",
8220
+ mode: parser.mode,
8221
+ body,
8222
+ left: funcName,
8223
+ right: rightDelim
8224
+ };
8225
+ },
8226
+ mathmlBuilder: (group, style) => {
8227
+ assertParsed(group);
8228
+ const inner = buildExpression(group.body, style);
8229
+
8230
+ const leftNode = makeFenceMo(group.left, group.mode, "prefix", false);
8231
+ inner.unshift(leftNode);
8232
+
8233
+ const rightNode = makeFenceMo(group.right, group.mode, "postfix", false);
8145
8234
  if (group.body.length > 0) {
8146
8235
  const lastElement = group.body[group.body.length - 1];
8147
8236
  if (lastElement.type === "color" && !lastElement.isTextColor) {
8148
- // \color is a switch. If the last element is of type "color" then
8149
- // the user set the \color switch and left it on.
8150
- // A \right delimiter turns the switch off, but the delimiter itself gets the color.
8151
8237
  rightNode.setAttribute("mathcolor", lastElement.color);
8152
8238
  }
8153
8239
  }
@@ -8176,7 +8262,7 @@ defineFunction({
8176
8262
  delim: delim.text
8177
8263
  };
8178
8264
  },
8179
- mathmlBuilder: (group, style) => {
8265
+ mathmlBuilder: (group) => {
8180
8266
  const textNode = makeText(group.delim, group.mode);
8181
8267
  const middleNode = new MathNode("mo", [textNode]);
8182
8268
  middleNode.setAttribute("fence", "true");
@@ -8222,7 +8308,7 @@ const mathmlBuilder$7 = (group, style) => {
8222
8308
  break
8223
8309
  case "\\xcancel":
8224
8310
  node.setAttribute("notation", "updiagonalstrike downdiagonalstrike");
8225
- node.classes.push("tml-xcancel");
8311
+ node.children.push(new MathNode("mrow", [], ["tml-cancel", "tml-xcancel"]));
8226
8312
  break
8227
8313
  // cancelto is handled in cancelto.js
8228
8314
  case "\\longdiv":
@@ -8659,22 +8745,37 @@ defineFunction({
8659
8745
  const stylArray = ["display", "text", "script", "scriptscript"];
8660
8746
  const scriptLevel = { auto: -1, display: 0, text: 0, script: 1, scriptscript: 2 };
8661
8747
 
8748
+ const adjustStyle = (functionSize, originalStyle) => {
8749
+ // Figure out what style this fraction should be in based on the
8750
+ // function used
8751
+ let style = originalStyle;
8752
+ if (functionSize === "display") { //\tfrac or \cfrac
8753
+ // Get display style as a default.
8754
+ // If incoming style is sub/sup, use style.text() to get correct size.
8755
+ const newSize = style.level >= StyleLevel.SCRIPT ? StyleLevel.TEXT : StyleLevel.DISPLAY;
8756
+ style = style.withLevel(newSize);
8757
+ } else if (functionSize === "text" &&
8758
+ style.level === StyleLevel.DISPLAY) {
8759
+ // We're in a \tfrac but incoming style is displaystyle, so:
8760
+ style = style.withLevel(StyleLevel.TEXT);
8761
+ } else if (functionSize === "auto") {
8762
+ style = style.incrementLevel();
8763
+ } else if (functionSize === "script") {
8764
+ style = style.withLevel(StyleLevel.SCRIPT);
8765
+ } else if (functionSize === "scriptscript") {
8766
+ style = style.withLevel(StyleLevel.SCRIPTSCRIPT);
8767
+ }
8768
+ return style;
8769
+ };
8770
+
8662
8771
  const mathmlBuilder$5 = (group, style) => {
8663
- // Track the scriptLevel of the numerator and denominator.
8664
- // We may need that info for \mathchoice or for adjusting em dimensions.
8665
- const childOptions = group.scriptLevel === "auto"
8666
- ? style.incrementLevel()
8667
- : group.scriptLevel === "display"
8668
- ? style.withLevel(StyleLevel.TEXT)
8669
- : group.scriptLevel === "text"
8670
- ? style.withLevel(StyleLevel.SCRIPT)
8671
- : style.withLevel(StyleLevel.SCRIPTSCRIPT);
8772
+ style = adjustStyle(group.scriptLevel, style);
8672
8773
 
8673
8774
  // Chromium (wrongly) continues to shrink fractions beyond scriptscriptlevel.
8674
8775
  // So we check for levels that Chromium shrinks too small.
8675
8776
  // If necessary, set an explicit fraction depth.
8676
- const numer = buildGroup$1(group.numer, childOptions);
8677
- const denom = buildGroup$1(group.denom, childOptions);
8777
+ const numer = buildGroup$1(group.numer, style);
8778
+ const denom = buildGroup$1(group.denom, style);
8678
8779
  if (style.level === 3) {
8679
8780
  numer.style.mathDepth = "2";
8680
8781
  numer.setAttribute("scriptlevel", "2");
@@ -8727,6 +8828,7 @@ const mathmlBuilder$5 = (group, style) => {
8727
8828
  defineFunction({
8728
8829
  type: "genfrac",
8729
8830
  names: [
8831
+ "\\cfrac",
8730
8832
  "\\dfrac",
8731
8833
  "\\frac",
8732
8834
  "\\tfrac",
@@ -8750,6 +8852,7 @@ defineFunction({
8750
8852
  let scriptLevel = "auto";
8751
8853
 
8752
8854
  switch (funcName) {
8855
+ case "\\cfrac":
8753
8856
  case "\\dfrac":
8754
8857
  case "\\frac":
8755
8858
  case "\\tfrac":
@@ -8776,15 +8879,10 @@ defineFunction({
8776
8879
  throw new Error("Unrecognized genfrac command");
8777
8880
  }
8778
8881
 
8779
- switch (funcName) {
8780
- case "\\dfrac":
8781
- case "\\dbinom":
8782
- scriptLevel = "display";
8783
- break;
8784
- case "\\tfrac":
8785
- case "\\tbinom":
8786
- scriptLevel = "text";
8787
- break;
8882
+ if (funcName === "\\cfrac" || funcName.startsWith("\\d")) {
8883
+ scriptLevel = "display";
8884
+ } else if (funcName.startsWith("\\t")) {
8885
+ scriptLevel = "text";
8788
8886
  }
8789
8887
 
8790
8888
  return {
@@ -8803,31 +8901,6 @@ defineFunction({
8803
8901
  mathmlBuilder: mathmlBuilder$5
8804
8902
  });
8805
8903
 
8806
- defineFunction({
8807
- type: "genfrac",
8808
- names: ["\\cfrac"],
8809
- props: {
8810
- numArgs: 2
8811
- },
8812
- handler: ({ parser, funcName }, args) => {
8813
- const numer = args[0];
8814
- const denom = args[1];
8815
-
8816
- return {
8817
- type: "genfrac",
8818
- mode: parser.mode,
8819
- continued: true,
8820
- numer,
8821
- denom,
8822
- hasBarLine: true,
8823
- leftDelim: null,
8824
- rightDelim: null,
8825
- scriptLevel: "display",
8826
- barSize: null
8827
- };
8828
- }
8829
- });
8830
-
8831
8904
  // Infix generalized fractions -- these are not rendered directly, but replaced
8832
8905
  // immediately by one of the variants above.
8833
8906
  defineFunction({
@@ -9656,8 +9729,16 @@ const binrelClass = (arg) => {
9656
9729
  const atom = arg.type === "ordgroup" && arg.body.length && arg.body.length === 1
9657
9730
  ? arg.body[0]
9658
9731
  : arg;
9659
- if (atom.type === "atom" && (atom.family === "bin" || atom.family === "rel")) {
9660
- return "m" + atom.family;
9732
+ if (atom.type === "atom") {
9733
+ // BIN args are sometimes changed to OPEN, so check the original family.
9734
+ const family = arg.body.length > 0 && arg.body[0].text && symbols.math[arg.body[0].text]
9735
+ ? symbols.math[arg.body[0].text].group
9736
+ : atom.family;
9737
+ if (family === "bin" || family === "rel") {
9738
+ return "m" + family;
9739
+ } else {
9740
+ return "mord";
9741
+ }
9661
9742
  } else {
9662
9743
  return "mord";
9663
9744
  }
@@ -10228,6 +10309,14 @@ const mathmlBuilder$1 = (group, style) => {
10228
10309
  if ((node.type === "mrow" || node.type === "mpadded") && node.children.length === 1 &&
10229
10310
  node.children[0] instanceof MathNode) {
10230
10311
  node = node.children[0];
10312
+ } else if (node.type === "mrow" && node.children.length === 2 &&
10313
+ node.children[0] instanceof MathNode &&
10314
+ node.children[1] instanceof MathNode &&
10315
+ node.children[1].type === "mspace" && !node.children[1].attributes.width &&
10316
+ node.children[1].children.length === 0) {
10317
+ // This is a workaround for a Firefox bug that applies spacing to
10318
+ // an <mi> with mathvariant="normal".
10319
+ node = node.children[0];
10231
10320
  }
10232
10321
  switch (node.type) {
10233
10322
  case "mi":
@@ -11498,8 +11587,8 @@ defineFunctionBuilders({
11498
11587
  node.setAttribute("mathvariant", "normal");
11499
11588
  if (text.text.length === 1) {
11500
11589
  // A Firefox bug will apply spacing here, but there should be none. Fix it.
11501
- node = new MathNode("mpadded", [node]);
11502
- node.setAttribute("lspace", "0");
11590
+ const mspace = new MathNode("mspace", []);
11591
+ node = new MathNode("mrow", [node, mspace]);
11503
11592
  }
11504
11593
  }
11505
11594
  return node
@@ -13036,7 +13125,7 @@ class Parser {
13036
13125
  * Parses an "expression", which is a list of atoms.
13037
13126
  *
13038
13127
  * `breakOnInfix`: Should the parsing stop when we hit infix nodes? This
13039
- * happens when functions have higher precedence han infix
13128
+ * happens when functions have higher precedence than infix
13040
13129
  * nodes in implicit parses.
13041
13130
  *
13042
13131
  * `breakOnTokenText`: The text of the token that the expression should end
@@ -13644,7 +13733,7 @@ class Parser {
13644
13733
  ) {
13645
13734
  const firstToken = this.fetch();
13646
13735
  const text = firstToken.text;
13647
-
13736
+ if (name === "argument to '\\left'") { return this.parseSymbol() }
13648
13737
  let result;
13649
13738
  // Try to parse an open brace or \begingroup
13650
13739
  if (text === "{" || text === "\\begingroup" || text === "\\toggle") {
@@ -13791,7 +13880,8 @@ class Parser {
13791
13880
  let symbol;
13792
13881
  if (symbols[this.mode][text]) {
13793
13882
  let group = symbols[this.mode][text].group;
13794
- if (group === "bin" && binLeftCancellers.includes(this.prevAtomType)) {
13883
+ if (group === "bin" &&
13884
+ (binLeftCancellers.includes(this.prevAtomType) || this.prevAtomType === "")) {
13795
13885
  // Change from a binary operator to a unary (prefix) operator
13796
13886
  group = "open";
13797
13887
  }
@@ -13887,11 +13977,27 @@ const parseTree = function(toParse, settings) {
13887
13977
  if (!(typeof toParse === "string" || toParse instanceof String)) {
13888
13978
  throw new TypeError("Temml can only parse string typed expression")
13889
13979
  }
13890
- const parser = new Parser(toParse, settings);
13891
- // Blank out any \df@tag to avoid spurious "Duplicate \tag" errors
13892
- delete parser.gullet.macros.current["\\df@tag"];
13980
+ let tree;
13981
+ let parser;
13982
+ try {
13983
+ parser = new Parser(toParse, settings);
13984
+ // Blank out any \df@tag to avoid spurious "Duplicate \tag" errors
13985
+ delete parser.gullet.macros.current["\\df@tag"];
13893
13986
 
13894
- let tree = parser.parse();
13987
+ tree = parser.parse();
13988
+ } catch (error) {
13989
+ if (error.toString() === "ParseError: Unmatched delimiter") {
13990
+ // Abandon the attempt to wrap delimiter pairs in an <mrow>.
13991
+ // Try again, and put each delimiter into an <mo> element.
13992
+ settings.wrapDelimiterPairs = false;
13993
+ parser = new Parser(toParse, settings);
13994
+ // Blank out any \df@tag to avoid spurious "Duplicate \tag" errors
13995
+ delete parser.gullet.macros.current["\\df@tag"];
13996
+ tree = parser.parse();
13997
+ } else {
13998
+ throw error;
13999
+ }
14000
+ }
13895
14001
 
13896
14002
  // LaTeX ignores a \tag placed outside an AMS environment.
13897
14003
  if (!(tree.length > 0 && tree[0].type && tree[0].type === "array" && tree[0].addEqnNum)) {
@@ -14068,7 +14174,7 @@ class Style {
14068
14174
  * https://mit-license.org/
14069
14175
  */
14070
14176
 
14071
- const version = "0.13.01";
14177
+ const version = "0.13.02";
14072
14178
 
14073
14179
  function postProcess(block) {
14074
14180
  const labelMap = {};