katex 0.10.0-rc → 0.10.2

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.
Files changed (144) hide show
  1. package/CHANGELOG.md +214 -126
  2. package/README.md +18 -17
  3. package/cli.js +5 -1
  4. package/contrib/auto-render/README.md +1 -1
  5. package/contrib/auto-render/auto-render.js +4 -1
  6. package/contrib/auto-render/test/auto-render-spec.js +17 -0
  7. package/contrib/copy-tex/README.md +8 -2
  8. package/contrib/copy-tex/copy-tex.js +0 -1
  9. package/contrib/copy-tex/copy-tex.webpack.js +6 -0
  10. package/contrib/mathtex-script-type/README.md +10 -6
  11. package/contrib/mhchem/README.md +19 -0
  12. package/contrib/mhchem/mhchem.js +1695 -0
  13. package/contrib/mhchem/mhchem.patch +235 -0
  14. package/dist/README.md +18 -17
  15. package/dist/contrib/auto-render.js +179 -161
  16. package/dist/contrib/auto-render.min.js +1 -1
  17. package/dist/contrib/auto-render.mjs +215 -0
  18. package/dist/contrib/copy-tex.js +84 -62
  19. package/dist/contrib/copy-tex.min.css +1 -1
  20. package/dist/contrib/copy-tex.min.js +1 -1
  21. package/dist/contrib/copy-tex.mjs +85 -0
  22. package/dist/contrib/mathtex-script-type.js +17 -14
  23. package/dist/contrib/mathtex-script-type.mjs +24 -0
  24. package/dist/contrib/mhchem.js +3241 -0
  25. package/dist/contrib/mhchem.min.js +1 -0
  26. package/dist/contrib/mhchem.mjs +3109 -0
  27. package/dist/fonts/KaTeX_AMS-Regular.ttf +0 -0
  28. package/dist/fonts/KaTeX_AMS-Regular.woff +0 -0
  29. package/dist/fonts/KaTeX_AMS-Regular.woff2 +0 -0
  30. package/dist/fonts/KaTeX_Caligraphic-Bold.ttf +0 -0
  31. package/dist/fonts/KaTeX_Caligraphic-Bold.woff +0 -0
  32. package/dist/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
  33. package/dist/fonts/KaTeX_Caligraphic-Regular.ttf +0 -0
  34. package/dist/fonts/KaTeX_Caligraphic-Regular.woff +0 -0
  35. package/dist/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
  36. package/dist/fonts/KaTeX_Fraktur-Bold.ttf +0 -0
  37. package/dist/fonts/KaTeX_Fraktur-Bold.woff +0 -0
  38. package/dist/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
  39. package/dist/fonts/KaTeX_Fraktur-Regular.ttf +0 -0
  40. package/dist/fonts/KaTeX_Fraktur-Regular.woff +0 -0
  41. package/dist/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
  42. package/dist/fonts/KaTeX_Main-Bold.ttf +0 -0
  43. package/dist/fonts/KaTeX_Main-Bold.woff +0 -0
  44. package/dist/fonts/KaTeX_Main-Bold.woff2 +0 -0
  45. package/dist/fonts/KaTeX_Main-BoldItalic.ttf +0 -0
  46. package/dist/fonts/KaTeX_Main-BoldItalic.woff +0 -0
  47. package/dist/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
  48. package/dist/fonts/KaTeX_Main-Italic.ttf +0 -0
  49. package/dist/fonts/KaTeX_Main-Italic.woff +0 -0
  50. package/dist/fonts/KaTeX_Main-Italic.woff2 +0 -0
  51. package/dist/fonts/KaTeX_Main-Regular.ttf +0 -0
  52. package/dist/fonts/KaTeX_Main-Regular.woff +0 -0
  53. package/dist/fonts/KaTeX_Main-Regular.woff2 +0 -0
  54. package/dist/fonts/KaTeX_Math-BoldItalic.ttf +0 -0
  55. package/dist/fonts/KaTeX_Math-BoldItalic.woff +0 -0
  56. package/dist/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
  57. package/dist/fonts/KaTeX_Math-Italic.ttf +0 -0
  58. package/dist/fonts/KaTeX_Math-Italic.woff +0 -0
  59. package/dist/fonts/KaTeX_Math-Italic.woff2 +0 -0
  60. package/dist/fonts/KaTeX_SansSerif-Bold.ttf +0 -0
  61. package/dist/fonts/KaTeX_SansSerif-Bold.woff +0 -0
  62. package/dist/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
  63. package/dist/fonts/KaTeX_SansSerif-Italic.ttf +0 -0
  64. package/dist/fonts/KaTeX_SansSerif-Italic.woff +0 -0
  65. package/dist/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
  66. package/dist/fonts/KaTeX_SansSerif-Regular.ttf +0 -0
  67. package/dist/fonts/KaTeX_SansSerif-Regular.woff +0 -0
  68. package/dist/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
  69. package/dist/fonts/KaTeX_Script-Regular.ttf +0 -0
  70. package/dist/fonts/KaTeX_Script-Regular.woff +0 -0
  71. package/dist/fonts/KaTeX_Script-Regular.woff2 +0 -0
  72. package/dist/fonts/KaTeX_Size1-Regular.ttf +0 -0
  73. package/dist/fonts/KaTeX_Size1-Regular.woff +0 -0
  74. package/dist/fonts/KaTeX_Size1-Regular.woff2 +0 -0
  75. package/dist/fonts/KaTeX_Size2-Regular.ttf +0 -0
  76. package/dist/fonts/KaTeX_Size2-Regular.woff +0 -0
  77. package/dist/fonts/KaTeX_Size2-Regular.woff2 +0 -0
  78. package/dist/fonts/KaTeX_Size3-Regular.ttf +0 -0
  79. package/dist/fonts/KaTeX_Size3-Regular.woff +0 -0
  80. package/dist/fonts/KaTeX_Size3-Regular.woff2 +0 -0
  81. package/dist/fonts/KaTeX_Size4-Regular.ttf +0 -0
  82. package/dist/fonts/KaTeX_Size4-Regular.woff +0 -0
  83. package/dist/fonts/KaTeX_Size4-Regular.woff2 +0 -0
  84. package/dist/fonts/KaTeX_Typewriter-Regular.ttf +0 -0
  85. package/dist/fonts/KaTeX_Typewriter-Regular.woff +0 -0
  86. package/dist/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
  87. package/dist/katex.css +24 -9
  88. package/dist/katex.js +13295 -12413
  89. package/dist/katex.min.css +1 -1
  90. package/dist/katex.min.js +1 -1
  91. package/dist/katex.mjs +13388 -11826
  92. package/katex.js +1 -2
  93. package/package.json +60 -48
  94. package/src/Lexer.js +25 -25
  95. package/src/MacroExpander.js +0 -1
  96. package/src/Options.js +11 -75
  97. package/src/Parser.js +231 -313
  98. package/src/Settings.js +6 -0
  99. package/src/buildCommon.js +140 -103
  100. package/src/buildHTML.js +125 -121
  101. package/src/buildMathML.js +14 -4
  102. package/src/buildTree.js +16 -10
  103. package/src/delimiter.js +4 -3
  104. package/src/domTree.js +91 -44
  105. package/src/environments/array.js +120 -7
  106. package/src/fontMetrics.js +3 -2
  107. package/src/functions/arrow.js +21 -7
  108. package/src/functions/color.js +2 -37
  109. package/src/functions/delimsizing.js +18 -11
  110. package/src/functions/enclose.js +19 -4
  111. package/src/functions/environment.js +35 -4
  112. package/src/functions/font.js +1 -2
  113. package/src/functions/genfrac.js +35 -20
  114. package/src/functions/href.js +5 -3
  115. package/src/functions/includegraphics.js +146 -0
  116. package/src/functions/mclass.js +1 -0
  117. package/src/functions/op.js +21 -32
  118. package/src/functions/operatorname.js +1 -2
  119. package/src/functions/ordgroup.js +4 -0
  120. package/src/functions/phantom.js +7 -3
  121. package/src/functions/rule.js +20 -9
  122. package/src/functions/sizing.js +2 -4
  123. package/src/functions/smash.js +5 -2
  124. package/src/functions/sqrt.js +1 -4
  125. package/src/functions/styling.js +0 -1
  126. package/src/functions/supsub.js +6 -2
  127. package/src/functions/symbolsOp.js +4 -0
  128. package/src/functions/symbolsSpacing.js +29 -6
  129. package/src/functions/tag.js +20 -4
  130. package/src/functions/text.js +6 -4
  131. package/src/functions/verb.js +16 -4
  132. package/src/functions.js +2 -0
  133. package/src/katex.less +35 -12
  134. package/src/macros.js +161 -36
  135. package/src/mathMLTree.js +17 -19
  136. package/src/parseNode.js +27 -1
  137. package/src/stretchy.js +3 -1
  138. package/src/svgGeometry.js +1 -1
  139. package/src/symbols.js +39 -17
  140. package/src/tree.js +0 -4
  141. package/src/types.js +4 -3
  142. package/src/unicodeMake.js +1 -1
  143. package/src/utils.js +1 -62
  144. package/src/wide-character.js +2 -2
@@ -25,6 +25,9 @@ export type AlignSpec = { type: "separator", separator: string } | {
25
25
  postgap?: number,
26
26
  };
27
27
 
28
+ // Type to indicate column separation in MathML
29
+ export type ColSeparationType = "align" | "alignat";
30
+
28
31
  function getHLines(parser: Parser): boolean[] {
29
32
  // Return an array. The array length = number of hlines.
30
33
  // Each element in the array tells if the line is dashed.
@@ -48,11 +51,12 @@ function getHLines(parser: Parser): boolean[] {
48
51
  */
49
52
  function parseArray(
50
53
  parser: Parser,
51
- {hskipBeforeAndAfter, addJot, cols, arraystretch}: {|
54
+ {hskipBeforeAndAfter, addJot, cols, arraystretch, colSeparationType}: {|
52
55
  hskipBeforeAndAfter?: boolean,
53
56
  addJot?: boolean,
54
57
  cols?: AlignSpec[],
55
58
  arraystretch?: number,
59
+ colSeparationType?: ColSeparationType,
56
60
  |},
57
61
  style: StyleStr,
58
62
  ): ParseNode<"array"> {
@@ -114,11 +118,8 @@ function parseArray(
114
118
  }
115
119
  break;
116
120
  } else if (next === "\\cr") {
117
- const cr = parser.parseFunction();
118
- if (!cr) {
119
- throw new ParseError(`Failed to parse function after ${next}`);
120
- }
121
- rowGaps.push(assertNodeType(cr, "cr").size);
121
+ const cr = assertNodeType(parser.parseFunction(), "cr");
122
+ rowGaps.push(cr.size);
122
123
 
123
124
  // check for \hline(s) following the row separator
124
125
  hLinesBeforeRow.push(getHLines(parser));
@@ -141,6 +142,7 @@ function parseArray(
141
142
  rowGaps,
142
143
  hskipBeforeAndAfter,
143
144
  hLinesBeforeRow,
145
+ colSeparationType,
144
146
  };
145
147
  }
146
148
 
@@ -370,8 +372,14 @@ const htmlBuilder: HtmlBuilder<"array"> = function(group, options) {
370
372
  return buildCommon.makeSpan(["mord"], [body], options);
371
373
  };
372
374
 
375
+ const alignMap = {
376
+ c: "center ",
377
+ l: "left ",
378
+ r: "right ",
379
+ };
380
+
373
381
  const mathmlBuilder: MathMLBuilder<"array"> = function(group, options) {
374
- return new mathMLTree.MathNode(
382
+ const table = new mathMLTree.MathNode(
375
383
  "mtable", group.body.map(function(row) {
376
384
  return new mathMLTree.MathNode(
377
385
  "mtr", row.map(function(cell) {
@@ -379,6 +387,110 @@ const mathmlBuilder: MathMLBuilder<"array"> = function(group, options) {
379
387
  "mtd", [mml.buildGroup(cell, options)]);
380
388
  }));
381
389
  }));
390
+
391
+ // Set column alignment, row spacing, column spacing, and
392
+ // array lines by setting attributes on the table element.
393
+
394
+ // Set the row spacing. In MathML, we specify a gap distance.
395
+ // We do not use rowGap[] because MathML automatically increases
396
+ // cell height with the height/depth of the element content.
397
+
398
+ // LaTeX \arraystretch multiplies the row baseline-to-baseline distance.
399
+ // We simulate this by adding (arraystretch - 1)em to the gap. This
400
+ // does a reasonable job of adjusting arrays containing 1 em tall content.
401
+
402
+ // The 0.16 and 0.09 values are found emprically. They produce an array
403
+ // similar to LaTeX and in which content does not interfere with \hines.
404
+ const gap = 0.16 + group.arraystretch - 1 + (group.addJot ? 0.09 : 0);
405
+ table.setAttribute("rowspacing", gap + "em");
406
+
407
+ // MathML table lines go only between cells.
408
+ // To place a line on an edge we'll use <menclose>, if necessary.
409
+ let menclose = "";
410
+ let align = "";
411
+
412
+ if (group.cols) {
413
+ // Find column alignment, column spacing, and vertical lines.
414
+ const cols = group.cols;
415
+ let columnLines = "";
416
+ let prevTypeWasAlign = false;
417
+ let iStart = 0;
418
+ let iEnd = cols.length;
419
+
420
+ if (cols[0].type === "separator") {
421
+ menclose += "top ";
422
+ iStart = 1;
423
+ }
424
+ if (cols[cols.length - 1].type === "separator") {
425
+ menclose += "bottom ";
426
+ iEnd -= 1;
427
+ }
428
+
429
+ for (let i = iStart; i < iEnd; i++) {
430
+ if (cols[i].type === "align") {
431
+ align += alignMap[cols[i].align];
432
+
433
+ if (prevTypeWasAlign) {
434
+ columnLines += "none ";
435
+ }
436
+ prevTypeWasAlign = true;
437
+ } else if (cols[i].type === "separator") {
438
+ // MathML accepts only single lines between cells.
439
+ // So we read only the first of consecutive separators.
440
+ if (prevTypeWasAlign) {
441
+ columnLines += cols[i].separator === "|"
442
+ ? "solid "
443
+ : "dashed ";
444
+ prevTypeWasAlign = false;
445
+ }
446
+ }
447
+ }
448
+
449
+ table.setAttribute("columnalign", align.trim());
450
+
451
+ if (/[sd]/.test(columnLines)) {
452
+ table.setAttribute("columnlines", columnLines.trim());
453
+ }
454
+ }
455
+
456
+ // Set column spacing.
457
+ if (group.colSeparationType === "align") {
458
+ const cols = group.cols || [];
459
+ let spacing = "";
460
+ for (let i = 1; i < cols.length; i++) {
461
+ spacing += i % 2 ? "0em " : "1em ";
462
+ }
463
+ table.setAttribute("columnspacing", spacing.trim());
464
+ } else if (group.colSeparationType === "alignat") {
465
+ table.setAttribute("columnspacing", "0em");
466
+ } else {
467
+ table.setAttribute("columnspacing", "1em");
468
+ }
469
+
470
+ // Address \hline and \hdashline
471
+ let rowLines = "";
472
+ const hlines = group.hLinesBeforeRow;
473
+
474
+ menclose += hlines[0].length > 0 ? "left " : "";
475
+ menclose += hlines[hlines.length - 1].length > 0 ? "right " : "";
476
+
477
+ for (let i = 1; i < hlines.length - 1; i++) {
478
+ rowLines += (hlines[i].length === 0)
479
+ ? "none "
480
+ // MathML accepts only a single line between rows. Read one element.
481
+ : hlines[i][0] ? "dashed " : "solid ";
482
+ }
483
+ if (/[sd]/.test(rowLines)) {
484
+ table.setAttribute("rowlines", rowLines.trim());
485
+ }
486
+
487
+ if (menclose === "") {
488
+ return table;
489
+ } else {
490
+ const wrapper = new mathMLTree.MathNode("menclose", [table]);
491
+ wrapper.setAttribute("notation", menclose.trim());
492
+ return wrapper;
493
+ }
382
494
  };
383
495
 
384
496
  // Convenience function for aligned and alignedat environments.
@@ -451,6 +563,7 @@ const alignedHandler = function(context, args) {
451
563
  postgap: 0,
452
564
  };
453
565
  }
566
+ res.colSeparationType = isAligned ? "align" : "alignat";
454
567
  return res;
455
568
  };
456
569
 
@@ -211,10 +211,11 @@ export function getCharacterMetrics(
211
211
  throw new Error(`Font metrics not found for font: ${font}.`);
212
212
  }
213
213
  let ch = character.charCodeAt(0);
214
- if (character[0] in extraCharacterMap) {
214
+ let metrics = metricMap[font][ch];
215
+ if (!metrics && character[0] in extraCharacterMap) {
215
216
  ch = extraCharacterMap[character[0]].charCodeAt(0);
217
+ metrics = metricMap[font][ch];
216
218
  }
217
- let metrics = metricMap[font][ch];
218
219
 
219
220
  if (!metrics && mode === 'text') {
220
221
  // We don't typically have font metrics for Asian scripts.
@@ -9,6 +9,14 @@ import * as mml from "../buildMathML";
9
9
 
10
10
  import type {ParseNode} from "../parseNode";
11
11
 
12
+ // Helper function
13
+ const paddedNode = group => {
14
+ const node = new mathMLTree.MathNode("mpadded", group ? [group] : []);
15
+ node.setAttribute("width", "+0.6em");
16
+ node.setAttribute("lspace", "0.3em");
17
+ return node;
18
+ };
19
+
12
20
  // Stretchy arrows with an optional argument
13
21
  defineFunction({
14
22
  type: "xArrow",
@@ -44,15 +52,19 @@ defineFunction({
44
52
  // Build the argument groups in the appropriate style.
45
53
  // Ref: amsmath.dtx: \hbox{$\scriptstyle\mkern#3mu{#6}\mkern#4mu$}%
46
54
 
55
+ // Some groups can return document fragments. Handle those by wrapping
56
+ // them in a span.
47
57
  let newOptions = options.havingStyle(style.sup());
48
- const upperGroup = html.buildGroup(group.body, newOptions, options);
58
+ const upperGroup = buildCommon.wrapFragment(
59
+ html.buildGroup(group.body, newOptions, options), options);
49
60
  upperGroup.classes.push("x-arrow-pad");
50
61
 
51
62
  let lowerGroup;
52
63
  if (group.below) {
53
64
  // Build the lower group
54
65
  newOptions = options.havingStyle(style.sub());
55
- lowerGroup = html.buildGroup(group.below, newOptions, options);
66
+ lowerGroup = buildCommon.wrapFragment(
67
+ html.buildGroup(group.below, newOptions, options), options);
56
68
  lowerGroup.classes.push("x-arrow-pad");
57
69
  }
58
70
 
@@ -101,12 +113,11 @@ defineFunction({
101
113
  mathmlBuilder(group, options) {
102
114
  const arrowNode = stretchy.mathMLnode(group.label);
103
115
  let node;
104
- let lowerNode;
105
116
 
106
117
  if (group.body) {
107
- const upperNode = mml.buildGroup(group.body, options);
118
+ const upperNode = paddedNode(mml.buildGroup(group.body, options));
108
119
  if (group.below) {
109
- lowerNode = mml.buildGroup(group.below, options);
120
+ const lowerNode = paddedNode(mml.buildGroup(group.below, options));
110
121
  node = new mathMLTree.MathNode(
111
122
  "munderover", [arrowNode, lowerNode, upperNode]
112
123
  );
@@ -114,10 +125,13 @@ defineFunction({
114
125
  node = new mathMLTree.MathNode("mover", [arrowNode, upperNode]);
115
126
  }
116
127
  } else if (group.below) {
117
- lowerNode = mml.buildGroup(group.below, options);
128
+ const lowerNode = paddedNode(mml.buildGroup(group.below, options));
118
129
  node = new mathMLTree.MathNode("munder", [arrowNode, lowerNode]);
119
130
  } else {
120
- node = new mathMLTree.MathNode("mover", [arrowNode]);
131
+ // This should never happen.
132
+ // Parser.js throws an error if there is no argument.
133
+ node = paddedNode();
134
+ node = new mathMLTree.MathNode("mover", [arrowNode, node]);
121
135
  }
122
136
  return node;
123
137
  },
@@ -22,7 +22,8 @@ const htmlBuilder = (group, options) => {
22
22
  };
23
23
 
24
24
  const mathmlBuilder = (group, options) => {
25
- const inner = mml.buildExpression(group.body, options);
25
+ const inner = mml.buildExpression(group.body,
26
+ options.withColor(group.color));
26
27
 
27
28
  const node = new mathMLTree.MathNode("mstyle", inner);
28
29
 
@@ -54,42 +55,6 @@ defineFunction({
54
55
  mathmlBuilder,
55
56
  });
56
57
 
57
- // TODO(kevinb): define these using macros
58
- defineFunction({
59
- type: "color",
60
- names: [
61
- "\\blue", "\\orange", "\\pink", "\\red",
62
- "\\green", "\\gray", "\\purple",
63
- "\\blueA", "\\blueB", "\\blueC", "\\blueD", "\\blueE",
64
- "\\tealA", "\\tealB", "\\tealC", "\\tealD", "\\tealE",
65
- "\\greenA", "\\greenB", "\\greenC", "\\greenD", "\\greenE",
66
- "\\goldA", "\\goldB", "\\goldC", "\\goldD", "\\goldE",
67
- "\\redA", "\\redB", "\\redC", "\\redD", "\\redE",
68
- "\\maroonA", "\\maroonB", "\\maroonC", "\\maroonD", "\\maroonE",
69
- "\\purpleA", "\\purpleB", "\\purpleC", "\\purpleD", "\\purpleE",
70
- "\\mintA", "\\mintB", "\\mintC",
71
- "\\grayA", "\\grayB", "\\grayC", "\\grayD", "\\grayE",
72
- "\\grayF", "\\grayG", "\\grayH", "\\grayI",
73
- "\\kaBlue", "\\kaGreen",
74
- ],
75
- props: {
76
- numArgs: 1,
77
- allowedInText: true,
78
- greediness: 3,
79
- },
80
- handler({parser, funcName}, args) {
81
- const body = args[0];
82
- return {
83
- type: "color",
84
- mode: parser.mode,
85
- color: "katex-" + funcName.slice(1),
86
- body: ordargument(body),
87
- };
88
- },
89
- htmlBuilder,
90
- mathmlBuilder,
91
- });
92
-
93
58
  defineFunction({
94
59
  type: "color",
95
60
  names: ["\\color"],
@@ -35,7 +35,8 @@ const delimiterSizes = {
35
35
  };
36
36
 
37
37
  const delimiters = [
38
- "(", ")", "[", "\\lbrack", "]", "\\rbrack",
38
+ "(", "\\lparen", ")", "\\rparen",
39
+ "[", "\\lbrack", "]", "\\rbrack",
39
40
  "\\{", "\\lbrace", "\\}", "\\rbrace",
40
41
  "\\lfloor", "\\rfloor", "\u230a", "\u230b",
41
42
  "\\lceil", "\\rceil", "\u2308", "\u2309",
@@ -64,8 +65,7 @@ function checkDelimiter(
64
65
  } else {
65
66
  throw new ParseError(
66
67
  "Invalid delimiter: '" +
67
- // $FlowFixMe, do not polyfill
68
- (symDelim ? symDelim.text : JSON["stringify"](delim)) +
68
+ (symDelim ? symDelim.text : JSON.stringify(delim)) +
69
69
  "' after '" + context.funcName + "'", delim);
70
70
  }
71
71
  }
@@ -171,23 +171,20 @@ defineFunction({
171
171
  --parser.leftrightDepth;
172
172
  // Check the next token
173
173
  parser.expect("\\right", false);
174
- const right = parser.parseFunction();
175
- if (!right) {
176
- throw new ParseError('failed to parse function after \\right');
177
- }
174
+ const right = assertNodeType(parser.parseFunction(), "leftright-right");
178
175
  return {
179
176
  type: "leftright",
180
177
  mode: parser.mode,
181
178
  body,
182
179
  left: delim.text,
183
- right: assertNodeType(right, "leftright-right").delim,
180
+ right: right.delim,
184
181
  };
185
182
  },
186
183
  htmlBuilder: (group, options) => {
187
184
  assertParsed(group);
188
185
  // Build the inner expression
189
186
  const inner = html.buildExpression(group.body, options, true,
190
- [null, "mclose"]);
187
+ ["mopen", "mclose"]);
191
188
 
192
189
  let innerHeight = 0;
193
190
  let innerDepth = 0;
@@ -321,9 +318,19 @@ defineFunction({
321
318
  return middleDelim;
322
319
  },
323
320
  mathmlBuilder: (group, options) => {
324
- const middleNode = new mathMLTree.MathNode(
325
- "mo", [mml.makeText(group.delim, group.mode)]);
321
+ // A Firefox \middle will strech a character vertically only if it
322
+ // is in the fence part of the operator dictionary at:
323
+ // https://www.w3.org/TR/MathML3/appendixc.html.
324
+ // So we need to avoid U+2223 and use plain "|" instead.
325
+ const textNode = (group.delim === "\\vert" || group.delim === "|")
326
+ ? mml.makeText("|", "text")
327
+ : mml.makeText(group.delim, group.mode);
328
+ const middleNode = new mathMLTree.MathNode("mo", [textNode]);
326
329
  middleNode.setAttribute("fence", "true");
330
+ // MathML gives 5/18em spacing to each <mo> element.
331
+ // \middle should get delimiter spacing instead.
332
+ middleNode.setAttribute("lspace", "0.05em");
333
+ middleNode.setAttribute("rspace", "0.05em");
327
334
  return middleNode;
328
335
  },
329
336
  });
@@ -12,7 +12,10 @@ import * as mml from "../buildMathML";
12
12
 
13
13
  const htmlBuilder = (group, options) => {
14
14
  // \cancel, \bcancel, \xcancel, \sout, \fbox, \colorbox, \fcolorbox
15
- const inner = html.buildGroup(group.body, options);
15
+ // Some groups can return document fragments. Handle those by wrapping
16
+ // them in a span.
17
+ const inner = buildCommon.wrapFragment(
18
+ html.buildGroup(group.body, options), options);
16
19
 
17
20
  const label = group.label.substr(1);
18
21
  const scale = options.sizeMultiplier;
@@ -109,7 +112,9 @@ const htmlBuilder = (group, options) => {
109
112
 
110
113
  const mathmlBuilder = (group, options) => {
111
114
  const node = new mathMLTree.MathNode(
112
- "menclose", [mml.buildGroup(group.body, options)]);
115
+ (group.label.indexOf("colorbox") > -1) ? "mpadded" : "menclose",
116
+ [mml.buildGroup(group.body, options)]
117
+ );
113
118
  switch (group.label) {
114
119
  case "\\cancel":
115
120
  node.setAttribute("notation", "updiagonalstrike");
@@ -124,8 +129,18 @@ const mathmlBuilder = (group, options) => {
124
129
  node.setAttribute("notation", "box");
125
130
  break;
126
131
  case "\\fcolorbox":
127
- // TODO(ron): I don't know any way to set the border color.
128
- node.setAttribute("notation", "box");
132
+ case "\\colorbox":
133
+ // <menclose> doesn't have a good notation option. So use <mpadded>
134
+ // instead. Set some attributes that come included with <menclose>.
135
+ node.setAttribute("width", "+6pt");
136
+ node.setAttribute("height", "+6pt");
137
+ node.setAttribute("lspace", "3pt"); // LaTeX source2e: \fboxsep = 3pt
138
+ node.setAttribute("voffset", "3pt");
139
+ if (group.label === "\\fcolorbox") {
140
+ const thk = options.fontMetrics().defaultRuleThickness;
141
+ node.setAttribute("style", "border: " + thk + "em solid " +
142
+ String(group.borderColor));
143
+ }
129
144
  break;
130
145
  case "\\xcancel":
131
146
  node.setAttribute("notation", "updiagonalstrike downdiagonalstrike");
@@ -2,9 +2,11 @@
2
2
  import defineFunction from "../defineFunction";
3
3
  import ParseError from "../ParseError";
4
4
  import {assertNodeType} from "../parseNode";
5
+ import environments from "../environments";
5
6
 
6
7
  // Environment delimiters. HTML/MathML rendering is defined in the corresponding
7
8
  // defineEnvironment definitions.
9
+ // $FlowFixMe, "environment" handler returns an environment ParseNode
8
10
  defineFunction({
9
11
  type: "environment",
10
12
  names: ["\\begin", "\\end"],
@@ -12,19 +14,48 @@ defineFunction({
12
14
  numArgs: 1,
13
15
  argTypes: ["text"],
14
16
  },
15
- handler({parser}, args) {
17
+ handler({parser, funcName}, args) {
16
18
  const nameGroup = args[0];
17
19
  if (nameGroup.type !== "ordgroup") {
18
20
  throw new ParseError("Invalid environment name", nameGroup);
19
21
  }
20
- let name = "";
22
+ let envName = "";
21
23
  for (let i = 0; i < nameGroup.body.length; ++i) {
22
- name += assertNodeType(nameGroup.body[i], "textord").text;
24
+ envName += assertNodeType(nameGroup.body[i], "textord").text;
23
25
  }
26
+
27
+ if (funcName === "\\begin") {
28
+ // begin...end is similar to left...right
29
+ if (!environments.hasOwnProperty(envName)) {
30
+ throw new ParseError(
31
+ "No such environment: " + envName, nameGroup);
32
+ }
33
+ // Build the environment object. Arguments and other information will
34
+ // be made available to the begin and end methods using properties.
35
+ const env = environments[envName];
36
+ const {args, optArgs} =
37
+ parser.parseArguments("\\begin{" + envName + "}", env);
38
+ const context = {
39
+ mode: parser.mode,
40
+ envName,
41
+ parser,
42
+ };
43
+ const result = env.handler(context, args, optArgs);
44
+ parser.expect("\\end", false);
45
+ const endNameToken = parser.nextToken;
46
+ const end = assertNodeType(parser.parseFunction(), "environment");
47
+ if (end.name !== envName) {
48
+ throw new ParseError(
49
+ `Mismatch: \\begin{${envName}} matched by \\end{${end.name}}`,
50
+ endNameToken);
51
+ }
52
+ return result;
53
+ }
54
+
24
55
  return {
25
56
  type: "environment",
26
57
  mode: parser.mode,
27
- name,
58
+ name: envName,
28
59
  nameGroup,
29
60
  };
30
61
  },
@@ -32,7 +32,7 @@ defineFunction({
32
32
  type: "font",
33
33
  names: [
34
34
  // styles, except \boldsymbol defined below
35
- "\\mathrm", "\\mathit", "\\mathbf",
35
+ "\\mathrm", "\\mathit", "\\mathbf", "\\mathnormal",
36
36
 
37
37
  // families
38
38
  "\\mathbb", "\\mathcal", "\\mathfrak", "\\mathscr", "\\mathsf",
@@ -99,7 +99,6 @@ defineFunction({
99
99
  },
100
100
  handler: ({parser, funcName, breakOnTokenText}, args) => {
101
101
  const {mode} = parser;
102
- parser.consumeSpaces();
103
102
  const body = parser.parseExpression(true, breakOnTokenText);
104
103
  const style = `math${funcName.slice(1)}`;
105
104
 
@@ -11,22 +11,29 @@ import * as html from "../buildHTML";
11
11
  import * as mml from "../buildMathML";
12
12
  import {calculateSize} from "../units";
13
13
 
14
- const htmlBuilder = (group, options) => {
15
- // Fractions are handled in the TeXbook on pages 444-445, rules 15(a-e).
14
+ const adjustStyle = (size, originalStyle) => {
16
15
  // Figure out what style this fraction should be in based on the
17
16
  // function used
18
- let style = options.style;
19
- if (group.size === "display") {
20
- style = Style.DISPLAY;
21
- } else if (group.size === "text" &&
17
+ let style = originalStyle;
18
+ if (size === "display") {
19
+ // Get display style as a default.
20
+ // If incoming style is sub/sup, use style.text() to get correct size.
21
+ style = style.id >= Style.SCRIPT.id ? style.text() : Style.DISPLAY;
22
+ } else if (size === "text" &&
22
23
  style.size === Style.DISPLAY.size) {
23
24
  // We're in a \tfrac but incoming style is displaystyle, so:
24
25
  style = Style.TEXT;
25
- } else if (group.size === "script") {
26
+ } else if (size === "script") {
26
27
  style = Style.SCRIPT;
27
- } else if (group.size === "scriptscript") {
28
+ } else if (size === "scriptscript") {
28
29
  style = Style.SCRIPTSCRIPT;
29
30
  }
31
+ return style;
32
+ };
33
+
34
+ const htmlBuilder = (group, options) => {
35
+ // Fractions are handled in the TeXbook on pages 444-445, rules 15(a-e).
36
+ const style = adjustStyle(group.size, options.style);
30
37
 
31
38
  const nstyle = style.fracNum();
32
39
  const dstyle = style.fracDen();
@@ -69,7 +76,7 @@ const htmlBuilder = (group, options) => {
69
76
  let numShift;
70
77
  let clearance;
71
78
  let denomShift;
72
- if (style.size === Style.DISPLAY.size) {
79
+ if (style.size === Style.DISPLAY.size || group.size === "display") {
73
80
  numShift = options.fontMetrics().num1;
74
81
  if (ruleWidth > 0) {
75
82
  clearance = 3 * ruleSpacing;
@@ -176,7 +183,7 @@ const htmlBuilder = (group, options) => {
176
183
  };
177
184
 
178
185
  const mathmlBuilder = (group, options) => {
179
- const node = new mathMLTree.MathNode(
186
+ let node = new mathMLTree.MathNode(
180
187
  "mfrac",
181
188
  [
182
189
  mml.buildGroup(group.numer, options),
@@ -190,12 +197,22 @@ const mathmlBuilder = (group, options) => {
190
197
  node.setAttribute("linethickness", ruleWidth + "em");
191
198
  }
192
199
 
200
+ const style = adjustStyle(group.size, options.style);
201
+ if (style.size !== options.style.size) {
202
+ node = new mathMLTree.MathNode("mstyle", [node]);
203
+ const isDisplay = (style.size === Style.DISPLAY.size) ? "true" : "false";
204
+ node.setAttribute("displaystyle", isDisplay);
205
+ node.setAttribute("scriptlevel", "0");
206
+ }
207
+
193
208
  if (group.leftDelim != null || group.rightDelim != null) {
194
209
  const withDelims = [];
195
210
 
196
211
  if (group.leftDelim != null) {
197
212
  const leftOp = new mathMLTree.MathNode(
198
- "mo", [new mathMLTree.TextNode(group.leftDelim)]);
213
+ "mo",
214
+ [new mathMLTree.TextNode(group.leftDelim.replace("\\", ""))]
215
+ );
199
216
 
200
217
  leftOp.setAttribute("fence", "true");
201
218
 
@@ -206,7 +223,9 @@ const mathmlBuilder = (group, options) => {
206
223
 
207
224
  if (group.rightDelim != null) {
208
225
  const rightOp = new mathMLTree.MathNode(
209
- "mo", [new mathMLTree.TextNode(group.rightDelim)]);
226
+ "mo",
227
+ [new mathMLTree.TextNode(group.rightDelim.replace("\\", ""))]
228
+ );
210
229
 
211
230
  rightOp.setAttribute("fence", "true");
212
231
 
@@ -363,21 +382,17 @@ defineFunction({
363
382
  const denom = args[5];
364
383
 
365
384
  // Look into the parse nodes to get the desired delimiters.
366
- let leftNode = checkNodeType(args[0], "ordgroup");
385
+ let leftNode = checkNodeType(args[0], "atom");
367
386
  if (leftNode) {
368
- leftNode = assertAtomFamily(leftNode.body[0], "open");
369
- } else {
370
387
  leftNode = assertAtomFamily(args[0], "open");
371
388
  }
372
- const leftDelim = delimFromValue(leftNode.text);
389
+ const leftDelim = leftNode ? delimFromValue(leftNode.text) : null;
373
390
 
374
- let rightNode = checkNodeType(args[1], "ordgroup");
391
+ let rightNode = checkNodeType(args[1], "atom");
375
392
  if (rightNode) {
376
- rightNode = assertAtomFamily(rightNode.body[0], "close");
377
- } else {
378
393
  rightNode = assertAtomFamily(args[1], "close");
379
394
  }
380
- const rightDelim = delimFromValue(rightNode.text);
395
+ const rightDelim = rightNode ? delimFromValue(rightNode.text) : null;
381
396
 
382
397
  const barNode = assertNodeType(args[2], "size");
383
398
  let hasBarLine;
@@ -2,7 +2,6 @@
2
2
  import defineFunction, {ordargument} from "../defineFunction";
3
3
  import buildCommon from "../buildCommon";
4
4
  import {assertNodeType} from "../parseNode";
5
- import {assertType} from "../utils";
6
5
  import {MathNode} from "../mathMLTree";
7
6
 
8
7
  import * as html from "../buildHTML";
@@ -31,8 +30,11 @@ defineFunction({
31
30
  return buildCommon.makeAnchor(group.href, [], elements, options);
32
31
  },
33
32
  mathmlBuilder: (group, options) => {
34
- const math = mml.buildExpressionRow(group.body, options);
35
- assertType(math, MathNode).setAttribute("href", group.href);
33
+ let math = mml.buildExpressionRow(group.body, options);
34
+ if (!(math instanceof MathNode)) {
35
+ math = new MathNode("mrow", [math]);
36
+ }
37
+ math.setAttribute("href", group.href);
36
38
  return math;
37
39
  },
38
40
  });