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