temml 0.10.32 → 0.10.33

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,39 +5,47 @@
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.33";
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 eqn number.
31
+ while (true) {
32
+ const labels = parent.getElementsByClassName("tml-label");
33
+ if (labels.length > 0) {
34
+ parent.setAttribute("id", labels[0].id);
35
+ labelMap[labels[0].id] = String(i);
36
+ break
37
+ } else {
38
+ if (parent.tagName === "mtable") { break }
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
51
  labelMap[labels[0].id] = tags[0].textContent;
@@ -48,17 +56,22 @@
48
56
  // Populate \ref & \eqref text content
49
57
  const refs = block.getElementsByClassName("tml-ref");
50
58
  [...refs].forEach(ref => {
51
- let str = labelMap[ref.getAttribute("href").slice(1)];
59
+ const attr = ref.getAttribute("href");
60
+ let str = labelMap[attr.slice(1)];
52
61
  if (ref.className.indexOf("tml-eqref") === -1) {
53
62
  // \ref. Omit parens.
54
63
  str = str.replace(/^\(/, "");
55
- str = str.replace(/\($/, "");
56
- } {
64
+ str = str.replace(/\)$/, "");
65
+ } else {
57
66
  // \eqref. Include parens
58
67
  if (str.charAt(0) !== "(") { str = "(" + str; }
59
68
  if (str.slice(-1) !== ")") { str = str + ")"; }
60
69
  }
61
- ref.textContent = str;
70
+ const mtext = document.createElementNS("http://www.w3.org/1998/Math/MathML", "mtext");
71
+ mtext.appendChild(document.createTextNode(str));
72
+ const math = document.createElementNS("http://www.w3.org/1998/Math/MathML", "math");
73
+ math.appendChild(mtext);
74
+ ref.appendChild(math);
62
75
  });
63
76
  }
64
77
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "temml",
3
- "version": "0.10.32",
3
+ "version": "0.10.33",
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
  /**
@@ -277,7 +291,12 @@ export default function buildMathML(tree, texExpression, style, settings) {
277
291
  tree = tree[0].body
278
292
  }
279
293
 
280
- const expression = buildExpression(tree, style);
294
+ const expression = buildExpression(tree, style)
295
+
296
+ if (expression.length === 1 && expression[0] instanceof AnchorNode) {
297
+ return expression[0]
298
+ }
299
+
281
300
  const wrap = (settings.displayMode || settings.annotate) ? "none" : settings.wrap
282
301
 
283
302
  const n1 = expression.length === 0 ? null : expression[0]
@@ -302,6 +321,9 @@ export default function buildMathML(tree, texExpression, style, settings) {
302
321
  if (settings.xml) {
303
322
  math.setAttribute("xmlns", "http://www.w3.org/1998/Math/MathML")
304
323
  }
324
+ if (wrapper.style.width) {
325
+ math.style.width = "100%"
326
+ }
305
327
  if (settings.displayMode) {
306
328
  math.setAttribute("display", "block");
307
329
  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
  */
@@ -218,18 +218,23 @@ defineFunction({
218
218
  };
219
219
  },
220
220
  mathmlBuilder(group, style) {
221
- let label = new mathMLTree.MathNode("mrow", [mml.buildGroup(group.label, style)]);
222
- label = new mathMLTree.MathNode("mpadded", [label]);
223
- label.setAttribute("width", "0");
221
+ if (group.label.body.length === 0) {
222
+ return new mathMLTree.MathNode("mrow", style) // empty label
223
+ }
224
+ // Abuse an <mtable> to create vertically centered content.
225
+ const mtd = new mathMLTree.MathNode("mtd", [mml.buildGroup(group.label, style)])
226
+ mtd.style.padding = "0"
227
+ const mtr = new mathMLTree.MathNode("mtr", [mtd])
228
+ const mtable = new mathMLTree.MathNode("mtable", [mtr])
229
+ const label = new mathMLTree.MathNode("mpadded", [mtable])
230
+ // Set the label width to zero so that the arrow will be centered under the corner cell.
231
+ label.setAttribute("width", "0")
232
+ label.setAttribute("displaystyle", "false")
233
+ label.setAttribute("scriptlevel", "1")
224
234
  if (group.side === "left") {
225
- label.setAttribute("lspace", "-1width");
235
+ label.style.display = "flex"
236
+ label.style.justifyContent = "flex-end"
226
237
  }
227
- // We have to guess at vertical alignment. We know the arrow is 1.8em tall,
228
- // But we don't know the height or depth of the label.
229
- label.setAttribute("voffset", "0.7em");
230
- label = new mathMLTree.MathNode("mstyle", [label]);
231
- label.setAttribute("displaystyle", "false");
232
- label.setAttribute("scriptlevel", "1");
233
238
  return label;
234
239
  }
235
240
  });
@@ -12,12 +12,19 @@ const padding = width => {
12
12
  return node
13
13
  }
14
14
 
15
- const paddedNode = (group, lspace = 0.3, rspace = 0) => {
15
+ const paddedNode = (group, lspace = 0.3, rspace = 0, mustSmash = false) => {
16
16
  if (group == null && rspace === 0) { return padding(lspace) }
17
17
  const row = group ? [group] : [];
18
18
  if (lspace !== 0) { row.unshift(padding(lspace)) }
19
19
  if (rspace > 0) { row.push(padding(rspace)) }
20
- return new mathMLTree.MathNode("mrow", row)
20
+ if (mustSmash) {
21
+ // Used for the bottom arrow in a {CD} environment
22
+ const mpadded = new mathMLTree.MathNode("mpadded", row)
23
+ mpadded.setAttribute("height", "0")
24
+ return mpadded
25
+ } else {
26
+ return new mathMLTree.MathNode("mrow", row)
27
+ }
21
28
  }
22
29
 
23
30
  const labelSize = (size, scriptLevel) => Number(size) / emScale(scriptLevel);
@@ -57,7 +64,8 @@ const munderoverNode = (fName, body, below, style) => {
57
64
  (body.body.body || body.body.length > 0))
58
65
  if (gotUpper) {
59
66
  let label = mml.buildGroup(body, labelStyle)
60
- label = paddedNode(label, space, space)
67
+ const mustSmash = (fName === "\\\\cdrightarrow" || fName === "\\\\cdleftarrow")
68
+ label = paddedNode(label, space, space, mustSmash)
61
69
  // Since Firefox does not support minsize, stack a invisible node
62
70
  // on top of the label. Its width will serve as a min-width.
63
71
  // TODO: Refactor this after Firefox supports minsize.
@@ -250,10 +250,13 @@ defineFunction({
250
250
  // replacement text, enclosed in '{' and '}' and properly nested
251
251
  const { tokens } = parser.gullet.consumeArg();
252
252
 
253
- parser.gullet.macros.set(
254
- name,
255
- { tokens, numArgs }
256
- )
253
+ if (!(funcName === "\\providecommand" && parser.gullet.macros.has(name))) {
254
+ // Ignore \providecommand
255
+ parser.gullet.macros.set(
256
+ name,
257
+ { tokens, numArgs }
258
+ )
259
+ }
257
260
 
258
261
  return { type: "internal", mode: parser.mode };
259
262
 
@@ -93,6 +93,7 @@ export const delimiters = [
93
93
  "\\vert",
94
94
  "\\|",
95
95
  "\\Vert",
96
+ "\u2016",
96
97
  "\\uparrow",
97
98
  "\\Uparrow",
98
99
  "\\downarrow",
@@ -94,6 +94,7 @@ defineFunction({
94
94
  "\\mathfrak",
95
95
  "\\mathscr",
96
96
  "\\mathsf",
97
+ "\\mathsfit",
97
98
  "\\mathtt",
98
99
 
99
100
  // aliases
@@ -20,10 +20,19 @@ const mathmlBuilder = (group, style) => {
20
20
  ? style.withLevel(StyleLevel.SCRIPT)
21
21
  : style.withLevel(StyleLevel.SCRIPTSCRIPT);
22
22
 
23
- let node = new mathMLTree.MathNode("mfrac", [
24
- mml.buildGroup(group.numer, childOptions),
25
- mml.buildGroup(group.denom, childOptions)
26
- ]);
23
+ // Chromium (wrongly) continues to shrink fractions beyond scriptscriptlevel.
24
+ // So we check for levels that Chromium shrinks too small.
25
+ // If necessary, set an explicit fraction depth.
26
+ const numer = mml.buildGroup(group.numer, childOptions)
27
+ const denom = mml.buildGroup(group.denom, childOptions)
28
+ if (style.level === 3) {
29
+ numer.style.mathDepth = "2"
30
+ numer.setAttribute("scriptlevel", "2")
31
+ denom.style.mathDepth = "2"
32
+ denom.setAttribute("scriptlevel", "2")
33
+ }
34
+
35
+ let node = new mathMLTree.MathNode("mfrac", [numer, denom]);
27
36
 
28
37
  if (!group.hasBarLine) {
29
38
  node.setAttribute("linethickness", "0px");
@@ -1,6 +1,7 @@
1
1
  import defineFunction, { ordargument } from "../defineFunction";
2
2
  import { assertNodeType } from "../parseNode";
3
3
  import { MathNode } from "../mathMLTree";
4
+ import { AnchorNode } from "../domTree";
4
5
  import * as mml from "../buildMathML";
5
6
  import ParseError from "../ParseError";
6
7
 
@@ -33,12 +34,9 @@ defineFunction({
33
34
  };
34
35
  },
35
36
  mathmlBuilder: (group, style) => {
36
- let math = mml.buildExpressionRow(group.body, style);
37
- if (!(math instanceof MathNode)) {
38
- math = new MathNode("mrow", [math]);
39
- }
40
- math.setAttribute("href", group.href);
41
- return math;
37
+ const math = new MathNode("math", [mml.buildExpressionRow(group.body, style)])
38
+ const anchorNode = new AnchorNode(group.href, [], [math])
39
+ return anchorNode
42
40
  }
43
41
  });
44
42
 
@@ -32,13 +32,6 @@ const mathmlBuilder = (group, style) => {
32
32
  node = new mathMLTree.MathNode("mo", [mml.makeText(group.name, group.mode)]);
33
33
  if (noSuccessor.includes(group.name)) {
34
34
  node.setAttribute("largeop", "false")
35
- } else if (group.limits) {
36
- // This is a workaround for a MathML/Chromium bug.
37
- // This is being applied to singleCharBigOps, which are not really stretchy.
38
- // But by setting the stretchy attribute, Chromium will vertically center
39
- // big ops around the math axis. This is needed since STIX TWO does not do so.
40
- // TODO: Remove this hack when MathML & Chromium fix their problem.
41
- node.setAttribute("stretchy", "true")
42
35
  } else {
43
36
  node.setAttribute("movablelimits", "false")
44
37
  }
@@ -1,5 +1,5 @@
1
1
  import defineFunction from "../defineFunction";
2
- import mathMLTree from "../mathMLTree";
2
+ import { AnchorNode } from "../domTree";
3
3
  import { invalidIdRegEx } from "./label";
4
4
 
5
5
  defineFunction({
@@ -18,11 +18,9 @@ defineFunction({
18
18
  };
19
19
  },
20
20
  mathmlBuilder(group, style) {
21
- // Create an empty text node. Set a class and an href.
21
+ // Create an empty <a> node. Set a class and an href attribute.
22
22
  // The post-processor will populate with the target's tag or equation number.
23
23
  const classes = group.funcName === "\\ref" ? ["tml-ref"] : ["tml-ref", "tml-eqref"]
24
- const node = new mathMLTree.MathNode("mtext", [new mathMLTree.TextNode("")], classes)
25
- node.setAttribute("href", "#" + group.string)
26
- return node
24
+ return new AnchorNode("#" + group.string, classes, null)
27
25
  }
28
26
  });
@@ -9,6 +9,8 @@ defineFunction({
9
9
  props: {
10
10
  numArgs: 2,
11
11
  numOptionalArgs: 1,
12
+ allowedInText: true,
13
+ allowedInMath: true,
12
14
  argTypes: ["size", "size", "size"]
13
15
  },
14
16
  handler({ parser }, args, optArgs) {
@@ -45,18 +45,27 @@ defineFunctionBuilders({
45
45
 
46
46
  const children = group.base && group.base.stack
47
47
  ? [mml.buildGroup(group.base.body[0], style)]
48
- : [mml.buildGroup(group.base, style)]
48
+ : [mml.buildGroup(group.base, style)];
49
+
50
+ // Note regarding scriptstyle level.
51
+ // (Sub|super)scripts should not shrink beyond MathML scriptlevel 2 aka \scriptscriptstyle
52
+ // Ref: https://w3c.github.io/mathml-core/#the-displaystyle-and-scriptlevel-attributes
53
+ // (BTW, MathML scriptlevel 2 is equal to Temml level 3.)
54
+ // But Chromium continues to shrink the (sub|super)scripts. So we explicitly set scriptlevel 2.
49
55
 
50
56
  const childStyle = style.inSubOrSup()
51
57
  if (group.sub) {
52
- children.push(mml.buildGroup(group.sub, childStyle))
58
+ const sub = mml.buildGroup(group.sub, childStyle)
59
+ if (style.level === 3) { sub.setAttribute("scriptlevel", "2") }
60
+ children.push(sub)
53
61
  }
54
62
 
55
63
  if (group.sup) {
56
64
  const sup = mml.buildGroup(group.sup, childStyle)
65
+ if (style.level === 3) { sup.setAttribute("scriptlevel", "2") }
57
66
  const testNode = sup.type === "mrow" ? sup.children[0] : sup
58
67
  if ((testNode && testNode.type === "mo" && testNode.classes.includes("tml-prime"))
59
- && group.base && group.base.text && group.base.text === "f") {
68
+ && group.base && group.base.text && "fF".indexOf(group.base.text) > -1) {
60
69
  // Chromium does not address italic correction on prime. Prevent f′ from overlapping.
61
70
  testNode.classes.push("prime-pad")
62
71
  }
@@ -0,0 +1,30 @@
1
+ import defineFunction from "../defineFunction";
2
+ import mathMLTree from "../mathMLTree";
3
+ import * as mml from "../buildMathML";
4
+
5
+ // \vcenter: Vertically center the argument group on the math axis.
6
+
7
+ defineFunction({
8
+ type: "vcenter",
9
+ names: ["\\vcenter"],
10
+ props: {
11
+ numArgs: 1,
12
+ argTypes: ["original"],
13
+ allowedInText: false
14
+ },
15
+ handler({ parser }, args) {
16
+ return {
17
+ type: "vcenter",
18
+ mode: parser.mode,
19
+ body: args[0]
20
+ };
21
+ },
22
+ mathmlBuilder(group, style) {
23
+ // Use a math table to create vertically centered content.
24
+ const mtd = new mathMLTree.MathNode("mtd", [mml.buildGroup(group.body, style)])
25
+ mtd.style.padding = "0"
26
+ const mtr = new mathMLTree.MathNode("mtr", [mtd])
27
+ return new mathMLTree.MathNode("mtable", [mtr])
28
+ }
29
+ });
30
+
package/src/functions.js CHANGED
@@ -56,4 +56,5 @@ import "./functions/tag";
56
56
  import "./functions/text";
57
57
  // import "./functions/tip";
58
58
  // import "./functions/toggle";
59
+ import "./functions/vcenter";
59
60
  import "./functions/verb";
package/src/macros.js CHANGED
@@ -243,7 +243,7 @@ defineMacro("\\underbar", "\\underline{\\text{#1}}");
243
243
  // \kern6\p@\hbox{.}\hbox{.}\hbox{.}}}
244
244
  // We'll call \varvdots, which gets a glyph from symbols.js.
245
245
  // The zero-width rule gets us an equivalent to the vertical 6pt kern.
246
- defineMacro("\\vdots", "\\TextOrMath{\\textvdots}{{\\varvdots\\rule{0pt}{15pt}}}\\relax");
246
+ defineMacro("\\vdots", "{\\varvdots\\rule{0pt}{15pt}}")
247
247
  defineMacro("\u22ee", "\\vdots");
248
248
 
249
249
  // {array} environment gaps