katex 0.10.1 → 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 (99) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/README.md +4 -4
  3. package/cli.js +0 -0
  4. package/contrib/copy-tex/README.md +3 -3
  5. package/contrib/mathtex-script-type/README.md +5 -5
  6. package/contrib/mhchem/README.md +1 -1
  7. package/dist/README.md +4 -4
  8. package/dist/fonts/KaTeX_AMS-Regular.ttf +0 -0
  9. package/dist/fonts/KaTeX_AMS-Regular.woff +0 -0
  10. package/dist/fonts/KaTeX_AMS-Regular.woff2 +0 -0
  11. package/dist/fonts/KaTeX_Caligraphic-Bold.ttf +0 -0
  12. package/dist/fonts/KaTeX_Caligraphic-Bold.woff +0 -0
  13. package/dist/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
  14. package/dist/fonts/KaTeX_Caligraphic-Regular.ttf +0 -0
  15. package/dist/fonts/KaTeX_Caligraphic-Regular.woff +0 -0
  16. package/dist/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
  17. package/dist/fonts/KaTeX_Fraktur-Bold.ttf +0 -0
  18. package/dist/fonts/KaTeX_Fraktur-Bold.woff +0 -0
  19. package/dist/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
  20. package/dist/fonts/KaTeX_Fraktur-Regular.ttf +0 -0
  21. package/dist/fonts/KaTeX_Fraktur-Regular.woff +0 -0
  22. package/dist/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
  23. package/dist/fonts/KaTeX_Main-Bold.ttf +0 -0
  24. package/dist/fonts/KaTeX_Main-Bold.woff +0 -0
  25. package/dist/fonts/KaTeX_Main-Bold.woff2 +0 -0
  26. package/dist/fonts/KaTeX_Main-BoldItalic.ttf +0 -0
  27. package/dist/fonts/KaTeX_Main-BoldItalic.woff +0 -0
  28. package/dist/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
  29. package/dist/fonts/KaTeX_Main-Italic.ttf +0 -0
  30. package/dist/fonts/KaTeX_Main-Italic.woff +0 -0
  31. package/dist/fonts/KaTeX_Main-Italic.woff2 +0 -0
  32. package/dist/fonts/KaTeX_Main-Regular.ttf +0 -0
  33. package/dist/fonts/KaTeX_Main-Regular.woff +0 -0
  34. package/dist/fonts/KaTeX_Main-Regular.woff2 +0 -0
  35. package/dist/fonts/KaTeX_Math-BoldItalic.ttf +0 -0
  36. package/dist/fonts/KaTeX_Math-BoldItalic.woff +0 -0
  37. package/dist/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
  38. package/dist/fonts/KaTeX_Math-Italic.ttf +0 -0
  39. package/dist/fonts/KaTeX_Math-Italic.woff +0 -0
  40. package/dist/fonts/KaTeX_Math-Italic.woff2 +0 -0
  41. package/dist/fonts/KaTeX_SansSerif-Bold.ttf +0 -0
  42. package/dist/fonts/KaTeX_SansSerif-Bold.woff +0 -0
  43. package/dist/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
  44. package/dist/fonts/KaTeX_SansSerif-Italic.ttf +0 -0
  45. package/dist/fonts/KaTeX_SansSerif-Italic.woff +0 -0
  46. package/dist/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
  47. package/dist/fonts/KaTeX_SansSerif-Regular.ttf +0 -0
  48. package/dist/fonts/KaTeX_SansSerif-Regular.woff +0 -0
  49. package/dist/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
  50. package/dist/fonts/KaTeX_Script-Regular.ttf +0 -0
  51. package/dist/fonts/KaTeX_Script-Regular.woff +0 -0
  52. package/dist/fonts/KaTeX_Script-Regular.woff2 +0 -0
  53. package/dist/fonts/KaTeX_Size1-Regular.ttf +0 -0
  54. package/dist/fonts/KaTeX_Size1-Regular.woff +0 -0
  55. package/dist/fonts/KaTeX_Size1-Regular.woff2 +0 -0
  56. package/dist/fonts/KaTeX_Size2-Regular.ttf +0 -0
  57. package/dist/fonts/KaTeX_Size2-Regular.woff +0 -0
  58. package/dist/fonts/KaTeX_Size2-Regular.woff2 +0 -0
  59. package/dist/fonts/KaTeX_Size3-Regular.ttf +0 -0
  60. package/dist/fonts/KaTeX_Size3-Regular.woff +0 -0
  61. package/dist/fonts/KaTeX_Size3-Regular.woff2 +0 -0
  62. package/dist/fonts/KaTeX_Size4-Regular.ttf +0 -0
  63. package/dist/fonts/KaTeX_Size4-Regular.woff +0 -0
  64. package/dist/fonts/KaTeX_Size4-Regular.woff2 +0 -0
  65. package/dist/fonts/KaTeX_Typewriter-Regular.ttf +0 -0
  66. package/dist/fonts/KaTeX_Typewriter-Regular.woff +0 -0
  67. package/dist/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
  68. package/dist/katex.css +10 -0
  69. package/dist/katex.js +541 -249
  70. package/dist/katex.min.css +1 -1
  71. package/dist/katex.min.js +1 -1
  72. package/dist/katex.mjs +418 -198
  73. package/package.json +1 -1
  74. package/src/Options.js +11 -75
  75. package/src/buildMathML.js +13 -3
  76. package/src/domTree.js +63 -0
  77. package/src/environments/array.js +118 -2
  78. package/src/fontMetrics.js +3 -2
  79. package/src/functions/arrow.js +15 -5
  80. package/src/functions/color.js +2 -37
  81. package/src/functions/delimsizing.js +12 -2
  82. package/src/functions/enclose.js +15 -3
  83. package/src/functions/genfrac.js +31 -12
  84. package/src/functions/includegraphics.js +146 -0
  85. package/src/functions/mclass.js +1 -0
  86. package/src/functions/op.js +18 -10
  87. package/src/functions/phantom.js +5 -2
  88. package/src/functions/rule.js +20 -9
  89. package/src/functions/supsub.js +6 -2
  90. package/src/functions/symbolsOp.js +4 -0
  91. package/src/functions/tag.js +20 -4
  92. package/src/functions/text.js +4 -2
  93. package/src/functions.js +2 -0
  94. package/src/katex.less +15 -0
  95. package/src/macros.js +144 -27
  96. package/src/mathMLTree.js +1 -1
  97. package/src/parseNode.js +20 -1
  98. package/src/stretchy.js +3 -1
  99. package/src/symbols.js +15 -15
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "katex",
3
- "version": "0.10.1",
3
+ "version": "0.10.2",
4
4
  "description": "Fast math typesetting for the web.",
5
5
  "main": "dist/katex.js",
6
6
  "homepage": "https://katex.org",
package/src/Options.js CHANGED
@@ -36,6 +36,10 @@ const sizeAtStyle = function(size: number, style: StyleInterface): number {
36
36
  return style.size < 2 ? size : sizeStyleMap[size - 1][style.size - 1];
37
37
  };
38
38
 
39
+ // In these types, "" (empty string) means "no change".
40
+ export type FontWeight = "textbf" | "textmd" | "";
41
+ export type FontShape = "textit" | "textup" | "";
42
+
39
43
  export type OptionsData = {
40
44
  style: StyleInterface;
41
45
  color?: string | void;
@@ -44,8 +48,8 @@ export type OptionsData = {
44
48
  phantom?: boolean;
45
49
  font?: string;
46
50
  fontFamily?: string;
47
- fontWeight?: string;
48
- fontShape?: string;
51
+ fontWeight?: FontWeight;
52
+ fontShape?: FontShape;
49
53
  sizeMultiplier?: number;
50
54
  maxSize: number;
51
55
  };
@@ -68,8 +72,8 @@ class Options {
68
72
  // See: https://tex.stackexchange.com/questions/22350/difference-between-textrm-and-mathrm
69
73
  font: string;
70
74
  fontFamily: string;
71
- fontWeight: string;
72
- fontShape: string;
75
+ fontWeight: FontWeight;
76
+ fontShape: FontShape;
73
77
  sizeMultiplier: number;
74
78
  maxSize: number;
75
79
  _fontMetrics: FontMetrics | void;
@@ -244,7 +248,7 @@ class Options {
244
248
  /**
245
249
  * Creates a new options object with the given font weight
246
250
  */
247
- withTextFontWeight(fontWeight: string): Options {
251
+ withTextFontWeight(fontWeight: FontWeight): Options {
248
252
  return this.extend({
249
253
  fontWeight,
250
254
  font: "",
@@ -254,7 +258,7 @@ class Options {
254
258
  /**
255
259
  * Creates a new options object with the given font weight
256
260
  */
257
- withTextFontShape(fontShape: string): Options {
261
+ withTextFontShape(fontShape: FontShape): Options {
258
262
  return this.extend({
259
263
  fontShape,
260
264
  font: "",
@@ -295,81 +299,13 @@ class Options {
295
299
  return this._fontMetrics;
296
300
  }
297
301
 
298
- /**
299
- * A map of color names to CSS colors.
300
- * TODO(emily): Remove this when we have real macros
301
- */
302
- static colorMap = {
303
- "katex-blue": "#6495ed",
304
- "katex-orange": "#ffa500",
305
- "katex-pink": "#ff00af",
306
- "katex-red": "#df0030",
307
- "katex-green": "#28ae7b",
308
- "katex-gray": "gray",
309
- "katex-purple": "#9d38bd",
310
- "katex-blueA": "#ccfaff",
311
- "katex-blueB": "#80f6ff",
312
- "katex-blueC": "#63d9ea",
313
- "katex-blueD": "#11accd",
314
- "katex-blueE": "#0c7f99",
315
- "katex-tealA": "#94fff5",
316
- "katex-tealB": "#26edd5",
317
- "katex-tealC": "#01d1c1",
318
- "katex-tealD": "#01a995",
319
- "katex-tealE": "#208170",
320
- "katex-greenA": "#b6ffb0",
321
- "katex-greenB": "#8af281",
322
- "katex-greenC": "#74cf70",
323
- "katex-greenD": "#1fab54",
324
- "katex-greenE": "#0d923f",
325
- "katex-goldA": "#ffd0a9",
326
- "katex-goldB": "#ffbb71",
327
- "katex-goldC": "#ff9c39",
328
- "katex-goldD": "#e07d10",
329
- "katex-goldE": "#a75a05",
330
- "katex-redA": "#fca9a9",
331
- "katex-redB": "#ff8482",
332
- "katex-redC": "#f9685d",
333
- "katex-redD": "#e84d39",
334
- "katex-redE": "#bc2612",
335
- "katex-maroonA": "#ffbde0",
336
- "katex-maroonB": "#ff92c6",
337
- "katex-maroonC": "#ed5fa6",
338
- "katex-maroonD": "#ca337c",
339
- "katex-maroonE": "#9e034e",
340
- "katex-purpleA": "#ddd7ff",
341
- "katex-purpleB": "#c6b9fc",
342
- "katex-purpleC": "#aa87ff",
343
- "katex-purpleD": "#7854ab",
344
- "katex-purpleE": "#543b78",
345
- "katex-mintA": "#f5f9e8",
346
- "katex-mintB": "#edf2df",
347
- "katex-mintC": "#e0e5cc",
348
- "katex-grayA": "#f6f7f7",
349
- "katex-grayB": "#f0f1f2",
350
- "katex-grayC": "#e3e5e6",
351
- "katex-grayD": "#d6d8da",
352
- "katex-grayE": "#babec2",
353
- "katex-grayF": "#888d93",
354
- "katex-grayG": "#626569",
355
- "katex-grayH": "#3b3e40",
356
- "katex-grayI": "#21242c",
357
- "katex-kaBlue": "#314453",
358
- "katex-kaGreen": "#71B307",
359
- };
360
302
 
361
303
  /**
362
- * Gets the CSS color of the current options object, accounting for the
363
- * `colorMap`.
304
+ * Gets the CSS color of the current options object
364
305
  */
365
306
  getColor(): string | void {
366
307
  if (this.phantom) {
367
308
  return "transparent";
368
- } else if (
369
- this.color != null &&
370
- Options.colorMap.hasOwnProperty(this.color)
371
- ) {
372
- return Options.colorMap[this.color];
373
309
  } else {
374
310
  return this.color;
375
311
  }
@@ -145,14 +145,24 @@ export const buildExpression = function(
145
145
  lastGroup.children.push(...group.children);
146
146
  continue;
147
147
  }
148
+ } else if (lastGroup.type === 'mi' && lastGroup.children.length === 1) {
149
+ const lastChild = lastGroup.children[0];
150
+ if (lastChild instanceof TextNode && lastChild.text === '\u0338' &&
151
+ (group.type === 'mo' || group.type === 'mi' ||
152
+ group.type === 'mn')) {
153
+ const child = group.children[0];
154
+ if (child instanceof TextNode && child.text.length > 0) {
155
+ // Overlay with combining character long solidus
156
+ child.text = child.text.slice(0, 1) + "\u0338" +
157
+ child.text.slice(1);
158
+ groups.pop();
159
+ }
160
+ }
148
161
  }
149
162
  }
150
163
  groups.push(group);
151
164
  lastGroup = group;
152
165
  }
153
-
154
- // TODO(kevinb): combine \\not with mrels and mords
155
-
156
166
  return groups;
157
167
  };
158
168
 
package/src/domTree.js CHANGED
@@ -264,6 +264,69 @@ export class Anchor implements HtmlDomNode {
264
264
  }
265
265
  }
266
266
 
267
+ /**
268
+ * This node represents an image embed (<img>) element.
269
+ */
270
+ export class Img implements VirtualNode {
271
+ src: string;
272
+ alt: string;
273
+ classes: string[];
274
+ height: number;
275
+ depth: number;
276
+ maxFontSize: number;
277
+ style: CssStyle;
278
+
279
+ constructor(
280
+ src: string,
281
+ alt: string,
282
+ style: CssStyle,
283
+ ) {
284
+ this.alt = alt;
285
+ this.src = src;
286
+ this.classes = ["mord"];
287
+ this.style = style;
288
+ }
289
+
290
+ hasClass(className: string): boolean {
291
+ return utils.contains(this.classes, className);
292
+ }
293
+
294
+ toNode(): Node {
295
+ const node = document.createElement("img");
296
+ node.src = this.src;
297
+ node.alt = this.alt;
298
+ node.className = "mord";
299
+
300
+ // Apply inline styles
301
+ for (const style in this.style) {
302
+ if (this.style.hasOwnProperty(style)) {
303
+ // $FlowFixMe
304
+ node.style[style] = this.style[style];
305
+ }
306
+ }
307
+
308
+ return node;
309
+ }
310
+
311
+ toMarkup(): string {
312
+ let markup = `<img src='${this.src} 'alt='${this.alt}' `;
313
+
314
+ // Add the styles, after hyphenation
315
+ let styles = "";
316
+ for (const style in this.style) {
317
+ if (this.style.hasOwnProperty(style)) {
318
+ styles += `${utils.hyphenate(style)}:${this.style[style]};`;
319
+ }
320
+ }
321
+ if (styles) {
322
+ markup += ` style="${utils.escape(styles)}"`;
323
+ }
324
+
325
+ markup += "'/>";
326
+ return markup;
327
+ }
328
+ }
329
+
267
330
  const iCombinations = {
268
331
  'î': '\u0131\u0302',
269
332
  'ï': '\u0131\u0308',
@@ -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"> {
@@ -138,6 +142,7 @@ function parseArray(
138
142
  rowGaps,
139
143
  hskipBeforeAndAfter,
140
144
  hLinesBeforeRow,
145
+ colSeparationType,
141
146
  };
142
147
  }
143
148
 
@@ -367,8 +372,14 @@ const htmlBuilder: HtmlBuilder<"array"> = function(group, options) {
367
372
  return buildCommon.makeSpan(["mord"], [body], options);
368
373
  };
369
374
 
375
+ const alignMap = {
376
+ c: "center ",
377
+ l: "left ",
378
+ r: "right ",
379
+ };
380
+
370
381
  const mathmlBuilder: MathMLBuilder<"array"> = function(group, options) {
371
- return new mathMLTree.MathNode(
382
+ const table = new mathMLTree.MathNode(
372
383
  "mtable", group.body.map(function(row) {
373
384
  return new mathMLTree.MathNode(
374
385
  "mtr", row.map(function(cell) {
@@ -376,6 +387,110 @@ const mathmlBuilder: MathMLBuilder<"array"> = function(group, options) {
376
387
  "mtd", [mml.buildGroup(cell, options)]);
377
388
  }));
378
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
+ }
379
494
  };
380
495
 
381
496
  // Convenience function for aligned and alignedat environments.
@@ -448,6 +563,7 @@ const alignedHandler = function(context, args) {
448
563
  postgap: 0,
449
564
  };
450
565
  }
566
+ res.colSeparationType = isAligned ? "align" : "alignat";
451
567
  return res;
452
568
  };
453
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",
@@ -105,12 +113,11 @@ defineFunction({
105
113
  mathmlBuilder(group, options) {
106
114
  const arrowNode = stretchy.mathMLnode(group.label);
107
115
  let node;
108
- let lowerNode;
109
116
 
110
117
  if (group.body) {
111
- const upperNode = mml.buildGroup(group.body, options);
118
+ const upperNode = paddedNode(mml.buildGroup(group.body, options));
112
119
  if (group.below) {
113
- lowerNode = mml.buildGroup(group.below, options);
120
+ const lowerNode = paddedNode(mml.buildGroup(group.below, options));
114
121
  node = new mathMLTree.MathNode(
115
122
  "munderover", [arrowNode, lowerNode, upperNode]
116
123
  );
@@ -118,10 +125,13 @@ defineFunction({
118
125
  node = new mathMLTree.MathNode("mover", [arrowNode, upperNode]);
119
126
  }
120
127
  } else if (group.below) {
121
- lowerNode = mml.buildGroup(group.below, options);
128
+ const lowerNode = paddedNode(mml.buildGroup(group.below, options));
122
129
  node = new mathMLTree.MathNode("munder", [arrowNode, lowerNode]);
123
130
  } else {
124
- 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]);
125
135
  }
126
136
  return node;
127
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"],
@@ -318,9 +318,19 @@ defineFunction({
318
318
  return middleDelim;
319
319
  },
320
320
  mathmlBuilder: (group, options) => {
321
- const middleNode = new mathMLTree.MathNode(
322
- "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]);
323
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");
324
334
  return middleNode;
325
335
  },
326
336
  });
@@ -112,7 +112,9 @@ const htmlBuilder = (group, options) => {
112
112
 
113
113
  const mathmlBuilder = (group, options) => {
114
114
  const node = new mathMLTree.MathNode(
115
- "menclose", [mml.buildGroup(group.body, options)]);
115
+ (group.label.indexOf("colorbox") > -1) ? "mpadded" : "menclose",
116
+ [mml.buildGroup(group.body, options)]
117
+ );
116
118
  switch (group.label) {
117
119
  case "\\cancel":
118
120
  node.setAttribute("notation", "updiagonalstrike");
@@ -127,8 +129,18 @@ const mathmlBuilder = (group, options) => {
127
129
  node.setAttribute("notation", "box");
128
130
  break;
129
131
  case "\\fcolorbox":
130
- // TODO(ron): I don't know any way to set the border color.
131
- 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
+ }
132
144
  break;
133
145
  case "\\xcancel":
134
146
  node.setAttribute("notation", "updiagonalstrike downdiagonalstrike");
@@ -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