katex 0.16.45 → 0.16.47

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 (60) hide show
  1. package/README.md +3 -3
  2. package/contrib/copy-tex/README.md +2 -2
  3. package/contrib/mathtex-script-type/README.md +5 -5
  4. package/contrib/mathtex-script-type/mathtex-script-type.js +2 -1
  5. package/contrib/mhchem/README.md +1 -1
  6. package/contrib/render-a11y-string/render-a11y-string.ts +7 -5
  7. package/contrib/render-a11y-string/test/render-a11y-string-spec.ts +0 -1
  8. package/dist/README.md +3 -3
  9. package/dist/contrib/mathtex-script-type.js +2 -1
  10. package/dist/contrib/mathtex-script-type.min.js +1 -1
  11. package/dist/contrib/mathtex-script-type.mjs +2 -1
  12. package/dist/contrib/render-a11y-string.js +47 -8
  13. package/dist/contrib/render-a11y-string.min.js +1 -1
  14. package/dist/contrib/render-a11y-string.mjs +27 -2
  15. package/dist/katex-swap.css +5 -2
  16. package/dist/katex-swap.min.css +1 -1
  17. package/dist/katex.css +5 -2
  18. package/dist/katex.js +520 -316
  19. package/dist/katex.min.css +1 -1
  20. package/dist/katex.min.js +1 -1
  21. package/dist/katex.mjs +492 -321
  22. package/package.json +15 -16
  23. package/src/Options.ts +29 -28
  24. package/src/Parser.ts +12 -15
  25. package/src/Settings.ts +177 -60
  26. package/src/atoms.ts +33 -0
  27. package/src/buildCommon.ts +54 -46
  28. package/src/buildHTML.ts +4 -3
  29. package/src/buildMathML.ts +54 -47
  30. package/src/defineEnvironment.ts +1 -1
  31. package/src/defineFunction.ts +10 -3
  32. package/src/delimiter.ts +17 -13
  33. package/src/domTree.ts +28 -23
  34. package/src/environments/array.ts +12 -6
  35. package/src/environments/cd.ts +9 -2
  36. package/src/fontMetrics.ts +10 -23
  37. package/src/fontMetricsData.d.ts +6 -1
  38. package/src/functions/arrow.ts +4 -5
  39. package/src/functions/delimsizing.ts +22 -16
  40. package/src/functions/enclose.ts +6 -6
  41. package/src/functions/environment.ts +7 -2
  42. package/src/functions/font.ts +13 -8
  43. package/src/functions/hbox.ts +2 -2
  44. package/src/functions/horizBrace.ts +4 -6
  45. package/src/functions/math.ts +1 -0
  46. package/src/functions/op.ts +10 -5
  47. package/src/functions/smash.ts +1 -1
  48. package/src/functions/styling.ts +17 -5
  49. package/src/functions/supsub.ts +6 -3
  50. package/src/functions/text.ts +7 -3
  51. package/src/parseNode.ts +7 -5
  52. package/src/stretchy.ts +14 -14
  53. package/src/styles/katex.scss +3 -1
  54. package/src/svgGeometry.ts +2 -2
  55. package/src/symbols.ts +11 -26
  56. package/src/tree.ts +11 -5
  57. package/src/types/fonts.ts +73 -0
  58. package/src/{types.ts → types/index.ts} +4 -10
  59. package/src/utils.ts +0 -1
  60. package/src/wide-character.ts +101 -55
@@ -25,7 +25,13 @@ const newCell = (): ParseNode<"styling"> => {
25
25
  // The parseTree from this module must be constructed like the
26
26
  // one created by parseArray(), so an empty CD cell must
27
27
  // be a ParseNode<"styling">. And CD is always displaystyle.
28
- return {type: "styling", body: [], mode: "math", style: "display"};
28
+ return {
29
+ type: "styling",
30
+ body: [],
31
+ mode: "math",
32
+ style: "display",
33
+ resetFont: true,
34
+ };
29
35
  };
30
36
 
31
37
  const isStartOfArrow = (node: AnyParseNode) => {
@@ -90,7 +96,7 @@ export function parseCD(parser: Parser): ParseNode<"array"> {
90
96
  parser.gullet.beginGroup();
91
97
  parser.gullet.macros.set("\\cr", "\\\\\\relax");
92
98
  parser.gullet.beginGroup();
93
- while (true) { // eslint-disable-line no-constant-condition
99
+ while (true) {
94
100
  // Get the parse nodes for the next row.
95
101
  parsedRows.push(parser.parseExpression(false, "\\\\"));
96
102
  parser.gullet.endGroup();
@@ -183,6 +189,7 @@ export function parseCD(parser: Parser): ParseNode<"array"> {
183
189
  body: [arrow],
184
190
  mode: "math",
185
191
  style: "display", // CD is always displaystyle.
192
+ resetFont: true,
186
193
  };
187
194
  row.push(wrappedArrow);
188
195
  // In CD's syntax, cells are implicit. That is, everything that
@@ -1,6 +1,12 @@
1
1
  import {supportedCodepoint} from "./unicodeScripts";
2
2
 
3
3
  import type {Mode} from "./types";
4
+ import type {CharacterMetrics, CharacterMetricsTuple, FontMetrics} from "./types/fonts";
5
+ // This map contains a mapping from font name and character code to character
6
+ // metrics, including height, depth, italic correction, and skew (kern from the
7
+ // character to the corresponding \skewchar)
8
+ // This map is generated via `make metrics`. It should not be changed manually.
9
+ import metricMap from "./fontMetricsData";
4
10
 
5
11
  /**
6
12
  * This file contains metrics regarding fonts and individual symbols. The sigma
@@ -91,12 +97,6 @@ const sigmasAndXis: Record<string, [number, number, number]> = {
91
97
  fboxrule: [0.04, 0.04, 0.04], // 0.4 pt / ptPerEm
92
98
  };
93
99
 
94
- // This map contains a mapping from font name and character code to character
95
- // metrics, including height, depth, italic correction, and skew (kern from the
96
- // character to the corresponding \skewchar)
97
- // This map is generated via `make metrics`. It should not be changed manually.
98
- import metricMap from "./fontMetricsData";
99
-
100
100
  // These are very rough approximations. We default to Times New Roman which
101
101
  // should have Latin-1 and Cyrillic characters, but may not depending on the
102
102
  // operating system. The metrics do not account for extra height from the
@@ -180,23 +180,14 @@ const extraCharacterMap: Record<string, string> = {
180
180
  'я': 'r',
181
181
  };
182
182
 
183
- export type CharacterMetrics = {
184
- depth: number;
185
- height: number;
186
- italic: number;
187
- skew: number;
188
- width: number;
189
- };
190
-
191
- export type MetricMap = {
192
- [key: string]: [number, number, number, number, number];
193
- };
194
-
195
183
  /**
196
184
  * This function adds new font metrics to default metricMap
197
185
  * It can also override existing metrics
198
186
  */
199
- export function setFontMetrics(fontName: string, metrics: MetricMap) {
187
+ export function setFontMetrics(
188
+ fontName: string,
189
+ metrics: {[key: string]: CharacterMetricsTuple}
190
+ ) {
200
191
  metricMap[fontName] = metrics;
201
192
  }
202
193
 
@@ -248,10 +239,6 @@ export function getCharacterMetrics(
248
239
  }
249
240
 
250
241
  type FontSizeIndex = 0 | 1 | 2;
251
- export type FontMetrics = {
252
- cssEmPerMu: number;
253
- [key: string]: number;
254
- };
255
242
  const fontMetricsBySizeIndex: Partial<Record<FontSizeIndex, FontMetrics>> = {};
256
243
 
257
244
  /**
@@ -1,3 +1,8 @@
1
- declare const fontMetricsData: Record<string, Record<number, [number, number, number, number, number]>>;
1
+ import type {CharacterMetricsTuple, FontName} from "./types/fonts";
2
+
3
+ type FontMetrics = Record<number, CharacterMetricsTuple>;
4
+
5
+ declare const fontMetricsData: Record<FontName, FontMetrics> &
6
+ Record<string, FontMetrics>;
2
7
 
3
8
  export default fontMetricsData;
@@ -92,7 +92,8 @@ defineFunction({
92
92
  positionType: "individualShift",
93
93
  children: [
94
94
  {type: "elem", elem: upperGroup, shift: upperShift},
95
- {type: "elem", elem: arrowBody, shift: arrowShift},
95
+ {type: "elem", elem: arrowBody, shift: arrowShift,
96
+ wrapperClasses: ["svg-align"]},
96
97
  {type: "elem", elem: lowerGroup, shift: lowerShift},
97
98
  ],
98
99
  }, options);
@@ -101,14 +102,12 @@ defineFunction({
101
102
  positionType: "individualShift",
102
103
  children: [
103
104
  {type: "elem", elem: upperGroup, shift: upperShift},
104
- {type: "elem", elem: arrowBody, shift: arrowShift},
105
+ {type: "elem", elem: arrowBody, shift: arrowShift,
106
+ wrapperClasses: ["svg-align"]},
105
107
  ],
106
108
  }, options);
107
109
  }
108
110
 
109
- // TODO(ts): Replace this with passing "svg-align" into makeVList.
110
- (vlist as any).children[0].children[0].children[1].classes.push("svg-align");
111
-
112
111
  return makeSpan(["mrel", "x-arrow"], [vlist], options);
113
112
  },
114
113
  mathmlBuilder(group, options) {
@@ -12,6 +12,7 @@ import * as mml from "../buildMathML";
12
12
  import type Options from "../Options";
13
13
  import type {AnyParseNode, ParseNode, SymbolParseNode} from "../parseNode";
14
14
  import type {FunctionContext} from "../defineFunction";
15
+ import type {HtmlDomNode} from "../domTree";
15
16
 
16
17
  // Extra data needed for the delimiter handler down below
17
18
  const delimiterSizes: Record<string, {
@@ -56,6 +57,16 @@ const delimiters = new Set([
56
57
 
57
58
  type IsMiddle = {delim: string, options: Options};
58
59
 
60
+ /**
61
+ * An HtmlDomNode that carries an `isMiddle` property, used by the
62
+ * \middle command to communicate delimiter info to the \left/\right builder.
63
+ */
64
+ type MiddleDelimNode = HtmlDomNode & {isMiddle: IsMiddle};
65
+
66
+ function isMiddleDelimNode(node: HtmlDomNode): node is MiddleDelimNode {
67
+ return 'isMiddle' in node;
68
+ }
69
+
59
70
  // Delimiter functions
60
71
  function checkDelimiter(
61
72
  delim: AnyParseNode,
@@ -209,10 +220,8 @@ defineFunction({
209
220
 
210
221
  // Calculate its height and depth
211
222
  for (let i = 0; i < inner.length; i++) {
212
- // Property `isMiddle` not defined on `span`. See comment in
213
- // "middle"'s htmlBuilder.
214
- // TODO(ts)
215
- if ((inner[i] as any).isMiddle) {
223
+ const node = inner[i];
224
+ if (isMiddleDelimNode(node)) {
216
225
  hadMiddle = true;
217
226
  } else {
218
227
  innerHeight = Math.max(inner[i].height, innerHeight);
@@ -244,11 +253,8 @@ defineFunction({
244
253
  if (hadMiddle) {
245
254
  for (let i = 1; i < inner.length; i++) {
246
255
  const middleDelim = inner[i];
247
- // Property `isMiddle` not defined on `span`. See comment in
248
- // "middle"'s htmlBuilder.
249
- // TODO(ts)
250
- const isMiddle: IsMiddle = (middleDelim as any).isMiddle;
251
- if (isMiddle) {
256
+ if (isMiddleDelimNode(middleDelim)) {
257
+ const isMiddle = middleDelim.isMiddle;
252
258
  // Apply the options that were active when \middle was called
253
259
  inner[i] = makeLeftRightDelim(
254
260
  isMiddle.delim, innerHeight, innerDepth,
@@ -331,13 +337,13 @@ defineFunction({
331
337
  group.delim, 1, options,
332
338
  group.mode, []);
333
339
 
334
- const isMiddle: IsMiddle = {delim: group.delim, options};
335
- // Property `isMiddle` not defined on `span`. It is only used in
336
- // this file above.
337
- // TODO: Fix this violation of the `span` type and possibly rename
338
- // things since `isMiddle` sounds like a boolean, but is a struct.
339
- // TODO(ts)
340
- (middleDelim as any).isMiddle = isMiddle;
340
+ // Patch an ad-hoc property onto the node so the \left/\right
341
+ // builder can reconstruct appropriately sized middle delimiters.
342
+ // isMiddle is not part of HtmlDomNode; the read side uses
343
+ // isMiddleDelimNode() to check before accessing.
344
+ (middleDelim as unknown as MiddleDelimNode).isMiddle = {
345
+ delim: group.delim, options,
346
+ };
341
347
  }
342
348
  return middleDelim;
343
349
  },
@@ -22,7 +22,7 @@ const htmlBuilder: HtmlBuilder<"enclose"> = (group, options) => {
22
22
  const label = group.label.slice(1);
23
23
  let scale = options.sizeMultiplier;
24
24
  let img;
25
- let imgShift = 0;
25
+ let imgShift;
26
26
 
27
27
  // In the LaTeX cancel package, line geometry is slightly different
28
28
  // depending on whether the subject is wider than it is tall, or vice versa.
@@ -76,8 +76,8 @@ const htmlBuilder: HtmlBuilder<"enclose"> = (group, options) => {
76
76
  }
77
77
 
78
78
  // Add vertical padding
79
- let topPad = 0;
80
- let bottomPad = 0;
79
+ let topPad;
80
+ let bottomPad;
81
81
  let ruleThickness = 0;
82
82
  // ref: cancel package: \advance\totalheight2\p@ % "+2"
83
83
  if (/box/.test(label)) {
@@ -165,7 +165,7 @@ const htmlBuilder: HtmlBuilder<"enclose"> = (group, options) => {
165
165
  };
166
166
 
167
167
  const mathmlBuilder: MathMLBuilder<"enclose"> = (group, options) => {
168
- let fboxsep = 0;
168
+ let fboxsep;
169
169
  const node = new MathNode(
170
170
  group.label.includes("colorbox") ? "mpadded" : "menclose",
171
171
  [mml.buildGroup(group.body, options)]
@@ -223,7 +223,7 @@ defineFunction({
223
223
  props: {
224
224
  numArgs: 2,
225
225
  allowedInText: true,
226
- argTypes: ["color", "text"],
226
+ argTypes: ["color", "hbox"],
227
227
  },
228
228
  handler({parser, funcName}, args, optArgs) {
229
229
  const color = assertNodeType(args[0], "color-token").color;
@@ -246,7 +246,7 @@ defineFunction({
246
246
  props: {
247
247
  numArgs: 3,
248
248
  allowedInText: true,
249
- argTypes: ["color", "color", "text"],
249
+ argTypes: ["color", "color", "hbox"],
250
250
  },
251
251
  handler({parser, funcName}, args, optArgs) {
252
252
  const borderColor = assertNodeType(args[0], "color-token").color;
@@ -3,6 +3,8 @@ import ParseError from "../ParseError";
3
3
  import {assertNodeType} from "../parseNode";
4
4
  import environments from "../environments";
5
5
 
6
+ import type {ParseNode} from "../parseNode";
7
+
6
8
  // Environment delimiters. HTML/MathML rendering is defined in the corresponding
7
9
  // defineEnvironment definitions.
8
10
  defineFunction({
@@ -47,8 +49,11 @@ defineFunction({
47
49
  `Mismatch: \\begin{${envName}} matched by \\end{${end.name}}`,
48
50
  endNameToken);
49
51
  }
50
- // TODO(ts), "environment" handler returns an environment ParseNode
51
- return result as any;
52
+ // env.handler returns the specific node type (e.g. "array"),
53
+ // not "environment". This cast is unavoidable: defineFunction
54
+ // requires the handler to return ParseNode<"environment"> but
55
+ // \begin delegates to environment handlers with different types.
56
+ return result as unknown as ParseNode<"environment">;
52
57
  }
53
58
 
54
59
  return {
@@ -9,6 +9,7 @@ import * as mml from "../buildMathML";
9
9
 
10
10
  import type Options from "../Options";
11
11
  import type {ParseNode} from "../parseNode";
12
+ import type {Slice1} from "../types";
12
13
 
13
14
  const htmlBuilder = (group: ParseNode<"font">, options: Options) => {
14
15
  const font = group.font;
@@ -22,12 +23,17 @@ const mathmlBuilder = (group: ParseNode<"font">, options: Options) => {
22
23
  return mml.buildGroup(group.body, newOptions);
23
24
  };
24
25
 
25
- const fontAliases: Record<string, string> = {
26
+ const fontAliases = {
26
27
  "\\Bbb": "\\mathbb",
27
28
  "\\bold": "\\mathbf",
28
29
  "\\frak": "\\mathfrak",
29
- "\\bm": "\\boldsymbol",
30
- };
30
+ } as const;
31
+
32
+ type OldFontCommands = "\\rm" | "\\sf" | "\\tt" | "\\bf" | "\\it" | "\\cal";
33
+ type FontCommands =
34
+ "\\mathrm" | "\\mathit" | "\\mathbf" | "\\mathnormal" | "\\mathsfit" |
35
+ "\\mathbb" | "\\mathcal" | "\\mathfrak" | "\\mathscr" | "\\mathsf" |
36
+ "\\mathtt";
31
37
 
32
38
  defineFunction({
33
39
  type: "font",
@@ -41,7 +47,7 @@ defineFunction({
41
47
 
42
48
  // aliases, except \bm defined below
43
49
  "\\Bbb", "\\bold", "\\frak",
44
- ],
50
+ ] satisfies (FontCommands | keyof typeof fontAliases)[],
45
51
  props: {
46
52
  numArgs: 1,
47
53
  allowedInArgument: true,
@@ -50,12 +56,12 @@ defineFunction({
50
56
  const body = normalizeArgument(args[0]);
51
57
  let func = funcName;
52
58
  if (func in fontAliases) {
53
- func = fontAliases[func];
59
+ func = fontAliases[func as keyof typeof fontAliases];
54
60
  }
55
61
  return {
56
62
  type: "font",
57
63
  mode: parser.mode,
58
- font: func.slice(1),
64
+ font: func.slice(1) as Slice1<FontCommands>,
59
65
  body,
60
66
  };
61
67
  },
@@ -101,12 +107,11 @@ defineFunction({
101
107
  handler: ({parser, funcName, breakOnTokenText}, args) => {
102
108
  const {mode} = parser;
103
109
  const body = parser.parseExpression(true, breakOnTokenText);
104
- const style = `math${funcName.slice(1)}`;
105
110
 
106
111
  return {
107
112
  type: "font",
108
113
  mode: mode,
109
- font: style,
114
+ font: `math${funcName.slice(1) as Slice1<OldFontCommands>}`,
110
115
  body: {
111
116
  type: "ordgroup",
112
117
  mode: parser.mode,
@@ -27,12 +27,12 @@ defineFunction({
27
27
  };
28
28
  },
29
29
  htmlBuilder(group, options) {
30
- const elements = html.buildExpression(group.body, options, false);
30
+ const elements = html.buildExpression(group.body, options.withFont(''), false);
31
31
  return makeFragment(elements);
32
32
  },
33
33
  mathmlBuilder(group, options) {
34
34
  return new MathNode(
35
- "mrow", mml.buildExpression(group.body, options)
35
+ "mrow", mml.buildExpression(group.body, options.withFont(''))
36
36
  );
37
37
  },
38
38
  });
@@ -47,23 +47,21 @@ export const htmlBuilder: HtmlBuilderSupSub<"horizBrace"> = (grp, options) => {
47
47
  children: [
48
48
  {type: "elem", elem: body},
49
49
  {type: "kern", size: 0.1},
50
- {type: "elem", elem: braceBody},
50
+ {type: "elem", elem: braceBody,
51
+ wrapperClasses: ["svg-align"]},
51
52
  ],
52
53
  }, options);
53
- // TODO(ts): Replace this with passing "svg-align" into makeVList.
54
- (vlist as any).children[0].children[0].children[1].classes.push("svg-align");
55
54
  } else {
56
55
  vlist = makeVList({
57
56
  positionType: "bottom",
58
57
  positionData: body.depth + 0.1 + braceBody.height,
59
58
  children: [
60
- {type: "elem", elem: braceBody},
59
+ {type: "elem", elem: braceBody,
60
+ wrapperClasses: ["svg-align"]},
61
61
  {type: "kern", size: 0.1},
62
62
  {type: "elem", elem: body},
63
63
  ],
64
64
  }, options);
65
- // TODO(ts): Replace this with passing "svg-align" into makeVList.
66
- (vlist as any).children[0].children[0].children[0].classes.push("svg-align");
67
65
  }
68
66
 
69
67
  if (supSubGroup) {
@@ -21,6 +21,7 @@ defineFunction({
21
21
  type: "styling",
22
22
  mode: parser.mode,
23
23
  style: "text",
24
+ resetFont: true,
24
25
  body,
25
26
  };
26
27
  },
@@ -51,6 +51,9 @@ export const htmlBuilder: HtmlBuilderSupSub<"op"> = (grp, options) => {
51
51
  }
52
52
 
53
53
  let base;
54
+ // Italic correction from the symbol glyph, captured before the symbol
55
+ // may be wrapped in a vlist (for \oiint/\oiiint). Stays 0 for non-symbol ops.
56
+ let symbolItalic;
54
57
  if (group.symbol) {
55
58
  // If this is a symbol, create the symbol.
56
59
  const fontName = large ? "Size2-Regular" : "Size1-Regular";
@@ -66,11 +69,11 @@ export const htmlBuilder: HtmlBuilderSupSub<"op"> = (grp, options) => {
66
69
  base = makeSymbol(
67
70
  group.name, fontName, "math", options,
68
71
  ["mop", "op-symbol", large ? "large-op" : "small-op"]);
72
+ symbolItalic = base.italic;
69
73
 
70
74
  if (stash.length > 0) {
71
75
  // We're in \oiint or \oiiint. Overlay the oval.
72
76
  // TODO: When font glyphs are available, delete this code.
73
- const italic = base.italic;
74
77
  const oval = staticSvg(stash + "Size"
75
78
  + (large ? "2" : "1"), options);
76
79
  base = makeVList({
@@ -82,8 +85,9 @@ export const htmlBuilder: HtmlBuilderSupSub<"op"> = (grp, options) => {
82
85
  }, options);
83
86
  group.name = "\\" + stash;
84
87
  base.classes.unshift("mop");
85
- // TODO(ts)
86
- (base as any).italic = italic;
88
+ // Carry the italic correction from the original symbol to the
89
+ // vlist wrapper so supsub can use it for subscript positioning.
90
+ base.italic = symbolItalic;
87
91
  }
88
92
  } else if (group.body) {
89
93
  // If this is a list, compose that list.
@@ -120,8 +124,9 @@ export const htmlBuilder: HtmlBuilderSupSub<"op"> = (grp, options) => {
120
124
  options.fontMetrics().axisHeight;
121
125
 
122
126
  // The slant of the symbol is just its italic correction.
123
- // TODO(ts)
124
- slant = (base as SymbolNode & {italic?: number}).italic || 0;
127
+ // SymbolNode carries .italic natively; Span (for \oiint/\oiiint)
128
+ // only has it set when nonzero, so default to 0.
129
+ slant = base.italic ?? 0;
125
130
  }
126
131
 
127
132
  if (hasLimits) {
@@ -23,7 +23,7 @@ defineFunction({
23
23
  // Optional [tb] argument is engaged.
24
24
  // ref: amsmath: \renewcommand{\smash}[1][tb]{%
25
25
  // def\mb@t{\ht}\def\mb@b{\dp}\def\mb@tb{\ht\z@\z@\dp}%
26
- let letter = "";
26
+ let letter;
27
27
  for (let i = 0; i < tbArg.body.length; ++i) {
28
28
  const node = tbArg.body[i];
29
29
  letter = assertSymbolNodeType(node).text;
@@ -6,13 +6,17 @@ import {sizingGroup} from "./sizing";
6
6
  import * as mml from "../buildMathML";
7
7
  import type {StyleStr} from "../types";
8
8
 
9
- const styleMap = {
9
+ const styleMap: Record<StyleStr, typeof Style.DISPLAY> = {
10
10
  "display": Style.DISPLAY,
11
11
  "text": Style.TEXT,
12
12
  "script": Style.SCRIPT,
13
13
  "scriptscript": Style.SCRIPTSCRIPT,
14
14
  };
15
15
 
16
+ function isStyleStr(s: string): s is StyleStr {
17
+ return s in styleMap;
18
+ }
19
+
16
20
  defineFunction({
17
21
  type: "styling",
18
22
  names: [
@@ -30,8 +34,10 @@ defineFunction({
30
34
 
31
35
  // TODO: Refactor to avoid duplicating styleMap in multiple places (e.g.
32
36
  // here and in buildHTML and de-dupe the enumeration of all the styles).
33
- // TODO(ts): The names above exactly match the styles.
34
- const style = funcName.slice(1, funcName.length - 5) as StyleStr;
37
+ const style = funcName.slice(1, funcName.length - 5);
38
+ if (!isStyleStr(style)) {
39
+ throw new Error(`Unknown style: ${style}`);
40
+ }
35
41
  return {
36
42
  type: "styling",
37
43
  mode: parser.mode,
@@ -44,13 +50,19 @@ defineFunction({
44
50
  htmlBuilder(group, options) {
45
51
  // Style changes are handled in the TeXbook on pg. 442, Rule 3.
46
52
  const newStyle = styleMap[group.style];
47
- const newOptions = options.havingStyle(newStyle).withFont('');
53
+ let newOptions = options.havingStyle(newStyle);
54
+ if (group.resetFont) {
55
+ newOptions = newOptions.withFont('');
56
+ }
48
57
  return sizingGroup(group.body, newOptions, options);
49
58
  },
50
59
  mathmlBuilder(group, options) {
51
60
  // Figure out what style we're changing to.
52
61
  const newStyle = styleMap[group.style];
53
- const newOptions = options.havingStyle(newStyle);
62
+ let newOptions = options.havingStyle(newStyle);
63
+ if (group.resetFont) {
64
+ newOptions = newOptions.withFont('');
65
+ }
54
66
 
55
67
  const inner = mml.buildExpression(group.body, newOptions);
56
68
 
@@ -1,6 +1,6 @@
1
1
  import {defineFunctionBuilders} from "../defineFunction";
2
2
  import {makeSpan, makeVList} from "../buildCommon";
3
- import {SymbolNode} from "../domTree";
3
+ import {Span, SymbolNode, type HtmlDomNode} from "../domTree";
4
4
  import {isCharacterBox} from "../utils";
5
5
  import {MathNode} from "../mathMLTree";
6
6
  import {makeEm} from "../units";
@@ -28,6 +28,7 @@ import type {MathNodeType} from "../mathMLTree";
28
28
  const htmlBuilderDelegate = function(
29
29
  group: ParseNode<"supsub">,
30
30
  options: Options,
31
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
31
32
  ): HtmlBuilder<any> | null | undefined {
32
33
  const base = group.base;
33
34
  if (!base) {
@@ -122,8 +123,10 @@ defineFunctionBuilders({
122
123
  group.base && group.base.type === "op" && group.base.name &&
123
124
  (group.base.name === "\\oiint" || group.base.name === "\\oiiint");
124
125
  if (base instanceof SymbolNode || isOiint) {
125
- // @ts-ignore
126
- marginLeft = makeEm(-base.italic);
126
+ // SymbolNode has .italic natively; for \oiint/\oiiint the
127
+ // op builder stores .italic on the wrapping Span.
128
+ marginLeft = makeEm(
129
+ -((base as SymbolNode | Span<HtmlDomNode>).italic ?? 0));
127
130
  }
128
131
  }
129
132
 
@@ -1,4 +1,5 @@
1
1
  import defineFunction, {ordargument} from "../defineFunction";
2
+ import type {TextFont} from "../types/fonts";
2
3
  import {makeSpan} from "../buildCommon";
3
4
 
4
5
  import * as html from "../buildHTML";
@@ -7,9 +8,12 @@ import type Options from "../Options";
7
8
  import type {ParseNode} from "../parseNode";
8
9
 
9
10
  // Non-mathy text, possibly in a font
10
- const textFontFamilies: Record<string, string | undefined> = {
11
- "\\text": undefined, "\\textrm": "textrm", "\\textsf": "textsf",
12
- "\\texttt": "texttt", "\\textnormal": "textrm",
11
+ const textFontFamilies: Record<string, TextFont | undefined> = {
12
+ "\\text": undefined,
13
+ "\\textrm": "textrm",
14
+ "\\textsf": "textsf",
15
+ "\\texttt": "texttt",
16
+ "\\textnormal": "textrm",
13
17
  };
14
18
 
15
19
  const textFontWeights: Record<string, "textbf" | "textmd"> = {
package/src/parseNode.ts CHANGED
@@ -1,8 +1,9 @@
1
- import {NON_ATOMS} from "./symbols";
1
+ import {NON_ATOMS} from "./atoms";
2
+ import type {Atom} from "./atoms";
2
3
  import type SourceLocation from "./SourceLocation";
3
4
  import type {AlignSpec, ColSeparationType} from "./environments/array";
4
- import type {Atom} from "./symbols";
5
- import type {Mode, StyleStr} from "./types";
5
+ import type {DelimiterSize, Mode, StyleStr} from "./types";
6
+ import type {MathFont} from "./types/fonts";
6
7
  import type {Token} from "./Token";
7
8
  import type {Measurement} from "./units";
8
9
  export type NodeType = keyof ParseNodeTypes;
@@ -122,6 +123,7 @@ type ParseNodeTypes = {
122
123
  mode: Mode;
123
124
  loc?: SourceLocation | null | undefined;
124
125
  style: StyleStr;
126
+ resetFont?: boolean;
125
127
  body: AnyParseNode[];
126
128
  };
127
129
  "supsub": {
@@ -231,7 +233,7 @@ type ParseNodeTypes = {
231
233
  type: "delimsizing";
232
234
  mode: Mode;
233
235
  loc?: SourceLocation | null | undefined;
234
- size: 1 | 2 | 3 | 4;
236
+ size: DelimiterSize;
235
237
  mclass: "mopen" | "mclose" | "mrel" | "mord";
236
238
  delim: string;
237
239
  };
@@ -255,7 +257,7 @@ type ParseNodeTypes = {
255
257
  type: "font";
256
258
  mode: Mode;
257
259
  loc?: SourceLocation | null | undefined;
258
- font: string;
260
+ font: Exclude<MathFont, "">;
259
261
  body: AnyParseNode;
260
262
  };
261
263
  "genfrac": {
package/src/stretchy.ts CHANGED
@@ -103,9 +103,9 @@ export const stretchyMathML = function(label: string): MathNode {
103
103
  // That is, inside the font, that arrowhead is 522 units tall, which
104
104
  // corresponds to 0.522 em inside the document.
105
105
 
106
- const katexImagesData: {
107
- [key: string]: ([string[], number, number] | [[string], number, number, string])
108
- } = {
106
+ type SvgData = [string[], number, number, string?];
107
+
108
+ const katexImagesData: {[key: string]: SvgData} = {
109
109
  // path(s), minWidth, height, align
110
110
  overrightarrow: [["rightarrow"], 0.888, 522, "xMaxYMin"],
111
111
  overleftarrow: [["leftarrow"], 0.888, 522, "xMinYMin"],
@@ -177,15 +177,11 @@ export const stretchySvg = function(
177
177
  } {
178
178
  let viewBoxWidth = 400000; // default
179
179
  const label = group.label.slice(1);
180
- if (wideAccentLabels.has(label)) {
181
- // Each type in the `if` statement corresponds to one of the ParseNode
182
- // types below. This narrowing is required to access `grp.base`.
183
- // TODO(ts)
184
- const grp = group as ParseNode<"accent"> | ParseNode<"accentUnder">;
180
+ if (wideAccentLabels.has(label) && 'base' in group) {
185
181
  // There are four SVG images available for each function.
186
182
  // Choose a taller image when there are more characters.
187
- const numChars = grp.base.type === "ordgroup" ?
188
- grp.base.body.length : 1;
183
+ const numChars = group.base.type === "ordgroup" ?
184
+ group.base.body.length : 1;
189
185
  let viewBoxHeight;
190
186
  let pathName;
191
187
  let height;
@@ -232,6 +228,9 @@ export const stretchySvg = function(
232
228
  const spans = [];
233
229
 
234
230
  const data = katexImagesData[label];
231
+ if (!data) {
232
+ throw new Error(`No SVG data for "${label}".`);
233
+ }
235
234
  const [paths, minWidth, viewBoxHeight] = data;
236
235
  const height = viewBoxHeight / 1000;
237
236
 
@@ -239,11 +238,12 @@ export const stretchySvg = function(
239
238
  let widthClasses;
240
239
  let aligns;
241
240
  if (numSvgChildren === 1) {
242
- // TODO(ts): All these cases must be of the 4-tuple type.
243
- const align1: string =
244
- (data as [[string], number, number, string])[3];
241
+ if (data.length !== 4) {
242
+ throw new Error(
243
+ `Expected 4-tuple for single-path SVG data "${label}".`);
244
+ }
245
245
  widthClasses = ["hide-tail"];
246
- aligns = [align1];
246
+ aligns = [data[3]];
247
247
  } else if (numSvgChildren === 2) {
248
248
  widthClasses = ["halfarrow-left", "halfarrow-right"];
249
249
  aligns = ["xMinYMin", "xMaxYMin"];