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.
@@ -3,6 +3,7 @@ import { parseCD } from "./cd";
3
3
  import defineFunction from "../defineFunction";
4
4
  import mathMLTree from "../mathMLTree";
5
5
  import { Span } from "../domTree"
6
+ import { Token } from "../Token";
6
7
  import { StyleLevel } from "../constants"
7
8
  import ParseError from "../ParseError";
8
9
  import { assertNodeType, assertSymbolNodeType } from "../parseNode";
@@ -57,31 +58,28 @@ const arrayGaps = macros => {
57
58
  return [arraystretch, arraycolsep]
58
59
  }
59
60
 
60
- const getTag = (group, style, rowNum) => {
61
- let tag
62
- const tagContents = group.tags.shift()
63
- if (tagContents) {
64
- // The author has written a \tag or a \notag in this row.
65
- if (tagContents.body) {
66
- tag = mml.buildExpressionRow(tagContents.body, style, true)
67
- tag.classes = ["tml-tag"]
68
- } else {
69
- // \notag. Return an empty span.
70
- tag = new mathMLTree.MathNode("mtext", [], [])
71
- return tag
61
+ const checkCellForLabels = cell => {
62
+ // Check if the author wrote a \tag{} inside this cell.
63
+ let rowLabel = ""
64
+ for (let i = 0; i < cell.length; i++) {
65
+ if (cell[i].type === "label") {
66
+ if (rowLabel) { throw new ParseError(("Multiple \\labels in one row")) }
67
+ rowLabel = cell[i].string
72
68
  }
73
- } else if (group.envClasses.includes("multline") &&
74
- ((group.leqno && rowNum !== 0) || (!group.leqno && rowNum !== group.body.length - 1))) {
75
- // A multiline that does not receive a tag. Return an empty cell.
76
- tag = new mathMLTree.MathNode("mtext", [], [])
77
- return tag
78
- } else {
79
- // AMS automatcally numbered equaton.
80
- // Insert a class so the element can be populated by a CSS counter.
81
- // WebKit will display the CSS counter only inside a span.
82
- tag = new mathMLTree.MathNode("mtext", [new Span(["tml-eqn"])])
83
69
  }
84
- return tag
70
+ return rowLabel
71
+ }
72
+
73
+ // autoTag (an argument to parseArray) can be one of three values:
74
+ // * undefined: Regular (not-top-level) array; no tags on each row
75
+ // * true: Automatic equation numbering, overridable by \tag
76
+ // * false: Tags allowed on each row, but no automatic numbering
77
+ // This function *doesn't* work with the "split" environment name.
78
+ function getAutoTag(name) {
79
+ if (name.indexOf("ed") === -1) {
80
+ return name.indexOf("*") === -1;
81
+ }
82
+ // return undefined;
85
83
  }
86
84
 
87
85
  /**
@@ -95,7 +93,7 @@ function parseArray(
95
93
  {
96
94
  cols, // [{ type: string , align: l|c|r|null }]
97
95
  envClasses, // align(ed|at|edat) | array | cases | cd | small | multline
98
- addEqnNum, // boolean
96
+ autoTag, // boolean
99
97
  singleRow, // boolean
100
98
  emptySingleRow, // boolean
101
99
  maxNumCols, // number
@@ -111,13 +109,6 @@ function parseArray(
111
109
  // TODO: provide helpful error when \cr is used outside array environment
112
110
  parser.gullet.macros.set("\\cr", "\\\\\\relax");
113
111
  }
114
- if (addEqnNum) {
115
- parser.gullet.macros.set("\\tag", "\\@ifstar\\envtag@literal\\envtag@paren")
116
- parser.gullet.macros.set("\\envtag@paren", "\\env@tag{{(\\text{#1})}}");
117
- parser.gullet.macros.set("\\envtag@literal", "\\env@tag{\\text{#1}}")
118
- parser.gullet.macros.set("\\notag", "\\env@notag");
119
- parser.gullet.macros.set("\\nonumber", "\\env@notag")
120
- }
121
112
 
122
113
  // Start group for first cell
123
114
  parser.gullet.beginGroup();
@@ -125,29 +116,39 @@ function parseArray(
125
116
  let row = [];
126
117
  const body = [row];
127
118
  const rowGaps = [];
128
- const tags = [];
129
- let rowTag;
119
+ const labels = [];
120
+
130
121
  const hLinesBeforeRow = [];
131
122
 
123
+ const tags = (autoTag != null ? [] : undefined);
124
+
125
+ // amsmath uses \global\@eqnswtrue and \global\@eqnswfalse to represent
126
+ // whether this row should have an equation number. Simulate this with
127
+ // a \@eqnsw macro set to 1 or 0.
128
+ function beginRow() {
129
+ if (autoTag) {
130
+ parser.gullet.macros.set("\\@eqnsw", "1", true);
131
+ }
132
+ }
133
+ function endRow() {
134
+ if (tags) {
135
+ if (parser.gullet.macros.get("\\df@tag")) {
136
+ tags.push(parser.subparse([new Token("\\df@tag")]));
137
+ parser.gullet.macros.set("\\df@tag", undefined, true);
138
+ } else {
139
+ tags.push(Boolean(autoTag) &&
140
+ parser.gullet.macros.get("\\@eqnsw") === "1");
141
+ }
142
+ }
143
+ }
144
+ beginRow();
145
+
132
146
  // Test for \hline at the top of the array.
133
147
  hLinesBeforeRow.push(getHLines(parser));
134
148
 
135
149
  while (true) {
136
150
  // Parse each cell in its own group (namespace)
137
151
  let cell = parser.parseExpression(false, singleRow ? "\\end" : "\\\\");
138
-
139
- if (addEqnNum && !rowTag) {
140
- // Check if the author wrote a \tag{} inside this cell.
141
- for (let i = 0; i < cell.length; i++) {
142
- if (cell[i].type === "envTag" || cell[i].type === "noTag") {
143
- // Get the contents of the \text{} nested inside the \env@Tag{}
144
- rowTag = cell[i].type === "envTag"
145
- ? cell.splice(i, 1)[0].body.body[0]
146
- : { body: null };
147
- break
148
- }
149
- }
150
- }
151
152
  parser.gullet.endGroup();
152
153
  parser.gullet.beginGroup();
153
154
 
@@ -176,6 +177,7 @@ function parseArray(
176
177
  }
177
178
  parser.consume();
178
179
  } else if (next === "\\end") {
180
+ endRow()
179
181
  // Arrays terminate newlines with `\crcr` which consumes a `\cr` if
180
182
  // the last line is empty. However, AMS environments keep the
181
183
  // empty row if it's the only one.
@@ -183,6 +185,7 @@ function parseArray(
183
185
  if (row.length === 1 && cell.body.length === 0 && (body.length > 1 || !emptySingleRow)) {
184
186
  body.pop();
185
187
  }
188
+ labels.push(checkCellForLabels(cell.body))
186
189
  if (hLinesBeforeRow.length < body.length + 1) {
187
190
  hLinesBeforeRow.push([]);
188
191
  }
@@ -198,16 +201,17 @@ function parseArray(
198
201
  if (parser.gullet.future().text !== " ") {
199
202
  size = parser.parseSizeGroup(true);
200
203
  }
201
- rowGaps.push(size ? size.value : null);
204
+ rowGaps.push(size ? size.value : null)
205
+ endRow()
202
206
 
203
- tags.push(rowTag)
207
+ labels.push(checkCellForLabels(cell.body))
204
208
 
205
209
  // check for \hline(s) following the row separator
206
210
  hLinesBeforeRow.push(getHLines(parser));
207
211
 
208
212
  row = [];
209
- rowTag = null;
210
213
  body.push(row);
214
+ beginRow();
211
215
  } else {
212
216
  throw new ParseError("Expected & or \\\\ or \\cr or \\end", parser.nextToken);
213
217
  }
@@ -218,8 +222,6 @@ function parseArray(
218
222
  // End array group defining \cr
219
223
  parser.gullet.endGroup();
220
224
 
221
- tags.push(rowTag)
222
-
223
225
  return {
224
226
  type: "array",
225
227
  mode: parser.mode,
@@ -228,9 +230,10 @@ function parseArray(
228
230
  rowGaps,
229
231
  hLinesBeforeRow,
230
232
  envClasses,
231
- addEqnNum,
233
+ autoTag,
232
234
  scriptLevel,
233
235
  tags,
236
+ labels,
234
237
  leqno,
235
238
  arraystretch,
236
239
  arraycolsep
@@ -287,19 +290,43 @@ const mathmlBuilder = function(group, style) {
287
290
  }
288
291
  row.push(mtd)
289
292
  }
290
- if (group.addEqnNum) {
291
- row.unshift(glue(group));
292
- row.push(glue(group));
293
- const tag = getTag(group, style.withLevel(cellLevel), i)
294
- if (group.leqno) {
295
- row[0].children.push(tag)
296
- row[0].classes.push("tml-left")
297
- } else {
298
- row[row.length - 1].children.push(tag)
299
- row[row.length - 1].classes.push("tml-right")
293
+ const numColumns = group.body[0].length
294
+ // Fill out a short row with empty <mtd> elements.
295
+ for (let k = 0; k < numColumns - rw.length; k++) {
296
+ row.push(new mathMLTree.MathNode("mtd", [], style))
297
+ }
298
+ if (group.autoTag) {
299
+ const tag = group.tags[i];
300
+ let tagElement
301
+ if (tag === true) { // automatic numbering
302
+ tagElement = new mathMLTree.MathNode("mtext", [new Span(["tml-eqn"])])
303
+ } else if (tag === false) {
304
+ // \nonumber/\notag or starred environment
305
+ tagElement = new mathMLTree.MathNode("mtext", [], [])
306
+ } else { // manual \tag
307
+ tagElement = mml.buildExpressionRow(tag[0].body, style.withLevel(cellLevel), true)
308
+ tagElement = mml.consolidateText(tagElement)
309
+ tagElement.classes = ["tml-tag"]
310
+ }
311
+ if (tagElement) {
312
+ row.unshift(glue(group))
313
+ row.push(glue(group))
314
+ if (group.leqno) {
315
+ row[0].children.push(tagElement)
316
+ row[0].classes.push("tml-left")
317
+ } else {
318
+ row[row.length - 1].children.push(tagElement)
319
+ row[row.length - 1].classes.push("tml-right")
320
+ }
300
321
  }
301
322
  }
302
323
  const mtr = new mathMLTree.MathNode("mtr", row, [])
324
+ const label = group.labels.shift()
325
+ if (label && group.tags && group.tags[i]) {
326
+ mtr.setAttribute("id", label)
327
+ if (Array.isArray(group.tags[i])) { mtr.classes.push("tml-tageqn") }
328
+ }
329
+
303
330
  // Write horizontal rules
304
331
  if (i === 0 && hlines[0].length > 0) {
305
332
  if (hlines[0].length === 2) {
@@ -323,16 +350,17 @@ const mathmlBuilder = function(group, style) {
323
350
  }
324
351
 
325
352
  if (group.envClasses.length > 0) {
326
- let pad = group.envClasses.includes("jot")
327
- ? "0.7" // 0.5ex + 0.09em top & bot padding
328
- : group.envClasses.includes("small")
329
- ? "0.35"
330
- : "0.5" // 0.5ex default top & bot padding
331
353
  if (group.arraystretch && group.arraystretch !== 1) {
332
354
  // In LaTeX, \arraystretch is a factor applied to a 12pt strut height.
333
355
  // It defines a baseline to baseline distance.
334
356
  // Here, we do an approximation of that approach.
335
- pad = String(1.4 * group.arraystretch - 0.8)
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
+ }
363
+ }
336
364
  }
337
365
  let sidePadding = group.envClasses.includes("abut")
338
366
  ? "0"
@@ -346,7 +374,7 @@ const mathmlBuilder = function(group, style) {
346
374
  let sidePadUnit = "em"
347
375
  if (group.arraycolsep) {
348
376
  const arraySidePad = calculateSize(group.arraycolsep, style)
349
- sidePadding = arraySidePad.number
377
+ sidePadding = arraySidePad.number.toFixed(4)
350
378
  sidePadUnit = arraySidePad.unit
351
379
  }
352
380
 
@@ -357,18 +385,18 @@ const mathmlBuilder = function(group, style) {
357
385
  if (j === numCols - 1 && hand === 1) { return "0" }
358
386
  if (group.envClasses[0] !== "align") { return sidePadding }
359
387
  if (hand === 1) { return "0" }
360
- if (group.addEqnNum) {
388
+ if (group.autoTag) {
361
389
  return (j % 2) ? "1" : "0"
362
390
  } else {
363
391
  return (j % 2) ? "0" : "1"
364
392
  }
365
393
  }
366
394
 
367
- // Padding
395
+ // Side padding
368
396
  for (let i = 0; i < tbl.length; i++) {
369
397
  for (let j = 0; j < tbl[i].children.length; j++) {
370
- tbl[i].children[j].style.padding = `${pad}ex ${sidePad(j, 1)}${sidePadUnit}`
371
- + ` ${pad}ex ${sidePad(j, 0)}${sidePadUnit}`
398
+ tbl[i].children[j].style.paddingLeft = `${sidePad(j, 0)}${sidePadUnit}`
399
+ tbl[i].children[j].style.paddingRight = `${sidePad(j, 1)}${sidePadUnit}`
372
400
  }
373
401
  }
374
402
 
@@ -382,13 +410,13 @@ const mathmlBuilder = function(group, style) {
382
410
  // TODO: Remove -webkit- when Chromium no longer needs it.
383
411
  row.children[j].classes = ["tml-" + (j % 2 ? "left" : "right")]
384
412
  }
385
- if (group.addEqnNum) {
413
+ if (group.autoTag) {
386
414
  const k = group.leqno ? 0 : row.children.length - 1
387
415
  row.children[k].classes = ["tml-" + (group.leqno ? "left" : "right")]
388
416
  }
389
417
  }
390
418
  if (row.children.length > 1 && group.envClasses.includes("cases")) {
391
- row.children[1].style.padding = row.children[1].style.padding.replace(/0em$/, "1em")
419
+ row.children[1].style.paddingLeft = "1em"
392
420
  }
393
421
 
394
422
  if (group.envClasses.includes("cases") || group.envClasses.includes("subarray")) {
@@ -408,9 +436,17 @@ const mathmlBuilder = function(group, style) {
408
436
  }
409
437
 
410
438
  let table = new mathMLTree.MathNode("mtable", tbl)
439
+ if (group.envClasses.length > 0) {
440
+ // Top & bottom padding
441
+ if (group.envClasses.includes("jot")) {
442
+ table.classes.push("tml-jot")
443
+ } else if (group.envClasses.includes("small")) {
444
+ table.classes.push("tml-small")
445
+ }
446
+ }
411
447
  if (group.scriptLevel === "display") { table.setAttribute("displaystyle", "true") }
412
448
 
413
- if (group.addEqnNum || group.envClasses.includes("multline")) {
449
+ if (group.autoTag || group.envClasses.includes("multline")) {
414
450
  table.style.width = "100%"
415
451
  }
416
452
 
@@ -440,7 +476,7 @@ const mathmlBuilder = function(group, style) {
440
476
  row.children[0].style.borderLeft = sep
441
477
  }
442
478
  }
443
- let iCol = group.addEqnNum ? 0 : -1
479
+ let iCol = group.autoTag ? 0 : -1
444
480
  for (let i = iStart; i < iEnd; i++) {
445
481
  if (cols[i].type === "align") {
446
482
  const colAlign = alignMap[cols[i].align];
@@ -482,7 +518,7 @@ const mathmlBuilder = function(group, style) {
482
518
  }
483
519
  }
484
520
  }
485
- if (group.addEqnNum) {
521
+ if (group.autoTag) {
486
522
  // allow for glue cells on each side
487
523
  align = "left " + (align.length > 0 ? align : "center ") + "right "
488
524
  }
@@ -506,13 +542,14 @@ const alignedHandler = function(context, args) {
506
542
  if (context.envName.indexOf("ed") === -1) {
507
543
  validateAmsEnvironmentContext(context);
508
544
  }
545
+ const isSplit = context.envName === "split";
509
546
  const cols = [];
510
547
  const res = parseArray(
511
548
  context.parser,
512
549
  {
513
550
  cols,
514
- addEqnNum: context.envName === "align" || context.envName === "alignat",
515
551
  emptySingleRow: true,
552
+ autoTag: isSplit ? undefined : getAutoTag(context.envName),
516
553
  envClasses: ["abut", "jot"], // set row spacing & provisional column spacing
517
554
  maxNumCols: context.envName === "split" ? 2 : undefined,
518
555
  leqno: context.parser.settings.leqno
@@ -836,7 +873,7 @@ defineEnvironment({
836
873
  const res = {
837
874
  cols: [],
838
875
  envClasses: ["abut", "jot"],
839
- addEqnNum: context.envName === "gather",
876
+ autoTag: getAutoTag(context.envName),
840
877
  emptySingleRow: true,
841
878
  leqno: context.parser.settings.leqno
842
879
  };
@@ -854,7 +891,7 @@ defineEnvironment({
854
891
  handler(context) {
855
892
  validateAmsEnvironmentContext(context);
856
893
  const res = {
857
- addEqnNum: context.envName === "equation",
894
+ autoTag: getAutoTag(context.envName),
858
895
  emptySingleRow: true,
859
896
  singleRow: true,
860
897
  maxNumCols: 1,
@@ -875,7 +912,7 @@ defineEnvironment({
875
912
  handler(context) {
876
913
  validateAmsEnvironmentContext(context);
877
914
  const res = {
878
- addEqnNum: context.envName === "multline",
915
+ autoTag: context.envName === "multline",
879
916
  maxNumCols: 1,
880
917
  envClasses: ["jot", "multline"],
881
918
  leqno: context.parser.settings.leqno
@@ -190,6 +190,8 @@ export function parseCD(parser) {
190
190
  type: "array",
191
191
  mode: "math",
192
192
  body,
193
+ tags: null,
194
+ labels: new Array(body.length + 1).fill(""),
193
195
  envClasses: ["jot", "cd"],
194
196
  cols: [],
195
197
  hLinesBeforeRow: new Array(body.length + 1).fill([])
@@ -218,18 +220,23 @@ defineFunction({
218
220
  };
219
221
  },
220
222
  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");
223
+ if (group.label.body.length === 0) {
224
+ return new mathMLTree.MathNode("mrow", style) // empty label
225
+ }
226
+ // Abuse an <mtable> to create vertically centered content.
227
+ const mtd = new mathMLTree.MathNode("mtd", [mml.buildGroup(group.label, style)])
228
+ mtd.style.padding = "0"
229
+ const mtr = new mathMLTree.MathNode("mtr", [mtd])
230
+ const mtable = new mathMLTree.MathNode("mtable", [mtr])
231
+ const label = new mathMLTree.MathNode("mpadded", [mtable])
232
+ // Set the label width to zero so that the arrow will be centered under the corner cell.
233
+ label.setAttribute("width", "0")
234
+ label.setAttribute("displaystyle", "false")
235
+ label.setAttribute("scriptlevel", "1")
224
236
  if (group.side === "left") {
225
- label.setAttribute("lspace", "-1width");
237
+ label.style.display = "flex"
238
+ label.style.justifyContent = "flex-end"
226
239
  }
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
240
  return label;
234
241
  }
235
242
  });
@@ -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
 
@@ -22,7 +22,7 @@ defineFunction({
22
22
  // Return a no-width, no-ink element with an HTML id.
23
23
  const node = new mathMLTree.MathNode("mrow", [], ["tml-label"])
24
24
  if (group.string.length > 0) {
25
- node.setAttribute("id", group.string)
25
+ node.setLabel(group.string)
26
26
  }
27
27
  return node
28
28
  }
@@ -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
  }