temml 0.10.20 → 0.10.22

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
@@ -934,7 +934,8 @@ defineSymbol(math, textord, "\u2135", "\\aleph", true);
934
934
  defineSymbol(math, textord, "\u2200", "\\forall", true);
935
935
  defineSymbol(math, textord, "\u210f", "\\hbar", true);
936
936
  defineSymbol(math, textord, "\u2203", "\\exists", true);
937
- defineSymbol(math, textord, "\u2207", "\\nabla", true);
937
+ // is actually a unary operator, not binary. But this works.
938
+ defineSymbol(math, bin, "\u2207", "\\nabla", true);
938
939
  defineSymbol(math, textord, "\u266d", "\\flat", true);
939
940
  defineSymbol(math, textord, "\u2113", "\\ell", true);
940
941
  defineSymbol(math, textord, "\u266e", "\\natural", true);
@@ -988,6 +989,7 @@ defineSymbol(math, bin, "\u2021", "\\ddagger");
988
989
  defineSymbol(math, bin, "\u2240", "\\wr", true);
989
990
  defineSymbol(math, bin, "\u2a3f", "\\amalg");
990
991
  defineSymbol(math, bin, "\u0026", "\\And"); // from amsmath
992
+ defineSymbol(math, bin, "\u2AFD", "\\sslash", true); // from stmaryrd
991
993
 
992
994
  // Arrow Symbols
993
995
  defineSymbol(math, rel, "\u27f5", "\\longleftarrow", true);
@@ -1406,6 +1408,7 @@ defineSymbol(math, mathord, "\u2aeb", "\\Bot");
1406
1408
  defineSymbol(math, bin, "\u2217", "\u2217", true);
1407
1409
  defineSymbol(math, bin, "+", "+");
1408
1410
  defineSymbol(math, bin, "*", "*");
1411
+ defineSymbol(math, bin, "\u2044", "/", true);
1409
1412
  defineSymbol(math, bin, "\u2044", "\u2044");
1410
1413
  defineSymbol(math, bin, "\u2212", "-", true);
1411
1414
  defineSymbol(math, bin, "\u22c5", "\\cdot", true);
@@ -1862,7 +1865,8 @@ function setLineBreaks(expression, wrapMode, isDisplayMode) {
1862
1865
  continue
1863
1866
  }
1864
1867
  block.push(node);
1865
- if (node.type && node.type === "mo" && node.children.length === 1) {
1868
+ if (node.type && node.type === "mo" && node.children.length === 1 &&
1869
+ !Object.hasOwn(node.attributes, "movablelimits")) {
1866
1870
  const ch = node.children[0].text;
1867
1871
  if (openDelims.indexOf(ch) > -1) {
1868
1872
  level += 1;
@@ -1877,7 +1881,7 @@ function setLineBreaks(expression, wrapMode, isDisplayMode) {
1877
1881
  mrows.push(element);
1878
1882
  block = [node];
1879
1883
  }
1880
- } else if (level === 0 && wrapMode === "tex") {
1884
+ } else if (level === 0 && wrapMode === "tex" && ch !== "∇") {
1881
1885
  // Check if the following node is a \nobreak text node, e.g. "~""
1882
1886
  const next = i < expression.length - 1 ? expression[i + 1] : null;
1883
1887
  let glueIsFreeOfNobreak = true;
@@ -3245,7 +3249,7 @@ defineFunction({
3245
3249
  allowedInText: true,
3246
3250
  argTypes: ["raw", "raw"]
3247
3251
  },
3248
- handler({ parser, token }, args, optArgs) {
3252
+ handler({ parser, breakOnTokenText, token }, args, optArgs) {
3249
3253
  const model = optArgs[0] && assertNodeType(optArgs[0], "raw").string;
3250
3254
  let color = "";
3251
3255
  if (model) {
@@ -3255,15 +3259,8 @@ defineFunction({
3255
3259
  color = validateColor(assertNodeType(args[0], "raw").string, parser.gullet.macros, token);
3256
3260
  }
3257
3261
 
3258
- // Set macro \current@color in current namespace to store the current
3259
- // color, mimicking the behavior of color.sty.
3260
- // This is currently used just to correctly color a \right
3261
- // that follows a \color command.
3262
- parser.gullet.macros.set("\\current@color", color);
3263
-
3264
3262
  // Parse out the implicit body that should be colored.
3265
- // Since \color nodes should not be nested, break on \color.
3266
- const body = parser.parseExpression(true, "\\color");
3263
+ const body = parser.parseExpression(true, breakOnTokenText, true);
3267
3264
 
3268
3265
  return {
3269
3266
  type: "color",
@@ -3705,17 +3702,13 @@ const sizeToMaxHeight = [0, 1.2, 1.8, 2.4, 3.0];
3705
3702
 
3706
3703
  // Delimiter functions
3707
3704
  function checkDelimiter(delim, context) {
3708
- if (delim.type === "ordgroup" && delim.body.length === 1 && delim.body[0].text === "\u2044") {
3709
- // Recover "/" from the zero spacing group. (See macros.js)
3710
- delim = { type: "textord", text: "/", mode: "math" };
3711
- }
3712
3705
  const symDelim = checkSymbolNodeType(delim);
3713
3706
  if (symDelim && delimiters.includes(symDelim.text)) {
3714
3707
  // If a character is not in the MathML operator dictionary, it will not stretch.
3715
3708
  // Replace such characters w/characters that will stretch.
3709
+ if (["/", "\u2044"].includes(symDelim.text)) { symDelim.text = "\u2215"; }
3716
3710
  if (["<", "\\lt"].includes(symDelim.text)) { symDelim.text = "⟨"; }
3717
3711
  if ([">", "\\gt"].includes(symDelim.text)) { symDelim.text = "⟩"; }
3718
- if (symDelim.text === "/") { symDelim.text = "\u2215"; }
3719
3712
  if (symDelim.text === "\\backslash") { symDelim.text = "\u2216"; }
3720
3713
  return symDelim;
3721
3714
  } else if (symDelim) {
@@ -3803,18 +3796,10 @@ defineFunction({
3803
3796
  argTypes: ["primitive"]
3804
3797
  },
3805
3798
  handler: (context, args) => {
3806
- // \left case below triggers parsing of \right in
3807
- // `const right = parser.parseFunction();`
3808
- // uses this return value.
3809
- const color = context.parser.gullet.macros.get("\\current@color");
3810
- if (color && typeof color !== "string") {
3811
- throw new ParseError("\\current@color set to non-string in \\right");
3812
- }
3813
3799
  return {
3814
3800
  type: "leftright-right",
3815
3801
  mode: context.parser.mode,
3816
- delim: checkDelimiter(args[0], context).text,
3817
- color // undefined if not set via \color
3802
+ delim: checkDelimiter(args[0], context).text
3818
3803
  };
3819
3804
  }
3820
3805
  });
@@ -3832,8 +3817,26 @@ defineFunction({
3832
3817
  const parser = context.parser;
3833
3818
  // Parse out the implicit body
3834
3819
  ++parser.leftrightDepth;
3835
- // parseExpression stops before '\\right'
3836
- const body = parser.parseExpression(false);
3820
+ // parseExpression stops before '\\right' or `\\middle`
3821
+ let body = parser.parseExpression(false, null, true);
3822
+ let nextToken = parser.fetch();
3823
+ while (nextToken.text === "\\middle") {
3824
+ // `\middle`, from the ε-TeX package, ends one group and starts another group.
3825
+ // We had to parse this expression with `breakOnMiddle` enabled in order
3826
+ // to get TeX-compliant parsing of \over.
3827
+ // But we do not want, at this point, to end on \middle, so continue
3828
+ // to parse until we fetch a `\right`.
3829
+ parser.consume();
3830
+ const middle = parser.fetch().text;
3831
+ if (!symbols.math[middle]) {
3832
+ throw new ParseError(`Invalid delimiter '${middle}' after '\\middle'`);
3833
+ }
3834
+ checkDelimiter({ type: "atom", mode: "math", text: middle }, { funcName: "\\middle" });
3835
+ body.push({ type: "middle", mode: "math", delim: middle });
3836
+ parser.consume();
3837
+ body = body.concat(parser.parseExpression(false, null, true));
3838
+ nextToken = parser.fetch();
3839
+ }
3837
3840
  --parser.leftrightDepth;
3838
3841
  // Check the next token
3839
3842
  parser.expect("\\right", false);
@@ -3843,8 +3846,7 @@ defineFunction({
3843
3846
  mode: parser.mode,
3844
3847
  body,
3845
3848
  left: delim.text,
3846
- right: right.delim,
3847
- rightColor: right.color
3849
+ right: right.delim
3848
3850
  };
3849
3851
  },
3850
3852
  mathmlBuilder: (group, style) => {
@@ -3867,7 +3869,6 @@ defineFunction({
3867
3869
  if (group.right === "\u2216" || group.right.indexOf("arrow") > -1) {
3868
3870
  rightNode.setAttribute("stretchy", "true");
3869
3871
  }
3870
- if (group.rightColor) { rightNode.style.color = group.rightColor; }
3871
3872
  inner.push(rightNode);
3872
3873
 
3873
3874
  return makeRow(inner);
@@ -5210,7 +5211,7 @@ defineFunction({
5210
5211
  },
5211
5212
  handler: ({ parser, funcName, breakOnTokenText }, args) => {
5212
5213
  const { mode } = parser;
5213
- const body = parser.parseExpression(true, breakOnTokenText);
5214
+ const body = parser.parseExpression(true, breakOnTokenText, true);
5214
5215
  const fontStyle = `math${funcName.slice(1)}`;
5215
5216
 
5216
5217
  return {
@@ -6171,6 +6172,9 @@ function mathmlBuilder$3(group, style) {
6171
6172
  if (group.isCharacterBox || inner[0].type === "mathord") {
6172
6173
  node = inner[0];
6173
6174
  node.type = "mi";
6175
+ if (node.children.length === 1 && node.children[0].text && node.children[0].text === "∇") {
6176
+ node.setAttribute("mathvariant", "normal");
6177
+ }
6174
6178
  } else {
6175
6179
  node = new mathMLTree.MathNode("mi", inner);
6176
6180
  }
@@ -7158,6 +7162,28 @@ defineFunction({
7158
7162
  }
7159
7163
  });
7160
7164
 
7165
+ defineFunction({
7166
+ type: "reflect",
7167
+ names: ["\\reflectbox"],
7168
+ props: {
7169
+ numArgs: 1,
7170
+ argTypes: ["hbox"],
7171
+ allowedInText: true
7172
+ },
7173
+ handler({ parser }, args) {
7174
+ return {
7175
+ type: "reflect",
7176
+ mode: parser.mode,
7177
+ body: args[0]
7178
+ };
7179
+ },
7180
+ mathmlBuilder(group, style) {
7181
+ const node = buildGroup$1(group.body, style);
7182
+ node.style.transform = "scaleX(-1)";
7183
+ return node
7184
+ }
7185
+ });
7186
+
7161
7187
  defineFunction({
7162
7188
  type: "internal",
7163
7189
  names: ["\\relax"],
@@ -7263,7 +7289,7 @@ defineFunction({
7263
7289
  // eslint-disable-next-line no-console
7264
7290
  console.log(`Temml strict-mode warning: Command ${funcName} is invalid in math mode.`);
7265
7291
  }
7266
- const body = parser.parseExpression(false, breakOnTokenText);
7292
+ const body = parser.parseExpression(false, breakOnTokenText, true);
7267
7293
  return {
7268
7294
  type: "sizing",
7269
7295
  mode: parser.mode,
@@ -7396,7 +7422,7 @@ defineFunction({
7396
7422
  },
7397
7423
  handler({ breakOnTokenText, funcName, parser }, args) {
7398
7424
  // parse out the implicit body
7399
- const body = parser.parseExpression(true, breakOnTokenText);
7425
+ const body = parser.parseExpression(true, breakOnTokenText, true);
7400
7426
 
7401
7427
  const scriptLevel = funcName.slice(1, funcName.length - 5);
7402
7428
  return {
@@ -7827,22 +7853,22 @@ const offset = Object.freeze({
7827
7853
  "sans-serif-bold-italic": ch => { return 0x1D5F5 },
7828
7854
  "monospace": ch => { return 0x1D629 }
7829
7855
  },
7830
- upperCaseGreek: { // A-Ω
7856
+ upperCaseGreek: { // A-Ω
7831
7857
  "normal": ch => { return 0 },
7832
- "bold": ch => { return ch === "∇" ? 0x1B4BA : 0x1D317 },
7833
- "italic": ch => { return ch === "∇" ? 0x1B4F4 : 0x1D351 },
7858
+ "bold": ch => { return 0x1D317 },
7859
+ "italic": ch => { return 0x1D351 },
7834
7860
  // \boldsymbol actually returns upright bold for upperCaseGreek
7835
- "bold-italic": ch => { return ch === "∇" ? 0x1B4BA : 0x1D317 },
7861
+ "bold-italic": ch => { return 0x1D317 },
7836
7862
  "script": ch => { return 0 },
7837
7863
  "script-bold": ch => { return 0 },
7838
7864
  "fraktur": ch => { return 0 },
7839
7865
  "fraktur-bold": ch => { return 0 },
7840
7866
  "double-struck": ch => { return 0 },
7841
7867
  // Unicode has no code points for regular-weight san-serif Greek. Use bold.
7842
- "sans-serif": ch => { return ch === "∇" ? 0x1B568 : 0x1D3C5 },
7843
- "sans-serif-bold": ch => { return ch === "∇" ? 0x1B568 : 0x1D3C5 },
7868
+ "sans-serif": ch => { return 0x1D3C5 },
7869
+ "sans-serif-bold": ch => { return 0x1D3C5 },
7844
7870
  "sans-serif-italic": ch => { return 0 },
7845
- "sans-serif-bold-italic": ch => { return ch === "∇" ? 0x1B5A2 : 0x1D3FF },
7871
+ "sans-serif-bold-italic": ch => { return 0x1D3FF },
7846
7872
  "monospace": ch => { return 0 }
7847
7873
  },
7848
7874
  lowerCaseGreek: { // α-ω
@@ -7902,7 +7928,7 @@ const variantChar = (ch, variant) => {
7902
7928
  ? "upperCaseLatin"
7903
7929
  : 0x60 < codePoint && codePoint < 0x7b
7904
7930
  ? "lowerCaseLatin"
7905
- : (0x390 < codePoint && codePoint < 0x3AA) || ch === "∇"
7931
+ : (0x390 < codePoint && codePoint < 0x3AA)
7906
7932
  ? "upperCaseGreek"
7907
7933
  : 0x3B0 < codePoint && codePoint < 0x3CA || ch === "\u03d5"
7908
7934
  ? "lowerCaseGreek"
@@ -8031,8 +8057,6 @@ defineFunctionBuilders({
8031
8057
  node = new mathMLTree.MathNode("mi", [text]);
8032
8058
  if (text.text === origText && latinRegEx.test(origText)) {
8033
8059
  node.setAttribute("mathvariant", "italic");
8034
- } else if (text.text === "∇" && variant === "normal") {
8035
- node.setAttribute("mathvariant", "normal");
8036
8060
  }
8037
8061
  }
8038
8062
  return node
@@ -8669,6 +8693,24 @@ defineMacro("\\char", function(context) {
8669
8693
  return `\\@char{${number}}`;
8670
8694
  });
8671
8695
 
8696
+ function recreateArgStr(context) {
8697
+ // Recreate the macro's original argument string from the array of parse tokens.
8698
+ const tokens = context.consumeArgs(1)[0];
8699
+ let str = "";
8700
+ let expectedLoc = tokens[tokens.length - 1].loc.start;
8701
+ for (let i = tokens.length - 1; i >= 0; i--) {
8702
+ const actualLoc = tokens[i].loc.start;
8703
+ if (actualLoc > expectedLoc) {
8704
+ // context.consumeArgs has eaten a space.
8705
+ str += " ";
8706
+ expectedLoc = actualLoc;
8707
+ }
8708
+ str += tokens[i].text;
8709
+ expectedLoc += tokens[i].text.length;
8710
+ }
8711
+ return str
8712
+ }
8713
+
8672
8714
  // The Latin Modern font renders <mi>√</mi> at the wrong vertical alignment.
8673
8715
  // This macro provides a better rendering.
8674
8716
  defineMacro("\\surd", '\\sqrt{\\vphantom{|}}');
@@ -8676,10 +8718,6 @@ defineMacro("\\surd", '\\sqrt{\\vphantom{|}}');
8676
8718
  // See comment for \oplus in symbols.js.
8677
8719
  defineMacro("\u2295", "\\oplus");
8678
8720
 
8679
- // Per TeXbook p.122, "/" gets zero operator spacing.
8680
- // And MDN recommends using U+2044 instead of / for inline
8681
- defineMacro("/", "{\u2044}");
8682
-
8683
8721
  // Since Temml has no \par, ignore \long.
8684
8722
  defineMacro("\\long", "");
8685
8723
 
@@ -9057,6 +9095,11 @@ defineMacro("\\argmin", "\\DOTSB\\operatorname*{arg\\,min}");
9057
9095
  defineMacro("\\argmax", "\\DOTSB\\operatorname*{arg\\,max}");
9058
9096
  defineMacro("\\plim", "\\DOTSB\\operatorname*{plim}");
9059
9097
 
9098
+ //////////////////////////////////////////////////////////////////////
9099
+ // MnSymbol.sty
9100
+
9101
+ defineMacro("\\leftmodels", "\\mathop{\\reflectbox{$\\models$}}");
9102
+
9060
9103
  //////////////////////////////////////////////////////////////////////
9061
9104
  // braket.sty
9062
9105
  // http://ctan.math.washington.edu/tex-archive/macros/latex/contrib/braket/braket.pdf
@@ -9066,56 +9109,33 @@ defineMacro("\\ket", "\\mathinner{|{#1}\\rangle}");
9066
9109
  defineMacro("\\braket", "\\mathinner{\\langle{#1}\\rangle}");
9067
9110
  defineMacro("\\Bra", "\\left\\langle#1\\right|");
9068
9111
  defineMacro("\\Ket", "\\left|#1\\right\\rangle");
9069
- const braketHelper = (one) => (context) => {
9070
- const left = context.consumeArg().tokens;
9071
- const middle = context.consumeArg().tokens;
9072
- const middleDouble = context.consumeArg().tokens;
9073
- const right = context.consumeArg().tokens;
9074
- const oldMiddle = context.macros.get("|");
9075
- const oldMiddleDouble = context.macros.get("\\|");
9076
- context.macros.beginGroup();
9077
- const midMacro = (double) => (context) => {
9078
- if (one) {
9079
- // Only modify the first instance of | or \|
9080
- context.macros.set("|", oldMiddle);
9081
- if (middleDouble.length) {
9082
- context.macros.set("\\|", oldMiddleDouble);
9083
- }
9084
- }
9085
- let doubled = double;
9086
- if (!double && middleDouble.length) {
9087
- // Mimic \@ifnextchar
9088
- const nextToken = context.future();
9089
- if (nextToken.text === "|") {
9090
- context.popToken();
9091
- doubled = true;
9092
- }
9093
- }
9094
- return {
9095
- tokens: doubled ? middleDouble : middle,
9096
- numArgs: 0
9097
- };
9098
- };
9099
- context.macros.set("|", midMacro(false));
9100
- if (middleDouble.length) {
9101
- context.macros.set("\\|", midMacro(true));
9102
- }
9103
- const arg = context.consumeArg().tokens;
9104
- const expanded = context.expandTokens([...right, ...arg, ...left]); // reversed
9105
- context.macros.endGroup();
9106
- return {
9107
- tokens: expanded.reverse(),
9108
- numArgs: 0
9109
- };
9112
+ // A helper for \Braket and \Set
9113
+ const replaceVert = (argStr, match) => {
9114
+ const ch = match[0] === "|" ? "\\vert" : "\\Vert";
9115
+ const replaceStr = `}\\,\\middle${ch}\\,{`;
9116
+ return argStr.slice(0, match.index) + replaceStr + argStr.slice(match.index + match[0].length)
9110
9117
  };
9111
- defineMacro("\\bra@ket", braketHelper(false));
9112
- defineMacro("\\bra@set", braketHelper(true));
9113
- defineMacro("\\Braket", "\\bra@ket{\\left\\langle}" +
9114
- "{\\,\\middle\\vert\\,}{\\,\\middle\\vert\\,}{\\right\\rangle}");
9115
- defineMacro("\\Set", "\\bra@set{\\left\\{\\:}" +
9116
- "{\\;\\middle\\vert\\;}{\\;\\middle\\Vert\\;}{\\:\\right\\}}");
9117
- defineMacro("\\set", "\\bra@set{\\{\\,}{\\mid}{}{\\,\\}}");
9118
- // has no support for special || or \|
9118
+ defineMacro("\\Braket", function(context) {
9119
+ let argStr = recreateArgStr(context);
9120
+ const regEx = /\|\||\||\\\|/g;
9121
+ let match;
9122
+ while ((match = regEx.exec(argStr)) !== null) {
9123
+ argStr = replaceVert(argStr, match);
9124
+ }
9125
+ return "\\left\\langle{" + argStr + "}\\right\\rangle"
9126
+ });
9127
+ defineMacro("\\Set", function(context) {
9128
+ let argStr = recreateArgStr(context);
9129
+ const match = /\|\||\||\\\|/.exec(argStr);
9130
+ if (match) {
9131
+ argStr = replaceVert(argStr, match);
9132
+ }
9133
+ return "\\left\\{\\:{" + argStr + "}\\:\\right\\}"
9134
+ });
9135
+ defineMacro("\\set", function(context) {
9136
+ const argStr = recreateArgStr(context);
9137
+ return "\\{{" + argStr.replace(/\|/, "}\\mid{") + "}\\}"
9138
+ });
9119
9139
 
9120
9140
  //////////////////////////////////////////////////////////////////////
9121
9141
  // actuarialangle.dtx
@@ -12164,8 +12184,12 @@ class Parser {
12164
12184
  * `breakOnTokenText`: The text of the token that the expression should end
12165
12185
  * with, or `null` if something else should end the
12166
12186
  * expression.
12187
+ *
12188
+ * `breakOnMiddle`: \color, \over, and old styling functions work on an implicit group.
12189
+ * These groups end just before the usual tokens, but they also
12190
+ * end just before `\middle`.
12167
12191
  */
12168
- parseExpression(breakOnInfix, breakOnTokenText) {
12192
+ parseExpression(breakOnInfix, breakOnTokenText, breakOnMiddle) {
12169
12193
  const body = [];
12170
12194
  // Keep adding atoms to the body until we can't parse any more atoms (either
12171
12195
  // we reached the end, a }, or a \right)
@@ -12181,6 +12205,9 @@ class Parser {
12181
12205
  if (breakOnTokenText && lex.text === breakOnTokenText) {
12182
12206
  break;
12183
12207
  }
12208
+ if (breakOnMiddle && lex.text === "\\middle") {
12209
+ break
12210
+ }
12184
12211
  if (breakOnInfix && functions[lex.text] && functions[lex.text].infix) {
12185
12212
  break;
12186
12213
  }
@@ -13159,7 +13186,7 @@ class Style {
13159
13186
  * https://mit-license.org/
13160
13187
  */
13161
13188
 
13162
- const version = "0.10.20";
13189
+ const version = "0.10.22";
13163
13190
 
13164
13191
  function postProcess(block) {
13165
13192
  const labelMap = {};
@@ -14,7 +14,7 @@
14
14
  * https://mit-license.org/
15
15
  */
16
16
 
17
- const version = "0.10.20";
17
+ const version = "0.10.22";
18
18
 
19
19
  function postProcess(block) {
20
20
  const labelMap = {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "temml",
3
- "version": "0.10.20",
3
+ "version": "0.10.22",
4
4
  "description": "TeX to MathML conversion in JavaScript.",
5
5
  "main": "dist/temml.js",
6
6
  "engines": {
package/src/Parser.js CHANGED
@@ -179,8 +179,12 @@ export default class Parser {
179
179
  * `breakOnTokenText`: The text of the token that the expression should end
180
180
  * with, or `null` if something else should end the
181
181
  * expression.
182
+ *
183
+ * `breakOnMiddle`: \color, \over, and old styling functions work on an implicit group.
184
+ * These groups end just before the usual tokens, but they also
185
+ * end just before `\middle`.
182
186
  */
183
- parseExpression(breakOnInfix, breakOnTokenText) {
187
+ parseExpression(breakOnInfix, breakOnTokenText, breakOnMiddle) {
184
188
  const body = [];
185
189
  // Keep adding atoms to the body until we can't parse any more atoms (either
186
190
  // we reached the end, a }, or a \right)
@@ -196,6 +200,9 @@ export default class Parser {
196
200
  if (breakOnTokenText && lex.text === breakOnTokenText) {
197
201
  break;
198
202
  }
203
+ if (breakOnMiddle && lex.text === "\\middle") {
204
+ break
205
+ }
199
206
  if (breakOnInfix && functions[lex.text] && functions[lex.text].infix) {
200
207
  break;
201
208
  }
@@ -201,7 +201,7 @@ defineFunction({
201
201
  allowedInText: true,
202
202
  argTypes: ["raw", "raw"]
203
203
  },
204
- handler({ parser, token }, args, optArgs) {
204
+ handler({ parser, breakOnTokenText, token }, args, optArgs) {
205
205
  const model = optArgs[0] && assertNodeType(optArgs[0], "raw").string
206
206
  let color = ""
207
207
  if (model) {
@@ -211,15 +211,8 @@ defineFunction({
211
211
  color = validateColor(assertNodeType(args[0], "raw").string, parser.gullet.macros, token)
212
212
  }
213
213
 
214
- // Set macro \current@color in current namespace to store the current
215
- // color, mimicking the behavior of color.sty.
216
- // This is currently used just to correctly color a \right
217
- // that follows a \color command.
218
- parser.gullet.macros.set("\\current@color", color)
219
-
220
214
  // Parse out the implicit body that should be colored.
221
- // Since \color nodes should not be nested, break on \color.
222
- const body = parser.parseExpression(true, "\\color")
215
+ const body = parser.parseExpression(true, breakOnTokenText, true)
223
216
 
224
217
  return {
225
218
  type: "color",
@@ -4,6 +4,7 @@ import ParseError from "../ParseError";
4
4
  import { assertNodeType, checkSymbolNodeType } from "../parseNode";
5
5
 
6
6
  import * as mml from "../buildMathML";
7
+ import symbols from "../symbols";
7
8
 
8
9
  // Extra data needed for the delimiter handler down below
9
10
  export const delimiterSizes = {
@@ -113,17 +114,13 @@ const sizeToMaxHeight = [0, 1.2, 1.8, 2.4, 3.0];
113
114
 
114
115
  // Delimiter functions
115
116
  function checkDelimiter(delim, context) {
116
- if (delim.type === "ordgroup" && delim.body.length === 1 && delim.body[0].text === "\u2044") {
117
- // Recover "/" from the zero spacing group. (See macros.js)
118
- delim = { type: "textord", text: "/", mode: "math" }
119
- }
120
117
  const symDelim = checkSymbolNodeType(delim)
121
118
  if (symDelim && delimiters.includes(symDelim.text)) {
122
119
  // If a character is not in the MathML operator dictionary, it will not stretch.
123
120
  // Replace such characters w/characters that will stretch.
121
+ if (["/", "\u2044"].includes(symDelim.text)) { symDelim.text = "\u2215" }
124
122
  if (["<", "\\lt"].includes(symDelim.text)) { symDelim.text = "⟨" }
125
123
  if ([">", "\\gt"].includes(symDelim.text)) { symDelim.text = "⟩" }
126
- if (symDelim.text === "/") { symDelim.text = "\u2215" }
127
124
  if (symDelim.text === "\\backslash") { symDelim.text = "\u2216" }
128
125
  return symDelim;
129
126
  } else if (symDelim) {
@@ -211,18 +208,10 @@ defineFunction({
211
208
  argTypes: ["primitive"]
212
209
  },
213
210
  handler: (context, args) => {
214
- // \left case below triggers parsing of \right in
215
- // `const right = parser.parseFunction();`
216
- // uses this return value.
217
- const color = context.parser.gullet.macros.get("\\current@color");
218
- if (color && typeof color !== "string") {
219
- throw new ParseError("\\current@color set to non-string in \\right");
220
- }
221
211
  return {
222
212
  type: "leftright-right",
223
213
  mode: context.parser.mode,
224
- delim: checkDelimiter(args[0], context).text,
225
- color // undefined if not set via \color
214
+ delim: checkDelimiter(args[0], context).text
226
215
  };
227
216
  }
228
217
  });
@@ -240,8 +229,26 @@ defineFunction({
240
229
  const parser = context.parser;
241
230
  // Parse out the implicit body
242
231
  ++parser.leftrightDepth;
243
- // parseExpression stops before '\\right'
244
- const body = parser.parseExpression(false);
232
+ // parseExpression stops before '\\right' or `\\middle`
233
+ let body = parser.parseExpression(false, null, true)
234
+ let nextToken = parser.fetch()
235
+ while (nextToken.text === "\\middle") {
236
+ // `\middle`, from the ε-TeX package, ends one group and starts another group.
237
+ // We had to parse this expression with `breakOnMiddle` enabled in order
238
+ // to get TeX-compliant parsing of \over.
239
+ // But we do not want, at this point, to end on \middle, so continue
240
+ // to parse until we fetch a `\right`.
241
+ parser.consume()
242
+ const middle = parser.fetch().text
243
+ if (!symbols.math[middle]) {
244
+ throw new ParseError(`Invalid delimiter '${middle}' after '\\middle'`);
245
+ }
246
+ checkDelimiter({ type: "atom", mode: "math", text: middle }, { funcName: "\\middle" })
247
+ body.push({ type: "middle", mode: "math", delim: middle })
248
+ parser.consume()
249
+ body = body.concat(parser.parseExpression(false, null, true))
250
+ nextToken = parser.fetch()
251
+ }
245
252
  --parser.leftrightDepth;
246
253
  // Check the next token
247
254
  parser.expect("\\right", false);
@@ -251,8 +258,7 @@ defineFunction({
251
258
  mode: parser.mode,
252
259
  body,
253
260
  left: delim.text,
254
- right: right.delim,
255
- rightColor: right.color
261
+ right: right.delim
256
262
  };
257
263
  },
258
264
  mathmlBuilder: (group, style) => {
@@ -275,7 +281,6 @@ defineFunction({
275
281
  if (group.right === "\u2216" || group.right.indexOf("arrow") > -1) {
276
282
  rightNode.setAttribute("stretchy", "true")
277
283
  }
278
- if (group.rightColor) { rightNode.style.color = group.rightColor }
279
284
  inner.push(rightNode)
280
285
 
281
286
  return mml.makeRow(inner);
@@ -103,7 +103,7 @@ defineFunction({
103
103
  },
104
104
  handler: ({ parser, funcName, breakOnTokenText }, args) => {
105
105
  const { mode } = parser;
106
- const body = parser.parseExpression(true, breakOnTokenText);
106
+ const body = parser.parseExpression(true, breakOnTokenText, true);
107
107
  const fontStyle = `math${funcName.slice(1)}`;
108
108
 
109
109
  return {
@@ -23,6 +23,9 @@ function mathmlBuilder(group, style) {
23
23
  if (group.isCharacterBox || inner[0].type === "mathord") {
24
24
  node = inner[0];
25
25
  node.type = "mi";
26
+ if (node.children.length === 1 && node.children[0].text && node.children[0].text === "∇") {
27
+ node.setAttribute("mathvariant", "normal")
28
+ }
26
29
  } else {
27
30
  node = new mathMLTree.MathNode("mi", inner);
28
31
  }
@@ -0,0 +1,24 @@
1
+ import defineFunction from "../defineFunction"
2
+ import * as mml from "../buildMathML"
3
+
4
+ defineFunction({
5
+ type: "reflect",
6
+ names: ["\\reflectbox"],
7
+ props: {
8
+ numArgs: 1,
9
+ argTypes: ["hbox"],
10
+ allowedInText: true
11
+ },
12
+ handler({ parser }, args) {
13
+ return {
14
+ type: "reflect",
15
+ mode: parser.mode,
16
+ body: args[0]
17
+ };
18
+ },
19
+ mathmlBuilder(group, style) {
20
+ const node = mml.buildGroup(group.body, style)
21
+ node.style.transform = "scaleX(-1)"
22
+ return node
23
+ }
24
+ })
@@ -44,7 +44,7 @@ defineFunction({
44
44
  // eslint-disable-next-line no-console
45
45
  console.log(`Temml strict-mode warning: Command ${funcName} is invalid in math mode.`)
46
46
  }
47
- const body = parser.parseExpression(false, breakOnTokenText);
47
+ const body = parser.parseExpression(false, breakOnTokenText, true);
48
48
  return {
49
49
  type: "sizing",
50
50
  mode: parser.mode,
@@ -26,7 +26,7 @@ defineFunction({
26
26
  },
27
27
  handler({ breakOnTokenText, funcName, parser }, args) {
28
28
  // parse out the implicit body
29
- const body = parser.parseExpression(true, breakOnTokenText);
29
+ const body = parser.parseExpression(true, breakOnTokenText, true);
30
30
 
31
31
  const scriptLevel = funcName.slice(1, funcName.length - 5);
32
32
  return {
@@ -90,8 +90,6 @@ defineFunctionBuilders({
90
90
  node = new mathMLTree.MathNode("mi", [text])
91
91
  if (text.text === origText && latinRegEx.test(origText)) {
92
92
  node.setAttribute("mathvariant", "italic")
93
- } else if (text.text === "∇" && variant === "normal") {
94
- node.setAttribute("mathvariant", "normal")
95
93
  }
96
94
  }
97
95
  return node