temml 0.10.21 → 0.10.23

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);
@@ -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
  }