temml 0.10.14 → 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.
@@ -276,24 +276,98 @@ const mathmlBuilder = function(group, style) {
276
276
  // Write horizontal rules
277
277
  if (i === 0 && hlines[0].length > 0) {
278
278
  if (hlines[0].length === 2) {
279
- mtr.classes.push("tml-top-double")
279
+ mtr.children.forEach(cell => { cell.style.borderTop = "0.15em double" })
280
280
  } else {
281
- mtr.classes.push(hlines[0][0] ? "tml-top-dashed" : "tml-top-solid")
281
+ mtr.children.forEach(cell => {
282
+ cell.style.borderTop = hlines[0][0] ? "0.06em dashed" : "0.06em solid"
283
+ })
282
284
  }
283
285
  }
284
286
  if (hlines[i + 1].length > 0) {
285
287
  if (hlines[i + 1].length === 2) {
286
- mtr.classes.push("tml-hline-double")
288
+ mtr.children.forEach(cell => { cell.style.borderBottom = "0.15em double" })
287
289
  } else {
288
- mtr.classes.push(hlines[i + 1][0] ? "tml-hline-dashed" : "tml-hline-solid")
290
+ mtr.children.forEach(cell => {
291
+ cell.style.borderBottom = hlines[i + 1][0] ? "0.06em dashed" : "0.06em solid"
292
+ })
289
293
  }
290
294
  }
291
295
  tbl.push(mtr);
292
296
  }
293
- let table = new mathMLTree.MathNode("mtable", tbl)
297
+
294
298
  if (group.envClasses.length > 0) {
295
- table.classes = group.envClasses.map(e => "tml-" + e)
299
+ const pad = group.envClasses.includes("jot")
300
+ ? "0.7" // 0.5ex + 0.09em top & bot padding
301
+ : group.envClasses.includes("small")
302
+ ? "0.35"
303
+ : "0.5" // 0.5ex default top & bot padding
304
+ const sidePadding = group.envClasses.includes("abut")
305
+ ? "0"
306
+ : group.envClasses.includes("cases")
307
+ ? "0"
308
+ : group.envClasses.includes("small")
309
+ ? "0.1389"
310
+ : group.envClasses.includes("cd")
311
+ ? "0.25"
312
+ : "0.4" // default side padding
313
+
314
+ const numCols = tbl.length === 0 ? 0 : tbl[0].children.length
315
+
316
+ const sidePad = (j, hand) => {
317
+ if (j === 0 && hand === 0) { return "0" }
318
+ if (j === numCols - 1 && hand === 1) { return "0" }
319
+ if (group.envClasses[0] !== "align") { return sidePadding }
320
+ if (hand === 1) { return "0" }
321
+ if (group.addEqnNum) {
322
+ return (j % 2) ? "1" : "0"
323
+ } else {
324
+ return (j % 2) ? "0" : "1"
325
+ }
326
+ }
327
+
328
+ // Padding
329
+ for (let i = 0; i < tbl.length; i++) {
330
+ for (let j = 0; j < tbl[i].children.length; j++) {
331
+ tbl[i].children[j].style.padding = `${pad}ex ${sidePad(j, 1)}em ${pad}ex ${sidePad(j, 0)}em`
332
+ }
333
+ }
334
+
335
+ // Justification
336
+ const align = group.envClasses.includes("align") || group.envClasses.includes("alignat")
337
+ for (let i = 0; i < tbl.length; i++) {
338
+ const row = tbl[i];
339
+ if (align) {
340
+ for (let j = 0; j < row.children.length; j++) {
341
+ // Chromium does not recognize text-align: left. Use -webkit-
342
+ // TODO: Remove -webkit- when Chromium no longer needs it.
343
+ row.children[j].style.textAlign = "-webkit-" + (j % 2 ? "left" : "right")
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
+ }
349
+ }
350
+ if (row.children.length > 1 && group.envClasses.includes("cases")) {
351
+ row.children[1].style.padding = row.children[1].style.padding.replace(/0em$/, "1em")
352
+ }
353
+
354
+ if (group.envClasses.includes("cases") || group.envClasses.includes("subarray")) {
355
+ for (const cell of row.children) {
356
+ cell.style.textAlign = "-webkit-" + "left"
357
+ }
358
+ }
359
+ }
360
+ } else {
361
+ // Set zero padding on side of the matrix
362
+ for (let i = 0; i < tbl.length; i++) {
363
+ tbl[i].children[0].style.paddingLeft = "0em"
364
+ if (tbl[i].children.length === tbl[0].children.length) {
365
+ tbl[i].children[tbl[i].children.length - 1].style.paddingRight = "0em"
366
+ }
367
+ }
296
368
  }
369
+
370
+ let table = new mathMLTree.MathNode("mtable", tbl)
297
371
  if (group.scriptLevel === "display") { table.setAttribute("displaystyle", "true") }
298
372
 
299
373
  if (group.addEqnNum || group.envClasses.includes("multline")) {
@@ -373,6 +447,8 @@ const mathmlBuilder = function(group, style) {
373
447
  align = "left " + (align.length > 0 ? align : "center ") + "right "
374
448
  }
375
449
  if (align) {
450
+ // Firefox reads this attribute, not the -webkit-left|right written above.
451
+ // TODO: When Chrome no longer needs "-webkit-", use CSS and delete the next line.
376
452
  table.setAttribute("columnalign", align.trim())
377
453
  }
378
454
 
@@ -397,7 +473,7 @@ const alignedHandler = function(context, args) {
397
473
  cols,
398
474
  addEqnNum: context.envName === "align" || context.envName === "alignat",
399
475
  emptySingleRow: true,
400
- envClasses: ["jot", "abut"], // set row spacing & provisional column spacing
476
+ envClasses: ["abut", "jot"], // set row spacing & provisional column spacing
401
477
  maxNumCols: context.envName === "split" ? 2 : undefined,
402
478
  leqno: context.parser.settings.leqno
403
479
  },
@@ -415,18 +491,22 @@ const alignedHandler = function(context, args) {
415
491
  // binary. This behavior is implemented in amsmath's \start@aligned.
416
492
  let numMaths;
417
493
  let numCols = 0;
418
- if (args[0] && args[0].type === "ordgroup") {
419
- let arg0 = "";
494
+ const isAlignedAt = context.envName.indexOf("at") > -1
495
+ if (args[0] && isAlignedAt) {
496
+ // alignat environment takes an argument w/ number of columns
497
+ let arg0 = ""
420
498
  for (let i = 0; i < args[0].body.length; i++) {
421
- const textord = assertNodeType(args[0].body[i], "textord");
422
- arg0 += textord.text;
499
+ const textord = assertNodeType(args[0].body[i], "textord")
500
+ arg0 += textord.text
501
+ }
502
+ if (isNaN(arg0)) {
503
+ throw new ParseError("The alignat enviroment requires a numeric first argument.")
423
504
  }
424
- numMaths = Number(arg0);
425
- numCols = numMaths * 2;
505
+ numMaths = Number(arg0)
506
+ numCols = numMaths * 2
426
507
  }
427
- const isAligned = !numCols;
428
508
  res.body.forEach(function(row) {
429
- if (!isAligned) {
509
+ if (isAlignedAt) {
430
510
  // Case 1
431
511
  const curMaths = row.length / 2;
432
512
  if (numMaths < curMaths) {
@@ -456,14 +536,10 @@ const alignedHandler = function(context, args) {
456
536
  }
457
537
  if (context.envName === "split") {
458
538
  // Append no more classes
459
- } else if (context.envName.indexOf("ed") > -1) {
460
- res.envClasses.push("aligned") // Sets justification
461
- } else if (isAligned) {
462
- res.envClasses[1] = context.envName === "align*"
463
- ? "align-star"
464
- : "align" // Sets column spacing & justification
539
+ } else if (isAlignedAt) {
540
+ res.envClasses.push("alignat") // Sets justification
465
541
  } else {
466
- res.envClasses.push("aligned") // Sets justification
542
+ res.envClasses[0] = "align" // Sets column spacing & justification
467
543
  }
468
544
  return res;
469
545
  };
@@ -713,7 +789,7 @@ defineEnvironment({
713
789
  }
714
790
  const res = {
715
791
  cols: [],
716
- envClasses: ["jot", "abut"],
792
+ envClasses: ["abut", "jot"],
717
793
  addEqnNum: context.envName === "gather",
718
794
  emptySingleRow: true,
719
795
  leqno: context.parser.settings.leqno
@@ -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),
@@ -34,10 +34,20 @@ const mathmlBuilder = (group, style) => {
34
34
  node.style.borderBottom = "0.065em solid"
35
35
  break
36
36
  case "\\cancel":
37
- node.classes.push("cancel")
37
+ node.style.background = `linear-gradient(to top left,
38
+ rgba(0,0,0,0) 0%,
39
+ rgba(0,0,0,0) calc(50% - 0.06em),
40
+ rgba(0,0,0,1) 50%,
41
+ rgba(0,0,0,0) calc(50% + 0.06em),
42
+ rgba(0,0,0,0) 100%);`
38
43
  break
39
44
  case "\\bcancel":
40
- node.classes.push("bcancel")
45
+ node.style.background = `linear-gradient(to top right,
46
+ rgba(0,0,0,0) 0%,
47
+ rgba(0,0,0,0) calc(50% - 0.06em),
48
+ rgba(0,0,0,1) 50%,
49
+ rgba(0,0,0,0) calc(50% + 0.06em),
50
+ rgba(0,0,0,0) 100%);`
41
51
  break
42
52
  /*
43
53
  case "\\longdiv":
@@ -81,7 +91,18 @@ const mathmlBuilder = (group, style) => {
81
91
  break
82
92
  }
83
93
  case "\\xcancel":
84
- node.classes.push("xcancel")
94
+ node.style.background = `linear-gradient(to top left,
95
+ rgba(0,0,0,0) 0%,
96
+ rgba(0,0,0,0) calc(50% - 0.06em),
97
+ rgba(0,0,0,1) 50%,
98
+ rgba(0,0,0,0) calc(50% + 0.06em),
99
+ rgba(0,0,0,0) 100%),
100
+ linear-gradient(to top right,
101
+ rgba(0,0,0,0) 0%,
102
+ rgba(0,0,0,0) calc(50% - 0.06em),
103
+ rgba(0,0,0,1) 50%,
104
+ rgba(0,0,0,0) calc(50% + 0.06em),
105
+ rgba(0,0,0,0) 100%);`
85
106
  break
86
107
  }
87
108
  if (group.backgroundColor) {
@@ -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.14";
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
@@ -212,6 +212,10 @@ defineSymbol(math, mathord, "\u21af", "\\lightning", true);
212
212
  defineSymbol(math, mathord, "\u220E", "\\QED", true);
213
213
  defineSymbol(math, mathord, "\u2030", "\\permil", true);
214
214
  defineSymbol(text, textord, "\u2030", "\\permil");
215
+ defineSymbol(math, mathord, "\u2609", "\\astrosun", true);
216
+ defineSymbol(math, mathord, "\u263c", "\\sun", true);
217
+ defineSymbol(math, mathord, "\u263e", "\\leftmoon", true);
218
+ defineSymbol(math, mathord, "\u263d", "\\rightmoon", true);
215
219
 
216
220
  // AMS Negated Binary Relations
217
221
  defineSymbol(math, rel, "\u226e", "\\nless", true);
@@ -290,6 +294,8 @@ defineSymbol(math, textord, "\u2127", "\\mho");
290
294
  defineSymbol(math, textord, "\u2132", "\\Finv", true);
291
295
  defineSymbol(math, textord, "\u2141", "\\Game", true);
292
296
  defineSymbol(math, textord, "\u2035", "\\backprime");
297
+ defineSymbol(math, textord, "\u2036", "\\backdprime");
298
+ defineSymbol(math, textord, "\u2037", "\\backtrprime");
293
299
  defineSymbol(math, textord, "\u25b2", "\\blacktriangle");
294
300
  defineSymbol(math, textord, "\u25bc", "\\blacktriangledown");
295
301
  defineSymbol(math, textord, "\u25a0", "\\blacksquare");
@@ -493,6 +499,9 @@ defineSymbol(text, textord, "\u2423", "\\textvisiblespace", true);
493
499
  defineSymbol(math, textord, "\u2220", "\\angle", true);
494
500
  defineSymbol(math, textord, "\u221e", "\\infty", true);
495
501
  defineSymbol(math, textord, "\u2032", "\\prime");
502
+ defineSymbol(math, textord, "\u2033", "\\dprime");
503
+ defineSymbol(math, textord, "\u2034", "\\trprime");
504
+ defineSymbol(math, textord, "\u2057", "\\qprime");
496
505
  defineSymbol(math, textord, "\u25b3", "\\triangle");
497
506
  defineSymbol(text, textord, "\u0391", "\\Alpha", true);
498
507
  defineSymbol(text, textord, "\u0392", "\\Beta", true);
@@ -672,7 +681,8 @@ defineSymbol(math, punct, ";", ";");
672
681
  defineSymbol(math, bin, "\u22bc", "\\barwedge", true);
673
682
  defineSymbol(math, bin, "\u22bb", "\\veebar", true);
674
683
  defineSymbol(math, bin, "\u2299", "\\odot", true);
675
- 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");
676
686
  defineSymbol(math, bin, "\u2297", "\\otimes", true);
677
687
  defineSymbol(math, textord, "\u2202", "\\partial", true);
678
688
  defineSymbol(math, bin, "\u2298", "\\oslash", true);
@@ -903,6 +913,8 @@ for (let i = 0; i < letters.length; i++) {
903
913
  defineSymbol(math, mathord, ch, ch);
904
914
  defineSymbol(text, textord, ch, ch);
905
915
  }
916
+ // Prevent Firefox from using a dotless i.
917
+ defineSymbol(text, textord, "i\uFE0E", "i")
906
918
 
907
919
  // Some more letters in Unicode Basic Multilingual Plane.
908
920
  const narrow = "ÇÐÞçþℂℍℕℙℚℝℤℎℏℊℋℌℐℑℒℓ℘ℛℜℬℰℱℳℭℨ";
package/temml.js CHANGED
@@ -21,6 +21,7 @@ import defineMacro from "./src/defineMacro";
21
21
  import { postProcess, version } from "./src/postProcess";
22
22
 
23
23
  /**
24
+ * @type {import('./temml').render}
24
25
  * Parse and build an expression, and place that expression in the DOM node
25
26
  * given.
26
27
  */
@@ -58,6 +59,7 @@ if (typeof document !== "undefined") {
58
59
  }
59
60
 
60
61
  /**
62
+ * @type {import('./temml').renderToString}
61
63
  * Parse and build an expression, and return the markup for that.
62
64
  */
63
65
  const renderToString = function(expression, options) {
@@ -66,6 +68,7 @@ const renderToString = function(expression, options) {
66
68
  };
67
69
 
68
70
  /**
71
+ * @type {import('./temml').generateParseTree}
69
72
  * Parse an expression and return the parse tree.
70
73
  */
71
74
  const generateParseTree = function(expression, options) {
@@ -74,6 +77,7 @@ const generateParseTree = function(expression, options) {
74
77
  };
75
78
 
76
79
  /**
80
+ * @type {import('./temml').definePreamble}
77
81
  * Take an expression which contains a preamble.
78
82
  * Parse it and return the macros.
79
83
  */
@@ -106,6 +110,7 @@ const renderError = function(error, expression, options) {
106
110
  };
107
111
 
108
112
  /**
113
+ * @type {import('./temml').renderToMathMLTree}
109
114
  * Generates and returns the Temml build tree. This is used for advanced
110
115
  * use cases (like rendering to custom output).
111
116
  */
@@ -123,6 +128,7 @@ const renderToMathMLTree = function(expression, options) {
123
128
  }
124
129
  };
125
130
 
131
+ /** @type {import('./temml').default} */
126
132
  export default {
127
133
  /**
128
134
  * Current Temml version