temml 0.13.1 → 0.13.3

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}');
@@ -6526,9 +6520,9 @@ const mathmlBuilder$9 = function(group, style) {
6526
6520
  }
6527
6521
  if (mustSquashRow) {
6528
6522
  // All the cell contents are \hphantom. Squash the cell.
6523
+ // TODO: Remove the next line when Firefox no longer needs it.
6524
+ mtr.classes.push("ff-squash"); // necessary in Firefox only.
6529
6525
  for (let j = 0; j < mtr.children.length; j++) {
6530
- mtr.children[j].style.display = "block"; // necessary in Firefox only
6531
- mtr.children[j].style.height = "0"; // necessary in Firefox only
6532
6526
  mtr.children[j].style.paddingTop = "0";
6533
6527
  mtr.children[j].style.paddingBottom = "0";
6534
6528
  }
@@ -7169,13 +7163,11 @@ defineFunction({
7169
7163
  },
7170
7164
  handler: ({ parser, funcName }, args, optArgs) => {
7171
7165
  // Find out if the author has defined custom delimiters
7172
- let delimiters = ["(", ")"];
7166
+ let delimiters = ["(", ")"]; // default
7173
7167
  if (funcName === "\\bordermatrix" && optArgs[0] && optArgs[0].body) {
7174
7168
  const body = optArgs[0].body;
7175
- if (body.length === 2 && body[0].type === "atom" && body[1].type === "atom") {
7176
- if (body[0].family === "open" && body[1].family === "close") {
7177
- delimiters = [body[0].text, body[1].text];
7178
- }
7169
+ if (body.length === 1 && body[0].type === "delimiter") {
7170
+ delimiters = [body[0].left, body[0].right];
7179
7171
  }
7180
7172
  }
7181
7173
  // consume the opening brace
@@ -7223,6 +7215,7 @@ defineFunction({
7223
7215
  // That way, the arrow will be an overlay on the content.
7224
7216
  const phantom = new MathNode("mphantom", [buildGroup$1(group.body, style)]);
7225
7217
  const arrow = new MathNode("mrow", [phantom], ["tml-cancelto"]);
7218
+ arrow.style.color = style.color;
7226
7219
  if (group.isCharacterBox && smalls.indexOf(group.body.body[0].text) > -1) {
7227
7220
  arrow.style.left = "0.1em";
7228
7221
  arrow.style.width = "90%";
@@ -7254,6 +7247,7 @@ defineFunction({
7254
7247
  dummyNode = new MathNode("mphantom", [zeroWidthNode]); // Hide it.
7255
7248
  }
7256
7249
  const toNode = buildGroup$1(group.to, style);
7250
+ toNode.style.color = style.color;
7257
7251
  const zeroWidthToNode = new MathNode("mpadded", [toNode]);
7258
7252
  if (!group.isCharacterBox || /[f∫∑]/.test(group.body.body[0].text)) {
7259
7253
  const w = new MathNode("mspace", []);
@@ -7312,7 +7306,7 @@ const toHex = num => {
7312
7306
 
7313
7307
  // Colors from Tables 4.1 and 4.2 of the xcolor package.
7314
7308
  // 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
7309
+ // Table 4.2 (Capitalized) values were sampled, because Chroma contains a unreliable
7316
7310
  // conversion from cmyk to RGB. See https://tex.stackexchange.com/a/537274.
7317
7311
  const xcolors = JSON.parse(`{
7318
7312
  "Apricot": "#ffb484",
@@ -7871,7 +7865,41 @@ const delimiterSizes = {
7871
7865
  "\\Bigg": { mclass: "mord", size: 4 }
7872
7866
  };
7873
7867
 
7874
- 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([
7875
7903
  "(",
7876
7904
  "\\lparen",
7877
7905
  ")",
@@ -7927,7 +7955,7 @@ const delimiters = [
7927
7955
  "\\llbracket",
7928
7956
  "\\rrbracket",
7929
7957
  "\u27e6",
7930
- "\u27e6",
7958
+ "\u27e7",
7931
7959
  "\\lBrace",
7932
7960
  "\\rBrace",
7933
7961
  "\u2983",
@@ -7946,12 +7974,12 @@ const delimiters = [
7946
7974
  "\\updownarrow",
7947
7975
  "\\Updownarrow",
7948
7976
  "."
7949
- ];
7977
+ ]);
7950
7978
 
7951
7979
  // Export isDelimiter for benefit of parser.
7952
- const dels = ["}", "\\left", "\\middle", "\\right"];
7980
+ const dels = new Set(["}", "\\left", "\\middle", "\\right"]);
7953
7981
  const isDelimiter = str => str.length > 0 &&
7954
- (delimiters.includes(str) || delimiterSizes[str] || dels.includes(str));
7982
+ (delimiters.has(str) || delimiterSizes[str] || dels.has(str));
7955
7983
 
7956
7984
  // Metrics of the different sizes. Found by looking at TeX's output of
7957
7985
  // $\bigl| // \Bigl| \biggl| \Biggl| \showlists$
@@ -7964,11 +7992,11 @@ function checkDelimiter(delim, context) {
7964
7992
  delim = delim.body[0]; // Unwrap the braces
7965
7993
  }
7966
7994
  const symDelim = checkSymbolNodeType(delim);
7967
- if (symDelim && delimiters.includes(symDelim.text)) {
7995
+ if (symDelim && delimiters.has(symDelim.text)) {
7968
7996
  // If a character is not in the MathML operator dictionary, it will not stretch.
7969
7997
  // Replace such characters w/characters that will stretch.
7970
- if (["<", "\\lt"].includes(symDelim.text)) { symDelim.text = "⟨"; }
7971
- 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 = "⟩"; }
7972
8000
  return symDelim;
7973
8001
  } else if (symDelim) {
7974
8002
  throw new ParseError(`Invalid delimiter '${symDelim.text}' after '${context.funcName}'`, delim);
@@ -7978,7 +8006,16 @@ function checkDelimiter(delim, context) {
7978
8006
  }
7979
8007
 
7980
8008
  // / \
7981
- 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
+ };
7982
8019
 
7983
8020
  defineFunction({
7984
8021
  type: "delimsizing",
@@ -8029,9 +8066,9 @@ defineFunction({
8029
8066
  },
8030
8067
  mathmlBuilder: (group) => {
8031
8068
  const children = [];
8069
+ const delim = group.delim === "." ? "" : group.delim;
8032
8070
 
8033
- if (group.delim === ".") { group.delim = ""; }
8034
- children.push(makeText(group.delim, group.mode));
8071
+ children.push(makeText(delim, group.mode));
8035
8072
 
8036
8073
  const node = new MathNode("mo", children);
8037
8074
 
@@ -8044,7 +8081,7 @@ defineFunction({
8044
8081
  // defaults.
8045
8082
  node.setAttribute("fence", "false");
8046
8083
  }
8047
- if (needExplicitStretch.includes(group.delim) || group.delim.indexOf("arrow") > -1) {
8084
+ if (needExplicitStretch.has(delim) || delim.indexOf("arrow") > -1) {
8048
8085
  // We have to explicitly set stretchy to true.
8049
8086
  node.setAttribute("stretchy", "true");
8050
8087
  }
@@ -8057,7 +8094,7 @@ defineFunction({
8057
8094
 
8058
8095
  function assertParsed(group) {
8059
8096
  if (!group.body) {
8060
- throw new Error("Bug: The leftright ParseNode wasn't fully parsed.");
8097
+ throw new Error("Bug: The delim ParseNode wasn't fully parsed.");
8061
8098
  }
8062
8099
  }
8063
8100
 
@@ -8088,17 +8125,10 @@ defineFunction({
8088
8125
  const delim = checkDelimiter(args[0], context);
8089
8126
 
8090
8127
  const parser = context.parser;
8091
- // Parse out the implicit body
8092
8128
  ++parser.leftrightDepth;
8093
- // parseExpression stops before '\\right' or `\\middle`
8094
- let body = parser.parseExpression(false, null, true);
8129
+ let body = parser.parseExpression(false, "\\right", true);
8095
8130
  let nextToken = parser.fetch();
8096
8131
  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
8132
  parser.consume();
8103
8133
  const middle = parser.fetch().text;
8104
8134
  if (!symbols.math[middle]) {
@@ -8107,11 +8137,10 @@ defineFunction({
8107
8137
  checkDelimiter({ type: "atom", mode: "math", text: middle }, { funcName: "\\middle" });
8108
8138
  body.push({ type: "middle", mode: "math", delim: middle });
8109
8139
  parser.consume();
8110
- body = body.concat(parser.parseExpression(false, null, true));
8140
+ body = body.concat(parser.parseExpression(false, "\\right", true));
8111
8141
  nextToken = parser.fetch();
8112
8142
  }
8113
8143
  --parser.leftrightDepth;
8114
- // Check the next token
8115
8144
  parser.expect("\\right", false);
8116
8145
  const right = assertNodeType(parser.parseFunction(), "leftright-right");
8117
8146
  return {
@@ -8119,35 +8148,90 @@ defineFunction({
8119
8148
  mode: parser.mode,
8120
8149
  body,
8121
8150
  left: delim.text,
8122
- right: right.delim
8151
+ right: right.delim,
8152
+ isStretchy: true
8123
8153
  };
8124
8154
  },
8125
8155
  mathmlBuilder: (group, style) => {
8126
8156
  assertParsed(group);
8127
8157
  const inner = buildExpression(group.body, style);
8128
8158
 
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
- }
8159
+ const leftNode = makeFenceMo(group.left, group.mode, "prefix", true);
8136
8160
  inner.unshift(leftNode);
8137
8161
 
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");
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
+ }
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");
8144
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);
8145
8232
  if (group.body.length > 0) {
8146
8233
  const lastElement = group.body[group.body.length - 1];
8147
8234
  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
8235
  rightNode.setAttribute("mathcolor", lastElement.color);
8152
8236
  }
8153
8237
  }
@@ -8176,20 +8260,17 @@ defineFunction({
8176
8260
  delim: delim.text
8177
8261
  };
8178
8262
  },
8179
- mathmlBuilder: (group, style) => {
8263
+ mathmlBuilder: (group) => {
8180
8264
  const textNode = makeText(group.delim, group.mode);
8181
8265
  const middleNode = new MathNode("mo", [textNode]);
8182
- middleNode.setAttribute("fence", "true");
8183
- if (group.delim.indexOf("arrow") > -1) {
8184
- middleNode.setAttribute("stretchy", "true");
8185
- }
8186
- // The next line is not semantically correct, but
8187
- // Chromium fails to stretch if it is not there.
8188
- middleNode.setAttribute("form", "prefix");
8189
- // MathML gives 5/18em spacing to each <mo> element.
8190
- // \middle should get delimiter spacing instead.
8191
- middleNode.setAttribute("lspace", "0.05em");
8192
- middleNode.setAttribute("rspace", "0.05em");
8266
+ middleNode.setAttribute("stretchy", "true");
8267
+ middleNode.setAttribute("form", "infix");
8268
+ if (textNode.text !== "/") {
8269
+ // MathML gives 5/18em spacing to each <mo> element.
8270
+ // \middle should get delimiter spacing instead.
8271
+ middleNode.setAttribute("lspace", "0.05em");
8272
+ middleNode.setAttribute("rspace", "0.05em");
8273
+ }
8193
8274
  return middleNode;
8194
8275
  }
8195
8276
  });
@@ -8222,7 +8303,7 @@ const mathmlBuilder$7 = (group, style) => {
8222
8303
  break
8223
8304
  case "\\xcancel":
8224
8305
  node.setAttribute("notation", "updiagonalstrike downdiagonalstrike");
8225
- node.classes.push("tml-xcancel");
8306
+ node.children.push(new MathNode("mrow", [], ["tml-cancel", "tml-xcancel"]));
8226
8307
  break
8227
8308
  // cancelto is handled in cancelto.js
8228
8309
  case "\\longdiv":
@@ -8356,7 +8437,7 @@ defineFunction({
8356
8437
 
8357
8438
  defineFunction({
8358
8439
  type: "enclose",
8359
- names: ["\\angl", "\\cancel", "\\bcancel", "\\xcancel", "\\sout", "\\overline",
8440
+ names: ["\\angl", "\\cancel", "\\bcancel", "\\xcancel", "\\overline",
8360
8441
  "\\boxed", "\\longdiv", "\\phase"],
8361
8442
  props: {
8362
8443
  numArgs: 1
@@ -8373,6 +8454,25 @@ defineFunction({
8373
8454
  mathmlBuilder: mathmlBuilder$7
8374
8455
  });
8375
8456
 
8457
+ defineFunction({
8458
+ type: "enclose",
8459
+ names: ["\\sout"],
8460
+ props: {
8461
+ numArgs: 1,
8462
+ allowedInText: true
8463
+ },
8464
+ handler({ parser, funcName }, args) {
8465
+ const body = args[0];
8466
+ return {
8467
+ type: "enclose",
8468
+ mode: parser.mode,
8469
+ label: funcName,
8470
+ body
8471
+ };
8472
+ },
8473
+ mathmlBuilder: mathmlBuilder$7
8474
+ });
8475
+
8376
8476
  defineFunction({
8377
8477
  type: "enclose",
8378
8478
  names: ["\\underline"],
@@ -8505,145 +8605,380 @@ defineFunction({
8505
8605
  }
8506
8606
  });
8507
8607
 
8508
- const isLongVariableName = (group, font) => {
8509
- if (font !== "mathrm" || group.body.type !== "ordgroup" || group.body.body.length === 1) {
8510
- return false
8511
- }
8512
- if (group.body.body[0].type !== "mathord") { return false }
8513
- for (let i = 1; i < group.body.body.length; i++) {
8514
- const parseNodeType = group.body.body[i].type;
8515
- if (!(parseNodeType === "mathord" ||
8516
- (parseNodeType === "textord" && !isNaN(group.body.body[i].text)))) {
8517
- return false
8518
- }
8519
- }
8520
- return true
8521
- };
8522
-
8523
- const mathmlBuilder$6 = (group, style) => {
8524
- const font = group.font;
8525
- const newStyle = style.withFont(font);
8526
- const mathGroup = buildGroup$1(group.body, newStyle);
8608
+ // Chromium does not support the MathML `mathvariant` attribute.
8609
+ // Instead, we replace ASCII characters with Unicode characters that
8610
+ // are defined in the font as bold, italic, double-struck, etc.
8611
+ // This module identifies those Unicode code points.
8527
8612
 
8528
- if (mathGroup.children.length === 0) { return mathGroup } // empty group, e.g., \mathrm{}
8529
- if (font === "boldsymbol" && ["mo", "mpadded", "mrow"].includes(mathGroup.type)) {
8530
- mathGroup.style.fontWeight = "bold";
8531
- return mathGroup
8532
- }
8533
- // Check if it is possible to consolidate elements into a single <mi> element.
8534
- if (isLongVariableName(group, font)) {
8535
- // This is a \mathrm{…} group. It gets special treatment because symbolsOrd.js
8536
- // wraps <mi> elements with <mpadded>s to work around a Firefox bug.
8537
- const mi = mathGroup.children[0].children[0].children
8538
- ? mathGroup.children[0].children[0]
8539
- : mathGroup.children[0];
8540
- delete mi.attributes.mathvariant;
8541
- for (let i = 1; i < mathGroup.children.length; i++) {
8542
- mi.children[0].text += mathGroup.children[i].children[0].children
8543
- ? mathGroup.children[i].children[0].children[0].text
8544
- : mathGroup.children[i].children[0].text;
8545
- }
8546
- // Wrap in a <mpadded> to prevent the same Firefox bug.
8547
- const mpadded = new MathNode("mpadded", [mi]);
8548
- mpadded.setAttribute("lspace", "0");
8549
- return mpadded
8550
- }
8551
- let canConsolidate = mathGroup.children[0].type === "mo";
8552
- for (let i = 1; i < mathGroup.children.length; i++) {
8553
- if (mathGroup.children[i].type === "mo" && font === "boldsymbol") {
8554
- mathGroup.children[i].style.fontWeight = "bold";
8555
- }
8556
- if (mathGroup.children[i].type !== "mi") { canConsolidate = false; }
8557
- const localVariant = mathGroup.children[i].attributes &&
8558
- mathGroup.children[i].attributes.mathvariant || "";
8559
- if (localVariant !== "normal") { canConsolidate = false; }
8560
- }
8561
- if (!canConsolidate) { return mathGroup }
8562
- // Consolidate the <mi> elements.
8563
- const mi = mathGroup.children[0];
8564
- for (let i = 1; i < mathGroup.children.length; i++) {
8565
- mi.children.push(mathGroup.children[i].children[0]);
8566
- }
8567
- if (mi.attributes.mathvariant && mi.attributes.mathvariant === "normal") {
8568
- // Workaround for a Firefox bug that renders spurious space around
8569
- // a <mi mathvariant="normal">
8570
- // Ref: https://bugs.webkit.org/show_bug.cgi?id=129097
8571
- // We insert a text node that contains a zero-width space and wrap in an mrow.
8572
- // TODO: Get rid of this <mi> workaround when the Firefox bug is fixed.
8573
- const bogus = new MathNode("mtext", new TextNode("\u200b"));
8574
- return new MathNode("mrow", [bogus, mi])
8575
- }
8576
- return mi
8577
- };
8613
+ // First, a few helpers.
8614
+ const script = Object.freeze({
8615
+ B: 0x20EA, // Offset from ASCII B to Unicode script B
8616
+ E: 0x20EB,
8617
+ F: 0x20EB,
8618
+ H: 0x20C3,
8619
+ I: 0x20C7,
8620
+ L: 0x20C6,
8621
+ M: 0x20E6,
8622
+ R: 0x20C9,
8623
+ e: 0x20CA,
8624
+ g: 0x20A3,
8625
+ o: 0x20C5
8626
+ });
8578
8627
 
8579
- const fontAliases = {
8580
- "\\Bbb": "\\mathbb",
8581
- "\\bold": "\\mathbf",
8582
- "\\frak": "\\mathfrak",
8583
- "\\bm": "\\boldsymbol"
8584
- };
8628
+ const frak = Object.freeze({
8629
+ C: 0x20EA,
8630
+ H: 0x20C4,
8631
+ I: 0x20C8,
8632
+ R: 0x20CA,
8633
+ Z: 0x20CE
8634
+ });
8585
8635
 
8586
- defineFunction({
8587
- type: "font",
8588
- names: [
8589
- // styles
8590
- "\\mathrm",
8591
- "\\mathit",
8592
- "\\mathbf",
8593
- "\\mathnormal",
8594
- "\\up@greek",
8595
- "\\boldsymbol",
8636
+ const bbb = Object.freeze({
8637
+ C: 0x20BF, // blackboard bold
8638
+ H: 0x20C5,
8639
+ N: 0x20C7,
8640
+ P: 0x20C9,
8641
+ Q: 0x20C9,
8642
+ R: 0x20CB,
8643
+ Z: 0x20CA
8644
+ });
8596
8645
 
8597
- // families
8598
- "\\mathbb",
8599
- "\\mathcal",
8600
- "\\mathfrak",
8601
- "\\mathscr",
8602
- "\\mathsf",
8603
- "\\mathsfit",
8604
- "\\mathtt",
8646
+ const bold = Object.freeze({
8647
+ "\u03f5": 0x1D2E7, // lunate epsilon
8648
+ "\u03d1": 0x1D30C, // vartheta
8649
+ "\u03f0": 0x1D2EE, // varkappa
8650
+ "\u03c6": 0x1D319, // varphi
8651
+ "\u03f1": 0x1D2EF, // varrho
8652
+ "\u03d6": 0x1D30B // varpi
8653
+ });
8605
8654
 
8606
- // aliases
8607
- "\\Bbb",
8608
- "\\bm",
8609
- "\\bold",
8610
- "\\frak"
8611
- ],
8612
- props: {
8613
- numArgs: 1,
8614
- allowedInArgument: true
8615
- },
8616
- handler: ({ parser, funcName }, args) => {
8617
- const body = normalizeArgument(args[0]);
8618
- let func = funcName;
8619
- if (func in fontAliases) {
8620
- func = fontAliases[func];
8621
- }
8622
- return {
8623
- type: "font",
8624
- mode: parser.mode,
8625
- font: func.slice(1),
8626
- body
8627
- };
8628
- },
8629
- mathmlBuilder: mathmlBuilder$6
8655
+ const boldItalic = Object.freeze({
8656
+ "\u03f5": 0x1D35B, // lunate epsilon
8657
+ "\u03d1": 0x1D380, // vartheta
8658
+ "\u03f0": 0x1D362, // varkappa
8659
+ "\u03c6": 0x1D38D, // varphi
8660
+ "\u03f1": 0x1D363, // varrho
8661
+ "\u03d6": 0x1D37F // varpi
8630
8662
  });
8631
8663
 
8632
- // Old font changing functions
8633
- defineFunction({
8634
- type: "font",
8635
- names: ["\\rm", "\\sf", "\\tt", "\\bf", "\\it", "\\cal"],
8636
- props: {
8637
- numArgs: 0,
8638
- allowedInText: true
8639
- },
8640
- handler: ({ parser, funcName, breakOnTokenText }, args) => {
8641
- const { mode } = parser;
8642
- const body = parser.parseExpression(true, breakOnTokenText, true);
8643
- const fontStyle = `math${funcName.slice(1)}`;
8664
+ const boldsf = Object.freeze({
8665
+ "\u03f5": 0x1D395, // lunate epsilon
8666
+ "\u03d1": 0x1D3BA, // vartheta
8667
+ "\u03f0": 0x1D39C, // varkappa
8668
+ "\u03c6": 0x1D3C7, // varphi
8669
+ "\u03f1": 0x1D39D, // varrho
8670
+ "\u03d6": 0x1D3B9 // varpi
8671
+ });
8644
8672
 
8645
- return {
8646
- type: "font",
8673
+ const bisf = Object.freeze({
8674
+ "\u03f5": 0x1D3CF, // lunate epsilon
8675
+ "\u03d1": 0x1D3F4, // vartheta
8676
+ "\u03f0": 0x1D3D6, // varkappa
8677
+ "\u03c6": 0x1D401, // varphi
8678
+ "\u03f1": 0x1D3D7, // varrho
8679
+ "\u03d6": 0x1D3F3 // varpi
8680
+ });
8681
+
8682
+ // Code point offsets below are derived from https://www.unicode.org/charts/PDF/U1D400.pdf
8683
+ const offset = Object.freeze({
8684
+ upperCaseLatin: { // A-Z
8685
+ "normal": ch => { return 0 },
8686
+ "bold": ch => { return 0x1D3BF },
8687
+ "italic": ch => { return 0x1D3F3 },
8688
+ "bold-italic": ch => { return 0x1D427 },
8689
+ "script": ch => { return script[ch] || 0x1D45B },
8690
+ "script-bold": ch => { return 0x1D48F },
8691
+ "fraktur": ch => { return frak[ch] || 0x1D4C3 },
8692
+ "fraktur-bold": ch => { return 0x1D52B },
8693
+ "double-struck": ch => { return bbb[ch] || 0x1D4F7 },
8694
+ "sans-serif": ch => { return 0x1D55F },
8695
+ "sans-serif-bold": ch => { return 0x1D593 },
8696
+ "sans-serif-italic": ch => { return 0x1D5C7 },
8697
+ "sans-serif-bold-italic": ch => { return 0x1D63C },
8698
+ "monospace": ch => { return 0x1D62F }
8699
+ },
8700
+ lowerCaseLatin: { // a-z
8701
+ "normal": ch => { return 0 },
8702
+ "bold": ch => { return 0x1D3B9 },
8703
+ "italic": ch => { return ch === "h" ? 0x20A6 : 0x1D3ED },
8704
+ "bold-italic": ch => { return 0x1D421 },
8705
+ "script": ch => { return script[ch] || 0x1D455 },
8706
+ "script-bold": ch => { return 0x1D489 },
8707
+ "fraktur": ch => { return 0x1D4BD },
8708
+ "fraktur-bold": ch => { return 0x1D525 },
8709
+ "double-struck": ch => { return 0x1D4F1 },
8710
+ "sans-serif": ch => { return 0x1D559 },
8711
+ "sans-serif-bold": ch => { return 0x1D58D },
8712
+ "sans-serif-italic": ch => { return 0x1D5C1 },
8713
+ "sans-serif-bold-italic": ch => { return 0x1D5F5 },
8714
+ "monospace": ch => { return 0x1D629 }
8715
+ },
8716
+ upperCaseGreek: { // A-Ω
8717
+ "normal": ch => { return 0 },
8718
+ "bold": ch => { return 0x1D317 },
8719
+ "italic": ch => { return 0x1D351 },
8720
+ // \boldsymbol actually returns upright bold for upperCaseGreek
8721
+ "bold-italic": ch => { return 0x1D317 },
8722
+ "script": ch => { return 0 },
8723
+ "script-bold": ch => { return 0 },
8724
+ "fraktur": ch => { return 0 },
8725
+ "fraktur-bold": ch => { return 0 },
8726
+ "double-struck": ch => { return 0 },
8727
+ // Unicode has no code points for regular-weight san-serif Greek. Use bold.
8728
+ "sans-serif": ch => { return 0x1D3C5 },
8729
+ "sans-serif-bold": ch => { return 0x1D3C5 },
8730
+ "sans-serif-italic": ch => { return 0 },
8731
+ "sans-serif-bold-italic": ch => { return 0x1D3FF },
8732
+ "monospace": ch => { return 0 }
8733
+ },
8734
+ lowerCaseGreek: { // α-ω
8735
+ "normal": ch => { return 0 },
8736
+ "bold": ch => { return 0x1D311 },
8737
+ "italic": ch => { return 0x1D34B },
8738
+ "bold-italic": ch => { return ch === "\u03d5" ? 0x1D37E : 0x1D385 },
8739
+ "script": ch => { return 0 },
8740
+ "script-bold": ch => { return 0 },
8741
+ "fraktur": ch => { return 0 },
8742
+ "fraktur-bold": ch => { return 0 },
8743
+ "double-struck": ch => { return 0 },
8744
+ // Unicode has no code points for regular-weight san-serif Greek. Use bold.
8745
+ "sans-serif": ch => { return 0x1D3BF },
8746
+ "sans-serif-bold": ch => { return 0x1D3BF },
8747
+ "sans-serif-italic": ch => { return 0 },
8748
+ "sans-serif-bold-italic": ch => { return 0x1D3F9 },
8749
+ "monospace": ch => { return 0 }
8750
+ },
8751
+ varGreek: { // \varGamma, etc
8752
+ "normal": ch => { return 0 },
8753
+ "bold": ch => { return bold[ch] || -51 },
8754
+ "italic": ch => { return 0 },
8755
+ "bold-italic": ch => { return boldItalic[ch] || 0x3A },
8756
+ "script": ch => { return 0 },
8757
+ "script-bold": ch => { return 0 },
8758
+ "fraktur": ch => { return 0 },
8759
+ "fraktur-bold": ch => { return 0 },
8760
+ "double-struck": ch => { return 0 },
8761
+ "sans-serif": ch => { return boldsf[ch] || 0x74 },
8762
+ "sans-serif-bold": ch => { return boldsf[ch] || 0x74 },
8763
+ "sans-serif-italic": ch => { return 0 },
8764
+ "sans-serif-bold-italic": ch => { return bisf[ch] || 0xAE },
8765
+ "monospace": ch => { return 0 }
8766
+ },
8767
+ numeral: { // 0-9
8768
+ "normal": ch => { return 0 },
8769
+ "bold": ch => { return 0x1D79E },
8770
+ "italic": ch => { return 0 },
8771
+ "bold-italic": ch => { return 0 },
8772
+ "script": ch => { return 0 },
8773
+ "script-bold": ch => { return 0 },
8774
+ "fraktur": ch => { return 0 },
8775
+ "fraktur-bold": ch => { return 0 },
8776
+ "double-struck": ch => { return 0x1D7A8 },
8777
+ "sans-serif": ch => { return 0x1D7B2 },
8778
+ "sans-serif-bold": ch => { return 0x1D7BC },
8779
+ "sans-serif-italic": ch => { return 0 },
8780
+ "sans-serif-bold-italic": ch => { return 0 },
8781
+ "monospace": ch => { return 0x1D7C6 }
8782
+ }
8783
+ });
8784
+
8785
+ const variantChar = (ch, variant) => {
8786
+ const codePoint = ch.codePointAt(0);
8787
+ const block = 0x40 < codePoint && codePoint < 0x5b
8788
+ ? "upperCaseLatin"
8789
+ : 0x60 < codePoint && codePoint < 0x7b
8790
+ ? "lowerCaseLatin"
8791
+ : (0x390 < codePoint && codePoint < 0x3AA)
8792
+ ? "upperCaseGreek"
8793
+ : 0x3B0 < codePoint && codePoint < 0x3CA || ch === "\u03d5"
8794
+ ? "lowerCaseGreek"
8795
+ : 0x1D6E1 < codePoint && codePoint < 0x1D6FC || bold[ch]
8796
+ ? "varGreek"
8797
+ : (0x2F < codePoint && codePoint < 0x3A)
8798
+ ? "numeral"
8799
+ : "other";
8800
+ return block === "other"
8801
+ ? ch
8802
+ : String.fromCodePoint(codePoint + offset[block][variant](ch))
8803
+ };
8804
+
8805
+ const smallCaps = Object.freeze({
8806
+ a: "ᴀ",
8807
+ b: "ʙ",
8808
+ c: "ᴄ",
8809
+ d: "ᴅ",
8810
+ e: "ᴇ",
8811
+ f: "ꜰ",
8812
+ g: "ɢ",
8813
+ h: "ʜ",
8814
+ i: "ɪ",
8815
+ j: "ᴊ",
8816
+ k: "ᴋ",
8817
+ l: "ʟ",
8818
+ m: "ᴍ",
8819
+ n: "ɴ",
8820
+ o: "ᴏ",
8821
+ p: "ᴘ",
8822
+ q: "ǫ",
8823
+ r: "ʀ",
8824
+ s: "s",
8825
+ t: "ᴛ",
8826
+ u: "ᴜ",
8827
+ v: "ᴠ",
8828
+ w: "ᴡ",
8829
+ x: "x",
8830
+ y: "ʏ",
8831
+ z: "ᴢ"
8832
+ });
8833
+
8834
+ const varNameFonts = ["mathrm", "mathit"];
8835
+
8836
+ const isLongVariableName = (group, font) => {
8837
+ if (!varNameFonts.includes(font) || !group.body || group.body.type !== "ordgroup" ||
8838
+ group.body.body.length === 1) {
8839
+ return false
8840
+ }
8841
+ if (group.body.body[0].type !== "mathord") { return false }
8842
+ for (let i = 1; i < group.body.body.length; i++) {
8843
+ const parseNodeType = group.body.body[i].type;
8844
+ if (!(parseNodeType === "mathord" ||
8845
+ (parseNodeType === "textord" && !isNaN(group.body.body[i].text)))) {
8846
+ return false
8847
+ }
8848
+ }
8849
+ return true
8850
+ };
8851
+
8852
+ const mathmlBuilder$6 = (group, style) => {
8853
+ const font = group.font;
8854
+ const newStyle = style.withFont(font);
8855
+ const mathGroup = buildGroup$1(group.body, newStyle);
8856
+
8857
+ if (mathGroup.children.length === 0) { return mathGroup } // empty group, e.g., \mathrm{}
8858
+ if (font === "boldsymbol" && ["mo", "mpadded", "mrow"].includes(mathGroup.type)) {
8859
+ mathGroup.style.fontWeight = "bold";
8860
+ return mathGroup
8861
+ }
8862
+ // Check if it is possible to consolidate elements into a single <mi> element.
8863
+ if (isLongVariableName(group, font)) {
8864
+ // This is a \mathrm{…} or \mathit{…} group. It gets special treatment.
8865
+ const mi = mathGroup.children[0].children[0].children
8866
+ ? mathGroup.children[0].children[0]
8867
+ : mathGroup.children[0];
8868
+ delete mi.attributes.mathvariant;
8869
+ for (let i = 1; i < mathGroup.children.length; i++) {
8870
+ mi.children[0].text += mathGroup.children[i].children[0].children
8871
+ ? mathGroup.children[i].children[0].children[0].text
8872
+ : mathGroup.children[i].children[0].text;
8873
+ }
8874
+ if (font === "mathit") {
8875
+ // Long <mi> elements are normally rendered in upright font.
8876
+ // To get italic, we need to convert each character to the corresponding italic character.
8877
+ mi.children[0].text = mi.children[0].text.split("")
8878
+ .map(c => variantChar(c, "italic")).join("");
8879
+ return mi
8880
+ }
8881
+ // Otherwise, font is "mathrm". Wrap in a <mpadded> to prevent a Firefox spacing bug.
8882
+ const mpadded = new MathNode("mpadded", [mi]);
8883
+ mpadded.setAttribute("lspace", "0");
8884
+ return mpadded
8885
+ }
8886
+ let canConsolidate = mathGroup.children[0].type === "mo";
8887
+ for (let i = 1; i < mathGroup.children.length; i++) {
8888
+ if (mathGroup.children[i].type === "mo" && font === "boldsymbol") {
8889
+ mathGroup.children[i].style.fontWeight = "bold";
8890
+ }
8891
+ if (mathGroup.children[i].type !== "mi") { canConsolidate = false; }
8892
+ const localVariant = mathGroup.children[i].attributes &&
8893
+ mathGroup.children[i].attributes.mathvariant || "";
8894
+ if (localVariant !== "normal") { canConsolidate = false; }
8895
+ }
8896
+ if (!canConsolidate) { return mathGroup }
8897
+ // Consolidate the <mi> elements.
8898
+ const mi = mathGroup.children[0];
8899
+ for (let i = 1; i < mathGroup.children.length; i++) {
8900
+ mi.children.push(mathGroup.children[i].children[0]);
8901
+ }
8902
+ if (mi.attributes.mathvariant && mi.attributes.mathvariant === "normal") {
8903
+ // Workaround for a Firefox bug that renders spurious space around
8904
+ // a <mi mathvariant="normal">
8905
+ // Ref: https://bugs.webkit.org/show_bug.cgi?id=129097
8906
+ // We insert a text node that contains a zero-width space and wrap in an mrow.
8907
+ // TODO: Get rid of this <mi> workaround when the Firefox bug is fixed.
8908
+ const bogus = new MathNode("mtext", new TextNode("\u200b"));
8909
+ return new MathNode("mrow", [bogus, mi])
8910
+ }
8911
+ return mi
8912
+ };
8913
+
8914
+ const fontAliases = {
8915
+ "\\Bbb": "\\mathbb",
8916
+ "\\bold": "\\mathbf",
8917
+ "\\frak": "\\mathfrak",
8918
+ "\\bm": "\\boldsymbol"
8919
+ };
8920
+
8921
+ defineFunction({
8922
+ type: "font",
8923
+ names: [
8924
+ // styles
8925
+ "\\mathrm",
8926
+ "\\mathit",
8927
+ "\\mathbf",
8928
+ "\\mathnormal",
8929
+ "\\up@greek",
8930
+ "\\boldsymbol",
8931
+
8932
+ // families
8933
+ "\\mathbb",
8934
+ "\\mathcal",
8935
+ "\\mathfrak",
8936
+ "\\mathscr",
8937
+ "\\mathsf",
8938
+ "\\mathsfit",
8939
+ "\\mathtt",
8940
+
8941
+ // aliases
8942
+ "\\Bbb",
8943
+ "\\bm",
8944
+ "\\bold",
8945
+ "\\frak"
8946
+ ],
8947
+ props: {
8948
+ numArgs: 1,
8949
+ allowedInArgument: true
8950
+ },
8951
+ handler: ({ parser, funcName }, args) => {
8952
+ const body = normalizeArgument(args[0]);
8953
+ let func = funcName;
8954
+ if (func in fontAliases) {
8955
+ func = fontAliases[func];
8956
+ }
8957
+ return {
8958
+ type: "font",
8959
+ mode: parser.mode,
8960
+ font: func.slice(1),
8961
+ body
8962
+ };
8963
+ },
8964
+ mathmlBuilder: mathmlBuilder$6
8965
+ });
8966
+
8967
+ // Old font changing functions
8968
+ defineFunction({
8969
+ type: "font",
8970
+ names: ["\\rm", "\\sf", "\\tt", "\\bf", "\\it", "\\cal"],
8971
+ props: {
8972
+ numArgs: 0,
8973
+ allowedInText: true
8974
+ },
8975
+ handler: ({ parser, funcName, breakOnTokenText }, args) => {
8976
+ const { mode } = parser;
8977
+ const body = parser.parseExpression(true, breakOnTokenText, true);
8978
+ const fontStyle = `math${funcName.slice(1)}`;
8979
+
8980
+ return {
8981
+ type: "font",
8647
8982
  mode: mode,
8648
8983
  font: fontStyle,
8649
8984
  body: {
@@ -8659,22 +8994,37 @@ defineFunction({
8659
8994
  const stylArray = ["display", "text", "script", "scriptscript"];
8660
8995
  const scriptLevel = { auto: -1, display: 0, text: 0, script: 1, scriptscript: 2 };
8661
8996
 
8997
+ const adjustStyle = (functionSize, originalStyle) => {
8998
+ // Figure out what style this fraction should be in based on the
8999
+ // function used
9000
+ let style = originalStyle;
9001
+ if (functionSize === "display") { //\tfrac or \cfrac
9002
+ // Get display style as a default.
9003
+ // If incoming style is sub/sup, use style.text() to get correct size.
9004
+ const newSize = style.level >= StyleLevel.SCRIPT ? StyleLevel.TEXT : StyleLevel.DISPLAY;
9005
+ style = style.withLevel(newSize);
9006
+ } else if (functionSize === "text" &&
9007
+ style.level === StyleLevel.DISPLAY) {
9008
+ // We're in a \tfrac but incoming style is displaystyle, so:
9009
+ style = style.withLevel(StyleLevel.TEXT);
9010
+ } else if (functionSize === "auto") {
9011
+ style = style.incrementLevel();
9012
+ } else if (functionSize === "script") {
9013
+ style = style.withLevel(StyleLevel.SCRIPT);
9014
+ } else if (functionSize === "scriptscript") {
9015
+ style = style.withLevel(StyleLevel.SCRIPTSCRIPT);
9016
+ }
9017
+ return style;
9018
+ };
9019
+
8662
9020
  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);
9021
+ style = adjustStyle(group.scriptLevel, style);
8672
9022
 
8673
9023
  // Chromium (wrongly) continues to shrink fractions beyond scriptscriptlevel.
8674
9024
  // So we check for levels that Chromium shrinks too small.
8675
9025
  // If necessary, set an explicit fraction depth.
8676
- const numer = buildGroup$1(group.numer, childOptions);
8677
- const denom = buildGroup$1(group.denom, childOptions);
9026
+ const numer = buildGroup$1(group.numer, style);
9027
+ const denom = buildGroup$1(group.denom, style);
8678
9028
  if (style.level === 3) {
8679
9029
  numer.style.mathDepth = "2";
8680
9030
  numer.setAttribute("scriptlevel", "2");
@@ -8727,6 +9077,7 @@ const mathmlBuilder$5 = (group, style) => {
8727
9077
  defineFunction({
8728
9078
  type: "genfrac",
8729
9079
  names: [
9080
+ "\\cfrac",
8730
9081
  "\\dfrac",
8731
9082
  "\\frac",
8732
9083
  "\\tfrac",
@@ -8750,6 +9101,7 @@ defineFunction({
8750
9101
  let scriptLevel = "auto";
8751
9102
 
8752
9103
  switch (funcName) {
9104
+ case "\\cfrac":
8753
9105
  case "\\dfrac":
8754
9106
  case "\\frac":
8755
9107
  case "\\tfrac":
@@ -8776,56 +9128,26 @@ defineFunction({
8776
9128
  throw new Error("Unrecognized genfrac command");
8777
9129
  }
8778
9130
 
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;
8788
- }
8789
-
8790
- return {
8791
- type: "genfrac",
8792
- mode: parser.mode,
8793
- continued: false,
8794
- numer,
8795
- denom,
8796
- hasBarLine,
8797
- leftDelim,
8798
- rightDelim,
8799
- scriptLevel,
8800
- barSize: null
8801
- };
8802
- },
8803
- mathmlBuilder: mathmlBuilder$5
8804
- });
8805
-
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];
9131
+ if (funcName === "\\cfrac" || funcName.startsWith("\\d")) {
9132
+ scriptLevel = "display";
9133
+ } else if (funcName.startsWith("\\t")) {
9134
+ scriptLevel = "text";
9135
+ }
8815
9136
 
8816
9137
  return {
8817
9138
  type: "genfrac",
8818
9139
  mode: parser.mode,
8819
- continued: true,
9140
+ continued: false,
8820
9141
  numer,
8821
9142
  denom,
8822
- hasBarLine: true,
8823
- leftDelim: null,
8824
- rightDelim: null,
8825
- scriptLevel: "display",
9143
+ hasBarLine,
9144
+ leftDelim,
9145
+ rightDelim,
9146
+ scriptLevel,
8826
9147
  barSize: null
8827
9148
  };
8828
- }
9149
+ },
9150
+ mathmlBuilder: mathmlBuilder$5
8829
9151
  });
8830
9152
 
8831
9153
  // Infix generalized fractions -- these are not rendered directly, but replaced
@@ -9656,8 +9978,16 @@ const binrelClass = (arg) => {
9656
9978
  const atom = arg.type === "ordgroup" && arg.body.length && arg.body.length === 1
9657
9979
  ? arg.body[0]
9658
9980
  : arg;
9659
- if (atom.type === "atom" && (atom.family === "bin" || atom.family === "rel")) {
9660
- return "m" + atom.family;
9981
+ if (atom.type === "atom") {
9982
+ // BIN args are sometimes changed to OPEN, so check the original family.
9983
+ const family = arg.body.length > 0 && arg.body[0].text && symbols.math[arg.body[0].text]
9984
+ ? symbols.math[arg.body[0].text].group
9985
+ : atom.family;
9986
+ if (family === "bin" || family === "rel") {
9987
+ return "m" + family;
9988
+ } else {
9989
+ return "mord";
9990
+ }
9661
9991
  } else {
9662
9992
  return "mord";
9663
9993
  }
@@ -10228,6 +10558,14 @@ const mathmlBuilder$1 = (group, style) => {
10228
10558
  if ((node.type === "mrow" || node.type === "mpadded") && node.children.length === 1 &&
10229
10559
  node.children[0] instanceof MathNode) {
10230
10560
  node = node.children[0];
10561
+ } else if (node.type === "mrow" && node.children.length === 2 &&
10562
+ node.children[0] instanceof MathNode &&
10563
+ node.children[1] instanceof MathNode &&
10564
+ node.children[1].type === "mspace" && !node.children[1].attributes.width &&
10565
+ node.children[1].children.length === 0) {
10566
+ // This is a workaround for a Firefox bug that applies spacing to
10567
+ // an <mi> with mathvariant="normal".
10568
+ node = node.children[0];
10231
10569
  }
10232
10570
  switch (node.type) {
10233
10571
  case "mi":
@@ -10418,7 +10756,7 @@ defineFunction({
10418
10756
  const inner = buildExpression(ordargument(group.body), style);
10419
10757
  const phantom = new MathNode("mphantom", inner);
10420
10758
  const node = new MathNode("mpadded", [phantom]);
10421
- node.setAttribute("width", "0px");
10759
+ node.setAttribute("width", "0.1px");
10422
10760
  return node;
10423
10761
  }
10424
10762
  });
@@ -11235,232 +11573,6 @@ const getVariant = function(group, style) {
11235
11573
  return Object.prototype.hasOwnProperty.call(fontMap, font) ? fontMap[font] : null
11236
11574
  };
11237
11575
 
11238
- // Chromium does not support the MathML `mathvariant` attribute.
11239
- // Instead, we replace ASCII characters with Unicode characters that
11240
- // are defined in the font as bold, italic, double-struck, etc.
11241
- // This module identifies those Unicode code points.
11242
-
11243
- // First, a few helpers.
11244
- const script = Object.freeze({
11245
- B: 0x20EA, // Offset from ASCII B to Unicode script B
11246
- E: 0x20EB,
11247
- F: 0x20EB,
11248
- H: 0x20C3,
11249
- I: 0x20C7,
11250
- L: 0x20C6,
11251
- M: 0x20E6,
11252
- R: 0x20C9,
11253
- e: 0x20CA,
11254
- g: 0x20A3,
11255
- o: 0x20C5
11256
- });
11257
-
11258
- const frak = Object.freeze({
11259
- C: 0x20EA,
11260
- H: 0x20C4,
11261
- I: 0x20C8,
11262
- R: 0x20CA,
11263
- Z: 0x20CE
11264
- });
11265
-
11266
- const bbb = Object.freeze({
11267
- C: 0x20BF, // blackboard bold
11268
- H: 0x20C5,
11269
- N: 0x20C7,
11270
- P: 0x20C9,
11271
- Q: 0x20C9,
11272
- R: 0x20CB,
11273
- Z: 0x20CA
11274
- });
11275
-
11276
- const bold = Object.freeze({
11277
- "\u03f5": 0x1D2E7, // lunate epsilon
11278
- "\u03d1": 0x1D30C, // vartheta
11279
- "\u03f0": 0x1D2EE, // varkappa
11280
- "\u03c6": 0x1D319, // varphi
11281
- "\u03f1": 0x1D2EF, // varrho
11282
- "\u03d6": 0x1D30B // varpi
11283
- });
11284
-
11285
- const boldItalic = Object.freeze({
11286
- "\u03f5": 0x1D35B, // lunate epsilon
11287
- "\u03d1": 0x1D380, // vartheta
11288
- "\u03f0": 0x1D362, // varkappa
11289
- "\u03c6": 0x1D38D, // varphi
11290
- "\u03f1": 0x1D363, // varrho
11291
- "\u03d6": 0x1D37F // varpi
11292
- });
11293
-
11294
- const boldsf = Object.freeze({
11295
- "\u03f5": 0x1D395, // lunate epsilon
11296
- "\u03d1": 0x1D3BA, // vartheta
11297
- "\u03f0": 0x1D39C, // varkappa
11298
- "\u03c6": 0x1D3C7, // varphi
11299
- "\u03f1": 0x1D39D, // varrho
11300
- "\u03d6": 0x1D3B9 // varpi
11301
- });
11302
-
11303
- const bisf = Object.freeze({
11304
- "\u03f5": 0x1D3CF, // lunate epsilon
11305
- "\u03d1": 0x1D3F4, // vartheta
11306
- "\u03f0": 0x1D3D6, // varkappa
11307
- "\u03c6": 0x1D401, // varphi
11308
- "\u03f1": 0x1D3D7, // varrho
11309
- "\u03d6": 0x1D3F3 // varpi
11310
- });
11311
-
11312
- // Code point offsets below are derived from https://www.unicode.org/charts/PDF/U1D400.pdf
11313
- const offset = Object.freeze({
11314
- upperCaseLatin: { // A-Z
11315
- "normal": ch => { return 0 },
11316
- "bold": ch => { return 0x1D3BF },
11317
- "italic": ch => { return 0x1D3F3 },
11318
- "bold-italic": ch => { return 0x1D427 },
11319
- "script": ch => { return script[ch] || 0x1D45B },
11320
- "script-bold": ch => { return 0x1D48F },
11321
- "fraktur": ch => { return frak[ch] || 0x1D4C3 },
11322
- "fraktur-bold": ch => { return 0x1D52B },
11323
- "double-struck": ch => { return bbb[ch] || 0x1D4F7 },
11324
- "sans-serif": ch => { return 0x1D55F },
11325
- "sans-serif-bold": ch => { return 0x1D593 },
11326
- "sans-serif-italic": ch => { return 0x1D5C7 },
11327
- "sans-serif-bold-italic": ch => { return 0x1D63C },
11328
- "monospace": ch => { return 0x1D62F }
11329
- },
11330
- lowerCaseLatin: { // a-z
11331
- "normal": ch => { return 0 },
11332
- "bold": ch => { return 0x1D3B9 },
11333
- "italic": ch => { return ch === "h" ? 0x20A6 : 0x1D3ED },
11334
- "bold-italic": ch => { return 0x1D421 },
11335
- "script": ch => { return script[ch] || 0x1D455 },
11336
- "script-bold": ch => { return 0x1D489 },
11337
- "fraktur": ch => { return 0x1D4BD },
11338
- "fraktur-bold": ch => { return 0x1D525 },
11339
- "double-struck": ch => { return 0x1D4F1 },
11340
- "sans-serif": ch => { return 0x1D559 },
11341
- "sans-serif-bold": ch => { return 0x1D58D },
11342
- "sans-serif-italic": ch => { return 0x1D5C1 },
11343
- "sans-serif-bold-italic": ch => { return 0x1D5F5 },
11344
- "monospace": ch => { return 0x1D629 }
11345
- },
11346
- upperCaseGreek: { // A-Ω
11347
- "normal": ch => { return 0 },
11348
- "bold": ch => { return 0x1D317 },
11349
- "italic": ch => { return 0x1D351 },
11350
- // \boldsymbol actually returns upright bold for upperCaseGreek
11351
- "bold-italic": ch => { return 0x1D317 },
11352
- "script": ch => { return 0 },
11353
- "script-bold": ch => { return 0 },
11354
- "fraktur": ch => { return 0 },
11355
- "fraktur-bold": ch => { return 0 },
11356
- "double-struck": ch => { return 0 },
11357
- // Unicode has no code points for regular-weight san-serif Greek. Use bold.
11358
- "sans-serif": ch => { return 0x1D3C5 },
11359
- "sans-serif-bold": ch => { return 0x1D3C5 },
11360
- "sans-serif-italic": ch => { return 0 },
11361
- "sans-serif-bold-italic": ch => { return 0x1D3FF },
11362
- "monospace": ch => { return 0 }
11363
- },
11364
- lowerCaseGreek: { // α-ω
11365
- "normal": ch => { return 0 },
11366
- "bold": ch => { return 0x1D311 },
11367
- "italic": ch => { return 0x1D34B },
11368
- "bold-italic": ch => { return ch === "\u03d5" ? 0x1D37E : 0x1D385 },
11369
- "script": ch => { return 0 },
11370
- "script-bold": ch => { return 0 },
11371
- "fraktur": ch => { return 0 },
11372
- "fraktur-bold": ch => { return 0 },
11373
- "double-struck": ch => { return 0 },
11374
- // Unicode has no code points for regular-weight san-serif Greek. Use bold.
11375
- "sans-serif": ch => { return 0x1D3BF },
11376
- "sans-serif-bold": ch => { return 0x1D3BF },
11377
- "sans-serif-italic": ch => { return 0 },
11378
- "sans-serif-bold-italic": ch => { return 0x1D3F9 },
11379
- "monospace": ch => { return 0 }
11380
- },
11381
- varGreek: { // \varGamma, etc
11382
- "normal": ch => { return 0 },
11383
- "bold": ch => { return bold[ch] || -51 },
11384
- "italic": ch => { return 0 },
11385
- "bold-italic": ch => { return boldItalic[ch] || 0x3A },
11386
- "script": ch => { return 0 },
11387
- "script-bold": ch => { return 0 },
11388
- "fraktur": ch => { return 0 },
11389
- "fraktur-bold": ch => { return 0 },
11390
- "double-struck": ch => { return 0 },
11391
- "sans-serif": ch => { return boldsf[ch] || 0x74 },
11392
- "sans-serif-bold": ch => { return boldsf[ch] || 0x74 },
11393
- "sans-serif-italic": ch => { return 0 },
11394
- "sans-serif-bold-italic": ch => { return bisf[ch] || 0xAE },
11395
- "monospace": ch => { return 0 }
11396
- },
11397
- numeral: { // 0-9
11398
- "normal": ch => { return 0 },
11399
- "bold": ch => { return 0x1D79E },
11400
- "italic": ch => { return 0 },
11401
- "bold-italic": ch => { return 0 },
11402
- "script": ch => { return 0 },
11403
- "script-bold": ch => { return 0 },
11404
- "fraktur": ch => { return 0 },
11405
- "fraktur-bold": ch => { return 0 },
11406
- "double-struck": ch => { return 0x1D7A8 },
11407
- "sans-serif": ch => { return 0x1D7B2 },
11408
- "sans-serif-bold": ch => { return 0x1D7BC },
11409
- "sans-serif-italic": ch => { return 0 },
11410
- "sans-serif-bold-italic": ch => { return 0 },
11411
- "monospace": ch => { return 0x1D7C6 }
11412
- }
11413
- });
11414
-
11415
- const variantChar = (ch, variant) => {
11416
- const codePoint = ch.codePointAt(0);
11417
- const block = 0x40 < codePoint && codePoint < 0x5b
11418
- ? "upperCaseLatin"
11419
- : 0x60 < codePoint && codePoint < 0x7b
11420
- ? "lowerCaseLatin"
11421
- : (0x390 < codePoint && codePoint < 0x3AA)
11422
- ? "upperCaseGreek"
11423
- : 0x3B0 < codePoint && codePoint < 0x3CA || ch === "\u03d5"
11424
- ? "lowerCaseGreek"
11425
- : 0x1D6E1 < codePoint && codePoint < 0x1D6FC || bold[ch]
11426
- ? "varGreek"
11427
- : (0x2F < codePoint && codePoint < 0x3A)
11428
- ? "numeral"
11429
- : "other";
11430
- return block === "other"
11431
- ? ch
11432
- : String.fromCodePoint(codePoint + offset[block][variant](ch))
11433
- };
11434
-
11435
- const smallCaps = Object.freeze({
11436
- a: "ᴀ",
11437
- b: "ʙ",
11438
- c: "ᴄ",
11439
- d: "ᴅ",
11440
- e: "ᴇ",
11441
- f: "ꜰ",
11442
- g: "ɢ",
11443
- h: "ʜ",
11444
- i: "ɪ",
11445
- j: "ᴊ",
11446
- k: "ᴋ",
11447
- l: "ʟ",
11448
- m: "ᴍ",
11449
- n: "ɴ",
11450
- o: "ᴏ",
11451
- p: "ᴘ",
11452
- q: "ǫ",
11453
- r: "ʀ",
11454
- s: "s",
11455
- t: "ᴛ",
11456
- u: "ᴜ",
11457
- v: "ᴠ",
11458
- w: "ᴡ",
11459
- x: "x",
11460
- y: "ʏ",
11461
- z: "ᴢ"
11462
- });
11463
-
11464
11576
  // "mathord" and "textord" ParseNodes created in Parser.js from symbol Groups in
11465
11577
  // src/symbols.js.
11466
11578
 
@@ -11498,8 +11610,8 @@ defineFunctionBuilders({
11498
11610
  node.setAttribute("mathvariant", "normal");
11499
11611
  if (text.text.length === 1) {
11500
11612
  // 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");
11613
+ const mspace = new MathNode("mspace", []);
11614
+ node = new MathNode("mrow", [node, mspace]);
11503
11615
  }
11504
11616
  }
11505
11617
  return node
@@ -12080,7 +12192,7 @@ class MacroExpander {
12080
12192
  this.pushToken(new Token("EOF", end.loc));
12081
12193
 
12082
12194
  this.pushTokens(tokens);
12083
- return start.range(end, "");
12195
+ return new Token("", SourceLocation.range(start, end));
12084
12196
  }
12085
12197
 
12086
12198
  /**
@@ -13036,7 +13148,7 @@ class Parser {
13036
13148
  * Parses an "expression", which is a list of atoms.
13037
13149
  *
13038
13150
  * `breakOnInfix`: Should the parsing stop when we hit infix nodes? This
13039
- * happens when functions have higher precedence han infix
13151
+ * happens when functions have higher precedence than infix
13040
13152
  * nodes in implicit parses.
13041
13153
  *
13042
13154
  * `breakOnTokenText`: The text of the token that the expression should end
@@ -13644,7 +13756,7 @@ class Parser {
13644
13756
  ) {
13645
13757
  const firstToken = this.fetch();
13646
13758
  const text = firstToken.text;
13647
-
13759
+ if (name === "argument to '\\left'") { return this.parseSymbol() }
13648
13760
  let result;
13649
13761
  // Try to parse an open brace or \begingroup
13650
13762
  if (text === "{" || text === "\\begingroup" || text === "\\toggle") {
@@ -13791,7 +13903,8 @@ class Parser {
13791
13903
  let symbol;
13792
13904
  if (symbols[this.mode][text]) {
13793
13905
  let group = symbols[this.mode][text].group;
13794
- if (group === "bin" && binLeftCancellers.includes(this.prevAtomType)) {
13906
+ if (group === "bin" &&
13907
+ (binLeftCancellers.includes(this.prevAtomType) || this.prevAtomType === "")) {
13795
13908
  // Change from a binary operator to a unary (prefix) operator
13796
13909
  group = "open";
13797
13910
  }
@@ -13887,11 +14000,27 @@ const parseTree = function(toParse, settings) {
13887
14000
  if (!(typeof toParse === "string" || toParse instanceof String)) {
13888
14001
  throw new TypeError("Temml can only parse string typed expression")
13889
14002
  }
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"];
14003
+ let tree;
14004
+ let parser;
14005
+ try {
14006
+ parser = new Parser(toParse, settings);
14007
+ // Blank out any \df@tag to avoid spurious "Duplicate \tag" errors
14008
+ delete parser.gullet.macros.current["\\df@tag"];
13893
14009
 
13894
- let tree = parser.parse();
14010
+ tree = parser.parse();
14011
+ } catch (error) {
14012
+ if (error.toString() === "ParseError: Unmatched delimiter") {
14013
+ // Abandon the attempt to wrap delimiter pairs in an <mrow>.
14014
+ // Try again, and put each delimiter into an <mo> element.
14015
+ settings.wrapDelimiterPairs = false;
14016
+ parser = new Parser(toParse, settings);
14017
+ // Blank out any \df@tag to avoid spurious "Duplicate \tag" errors
14018
+ delete parser.gullet.macros.current["\\df@tag"];
14019
+ tree = parser.parse();
14020
+ } else {
14021
+ throw error;
14022
+ }
14023
+ }
13895
14024
 
13896
14025
  // LaTeX ignores a \tag placed outside an AMS environment.
13897
14026
  if (!(tree.length > 0 && tree[0].type && tree[0].type === "array" && tree[0].addEqnNum)) {
@@ -14068,7 +14197,7 @@ class Style {
14068
14197
  * https://mit-license.org/
14069
14198
  */
14070
14199
 
14071
- const version = "0.13.01";
14200
+ const version = "0.13.3";
14072
14201
 
14073
14202
  function postProcess(block) {
14074
14203
  const labelMap = {};