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