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