temml 0.11.0 → 0.11.1

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.
@@ -11,7 +11,7 @@
11
11
  * https://mit-license.org/
12
12
  */
13
13
 
14
- const version = "0.11.00";
14
+ const version = "0.11.01";
15
15
 
16
16
  function postProcess(block) {
17
17
  const labelMap = {};
@@ -72,6 +72,7 @@
72
72
  mtext.appendChild(document.createTextNode(str));
73
73
  const math = document.createElementNS("http://www.w3.org/1998/Math/MathML", "math");
74
74
  math.appendChild(mtext);
75
+ ref.textContent = '';
75
76
  ref.appendChild(math);
76
77
  });
77
78
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "temml",
3
- "version": "0.11.00",
3
+ "version": "0.11.01",
4
4
  "description": "TeX to MathML conversion in JavaScript.",
5
5
  "main": "dist/temml.js",
6
6
  "engines": {
package/src/ParseError.js CHANGED
@@ -27,7 +27,7 @@ class ParseError {
27
27
  if (start === input.length) {
28
28
  error += " at end of input: ";
29
29
  } else {
30
- error += " at position " + (start + 1) + ": ";
30
+ error += " at position " + (start + 1) + ": \n";
31
31
  }
32
32
 
33
33
  // Underline token in question using combining underscores
@@ -1,5 +1,6 @@
1
1
  import defineEnvironment from "../defineEnvironment";
2
2
  import { parseCD } from "./cd";
3
+ import { bordermatrixParseTree } from "./borderTree.js"
3
4
  import defineFunction from "../defineFunction";
4
5
  import mathMLTree from "../mathMLTree";
5
6
  import { Span } from "../domTree"
@@ -103,6 +104,7 @@ function parseArray(
103
104
  },
104
105
  scriptLevel
105
106
  ) {
107
+ const endToken = envClasses && envClasses.includes("bordermatrix") ? "}" : "\\end"
106
108
  parser.gullet.beginGroup();
107
109
  if (!singleRow) {
108
110
  // \cr is equivalent to \\ without the optional size argument (see below)
@@ -176,7 +178,7 @@ function parseArray(
176
178
  }
177
179
  }
178
180
  parser.consume();
179
- } else if (next === "\\end") {
181
+ } else if (next === endToken) {
180
182
  endRow()
181
183
  // Arrays terminate newlines with `\crcr` which consumes a `\cr` if
182
184
  // the last line is empty. However, AMS environments keep the
@@ -213,7 +215,7 @@ function parseArray(
213
215
  body.push(row);
214
216
  beginRow();
215
217
  } else {
216
- throw new ParseError("Expected & or \\\\ or \\cr or \\end", parser.nextToken);
218
+ throw new ParseError("Expected & or \\\\ or \\cr or " + endToken, parser.nextToken);
217
219
  }
218
220
  }
219
221
 
@@ -346,23 +348,44 @@ const mathmlBuilder = function(group, style) {
346
348
  })
347
349
  }
348
350
  }
349
- tbl.push(mtr);
351
+
352
+ // Check for \hphantom \from \bordermatrix
353
+ let mustSquashRow = true
354
+ for (let j = 0; j < mtr.children.length; j++) {
355
+ const child = mtr.children[j].children[0];
356
+ if (!(child && child.type === "mpadded" && child.attributes.height === "0px")) {
357
+ mustSquashRow = false
358
+ break
359
+ }
360
+ }
361
+ if (mustSquashRow) {
362
+ // All the cell contents are \hphantom. Squash the padding.
363
+ for (let j = 0; j < mtr.children.length; j++) {
364
+ mtr.children[j].style.paddingTop = "0"
365
+ mtr.children[j].style.paddingBottom = "0"
366
+ }
367
+ }
368
+
369
+ tbl.push(mtr)
350
370
  }
351
371
 
352
- if (group.envClasses.length > 0) {
353
- if (group.arraystretch && group.arraystretch !== 1) {
354
- // In LaTeX, \arraystretch is a factor applied to a 12pt strut height.
355
- // It defines a baseline to baseline distance.
356
- // Here, we do an approximation of that approach.
357
- const pad = String(1.4 * group.arraystretch - 0.8) + "ex"
358
- for (let i = 0; i < tbl.length; i++) {
359
- for (let j = 0; j < tbl[i].children.length; j++) {
360
- tbl[i].children[j].style.paddingTop = pad
361
- tbl[i].children[j].style.paddingBottom = pad
362
- }
372
+ if (group.arraystretch && group.arraystretch !== 1) {
373
+ // In LaTeX, \arraystretch is a factor applied to a 12pt strut height.
374
+ // It defines a baseline to baseline distance.
375
+ // Here, we do an approximation of that approach.
376
+ const pad = String(1.4 * group.arraystretch - 0.8) + "ex"
377
+ for (let i = 0; i < tbl.length; i++) {
378
+ for (let j = 0; j < tbl[i].children.length; j++) {
379
+ tbl[i].children[j].style.paddingTop = pad
380
+ tbl[i].children[j].style.paddingBottom = pad
363
381
  }
364
382
  }
365
- let sidePadding = group.envClasses.includes("abut")
383
+ }
384
+
385
+ let sidePadding
386
+ let sidePadUnit
387
+ if (group.envClasses.length > 0) {
388
+ sidePadding = group.envClasses.includes("abut")
366
389
  ? "0"
367
390
  : group.envClasses.includes("cases")
368
391
  ? "0"
@@ -371,13 +394,14 @@ const mathmlBuilder = function(group, style) {
371
394
  : group.envClasses.includes("cd")
372
395
  ? "0.25"
373
396
  : "0.4" // default side padding
374
- let sidePadUnit = "em"
375
- if (group.arraycolsep) {
376
- const arraySidePad = calculateSize(group.arraycolsep, style)
377
- sidePadding = arraySidePad.number.toFixed(4)
378
- sidePadUnit = arraySidePad.unit
379
- }
380
-
397
+ sidePadUnit = "em"
398
+ }
399
+ if (group.arraycolsep) {
400
+ const arraySidePad = calculateSize(group.arraycolsep, style)
401
+ sidePadding = arraySidePad.number.toFixed(4)
402
+ sidePadUnit = arraySidePad.unit
403
+ }
404
+ if (sidePadding) {
381
405
  const numCols = tbl.length === 0 ? 0 : tbl[0].children.length
382
406
 
383
407
  const sidePad = (j, hand) => {
@@ -399,7 +423,18 @@ const mathmlBuilder = function(group, style) {
399
423
  tbl[i].children[j].style.paddingRight = `${sidePad(j, 1)}${sidePadUnit}`
400
424
  }
401
425
  }
426
+ }
427
+ if (group.envClasses.length === 0) {
428
+ // Set zero padding on side of the matrix
429
+ for (let i = 0; i < tbl.length; i++) {
430
+ tbl[i].children[0].style.paddingLeft = "0em"
431
+ if (tbl[i].children.length === tbl[0].children.length) {
432
+ tbl[i].children[tbl[i].children.length - 1].style.paddingRight = "0em"
433
+ }
434
+ }
435
+ }
402
436
 
437
+ if (group.envClasses.length > 0) {
403
438
  // Justification
404
439
  const align = group.envClasses.includes("align") || group.envClasses.includes("alignat")
405
440
  for (let i = 0; i < tbl.length; i++) {
@@ -425,14 +460,6 @@ const mathmlBuilder = function(group, style) {
425
460
  }
426
461
  }
427
462
  }
428
- } else {
429
- // Set zero padding on side of the matrix
430
- for (let i = 0; i < tbl.length; i++) {
431
- tbl[i].children[0].style.paddingLeft = "0em"
432
- if (tbl[i].children.length === tbl[0].children.length) {
433
- tbl[i].children[tbl[i].children.length - 1].style.paddingRight = "0em"
434
- }
435
- }
436
463
  }
437
464
 
438
465
  let table = new mathMLTree.MathNode("mtable", tbl)
@@ -672,7 +699,7 @@ defineEnvironment({
672
699
  mathmlBuilder
673
700
  });
674
701
 
675
- // The matrix environments of amsmath builds on the array environment
702
+ // The matrix environments of amsmath build on the array environment
676
703
  // of LaTeX, which is discussed above.
677
704
  // The mathtools package adds starred versions of the same environments.
678
705
  // These have an optional argument to choose left|center|right justification.
@@ -732,6 +759,10 @@ defineEnvironment({
732
759
  const res = parseArray(context.parser, payload, "text")
733
760
  res.cols = new Array(res.body[0].length).fill({ type: "align", align: colAlign })
734
761
  const [arraystretch, arraycolsep] = arrayGaps(context.parser.gullet.macros)
762
+ res.arraystretch = arraystretch
763
+ if (arraycolsep && !(arraycolsep === 6 && arraycolsep === "pt")) {
764
+ res.arraycolsep = arraycolsep
765
+ }
735
766
  return delimiters
736
767
  ? {
737
768
  type: "leftright",
@@ -739,15 +770,31 @@ defineEnvironment({
739
770
  body: [res],
740
771
  left: delimiters[0],
741
772
  right: delimiters[1],
742
- rightColor: undefined, // \right uninfluenced by \color in array
743
- arraystretch,
744
- arraycolsep
773
+ rightColor: undefined // \right uninfluenced by \color in array
745
774
  }
746
775
  : res;
747
776
  },
748
777
  mathmlBuilder
749
778
  });
750
779
 
780
+ defineEnvironment({
781
+ type: "array",
782
+ names: ["bordermatrix"],
783
+ props: {
784
+ numArgs: 0
785
+ },
786
+ handler(context) {
787
+ const payload = { cols: [], envClasses: ["bordermatrix"] }
788
+ const res = parseArray(context.parser, payload, "text")
789
+ res.cols = new Array(res.body[0].length).fill({ type: "align", align: "c" })
790
+ res.envClasses = [];
791
+ res.arraystretch = 1
792
+ if (context.envName === "matrix") { return res}
793
+ return bordermatrixParseTree(res, context.delimiters)
794
+ },
795
+ mathmlBuilder
796
+ });
797
+
751
798
  defineEnvironment({
752
799
  type: "array",
753
800
  names: ["smallmatrix"],
@@ -0,0 +1,139 @@
1
+
2
+ const ordGroup = (body) => {
3
+ return {
4
+ "type": "ordgroup",
5
+ "mode": "math",
6
+ "body": body,
7
+ "semisimple": true
8
+ }
9
+ }
10
+
11
+ const phantom = (body, type) => {
12
+ return {
13
+ "type": type,
14
+ "mode": "math",
15
+ "body": ordGroup(body)
16
+ }
17
+ }
18
+
19
+ /*
20
+ * A helper for \bordermatrix.
21
+ * parseArray() has parsed the tokens as if the environment
22
+ * was \begin{matrix}. That parse tree is this function’s input.
23
+ * Here, we rearrange the parse tree to get one that will
24
+ * result in TeX \bordermatrix.
25
+ * The final result includes a {pmatrix}, which is the bottom
26
+ * half of a <mover> element. The top of the <mover> contains
27
+ * the \bordermatrix headings. The top section also contains the
28
+ * contents of the bottom {pmatrix}. Those elements are hidden via
29
+ * \hphantom, but they ensure that column widths are the same top and
30
+ * bottom.
31
+ *
32
+ * We also create a left {matrix} with a single column that contains
33
+ * elements shifted out of the matrix. The left {matrix} also
34
+ * contains \vphantom copies of the other {pmatrix} elements.
35
+ * As before, this ensures consistent row heights of left and main.
36
+ */
37
+
38
+ export const bordermatrixParseTree = (matrix, delimiters) => {
39
+ const body = matrix.body
40
+ body[0].shift() // dispose of top left cell
41
+
42
+ // Create an array for the left column
43
+ const leftColumnBody = new Array(body.length - 1).fill().map(() => [])
44
+ for (let i = 1; i < body.length; i++) {
45
+ // The visible part of the cell
46
+ leftColumnBody[i - 1].push(body[i].shift())
47
+ // A vphantom with contents from the pmatrix, to set minimum cell height
48
+ const phantomBody = [];
49
+ for (let j = 0; j < body[i].length; j++) {
50
+ phantomBody.push(structuredClone(body[i][j]))
51
+ }
52
+ leftColumnBody[i - 1].push(phantom(phantomBody, "vphantom"))
53
+ }
54
+
55
+ // Create an array for the top row
56
+ const topRowBody = new Array(body.length).fill().map(() => [])
57
+ for (let j = 0; j < body[0].length; j++) {
58
+ topRowBody[0].push(structuredClone(body[0][j]))
59
+ }
60
+ // Copy the rest of the pmatrix, but squashed via \hphantom
61
+ for (let i = 1; i < body.length; i++) {
62
+ for (let j = 0; j < body[0].length; j++) {
63
+ topRowBody[i].push(phantom(structuredClone(body[i][j]).body, "hphantom"))
64
+ }
65
+ }
66
+
67
+ // Squash the top row of the main {pmatrix}
68
+ for (let j = 0; j < body[0].length; j++) {
69
+ body[0][j] = phantom(structuredClone(body[0][j]).body, "hphantom")
70
+ }
71
+
72
+ // Now wrap the arrays in the proper parse nodes.
73
+
74
+ const leftColumn = {
75
+ type: "array",
76
+ mode: "math",
77
+ body: leftColumnBody,
78
+ cols: [{ type: "align", align: "c" }],
79
+ rowGaps: new Array(leftColumnBody.length - 1).fill(null),
80
+ hLinesBeforeRow: new Array(leftColumnBody.length + 1).fill().map(() => []),
81
+ envClasses: [],
82
+ scriptLevel: "text",
83
+ arraystretch: 1,
84
+ labels: new Array(leftColumnBody.length).fill(""),
85
+ arraycolsep: { "number": 0.04, unit: "em" }
86
+ }
87
+
88
+ const topRow = {
89
+ type: "array",
90
+ mode: "math",
91
+ body: topRowBody,
92
+ cols: new Array(topRowBody.length).fill({ type: "align", align: "c" }),
93
+ rowGaps: new Array(topRowBody.length - 1).fill(null),
94
+ hLinesBeforeRow: new Array(topRowBody.length + 1).fill().map(() => []),
95
+ envClasses: [],
96
+ scriptLevel: "text",
97
+ arraystretch: 1,
98
+ labels: new Array(topRowBody.length).fill(""),
99
+ arraycolsep: null
100
+ }
101
+
102
+ const topWrapper = {
103
+ type: "styling",
104
+ mode: "math",
105
+ scriptLevel: "text", // Must set this explicitly.
106
+ body: [topRow] // Default level is "script".
107
+ }
108
+
109
+ const container = {
110
+ type: "leftright",
111
+ mode: "math",
112
+ body: [matrix],
113
+ left: delimiters ? delimiters[0] : "(",
114
+ right: delimiters ? delimiters[1] : ")",
115
+ rightColor: undefined
116
+ }
117
+
118
+ const base = {
119
+ type: "op", // The base of a TeX \overset
120
+ mode: "math",
121
+ limits: true,
122
+ alwaysHandleSupSub: true,
123
+ parentIsSupSub: true,
124
+ symbol: false,
125
+ stack: true,
126
+ suppressBaseShift: true,
127
+ body: [container]
128
+ }
129
+
130
+ const mover = {
131
+ type: "supsub", // We're using the MathML equivalent
132
+ mode: "math", // of TeX \overset.
133
+ base: base, // That keeps the {pmatrix} aligned with
134
+ sup: topWrapper, // the math centerline.
135
+ sub: null
136
+ }
137
+
138
+ return ordGroup([leftColumn, mover])
139
+ }
@@ -0,0 +1,42 @@
1
+ import defineFunction from "../defineFunction"
2
+ import environments from "../environments"
3
+
4
+ // \bordermatrix from TeXbook pp 177 & 361
5
+ // Optional argument from Herbert Voß, Math mode, p 20
6
+ // Ref: https://tug.ctan.org/obsolete/info/math/voss/mathmode/Mathmode.pdf
7
+
8
+ defineFunction({
9
+ type: "bordermatrix",
10
+ names: ["\\bordermatrix", "\\matrix"],
11
+ props: {
12
+ numArgs: 0,
13
+ numOptionalArgs: 1
14
+ },
15
+ handler: ({ parser, funcName }, args, optArgs) => {
16
+ // Find out if the author has defined custom delimiters
17
+ let delimiters = ["(", ")"]
18
+ if (funcName === "\\bordermatrix" && optArgs[0] && optArgs[0].body) {
19
+ const body = optArgs[0].body
20
+ if (body.length === 2 && body[0].type === "atom" && body[1].type === "atom") {
21
+ if (body[0].family === "open" && body[1].family === "close") {
22
+ delimiters = [body[0].text, body[1].text]
23
+ }
24
+ }
25
+ }
26
+ // consume the opening brace
27
+ parser.consumeSpaces()
28
+ parser.consume()
29
+
30
+ // Pass control to the environment handler in array.js.
31
+ const env = environments["bordermatrix"];
32
+ const context = {
33
+ mode: parser.mode,
34
+ envName: funcName.slice(1),
35
+ delimiters,
36
+ parser
37
+ }
38
+ const result = env.handler(context)
39
+ parser.expect("}", true)
40
+ return result
41
+ }
42
+ });
package/src/functions.js CHANGED
@@ -9,6 +9,7 @@ export default functions;
9
9
  import "./functions/accent";
10
10
  import "./functions/accentunder";
11
11
  import "./functions/arrow";
12
+ import "./functions/bordermatrix"
12
13
  //import "./functions/cancelto";
13
14
  import "./environments/cd";
14
15
  import "./functions/char";
@@ -5,7 +5,7 @@
5
5
  * https://mit-license.org/
6
6
  */
7
7
 
8
- export const version = "0.11.00";
8
+ export const version = "0.11.01";
9
9
 
10
10
  export function postProcess(block) {
11
11
  const labelMap = {}
@@ -66,6 +66,7 @@ export function postProcess(block) {
66
66
  mtext.appendChild(document.createTextNode(str))
67
67
  const math = document.createElementNS("http://www.w3.org/1998/Math/MathML", "math")
68
68
  math.appendChild(mtext)
69
+ ref.textContent = ''
69
70
  ref.appendChild(math)
70
71
  })
71
72
  }
package/temml.js CHANGED
@@ -104,7 +104,7 @@ const renderError = function(error, expression, options) {
104
104
  if (options.throwOnError || !(error instanceof ParseError)) {
105
105
  throw error;
106
106
  }
107
- const node = new Span(["temml-error"], [new TextNode(expression + "\n" + error.toString())]);
107
+ const node = new Span(["temml-error"], [new TextNode(expression + "\n\n" + error.toString())]);
108
108
  node.style.color = options.errorColor
109
109
  node.style.whiteSpace = "pre-line"
110
110
  return node;