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.
package/dist/temml.mjs CHANGED
@@ -499,6 +499,40 @@ let TextNode$1 = class TextNode {
499
499
  }
500
500
  };
501
501
 
502
+ // Create an <a href="…"> node.
503
+ class AnchorNode {
504
+ constructor(href, classes, children) {
505
+ this.href = href;
506
+ this.classes = classes;
507
+ this.children = children || [];
508
+ }
509
+
510
+ toNode() {
511
+ const node = document.createElement("a");
512
+ node.setAttribute("href", this.href);
513
+ if (this.classes.length > 0) {
514
+ node.className = createClass(this.classes);
515
+ }
516
+ for (let i = 0; i < this.children.length; i++) {
517
+ node.appendChild(this.children[i].toNode());
518
+ }
519
+ return node
520
+ }
521
+
522
+ toMarkup() {
523
+ let markup = `<a href='${utils.escape(this.href)}'`;
524
+ if (this.classes.length > 0) {
525
+ markup += ` class="${utils.escape(createClass(this.classes))}"`;
526
+ }
527
+ markup += ">";
528
+ for (let i = 0; i < this.children.length; i++) {
529
+ markup += this.children[i].toMarkup();
530
+ }
531
+ markup += "</a>";
532
+ return markup
533
+ }
534
+ }
535
+
502
536
  /*
503
537
  * This node represents an image embed (<img>) element.
504
538
  */
@@ -573,6 +607,7 @@ class MathNode {
573
607
  this.children = children || [];
574
608
  this.classes = classes || [];
575
609
  this.style = style || {}; // Used for <mstyle> elements
610
+ this.label = "";
576
611
  }
577
612
 
578
613
  /**
@@ -590,6 +625,10 @@ class MathNode {
590
625
  return this.attributes[name];
591
626
  }
592
627
 
628
+ setLabel(value) {
629
+ this.label = value;
630
+ }
631
+
593
632
  /**
594
633
  * Converts the math node into a MathML-namespaced DOM element.
595
634
  */
@@ -1506,7 +1545,7 @@ defineSymbol(math, mathord, "\u03db", "\\stigma", true);
1506
1545
  defineSymbol(math, mathord, "\u2aeb", "\\Bot");
1507
1546
  defineSymbol(math, bin, "\u2217", "\u2217", true);
1508
1547
  defineSymbol(math, bin, "+", "+");
1509
- defineSymbol(math, bin, "*", "*");
1548
+ defineSymbol(math, bin, "\u2217", "*");
1510
1549
  defineSymbol(math, bin, "\u2044", "/", true);
1511
1550
  defineSymbol(math, bin, "\u2044", "\u2044");
1512
1551
  defineSymbol(math, bin, "\u2212", "-", true);
@@ -1528,7 +1567,7 @@ defineSymbol(math, open, "\u27e8", "\\langle", true);
1528
1567
  defineSymbol(math, open, "\u27ea", "\\lAngle", true);
1529
1568
  defineSymbol(math, open, "\u2989", "\\llangle", true);
1530
1569
  defineSymbol(math, open, "|", "\\lvert");
1531
- defineSymbol(math, open, "\u2016", "\\lVert");
1570
+ defineSymbol(math, open, "\u2016", "\\lVert", true);
1532
1571
  defineSymbol(math, textord, "!", "\\oc"); // cmll package
1533
1572
  defineSymbol(math, textord, "?", "\\wn");
1534
1573
  defineSymbol(math, textord, "\u2193", "\\shpos");
@@ -1692,7 +1731,7 @@ defineSymbol(math, inner, "\u22f0", "\\iddots", true);
1692
1731
  defineSymbol(math, inner, "\u22ef", "\\@cdots", true);
1693
1732
  defineSymbol(math, inner, "\u22f1", "\\ddots", true);
1694
1733
  defineSymbol(math, textord, "\u22ee", "\\varvdots"); // \vdots is a macro
1695
- defineSymbol(text, textord, "\u22ee", "\\textvdots");
1734
+ defineSymbol(text, textord, "\u22ee", "\\varvdots");
1696
1735
  defineSymbol(math, accent, "\u02ca", "\\acute");
1697
1736
  defineSymbol(math, accent, "\u0060", "\\grave");
1698
1737
  defineSymbol(math, accent, "\u00a8", "\\ddot");
@@ -2145,61 +2184,6 @@ const consolidateText = mrow => {
2145
2184
  }
2146
2185
  };
2147
2186
 
2148
- const numberRegEx$1 = /^[0-9]$/;
2149
- const isDotOrComma = (node, followingNode) => {
2150
- return ((node.type === "textord" && node.text === ".") ||
2151
- (node.type === "atom" && node.text === ",")) &&
2152
- // Don't consolidate if there is a space after the comma.
2153
- node.loc && followingNode.loc && node.loc.end === followingNode.loc.start
2154
- };
2155
- const consolidateNumbers = expression => {
2156
- // Consolidate adjacent numbers. We want to return <mn>1,506.3</mn>,
2157
- // not <mn>1</mn><mo>,</mo><mn>5</mn><mn>0</mn><mn>6</mn><mi>.</mi><mn>3</mn>
2158
- if (expression.length < 2) { return }
2159
- const nums = [];
2160
- let inNum = false;
2161
- // Find adjacent numerals
2162
- for (let i = 0; i < expression.length; i++) {
2163
- const node = expression[i];
2164
- if (node.type === "textord" && numberRegEx$1.test(node.text)) {
2165
- if (!inNum) { nums.push({ start: i }); }
2166
- inNum = true;
2167
- } else {
2168
- if (inNum) { nums[nums.length - 1].end = i - 1; }
2169
- inNum = false;
2170
- }
2171
- }
2172
- if (inNum) { nums[nums.length - 1].end = expression.length - 1; }
2173
-
2174
- // Determine if numeral groups are separated by a comma or dot.
2175
- for (let i = nums.length - 1; i > 0; i--) {
2176
- if (nums[i - 1].end === nums[i].start - 2 &&
2177
- isDotOrComma(expression[nums[i].start - 1], expression[nums[i].start])) {
2178
- // Merge the two groups.
2179
- nums[i - 1].end = nums[i].end;
2180
- nums.splice(i, 1);
2181
- }
2182
- }
2183
-
2184
- // Consolidate the number nodes
2185
- for (let i = nums.length - 1; i >= 0; i--) {
2186
- for (let j = nums[i].start + 1; j <= nums[i].end; j++) {
2187
- expression[nums[i].start].text += expression[j].text;
2188
- }
2189
- expression.splice(nums[i].start + 1, nums[i].end - nums[i].start);
2190
- // Check if the <mn> is followed by a numeric base in a supsub, e.g. the "3" in 123^4
2191
- // If so, merge the first <mn> into the base.
2192
- if (expression.length > nums[i].start + 1) {
2193
- const nextTerm = expression[nums[i].start + 1];
2194
- if (nextTerm.type === "supsub" && nextTerm.base && nextTerm.base.type === "textord" &&
2195
- numberRegEx$1.test(nextTerm.base.text)) {
2196
- nextTerm.base.text = expression[nums[i].start].text + nextTerm.base.text;
2197
- expression.splice(nums[i].start, 1);
2198
- }
2199
- }
2200
- }
2201
- };
2202
-
2203
2187
  /**
2204
2188
  * Wrap the given array of nodes in an <mrow> node if needed, i.e.,
2205
2189
  * unless the array has length 1. Always returns a single node.
@@ -2222,6 +2206,39 @@ const makeRow = function(body, semisimple = false) {
2222
2206
  return new mathMLTree.MathNode("mrow", body);
2223
2207
  };
2224
2208
 
2209
+ /**
2210
+ * Check for <mi>.</mi> which is how a dot renders in MathML,
2211
+ * or <mo separator="true" lspace="0em" rspace="0em">,</mo>
2212
+ * which is how a braced comma {,} renders in MathML
2213
+ */
2214
+ function isNumberPunctuation(group) {
2215
+ if (!group) {
2216
+ return false
2217
+ }
2218
+ if (group.type === 'mi' && group.children.length === 1) {
2219
+ const child = group.children[0];
2220
+ return child instanceof TextNode && child.text === '.'
2221
+ } else if (group.type === "mtext" && group.children.length === 1) {
2222
+ const child = group.children[0];
2223
+ return child instanceof TextNode && child.text === '\u2008' // punctuation space
2224
+ } else if (group.type === 'mo' && group.children.length === 1 &&
2225
+ group.getAttribute('separator') === 'true' &&
2226
+ group.getAttribute('lspace') === '0em' &&
2227
+ group.getAttribute('rspace') === '0em') {
2228
+ const child = group.children[0];
2229
+ return child instanceof TextNode && child.text === ','
2230
+ } else {
2231
+ return false
2232
+ }
2233
+ }
2234
+ const isComma = (expression, i) => {
2235
+ const node = expression[i];
2236
+ const followingNode = expression[i + 1];
2237
+ return (node.type === "atom" && node.text === ",") &&
2238
+ // Don't consolidate if there is a space after the comma.
2239
+ node.loc && followingNode.loc && node.loc.end === followingNode.loc.start
2240
+ };
2241
+
2225
2242
  const isRel = item => {
2226
2243
  return (item.type === "atom" && item.family === "rel") ||
2227
2244
  (item.type === "mclass" && item.mclass === "mrel")
@@ -2245,11 +2262,16 @@ const buildExpression = function(expression, style, semisimple = false) {
2245
2262
  return [group];
2246
2263
  }
2247
2264
 
2248
- consolidateNumbers(expression);
2249
-
2250
2265
  const groups = [];
2266
+ const groupArray = [];
2267
+ let lastGroup;
2251
2268
  for (let i = 0; i < expression.length; i++) {
2252
- const group = buildGroup$1(expression[i], style);
2269
+ groupArray.push(buildGroup$1(expression[i], style));
2270
+ }
2271
+
2272
+ for (let i = 0; i < groupArray.length; i++) {
2273
+ const group = groupArray[i];
2274
+
2253
2275
  // Suppress spacing between adjacent relations
2254
2276
  if (i < expression.length - 1 && isRel(expression[i]) && isRel(expression[i + 1])) {
2255
2277
  group.setAttribute("rspace", "0em");
@@ -2257,9 +2279,39 @@ const buildExpression = function(expression, style, semisimple = false) {
2257
2279
  if (i > 0 && isRel(expression[i]) && isRel(expression[i - 1])) {
2258
2280
  group.setAttribute("lspace", "0em");
2259
2281
  }
2282
+
2283
+ // Concatenate numbers
2284
+ if (group.type === 'mn' && lastGroup && lastGroup.type === 'mn') {
2285
+ // Concatenate <mn>...</mn> followed by <mi>.</mi>
2286
+ lastGroup.children.push(...group.children);
2287
+ continue
2288
+ } else if (isNumberPunctuation(group) && lastGroup && lastGroup.type === 'mn') {
2289
+ // Concatenate <mn>...</mn> followed by <mi>.</mi>
2290
+ lastGroup.children.push(...group.children);
2291
+ continue
2292
+ } else if (lastGroup && lastGroup.type === "mn" && i < groupArray.length - 1 &&
2293
+ groupArray[i + 1].type === "mn" && isComma(expression, i)) {
2294
+ lastGroup.children.push(...group.children);
2295
+ continue
2296
+ } else if (group.type === 'mn' && isNumberPunctuation(lastGroup)) {
2297
+ // Concatenate <mi>.</mi> followed by <mn>...</mn>
2298
+ group.children = [...lastGroup.children, ...group.children];
2299
+ groups.pop();
2300
+ } else if ((group.type === 'msup' || group.type === 'msub') &&
2301
+ group.children.length >= 1 && lastGroup &&
2302
+ (lastGroup.type === 'mn' || isNumberPunctuation(lastGroup))) {
2303
+ // Put preceding <mn>...</mn> or <mi>.</mi> inside base of
2304
+ // <msup><mn>...base...</mn>...exponent...</msup> (or <msub>)
2305
+ const base = group.children[0];
2306
+ if (base instanceof MathNode && base.type === 'mn' && lastGroup) {
2307
+ base.children = [...lastGroup.children, ...base.children];
2308
+ groups.pop();
2309
+ }
2310
+ }
2260
2311
  groups.push(group);
2312
+ lastGroup = group;
2261
2313
  }
2262
- return groups;
2314
+ return groups
2263
2315
  };
2264
2316
 
2265
2317
  /**
@@ -2292,16 +2344,36 @@ const glue$1 = _ => {
2292
2344
  return new mathMLTree.MathNode("mtd", [], [], { padding: "0", width: "50%" })
2293
2345
  };
2294
2346
 
2347
+ const labelContainers = ["mrow", "mtd", "mtable", "mtr"];
2348
+ const getLabel = parent => {
2349
+ for (const node of parent.children) {
2350
+ if (node.type && labelContainers.includes(node.type)) {
2351
+ if (node.classes && node.classes[0] === "tml-label") {
2352
+ const label = node.label;
2353
+ return label
2354
+ } else {
2355
+ const label = getLabel(node);
2356
+ if (label) { return label }
2357
+ }
2358
+ } else if (!node.type) {
2359
+ const label = getLabel(node);
2360
+ if (label) { return label }
2361
+ }
2362
+ }
2363
+ };
2364
+
2295
2365
  const taggedExpression = (expression, tag, style, leqno) => {
2296
2366
  tag = buildExpressionRow(tag[0].body, style);
2297
2367
  tag = consolidateText(tag);
2298
2368
  tag.classes.push("tml-tag");
2299
2369
 
2370
+ const label = getLabel(expression); // from a \label{} function.
2300
2371
  expression = new mathMLTree.MathNode("mtd", [expression]);
2301
2372
  const rowArray = [glue$1(), expression, glue$1()];
2302
2373
  rowArray[leqno ? 0 : 2].classes.push(leqno ? "tml-left" : "tml-right");
2303
2374
  rowArray[leqno ? 0 : 2].children.push(tag);
2304
2375
  const mtr = new mathMLTree.MathNode("mtr", rowArray, ["tml-tageqn"]);
2376
+ if (label) { mtr.setAttribute("id", label); }
2305
2377
  const table = new mathMLTree.MathNode("mtable", [mtr]);
2306
2378
  table.style.width = "100%";
2307
2379
  table.setAttribute("displaystyle", "true");
@@ -2321,6 +2393,11 @@ function buildMathML(tree, texExpression, style, settings) {
2321
2393
  }
2322
2394
 
2323
2395
  const expression = buildExpression(tree, style);
2396
+
2397
+ if (expression.length === 1 && expression[0] instanceof AnchorNode) {
2398
+ return expression[0]
2399
+ }
2400
+
2324
2401
  const wrap = (settings.displayMode || settings.annotate) ? "none" : settings.wrap;
2325
2402
 
2326
2403
  const n1 = expression.length === 0 ? null : expression[0];
@@ -2345,6 +2422,9 @@ function buildMathML(tree, texExpression, style, settings) {
2345
2422
  if (settings.xml) {
2346
2423
  math.setAttribute("xmlns", "http://www.w3.org/1998/Math/MathML");
2347
2424
  }
2425
+ if (wrapper.style.width) {
2426
+ math.style.width = "100%";
2427
+ }
2348
2428
  if (settings.displayMode) {
2349
2429
  math.setAttribute("display", "block");
2350
2430
  math.style.display = "block math"; // necessary in Chromium.
@@ -2676,12 +2756,19 @@ const padding$2 = width => {
2676
2756
  return node
2677
2757
  };
2678
2758
 
2679
- const paddedNode = (group, lspace = 0.3, rspace = 0) => {
2759
+ const paddedNode = (group, lspace = 0.3, rspace = 0, mustSmash = false) => {
2680
2760
  if (group == null && rspace === 0) { return padding$2(lspace) }
2681
2761
  const row = group ? [group] : [];
2682
2762
  if (lspace !== 0) { row.unshift(padding$2(lspace)); }
2683
2763
  if (rspace > 0) { row.push(padding$2(rspace)); }
2684
- return new mathMLTree.MathNode("mrow", row)
2764
+ if (mustSmash) {
2765
+ // Used for the bottom arrow in a {CD} environment
2766
+ const mpadded = new mathMLTree.MathNode("mpadded", row);
2767
+ mpadded.setAttribute("height", "0");
2768
+ return mpadded
2769
+ } else {
2770
+ return new mathMLTree.MathNode("mrow", row)
2771
+ }
2685
2772
  };
2686
2773
 
2687
2774
  const labelSize = (size, scriptLevel) => Number(size) / emScale(scriptLevel);
@@ -2721,7 +2808,8 @@ const munderoverNode = (fName, body, below, style) => {
2721
2808
  (body.body.body || body.body.length > 0));
2722
2809
  if (gotUpper) {
2723
2810
  let label = buildGroup$1(body, labelStyle);
2724
- label = paddedNode(label, space, space);
2811
+ const mustSmash = (fName === "\\\\cdrightarrow" || fName === "\\\\cdleftarrow");
2812
+ label = paddedNode(label, space, space, mustSmash);
2725
2813
  // Since Firefox does not support minsize, stack a invisible node
2726
2814
  // on top of the label. Its width will serve as a min-width.
2727
2815
  // TODO: Refactor this after Firefox supports minsize.
@@ -3116,6 +3204,8 @@ function parseCD(parser) {
3116
3204
  type: "array",
3117
3205
  mode: "math",
3118
3206
  body,
3207
+ tags: null,
3208
+ labels: new Array(body.length + 1).fill(""),
3119
3209
  envClasses: ["jot", "cd"],
3120
3210
  cols: [],
3121
3211
  hLinesBeforeRow: new Array(body.length + 1).fill([])
@@ -3144,18 +3234,23 @@ defineFunction({
3144
3234
  };
3145
3235
  },
3146
3236
  mathmlBuilder(group, style) {
3147
- let label = new mathMLTree.MathNode("mrow", [buildGroup$1(group.label, style)]);
3148
- label = new mathMLTree.MathNode("mpadded", [label]);
3149
- label.setAttribute("width", "0");
3150
- if (group.side === "left") {
3151
- label.setAttribute("lspace", "-1width");
3237
+ if (group.label.body.length === 0) {
3238
+ return new mathMLTree.MathNode("mrow", style) // empty label
3152
3239
  }
3153
- // We have to guess at vertical alignment. We know the arrow is 1.8em tall,
3154
- // But we don't know the height or depth of the label.
3155
- label.setAttribute("voffset", "0.7em");
3156
- label = new mathMLTree.MathNode("mstyle", [label]);
3240
+ // Abuse an <mtable> to create vertically centered content.
3241
+ const mtd = new mathMLTree.MathNode("mtd", [buildGroup$1(group.label, style)]);
3242
+ mtd.style.padding = "0";
3243
+ const mtr = new mathMLTree.MathNode("mtr", [mtd]);
3244
+ const mtable = new mathMLTree.MathNode("mtable", [mtr]);
3245
+ const label = new mathMLTree.MathNode("mpadded", [mtable]);
3246
+ // Set the label width to zero so that the arrow will be centered under the corner cell.
3247
+ label.setAttribute("width", "0");
3157
3248
  label.setAttribute("displaystyle", "false");
3158
3249
  label.setAttribute("scriptlevel", "1");
3250
+ if (group.side === "left") {
3251
+ label.style.display = "flex";
3252
+ label.style.justifyContent = "flex-end";
3253
+ }
3159
3254
  return label;
3160
3255
  }
3161
3256
  });
@@ -3745,10 +3840,13 @@ defineFunction({
3745
3840
  // replacement text, enclosed in '{' and '}' and properly nested
3746
3841
  const { tokens } = parser.gullet.consumeArg();
3747
3842
 
3748
- parser.gullet.macros.set(
3749
- name,
3750
- { tokens, numArgs }
3751
- );
3843
+ if (!(funcName === "\\providecommand" && parser.gullet.macros.has(name))) {
3844
+ // Ignore \providecommand
3845
+ parser.gullet.macros.set(
3846
+ name,
3847
+ { tokens, numArgs }
3848
+ );
3849
+ }
3752
3850
 
3753
3851
  return { type: "internal", mode: parser.mode };
3754
3852
 
@@ -3842,6 +3940,7 @@ const delimiters = [
3842
3940
  "\\vert",
3843
3941
  "\\|",
3844
3942
  "\\Vert",
3943
+ "\u2016",
3845
3944
  "\\uparrow",
3846
3945
  "\\Uparrow",
3847
3946
  "\\downarrow",
@@ -4347,6 +4446,75 @@ function defineEnvironment({ type, names, props, handler, mathmlBuilder }) {
4347
4446
  }
4348
4447
  }
4349
4448
 
4449
+ /**
4450
+ * Lexing or parsing positional information for error reporting.
4451
+ * This object is immutable.
4452
+ */
4453
+ class SourceLocation {
4454
+ constructor(lexer, start, end) {
4455
+ this.lexer = lexer; // Lexer holding the input string.
4456
+ this.start = start; // Start offset, zero-based inclusive.
4457
+ this.end = end; // End offset, zero-based exclusive.
4458
+ }
4459
+
4460
+ /**
4461
+ * Merges two `SourceLocation`s from location providers, given they are
4462
+ * provided in order of appearance.
4463
+ * - Returns the first one's location if only the first is provided.
4464
+ * - Returns a merged range of the first and the last if both are provided
4465
+ * and their lexers match.
4466
+ * - Otherwise, returns null.
4467
+ */
4468
+ static range(first, second) {
4469
+ if (!second) {
4470
+ return first && first.loc;
4471
+ } else if (!first || !first.loc || !second.loc || first.loc.lexer !== second.loc.lexer) {
4472
+ return null;
4473
+ } else {
4474
+ return new SourceLocation(first.loc.lexer, first.loc.start, second.loc.end);
4475
+ }
4476
+ }
4477
+ }
4478
+
4479
+ /**
4480
+ * Interface required to break circular dependency between Token, Lexer, and
4481
+ * ParseError.
4482
+ */
4483
+
4484
+ /**
4485
+ * The resulting token returned from `lex`.
4486
+ *
4487
+ * It consists of the token text plus some position information.
4488
+ * The position information is essentially a range in an input string,
4489
+ * but instead of referencing the bare input string, we refer to the lexer.
4490
+ * That way it is possible to attach extra metadata to the input string,
4491
+ * like for example a file name or similar.
4492
+ *
4493
+ * The position information is optional, so it is OK to construct synthetic
4494
+ * tokens if appropriate. Not providing available position information may
4495
+ * lead to degraded error reporting, though.
4496
+ */
4497
+ class Token {
4498
+ constructor(
4499
+ text, // the text of this token
4500
+ loc
4501
+ ) {
4502
+ this.text = text;
4503
+ this.loc = loc;
4504
+ }
4505
+
4506
+ /**
4507
+ * Given a pair of tokens (this and endToken), compute a `Token` encompassing
4508
+ * the whole input range enclosed by these two.
4509
+ */
4510
+ range(
4511
+ endToken, // last token of the range, inclusive
4512
+ text // the text of the newly constructed token
4513
+ ) {
4514
+ return new Token(text, SourceLocation.range(this, endToken));
4515
+ }
4516
+ }
4517
+
4350
4518
  // In TeX, there are actually three sets of dimensions, one for each of
4351
4519
  // textstyle, scriptstyle, and scriptscriptstyle. These are
4352
4520
  // provided in the the arrays below, in that order.
@@ -4611,7 +4779,7 @@ defineMacro("\\underbar", "\\underline{\\text{#1}}");
4611
4779
  // \kern6\p@\hbox{.}\hbox{.}\hbox{.}}}
4612
4780
  // We'll call \varvdots, which gets a glyph from symbols.js.
4613
4781
  // The zero-width rule gets us an equivalent to the vertical 6pt kern.
4614
- defineMacro("\\vdots", "\\TextOrMath{\\textvdots}{{\\varvdots\\rule{0pt}{15pt}}}\\relax");
4782
+ defineMacro("\\vdots", "{\\varvdots\\rule{0pt}{15pt}}");
4615
4783
  defineMacro("\u22ee", "\\vdots");
4616
4784
 
4617
4785
  // {array} environment gaps
@@ -4831,8 +4999,10 @@ defineMacro("\\tag@literal", (context) => {
4831
4999
  if (context.macros.get("\\df@tag")) {
4832
5000
  throw new ParseError("Multiple \\tag");
4833
5001
  }
4834
- return "\\def\\df@tag{\\text{#1}}";
5002
+ return "\\gdef\\df@tag{\\text{#1}}";
4835
5003
  });
5004
+ defineMacro("\\notag", "\\nonumber");
5005
+ defineMacro("\\nonumber", "\\gdef\\@eqnsw{0}");
4836
5006
 
4837
5007
  // \renewcommand{\bmod}{\nonscript\mskip-\medmuskip\mkern5mu\mathbin
4838
5008
  // {\operator@font mod}\penalty900
@@ -7036,33 +7206,30 @@ const arrayGaps = macros => {
7036
7206
  return [arraystretch, arraycolsep]
7037
7207
  };
7038
7208
 
7039
- const getTag = (group, style, rowNum) => {
7040
- let tag;
7041
- const tagContents = group.tags.shift();
7042
- if (tagContents) {
7043
- // The author has written a \tag or a \notag in this row.
7044
- if (tagContents.body) {
7045
- tag = buildExpressionRow(tagContents.body, style, true);
7046
- tag.classes = ["tml-tag"];
7047
- } else {
7048
- // \notag. Return an empty span.
7049
- tag = new mathMLTree.MathNode("mtext", [], []);
7050
- return tag
7051
- }
7052
- } else if (group.envClasses.includes("multline") &&
7053
- ((group.leqno && rowNum !== 0) || (!group.leqno && rowNum !== group.body.length - 1))) {
7054
- // A multiline that does not receive a tag. Return an empty cell.
7055
- tag = new mathMLTree.MathNode("mtext", [], []);
7056
- return tag
7057
- } else {
7058
- // AMS automatcally numbered equaton.
7059
- // Insert a class so the element can be populated by a CSS counter.
7060
- // WebKit will display the CSS counter only inside a span.
7061
- tag = new mathMLTree.MathNode("mtext", [new Span(["tml-eqn"])]);
7209
+ const checkCellForLabels = cell => {
7210
+ // Check if the author wrote a \tag{} inside this cell.
7211
+ let rowLabel = "";
7212
+ for (let i = 0; i < cell.length; i++) {
7213
+ if (cell[i].type === "label") {
7214
+ if (rowLabel) { throw new ParseError(("Multiple \\labels in one row")) }
7215
+ rowLabel = cell[i].string;
7216
+ }
7062
7217
  }
7063
- return tag
7218
+ return rowLabel
7064
7219
  };
7065
7220
 
7221
+ // autoTag (an argument to parseArray) can be one of three values:
7222
+ // * undefined: Regular (not-top-level) array; no tags on each row
7223
+ // * true: Automatic equation numbering, overridable by \tag
7224
+ // * false: Tags allowed on each row, but no automatic numbering
7225
+ // This function *doesn't* work with the "split" environment name.
7226
+ function getAutoTag(name) {
7227
+ if (name.indexOf("ed") === -1) {
7228
+ return name.indexOf("*") === -1;
7229
+ }
7230
+ // return undefined;
7231
+ }
7232
+
7066
7233
  /**
7067
7234
  * Parse the body of the environment, with rows delimited by \\ and
7068
7235
  * columns delimited by &, and create a nested list in row-major order
@@ -7074,7 +7241,7 @@ function parseArray(
7074
7241
  {
7075
7242
  cols, // [{ type: string , align: l|c|r|null }]
7076
7243
  envClasses, // align(ed|at|edat) | array | cases | cd | small | multline
7077
- addEqnNum, // boolean
7244
+ autoTag, // boolean
7078
7245
  singleRow, // boolean
7079
7246
  emptySingleRow, // boolean
7080
7247
  maxNumCols, // number
@@ -7090,13 +7257,6 @@ function parseArray(
7090
7257
  // TODO: provide helpful error when \cr is used outside array environment
7091
7258
  parser.gullet.macros.set("\\cr", "\\\\\\relax");
7092
7259
  }
7093
- if (addEqnNum) {
7094
- parser.gullet.macros.set("\\tag", "\\@ifstar\\envtag@literal\\envtag@paren");
7095
- parser.gullet.macros.set("\\envtag@paren", "\\env@tag{{(\\text{#1})}}");
7096
- parser.gullet.macros.set("\\envtag@literal", "\\env@tag{\\text{#1}}");
7097
- parser.gullet.macros.set("\\notag", "\\env@notag");
7098
- parser.gullet.macros.set("\\nonumber", "\\env@notag");
7099
- }
7100
7260
 
7101
7261
  // Start group for first cell
7102
7262
  parser.gullet.beginGroup();
@@ -7104,29 +7264,39 @@ function parseArray(
7104
7264
  let row = [];
7105
7265
  const body = [row];
7106
7266
  const rowGaps = [];
7107
- const tags = [];
7108
- let rowTag;
7267
+ const labels = [];
7268
+
7109
7269
  const hLinesBeforeRow = [];
7110
7270
 
7271
+ const tags = (autoTag != null ? [] : undefined);
7272
+
7273
+ // amsmath uses \global\@eqnswtrue and \global\@eqnswfalse to represent
7274
+ // whether this row should have an equation number. Simulate this with
7275
+ // a \@eqnsw macro set to 1 or 0.
7276
+ function beginRow() {
7277
+ if (autoTag) {
7278
+ parser.gullet.macros.set("\\@eqnsw", "1", true);
7279
+ }
7280
+ }
7281
+ function endRow() {
7282
+ if (tags) {
7283
+ if (parser.gullet.macros.get("\\df@tag")) {
7284
+ tags.push(parser.subparse([new Token("\\df@tag")]));
7285
+ parser.gullet.macros.set("\\df@tag", undefined, true);
7286
+ } else {
7287
+ tags.push(Boolean(autoTag) &&
7288
+ parser.gullet.macros.get("\\@eqnsw") === "1");
7289
+ }
7290
+ }
7291
+ }
7292
+ beginRow();
7293
+
7111
7294
  // Test for \hline at the top of the array.
7112
7295
  hLinesBeforeRow.push(getHLines(parser));
7113
7296
 
7114
7297
  while (true) {
7115
7298
  // Parse each cell in its own group (namespace)
7116
7299
  let cell = parser.parseExpression(false, singleRow ? "\\end" : "\\\\");
7117
-
7118
- if (addEqnNum && !rowTag) {
7119
- // Check if the author wrote a \tag{} inside this cell.
7120
- for (let i = 0; i < cell.length; i++) {
7121
- if (cell[i].type === "envTag" || cell[i].type === "noTag") {
7122
- // Get the contents of the \text{} nested inside the \env@Tag{}
7123
- rowTag = cell[i].type === "envTag"
7124
- ? cell.splice(i, 1)[0].body.body[0]
7125
- : { body: null };
7126
- break
7127
- }
7128
- }
7129
- }
7130
7300
  parser.gullet.endGroup();
7131
7301
  parser.gullet.beginGroup();
7132
7302
 
@@ -7155,6 +7325,7 @@ function parseArray(
7155
7325
  }
7156
7326
  parser.consume();
7157
7327
  } else if (next === "\\end") {
7328
+ endRow();
7158
7329
  // Arrays terminate newlines with `\crcr` which consumes a `\cr` if
7159
7330
  // the last line is empty. However, AMS environments keep the
7160
7331
  // empty row if it's the only one.
@@ -7162,6 +7333,7 @@ function parseArray(
7162
7333
  if (row.length === 1 && cell.body.length === 0 && (body.length > 1 || !emptySingleRow)) {
7163
7334
  body.pop();
7164
7335
  }
7336
+ labels.push(checkCellForLabels(cell.body));
7165
7337
  if (hLinesBeforeRow.length < body.length + 1) {
7166
7338
  hLinesBeforeRow.push([]);
7167
7339
  }
@@ -7178,15 +7350,16 @@ function parseArray(
7178
7350
  size = parser.parseSizeGroup(true);
7179
7351
  }
7180
7352
  rowGaps.push(size ? size.value : null);
7353
+ endRow();
7181
7354
 
7182
- tags.push(rowTag);
7355
+ labels.push(checkCellForLabels(cell.body));
7183
7356
 
7184
7357
  // check for \hline(s) following the row separator
7185
7358
  hLinesBeforeRow.push(getHLines(parser));
7186
7359
 
7187
7360
  row = [];
7188
- rowTag = null;
7189
7361
  body.push(row);
7362
+ beginRow();
7190
7363
  } else {
7191
7364
  throw new ParseError("Expected & or \\\\ or \\cr or \\end", parser.nextToken);
7192
7365
  }
@@ -7197,8 +7370,6 @@ function parseArray(
7197
7370
  // End array group defining \cr
7198
7371
  parser.gullet.endGroup();
7199
7372
 
7200
- tags.push(rowTag);
7201
-
7202
7373
  return {
7203
7374
  type: "array",
7204
7375
  mode: parser.mode,
@@ -7207,9 +7378,10 @@ function parseArray(
7207
7378
  rowGaps,
7208
7379
  hLinesBeforeRow,
7209
7380
  envClasses,
7210
- addEqnNum,
7381
+ autoTag,
7211
7382
  scriptLevel,
7212
7383
  tags,
7384
+ labels,
7213
7385
  leqno,
7214
7386
  arraystretch,
7215
7387
  arraycolsep
@@ -7266,19 +7438,43 @@ const mathmlBuilder$7 = function(group, style) {
7266
7438
  }
7267
7439
  row.push(mtd);
7268
7440
  }
7269
- if (group.addEqnNum) {
7270
- row.unshift(glue(group));
7271
- row.push(glue(group));
7272
- const tag = getTag(group, style.withLevel(cellLevel), i);
7273
- if (group.leqno) {
7274
- row[0].children.push(tag);
7275
- row[0].classes.push("tml-left");
7276
- } else {
7277
- row[row.length - 1].children.push(tag);
7278
- row[row.length - 1].classes.push("tml-right");
7441
+ const numColumns = group.body[0].length;
7442
+ // Fill out a short row with empty <mtd> elements.
7443
+ for (let k = 0; k < numColumns - rw.length; k++) {
7444
+ row.push(new mathMLTree.MathNode("mtd", [], style));
7445
+ }
7446
+ if (group.autoTag) {
7447
+ const tag = group.tags[i];
7448
+ let tagElement;
7449
+ if (tag === true) { // automatic numbering
7450
+ tagElement = new mathMLTree.MathNode("mtext", [new Span(["tml-eqn"])]);
7451
+ } else if (tag === false) {
7452
+ // \nonumber/\notag or starred environment
7453
+ tagElement = new mathMLTree.MathNode("mtext", [], []);
7454
+ } else { // manual \tag
7455
+ tagElement = buildExpressionRow(tag[0].body, style.withLevel(cellLevel), true);
7456
+ tagElement = consolidateText(tagElement);
7457
+ tagElement.classes = ["tml-tag"];
7458
+ }
7459
+ if (tagElement) {
7460
+ row.unshift(glue(group));
7461
+ row.push(glue(group));
7462
+ if (group.leqno) {
7463
+ row[0].children.push(tagElement);
7464
+ row[0].classes.push("tml-left");
7465
+ } else {
7466
+ row[row.length - 1].children.push(tagElement);
7467
+ row[row.length - 1].classes.push("tml-right");
7468
+ }
7279
7469
  }
7280
7470
  }
7281
7471
  const mtr = new mathMLTree.MathNode("mtr", row, []);
7472
+ const label = group.labels.shift();
7473
+ if (label && group.tags && group.tags[i]) {
7474
+ mtr.setAttribute("id", label);
7475
+ if (Array.isArray(group.tags[i])) { mtr.classes.push("tml-tageqn"); }
7476
+ }
7477
+
7282
7478
  // Write horizontal rules
7283
7479
  if (i === 0 && hlines[0].length > 0) {
7284
7480
  if (hlines[0].length === 2) {
@@ -7302,16 +7498,17 @@ const mathmlBuilder$7 = function(group, style) {
7302
7498
  }
7303
7499
 
7304
7500
  if (group.envClasses.length > 0) {
7305
- let pad = group.envClasses.includes("jot")
7306
- ? "0.7" // 0.5ex + 0.09em top & bot padding
7307
- : group.envClasses.includes("small")
7308
- ? "0.35"
7309
- : "0.5"; // 0.5ex default top & bot padding
7310
7501
  if (group.arraystretch && group.arraystretch !== 1) {
7311
7502
  // In LaTeX, \arraystretch is a factor applied to a 12pt strut height.
7312
7503
  // It defines a baseline to baseline distance.
7313
7504
  // Here, we do an approximation of that approach.
7314
- pad = String(1.4 * group.arraystretch - 0.8);
7505
+ const pad = String(1.4 * group.arraystretch - 0.8) + "ex";
7506
+ for (let i = 0; i < tbl.length; i++) {
7507
+ for (let j = 0; j < tbl[i].children.length; j++) {
7508
+ tbl[i].children[j].style.paddingTop = pad;
7509
+ tbl[i].children[j].style.paddingBottom = pad;
7510
+ }
7511
+ }
7315
7512
  }
7316
7513
  let sidePadding = group.envClasses.includes("abut")
7317
7514
  ? "0"
@@ -7325,7 +7522,7 @@ const mathmlBuilder$7 = function(group, style) {
7325
7522
  let sidePadUnit = "em";
7326
7523
  if (group.arraycolsep) {
7327
7524
  const arraySidePad = calculateSize(group.arraycolsep, style);
7328
- sidePadding = arraySidePad.number;
7525
+ sidePadding = arraySidePad.number.toFixed(4);
7329
7526
  sidePadUnit = arraySidePad.unit;
7330
7527
  }
7331
7528
 
@@ -7336,18 +7533,18 @@ const mathmlBuilder$7 = function(group, style) {
7336
7533
  if (j === numCols - 1 && hand === 1) { return "0" }
7337
7534
  if (group.envClasses[0] !== "align") { return sidePadding }
7338
7535
  if (hand === 1) { return "0" }
7339
- if (group.addEqnNum) {
7536
+ if (group.autoTag) {
7340
7537
  return (j % 2) ? "1" : "0"
7341
7538
  } else {
7342
7539
  return (j % 2) ? "0" : "1"
7343
7540
  }
7344
7541
  };
7345
7542
 
7346
- // Padding
7543
+ // Side padding
7347
7544
  for (let i = 0; i < tbl.length; i++) {
7348
7545
  for (let j = 0; j < tbl[i].children.length; j++) {
7349
- tbl[i].children[j].style.padding = `${pad}ex ${sidePad(j, 1)}${sidePadUnit}`
7350
- + ` ${pad}ex ${sidePad(j, 0)}${sidePadUnit}`;
7546
+ tbl[i].children[j].style.paddingLeft = `${sidePad(j, 0)}${sidePadUnit}`;
7547
+ tbl[i].children[j].style.paddingRight = `${sidePad(j, 1)}${sidePadUnit}`;
7351
7548
  }
7352
7549
  }
7353
7550
 
@@ -7361,13 +7558,13 @@ const mathmlBuilder$7 = function(group, style) {
7361
7558
  // TODO: Remove -webkit- when Chromium no longer needs it.
7362
7559
  row.children[j].classes = ["tml-" + (j % 2 ? "left" : "right")];
7363
7560
  }
7364
- if (group.addEqnNum) {
7561
+ if (group.autoTag) {
7365
7562
  const k = group.leqno ? 0 : row.children.length - 1;
7366
7563
  row.children[k].classes = ["tml-" + (group.leqno ? "left" : "right")];
7367
7564
  }
7368
7565
  }
7369
7566
  if (row.children.length > 1 && group.envClasses.includes("cases")) {
7370
- row.children[1].style.padding = row.children[1].style.padding.replace(/0em$/, "1em");
7567
+ row.children[1].style.paddingLeft = "1em";
7371
7568
  }
7372
7569
 
7373
7570
  if (group.envClasses.includes("cases") || group.envClasses.includes("subarray")) {
@@ -7387,9 +7584,17 @@ const mathmlBuilder$7 = function(group, style) {
7387
7584
  }
7388
7585
 
7389
7586
  let table = new mathMLTree.MathNode("mtable", tbl);
7587
+ if (group.envClasses.length > 0) {
7588
+ // Top & bottom padding
7589
+ if (group.envClasses.includes("jot")) {
7590
+ table.classes.push("tml-jot");
7591
+ } else if (group.envClasses.includes("small")) {
7592
+ table.classes.push("tml-small");
7593
+ }
7594
+ }
7390
7595
  if (group.scriptLevel === "display") { table.setAttribute("displaystyle", "true"); }
7391
7596
 
7392
- if (group.addEqnNum || group.envClasses.includes("multline")) {
7597
+ if (group.autoTag || group.envClasses.includes("multline")) {
7393
7598
  table.style.width = "100%";
7394
7599
  }
7395
7600
 
@@ -7419,7 +7624,7 @@ const mathmlBuilder$7 = function(group, style) {
7419
7624
  row.children[0].style.borderLeft = sep;
7420
7625
  }
7421
7626
  }
7422
- let iCol = group.addEqnNum ? 0 : -1;
7627
+ let iCol = group.autoTag ? 0 : -1;
7423
7628
  for (let i = iStart; i < iEnd; i++) {
7424
7629
  if (cols[i].type === "align") {
7425
7630
  const colAlign = alignMap[cols[i].align];
@@ -7461,7 +7666,7 @@ const mathmlBuilder$7 = function(group, style) {
7461
7666
  }
7462
7667
  }
7463
7668
  }
7464
- if (group.addEqnNum) {
7669
+ if (group.autoTag) {
7465
7670
  // allow for glue cells on each side
7466
7671
  align = "left " + (align.length > 0 ? align : "center ") + "right ";
7467
7672
  }
@@ -7485,13 +7690,14 @@ const alignedHandler = function(context, args) {
7485
7690
  if (context.envName.indexOf("ed") === -1) {
7486
7691
  validateAmsEnvironmentContext(context);
7487
7692
  }
7693
+ const isSplit = context.envName === "split";
7488
7694
  const cols = [];
7489
7695
  const res = parseArray(
7490
7696
  context.parser,
7491
7697
  {
7492
7698
  cols,
7493
- addEqnNum: context.envName === "align" || context.envName === "alignat",
7494
7699
  emptySingleRow: true,
7700
+ autoTag: isSplit ? undefined : getAutoTag(context.envName),
7495
7701
  envClasses: ["abut", "jot"], // set row spacing & provisional column spacing
7496
7702
  maxNumCols: context.envName === "split" ? 2 : undefined,
7497
7703
  leqno: context.parser.settings.leqno
@@ -7813,7 +8019,7 @@ defineEnvironment({
7813
8019
  const res = {
7814
8020
  cols: [],
7815
8021
  envClasses: ["abut", "jot"],
7816
- addEqnNum: context.envName === "gather",
8022
+ autoTag: getAutoTag(context.envName),
7817
8023
  emptySingleRow: true,
7818
8024
  leqno: context.parser.settings.leqno
7819
8025
  };
@@ -7831,7 +8037,7 @@ defineEnvironment({
7831
8037
  handler(context) {
7832
8038
  validateAmsEnvironmentContext(context);
7833
8039
  const res = {
7834
- addEqnNum: context.envName === "equation",
8040
+ autoTag: getAutoTag(context.envName),
7835
8041
  emptySingleRow: true,
7836
8042
  singleRow: true,
7837
8043
  maxNumCols: 1,
@@ -7852,7 +8058,7 @@ defineEnvironment({
7852
8058
  handler(context) {
7853
8059
  validateAmsEnvironmentContext(context);
7854
8060
  const res = {
7855
- addEqnNum: context.envName === "multline",
8061
+ autoTag: context.envName === "multline",
7856
8062
  maxNumCols: 1,
7857
8063
  envClasses: ["jot", "multline"],
7858
8064
  leqno: context.parser.settings.leqno
@@ -8074,6 +8280,7 @@ defineFunction({
8074
8280
  "\\mathfrak",
8075
8281
  "\\mathscr",
8076
8282
  "\\mathsf",
8283
+ "\\mathsfit",
8077
8284
  "\\mathtt",
8078
8285
 
8079
8286
  // aliases
@@ -8143,10 +8350,19 @@ const mathmlBuilder$5 = (group, style) => {
8143
8350
  ? style.withLevel(StyleLevel.SCRIPT)
8144
8351
  : style.withLevel(StyleLevel.SCRIPTSCRIPT);
8145
8352
 
8146
- let node = new mathMLTree.MathNode("mfrac", [
8147
- buildGroup$1(group.numer, childOptions),
8148
- buildGroup$1(group.denom, childOptions)
8149
- ]);
8353
+ // Chromium (wrongly) continues to shrink fractions beyond scriptscriptlevel.
8354
+ // So we check for levels that Chromium shrinks too small.
8355
+ // If necessary, set an explicit fraction depth.
8356
+ const numer = buildGroup$1(group.numer, childOptions);
8357
+ const denom = buildGroup$1(group.denom, childOptions);
8358
+ if (style.level === 3) {
8359
+ numer.style.mathDepth = "2";
8360
+ numer.setAttribute("scriptlevel", "2");
8361
+ denom.style.mathDepth = "2";
8362
+ denom.setAttribute("scriptlevel", "2");
8363
+ }
8364
+
8365
+ let node = new mathMLTree.MathNode("mfrac", [numer, denom]);
8150
8366
 
8151
8367
  if (!group.hasBarLine) {
8152
8368
  node.setAttribute("linethickness", "0px");
@@ -8539,12 +8755,9 @@ defineFunction({
8539
8755
  };
8540
8756
  },
8541
8757
  mathmlBuilder: (group, style) => {
8542
- let math = buildExpressionRow(group.body, style);
8543
- if (!(math instanceof MathNode)) {
8544
- math = new MathNode("mrow", [math]);
8545
- }
8546
- math.setAttribute("href", group.href);
8547
- return math;
8758
+ const math = new MathNode("math", [buildExpressionRow(group.body, style)]);
8759
+ const anchorNode = new AnchorNode(group.href, [], [math]);
8760
+ return anchorNode
8548
8761
  }
8549
8762
  });
8550
8763
 
@@ -8902,7 +9115,7 @@ defineFunction({
8902
9115
  // Return a no-width, no-ink element with an HTML id.
8903
9116
  const node = new mathMLTree.MathNode("mrow", [], ["tml-label"]);
8904
9117
  if (group.string.length > 0) {
8905
- node.setAttribute("id", group.string);
9118
+ node.setLabel(group.string);
8906
9119
  }
8907
9120
  return node
8908
9121
  }
@@ -9416,13 +9629,6 @@ const mathmlBuilder$2 = (group, style) => {
9416
9629
  node = new MathNode("mo", [makeText(group.name, group.mode)]);
9417
9630
  if (noSuccessor.includes(group.name)) {
9418
9631
  node.setAttribute("largeop", "false");
9419
- } else if (group.limits) {
9420
- // This is a workaround for a MathML/Chromium bug.
9421
- // This is being applied to singleCharBigOps, which are not really stretchy.
9422
- // But by setting the stretchy attribute, Chromium will vertically center
9423
- // big ops around the math axis. This is needed since STIX TWO does not do so.
9424
- // TODO: Remove this hack when MathML & Chromium fix their problem.
9425
- node.setAttribute("stretchy", "true");
9426
9632
  } else {
9427
9633
  node.setAttribute("movablelimits", "false");
9428
9634
  }
@@ -10057,12 +10263,10 @@ defineFunction({
10057
10263
  };
10058
10264
  },
10059
10265
  mathmlBuilder(group, style) {
10060
- // Create an empty text node. Set a class and an href.
10266
+ // Create an empty <a> node. Set a class and an href attribute.
10061
10267
  // The post-processor will populate with the target's tag or equation number.
10062
10268
  const classes = group.funcName === "\\ref" ? ["tml-ref"] : ["tml-ref", "tml-eqref"];
10063
- const node = new mathMLTree.MathNode("mtext", [new mathMLTree.TextNode("")], classes);
10064
- node.setAttribute("href", "#" + group.string);
10065
- return node
10269
+ return new AnchorNode("#" + group.string, classes, null)
10066
10270
  }
10067
10271
  });
10068
10272
 
@@ -10109,6 +10313,8 @@ defineFunction({
10109
10313
  props: {
10110
10314
  numArgs: 2,
10111
10315
  numOptionalArgs: 1,
10316
+ allowedInText: true,
10317
+ allowedInMath: true,
10112
10318
  argTypes: ["size", "size", "size"]
10113
10319
  },
10114
10320
  handler({ parser }, args, optArgs) {
@@ -10401,16 +10607,25 @@ defineFunctionBuilders({
10401
10607
  ? [buildGroup$1(group.base.body[0], style)]
10402
10608
  : [buildGroup$1(group.base, style)];
10403
10609
 
10610
+ // Note regarding scriptstyle level.
10611
+ // (Sub|super)scripts should not shrink beyond MathML scriptlevel 2 aka \scriptscriptstyle
10612
+ // Ref: https://w3c.github.io/mathml-core/#the-displaystyle-and-scriptlevel-attributes
10613
+ // (BTW, MathML scriptlevel 2 is equal to Temml level 3.)
10614
+ // But Chromium continues to shrink the (sub|super)scripts. So we explicitly set scriptlevel 2.
10615
+
10404
10616
  const childStyle = style.inSubOrSup();
10405
10617
  if (group.sub) {
10406
- children.push(buildGroup$1(group.sub, childStyle));
10618
+ const sub = buildGroup$1(group.sub, childStyle);
10619
+ if (style.level === 3) { sub.setAttribute("scriptlevel", "2"); }
10620
+ children.push(sub);
10407
10621
  }
10408
10622
 
10409
10623
  if (group.sup) {
10410
10624
  const sup = buildGroup$1(group.sup, childStyle);
10625
+ if (style.level === 3) { sup.setAttribute("scriptlevel", "2"); }
10411
10626
  const testNode = sup.type === "mrow" ? sup.children[0] : sup;
10412
10627
  if ((testNode && testNode.type === "mo" && testNode.classes.includes("tml-prime"))
10413
- && group.base && group.base.text && group.base.text === "f") {
10628
+ && group.base && group.base.text && "fF".indexOf(group.base.text) > -1) {
10414
10629
  // Chromium does not address italic correction on prime. Prevent f′ from overlapping.
10415
10630
  testNode.classes.push("prime-pad");
10416
10631
  }
@@ -10637,6 +10852,8 @@ const getVariant = function(group, style) {
10637
10852
  return "script"
10638
10853
  case "mathsf":
10639
10854
  return "sans-serif"
10855
+ case "mathsfit":
10856
+ return "sans-serif-italic"
10640
10857
  case "mathtt":
10641
10858
  return "monospace"
10642
10859
  }
@@ -11101,6 +11318,32 @@ defineFunction({
11101
11318
  }
11102
11319
  });
11103
11320
 
11321
+ // \vcenter: Vertically center the argument group on the math axis.
11322
+
11323
+ defineFunction({
11324
+ type: "vcenter",
11325
+ names: ["\\vcenter"],
11326
+ props: {
11327
+ numArgs: 1,
11328
+ argTypes: ["original"],
11329
+ allowedInText: false
11330
+ },
11331
+ handler({ parser }, args) {
11332
+ return {
11333
+ type: "vcenter",
11334
+ mode: parser.mode,
11335
+ body: args[0]
11336
+ };
11337
+ },
11338
+ mathmlBuilder(group, style) {
11339
+ // Use a math table to create vertically centered content.
11340
+ const mtd = new mathMLTree.MathNode("mtd", [buildGroup$1(group.body, style)]);
11341
+ mtd.style.padding = "0";
11342
+ const mtr = new mathMLTree.MathNode("mtr", [mtd]);
11343
+ return new mathMLTree.MathNode("mtable", [mtr])
11344
+ }
11345
+ });
11346
+
11104
11347
  defineFunction({
11105
11348
  type: "verb",
11106
11349
  names: ["\\verb"],
@@ -11135,75 +11378,6 @@ const makeVerb = (group) => group.body.replace(/ /g, group.star ? "\u2423" : "\x
11135
11378
 
11136
11379
  const functions = _functions;
11137
11380
 
11138
- /**
11139
- * Lexing or parsing positional information for error reporting.
11140
- * This object is immutable.
11141
- */
11142
- class SourceLocation {
11143
- constructor(lexer, start, end) {
11144
- this.lexer = lexer; // Lexer holding the input string.
11145
- this.start = start; // Start offset, zero-based inclusive.
11146
- this.end = end; // End offset, zero-based exclusive.
11147
- }
11148
-
11149
- /**
11150
- * Merges two `SourceLocation`s from location providers, given they are
11151
- * provided in order of appearance.
11152
- * - Returns the first one's location if only the first is provided.
11153
- * - Returns a merged range of the first and the last if both are provided
11154
- * and their lexers match.
11155
- * - Otherwise, returns null.
11156
- */
11157
- static range(first, second) {
11158
- if (!second) {
11159
- return first && first.loc;
11160
- } else if (!first || !first.loc || !second.loc || first.loc.lexer !== second.loc.lexer) {
11161
- return null;
11162
- } else {
11163
- return new SourceLocation(first.loc.lexer, first.loc.start, second.loc.end);
11164
- }
11165
- }
11166
- }
11167
-
11168
- /**
11169
- * Interface required to break circular dependency between Token, Lexer, and
11170
- * ParseError.
11171
- */
11172
-
11173
- /**
11174
- * The resulting token returned from `lex`.
11175
- *
11176
- * It consists of the token text plus some position information.
11177
- * The position information is essentially a range in an input string,
11178
- * but instead of referencing the bare input string, we refer to the lexer.
11179
- * That way it is possible to attach extra metadata to the input string,
11180
- * like for example a file name or similar.
11181
- *
11182
- * The position information is optional, so it is OK to construct synthetic
11183
- * tokens if appropriate. Not providing available position information may
11184
- * lead to degraded error reporting, though.
11185
- */
11186
- class Token {
11187
- constructor(
11188
- text, // the text of this token
11189
- loc
11190
- ) {
11191
- this.text = text;
11192
- this.loc = loc;
11193
- }
11194
-
11195
- /**
11196
- * Given a pair of tokens (this and endToken), compute a `Token` encompassing
11197
- * the whole input range enclosed by these two.
11198
- */
11199
- range(
11200
- endToken, // last token of the range, inclusive
11201
- text // the text of the newly constructed token
11202
- ) {
11203
- return new Token(text, SourceLocation.range(this, endToken));
11204
- }
11205
- }
11206
-
11207
11381
  /**
11208
11382
  * The Lexer class handles tokenizing the input in various ways. Since our
11209
11383
  * parser expects us to be able to backtrack, the lexer allows lexing from any
@@ -13367,9 +13541,11 @@ class Style {
13367
13541
  constructor(data) {
13368
13542
  // Style.level can be 0 | 1 | 2 | 3, which correspond to
13369
13543
  // displaystyle, textstyle, scriptstyle, and scriptscriptstyle.
13370
- // style.level does not directly set MathML's script level. MathML does that itself.
13371
- // We use style.level to track, not set, math style so that we can get the
13372
- // correct scriptlevel when needed in supsub.js, mathchoice.js, or for dimensions in em.
13544
+ // style.level usually does not directly set MathML's script level. MathML does that itself.
13545
+ // However, Chromium does not stop shrinking after scriptscriptstyle, so we do explicitly
13546
+ // set a scriptlevel attribute in those conditions.
13547
+ // We also use style.level to track math style so that we can get the correct
13548
+ // scriptlevel when needed in supsub.js, mathchoice.js, or for dimensions in em.
13373
13549
  this.level = data.level;
13374
13550
  this.color = data.color; // string | void
13375
13551
  // A font family applies to a group of fonts (i.e. SansSerif), while a font
@@ -13493,42 +13669,51 @@ class Style {
13493
13669
  }
13494
13670
 
13495
13671
  /* Temml Post Process
13496
- * Perform two tasks not done by Temml when it created each individual Temml <math> element.
13497
- * Given a block,
13498
- * 1. At each AMS auto-numbered environment, assign an id.
13499
- * 2. Populate the text contents of each \ref & \eqref
13672
+ * Populate the text contents of each \ref & \eqref
13500
13673
  *
13501
13674
  * As with other Temml code, this file is released under terms of the MIT license.
13502
13675
  * https://mit-license.org/
13503
13676
  */
13504
13677
 
13505
- const version = "0.10.32";
13678
+ const version = "0.10.34";
13506
13679
 
13507
13680
  function postProcess(block) {
13508
13681
  const labelMap = {};
13509
13682
  let i = 0;
13510
13683
 
13511
13684
  // Get a collection of the parents of each \tag & auto-numbered equation
13512
- const parents = block.getElementsByClassName("tml-tageqn");
13513
- for (const parent of parents) {
13514
- const eqns = parent.getElementsByClassName("tml-eqn");
13515
- if (eqns. length > 0 ) {
13516
- // AMS automatically numbered equation.
13517
- // Assign an id.
13518
- i += 1;
13519
- eqns[0].id = "tml-eqn-" + i;
13520
- // No need to write a number into the text content of the element.
13521
- // A CSS counter does that even if this postProcess() function is not used.
13685
+ const amsEqns = document.getElementsByClassName('tml-eqn');
13686
+ for (let parent of amsEqns) {
13687
+ // AMS automatically numbered equation.
13688
+ // Assign an id.
13689
+ i += 1;
13690
+ parent.setAttribute("id", "tml-eqn-" + String(i));
13691
+ // No need to write a number into the text content of the element.
13692
+ // A CSS counter has done that even if this postProcess() function is not used.
13693
+
13694
+ // Find any \label that refers to an AMS automatic eqn number.
13695
+ while (true) {
13696
+ if (parent.tagName === "mtable") { break }
13697
+ const labels = parent.getElementsByClassName("tml-label");
13698
+ if (labels.length > 0) {
13699
+ const id = parent.attributes.id.value;
13700
+ labelMap[id] = String(i);
13701
+ break
13702
+ } else {
13703
+ parent = parent.parentElement;
13704
+ }
13522
13705
  }
13523
- // If there is a \label, add it to labelMap
13706
+ }
13707
+
13708
+ // Find \labels associated with \tag
13709
+ const taggedEqns = document.getElementsByClassName('tml-tageqn');
13710
+ for (const parent of taggedEqns) {
13524
13711
  const labels = parent.getElementsByClassName("tml-label");
13525
- if (labels.length === 0) { continue }
13526
- if (eqns.length > 0) {
13527
- labelMap[labels[0].id] = String(i);
13528
- } else {
13712
+ if (labels.length > 0) {
13529
13713
  const tags = parent.getElementsByClassName("tml-tag");
13530
13714
  if (tags.length > 0) {
13531
- labelMap[labels[0].id] = tags[0].textContent;
13715
+ const id = parent.attributes.id.value;
13716
+ labelMap[id] = tags[0].textContent;
13532
13717
  }
13533
13718
  }
13534
13719
  }
@@ -13536,17 +13721,22 @@ function postProcess(block) {
13536
13721
  // Populate \ref & \eqref text content
13537
13722
  const refs = block.getElementsByClassName("tml-ref");
13538
13723
  [...refs].forEach(ref => {
13539
- let str = labelMap[ref.getAttribute("href").slice(1)];
13724
+ const attr = ref.getAttribute("href");
13725
+ let str = labelMap[attr.slice(1)];
13540
13726
  if (ref.className.indexOf("tml-eqref") === -1) {
13541
13727
  // \ref. Omit parens.
13542
13728
  str = str.replace(/^\(/, "");
13543
- str = str.replace(/\($/, "");
13544
- } {
13729
+ str = str.replace(/\)$/, "");
13730
+ } else {
13545
13731
  // \eqref. Include parens
13546
13732
  if (str.charAt(0) !== "(") { str = "(" + str; }
13547
13733
  if (str.slice(-1) !== ")") { str = str + ")"; }
13548
13734
  }
13549
- ref.textContent = str;
13735
+ const mtext = document.createElementNS("http://www.w3.org/1998/Math/MathML", "mtext");
13736
+ mtext.appendChild(document.createTextNode(str));
13737
+ const math = document.createElementNS("http://www.w3.org/1998/Math/MathML", "math");
13738
+ math.appendChild(mtext);
13739
+ ref.appendChild(math);
13550
13740
  });
13551
13741
  }
13552
13742