temml 0.10.20 → 0.10.22

Sign up to get free protection for your applications and to get access to all the features.
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