temml 0.10.32 → 0.10.34

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.
@@ -5,42 +5,51 @@
5
5
  })(this, (function (exports) { 'use strict';
6
6
 
7
7
  /* Temml Post Process
8
- * Perform two tasks not done by Temml when it created each individual Temml <math> element.
9
- * Given a block,
10
- * 1. At each AMS auto-numbered environment, assign an id.
11
- * 2. Populate the text contents of each \ref & \eqref
8
+ * Populate the text contents of each \ref & \eqref
12
9
  *
13
10
  * As with other Temml code, this file is released under terms of the MIT license.
14
11
  * https://mit-license.org/
15
12
  */
16
13
 
17
- const version = "0.10.32";
14
+ const version = "0.10.34";
18
15
 
19
16
  function postProcess(block) {
20
17
  const labelMap = {};
21
18
  let i = 0;
22
19
 
23
20
  // Get a collection of the parents of each \tag & auto-numbered equation
24
- const parents = block.getElementsByClassName("tml-tageqn");
25
- for (const parent of parents) {
26
- const eqns = parent.getElementsByClassName("tml-eqn");
27
- if (eqns. length > 0 ) {
28
- // AMS automatically numbered equation.
29
- // Assign an id.
30
- i += 1;
31
- eqns[0].id = "tml-eqn-" + i;
32
- // No need to write a number into the text content of the element.
33
- // A CSS counter does that even if this postProcess() function is not used.
21
+ const amsEqns = document.getElementsByClassName('tml-eqn');
22
+ for (let parent of amsEqns) {
23
+ // AMS automatically numbered equation.
24
+ // Assign an id.
25
+ i += 1;
26
+ parent.setAttribute("id", "tml-eqn-" + String(i));
27
+ // No need to write a number into the text content of the element.
28
+ // A CSS counter has done that even if this postProcess() function is not used.
29
+
30
+ // Find any \label that refers to an AMS automatic eqn number.
31
+ while (true) {
32
+ if (parent.tagName === "mtable") { break }
33
+ const labels = parent.getElementsByClassName("tml-label");
34
+ if (labels.length > 0) {
35
+ const id = parent.attributes.id.value;
36
+ labelMap[id] = String(i);
37
+ break
38
+ } else {
39
+ parent = parent.parentElement;
40
+ }
34
41
  }
35
- // If there is a \label, add it to labelMap
42
+ }
43
+
44
+ // Find \labels associated with \tag
45
+ const taggedEqns = document.getElementsByClassName('tml-tageqn');
46
+ for (const parent of taggedEqns) {
36
47
  const labels = parent.getElementsByClassName("tml-label");
37
- if (labels.length === 0) { continue }
38
- if (eqns.length > 0) {
39
- labelMap[labels[0].id] = String(i);
40
- } else {
48
+ if (labels.length > 0) {
41
49
  const tags = parent.getElementsByClassName("tml-tag");
42
50
  if (tags.length > 0) {
43
- labelMap[labels[0].id] = tags[0].textContent;
51
+ const id = parent.attributes.id.value;
52
+ labelMap[id] = tags[0].textContent;
44
53
  }
45
54
  }
46
55
  }
@@ -48,17 +57,22 @@
48
57
  // Populate \ref & \eqref text content
49
58
  const refs = block.getElementsByClassName("tml-ref");
50
59
  [...refs].forEach(ref => {
51
- let str = labelMap[ref.getAttribute("href").slice(1)];
60
+ const attr = ref.getAttribute("href");
61
+ let str = labelMap[attr.slice(1)];
52
62
  if (ref.className.indexOf("tml-eqref") === -1) {
53
63
  // \ref. Omit parens.
54
64
  str = str.replace(/^\(/, "");
55
- str = str.replace(/\($/, "");
56
- } {
65
+ str = str.replace(/\)$/, "");
66
+ } else {
57
67
  // \eqref. Include parens
58
68
  if (str.charAt(0) !== "(") { str = "(" + str; }
59
69
  if (str.slice(-1) !== ")") { str = str + ")"; }
60
70
  }
61
- ref.textContent = str;
71
+ const mtext = document.createElementNS("http://www.w3.org/1998/Math/MathML", "mtext");
72
+ mtext.appendChild(document.createTextNode(str));
73
+ const math = document.createElementNS("http://www.w3.org/1998/Math/MathML", "math");
74
+ math.appendChild(mtext);
75
+ ref.appendChild(math);
62
76
  });
63
77
  }
64
78
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "temml",
3
- "version": "0.10.32",
3
+ "version": "0.10.34",
4
4
  "description": "TeX to MathML conversion in JavaScript.",
5
5
  "main": "dist/temml.js",
6
6
  "engines": {
package/src/Style.js CHANGED
@@ -16,9 +16,11 @@ class Style {
16
16
  constructor(data) {
17
17
  // Style.level can be 0 | 1 | 2 | 3, which correspond to
18
18
  // displaystyle, textstyle, scriptstyle, and scriptscriptstyle.
19
- // style.level does not directly set MathML's script level. MathML does that itself.
20
- // We use style.level to track, not set, math style so that we can get the
21
- // correct scriptlevel when needed in supsub.js, mathchoice.js, or for dimensions in em.
19
+ // style.level usually does not directly set MathML's script level. MathML does that itself.
20
+ // However, Chromium does not stop shrinking after scriptscriptstyle, so we do explicitly
21
+ // set a scriptlevel attribute in those conditions.
22
+ // We also use style.level to track math style so that we can get the correct
23
+ // scriptlevel when needed in supsub.js, mathchoice.js, or for dimensions in em.
22
24
  this.level = data.level;
23
25
  this.color = data.color; // string | void
24
26
  // A font family applies to a group of fonts (i.e. SansSerif), while a font
@@ -8,9 +8,10 @@ import mathMLTree from "./mathMLTree"
8
8
  import ParseError from "./ParseError"
9
9
  import symbols, { ligatures } from "./symbols"
10
10
  import { _mathmlGroupBuilders as groupBuilders } from "./defineFunction"
11
- import { MathNode } from "./mathMLTree"
11
+ import { MathNode, TextNode } from "./mathMLTree"
12
12
  import { DocumentFragment } from "./tree"
13
13
  import setLineBreaks from "./linebreaking"
14
+ import { AnchorNode } from "./domTree"
14
15
 
15
16
  /**
16
17
  * Takes a symbol and converts it into a MathML text node after performing
@@ -102,61 +103,6 @@ export const consolidateText = mrow => {
102
103
  }
103
104
  }
104
105
 
105
- const numberRegEx = /^[0-9]$/
106
- const isDotOrComma = (node, followingNode) => {
107
- return ((node.type === "textord" && node.text === ".") ||
108
- (node.type === "atom" && node.text === ",")) &&
109
- // Don't consolidate if there is a space after the comma.
110
- node.loc && followingNode.loc && node.loc.end === followingNode.loc.start
111
- }
112
- const consolidateNumbers = expression => {
113
- // Consolidate adjacent numbers. We want to return <mn>1,506.3</mn>,
114
- // not <mn>1</mn><mo>,</mo><mn>5</mn><mn>0</mn><mn>6</mn><mi>.</mi><mn>3</mn>
115
- if (expression.length < 2) { return }
116
- const nums = [];
117
- let inNum = false
118
- // Find adjacent numerals
119
- for (let i = 0; i < expression.length; i++) {
120
- const node = expression[i];
121
- if (node.type === "textord" && numberRegEx.test(node.text)) {
122
- if (!inNum) { nums.push({ start: i }) }
123
- inNum = true
124
- } else {
125
- if (inNum) { nums[nums.length - 1].end = i - 1 }
126
- inNum = false
127
- }
128
- }
129
- if (inNum) { nums[nums.length - 1].end = expression.length - 1 }
130
-
131
- // Determine if numeral groups are separated by a comma or dot.
132
- for (let i = nums.length - 1; i > 0; i--) {
133
- if (nums[i - 1].end === nums[i].start - 2 &&
134
- isDotOrComma(expression[nums[i].start - 1], expression[nums[i].start])) {
135
- // Merge the two groups.
136
- nums[i - 1].end = nums[i].end
137
- nums.splice(i, 1)
138
- }
139
- }
140
-
141
- // Consolidate the number nodes
142
- for (let i = nums.length - 1; i >= 0; i--) {
143
- for (let j = nums[i].start + 1; j <= nums[i].end; j++) {
144
- expression[nums[i].start].text += expression[j].text
145
- }
146
- expression.splice(nums[i].start + 1, nums[i].end - nums[i].start)
147
- // Check if the <mn> is followed by a numeric base in a supsub, e.g. the "3" in 123^4
148
- // If so, merge the first <mn> into the base.
149
- if (expression.length > nums[i].start + 1) {
150
- const nextTerm = expression[nums[i].start + 1];
151
- if (nextTerm.type === "supsub" && nextTerm.base && nextTerm.base.type === "textord" &&
152
- numberRegEx.test(nextTerm.base.text)) {
153
- nextTerm.base.text = expression[nums[i].start].text + nextTerm.base.text
154
- expression.splice(nums[i].start, 1)
155
- }
156
- }
157
- }
158
- }
159
-
160
106
  /**
161
107
  * Wrap the given array of nodes in an <mrow> node if needed, i.e.,
162
108
  * unless the array has length 1. Always returns a single node.
@@ -179,6 +125,39 @@ export const makeRow = function(body, semisimple = false) {
179
125
  return new mathMLTree.MathNode("mrow", body);
180
126
  };
181
127
 
128
+ /**
129
+ * Check for <mi>.</mi> which is how a dot renders in MathML,
130
+ * or <mo separator="true" lspace="0em" rspace="0em">,</mo>
131
+ * which is how a braced comma {,} renders in MathML
132
+ */
133
+ function isNumberPunctuation(group) {
134
+ if (!group) {
135
+ return false
136
+ }
137
+ if (group.type === 'mi' && group.children.length === 1) {
138
+ const child = group.children[0];
139
+ return child instanceof TextNode && child.text === '.'
140
+ } else if (group.type === "mtext" && group.children.length === 1) {
141
+ const child = group.children[0];
142
+ return child instanceof TextNode && child.text === '\u2008' // punctuation space
143
+ } else if (group.type === 'mo' && group.children.length === 1 &&
144
+ group.getAttribute('separator') === 'true' &&
145
+ group.getAttribute('lspace') === '0em' &&
146
+ group.getAttribute('rspace') === '0em') {
147
+ const child = group.children[0];
148
+ return child instanceof TextNode && child.text === ','
149
+ } else {
150
+ return false
151
+ }
152
+ }
153
+ const isComma = (expression, i) => {
154
+ const node = expression[i];
155
+ const followingNode = expression[i + 1];
156
+ return (node.type === "atom" && node.text === ",") &&
157
+ // Don't consolidate if there is a space after the comma.
158
+ node.loc && followingNode.loc && node.loc.end === followingNode.loc.start
159
+ }
160
+
182
161
  const isRel = item => {
183
162
  return (item.type === "atom" && item.family === "rel") ||
184
163
  (item.type === "mclass" && item.mclass === "mrel")
@@ -202,11 +181,16 @@ export const buildExpression = function(expression, style, semisimple = false) {
202
181
  return [group];
203
182
  }
204
183
 
205
- consolidateNumbers(expression)
206
-
207
184
  const groups = [];
185
+ const groupArray = [];
186
+ let lastGroup
208
187
  for (let i = 0; i < expression.length; i++) {
209
- const group = buildGroup(expression[i], style);
188
+ groupArray.push(buildGroup(expression[i], style))
189
+ }
190
+
191
+ for (let i = 0; i < groupArray.length; i++) {
192
+ const group = groupArray[i];
193
+
210
194
  // Suppress spacing between adjacent relations
211
195
  if (i < expression.length - 1 && isRel(expression[i]) && isRel(expression[i + 1])) {
212
196
  group.setAttribute("rspace", "0em")
@@ -214,9 +198,39 @@ export const buildExpression = function(expression, style, semisimple = false) {
214
198
  if (i > 0 && isRel(expression[i]) && isRel(expression[i - 1])) {
215
199
  group.setAttribute("lspace", "0em")
216
200
  }
217
- groups.push(group);
201
+
202
+ // Concatenate numbers
203
+ if (group.type === 'mn' && lastGroup && lastGroup.type === 'mn') {
204
+ // Concatenate <mn>...</mn> followed by <mi>.</mi>
205
+ lastGroup.children.push(...group.children)
206
+ continue
207
+ } else if (isNumberPunctuation(group) && lastGroup && lastGroup.type === 'mn') {
208
+ // Concatenate <mn>...</mn> followed by <mi>.</mi>
209
+ lastGroup.children.push(...group.children)
210
+ continue
211
+ } else if (lastGroup && lastGroup.type === "mn" && i < groupArray.length - 1 &&
212
+ groupArray[i + 1].type === "mn" && isComma(expression, i)) {
213
+ lastGroup.children.push(...group.children)
214
+ continue
215
+ } else if (group.type === 'mn' && isNumberPunctuation(lastGroup)) {
216
+ // Concatenate <mi>.</mi> followed by <mn>...</mn>
217
+ group.children = [...lastGroup.children, ...group.children];
218
+ groups.pop()
219
+ } else if ((group.type === 'msup' || group.type === 'msub') &&
220
+ group.children.length >= 1 && lastGroup &&
221
+ (lastGroup.type === 'mn' || isNumberPunctuation(lastGroup))) {
222
+ // Put preceding <mn>...</mn> or <mi>.</mi> inside base of
223
+ // <msup><mn>...base...</mn>...exponent...</msup> (or <msub>)
224
+ const base = group.children[0];
225
+ if (base instanceof MathNode && base.type === 'mn' && lastGroup) {
226
+ base.children = [...lastGroup.children, ...base.children];
227
+ groups.pop()
228
+ }
229
+ }
230
+ groups.push(group)
231
+ lastGroup = group
218
232
  }
219
- return groups;
233
+ return groups
220
234
  };
221
235
 
222
236
  /**
@@ -249,16 +263,36 @@ const glue = _ => {
249
263
  return new mathMLTree.MathNode("mtd", [], [], { padding: "0", width: "50%" })
250
264
  }
251
265
 
266
+ const labelContainers = ["mrow", "mtd", "mtable", "mtr"];
267
+ const getLabel = parent => {
268
+ for (const node of parent.children) {
269
+ if (node.type && labelContainers.includes(node.type)) {
270
+ if (node.classes && node.classes[0] === "tml-label") {
271
+ const label = node.label
272
+ return label
273
+ } else {
274
+ const label = getLabel(node)
275
+ if (label) { return label }
276
+ }
277
+ } else if (!node.type) {
278
+ const label = getLabel(node)
279
+ if (label) { return label }
280
+ }
281
+ }
282
+ }
283
+
252
284
  const taggedExpression = (expression, tag, style, leqno) => {
253
285
  tag = buildExpressionRow(tag[0].body, style)
254
286
  tag = consolidateText(tag)
255
287
  tag.classes.push("tml-tag")
256
288
 
289
+ const label = getLabel(expression) // from a \label{} function.
257
290
  expression = new mathMLTree.MathNode("mtd", [expression])
258
291
  const rowArray = [glue(), expression, glue()]
259
292
  rowArray[leqno ? 0 : 2].classes.push(leqno ? "tml-left" : "tml-right")
260
293
  rowArray[leqno ? 0 : 2].children.push(tag)
261
294
  const mtr = new mathMLTree.MathNode("mtr", rowArray, ["tml-tageqn"])
295
+ if (label) { mtr.setAttribute("id", label) }
262
296
  const table = new mathMLTree.MathNode("mtable", [mtr])
263
297
  table.style.width = "100%"
264
298
  table.setAttribute("displaystyle", "true")
@@ -277,7 +311,12 @@ export default function buildMathML(tree, texExpression, style, settings) {
277
311
  tree = tree[0].body
278
312
  }
279
313
 
280
- const expression = buildExpression(tree, style);
314
+ const expression = buildExpression(tree, style)
315
+
316
+ if (expression.length === 1 && expression[0] instanceof AnchorNode) {
317
+ return expression[0]
318
+ }
319
+
281
320
  const wrap = (settings.displayMode || settings.annotate) ? "none" : settings.wrap
282
321
 
283
322
  const n1 = expression.length === 0 ? null : expression[0]
@@ -302,6 +341,9 @@ export default function buildMathML(tree, texExpression, style, settings) {
302
341
  if (settings.xml) {
303
342
  math.setAttribute("xmlns", "http://www.w3.org/1998/Math/MathML")
304
343
  }
344
+ if (wrapper.style.width) {
345
+ math.style.width = "100%"
346
+ }
305
347
  if (settings.displayMode) {
306
348
  math.setAttribute("display", "block");
307
349
  math.style.display = "block math" // necessary in Chromium.
package/src/domTree.js CHANGED
@@ -134,6 +134,40 @@ export class TextNode {
134
134
  }
135
135
  }
136
136
 
137
+ // Create an <a href="…"> node.
138
+ export class AnchorNode {
139
+ constructor(href, classes, children) {
140
+ this.href = href;
141
+ this.classes = classes;
142
+ this.children = children || [];
143
+ }
144
+
145
+ toNode() {
146
+ const node = document.createElement("a")
147
+ node.setAttribute("href", this.href)
148
+ if (this.classes.length > 0) {
149
+ node.className = createClass(this.classes)
150
+ }
151
+ for (let i = 0; i < this.children.length; i++) {
152
+ node.appendChild(this.children[i].toNode());
153
+ }
154
+ return node
155
+ }
156
+
157
+ toMarkup() {
158
+ let markup = `<a href='${utils.escape(this.href)}'`
159
+ if (this.classes.length > 0) {
160
+ markup += ` class="${utils.escape(createClass(this.classes))}"`
161
+ }
162
+ markup += ">"
163
+ for (let i = 0; i < this.children.length; i++) {
164
+ markup += this.children[i].toMarkup();
165
+ }
166
+ markup += "</a>"
167
+ return markup
168
+ }
169
+ }
170
+
137
171
  /*
138
172
  * This node represents an image embed (<img>) element.
139
173
  */