temml 0.10.21 → 0.10.23

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);
@@ -1022,6 +1024,7 @@ defineSymbol(math, mathord, "\u2609", "\\astrosun", true);
1022
1024
  defineSymbol(math, mathord, "\u263c", "\\sun", true);
1023
1025
  defineSymbol(math, mathord, "\u263e", "\\leftmoon", true);
1024
1026
  defineSymbol(math, mathord, "\u263d", "\\rightmoon", true);
1027
+ defineSymbol(math, mathord, "\u2295", "\\Earth");
1025
1028
 
1026
1029
  // AMS Negated Binary Relations
1027
1030
  defineSymbol(math, rel, "\u226e", "\\nless", true);
@@ -1249,6 +1252,8 @@ defineSymbol(math, bin, "\u27d5", "\\leftouterjoin", true);
1249
1252
  defineSymbol(math, bin, "\u27d6", "\\rightouterjoin", true);
1250
1253
  defineSymbol(math, bin, "\u27d7", "\\fullouterjoin", true);
1251
1254
 
1255
+ defineSymbol(math, bin, "\u2238", "\\dotminus", true); // stix
1256
+
1252
1257
  // AMS Arrows
1253
1258
  // Note: unicode-math maps \u21e2 to their own function \rightdasharrow.
1254
1259
  // We'll map it to AMS function \dashrightarrow. It produces the same atom.
@@ -1406,6 +1411,7 @@ defineSymbol(math, mathord, "\u2aeb", "\\Bot");
1406
1411
  defineSymbol(math, bin, "\u2217", "\u2217", true);
1407
1412
  defineSymbol(math, bin, "+", "+");
1408
1413
  defineSymbol(math, bin, "*", "*");
1414
+ defineSymbol(math, bin, "\u2044", "/", true);
1409
1415
  defineSymbol(math, bin, "\u2044", "\u2044");
1410
1416
  defineSymbol(math, bin, "\u2212", "-", true);
1411
1417
  defineSymbol(math, bin, "\u22c5", "\\cdot", true);
@@ -1862,7 +1868,8 @@ function setLineBreaks(expression, wrapMode, isDisplayMode) {
1862
1868
  continue
1863
1869
  }
1864
1870
  block.push(node);
1865
- if (node.type && node.type === "mo" && node.children.length === 1) {
1871
+ if (node.type && node.type === "mo" && node.children.length === 1 &&
1872
+ !Object.hasOwn(node.attributes, "movablelimits")) {
1866
1873
  const ch = node.children[0].text;
1867
1874
  if (openDelims.indexOf(ch) > -1) {
1868
1875
  level += 1;
@@ -1877,7 +1884,7 @@ function setLineBreaks(expression, wrapMode, isDisplayMode) {
1877
1884
  mrows.push(element);
1878
1885
  block = [node];
1879
1886
  }
1880
- } else if (level === 0 && wrapMode === "tex") {
1887
+ } else if (level === 0 && wrapMode === "tex" && ch !== "∇") {
1881
1888
  // Check if the following node is a \nobreak text node, e.g. "~""
1882
1889
  const next = i < expression.length - 1 ? expression[i + 1] : null;
1883
1890
  let glueIsFreeOfNobreak = true;
@@ -2015,9 +2022,11 @@ const consolidateText = mrow => {
2015
2022
  };
2016
2023
 
2017
2024
  const numberRegEx$1 = /^[0-9]$/;
2018
- const isCommaOrDot = node => {
2019
- return (node.type === "atom" && node.text === ",") ||
2020
- (node.type === "textord" && node.text === ".")
2025
+ const isDotOrComma = (node, followingNode) => {
2026
+ return ((node.type === "textord" && node.text === ".") ||
2027
+ (node.type === "atom" && node.text === ",")) &&
2028
+ // Don't consolidate if there is a space after the comma.
2029
+ node.loc && followingNode.loc && node.loc.end === followingNode.loc.start
2021
2030
  };
2022
2031
  const consolidateNumbers = expression => {
2023
2032
  // Consolidate adjacent numbers. We want to return <mn>1,506.3</mn>,
@@ -2040,7 +2049,8 @@ const consolidateNumbers = expression => {
2040
2049
 
2041
2050
  // Determine if numeral groups are separated by a comma or dot.
2042
2051
  for (let i = nums.length - 1; i > 0; i--) {
2043
- if (nums[i - 1].end === nums[i].start - 2 && isCommaOrDot(expression[nums[i].start - 1])) {
2052
+ if (nums[i - 1].end === nums[i].start - 2 &&
2053
+ isDotOrComma(expression[nums[i].start - 1], expression[nums[i].start])) {
2044
2054
  // Merge the two groups.
2045
2055
  nums[i - 1].end = nums[i].end;
2046
2056
  nums.splice(i, 1);
@@ -3256,7 +3266,7 @@ defineFunction({
3256
3266
  }
3257
3267
 
3258
3268
  // Parse out the implicit body that should be colored.
3259
- const body = parser.parseExpression(true, breakOnTokenText);
3269
+ const body = parser.parseExpression(true, breakOnTokenText, true);
3260
3270
 
3261
3271
  return {
3262
3272
  type: "color",
@@ -3698,17 +3708,13 @@ const sizeToMaxHeight = [0, 1.2, 1.8, 2.4, 3.0];
3698
3708
 
3699
3709
  // Delimiter functions
3700
3710
  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
3711
  const symDelim = checkSymbolNodeType(delim);
3706
3712
  if (symDelim && delimiters.includes(symDelim.text)) {
3707
3713
  // If a character is not in the MathML operator dictionary, it will not stretch.
3708
3714
  // Replace such characters w/characters that will stretch.
3715
+ if (["/", "\u2044"].includes(symDelim.text)) { symDelim.text = "\u2215"; }
3709
3716
  if (["<", "\\lt"].includes(symDelim.text)) { symDelim.text = "⟨"; }
3710
3717
  if ([">", "\\gt"].includes(symDelim.text)) { symDelim.text = "⟩"; }
3711
- if (symDelim.text === "/") { symDelim.text = "\u2215"; }
3712
3718
  if (symDelim.text === "\\backslash") { symDelim.text = "\u2216"; }
3713
3719
  return symDelim;
3714
3720
  } else if (symDelim) {
@@ -3817,8 +3823,26 @@ defineFunction({
3817
3823
  const parser = context.parser;
3818
3824
  // Parse out the implicit body
3819
3825
  ++parser.leftrightDepth;
3820
- // parseExpression stops before '\\right'
3821
- const body = parser.parseExpression(false);
3826
+ // parseExpression stops before '\\right' or `\\middle`
3827
+ let body = parser.parseExpression(false, null, true);
3828
+ let nextToken = parser.fetch();
3829
+ while (nextToken.text === "\\middle") {
3830
+ // `\middle`, from the ε-TeX package, ends one group and starts another group.
3831
+ // We had to parse this expression with `breakOnMiddle` enabled in order
3832
+ // to get TeX-compliant parsing of \over.
3833
+ // But we do not want, at this point, to end on \middle, so continue
3834
+ // to parse until we fetch a `\right`.
3835
+ parser.consume();
3836
+ const middle = parser.fetch().text;
3837
+ if (!symbols.math[middle]) {
3838
+ throw new ParseError(`Invalid delimiter '${middle}' after '\\middle'`);
3839
+ }
3840
+ checkDelimiter({ type: "atom", mode: "math", text: middle }, { funcName: "\\middle" });
3841
+ body.push({ type: "middle", mode: "math", delim: middle });
3842
+ parser.consume();
3843
+ body = body.concat(parser.parseExpression(false, null, true));
3844
+ nextToken = parser.fetch();
3845
+ }
3822
3846
  --parser.leftrightDepth;
3823
3847
  // Check the next token
3824
3848
  parser.expect("\\right", false);
@@ -5092,6 +5116,21 @@ defineFunction({
5092
5116
  }
5093
5117
  });
5094
5118
 
5119
+ const isLongVariableName = (group, font) => {
5120
+ if (font !== "mathrm" || group.body.type !== "ordgroup" || group.body.body.length === 1) {
5121
+ return false
5122
+ }
5123
+ if (group.body.body[0].type !== "mathord") { return false }
5124
+ for (let i = 1; i < group.body.body.length; i++) {
5125
+ const parseNodeType = group.body.body[i].type;
5126
+ if (!(parseNodeType === "mathord" ||
5127
+ (parseNodeType === "textord" && !isNaN(group.body.body[i].text)))) {
5128
+ return false
5129
+ }
5130
+ }
5131
+ return true
5132
+ };
5133
+
5095
5134
  const mathmlBuilder$6 = (group, style) => {
5096
5135
  const font = group.font;
5097
5136
  const newStyle = style.withFont(font);
@@ -5103,6 +5142,20 @@ const mathmlBuilder$6 = (group, style) => {
5103
5142
  return mathGroup
5104
5143
  }
5105
5144
  // Check if it is possible to consolidate elements into a single <mi> element.
5145
+ if (isLongVariableName(group, font)) {
5146
+ // This is a \mathrm{…} group. It gets special treatment because symbolsOrd.js
5147
+ // wraps <mi> elements with <mrow>s to work around a Firefox bug.
5148
+ const mi = mathGroup.children[0].children[0];
5149
+ delete mi.attributes.mathvariant;
5150
+ for (let i = 1; i < mathGroup.children.length; i++) {
5151
+ mi.children[0].text += mathGroup.children[i].type === "mn"
5152
+ ? mathGroup.children[i].children[0].text
5153
+ : mathGroup.children[i].children[0].children[0].text;
5154
+ }
5155
+ // Wrap in a <mrow> to prevent the same Firefox bug.
5156
+ const bogus = new mathMLTree.MathNode("mtext", new mathMLTree.TextNode("\u200b"));
5157
+ return new mathMLTree.MathNode("mrow", [bogus, mi])
5158
+ }
5106
5159
  let canConsolidate = mathGroup.children[0].type === "mo";
5107
5160
  for (let i = 1; i < mathGroup.children.length; i++) {
5108
5161
  if (mathGroup.children[i].type === "mo" && font === "boldsymbol") {
@@ -5193,7 +5246,7 @@ defineFunction({
5193
5246
  },
5194
5247
  handler: ({ parser, funcName, breakOnTokenText }, args) => {
5195
5248
  const { mode } = parser;
5196
- const body = parser.parseExpression(true, breakOnTokenText);
5249
+ const body = parser.parseExpression(true, breakOnTokenText, true);
5197
5250
  const fontStyle = `math${funcName.slice(1)}`;
5198
5251
 
5199
5252
  return {
@@ -6154,6 +6207,9 @@ function mathmlBuilder$3(group, style) {
6154
6207
  if (group.isCharacterBox || inner[0].type === "mathord") {
6155
6208
  node = inner[0];
6156
6209
  node.type = "mi";
6210
+ if (node.children.length === 1 && node.children[0].text && node.children[0].text === "∇") {
6211
+ node.setAttribute("mathvariant", "normal");
6212
+ }
6157
6213
  } else {
6158
6214
  node = new mathMLTree.MathNode("mi", inner);
6159
6215
  }
@@ -7141,6 +7197,28 @@ defineFunction({
7141
7197
  }
7142
7198
  });
7143
7199
 
7200
+ defineFunction({
7201
+ type: "reflect",
7202
+ names: ["\\reflectbox"],
7203
+ props: {
7204
+ numArgs: 1,
7205
+ argTypes: ["hbox"],
7206
+ allowedInText: true
7207
+ },
7208
+ handler({ parser }, args) {
7209
+ return {
7210
+ type: "reflect",
7211
+ mode: parser.mode,
7212
+ body: args[0]
7213
+ };
7214
+ },
7215
+ mathmlBuilder(group, style) {
7216
+ const node = buildGroup$1(group.body, style);
7217
+ node.style.transform = "scaleX(-1)";
7218
+ return node
7219
+ }
7220
+ });
7221
+
7144
7222
  defineFunction({
7145
7223
  type: "internal",
7146
7224
  names: ["\\relax"],
@@ -7246,7 +7324,7 @@ defineFunction({
7246
7324
  // eslint-disable-next-line no-console
7247
7325
  console.log(`Temml strict-mode warning: Command ${funcName} is invalid in math mode.`);
7248
7326
  }
7249
- const body = parser.parseExpression(false, breakOnTokenText);
7327
+ const body = parser.parseExpression(false, breakOnTokenText, true);
7250
7328
  return {
7251
7329
  type: "sizing",
7252
7330
  mode: parser.mode,
@@ -7379,7 +7457,7 @@ defineFunction({
7379
7457
  },
7380
7458
  handler({ breakOnTokenText, funcName, parser }, args) {
7381
7459
  // parse out the implicit body
7382
- const body = parser.parseExpression(true, breakOnTokenText);
7460
+ const body = parser.parseExpression(true, breakOnTokenText, true);
7383
7461
 
7384
7462
  const scriptLevel = funcName.slice(1, funcName.length - 5);
7385
7463
  return {
@@ -7810,22 +7888,22 @@ const offset = Object.freeze({
7810
7888
  "sans-serif-bold-italic": ch => { return 0x1D5F5 },
7811
7889
  "monospace": ch => { return 0x1D629 }
7812
7890
  },
7813
- upperCaseGreek: { // A-Ω
7891
+ upperCaseGreek: { // A-Ω
7814
7892
  "normal": ch => { return 0 },
7815
- "bold": ch => { return ch === "∇" ? 0x1B4BA : 0x1D317 },
7816
- "italic": ch => { return ch === "∇" ? 0x1B4F4 : 0x1D351 },
7893
+ "bold": ch => { return 0x1D317 },
7894
+ "italic": ch => { return 0x1D351 },
7817
7895
  // \boldsymbol actually returns upright bold for upperCaseGreek
7818
- "bold-italic": ch => { return ch === "∇" ? 0x1B4BA : 0x1D317 },
7896
+ "bold-italic": ch => { return 0x1D317 },
7819
7897
  "script": ch => { return 0 },
7820
7898
  "script-bold": ch => { return 0 },
7821
7899
  "fraktur": ch => { return 0 },
7822
7900
  "fraktur-bold": ch => { return 0 },
7823
7901
  "double-struck": ch => { return 0 },
7824
7902
  // 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 },
7903
+ "sans-serif": ch => { return 0x1D3C5 },
7904
+ "sans-serif-bold": ch => { return 0x1D3C5 },
7827
7905
  "sans-serif-italic": ch => { return 0 },
7828
- "sans-serif-bold-italic": ch => { return ch === "∇" ? 0x1B5A2 : 0x1D3FF },
7906
+ "sans-serif-bold-italic": ch => { return 0x1D3FF },
7829
7907
  "monospace": ch => { return 0 }
7830
7908
  },
7831
7909
  lowerCaseGreek: { // α-ω
@@ -7885,7 +7963,7 @@ const variantChar = (ch, variant) => {
7885
7963
  ? "upperCaseLatin"
7886
7964
  : 0x60 < codePoint && codePoint < 0x7b
7887
7965
  ? "lowerCaseLatin"
7888
- : (0x390 < codePoint && codePoint < 0x3AA) || ch === "∇"
7966
+ : (0x390 < codePoint && codePoint < 0x3AA)
7889
7967
  ? "upperCaseGreek"
7890
7968
  : 0x3B0 < codePoint && codePoint < 0x3CA || ch === "\u03d5"
7891
7969
  ? "lowerCaseGreek"
@@ -8014,8 +8092,6 @@ defineFunctionBuilders({
8014
8092
  node = new mathMLTree.MathNode("mi", [text]);
8015
8093
  if (text.text === origText && latinRegEx.test(origText)) {
8016
8094
  node.setAttribute("mathvariant", "italic");
8017
- } else if (text.text === "∇" && variant === "normal") {
8018
- node.setAttribute("mathvariant", "normal");
8019
8095
  }
8020
8096
  }
8021
8097
  return node
@@ -8652,6 +8728,24 @@ defineMacro("\\char", function(context) {
8652
8728
  return `\\@char{${number}}`;
8653
8729
  });
8654
8730
 
8731
+ function recreateArgStr(context) {
8732
+ // Recreate the macro's original argument string from the array of parse tokens.
8733
+ const tokens = context.consumeArgs(1)[0];
8734
+ let str = "";
8735
+ let expectedLoc = tokens[tokens.length - 1].loc.start;
8736
+ for (let i = tokens.length - 1; i >= 0; i--) {
8737
+ const actualLoc = tokens[i].loc.start;
8738
+ if (actualLoc > expectedLoc) {
8739
+ // context.consumeArgs has eaten a space.
8740
+ str += " ";
8741
+ expectedLoc = actualLoc;
8742
+ }
8743
+ str += tokens[i].text;
8744
+ expectedLoc += tokens[i].text.length;
8745
+ }
8746
+ return str
8747
+ }
8748
+
8655
8749
  // The Latin Modern font renders <mi>√</mi> at the wrong vertical alignment.
8656
8750
  // This macro provides a better rendering.
8657
8751
  defineMacro("\\surd", '\\sqrt{\\vphantom{|}}');
@@ -8659,10 +8753,6 @@ defineMacro("\\surd", '\\sqrt{\\vphantom{|}}');
8659
8753
  // See comment for \oplus in symbols.js.
8660
8754
  defineMacro("\u2295", "\\oplus");
8661
8755
 
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
8756
  // Since Temml has no \par, ignore \long.
8667
8757
  defineMacro("\\long", "");
8668
8758
 
@@ -9040,6 +9130,11 @@ defineMacro("\\argmin", "\\DOTSB\\operatorname*{arg\\,min}");
9040
9130
  defineMacro("\\argmax", "\\DOTSB\\operatorname*{arg\\,max}");
9041
9131
  defineMacro("\\plim", "\\DOTSB\\operatorname*{plim}");
9042
9132
 
9133
+ //////////////////////////////////////////////////////////////////////
9134
+ // MnSymbol.sty
9135
+
9136
+ defineMacro("\\leftmodels", "\\mathop{\\reflectbox{$\\models$}}");
9137
+
9043
9138
  //////////////////////////////////////////////////////////////////////
9044
9139
  // braket.sty
9045
9140
  // http://ctan.math.washington.edu/tex-archive/macros/latex/contrib/braket/braket.pdf
@@ -9049,56 +9144,33 @@ defineMacro("\\ket", "\\mathinner{|{#1}\\rangle}");
9049
9144
  defineMacro("\\braket", "\\mathinner{\\langle{#1}\\rangle}");
9050
9145
  defineMacro("\\Bra", "\\left\\langle#1\\right|");
9051
9146
  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
- };
9147
+ // A helper for \Braket and \Set
9148
+ const replaceVert = (argStr, match) => {
9149
+ const ch = match[0] === "|" ? "\\vert" : "\\Vert";
9150
+ const replaceStr = `}\\,\\middle${ch}\\,{`;
9151
+ return argStr.slice(0, match.index) + replaceStr + argStr.slice(match.index + match[0].length)
9093
9152
  };
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 \|
9153
+ defineMacro("\\Braket", function(context) {
9154
+ let argStr = recreateArgStr(context);
9155
+ const regEx = /\|\||\||\\\|/g;
9156
+ let match;
9157
+ while ((match = regEx.exec(argStr)) !== null) {
9158
+ argStr = replaceVert(argStr, match);
9159
+ }
9160
+ return "\\left\\langle{" + argStr + "}\\right\\rangle"
9161
+ });
9162
+ defineMacro("\\Set", function(context) {
9163
+ let argStr = recreateArgStr(context);
9164
+ const match = /\|\||\||\\\|/.exec(argStr);
9165
+ if (match) {
9166
+ argStr = replaceVert(argStr, match);
9167
+ }
9168
+ return "\\left\\{\\:{" + argStr + "}\\:\\right\\}"
9169
+ });
9170
+ defineMacro("\\set", function(context) {
9171
+ const argStr = recreateArgStr(context);
9172
+ return "\\{{" + argStr.replace(/\|/, "}\\mid{") + "}\\}"
9173
+ });
9102
9174
 
9103
9175
  //////////////////////////////////////////////////////////////////////
9104
9176
  // actuarialangle.dtx
@@ -12147,8 +12219,12 @@ class Parser {
12147
12219
  * `breakOnTokenText`: The text of the token that the expression should end
12148
12220
  * with, or `null` if something else should end the
12149
12221
  * expression.
12222
+ *
12223
+ * `breakOnMiddle`: \color, \over, and old styling functions work on an implicit group.
12224
+ * These groups end just before the usual tokens, but they also
12225
+ * end just before `\middle`.
12150
12226
  */
12151
- parseExpression(breakOnInfix, breakOnTokenText) {
12227
+ parseExpression(breakOnInfix, breakOnTokenText, breakOnMiddle) {
12152
12228
  const body = [];
12153
12229
  // Keep adding atoms to the body until we can't parse any more atoms (either
12154
12230
  // we reached the end, a }, or a \right)
@@ -12164,6 +12240,9 @@ class Parser {
12164
12240
  if (breakOnTokenText && lex.text === breakOnTokenText) {
12165
12241
  break;
12166
12242
  }
12243
+ if (breakOnMiddle && lex.text === "\\middle") {
12244
+ break
12245
+ }
12167
12246
  if (breakOnInfix && functions[lex.text] && functions[lex.text].infix) {
12168
12247
  break;
12169
12248
  }
@@ -13142,7 +13221,7 @@ class Style {
13142
13221
  * https://mit-license.org/
13143
13222
  */
13144
13223
 
13145
- const version = "0.10.21";
13224
+ const version = "0.10.23";
13146
13225
 
13147
13226
  function postProcess(block) {
13148
13227
  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.23";
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.23",
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
  }
@@ -80,9 +80,11 @@ export const consolidateText = mrow => {
80
80
  }
81
81
 
82
82
  const numberRegEx = /^[0-9]$/
83
- const isCommaOrDot = node => {
84
- return (node.type === "atom" && node.text === ",") ||
85
- (node.type === "textord" && node.text === ".")
83
+ const isDotOrComma = (node, followingNode) => {
84
+ return ((node.type === "textord" && node.text === ".") ||
85
+ (node.type === "atom" && node.text === ",")) &&
86
+ // Don't consolidate if there is a space after the comma.
87
+ node.loc && followingNode.loc && node.loc.end === followingNode.loc.start
86
88
  }
87
89
  const consolidateNumbers = expression => {
88
90
  // Consolidate adjacent numbers. We want to return <mn>1,506.3</mn>,
@@ -105,7 +107,8 @@ const consolidateNumbers = expression => {
105
107
 
106
108
  // Determine if numeral groups are separated by a comma or dot.
107
109
  for (let i = nums.length - 1; i > 0; i--) {
108
- if (nums[i - 1].end === nums[i].start - 2 && isCommaOrDot(expression[nums[i].start - 1])) {
110
+ if (nums[i - 1].end === nums[i].start - 2 &&
111
+ isDotOrComma(expression[nums[i].start - 1], expression[nums[i].start])) {
109
112
  // Merge the two groups.
110
113
  nums[i - 1].end = nums[i].end
111
114
  nums.splice(i, 1)
@@ -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);
@@ -2,6 +2,21 @@ import defineFunction, { normalizeArgument } from "../defineFunction"
2
2
  import * as mml from "../buildMathML"
3
3
  import mathMLTree from "../mathMLTree"
4
4
 
5
+ const isLongVariableName = (group, font) => {
6
+ if (font !== "mathrm" || group.body.type !== "ordgroup" || group.body.body.length === 1) {
7
+ return false
8
+ }
9
+ if (group.body.body[0].type !== "mathord") { return false }
10
+ for (let i = 1; i < group.body.body.length; i++) {
11
+ const parseNodeType = group.body.body[i].type
12
+ if (!(parseNodeType === "mathord" ||
13
+ (parseNodeType === "textord" && !isNaN(group.body.body[i].text)))) {
14
+ return false
15
+ }
16
+ }
17
+ return true
18
+ }
19
+
5
20
  const mathmlBuilder = (group, style) => {
6
21
  const font = group.font
7
22
  const newStyle = style.withFont(font)
@@ -13,6 +28,20 @@ const mathmlBuilder = (group, style) => {
13
28
  return mathGroup
14
29
  }
15
30
  // Check if it is possible to consolidate elements into a single <mi> element.
31
+ if (isLongVariableName(group, font)) {
32
+ // This is a \mathrm{…} group. It gets special treatment because symbolsOrd.js
33
+ // wraps <mi> elements with <mrow>s to work around a Firefox bug.
34
+ const mi = mathGroup.children[0].children[0];
35
+ delete mi.attributes.mathvariant
36
+ for (let i = 1; i < mathGroup.children.length; i++) {
37
+ mi.children[0].text += mathGroup.children[i].type === "mn"
38
+ ? mathGroup.children[i].children[0].text
39
+ : mathGroup.children[i].children[0].children[0].text
40
+ }
41
+ // Wrap in a <mrow> to prevent the same Firefox bug.
42
+ const bogus = new mathMLTree.MathNode("mtext", new mathMLTree.TextNode("\u200b"))
43
+ return new mathMLTree.MathNode("mrow", [bogus, mi])
44
+ }
16
45
  let canConsolidate = mathGroup.children[0].type === "mo"
17
46
  for (let i = 1; i < mathGroup.children.length; i++) {
18
47
  if (mathGroup.children[i].type === "mo" && font === "boldsymbol") {
@@ -25,7 +54,7 @@ const mathmlBuilder = (group, style) => {
25
54
  }
26
55
  if (!canConsolidate) { return mathGroup }
27
56
  // Consolidate the <mi> elements.
28
- const mi = mathGroup.children[0]
57
+ const mi = mathGroup.children[0];
29
58
  for (let i = 1; i < mathGroup.children.length; i++) {
30
59
  mi.children.push(mathGroup.children[i].children[0])
31
60
  }
@@ -103,7 +132,7 @@ defineFunction({
103
132
  },
104
133
  handler: ({ parser, funcName, breakOnTokenText }, args) => {
105
134
  const { mode } = parser;
106
- const body = parser.parseExpression(true, breakOnTokenText);
135
+ const body = parser.parseExpression(true, breakOnTokenText, true);
107
136
  const fontStyle = `math${funcName.slice(1)}`;
108
137
 
109
138
  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
  }