temml 0.10.23 → 0.10.29

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.
@@ -14,7 +14,7 @@
14
14
  * https://mit-license.org/
15
15
  */
16
16
 
17
- const version = "0.10.23";
17
+ const version = "0.10.29";
18
18
 
19
19
  function postProcess(block) {
20
20
  const labelMap = {};
@@ -65,6 +65,4 @@
65
65
  exports.postProcess = postProcess;
66
66
  exports.version = version;
67
67
 
68
- Object.defineProperty(exports, '__esModule', { value: true });
69
-
70
68
  }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "temml",
3
- "version": "0.10.23",
3
+ "version": "0.10.29",
4
4
  "description": "TeX to MathML conversion in JavaScript.",
5
5
  "main": "dist/temml.js",
6
6
  "engines": {
@@ -28,10 +28,13 @@
28
28
  ],
29
29
  "license": "MIT",
30
30
  "devDependencies": {
31
- "eslint": "^8.39.0",
31
+ "@eslint/eslintrc": "^3.1.0",
32
+ "@eslint/js": "^9.9.0",
33
+ "eslint": "^9.9.0",
32
34
  "esm": "^3.2.25",
33
- "rollup": "^2.66.1",
34
- "terser": "^5.14.2"
35
+ "globals": "^15.9.0",
36
+ "rollup": "^4.20.0",
37
+ "terser": "^5.31.6"
35
38
  },
36
39
  "scripts": {
37
40
  "lint": "eslint temml.js src",
@@ -39,7 +42,7 @@
39
42
  "visual-test": "node utils/buildTests.js",
40
43
  "test": "yarn lint && node utils/buildTests.js && yarn unit-test",
41
44
  "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",
42
- "build": "rollup --config ./utils/rollupConfig.js && yarn minify && node utils/insertPlugins.js",
45
+ "build": "rollup --config ./utils/rollupConfig.mjs && yarn minify && node utils/insertPlugins.js",
43
46
  "docs": "node utils/buildDocs.js",
44
47
  "dist": "yarn build && node ./utils/copyfiles.js && terser contrib/auto-render/test/auto-render.js -o contrib/auto-render/dist/auto-render.min.js -c -m"
45
48
  }
package/src/Parser.js CHANGED
@@ -16,6 +16,7 @@ import unicodeAccents from /*preval*/ "./unicodeAccents";
16
16
  import unicodeSymbols from /*preval*/ "./unicodeSymbols";
17
17
 
18
18
  const binLeftCancellers = ["bin", "op", "open", "punct", "rel"];
19
+ const sizeRegEx = /([-+]?) *(\d+(?:\.\d*)?|\.\d+) *([a-z]{2})/
19
20
 
20
21
  /**
21
22
  * This file contains the parser used to parse out a TeX expression from the
@@ -679,7 +680,7 @@ export default class Parser {
679
680
  res.text = "0pt"; // Enable \above{}
680
681
  isBlank = true; // This is here specifically for \genfrac
681
682
  }
682
- const match = /([-+]?) *(\d+(?:\.\d*)?|\.\d+) *([a-z]{2})/.exec(res.text);
683
+ const match = sizeRegEx.exec(res.text);
683
684
  if (!match) {
684
685
  throw new ParseError("Invalid size: '" + res.text + "'", res);
685
686
  }
@@ -885,7 +886,7 @@ export default class Parser {
885
886
  // At this point, we should have a symbol, possibly with accents.
886
887
  // First expand any accented base symbol according to unicodeSymbols.
887
888
  if (Object.prototype.hasOwnProperty.call(unicodeSymbols, text[0]) &&
888
- !symbols[this.mode][text[0]]) {
889
+ this.mode === "math" && !symbols[this.mode][text[0]]) {
889
890
  // This behavior is not strict (XeTeX-compatible) in math mode.
890
891
  if (this.settings.strict && this.mode === "math") {
891
892
  throw new ParseError(`Accented Unicode text character "${text[0]}" used in ` + `math mode`,
@@ -895,7 +896,9 @@ export default class Parser {
895
896
  text = unicodeSymbols[text[0]] + text.slice(1);
896
897
  }
897
898
  // Strip off any combining characters
898
- const match = combiningDiacriticalMarksEndRegex.exec(text);
899
+ const match = this.mode === "math"
900
+ ? combiningDiacriticalMarksEndRegex.exec(text)
901
+ : null
899
902
  if (match) {
900
903
  text = text.substring(0, match.index);
901
904
  if (text === "i") {
@@ -948,7 +951,7 @@ export default class Parser {
948
951
  };
949
952
  }
950
953
  symbol = s;
951
- } else if (text.charCodeAt(0) >= 0x80) {
954
+ } else if (text.charCodeAt(0) >= 0x80 || combiningDiacriticalMarksEndRegex.exec(text)) {
952
955
  // no symbol for e.g. ^
953
956
  if (this.settings.strict && this.mode === "math") {
954
957
  throw new ParseError(`Unicode text character "${text[0]}" used in math mode`, nucleus)
package/src/Settings.js CHANGED
@@ -42,7 +42,11 @@ export default class Settings {
42
42
  */
43
43
  isTrusted(context) {
44
44
  if (context.url && !context.protocol) {
45
- context.protocol = utils.protocolFromUrl(context.url);
45
+ const protocol = utils.protocolFromUrl(context.url);
46
+ if (protocol == null) {
47
+ return false
48
+ }
49
+ context.protocol = protocol
46
50
  }
47
51
  const trust = typeof this.trust === "function" ? this.trust(context) : this.trust;
48
52
  return Boolean(trust);
@@ -34,49 +34,72 @@ export const makeText = function(text, mode, style) {
34
34
  return new mathMLTree.TextNode(text);
35
35
  };
36
36
 
37
+ const copyChar = (newRow, child) => {
38
+ if (newRow.children.length === 0 ||
39
+ newRow.children[newRow.children.length - 1].type !== "mtext") {
40
+ const mtext = new mathMLTree.MathNode(
41
+ "mtext",
42
+ [new mathMLTree.TextNode(child.children[0].text)]
43
+ )
44
+ newRow.children.push(mtext)
45
+ } else {
46
+ newRow.children[newRow.children.length - 1].children[0].text += child.children[0].text
47
+ }
48
+ }
49
+
37
50
  export const consolidateText = mrow => {
38
51
  // If possible, consolidate adjacent <mtext> elements into a single element.
39
52
  if (mrow.type !== "mrow" && mrow.type !== "mstyle") { return mrow }
40
53
  if (mrow.children.length === 0) { return mrow } // empty group, e.g., \text{}
41
- if (!mrow.children[0].attributes || mrow.children[0].type !== "mtext") { return mrow }
42
- const variant = mrow.children[0].attributes.mathvariant || ""
43
- const mtext = new mathMLTree.MathNode(
44
- "mtext",
45
- [new mathMLTree.TextNode(mrow.children[0].children[0].text)]
46
- )
47
- for (let i = 1; i < mrow.children.length; i++) {
48
- // Check each child and, if possible, copy the character into child[0].
49
- const localVariant = mrow.children[i].attributes.mathvariant || ""
50
- if (mrow.children[i].type === "mrow") {
51
- const childRow = mrow.children[i]
52
- for (let j = 0; j < childRow.children.length; j++) {
53
- // We'll also check the children of a mrow. One level only. No recursion.
54
- const childVariant = childRow.children[j].attributes.mathvariant || ""
55
- if (childVariant !== variant || childRow.children[j].type !== "mtext") {
56
- return mrow // At least one element cannot be consolidated. Get out.
57
- } else {
58
- mtext.children[0].text += childRow.children[j].children[0].text
54
+ const newRow = new mathMLTree.MathNode("mrow")
55
+ for (let i = 0; i < mrow.children.length; i++) {
56
+ const child = mrow.children[i];
57
+ if (child.type === "mtext" && Object.keys(child.attributes).length === 0) {
58
+ copyChar(newRow, child)
59
+ } else if (child.type === "mrow") {
60
+ // We'll also check the children of an mrow. One level only. No recursion.
61
+ let canConsolidate = true
62
+ for (let j = 0; j < child.children.length; j++) {
63
+ const grandChild = child.children[j];
64
+ if (grandChild.type !== "mtext" || Object.keys(child.attributes).length !== 0) {
65
+ canConsolidate = false
66
+ break
67
+ }
68
+ }
69
+ if (canConsolidate) {
70
+ for (let j = 0; j < child.children.length; j++) {
71
+ const grandChild = child.children[j];
72
+ copyChar(newRow, grandChild)
59
73
  }
74
+ } else {
75
+ newRow.children.push(child)
60
76
  }
61
- } else if (localVariant !== variant || mrow.children[i].type !== "mtext") {
62
- return mrow
63
77
  } else {
64
- mtext.children[0].text += mrow.children[i].children[0].text
78
+ newRow.children.push(child)
65
79
  }
66
80
  }
67
- // Firefox does not render a space at either end of an <mtext> string.
68
- // To get proper rendering, we replace leading or trailing spaces with no-break spaces.
69
- if (mtext.children[0].text.charAt(0) === " ") {
70
- mtext.children[0].text = "\u00a0" + mtext.children[0].text.slice(1)
71
- }
72
- const L = mtext.children[0].text.length
73
- if (L > 0 && mtext.children[0].text.charAt(L - 1) === " ") {
74
- mtext.children[0].text = mtext.children[0].text.slice(0, -1) + "\u00a0"
81
+ for (let i = 0; i < newRow.children.length; i++) {
82
+ if (newRow.children[i].type === "mtext") {
83
+ const mtext = newRow.children[i];
84
+ // Firefox does not render a space at either end of an <mtext> string.
85
+ // To get proper rendering, we replace leading or trailing spaces with no-break spaces.
86
+ if (mtext.children[0].text.charAt(0) === " ") {
87
+ mtext.children[0].text = "\u00a0" + mtext.children[0].text.slice(1)
88
+ }
89
+ const L = mtext.children[0].text.length
90
+ if (L > 0 && mtext.children[0].text.charAt(L - 1) === " ") {
91
+ mtext.children[0].text = mtext.children[0].text.slice(0, -1) + "\u00a0"
92
+ }
93
+ for (const [key, value] of Object.entries(mrow.attributes)) {
94
+ mtext.attributes[key] = value
95
+ }
96
+ }
75
97
  }
76
- for (const [key, value] of Object.entries(mrow.attributes)) {
77
- mtext.attributes[key] = value
98
+ if (newRow.children.length === 1 && newRow.children[0].type === "mtext") {
99
+ return newRow.children[0]; // A consolidated <mtext>
100
+ } else {
101
+ return newRow
78
102
  }
79
- return mtext
80
103
  }
81
104
 
82
105
  const numberRegEx = /^[0-9]$/
@@ -7,7 +7,8 @@ import { StyleLevel } from "../constants"
7
7
  import ParseError from "../ParseError";
8
8
  import { assertNodeType, assertSymbolNodeType } from "../parseNode";
9
9
  import { checkSymbolNodeType } from "../parseNode";
10
-
10
+ import { stringFromArg } from "../macros"
11
+ import { calculateSize } from "../units"
11
12
  import * as mml from "../buildMathML";
12
13
 
13
14
  // Helper functions
@@ -38,6 +39,24 @@ const validateAmsEnvironmentContext = context => {
38
39
  }
39
40
  }
40
41
 
42
+ const sizeRegEx = /([-+]?) *(\d+(?:\.\d*)?|\.\d+) *([a-z]{2})/
43
+ const arrayGaps = macros => {
44
+ let arraystretch = macros.get("\\arraystretch")
45
+ if (typeof arraystretch !== "string") {
46
+ arraystretch = stringFromArg(arraystretch.tokens)
47
+ }
48
+ arraystretch = isNaN(arraystretch) ? null : Number(arraystretch)
49
+ let arraycolsepStr = macros.get("\\arraycolsep")
50
+ if (typeof arraycolsepStr !== "string") {
51
+ arraycolsepStr = stringFromArg(arraycolsepStr.tokens)
52
+ }
53
+ const match = sizeRegEx.exec(arraycolsepStr)
54
+ const arraycolsep = match
55
+ ? { number: +(match[1] + match[2]), unit: match[3] }
56
+ : null
57
+ return [arraystretch, arraycolsep]
58
+ }
59
+
41
60
  const getTag = (group, style, rowNum) => {
42
61
  let tag
43
62
  const tagContents = group.tags.shift()
@@ -76,12 +95,14 @@ function parseArray(
76
95
  {
77
96
  cols, // [{ type: string , align: l|c|r|null }]
78
97
  envClasses, // align(ed|at|edat) | array | cases | cd | small | multline
79
- addEqnNum, // boolean
80
- singleRow, // boolean
98
+ addEqnNum, // boolean
99
+ singleRow, // boolean
81
100
  emptySingleRow, // boolean
82
- maxNumCols, // number
83
- leqno // boolean
84
- },
101
+ maxNumCols, // number
102
+ leqno, // boolean
103
+ arraystretch, // number | null
104
+ arraycolsep // size value | null
105
+ },
85
106
  scriptLevel
86
107
  ) {
87
108
  parser.gullet.beginGroup();
@@ -111,7 +132,6 @@ function parseArray(
111
132
  // Test for \hline at the top of the array.
112
133
  hLinesBeforeRow.push(getHLines(parser));
113
134
 
114
- // eslint-disable-next-line no-constant-condition
115
135
  while (true) {
116
136
  // Parse each cell in its own group (namespace)
117
137
  let cell = parser.parseExpression(false, singleRow ? "\\end" : "\\\\");
@@ -211,7 +231,9 @@ function parseArray(
211
231
  addEqnNum,
212
232
  scriptLevel,
213
233
  tags,
214
- leqno
234
+ leqno,
235
+ arraystretch,
236
+ arraycolsep
215
237
  };
216
238
  }
217
239
 
@@ -301,12 +323,18 @@ const mathmlBuilder = function(group, style) {
301
323
  }
302
324
 
303
325
  if (group.envClasses.length > 0) {
304
- const pad = group.envClasses.includes("jot")
326
+ let pad = group.envClasses.includes("jot")
305
327
  ? "0.7" // 0.5ex + 0.09em top & bot padding
306
328
  : group.envClasses.includes("small")
307
329
  ? "0.35"
308
330
  : "0.5" // 0.5ex default top & bot padding
309
- const sidePadding = group.envClasses.includes("abut")
331
+ if (group.arraystretch && group.arraystretch !== 1) {
332
+ // In LaTeX, \arraystretch is a factor applied to a 12pt strut height.
333
+ // It defines a baseline to baseline distance.
334
+ // Here, we do an approximation of that approach.
335
+ pad = String(1.4 * group.arraystretch - 0.8)
336
+ }
337
+ let sidePadding = group.envClasses.includes("abut")
310
338
  ? "0"
311
339
  : group.envClasses.includes("cases")
312
340
  ? "0"
@@ -315,6 +343,12 @@ const mathmlBuilder = function(group, style) {
315
343
  : group.envClasses.includes("cd")
316
344
  ? "0.25"
317
345
  : "0.4" // default side padding
346
+ let sidePadUnit = "em"
347
+ if (group.arraycolsep) {
348
+ const arraySidePad = calculateSize(group.arraycolsep, style)
349
+ sidePadding = arraySidePad.number
350
+ sidePadUnit = arraySidePad.unit
351
+ }
318
352
 
319
353
  const numCols = tbl.length === 0 ? 0 : tbl[0].children.length
320
354
 
@@ -333,7 +367,8 @@ const mathmlBuilder = function(group, style) {
333
367
  // Padding
334
368
  for (let i = 0; i < tbl.length; i++) {
335
369
  for (let j = 0; j < tbl[i].children.length; j++) {
336
- tbl[i].children[j].style.padding = `${pad}ex ${sidePad(j, 1)}em ${pad}ex ${sidePad(j, 0)}em`
370
+ tbl[i].children[j].style.padding = `${pad}ex ${sidePad(j, 1)}${sidePadUnit}`
371
+ + ` ${pad}ex ${sidePad(j, 0)}${sidePadUnit}`
337
372
  }
338
373
  }
339
374
 
@@ -587,10 +622,13 @@ defineEnvironment({
587
622
  }
588
623
  throw new ParseError("Unknown column alignment: " + ca, nde);
589
624
  });
625
+ const [arraystretch, arraycolsep] = arrayGaps(context.parser.gullet.macros)
590
626
  const res = {
591
627
  cols,
592
628
  envClasses: ["array"],
593
- maxNumCols: cols.length
629
+ maxNumCols: cols.length,
630
+ arraystretch,
631
+ arraycolsep
594
632
  };
595
633
  return parseArray(context.parser, res, dCellStyle(context.envName));
596
634
  },
@@ -656,6 +694,7 @@ defineEnvironment({
656
694
  }
657
695
  const res = parseArray(context.parser, payload, "text")
658
696
  res.cols = new Array(res.body[0].length).fill({ type: "align", align: colAlign })
697
+ const [arraystretch, arraycolsep] = arrayGaps(context.parser.gullet.macros)
659
698
  return delimiters
660
699
  ? {
661
700
  type: "leftright",
@@ -663,7 +702,9 @@ defineEnvironment({
663
702
  body: [res],
664
703
  left: delimiters[0],
665
704
  right: delimiters[1],
666
- rightColor: undefined // \right uninfluenced by \color in array
705
+ rightColor: undefined, // \right uninfluenced by \color in array
706
+ arraystretch,
707
+ arraycolsep
667
708
  }
668
709
  : res;
669
710
  },
@@ -71,7 +71,7 @@ export function parseCD(parser) {
71
71
  parser.gullet.beginGroup();
72
72
  parser.gullet.macros.set("\\cr", "\\\\\\relax");
73
73
  parser.gullet.beginGroup();
74
- while (true) { // eslint-disable-line no-constant-condition
74
+ while (true) {
75
75
  // Get the parse nodes for the next row.
76
76
  parsedRows.push(parser.parseExpression(false, "\\\\"));
77
77
  parser.gullet.endGroup();
@@ -71,6 +71,20 @@ const needWebkitShift = new Set([
71
71
  "\\'", "\\^", "\\~", "\\=", "\\u", "\\.", '\\"', "\\r", "\\H", "\\v"
72
72
  ])
73
73
 
74
+ const combiningChar = {
75
+ "\\`": "\u0300",
76
+ "\\'": "\u0301",
77
+ "\\^": "\u0302",
78
+ "\\~": "\u0303",
79
+ "\\=": "\u0304",
80
+ "\\u": "\u0306",
81
+ "\\.": "\u0307",
82
+ '\\"': "\u0308",
83
+ "\\r": "\u030A",
84
+ "\\H": "\u030B",
85
+ "\\v": "\u030C"
86
+ }
87
+
74
88
  // Accents
75
89
  defineFunction({
76
90
  type: "accent",
@@ -140,13 +154,24 @@ defineFunction({
140
154
  console.log(`Temml parse error: Command ${context.funcName} is invalid in math mode.`)
141
155
  }
142
156
 
143
- return {
144
- type: "accent",
145
- mode: mode,
146
- label: context.funcName,
147
- isStretchy: false,
148
- base: base
149
- };
157
+ if (mode === "text" && base.text && base.text.length === 1
158
+ && context.funcName in combiningChar && smalls.indexOf(base.text) > -1) {
159
+ // Return a combining accent character
160
+ return {
161
+ type: "textord",
162
+ mode: "text",
163
+ text: base.text + combiningChar[context.funcName]
164
+ }
165
+ } else {
166
+ // Build up the accent
167
+ return {
168
+ type: "accent",
169
+ mode: mode,
170
+ label: context.funcName,
171
+ isStretchy: false,
172
+ base: base
173
+ }
174
+ }
150
175
  },
151
176
  mathmlBuilder
152
177
  });
@@ -141,6 +141,9 @@ defineFunction({
141
141
 
142
142
  if (funcName === "\\edef" || funcName === "\\xdef") {
143
143
  tokens = parser.gullet.expandTokens(tokens);
144
+ if (tokens.length > parser.gullet.settings.maxExpand) {
145
+ throw new ParseError("Too many expansions in an " + funcName);
146
+ }
144
147
  tokens.reverse(); // to fit in with stack order
145
148
  }
146
149
  // Final arg is the expansion of the macro
@@ -249,8 +252,7 @@ defineFunction({
249
252
 
250
253
  parser.gullet.macros.set(
251
254
  name,
252
- { tokens, numArgs },
253
- !parser.settings.strict
255
+ { tokens, numArgs }
254
256
  )
255
257
 
256
258
  return { type: "internal", mode: parser.mode };
@@ -22,52 +22,62 @@ const mathmlBuilder = (group, style) => {
22
22
  padding()
23
23
  ])
24
24
  } else {
25
- node = new mathMLTree.MathNode("mrow", [mml.buildGroup(group.body, style)])
25
+ node = new mathMLTree.MathNode("menclose", [mml.buildGroup(group.body, style)])
26
26
  }
27
27
  switch (group.label) {
28
28
  case "\\overline":
29
- node.style.padding = "0.1em 0 0 0"
30
- node.style.borderTop = "0.065em solid"
29
+ node.setAttribute("notation", "top") // for Firefox & WebKit
30
+ node.classes.push("tml-overline") // for Chromium
31
31
  break
32
32
  case "\\underline":
33
- node.style.padding = "0 0 0.1em 0"
34
- node.style.borderBottom = "0.065em solid"
33
+ node.setAttribute("notation", "bottom")
34
+ node.classes.push("tml-underline")
35
35
  break
36
36
  case "\\cancel":
37
- // We can't use an inline background-gradient. It does not work client-side.
38
- // So set a class and put the rule in the external CSS file.
39
- node.classes.push("tml-cancel")
37
+ node.setAttribute("notation", "updiagonalstrike")
38
+ node.children.push(new mathMLTree.MathNode("mrow", [], ["tml-cancel", "upstrike"]))
40
39
  break
41
40
  case "\\bcancel":
42
- node.classes.push("tml-bcancel")
41
+ node.setAttribute("notation", "downdiagonalstrike")
42
+ node.children.push(new mathMLTree.MathNode("mrow", [], ["tml-cancel", "downstrike"]))
43
+ break
44
+ case "\\sout":
45
+ node.setAttribute("notation", "horizontalstrike")
46
+ node.children.push(new mathMLTree.MathNode("mrow", [], ["tml-cancel", "sout"]))
47
+ break
48
+ case "\\xcancel":
49
+ node.setAttribute("notation", "updiagonalstrike downdiagonalstrike")
50
+ node.classes.push("tml-xcancel")
43
51
  break
44
- /*
45
52
  case "\\longdiv":
46
- node.setAttribute("notation", "longdiv");
53
+ node.setAttribute("notation", "longdiv")
54
+ node.classes.push("longdiv-top")
55
+ node.children.push(new mathMLTree.MathNode("mrow", [], ["longdiv-arc"]))
47
56
  break
48
57
  case "\\phase":
49
- node.setAttribute("notation", "phasorangle");
50
- break */
51
- case "\\angl":
52
- node.style.padding = "0.03889em 0.03889em 0 0.03889em"
53
- node.style.borderTop = "0.049em solid"
54
- node.style.borderRight = "0.049em solid"
55
- node.style.marginRight = "0.03889em"
58
+ node.setAttribute("notation", "phasorangle")
59
+ node.classes.push("phasor-bottom")
60
+ node.children.push(new mathMLTree.MathNode("mrow", [], ["phasor-angle"]))
56
61
  break
57
- case "\\sout":
58
- node.style.backgroundImage = 'linear-gradient(black, black)'
59
- node.style.backgroundRepeat = 'no-repeat'
60
- node.style.backgroundSize = '100% 1.5px'
61
- node.style.backgroundPosition = '0 center'
62
+ case "\\textcircled":
63
+ node.setAttribute("notation", "circle")
64
+ node.classes.push("circle-pad")
65
+ node.children.push(new mathMLTree.MathNode("mrow", [], ["textcircle"]))
66
+ break
67
+ case "\\angl":
68
+ node.setAttribute("notation", "actuarial")
69
+ node.classes.push("actuarial")
62
70
  break
63
71
  case "\\boxed":
64
72
  // \newcommand{\boxed}[1]{\fbox{\m@th$\displaystyle#1$}} from amsmath.sty
65
- node.style = { padding: "3pt 0 3pt 0", border: "1px solid" }
73
+ node.setAttribute("notation", "box")
74
+ node.classes.push("tml-box")
66
75
  node.setAttribute("scriptlevel", "0")
67
76
  node.setAttribute("displaystyle", "true")
68
77
  break
69
78
  case "\\fbox":
70
- node.style = { padding: "3pt", border: "1px solid" }
79
+ node.setAttribute("notation", "box")
80
+ node.classes.push("tml-fbox")
71
81
  break
72
82
  case "\\fcolorbox":
73
83
  case "\\colorbox": {
@@ -80,14 +90,11 @@ const mathmlBuilder = (group, style) => {
80
90
  const style = { padding: "3pt 0 3pt 0" }
81
91
 
82
92
  if (group.label === "\\fcolorbox") {
83
- style.border = "0.06em solid " + String(group.borderColor)
93
+ style.border = "0.0667em solid " + String(group.borderColor)
84
94
  }
85
95
  node.style = style
86
96
  break
87
97
  }
88
- case "\\xcancel":
89
- node.classes.push("tml-xcancel")
90
- break
91
98
  }
92
99
  if (group.backgroundColor) {
93
100
  node.setAttribute("mathbackground", group.backgroundColor);
@@ -180,8 +187,8 @@ defineFunction({
180
187
 
181
188
  defineFunction({
182
189
  type: "enclose",
183
- names: ["\\angl", "\\cancel", "\\bcancel", "\\xcancel", "\\sout", "\\overline", "\\boxed"],
184
- // , "\\phase", "\\longdiv"
190
+ names: ["\\angl", "\\cancel", "\\bcancel", "\\xcancel", "\\sout", "\\overline",
191
+ "\\boxed", "\\longdiv", "\\phase"],
185
192
  props: {
186
193
  numArgs: 1
187
194
  },
@@ -215,3 +222,25 @@ defineFunction({
215
222
  },
216
223
  mathmlBuilder
217
224
  });
225
+
226
+
227
+ defineFunction({
228
+ type: "enclose",
229
+ names: ["\\textcircled"],
230
+ props: {
231
+ numArgs: 1,
232
+ argTypes: ["text"],
233
+ allowedInArgument: true,
234
+ allowedInText: true
235
+ },
236
+ handler({ parser, funcName }, args) {
237
+ const body = args[0];
238
+ return {
239
+ type: "enclose",
240
+ mode: parser.mode,
241
+ label: funcName,
242
+ body
243
+ };
244
+ },
245
+ mathmlBuilder
246
+ });
@@ -12,7 +12,7 @@ const ordAtomTypes = ["textord", "mathord", "atom"]
12
12
  const noSuccessor = ["\\smallint"];
13
13
 
14
14
  // Math operators (e.g. \sin) need a space between these types and themselves:
15
- export const ordTypes = ["textord", "mathord", "ordgroup", "close", "leftright"];
15
+ export const ordTypes = ["textord", "mathord", "ordgroup", "close", "leftright", "font"];
16
16
 
17
17
  // NOTE: Unlike most `builders`s, this one handles not only "op", but also
18
18
  // "supsub" since some of them (like \int) can affect super/subscripting.
@@ -32,6 +32,13 @@ const mathmlBuilder = (group, style) => {
32
32
  node = new mathMLTree.MathNode("mo", [mml.makeText(group.name, group.mode)]);
33
33
  if (noSuccessor.includes(group.name)) {
34
34
  node.setAttribute("largeop", "false")
35
+ } else if (group.limits) {
36
+ // This is a workaround for a MathML/Chromium bug.
37
+ // This is being applied to singleCharBigOps, which are not really stretchy.
38
+ // But by setting the stretchy attribute, Chromium will vertically center
39
+ // big ops around the math axis. This is needed since STIX TWO does not do so.
40
+ // TODO: Remove this hack when MathML & Chromium fix their problem.
41
+ node.setAttribute("stretchy", "true")
35
42
  } else {
36
43
  node.setAttribute("movablelimits", "false")
37
44
  }
@@ -81,6 +88,9 @@ const singleCharBigOps = {
81
88
  "\u2a04": "\\biguplus",
82
89
  "\u2a05": "\\bigsqcap",
83
90
  "\u2a06": "\\bigsqcup",
91
+ "\u2a03": "\\bigcupdot",
92
+ "\u2a07": "\\bigdoublevee",
93
+ "\u2a08": "\\bigdoublewedge",
84
94
  "\u2a09": "\\bigtimes"
85
95
  };
86
96
 
@@ -91,8 +101,12 @@ defineFunction({
91
101
  "\\bigvee",
92
102
  "\\bigwedge",
93
103
  "\\biguplus",
104
+ "\\bigcupplus",
105
+ "\\bigcupdot",
94
106
  "\\bigcap",
95
107
  "\\bigcup",
108
+ "\\bigdoublevee",
109
+ "\\bigdoublewedge",
96
110
  "\\intop",
97
111
  "\\prod",
98
112
  "\\sum",
@@ -55,9 +55,9 @@ defineFunctionBuilders({
55
55
  if (group.sup) {
56
56
  const sup = mml.buildGroup(group.sup, childStyle)
57
57
  const testNode = sup.type === "mrow" ? sup.children[0] : sup
58
- if ((testNode.type === "mo" && testNode.classes.includes("tml-prime"))
58
+ if ((testNode && testNode.type === "mo" && testNode.classes.includes("tml-prime"))
59
59
  && group.base && group.base.text && group.base.text === "f") {
60
- // Chromium does not address italic correction on prime. Prevent f′ from overlapping.
60
+ // Chromium does not address italic correction on prime. Prevent f′ from overlapping.
61
61
  testNode.classes.push("prime-pad")
62
62
  }
63
63
  children.push(sup)