temml 0.13.1 → 0.13.3

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.
@@ -11,7 +11,7 @@
11
11
  * https://mit-license.org/
12
12
  */
13
13
 
14
- const version = "0.13.01";
14
+ const version = "0.13.3";
15
15
 
16
16
  function postProcess(block) {
17
17
  const labelMap = {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "temml",
3
- "version": "0.13.01",
3
+ "version": "0.13.3",
4
4
  "description": "TeX to MathML conversion in JavaScript.",
5
5
  "main": "dist/temml.js",
6
6
  "engines": {
@@ -33,8 +33,9 @@
33
33
  "eslint": "^9.11.1",
34
34
  "esm": "^3.2.25",
35
35
  "globals": "^15.9.0",
36
- "rollup": "^4.22.4",
37
- "terser": "^5.34.0"
36
+ "rollup": "^4.59.0",
37
+ "terser": "^5.34.0",
38
+ "ws": "^8.20.0"
38
39
  },
39
40
  "scripts": {
40
41
  "lint": "eslint temml.js src",
@@ -44,6 +45,7 @@
44
45
  "minify": "terser test/temml.js -o site/assets/temml.min.js -c -m && terser contrib/mhchem/mhchem.js -o site/assets/mhchem.min.js -c -m",
45
46
  "build": "rollup --config ./utils/rollupConfig.mjs && yarn minify && node utils/insertPlugins.js",
46
47
  "docs": "node utils/buildDocs.js",
47
- "dist": "yarn build && node ./utils/copyfiles.js"
48
+ "dist": "yarn build && node ./utils/copyfiles.js",
49
+ "serve": "node utils/server.cjs site/docs/en/mathml-status.html 8000"
48
50
  }
49
51
  }
@@ -9,6 +9,7 @@ import Lexer from "./Lexer";
9
9
  import { Token } from "./Token";
10
10
 
11
11
  import ParseError from "./ParseError";
12
+ import SourceLocation from "./SourceLocation";
12
13
  import Namespace from "./Namespace";
13
14
  import macros from "./macros";
14
15
 
@@ -118,7 +119,7 @@ export default class MacroExpander {
118
119
  this.pushToken(new Token("EOF", end.loc));
119
120
 
120
121
  this.pushTokens(tokens);
121
- return start.range(end, "");
122
+ return new Token("", SourceLocation.range(start, end));
122
123
  }
123
124
 
124
125
  /**
package/src/Parser.js CHANGED
@@ -9,7 +9,7 @@ import { uSubsAndSups, unicodeSubRegEx } from "./unicodeSupOrSub"
9
9
  import { asciiFromScript } from "./asciiFromScript"
10
10
  import SourceLocation from "./SourceLocation";
11
11
  import { Token } from "./Token";
12
- import { isDelimiter } from "./functions/delimsizing"
12
+ import { isDelimiter } from "./functions/delimiter"
13
13
 
14
14
  // Pre-evaluate both modules as unicodeSymbols require String.normalize()
15
15
  import unicodeAccents from /*preval*/ "./unicodeAccents";
@@ -175,7 +175,7 @@ export default class Parser {
175
175
  * Parses an "expression", which is a list of atoms.
176
176
  *
177
177
  * `breakOnInfix`: Should the parsing stop when we hit infix nodes? This
178
- * happens when functions have higher precedence han infix
178
+ * happens when functions have higher precedence than infix
179
179
  * nodes in implicit parses.
180
180
  *
181
181
  * `breakOnTokenText`: The text of the token that the expression should end
@@ -783,7 +783,7 @@ export default class Parser {
783
783
  ) {
784
784
  const firstToken = this.fetch();
785
785
  const text = firstToken.text;
786
-
786
+ if (name === "argument to '\\left'") { return this.parseSymbol() }
787
787
  let result;
788
788
  // Try to parse an open brace or \begingroup
789
789
  if (text === "{" || text === "\\begingroup" || text === "\\toggle") {
@@ -930,7 +930,8 @@ export default class Parser {
930
930
  let symbol;
931
931
  if (symbols[this.mode][text]) {
932
932
  let group = symbols[this.mode][text].group;
933
- if (group === "bin" && binLeftCancellers.includes(this.prevAtomType)) {
933
+ if (group === "bin" &&
934
+ (binLeftCancellers.includes(this.prevAtomType) || this.prevAtomType === "")) {
934
935
  // Change from a binary operator to a unary (prefix) operator
935
936
  group = "open"
936
937
  }
package/src/Settings.js CHANGED
@@ -30,6 +30,7 @@ export default class Settings {
30
30
  : [Infinity, Infinity]
31
31
  )
32
32
  this.maxExpand = Math.max(0, utils.deflt(options.maxExpand, 1000)); // number
33
+ this.wrapDelimiterPairs = true; // boolean
33
34
  }
34
35
 
35
36
  /**
@@ -358,9 +358,9 @@ const mathmlBuilder = function(group, style) {
358
358
  }
359
359
  if (mustSquashRow) {
360
360
  // All the cell contents are \hphantom. Squash the cell.
361
+ // TODO: Remove the next line when Firefox no longer needs it.
362
+ mtr.classes.push("ff-squash") // necessary in Firefox only.
361
363
  for (let j = 0; j < mtr.children.length; j++) {
362
- mtr.children[j].style.display = "block" // necessary in Firefox only
363
- mtr.children[j].style.height = "0" // necessary in Firefox only
364
364
  mtr.children[j].style.paddingTop = "0"
365
365
  mtr.children[j].style.paddingBottom = "0"
366
366
  }
@@ -14,13 +14,11 @@ defineFunction({
14
14
  },
15
15
  handler: ({ parser, funcName }, args, optArgs) => {
16
16
  // Find out if the author has defined custom delimiters
17
- let delimiters = ["(", ")"]
17
+ let delimiters = ["(", ")"]; // default
18
18
  if (funcName === "\\bordermatrix" && optArgs[0] && optArgs[0].body) {
19
19
  const body = optArgs[0].body
20
- if (body.length === 2 && body[0].type === "atom" && body[1].type === "atom") {
21
- if (body[0].family === "open" && body[1].family === "close") {
22
- delimiters = [body[0].text, body[1].text]
23
- }
20
+ if (body.length === 1 && body[0].type === "delimiter") {
21
+ delimiters = [body[0].left, body[0].right]
24
22
  }
25
23
  }
26
24
  // consume the opening brace
@@ -30,6 +30,7 @@ defineFunction({
30
30
  // That way, the arrow will be an overlay on the content.
31
31
  const phantom = new mathMLTree.MathNode("mphantom", [mml.buildGroup(group.body, style)])
32
32
  const arrow = new mathMLTree.MathNode("mrow", [phantom], ["tml-cancelto"])
33
+ arrow.style.color = style.color
33
34
  if (group.isCharacterBox && smalls.indexOf(group.body.body[0].text) > -1) {
34
35
  arrow.style.left = "0.1em"
35
36
  arrow.style.width = "90%"
@@ -61,6 +62,7 @@ defineFunction({
61
62
  dummyNode = new mathMLTree.MathNode("mphantom", [zeroWidthNode]) // Hide it.
62
63
  }
63
64
  const toNode = mml.buildGroup(group.to, style)
65
+ toNode.style.color = style.color
64
66
  const zeroWidthToNode = new mathMLTree.MathNode("mpadded", [toNode])
65
67
  if (!group.isCharacterBox || /[f∫∑]/.test(group.body.body[0].text)) {
66
68
  const w = new mathMLTree.MathNode("mspace", [])
@@ -18,7 +18,7 @@ const toHex = num => {
18
18
 
19
19
  // Colors from Tables 4.1 and 4.2 of the xcolor package.
20
20
  // Table 4.1 (lower case) RGB values are taken from chroma and xcolor.dtx.
21
- // Table 4.2 (Capitalizzed) values were sampled, because Chroma contains a unreliable
21
+ // Table 4.2 (Capitalized) values were sampled, because Chroma contains a unreliable
22
22
  // conversion from cmyk to RGB. See https://tex.stackexchange.com/a/537274.
23
23
  const xcolors = JSON.parse(`{
24
24
  "Apricot": "#ffb484",
@@ -26,7 +26,41 @@ export const delimiterSizes = {
26
26
  "\\Bigg": { mclass: "mord", size: 4 }
27
27
  };
28
28
 
29
- export const delimiters = [
29
+ export const leftToRight = {
30
+ "(": ")",
31
+ "\\lparen": "\\rparen",
32
+ "[": "]",
33
+ "\\lbrack": "\\rbrack",
34
+ "\\{": "\\}",
35
+ "\\lbrace": "\\rbrace",
36
+ "⦇": "⦈",
37
+ "\\llparenthesis": "\\rrparenthesis",
38
+ "\\lfloor": "\\rfloor",
39
+ "\u230a": "\u230b",
40
+ "\\lceil": "\\rceil",
41
+ "\u2308": "\u2309",
42
+ "\\langle": "\\rangle",
43
+ "\u27e8": "\u27e9",
44
+ "\\lAngle": "\\rAngle",
45
+ "\u27ea": "\u27eb",
46
+ "\\llangle": "\\rrangle",
47
+ "⦉": "⦊",
48
+ "\\lvert": "\\rvert",
49
+ "\\lVert": "\\rVert",
50
+ "\\lgroup": "\\rgroup",
51
+ "\u27ee": "\u27ef",
52
+ "\\lmoustache": "\\rmoustache",
53
+ "\u23b0": "\u23b1",
54
+ "\\llbracket": "\\rrbracket",
55
+ "\u27e6": "\u27e7",
56
+ "\\lBrace": "\\rBrace",
57
+ "\u2983": "\u2984"
58
+ };
59
+
60
+ export const leftDelimiterNames = new Set(Object.keys(leftToRight));
61
+ export const rightDelimiterNames = new Set(Object.values(leftToRight));
62
+
63
+ const delimiters = new Set([
30
64
  "(",
31
65
  "\\lparen",
32
66
  ")",
@@ -82,7 +116,7 @@ export const delimiters = [
82
116
  "\\llbracket",
83
117
  "\\rrbracket",
84
118
  "\u27e6",
85
- "\u27e6",
119
+ "\u27e7",
86
120
  "\\lBrace",
87
121
  "\\rBrace",
88
122
  "\u2983",
@@ -101,12 +135,12 @@ export const delimiters = [
101
135
  "\\updownarrow",
102
136
  "\\Updownarrow",
103
137
  "."
104
- ];
138
+ ]);
105
139
 
106
140
  // Export isDelimiter for benefit of parser.
107
- const dels = ["}", "\\left", "\\middle", "\\right"]
141
+ const dels = new Set(["}", "\\left", "\\middle", "\\right"]);
108
142
  export const isDelimiter = str => str.length > 0 &&
109
- (delimiters.includes(str) || delimiterSizes[str] || dels.includes(str))
143
+ (delimiters.has(str) || delimiterSizes[str] || dels.has(str));
110
144
 
111
145
  // Metrics of the different sizes. Found by looking at TeX's output of
112
146
  // $\bigl| // \Bigl| \biggl| \Biggl| \showlists$
@@ -119,11 +153,11 @@ function checkDelimiter(delim, context) {
119
153
  delim = delim.body[0]; // Unwrap the braces
120
154
  }
121
155
  const symDelim = checkSymbolNodeType(delim)
122
- if (symDelim && delimiters.includes(symDelim.text)) {
156
+ if (symDelim && delimiters.has(symDelim.text)) {
123
157
  // If a character is not in the MathML operator dictionary, it will not stretch.
124
158
  // Replace such characters w/characters that will stretch.
125
- if (["<", "\\lt"].includes(symDelim.text)) { symDelim.text = "⟨" }
126
- if ([">", "\\gt"].includes(symDelim.text)) { symDelim.text = "⟩" }
159
+ if (symDelim.text === "<" || symDelim.text === "\\lt") { symDelim.text = "⟨" }
160
+ if (symDelim.text === ">" || symDelim.text === "\\gt") { symDelim.text = "⟩" }
127
161
  return symDelim;
128
162
  } else if (symDelim) {
129
163
  throw new ParseError(`Invalid delimiter '${symDelim.text}' after '${context.funcName}'`, delim);
@@ -133,7 +167,16 @@ function checkDelimiter(delim, context) {
133
167
  }
134
168
 
135
169
  // / \
136
- const needExplicitStretch = ["\u002F", "\u005C", "\\backslash", "\\vert", "|"];
170
+ const needExplicitStretch = new Set(["\u002F", "\u005C", "\\backslash", "\u2216", "\\vert", "|"]);
171
+
172
+ const makeFenceMo = (delim, mode, form, isStretchy) => {
173
+ const text = delim === "." ? "" : delim;
174
+ const node = new mathMLTree.MathNode("mo", [mml.makeText(text, mode)]);
175
+ node.setAttribute("fence", "true");
176
+ node.setAttribute("form", form);
177
+ node.setAttribute("stretchy", isStretchy ? "true" : "false");
178
+ return node;
179
+ };
137
180
 
138
181
  defineFunction({
139
182
  type: "delimsizing",
@@ -184,9 +227,9 @@ defineFunction({
184
227
  },
185
228
  mathmlBuilder: (group) => {
186
229
  const children = [];
230
+ const delim = group.delim === "." ? "" : group.delim;
187
231
 
188
- if (group.delim === ".") { group.delim = "" }
189
- children.push(mml.makeText(group.delim, group.mode));
232
+ children.push(mml.makeText(delim, group.mode));
190
233
 
191
234
  const node = new mathMLTree.MathNode("mo", children);
192
235
 
@@ -199,7 +242,7 @@ defineFunction({
199
242
  // defaults.
200
243
  node.setAttribute("fence", "false");
201
244
  }
202
- if (needExplicitStretch.includes(group.delim) || group.delim.indexOf("arrow") > -1) {
245
+ if (needExplicitStretch.has(delim) || delim.indexOf("arrow") > -1) {
203
246
  // We have to explicitly set stretchy to true.
204
247
  node.setAttribute("stretchy", "true")
205
248
  }
@@ -212,7 +255,7 @@ defineFunction({
212
255
 
213
256
  function assertParsed(group) {
214
257
  if (!group.body) {
215
- throw new Error("Bug: The leftright ParseNode wasn't fully parsed.");
258
+ throw new Error("Bug: The delim ParseNode wasn't fully parsed.");
216
259
  }
217
260
  }
218
261
 
@@ -243,17 +286,10 @@ defineFunction({
243
286
  const delim = checkDelimiter(args[0], context);
244
287
 
245
288
  const parser = context.parser;
246
- // Parse out the implicit body
247
289
  ++parser.leftrightDepth;
248
- // parseExpression stops before '\\right' or `\\middle`
249
- let body = parser.parseExpression(false, null, true)
290
+ let body = parser.parseExpression(false, "\\right", true)
250
291
  let nextToken = parser.fetch()
251
292
  while (nextToken.text === "\\middle") {
252
- // `\middle`, from the ε-TeX package, ends one group and starts another group.
253
- // We had to parse this expression with `breakOnMiddle` enabled in order
254
- // to get TeX-compliant parsing of \over.
255
- // But we do not want, at this point, to end on \middle, so continue
256
- // to parse until we fetch a `\right`.
257
293
  parser.consume()
258
294
  const middle = parser.fetch().text
259
295
  if (!symbols.math[middle]) {
@@ -262,11 +298,10 @@ defineFunction({
262
298
  checkDelimiter({ type: "atom", mode: "math", text: middle }, { funcName: "\\middle" })
263
299
  body.push({ type: "middle", mode: "math", delim: middle })
264
300
  parser.consume()
265
- body = body.concat(parser.parseExpression(false, null, true))
301
+ body = body.concat(parser.parseExpression(false, "\\right", true))
266
302
  nextToken = parser.fetch()
267
303
  }
268
304
  --parser.leftrightDepth;
269
- // Check the next token
270
305
  parser.expect("\\right", false);
271
306
  const right = assertNodeType(parser.parseFunction(), "leftright-right");
272
307
  return {
@@ -274,39 +309,94 @@ defineFunction({
274
309
  mode: parser.mode,
275
310
  body,
276
311
  left: delim.text,
277
- right: right.delim
312
+ right: right.delim,
313
+ isStretchy: true
278
314
  };
279
315
  },
280
316
  mathmlBuilder: (group, style) => {
281
317
  assertParsed(group);
282
318
  const inner = mml.buildExpression(group.body, style);
283
319
 
284
- if (group.left === ".") { group.left = "" }
285
- const leftNode = new mathMLTree.MathNode("mo", [mml.makeText(group.left, group.mode)]);
286
- leftNode.setAttribute("fence", "true")
287
- leftNode.setAttribute("form", "prefix")
288
- if (group.left === "/" || group.left === "\u005C" || group.left.indexOf("arrow") > -1) {
289
- leftNode.setAttribute("stretchy", "true")
320
+ const leftNode = makeFenceMo(group.left, group.mode, "prefix", true);
321
+ inner.unshift(leftNode);
322
+
323
+ const rightNode = makeFenceMo(group.right, group.mode, "postfix", true);
324
+ if (group.body.length > 0) {
325
+ const lastElement = group.body[group.body.length - 1];
326
+ if (lastElement.type === "color" && !lastElement.isTextColor) {
327
+ rightNode.setAttribute("mathcolor", lastElement.color);
328
+ }
329
+ }
330
+ inner.push(rightNode);
331
+
332
+ return mml.makeRow(inner);
333
+ }
334
+ });
335
+
336
+ defineFunction({
337
+ type: "delimiter",
338
+ names: Array.from(leftDelimiterNames),
339
+ props: {
340
+ numArgs: 0,
341
+ allowedInText: true,
342
+ allowedInMath: true,
343
+ allowedInArgument: true
344
+ },
345
+ handler: ({ parser, funcName, token }) => {
346
+ if (parser.mode === "text") {
347
+ return {
348
+ type: "textord",
349
+ mode: "text",
350
+ text: funcName,
351
+ loc: token.loc
352
+ }
353
+ } else if (!parser.settings.wrapDelimiterPairs) {
354
+ // Treat this token as an ordinary symbol.
355
+ return {
356
+ type: "atom",
357
+ mode: "math",
358
+ family: "open",
359
+ loc: token.loc,
360
+ text: funcName
361
+ };
290
362
  }
291
- inner.unshift(leftNode)
363
+ // Otherwise, try to wrap a pair of delimiters with an <mrow>.
364
+ const rightDelim = leftToRight[funcName];
365
+ // Parse the inner expression, looking for the corresponding right delimiter.
366
+ const body = parser.parseExpression(false, rightDelim, false);
367
+ const nextToken = parser.fetch().text;
292
368
 
293
- if (group.right === ".") { group.right = "" }
294
- const rightNode = new mathMLTree.MathNode("mo", [mml.makeText(group.right, group.mode)]);
295
- rightNode.setAttribute("fence", "true")
296
- rightNode.setAttribute("form", "postfix")
297
- if (group.right === "\u2216" || group.right.indexOf("arrow") > -1) {
298
- rightNode.setAttribute("stretchy", "true")
369
+ if (nextToken !== rightDelim) {
370
+ // We were unable to find a matching right delimiter.
371
+ // Throw control back to renderToMathMLTree.
372
+ // It will reparse the entire expression with wrapDelimiterPairs set to false.
373
+ throw new ParseError("Unmatched delimiter");
299
374
  }
375
+ parser.consume();
376
+
377
+ return {
378
+ type: "delimiter",
379
+ mode: parser.mode,
380
+ body,
381
+ left: funcName,
382
+ right: rightDelim
383
+ };
384
+ },
385
+ mathmlBuilder: (group, style) => {
386
+ assertParsed(group);
387
+ const inner = mml.buildExpression(group.body, style);
388
+
389
+ const leftNode = makeFenceMo(group.left, group.mode, "prefix", false);
390
+ inner.unshift(leftNode);
391
+
392
+ const rightNode = makeFenceMo(group.right, group.mode, "postfix", false);
300
393
  if (group.body.length > 0) {
301
394
  const lastElement = group.body[group.body.length - 1];
302
395
  if (lastElement.type === "color" && !lastElement.isTextColor) {
303
- // \color is a switch. If the last element is of type "color" then
304
- // the user set the \color switch and left it on.
305
- // A \right delimiter turns the switch off, but the delimiter itself gets the color.
306
396
  rightNode.setAttribute("mathcolor", lastElement.color);
307
397
  }
308
398
  }
309
- inner.push(rightNode)
399
+ inner.push(rightNode);
310
400
 
311
401
  return mml.makeRow(inner);
312
402
  }
@@ -331,20 +421,17 @@ defineFunction({
331
421
  delim: delim.text
332
422
  };
333
423
  },
334
- mathmlBuilder: (group, style) => {
424
+ mathmlBuilder: (group) => {
335
425
  const textNode = mml.makeText(group.delim, group.mode);
336
426
  const middleNode = new mathMLTree.MathNode("mo", [textNode]);
337
- middleNode.setAttribute("fence", "true");
338
- if (group.delim.indexOf("arrow") > -1) {
339
- middleNode.setAttribute("stretchy", "true")
427
+ middleNode.setAttribute("stretchy", "true")
428
+ middleNode.setAttribute("form", "infix")
429
+ if (textNode.text !== "/") {
430
+ // MathML gives 5/18em spacing to each <mo> element.
431
+ // \middle should get delimiter spacing instead.
432
+ middleNode.setAttribute("lspace", "0.05em");
433
+ middleNode.setAttribute("rspace", "0.05em");
340
434
  }
341
- // The next line is not semantically correct, but
342
- // Chromium fails to stretch if it is not there.
343
- middleNode.setAttribute("form", "prefix")
344
- // MathML gives 5/18em spacing to each <mo> element.
345
- // \middle should get delimiter spacing instead.
346
- middleNode.setAttribute("lspace", "0.05em");
347
- middleNode.setAttribute("rspace", "0.05em");
348
435
  return middleNode;
349
436
  }
350
437
  });
@@ -32,7 +32,7 @@ const mathmlBuilder = (group, style) => {
32
32
  break
33
33
  case "\\xcancel":
34
34
  node.setAttribute("notation", "updiagonalstrike downdiagonalstrike")
35
- node.classes.push("tml-xcancel")
35
+ node.children.push(new mathMLTree.MathNode("mrow", [], ["tml-cancel", "tml-xcancel"]))
36
36
  break
37
37
  // cancelto is handled in cancelto.js
38
38
  case "\\longdiv":
@@ -166,7 +166,7 @@ defineFunction({
166
166
 
167
167
  defineFunction({
168
168
  type: "enclose",
169
- names: ["\\angl", "\\cancel", "\\bcancel", "\\xcancel", "\\sout", "\\overline",
169
+ names: ["\\angl", "\\cancel", "\\bcancel", "\\xcancel", "\\overline",
170
170
  "\\boxed", "\\longdiv", "\\phase"],
171
171
  props: {
172
172
  numArgs: 1
@@ -183,6 +183,25 @@ defineFunction({
183
183
  mathmlBuilder
184
184
  });
185
185
 
186
+ defineFunction({
187
+ type: "enclose",
188
+ names: ["\\sout"],
189
+ props: {
190
+ numArgs: 1,
191
+ allowedInText: true
192
+ },
193
+ handler({ parser, funcName }, args) {
194
+ const body = args[0];
195
+ return {
196
+ type: "enclose",
197
+ mode: parser.mode,
198
+ label: funcName,
199
+ body
200
+ };
201
+ },
202
+ mathmlBuilder
203
+ });
204
+
186
205
  defineFunction({
187
206
  type: "enclose",
188
207
  names: ["\\underline"],
@@ -1,9 +1,13 @@
1
1
  import defineFunction, { normalizeArgument } from "../defineFunction"
2
2
  import * as mml from "../buildMathML"
3
3
  import * as mathMLTree from "../mathMLTree"
4
+ import { variantChar } from "../replace"
5
+
6
+ const varNameFonts = ["mathrm", "mathit"];
4
7
 
5
8
  const isLongVariableName = (group, font) => {
6
- if (font !== "mathrm" || group.body.type !== "ordgroup" || group.body.body.length === 1) {
9
+ if (!varNameFonts.includes(font) || !group.body || group.body.type !== "ordgroup" ||
10
+ group.body.body.length === 1) {
7
11
  return false
8
12
  }
9
13
  if (group.body.body[0].type !== "mathord") { return false }
@@ -29,8 +33,7 @@ const mathmlBuilder = (group, style) => {
29
33
  }
30
34
  // Check if it is possible to consolidate elements into a single <mi> element.
31
35
  if (isLongVariableName(group, font)) {
32
- // This is a \mathrm{…} group. It gets special treatment because symbolsOrd.js
33
- // wraps <mi> elements with <mpadded>s to work around a Firefox bug.
36
+ // This is a \mathrm{…} or \mathit{…} group. It gets special treatment.
34
37
  const mi = mathGroup.children[0].children[0].children
35
38
  ? mathGroup.children[0].children[0]
36
39
  : mathGroup.children[0];
@@ -40,7 +43,14 @@ const mathmlBuilder = (group, style) => {
40
43
  ? mathGroup.children[i].children[0].children[0].text
41
44
  : mathGroup.children[i].children[0].text
42
45
  }
43
- // Wrap in a <mpadded> to prevent the same Firefox bug.
46
+ if (font === "mathit") {
47
+ // Long <mi> elements are normally rendered in upright font.
48
+ // To get italic, we need to convert each character to the corresponding italic character.
49
+ mi.children[0].text = mi.children[0].text.split("")
50
+ .map(c => variantChar(c, "italic")).join("")
51
+ return mi
52
+ }
53
+ // Otherwise, font is "mathrm". Wrap in a <mpadded> to prevent a Firefox spacing bug.
44
54
  const mpadded = new mathMLTree.MathNode("mpadded", [mi])
45
55
  mpadded.setAttribute("lspace", "0")
46
56
  return mpadded
@@ -9,22 +9,37 @@ import { calculateSize } from "../units";
9
9
  const stylArray = ["display", "text", "script", "scriptscript"];
10
10
  const scriptLevel = { auto: -1, display: 0, text: 0, script: 1, scriptscript: 2 };
11
11
 
12
+ const adjustStyle = (functionSize, originalStyle) => {
13
+ // Figure out what style this fraction should be in based on the
14
+ // function used
15
+ let style = originalStyle;
16
+ if (functionSize === "display") { //\tfrac or \cfrac
17
+ // Get display style as a default.
18
+ // If incoming style is sub/sup, use style.text() to get correct size.
19
+ const newSize = style.level >= StyleLevel.SCRIPT ? StyleLevel.TEXT : StyleLevel.DISPLAY;
20
+ style = style.withLevel(newSize)
21
+ } else if (functionSize === "text" &&
22
+ style.level === StyleLevel.DISPLAY) {
23
+ // We're in a \tfrac but incoming style is displaystyle, so:
24
+ style = style.withLevel(StyleLevel.TEXT);
25
+ } else if (functionSize === "auto") {
26
+ style = style.incrementLevel()
27
+ } else if (functionSize === "script") {
28
+ style = style.withLevel(StyleLevel.SCRIPT);
29
+ } else if (functionSize === "scriptscript") {
30
+ style = style.withLevel(StyleLevel.SCRIPTSCRIPT)
31
+ }
32
+ return style;
33
+ };
34
+
12
35
  const mathmlBuilder = (group, style) => {
13
- // Track the scriptLevel of the numerator and denominator.
14
- // We may need that info for \mathchoice or for adjusting em dimensions.
15
- const childOptions = group.scriptLevel === "auto"
16
- ? style.incrementLevel()
17
- : group.scriptLevel === "display"
18
- ? style.withLevel(StyleLevel.TEXT)
19
- : group.scriptLevel === "text"
20
- ? style.withLevel(StyleLevel.SCRIPT)
21
- : style.withLevel(StyleLevel.SCRIPTSCRIPT);
36
+ style = adjustStyle(group.scriptLevel, style)
22
37
 
23
38
  // Chromium (wrongly) continues to shrink fractions beyond scriptscriptlevel.
24
39
  // So we check for levels that Chromium shrinks too small.
25
40
  // If necessary, set an explicit fraction depth.
26
- const numer = mml.buildGroup(group.numer, childOptions)
27
- const denom = mml.buildGroup(group.denom, childOptions)
41
+ const numer = mml.buildGroup(group.numer, style)
42
+ const denom = mml.buildGroup(group.denom, style)
28
43
  if (style.level === 3) {
29
44
  numer.style.mathDepth = "2"
30
45
  numer.setAttribute("scriptlevel", "2")
@@ -77,6 +92,7 @@ const mathmlBuilder = (group, style) => {
77
92
  defineFunction({
78
93
  type: "genfrac",
79
94
  names: [
95
+ "\\cfrac",
80
96
  "\\dfrac",
81
97
  "\\frac",
82
98
  "\\tfrac",
@@ -100,6 +116,7 @@ defineFunction({
100
116
  let scriptLevel = "auto";
101
117
 
102
118
  switch (funcName) {
119
+ case "\\cfrac":
103
120
  case "\\dfrac":
104
121
  case "\\frac":
105
122
  case "\\tfrac":
@@ -126,15 +143,10 @@ defineFunction({
126
143
  throw new Error("Unrecognized genfrac command");
127
144
  }
128
145
 
129
- switch (funcName) {
130
- case "\\dfrac":
131
- case "\\dbinom":
132
- scriptLevel = "display";
133
- break;
134
- case "\\tfrac":
135
- case "\\tbinom":
136
- scriptLevel = "text";
137
- break;
146
+ if (funcName === "\\cfrac" || funcName.startsWith("\\d")) {
147
+ scriptLevel = "display";
148
+ } else if (funcName.startsWith("\\t")) {
149
+ scriptLevel = "text";
138
150
  }
139
151
 
140
152
  return {
@@ -153,31 +165,6 @@ defineFunction({
153
165
  mathmlBuilder
154
166
  });
155
167
 
156
- defineFunction({
157
- type: "genfrac",
158
- names: ["\\cfrac"],
159
- props: {
160
- numArgs: 2
161
- },
162
- handler: ({ parser, funcName }, args) => {
163
- const numer = args[0];
164
- const denom = args[1];
165
-
166
- return {
167
- type: "genfrac",
168
- mode: parser.mode,
169
- continued: true,
170
- numer,
171
- denom,
172
- hasBarLine: true,
173
- leftDelim: null,
174
- rightDelim: null,
175
- scriptLevel: "display",
176
- barSize: null
177
- };
178
- }
179
- });
180
-
181
168
  // Infix generalized fractions -- these are not rendered directly, but replaced
182
169
  // immediately by one of the variants above.
183
170
  defineFunction({