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/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
  }
@@ -3876,7 +3869,6 @@ const dotsByToken = {
3876
3869
  "\\iint": "\\dotsi",
3877
3870
  "\\iiint": "\\dotsi",
3878
3871
  "\\iiiint": "\\dotsi",
3879
- "\\idotsint": "\\dotsi",
3880
3872
  // Symbols whose definition starts with \DOTSX:
3881
3873
  "\\DOTSX": "\\dotsx"
3882
3874
  };
@@ -3958,7 +3950,7 @@ defineMacro("\\cdots", function(context) {
3958
3950
  defineMacro("\\dotsb", "\\cdots");
3959
3951
  defineMacro("\\dotsm", "\\cdots");
3960
3952
  defineMacro("\\dotsi", "\\!\\cdots");
3961
- defineMacro("\\idotsint", "\\dotsi");
3953
+ defineMacro("\\idotsint", "\\int\\!\\cdots\\!\\int");
3962
3954
  // amsmath doesn't actually define \dotsx, but \dots followed by a macro
3963
3955
  // starting with \DOTSX implies \dotso, and then \extra@ detects this case
3964
3956
  // and forces the added `\,`.
@@ -4253,6 +4245,7 @@ defineMacro("\\upomega", "\\up@greek{\\omega}");
4253
4245
  // cmll package
4254
4246
  defineMacro("\\invamp", '\\mathbin{\\char"214b}');
4255
4247
  defineMacro("\\parr", '\\mathbin{\\char"214b}');
4248
+ defineMacro("\\upand", '\\mathbin{\\char"214b}'); // STIX package
4256
4249
  defineMacro("\\with", '\\mathbin{\\char"26}');
4257
4250
  defineMacro("\\multimapinv", '\\mathrel{\\char"27dc}');
4258
4251
  defineMacro("\\multimapboth", '\\mathrel{\\char"29df}');
@@ -7224,6 +7217,7 @@ defineFunction({
7224
7217
  // That way, the arrow will be an overlay on the content.
7225
7218
  const phantom = new MathNode("mphantom", [buildGroup$1(group.body, style)]);
7226
7219
  const arrow = new MathNode("mrow", [phantom], ["tml-cancelto"]);
7220
+ arrow.style.color = style.color;
7227
7221
  if (group.isCharacterBox && smalls.indexOf(group.body.body[0].text) > -1) {
7228
7222
  arrow.style.left = "0.1em";
7229
7223
  arrow.style.width = "90%";
@@ -7255,6 +7249,7 @@ defineFunction({
7255
7249
  dummyNode = new MathNode("mphantom", [zeroWidthNode]); // Hide it.
7256
7250
  }
7257
7251
  const toNode = buildGroup$1(group.to, style);
7252
+ toNode.style.color = style.color;
7258
7253
  const zeroWidthToNode = new MathNode("mpadded", [toNode]);
7259
7254
  if (!group.isCharacterBox || /[f∫∑]/.test(group.body.body[0].text)) {
7260
7255
  const w = new MathNode("mspace", []);
@@ -7313,7 +7308,7 @@ const toHex = num => {
7313
7308
 
7314
7309
  // Colors from Tables 4.1 and 4.2 of the xcolor package.
7315
7310
  // Table 4.1 (lower case) RGB values are taken from chroma and xcolor.dtx.
7316
- // 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
7317
7312
  // conversion from cmyk to RGB. See https://tex.stackexchange.com/a/537274.
7318
7313
  const xcolors = JSON.parse(`{
7319
7314
  "Apricot": "#ffb484",
@@ -7872,7 +7867,41 @@ const delimiterSizes = {
7872
7867
  "\\Bigg": { mclass: "mord", size: 4 }
7873
7868
  };
7874
7869
 
7875
- 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([
7876
7905
  "(",
7877
7906
  "\\lparen",
7878
7907
  ")",
@@ -7928,7 +7957,7 @@ const delimiters = [
7928
7957
  "\\llbracket",
7929
7958
  "\\rrbracket",
7930
7959
  "\u27e6",
7931
- "\u27e6",
7960
+ "\u27e7",
7932
7961
  "\\lBrace",
7933
7962
  "\\rBrace",
7934
7963
  "\u2983",
@@ -7947,12 +7976,12 @@ const delimiters = [
7947
7976
  "\\updownarrow",
7948
7977
  "\\Updownarrow",
7949
7978
  "."
7950
- ];
7979
+ ]);
7951
7980
 
7952
7981
  // Export isDelimiter for benefit of parser.
7953
- const dels = ["}", "\\left", "\\middle", "\\right"];
7982
+ const dels = new Set(["}", "\\left", "\\middle", "\\right"]);
7954
7983
  const isDelimiter = str => str.length > 0 &&
7955
- (delimiters.includes(str) || delimiterSizes[str] || dels.includes(str));
7984
+ (delimiters.has(str) || delimiterSizes[str] || dels.has(str));
7956
7985
 
7957
7986
  // Metrics of the different sizes. Found by looking at TeX's output of
7958
7987
  // $\bigl| // \Bigl| \biggl| \Biggl| \showlists$
@@ -7965,11 +7994,11 @@ function checkDelimiter(delim, context) {
7965
7994
  delim = delim.body[0]; // Unwrap the braces
7966
7995
  }
7967
7996
  const symDelim = checkSymbolNodeType(delim);
7968
- if (symDelim && delimiters.includes(symDelim.text)) {
7997
+ if (symDelim && delimiters.has(symDelim.text)) {
7969
7998
  // If a character is not in the MathML operator dictionary, it will not stretch.
7970
7999
  // Replace such characters w/characters that will stretch.
7971
- if (["<", "\\lt"].includes(symDelim.text)) { symDelim.text = "⟨"; }
7972
- 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 = "⟩"; }
7973
8002
  return symDelim;
7974
8003
  } else if (symDelim) {
7975
8004
  throw new ParseError(`Invalid delimiter '${symDelim.text}' after '${context.funcName}'`, delim);
@@ -7979,7 +8008,16 @@ function checkDelimiter(delim, context) {
7979
8008
  }
7980
8009
 
7981
8010
  // / \
7982
- 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
+ };
7983
8021
 
7984
8022
  defineFunction({
7985
8023
  type: "delimsizing",
@@ -8030,9 +8068,9 @@ defineFunction({
8030
8068
  },
8031
8069
  mathmlBuilder: (group) => {
8032
8070
  const children = [];
8071
+ const delim = group.delim === "." ? "" : group.delim;
8033
8072
 
8034
- if (group.delim === ".") { group.delim = ""; }
8035
- children.push(makeText(group.delim, group.mode));
8073
+ children.push(makeText(delim, group.mode));
8036
8074
 
8037
8075
  const node = new MathNode("mo", children);
8038
8076
 
@@ -8045,7 +8083,7 @@ defineFunction({
8045
8083
  // defaults.
8046
8084
  node.setAttribute("fence", "false");
8047
8085
  }
8048
- if (needExplicitStretch.includes(group.delim) || group.delim.indexOf("arrow") > -1) {
8086
+ if (needExplicitStretch.has(delim) || delim.indexOf("arrow") > -1) {
8049
8087
  // We have to explicitly set stretchy to true.
8050
8088
  node.setAttribute("stretchy", "true");
8051
8089
  }
@@ -8058,7 +8096,7 @@ defineFunction({
8058
8096
 
8059
8097
  function assertParsed(group) {
8060
8098
  if (!group.body) {
8061
- throw new Error("Bug: The leftright ParseNode wasn't fully parsed.");
8099
+ throw new Error("Bug: The delim ParseNode wasn't fully parsed.");
8062
8100
  }
8063
8101
  }
8064
8102
 
@@ -8089,17 +8127,10 @@ defineFunction({
8089
8127
  const delim = checkDelimiter(args[0], context);
8090
8128
 
8091
8129
  const parser = context.parser;
8092
- // Parse out the implicit body
8093
8130
  ++parser.leftrightDepth;
8094
- // parseExpression stops before '\\right' or `\\middle`
8095
- let body = parser.parseExpression(false, null, true);
8131
+ let body = parser.parseExpression(false, "\\right", true);
8096
8132
  let nextToken = parser.fetch();
8097
8133
  while (nextToken.text === "\\middle") {
8098
- // `\middle`, from the ε-TeX package, ends one group and starts another group.
8099
- // We had to parse this expression with `breakOnMiddle` enabled in order
8100
- // to get TeX-compliant parsing of \over.
8101
- // But we do not want, at this point, to end on \middle, so continue
8102
- // to parse until we fetch a `\right`.
8103
8134
  parser.consume();
8104
8135
  const middle = parser.fetch().text;
8105
8136
  if (!symbols.math[middle]) {
@@ -8108,11 +8139,10 @@ defineFunction({
8108
8139
  checkDelimiter({ type: "atom", mode: "math", text: middle }, { funcName: "\\middle" });
8109
8140
  body.push({ type: "middle", mode: "math", delim: middle });
8110
8141
  parser.consume();
8111
- body = body.concat(parser.parseExpression(false, null, true));
8142
+ body = body.concat(parser.parseExpression(false, "\\right", true));
8112
8143
  nextToken = parser.fetch();
8113
8144
  }
8114
8145
  --parser.leftrightDepth;
8115
- // Check the next token
8116
8146
  parser.expect("\\right", false);
8117
8147
  const right = assertNodeType(parser.parseFunction(), "leftright-right");
8118
8148
  return {
@@ -8120,35 +8150,90 @@ defineFunction({
8120
8150
  mode: parser.mode,
8121
8151
  body,
8122
8152
  left: delim.text,
8123
- right: right.delim
8153
+ right: right.delim,
8154
+ isStretchy: true
8124
8155
  };
8125
8156
  },
8126
8157
  mathmlBuilder: (group, style) => {
8127
8158
  assertParsed(group);
8128
8159
  const inner = buildExpression(group.body, style);
8129
8160
 
8130
- if (group.left === ".") { group.left = ""; }
8131
- const leftNode = new MathNode("mo", [makeText(group.left, group.mode)]);
8132
- leftNode.setAttribute("fence", "true");
8133
- leftNode.setAttribute("form", "prefix");
8134
- if (group.left === "/" || group.left === "\u005C" || group.left.indexOf("arrow") > -1) {
8135
- leftNode.setAttribute("stretchy", "true");
8136
- }
8161
+ const leftNode = makeFenceMo(group.left, group.mode, "prefix", true);
8137
8162
  inner.unshift(leftNode);
8138
8163
 
8139
- if (group.right === ".") { group.right = ""; }
8140
- const rightNode = new MathNode("mo", [makeText(group.right, group.mode)]);
8141
- rightNode.setAttribute("fence", "true");
8142
- rightNode.setAttribute("form", "postfix");
8143
- if (group.right === "\u2216" || group.right.indexOf("arrow") > -1) {
8144
- 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
+ }
8145
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);
8146
8234
  if (group.body.length > 0) {
8147
8235
  const lastElement = group.body[group.body.length - 1];
8148
8236
  if (lastElement.type === "color" && !lastElement.isTextColor) {
8149
- // \color is a switch. If the last element is of type "color" then
8150
- // the user set the \color switch and left it on.
8151
- // A \right delimiter turns the switch off, but the delimiter itself gets the color.
8152
8237
  rightNode.setAttribute("mathcolor", lastElement.color);
8153
8238
  }
8154
8239
  }
@@ -8177,7 +8262,7 @@ defineFunction({
8177
8262
  delim: delim.text
8178
8263
  };
8179
8264
  },
8180
- mathmlBuilder: (group, style) => {
8265
+ mathmlBuilder: (group) => {
8181
8266
  const textNode = makeText(group.delim, group.mode);
8182
8267
  const middleNode = new MathNode("mo", [textNode]);
8183
8268
  middleNode.setAttribute("fence", "true");
@@ -8223,7 +8308,7 @@ const mathmlBuilder$7 = (group, style) => {
8223
8308
  break
8224
8309
  case "\\xcancel":
8225
8310
  node.setAttribute("notation", "updiagonalstrike downdiagonalstrike");
8226
- node.classes.push("tml-xcancel");
8311
+ node.children.push(new MathNode("mrow", [], ["tml-cancel", "tml-xcancel"]));
8227
8312
  break
8228
8313
  // cancelto is handled in cancelto.js
8229
8314
  case "\\longdiv":
@@ -8660,22 +8745,37 @@ defineFunction({
8660
8745
  const stylArray = ["display", "text", "script", "scriptscript"];
8661
8746
  const scriptLevel = { auto: -1, display: 0, text: 0, script: 1, scriptscript: 2 };
8662
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
+
8663
8771
  const mathmlBuilder$5 = (group, style) => {
8664
- // Track the scriptLevel of the numerator and denominator.
8665
- // We may need that info for \mathchoice or for adjusting em dimensions.
8666
- const childOptions = group.scriptLevel === "auto"
8667
- ? style.incrementLevel()
8668
- : group.scriptLevel === "display"
8669
- ? style.withLevel(StyleLevel.TEXT)
8670
- : group.scriptLevel === "text"
8671
- ? style.withLevel(StyleLevel.SCRIPT)
8672
- : style.withLevel(StyleLevel.SCRIPTSCRIPT);
8772
+ style = adjustStyle(group.scriptLevel, style);
8673
8773
 
8674
8774
  // Chromium (wrongly) continues to shrink fractions beyond scriptscriptlevel.
8675
8775
  // So we check for levels that Chromium shrinks too small.
8676
8776
  // If necessary, set an explicit fraction depth.
8677
- const numer = buildGroup$1(group.numer, childOptions);
8678
- const denom = buildGroup$1(group.denom, childOptions);
8777
+ const numer = buildGroup$1(group.numer, style);
8778
+ const denom = buildGroup$1(group.denom, style);
8679
8779
  if (style.level === 3) {
8680
8780
  numer.style.mathDepth = "2";
8681
8781
  numer.setAttribute("scriptlevel", "2");
@@ -8728,6 +8828,7 @@ const mathmlBuilder$5 = (group, style) => {
8728
8828
  defineFunction({
8729
8829
  type: "genfrac",
8730
8830
  names: [
8831
+ "\\cfrac",
8731
8832
  "\\dfrac",
8732
8833
  "\\frac",
8733
8834
  "\\tfrac",
@@ -8751,6 +8852,7 @@ defineFunction({
8751
8852
  let scriptLevel = "auto";
8752
8853
 
8753
8854
  switch (funcName) {
8855
+ case "\\cfrac":
8754
8856
  case "\\dfrac":
8755
8857
  case "\\frac":
8756
8858
  case "\\tfrac":
@@ -8777,15 +8879,10 @@ defineFunction({
8777
8879
  throw new Error("Unrecognized genfrac command");
8778
8880
  }
8779
8881
 
8780
- switch (funcName) {
8781
- case "\\dfrac":
8782
- case "\\dbinom":
8783
- scriptLevel = "display";
8784
- break;
8785
- case "\\tfrac":
8786
- case "\\tbinom":
8787
- scriptLevel = "text";
8788
- break;
8882
+ if (funcName === "\\cfrac" || funcName.startsWith("\\d")) {
8883
+ scriptLevel = "display";
8884
+ } else if (funcName.startsWith("\\t")) {
8885
+ scriptLevel = "text";
8789
8886
  }
8790
8887
 
8791
8888
  return {
@@ -8804,31 +8901,6 @@ defineFunction({
8804
8901
  mathmlBuilder: mathmlBuilder$5
8805
8902
  });
8806
8903
 
8807
- defineFunction({
8808
- type: "genfrac",
8809
- names: ["\\cfrac"],
8810
- props: {
8811
- numArgs: 2
8812
- },
8813
- handler: ({ parser, funcName }, args) => {
8814
- const numer = args[0];
8815
- const denom = args[1];
8816
-
8817
- return {
8818
- type: "genfrac",
8819
- mode: parser.mode,
8820
- continued: true,
8821
- numer,
8822
- denom,
8823
- hasBarLine: true,
8824
- leftDelim: null,
8825
- rightDelim: null,
8826
- scriptLevel: "display",
8827
- barSize: null
8828
- };
8829
- }
8830
- });
8831
-
8832
8904
  // Infix generalized fractions -- these are not rendered directly, but replaced
8833
8905
  // immediately by one of the variants above.
8834
8906
  defineFunction({
@@ -9657,8 +9729,16 @@ const binrelClass = (arg) => {
9657
9729
  const atom = arg.type === "ordgroup" && arg.body.length && arg.body.length === 1
9658
9730
  ? arg.body[0]
9659
9731
  : arg;
9660
- if (atom.type === "atom" && (atom.family === "bin" || atom.family === "rel")) {
9661
- 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
+ }
9662
9742
  } else {
9663
9743
  return "mord";
9664
9744
  }
@@ -10229,6 +10309,14 @@ const mathmlBuilder$1 = (group, style) => {
10229
10309
  if ((node.type === "mrow" || node.type === "mpadded") && node.children.length === 1 &&
10230
10310
  node.children[0] instanceof MathNode) {
10231
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];
10232
10320
  }
10233
10321
  switch (node.type) {
10234
10322
  case "mi":
@@ -11499,8 +11587,8 @@ defineFunctionBuilders({
11499
11587
  node.setAttribute("mathvariant", "normal");
11500
11588
  if (text.text.length === 1) {
11501
11589
  // A Firefox bug will apply spacing here, but there should be none. Fix it.
11502
- node = new MathNode("mpadded", [node]);
11503
- node.setAttribute("lspace", "0");
11590
+ const mspace = new MathNode("mspace", []);
11591
+ node = new MathNode("mrow", [node, mspace]);
11504
11592
  }
11505
11593
  }
11506
11594
  return node
@@ -13037,7 +13125,7 @@ class Parser {
13037
13125
  * Parses an "expression", which is a list of atoms.
13038
13126
  *
13039
13127
  * `breakOnInfix`: Should the parsing stop when we hit infix nodes? This
13040
- * happens when functions have higher precedence han infix
13128
+ * happens when functions have higher precedence than infix
13041
13129
  * nodes in implicit parses.
13042
13130
  *
13043
13131
  * `breakOnTokenText`: The text of the token that the expression should end
@@ -13645,7 +13733,7 @@ class Parser {
13645
13733
  ) {
13646
13734
  const firstToken = this.fetch();
13647
13735
  const text = firstToken.text;
13648
-
13736
+ if (name === "argument to '\\left'") { return this.parseSymbol() }
13649
13737
  let result;
13650
13738
  // Try to parse an open brace or \begingroup
13651
13739
  if (text === "{" || text === "\\begingroup" || text === "\\toggle") {
@@ -13678,6 +13766,12 @@ class Parser {
13678
13766
  result = this.parseFunction(breakOnTokenText, name) || this.parseSymbol();
13679
13767
  if (result == null && text[0] === "\\" &&
13680
13768
  !Object.prototype.hasOwnProperty.call(implicitCommands, text )) {
13769
+ if (this.settings.throwOnError) {
13770
+ throw new ParseError("Unsupported function name: " + text, firstToken);
13771
+ }
13772
+ // For people getting dyanamically rendered math, it's better to
13773
+ // show the unsupported command in red rather than panicking for every
13774
+ // partially written expression.
13681
13775
  result = this.formatUnsupportedCmd(text);
13682
13776
  this.consume();
13683
13777
  }
@@ -13786,7 +13880,8 @@ class Parser {
13786
13880
  let symbol;
13787
13881
  if (symbols[this.mode][text]) {
13788
13882
  let group = symbols[this.mode][text].group;
13789
- if (group === "bin" && binLeftCancellers.includes(this.prevAtomType)) {
13883
+ if (group === "bin" &&
13884
+ (binLeftCancellers.includes(this.prevAtomType) || this.prevAtomType === "")) {
13790
13885
  // Change from a binary operator to a unary (prefix) operator
13791
13886
  group = "open";
13792
13887
  }
@@ -13882,11 +13977,27 @@ const parseTree = function(toParse, settings) {
13882
13977
  if (!(typeof toParse === "string" || toParse instanceof String)) {
13883
13978
  throw new TypeError("Temml can only parse string typed expression")
13884
13979
  }
13885
- const parser = new Parser(toParse, settings);
13886
- // Blank out any \df@tag to avoid spurious "Duplicate \tag" errors
13887
- 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"];
13888
13986
 
13889
- 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
+ }
13890
14001
 
13891
14002
  // LaTeX ignores a \tag placed outside an AMS environment.
13892
14003
  if (!(tree.length > 0 && tree[0].type && tree[0].type === "array" && tree[0].addEqnNum)) {
@@ -14063,7 +14174,7 @@ class Style {
14063
14174
  * https://mit-license.org/
14064
14175
  */
14065
14176
 
14066
- const version = "0.12.02";
14177
+ const version = "0.13.02";
14067
14178
 
14068
14179
  function postProcess(block) {
14069
14180
  const labelMap = {};