temml 0.10.14 → 0.10.16

Sign up to get free protection for your applications and to get access to all the features.
@@ -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