temml 0.10.15 → 0.10.16

Sign up to get free protection for your applications and to get access to all the features.
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 = "ÇÐÞçþℂℍℕℙℚℝℤℎℏℊℋℌℐℑℒℓ℘ℛℜℬℰℱℳℭℨ";