temml 0.10.15 → 0.10.16

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
@@ -476,7 +476,7 @@ class TextNode$1 {
476
476
  }
477
477
  }
478
478
 
479
- /**
479
+ /*
480
480
  * This node represents an image embed (<img>) element.
481
481
  */
482
482
  class Img {
@@ -521,7 +521,7 @@ class Img {
521
521
  markup += ` style="${utils.escape(styles)}"`;
522
522
  }
523
523
 
524
- markup += "/>";
524
+ markup += ">";
525
525
  return markup;
526
526
  }
527
527
  }
@@ -697,6 +697,34 @@ var mathMLTree = {
697
697
  * This file provides support for building horizontal stretchy elements.
698
698
  */
699
699
 
700
+ // TODO: Remove when Chromium stretches \widetilde & \widehat
701
+ const estimatedWidth = node => {
702
+ let width = 0;
703
+ if (node.body) {
704
+ for (const item of node.body) {
705
+ width += estimatedWidth(item);
706
+ }
707
+ } else if (node.type === "supsub") {
708
+ width += estimatedWidth(node.base);
709
+ if (node.sub) { width += 0.7 * estimatedWidth(node.sub); }
710
+ if (node.sup) { width += 0.7 * estimatedWidth(node.sup); }
711
+ } else if (node.type === "mathord" || node.type === "textord") {
712
+ for (const ch of node.text.split('')) {
713
+ const codePoint = ch.codePointAt(0);
714
+ if ((0x60 < codePoint && codePoint < 0x7B) || (0x03B0 < codePoint && codePoint < 0x3CA)) {
715
+ width += 0.56; // lower case latin or greek. Use advance width of letter n
716
+ } else if (0x2F < codePoint && codePoint < 0x3A) {
717
+ width += 0.50; // numerals.
718
+ } else {
719
+ width += 0.92; // advance width of letter M
720
+ }
721
+ }
722
+ } else {
723
+ width += 1.0;
724
+ }
725
+ return width
726
+ };
727
+
700
728
  const stretchyCodePoint = {
701
729
  widehat: "^",
702
730
  widecheck: "ˇ",
@@ -754,8 +782,27 @@ const mathMLnode = function(label) {
754
782
  return node
755
783
  };
756
784
 
785
+ const crookedWides = ["\\widetilde", "\\widehat", "\\widecheck", "\\utilde"];
786
+
787
+ // TODO: Remove when Chromium stretches \widetilde & \widehat
788
+ const accentNode = (group) => {
789
+ const mo = mathMLnode(group.label);
790
+ if (crookedWides.includes(group.label)) {
791
+ const width = estimatedWidth(group.base);
792
+ if (1 < width && width < 1.6) {
793
+ mo.classes.push("tml-crooked-2");
794
+ } else if (1.6 <= width && width < 2.5) {
795
+ mo.classes.push("tml-crooked-3");
796
+ } else if (2.5 <= width) {
797
+ mo.classes.push("tml-crooked-4");
798
+ }
799
+ }
800
+ return mo
801
+ };
802
+
757
803
  var stretchy = {
758
- mathMLnode
804
+ mathMLnode,
805
+ accentNode
759
806
  };
760
807
 
761
808
  /**
@@ -1053,6 +1100,8 @@ defineSymbol(math, textord, "\u2127", "\\mho");
1053
1100
  defineSymbol(math, textord, "\u2132", "\\Finv", true);
1054
1101
  defineSymbol(math, textord, "\u2141", "\\Game", true);
1055
1102
  defineSymbol(math, textord, "\u2035", "\\backprime");
1103
+ defineSymbol(math, textord, "\u2036", "\\backdprime");
1104
+ defineSymbol(math, textord, "\u2037", "\\backtrprime");
1056
1105
  defineSymbol(math, textord, "\u25b2", "\\blacktriangle");
1057
1106
  defineSymbol(math, textord, "\u25bc", "\\blacktriangledown");
1058
1107
  defineSymbol(math, textord, "\u25a0", "\\blacksquare");
@@ -1256,6 +1305,9 @@ defineSymbol(text, textord, "\u2423", "\\textvisiblespace", true);
1256
1305
  defineSymbol(math, textord, "\u2220", "\\angle", true);
1257
1306
  defineSymbol(math, textord, "\u221e", "\\infty", true);
1258
1307
  defineSymbol(math, textord, "\u2032", "\\prime");
1308
+ defineSymbol(math, textord, "\u2033", "\\dprime");
1309
+ defineSymbol(math, textord, "\u2034", "\\trprime");
1310
+ defineSymbol(math, textord, "\u2057", "\\qprime");
1259
1311
  defineSymbol(math, textord, "\u25b3", "\\triangle");
1260
1312
  defineSymbol(text, textord, "\u0391", "\\Alpha", true);
1261
1313
  defineSymbol(text, textord, "\u0392", "\\Beta", true);
@@ -1435,7 +1487,8 @@ defineSymbol(math, punct, ";", ";");
1435
1487
  defineSymbol(math, bin, "\u22bc", "\\barwedge", true);
1436
1488
  defineSymbol(math, bin, "\u22bb", "\\veebar", true);
1437
1489
  defineSymbol(math, bin, "\u2299", "\\odot", true);
1438
- defineSymbol(math, bin, "\u2295", "\\oplus", true);
1490
+ // Firefox turns ⊕ into an emoji. So append \uFE0E. Define Unicode character in macros, not here.
1491
+ defineSymbol(math, bin, "\u2295\uFE0E", "\\oplus");
1439
1492
  defineSymbol(math, bin, "\u2297", "\\otimes", true);
1440
1493
  defineSymbol(math, textord, "\u2202", "\\partial", true);
1441
1494
  defineSymbol(math, bin, "\u2298", "\\oslash", true);
@@ -1666,6 +1719,8 @@ for (let i = 0; i < letters.length; i++) {
1666
1719
  defineSymbol(math, mathord, ch, ch);
1667
1720
  defineSymbol(text, textord, ch, ch);
1668
1721
  }
1722
+ // Prevent Firefox from using a dotless i.
1723
+ defineSymbol(text, textord, "i\uFE0E", "i");
1669
1724
 
1670
1725
  // Some more letters in Unicode Basic Multilingual Plane.
1671
1726
  const narrow = "ÇÐÞçþℂℍℕℙℚℝℤℎℏℊℋℌℐℑℒℓ℘ℛℜℬℰℱℳℭℨ";
@@ -2143,7 +2198,7 @@ function buildMathML(tree, texExpression, style, settings) {
2143
2198
 
2144
2199
  const mathmlBuilder$a = (group, style) => {
2145
2200
  const accentNode = group.isStretchy
2146
- ? stretchy.mathMLnode(group.label)
2201
+ ? stretchy.accentNode(group)
2147
2202
  : new mathMLTree.MathNode("mo", [makeText(group.label, group.mode)]);
2148
2203
 
2149
2204
  if (group.label === "\\vec") {
@@ -2163,25 +2218,21 @@ const mathmlBuilder$a = (group, style) => {
2163
2218
  return node;
2164
2219
  };
2165
2220
 
2166
- const NON_STRETCHY_ACCENT_REGEX = new RegExp(
2167
- [
2168
- "\\acute",
2169
- "\\grave",
2170
- "\\ddot",
2171
- "\\dddot",
2172
- "\\ddddot",
2173
- "\\tilde",
2174
- "\\bar",
2175
- "\\breve",
2176
- "\\check",
2177
- "\\hat",
2178
- "\\vec",
2179
- "\\dot",
2180
- "\\mathring"
2181
- ]
2182
- .map((accent) => `\\${accent}`)
2183
- .join("|")
2184
- );
2221
+ const nonStretchyAccents = new Set([
2222
+ "\\acute",
2223
+ "\\grave",
2224
+ "\\ddot",
2225
+ "\\dddot",
2226
+ "\\ddddot",
2227
+ "\\tilde",
2228
+ "\\bar",
2229
+ "\\breve",
2230
+ "\\check",
2231
+ "\\hat",
2232
+ "\\vec",
2233
+ "\\dot",
2234
+ "\\mathring"
2235
+ ]);
2185
2236
 
2186
2237
  // Accents
2187
2238
  defineFunction({
@@ -2219,7 +2270,7 @@ defineFunction({
2219
2270
  handler: (context, args) => {
2220
2271
  const base = normalizeArgument(args[0]);
2221
2272
 
2222
- const isStretchy = !NON_STRETCHY_ACCENT_REGEX.test(context.funcName);
2273
+ const isStretchy = !nonStretchyAccents.has(context.funcName);
2223
2274
 
2224
2275
  return {
2225
2276
  type: "accent",
@@ -2257,7 +2308,6 @@ defineFunction({
2257
2308
  mode: mode,
2258
2309
  label: context.funcName,
2259
2310
  isStretchy: false,
2260
- isShifty: true,
2261
2311
  base: base
2262
2312
  };
2263
2313
  },
@@ -2287,7 +2337,7 @@ defineFunction({
2287
2337
  };
2288
2338
  },
2289
2339
  mathmlBuilder: (group, style) => {
2290
- const accentNode = stretchy.mathMLnode(group.label);
2340
+ const accentNode = stretchy.accentNode(group);
2291
2341
  accentNode.style["math-depth"] = 0;
2292
2342
  const node = new mathMLTree.MathNode("munder", [
2293
2343
  buildGroup$1(group.base, style),
@@ -4389,6 +4439,10 @@ const mathmlBuilder$7 = function(group, style) {
4389
4439
  // TODO: Remove -webkit- when Chromium no longer needs it.
4390
4440
  row.children[j].style.textAlign = "-webkit-" + (j % 2 ? "left" : "right");
4391
4441
  }
4442
+ if (group.addEqnNum) {
4443
+ const k = group.leqno ? 0 : row.children.length - 1;
4444
+ row.children[k].style.textAlign = "-webkit-" + (group.leqno ? "left" : "right");
4445
+ }
4392
4446
  }
4393
4447
  if (row.children.length > 1 && group.envClasses.includes("cases")) {
4394
4448
  row.children[1].style.padding = row.children[1].style.padding.replace(/0em$/, "1em");
@@ -7329,7 +7383,14 @@ defineFunctionBuilders({
7329
7383
  }
7330
7384
 
7331
7385
  if (group.sup) {
7332
- children.push(buildGroup$1(group.sup, childStyle));
7386
+ const sup = buildGroup$1(group.sup, childStyle);
7387
+ const testNode = sup.type === "mrow" ? sup.children[0] : sup;
7388
+ if ((testNode.type === "mo" && testNode.classes.includes("tml-prime"))
7389
+ && group.base && group.base.text && group.base.text === "f") {
7390
+ // Chromium does not address italic correction on prime. Prevent f′ from overlapping.
7391
+ testNode.classes.push("prime-pad");
7392
+ }
7393
+ children.push(sup);
7333
7394
  }
7334
7395
 
7335
7396
  let nodeType;
@@ -7795,6 +7856,8 @@ const smallCaps = Object.freeze({
7795
7856
 
7796
7857
  const numberRegEx = /^\d(?:[\d,.]*\d)?$/;
7797
7858
  const latinRegEx = /[A-Ba-z]/;
7859
+ const primes = new Set(["\\prime", "\\dprime", "\\trprime", "\\qprime",
7860
+ "\\backprime", "\\backdprime", "\\backtrprime"]);
7798
7861
 
7799
7862
  const italicNumber = (text, variant, tag) => {
7800
7863
  const mn = new mathMLTree.MathNode(tag, [text]);
@@ -7862,7 +7925,7 @@ defineFunctionBuilders({
7862
7925
  text.text = variantChar(text.text, variant);
7863
7926
  }
7864
7927
  node = new mathMLTree.MathNode("mtext", [text]);
7865
- } else if (group.text === "\\prime") {
7928
+ } else if (primes.has(group.text)) {
7866
7929
  node = new mathMLTree.MathNode("mo", [text]);
7867
7930
  // TODO: If/when Chromium uses ssty variant for prime, remove the next line.
7868
7931
  node.classes.push("tml-prime");
@@ -8516,6 +8579,9 @@ defineMacro("\\char", function(context) {
8516
8579
  // This macro provides a better rendering.
8517
8580
  defineMacro("\\surd", '\\sqrt{\\vphantom{|}}');
8518
8581
 
8582
+ // See comment for \oplus in symbols.js.
8583
+ defineMacro("\u2295", "\\oplus");
8584
+
8519
8585
  defineMacro("\\hbox", "\\text{#1}");
8520
8586
 
8521
8587
  // Per TeXbook p.122, "/" gets zero operator spacing.
@@ -12796,7 +12862,6 @@ class Parser {
12796
12862
  loc: SourceLocation.range(nucleus),
12797
12863
  label: command,
12798
12864
  isStretchy: false,
12799
- isShifty: true,
12800
12865
  base: symbol
12801
12866
  };
12802
12867
  }
@@ -12994,7 +13059,7 @@ class Style {
12994
13059
  * https://mit-license.org/
12995
13060
  */
12996
13061
 
12997
- const version = "0.10.15";
13062
+ const version = "0.10.16";
12998
13063
 
12999
13064
  function postProcess(block) {
13000
13065
  const labelMap = {};
@@ -14,7 +14,7 @@
14
14
  * https://mit-license.org/
15
15
  */
16
16
 
17
- const version = "0.10.15";
17
+ const version = "0.10.16";
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.15",
3
+ "version": "0.10.16",
4
4
  "description": "TeX to MathML conversion in JavaScript.",
5
5
  "main": "dist/temml.js",
6
6
  "engines": {
package/src/Parser.js CHANGED
@@ -972,7 +972,6 @@ export default class Parser {
972
972
  loc: SourceLocation.range(nucleus),
973
973
  label: command,
974
974
  isStretchy: false,
975
- isShifty: true,
976
975
  base: symbol
977
976
  };
978
977
  }
package/src/domTree.js CHANGED
@@ -134,7 +134,7 @@ export class TextNode {
134
134
  }
135
135
  }
136
136
 
137
- /**
137
+ /*
138
138
  * This node represents an image embed (<img>) element.
139
139
  */
140
140
  export class Img {
@@ -179,7 +179,7 @@ export class Img {
179
179
  markup += ` style="${utils.escape(styles)}"`;
180
180
  }
181
181
 
182
- markup += "/>";
182
+ markup += ">";
183
183
  return markup;
184
184
  }
185
185
  }
@@ -342,6 +342,10 @@ const mathmlBuilder = function(group, style) {
342
342
  // TODO: Remove -webkit- when Chromium no longer needs it.
343
343
  row.children[j].style.textAlign = "-webkit-" + (j % 2 ? "left" : "right")
344
344
  }
345
+ if (group.addEqnNum) {
346
+ const k = group.leqno ? 0 : row.children.length - 1
347
+ row.children[k].style.textAlign = "-webkit-" + (group.leqno ? "left" : "right")
348
+ }
345
349
  }
346
350
  if (row.children.length > 1 && group.envClasses.includes("cases")) {
347
351
  row.children[1].style.padding = row.children[1].style.padding.replace(/0em$/, "1em")
@@ -5,7 +5,7 @@ import * as mml from "../buildMathML"
5
5
 
6
6
  const mathmlBuilder = (group, style) => {
7
7
  const accentNode = group.isStretchy
8
- ? stretchy.mathMLnode(group.label)
8
+ ? stretchy.accentNode(group)
9
9
  : new mathMLTree.MathNode("mo", [mml.makeText(group.label, group.mode)]);
10
10
 
11
11
  if (group.label === "\\vec") {
@@ -25,25 +25,21 @@ const mathmlBuilder = (group, style) => {
25
25
  return node;
26
26
  };
27
27
 
28
- const NON_STRETCHY_ACCENT_REGEX = new RegExp(
29
- [
30
- "\\acute",
31
- "\\grave",
32
- "\\ddot",
33
- "\\dddot",
34
- "\\ddddot",
35
- "\\tilde",
36
- "\\bar",
37
- "\\breve",
38
- "\\check",
39
- "\\hat",
40
- "\\vec",
41
- "\\dot",
42
- "\\mathring"
43
- ]
44
- .map((accent) => `\\${accent}`)
45
- .join("|")
46
- );
28
+ const nonStretchyAccents = new Set([
29
+ "\\acute",
30
+ "\\grave",
31
+ "\\ddot",
32
+ "\\dddot",
33
+ "\\ddddot",
34
+ "\\tilde",
35
+ "\\bar",
36
+ "\\breve",
37
+ "\\check",
38
+ "\\hat",
39
+ "\\vec",
40
+ "\\dot",
41
+ "\\mathring"
42
+ ])
47
43
 
48
44
  // Accents
49
45
  defineFunction({
@@ -81,7 +77,7 @@ defineFunction({
81
77
  handler: (context, args) => {
82
78
  const base = normalizeArgument(args[0]);
83
79
 
84
- const isStretchy = !NON_STRETCHY_ACCENT_REGEX.test(context.funcName);
80
+ const isStretchy = !nonStretchyAccents.has(context.funcName);
85
81
 
86
82
  return {
87
83
  type: "accent",
@@ -119,7 +115,6 @@ defineFunction({
119
115
  mode: mode,
120
116
  label: context.funcName,
121
117
  isStretchy: false,
122
- isShifty: true,
123
118
  base: base
124
119
  };
125
120
  },
@@ -27,7 +27,7 @@ defineFunction({
27
27
  };
28
28
  },
29
29
  mathmlBuilder: (group, style) => {
30
- const accentNode = stretchy.mathMLnode(group.label);
30
+ const accentNode = stretchy.accentNode(group);
31
31
  accentNode.style["math-depth"] = 0
32
32
  const node = new mathMLTree.MathNode("munder", [
33
33
  mml.buildGroup(group.base, style),
@@ -53,7 +53,14 @@ defineFunctionBuilders({
53
53
  }
54
54
 
55
55
  if (group.sup) {
56
- children.push(mml.buildGroup(group.sup, childStyle))
56
+ const sup = mml.buildGroup(group.sup, childStyle)
57
+ const testNode = sup.type === "mrow" ? sup.children[0] : sup
58
+ if ((testNode.type === "mo" && testNode.classes.includes("tml-prime"))
59
+ && group.base && group.base.text && group.base.text === "f") {
60
+ // Chromium does not address italic correction on prime. Prevent f′ from overlapping.
61
+ testNode.classes.push("prime-pad")
62
+ }
63
+ children.push(sup)
57
64
  }
58
65
 
59
66
  let nodeType;
@@ -9,6 +9,8 @@ import * as mml from "../buildMathML"
9
9
 
10
10
  const numberRegEx = /^\d(?:[\d,.]*\d)?$/
11
11
  const latinRegEx = /[A-Ba-z]/
12
+ const primes = new Set(["\\prime", "\\dprime", "\\trprime", "\\qprime",
13
+ "\\backprime", "\\backdprime", "\\backtrprime"]);
12
14
 
13
15
  const italicNumber = (text, variant, tag) => {
14
16
  const mn = new mathMLTree.MathNode(tag, [text])
@@ -76,7 +78,7 @@ defineFunctionBuilders({
76
78
  text.text = variantChar(text.text, variant)
77
79
  }
78
80
  node = new mathMLTree.MathNode("mtext", [text])
79
- } else if (group.text === "\\prime") {
81
+ } else if (primes.has(group.text)) {
80
82
  node = new mathMLTree.MathNode("mo", [text])
81
83
  // TODO: If/when Chromium uses ssty variant for prime, remove the next line.
82
84
  node.classes.push("tml-prime")
package/src/macros.js CHANGED
@@ -188,6 +188,9 @@ defineMacro("\\char", function(context) {
188
188
  // This macro provides a better rendering.
189
189
  defineMacro("\\surd", '\\sqrt{\\vphantom{|}}')
190
190
 
191
+ // See comment for \oplus in symbols.js.
192
+ defineMacro("\u2295", "\\oplus")
193
+
191
194
  defineMacro("\\hbox", "\\text{#1}");
192
195
 
193
196
  // Per TeXbook p.122, "/" gets zero operator spacing.
@@ -8,7 +8,7 @@
8
8
  * https://mit-license.org/
9
9
  */
10
10
 
11
- export const version = "0.10.15";
11
+ export const version = "0.10.16";
12
12
 
13
13
  export function postProcess(block) {
14
14
  const labelMap = {}
package/src/stretchy.js CHANGED
@@ -4,6 +4,34 @@
4
4
 
5
5
  import mathMLTree from "./mathMLTree"
6
6
 
7
+ // TODO: Remove when Chromium stretches \widetilde & \widehat
8
+ const estimatedWidth = node => {
9
+ let width = 0
10
+ if (node.body) {
11
+ for (const item of node.body) {
12
+ width += estimatedWidth(item)
13
+ }
14
+ } else if (node.type === "supsub") {
15
+ width += estimatedWidth(node.base)
16
+ if (node.sub) { width += 0.7 * estimatedWidth(node.sub) }
17
+ if (node.sup) { width += 0.7 * estimatedWidth(node.sup) }
18
+ } else if (node.type === "mathord" || node.type === "textord") {
19
+ for (const ch of node.text.split('')) {
20
+ const codePoint = ch.codePointAt(0)
21
+ if ((0x60 < codePoint && codePoint < 0x7B) || (0x03B0 < codePoint && codePoint < 0x3CA)) {
22
+ width += 0.56 // lower case latin or greek. Use advance width of letter n
23
+ } else if (0x2F < codePoint && codePoint < 0x3A) {
24
+ width += 0.50 // numerals.
25
+ } else {
26
+ width += 0.92 // advance width of letter M
27
+ }
28
+ }
29
+ } else {
30
+ width += 1.0
31
+ }
32
+ return width
33
+ }
34
+
7
35
  const stretchyCodePoint = {
8
36
  widehat: "^",
9
37
  widecheck: "ˇ",
@@ -61,6 +89,25 @@ const mathMLnode = function(label) {
61
89
  return node
62
90
  }
63
91
 
92
+ const crookedWides = ["\\widetilde", "\\widehat", "\\widecheck", "\\utilde"]
93
+
94
+ // TODO: Remove when Chromium stretches \widetilde & \widehat
95
+ const accentNode = (group) => {
96
+ const mo = mathMLnode(group.label)
97
+ if (crookedWides.includes(group.label)) {
98
+ const width = estimatedWidth(group.base)
99
+ if (1 < width && width < 1.6) {
100
+ mo.classes.push("tml-crooked-2")
101
+ } else if (1.6 <= width && width < 2.5) {
102
+ mo.classes.push("tml-crooked-3")
103
+ } else if (2.5 <= width) {
104
+ mo.classes.push("tml-crooked-4")
105
+ }
106
+ }
107
+ return mo
108
+ }
109
+
64
110
  export default {
65
- mathMLnode
111
+ mathMLnode,
112
+ accentNode
66
113
  }
package/src/symbols.js CHANGED
@@ -294,6 +294,8 @@ defineSymbol(math, textord, "\u2127", "\\mho");
294
294
  defineSymbol(math, textord, "\u2132", "\\Finv", true);
295
295
  defineSymbol(math, textord, "\u2141", "\\Game", true);
296
296
  defineSymbol(math, textord, "\u2035", "\\backprime");
297
+ defineSymbol(math, textord, "\u2036", "\\backdprime");
298
+ defineSymbol(math, textord, "\u2037", "\\backtrprime");
297
299
  defineSymbol(math, textord, "\u25b2", "\\blacktriangle");
298
300
  defineSymbol(math, textord, "\u25bc", "\\blacktriangledown");
299
301
  defineSymbol(math, textord, "\u25a0", "\\blacksquare");
@@ -497,6 +499,9 @@ defineSymbol(text, textord, "\u2423", "\\textvisiblespace", true);
497
499
  defineSymbol(math, textord, "\u2220", "\\angle", true);
498
500
  defineSymbol(math, textord, "\u221e", "\\infty", true);
499
501
  defineSymbol(math, textord, "\u2032", "\\prime");
502
+ defineSymbol(math, textord, "\u2033", "\\dprime");
503
+ defineSymbol(math, textord, "\u2034", "\\trprime");
504
+ defineSymbol(math, textord, "\u2057", "\\qprime");
500
505
  defineSymbol(math, textord, "\u25b3", "\\triangle");
501
506
  defineSymbol(text, textord, "\u0391", "\\Alpha", true);
502
507
  defineSymbol(text, textord, "\u0392", "\\Beta", true);
@@ -676,7 +681,8 @@ defineSymbol(math, punct, ";", ";");
676
681
  defineSymbol(math, bin, "\u22bc", "\\barwedge", true);
677
682
  defineSymbol(math, bin, "\u22bb", "\\veebar", true);
678
683
  defineSymbol(math, bin, "\u2299", "\\odot", true);
679
- defineSymbol(math, bin, "\u2295", "\\oplus", true);
684
+ // Firefox turns ⊕ into an emoji. So append \uFE0E. Define Unicode character in macros, not here.
685
+ defineSymbol(math, bin, "\u2295\uFE0E", "\\oplus");
680
686
  defineSymbol(math, bin, "\u2297", "\\otimes", true);
681
687
  defineSymbol(math, textord, "\u2202", "\\partial", true);
682
688
  defineSymbol(math, bin, "\u2298", "\\oslash", true);
@@ -907,6 +913,8 @@ for (let i = 0; i < letters.length; i++) {
907
913
  defineSymbol(math, mathord, ch, ch);
908
914
  defineSymbol(text, textord, ch, ch);
909
915
  }
916
+ // Prevent Firefox from using a dotless i.
917
+ defineSymbol(text, textord, "i\uFE0E", "i")
910
918
 
911
919
  // Some more letters in Unicode Basic Multilingual Plane.
912
920
  const narrow = "ÇÐÞçþℂℍℕℙℚℝℤℎℏℊℋℌℐℑℒℓ℘ℛℜℬℰℱℳℭℨ";