temml 0.10.21 → 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;
@@ -3256,7 +3260,7 @@ defineFunction({
3256
3260
  }
3257
3261
 
3258
3262
  // Parse out the implicit body that should be colored.
3259
- const body = parser.parseExpression(true, breakOnTokenText);
3263
+ const body = parser.parseExpression(true, breakOnTokenText, true);
3260
3264
 
3261
3265
  return {
3262
3266
  type: "color",
@@ -3698,17 +3702,13 @@ const sizeToMaxHeight = [0, 1.2, 1.8, 2.4, 3.0];
3698
3702
 
3699
3703
  // Delimiter functions
3700
3704
  function checkDelimiter(delim, context) {
3701
- if (delim.type === "ordgroup" && delim.body.length === 1 && delim.body[0].text === "\u2044") {
3702
- // Recover "/" from the zero spacing group. (See macros.js)
3703
- delim = { type: "textord", text: "/", mode: "math" };
3704
- }
3705
3705
  const symDelim = checkSymbolNodeType(delim);
3706
3706
  if (symDelim && delimiters.includes(symDelim.text)) {
3707
3707
  // If a character is not in the MathML operator dictionary, it will not stretch.
3708
3708
  // Replace such characters w/characters that will stretch.
3709
+ if (["/", "\u2044"].includes(symDelim.text)) { symDelim.text = "\u2215"; }
3709
3710
  if (["<", "\\lt"].includes(symDelim.text)) { symDelim.text = "⟨"; }
3710
3711
  if ([">", "\\gt"].includes(symDelim.text)) { symDelim.text = "⟩"; }
3711
- if (symDelim.text === "/") { symDelim.text = "\u2215"; }
3712
3712
  if (symDelim.text === "\\backslash") { symDelim.text = "\u2216"; }
3713
3713
  return symDelim;
3714
3714
  } else if (symDelim) {
@@ -3817,8 +3817,26 @@ defineFunction({
3817
3817
  const parser = context.parser;
3818
3818
  // Parse out the implicit body
3819
3819
  ++parser.leftrightDepth;
3820
- // parseExpression stops before '\\right'
3821
- 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
+ }
3822
3840
  --parser.leftrightDepth;
3823
3841
  // Check the next token
3824
3842
  parser.expect("\\right", false);
@@ -5193,7 +5211,7 @@ defineFunction({
5193
5211
  },
5194
5212
  handler: ({ parser, funcName, breakOnTokenText }, args) => {
5195
5213
  const { mode } = parser;
5196
- const body = parser.parseExpression(true, breakOnTokenText);
5214
+ const body = parser.parseExpression(true, breakOnTokenText, true);
5197
5215
  const fontStyle = `math${funcName.slice(1)}`;
5198
5216
 
5199
5217
  return {
@@ -6154,6 +6172,9 @@ function mathmlBuilder$3(group, style) {
6154
6172
  if (group.isCharacterBox || inner[0].type === "mathord") {
6155
6173
  node = inner[0];
6156
6174
  node.type = "mi";
6175
+ if (node.children.length === 1 && node.children[0].text && node.children[0].text === "∇") {
6176
+ node.setAttribute("mathvariant", "normal");
6177
+ }
6157
6178
  } else {
6158
6179
  node = new mathMLTree.MathNode("mi", inner);
6159
6180
  }
@@ -7141,6 +7162,28 @@ defineFunction({
7141
7162
  }
7142
7163
  });
7143
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
+
7144
7187
  defineFunction({
7145
7188
  type: "internal",
7146
7189
  names: ["\\relax"],
@@ -7246,7 +7289,7 @@ defineFunction({
7246
7289
  // eslint-disable-next-line no-console
7247
7290
  console.log(`Temml strict-mode warning: Command ${funcName} is invalid in math mode.`);
7248
7291
  }
7249
- const body = parser.parseExpression(false, breakOnTokenText);
7292
+ const body = parser.parseExpression(false, breakOnTokenText, true);
7250
7293
  return {
7251
7294
  type: "sizing",
7252
7295
  mode: parser.mode,
@@ -7379,7 +7422,7 @@ defineFunction({
7379
7422
  },
7380
7423
  handler({ breakOnTokenText, funcName, parser }, args) {
7381
7424
  // parse out the implicit body
7382
- const body = parser.parseExpression(true, breakOnTokenText);
7425
+ const body = parser.parseExpression(true, breakOnTokenText, true);
7383
7426
 
7384
7427
  const scriptLevel = funcName.slice(1, funcName.length - 5);
7385
7428
  return {
@@ -7810,22 +7853,22 @@ const offset = Object.freeze({
7810
7853
  "sans-serif-bold-italic": ch => { return 0x1D5F5 },
7811
7854
  "monospace": ch => { return 0x1D629 }
7812
7855
  },
7813
- upperCaseGreek: { // A-Ω
7856
+ upperCaseGreek: { // A-Ω
7814
7857
  "normal": ch => { return 0 },
7815
- "bold": ch => { return ch === "∇" ? 0x1B4BA : 0x1D317 },
7816
- "italic": ch => { return ch === "∇" ? 0x1B4F4 : 0x1D351 },
7858
+ "bold": ch => { return 0x1D317 },
7859
+ "italic": ch => { return 0x1D351 },
7817
7860
  // \boldsymbol actually returns upright bold for upperCaseGreek
7818
- "bold-italic": ch => { return ch === "∇" ? 0x1B4BA : 0x1D317 },
7861
+ "bold-italic": ch => { return 0x1D317 },
7819
7862
  "script": ch => { return 0 },
7820
7863
  "script-bold": ch => { return 0 },
7821
7864
  "fraktur": ch => { return 0 },
7822
7865
  "fraktur-bold": ch => { return 0 },
7823
7866
  "double-struck": ch => { return 0 },
7824
7867
  // Unicode has no code points for regular-weight san-serif Greek. Use bold.
7825
- "sans-serif": ch => { return ch === "∇" ? 0x1B568 : 0x1D3C5 },
7826
- "sans-serif-bold": ch => { return ch === "∇" ? 0x1B568 : 0x1D3C5 },
7868
+ "sans-serif": ch => { return 0x1D3C5 },
7869
+ "sans-serif-bold": ch => { return 0x1D3C5 },
7827
7870
  "sans-serif-italic": ch => { return 0 },
7828
- "sans-serif-bold-italic": ch => { return ch === "∇" ? 0x1B5A2 : 0x1D3FF },
7871
+ "sans-serif-bold-italic": ch => { return 0x1D3FF },
7829
7872
  "monospace": ch => { return 0 }
7830
7873
  },
7831
7874
  lowerCaseGreek: { // α-ω
@@ -7885,7 +7928,7 @@ const variantChar = (ch, variant) => {
7885
7928
  ? "upperCaseLatin"
7886
7929
  : 0x60 < codePoint && codePoint < 0x7b
7887
7930
  ? "lowerCaseLatin"
7888
- : (0x390 < codePoint && codePoint < 0x3AA) || ch === "∇"
7931
+ : (0x390 < codePoint && codePoint < 0x3AA)
7889
7932
  ? "upperCaseGreek"
7890
7933
  : 0x3B0 < codePoint && codePoint < 0x3CA || ch === "\u03d5"
7891
7934
  ? "lowerCaseGreek"
@@ -8014,8 +8057,6 @@ defineFunctionBuilders({
8014
8057
  node = new mathMLTree.MathNode("mi", [text]);
8015
8058
  if (text.text === origText && latinRegEx.test(origText)) {
8016
8059
  node.setAttribute("mathvariant", "italic");
8017
- } else if (text.text === "∇" && variant === "normal") {
8018
- node.setAttribute("mathvariant", "normal");
8019
8060
  }
8020
8061
  }
8021
8062
  return node
@@ -8652,6 +8693,24 @@ defineMacro("\\char", function(context) {
8652
8693
  return `\\@char{${number}}`;
8653
8694
  });
8654
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
+
8655
8714
  // The Latin Modern font renders <mi>√</mi> at the wrong vertical alignment.
8656
8715
  // This macro provides a better rendering.
8657
8716
  defineMacro("\\surd", '\\sqrt{\\vphantom{|}}');
@@ -8659,10 +8718,6 @@ defineMacro("\\surd", '\\sqrt{\\vphantom{|}}');
8659
8718
  // See comment for \oplus in symbols.js.
8660
8719
  defineMacro("\u2295", "\\oplus");
8661
8720
 
8662
- // Per TeXbook p.122, "/" gets zero operator spacing.
8663
- // And MDN recommends using U+2044 instead of / for inline
8664
- defineMacro("/", "{\u2044}");
8665
-
8666
8721
  // Since Temml has no \par, ignore \long.
8667
8722
  defineMacro("\\long", "");
8668
8723
 
@@ -9040,6 +9095,11 @@ defineMacro("\\argmin", "\\DOTSB\\operatorname*{arg\\,min}");
9040
9095
  defineMacro("\\argmax", "\\DOTSB\\operatorname*{arg\\,max}");
9041
9096
  defineMacro("\\plim", "\\DOTSB\\operatorname*{plim}");
9042
9097
 
9098
+ //////////////////////////////////////////////////////////////////////
9099
+ // MnSymbol.sty
9100
+
9101
+ defineMacro("\\leftmodels", "\\mathop{\\reflectbox{$\\models$}}");
9102
+
9043
9103
  //////////////////////////////////////////////////////////////////////
9044
9104
  // braket.sty
9045
9105
  // http://ctan.math.washington.edu/tex-archive/macros/latex/contrib/braket/braket.pdf
@@ -9049,56 +9109,33 @@ defineMacro("\\ket", "\\mathinner{|{#1}\\rangle}");
9049
9109
  defineMacro("\\braket", "\\mathinner{\\langle{#1}\\rangle}");
9050
9110
  defineMacro("\\Bra", "\\left\\langle#1\\right|");
9051
9111
  defineMacro("\\Ket", "\\left|#1\\right\\rangle");
9052
- const braketHelper = (one) => (context) => {
9053
- const left = context.consumeArg().tokens;
9054
- const middle = context.consumeArg().tokens;
9055
- const middleDouble = context.consumeArg().tokens;
9056
- const right = context.consumeArg().tokens;
9057
- const oldMiddle = context.macros.get("|");
9058
- const oldMiddleDouble = context.macros.get("\\|");
9059
- context.macros.beginGroup();
9060
- const midMacro = (double) => (context) => {
9061
- if (one) {
9062
- // Only modify the first instance of | or \|
9063
- context.macros.set("|", oldMiddle);
9064
- if (middleDouble.length) {
9065
- context.macros.set("\\|", oldMiddleDouble);
9066
- }
9067
- }
9068
- let doubled = double;
9069
- if (!double && middleDouble.length) {
9070
- // Mimic \@ifnextchar
9071
- const nextToken = context.future();
9072
- if (nextToken.text === "|") {
9073
- context.popToken();
9074
- doubled = true;
9075
- }
9076
- }
9077
- return {
9078
- tokens: doubled ? middleDouble : middle,
9079
- numArgs: 0
9080
- };
9081
- };
9082
- context.macros.set("|", midMacro(false));
9083
- if (middleDouble.length) {
9084
- context.macros.set("\\|", midMacro(true));
9085
- }
9086
- const arg = context.consumeArg().tokens;
9087
- const expanded = context.expandTokens([...right, ...arg, ...left]); // reversed
9088
- context.macros.endGroup();
9089
- return {
9090
- tokens: expanded.reverse(),
9091
- numArgs: 0
9092
- };
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)
9093
9117
  };
9094
- defineMacro("\\bra@ket", braketHelper(false));
9095
- defineMacro("\\bra@set", braketHelper(true));
9096
- defineMacro("\\Braket", "\\bra@ket{\\left\\langle}" +
9097
- "{\\,\\middle\\vert\\,}{\\,\\middle\\vert\\,}{\\right\\rangle}");
9098
- defineMacro("\\Set", "\\bra@set{\\left\\{\\:}" +
9099
- "{\\;\\middle\\vert\\;}{\\;\\middle\\Vert\\;}{\\:\\right\\}}");
9100
- defineMacro("\\set", "\\bra@set{\\{\\,}{\\mid}{}{\\,\\}}");
9101
- // 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
+ });
9102
9139
 
9103
9140
  //////////////////////////////////////////////////////////////////////
9104
9141
  // actuarialangle.dtx
@@ -12147,8 +12184,12 @@ class Parser {
12147
12184
  * `breakOnTokenText`: The text of the token that the expression should end
12148
12185
  * with, or `null` if something else should end the
12149
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`.
12150
12191
  */
12151
- parseExpression(breakOnInfix, breakOnTokenText) {
12192
+ parseExpression(breakOnInfix, breakOnTokenText, breakOnMiddle) {
12152
12193
  const body = [];
12153
12194
  // Keep adding atoms to the body until we can't parse any more atoms (either
12154
12195
  // we reached the end, a }, or a \right)
@@ -12164,6 +12205,9 @@ class Parser {
12164
12205
  if (breakOnTokenText && lex.text === breakOnTokenText) {
12165
12206
  break;
12166
12207
  }
12208
+ if (breakOnMiddle && lex.text === "\\middle") {
12209
+ break
12210
+ }
12167
12211
  if (breakOnInfix && functions[lex.text] && functions[lex.text].infix) {
12168
12212
  break;
12169
12213
  }
@@ -13142,7 +13186,7 @@ class Style {
13142
13186
  * https://mit-license.org/
13143
13187
  */
13144
13188
 
13145
- const version = "0.10.21";
13189
+ const version = "0.10.22";
13146
13190
 
13147
13191
  function postProcess(block) {
13148
13192
  const labelMap = {};
@@ -14,7 +14,7 @@
14
14
  * https://mit-license.org/
15
15
  */
16
16
 
17
- const version = "0.10.21";
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.21",
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
  }
@@ -212,7 +212,7 @@ defineFunction({
212
212
  }
213
213
 
214
214
  // Parse out the implicit body that should be colored.
215
- const body = parser.parseExpression(true, breakOnTokenText)
215
+ const body = parser.parseExpression(true, breakOnTokenText, true)
216
216
 
217
217
  return {
218
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) {
@@ -232,8 +229,26 @@ defineFunction({
232
229
  const parser = context.parser;
233
230
  // Parse out the implicit body
234
231
  ++parser.leftrightDepth;
235
- // parseExpression stops before '\\right'
236
- 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
+ }
237
252
  --parser.leftrightDepth;
238
253
  // Check the next token
239
254
  parser.expect("\\right", false);
@@ -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
package/src/functions.js CHANGED
@@ -41,6 +41,7 @@ import "./functions/phantom";
41
41
  import "./functions/pmb";
42
42
  import "./functions/raise";
43
43
  import "./functions/ref";
44
+ import "./functions/reflect";
44
45
  import "./functions/relax";
45
46
  import "./functions/rule";
46
47
  import "./functions/sizing";
@@ -57,7 +57,8 @@ export default function setLineBreaks(expression, wrapMode, isDisplayMode) {
57
57
  continue
58
58
  }
59
59
  block.push(node);
60
- if (node.type && node.type === "mo" && node.children.length === 1) {
60
+ if (node.type && node.type === "mo" && node.children.length === 1 &&
61
+ !Object.hasOwn(node.attributes, "movablelimits")) {
61
62
  const ch = node.children[0].text
62
63
  if (openDelims.indexOf(ch) > -1) {
63
64
  level += 1
@@ -72,7 +73,7 @@ export default function setLineBreaks(expression, wrapMode, isDisplayMode) {
72
73
  mrows.push(element)
73
74
  block = [node];
74
75
  }
75
- } else if (level === 0 && wrapMode === "tex") {
76
+ } else if (level === 0 && wrapMode === "tex" && ch !== "∇") {
76
77
  // Check if the following node is a \nobreak text node, e.g. "~""
77
78
  const next = i < expression.length - 1 ? expression[i + 1] : null;
78
79
  let glueIsFreeOfNobreak = true;