katex 0.16.45 → 0.16.46

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 (59) 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 +519 -315
  19. package/dist/katex.min.css +1 -1
  20. package/dist/katex.min.js +1 -1
  21. package/dist/katex.mjs +491 -320
  22. package/package.json +15 -15
  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/symbols.ts +11 -26
  55. package/src/tree.ts +11 -5
  56. package/src/types/fonts.ts +73 -0
  57. package/src/{types.ts → types/index.ts} +4 -10
  58. package/src/utils.ts +0 -1
  59. package/src/wide-character.ts +101 -55
@@ -13,11 +13,18 @@ import {DocumentFragment} from "./tree";
13
13
 
14
14
  import type Options from "./Options";
15
15
  import type {ParseNode} from "./parseNode";
16
- import type {CharacterMetrics} from "./fontMetrics";
17
- import type {FontVariant, Mode} from "./types";
16
+ import type {Mode} from "./types";
18
17
  import type {documentFragment as HtmlDocumentFragment} from "./domTree";
19
18
  import type {HtmlDomNode, DomSpan, SvgSpan, CssStyle} from "./domTree";
20
19
  import type {Measurement} from "./units";
20
+ import type {
21
+ CharacterMetrics,
22
+ FontName,
23
+ FontShape,
24
+ FontVariant,
25
+ FontWeight,
26
+ TextFont,
27
+ } from "./types/fonts";
21
28
 
22
29
  /**
23
30
  * Looks up the given symbol in fontMetrics, after applying any symbol
@@ -25,8 +32,7 @@ import type {Measurement} from "./units";
25
32
  */
26
33
  const lookupSymbol = function(
27
34
  value: string,
28
- // TODO(#963): Use a union type for this.
29
- fontName: string,
35
+ fontName: FontName,
30
36
  mode: Mode,
31
37
  ): {
32
38
  value: string;
@@ -58,7 +64,7 @@ const lookupSymbol = function(
58
64
  */
59
65
  export const makeSymbol = function(
60
66
  value: string,
61
- fontName: string,
67
+ fontName: FontName,
62
68
  mode: Mode,
63
69
  options?: Options,
64
70
  classes?: string[],
@@ -132,18 +138,18 @@ export const mathsym = function(
132
138
  * depending on the symbol. Use this function instead of fontMap for font
133
139
  * "boldsymbol".
134
140
  */
135
- const boldsymbol = function(
141
+ const boldSymbol = function(
136
142
  value: string,
137
143
  mode: Mode,
138
- options: Options,
139
- classes: string[],
140
144
  type: "mathord" | "textord",
141
145
  ): {
142
- fontName: string;
143
- fontClass: string;
146
+ fontName: "Math-BoldItalic" | "Main-Bold";
147
+ fontClass: "boldsymbol" | "mathbf";
144
148
  } {
145
- if (type !== "textord" &&
146
- lookupSymbol(value, "Math-BoldItalic", mode).metrics) {
149
+ if (
150
+ type !== "textord" &&
151
+ lookupSymbol(value, "Math-BoldItalic", mode).metrics
152
+ ) {
147
153
  return {
148
154
  fontName: "Math-BoldItalic",
149
155
  fontClass: "boldsymbol",
@@ -168,35 +174,39 @@ export const makeOrd = function<NODETYPE extends "spacing" | "mathord" | "textor
168
174
  ): HtmlDocumentFragment | SymbolNode {
169
175
  const mode = group.mode;
170
176
  const text = group.text;
171
-
172
177
  const classes = ["mord"];
178
+ const {font, fontFamily, fontWeight, fontShape} = options;
173
179
 
174
180
  // Math mode or Old font (i.e. \rm)
175
- const isFont = mode === "math" || (mode === "text" && options.font);
176
- const fontOrFamily = isFont ? options.font : options.fontFamily;
177
- let wideFontName = "";
181
+ const useFont = mode === "math" || (mode === "text" && !!font);
182
+ const fontOrFamily = useFont ? font : fontFamily;
183
+ let wideFontName: FontName | "" = "";
178
184
  let wideFontClass = "";
179
185
  if (text.charCodeAt(0) === 0xD835) {
180
- [wideFontName, wideFontClass] = wideCharacterFont(text, mode);
186
+ const wideCharData = wideCharacterFont(text);
187
+ wideFontName = wideCharData.font;
188
+ wideFontClass = wideCharData[`${mode}Class`];
181
189
  }
182
- if (wideFontName.length > 0) {
190
+ if (wideFontName) {
183
191
  // surrogate pairs get special treatment
184
- return makeSymbol(text, wideFontName, mode, options,
185
- classes.concat(wideFontClass));
192
+ return makeSymbol(text, wideFontName, mode, options, classes.concat(wideFontClass));
186
193
  } else if (fontOrFamily) {
187
194
  let fontName;
188
195
  let fontClasses;
189
196
  if (fontOrFamily === "boldsymbol") {
190
- const fontData = boldsymbol(text, mode, options, classes, type);
197
+ const fontData = boldSymbol(text, mode, type);
191
198
  fontName = fontData.fontName;
192
199
  fontClasses = [fontData.fontClass];
193
- } else if (isFont) {
194
- fontName = fontMap[fontOrFamily].fontName;
195
- fontClasses = [fontOrFamily];
200
+ } else if (useFont) {
201
+ fontName = fontMap[font].fontName;
202
+ fontClasses = [font];
196
203
  } else {
197
- fontName = retrieveTextFontName(fontOrFamily, options.fontWeight,
198
- options.fontShape);
199
- fontClasses = [fontOrFamily, options.fontWeight, options.fontShape];
204
+ fontName = retrieveTextFontName(
205
+ fontFamily,
206
+ fontWeight,
207
+ fontShape,
208
+ );
209
+ fontClasses = [fontFamily, fontWeight, fontShape];
200
210
  }
201
211
 
202
212
  if (lookupSymbol(text, fontName, mode).metrics) {
@@ -221,24 +231,21 @@ export const makeOrd = function<NODETYPE extends "spacing" | "mathord" | "textor
221
231
  } else if (type === "textord") {
222
232
  const font = symbols[mode][text] && symbols[mode][text].font;
223
233
  if (font === "ams") {
224
- const fontName = retrieveTextFontName("amsrm", options.fontWeight,
225
- options.fontShape);
234
+ const fontName = retrieveTextFontName("amsrm", fontWeight, fontShape);
226
235
  return makeSymbol(
227
236
  text, fontName, mode, options,
228
- classes.concat("amsrm", options.fontWeight, options.fontShape));
237
+ classes.concat("amsrm", fontWeight, fontShape));
229
238
  } else if (font === "main" || !font) {
230
- const fontName = retrieveTextFontName("textrm", options.fontWeight,
231
- options.fontShape);
239
+ const fontName = retrieveTextFontName("textrm", fontWeight, fontShape);
232
240
  return makeSymbol(
233
241
  text, fontName, mode, options,
234
- classes.concat(options.fontWeight, options.fontShape));
242
+ classes.concat(fontWeight, fontShape));
235
243
  } else { // fonts added by plugins
236
- const fontName = retrieveTextFontName(font, options.fontWeight,
237
- options.fontShape);
244
+ const fontName = retrieveTextFontName(font, fontWeight, fontShape);
238
245
  // We add font name as a css class
239
246
  return makeSymbol(
240
247
  text, fontName, mode, options,
241
- classes.concat(fontName, options.fontWeight, options.fontShape));
248
+ classes.concat(fontName, fontWeight, fontShape));
242
249
  }
243
250
  } else {
244
251
  throw new Error("unexpected type: " + type + " in makeOrd");
@@ -643,12 +650,14 @@ export const makeGlue = (measurement: Measurement, options: Options): DomSpan =>
643
650
  };
644
651
 
645
652
  // Takes font options, and returns the appropriate fontLookup name
646
- const retrieveTextFontName = function(
647
- fontFamily: string,
648
- fontWeight: string,
649
- fontShape: string,
650
- ): string {
651
- let baseFontName = "";
653
+ const retrieveTextFontName = (
654
+ fontFamily: TextFont,
655
+ fontWeight: FontWeight,
656
+ fontShape: FontShape,
657
+ ): FontName => {
658
+ let baseFontName: "AMS" | "Main" | "SansSerif" | "Typewriter" | TextFont;
659
+ let fontStylesName: "BoldItalic" | "Bold" | "Italic" | "Regular";
660
+
652
661
  switch (fontFamily) {
653
662
  case "amsrm":
654
663
  baseFontName = "AMS";
@@ -666,18 +675,17 @@ const retrieveTextFontName = function(
666
675
  baseFontName = fontFamily; // use fonts added by a plugin
667
676
  }
668
677
 
669
- let fontStylesName;
670
678
  if (fontWeight === "textbf" && fontShape === "textit") {
671
679
  fontStylesName = "BoldItalic";
672
680
  } else if (fontWeight === "textbf") {
673
681
  fontStylesName = "Bold";
674
- } else if (fontWeight === "textit") {
682
+ } else if (fontShape === "textit") {
675
683
  fontStylesName = "Italic";
676
684
  } else {
677
685
  fontStylesName = "Regular";
678
686
  }
679
687
 
680
- return `${baseFontName}-${fontStylesName}`;
688
+ return `${baseFontName}-${fontStylesName}` as FontName;
681
689
  };
682
690
 
683
691
  /**
@@ -688,7 +696,7 @@ const retrieveTextFontName = function(
688
696
  // A map between tex font commands an MathML mathvariant attribute values
689
697
  export const fontMap: Record<string, {
690
698
  variant: FontVariant;
691
- fontName: string;
699
+ fontName: FontName;
692
700
  }> = {
693
701
  // styles
694
702
  "mathbf": {
package/src/buildHTML.ts CHANGED
@@ -159,7 +159,8 @@ const traverseNonSpaceNodes = function(
159
159
  const partialGroup = checkPartialGroup(node);
160
160
 
161
161
  if (partialGroup) { // Recursive DFS
162
- // TODO(ts): make nodes a $ReadOnlyArray by returning a new array
162
+ // TODO(ts): partialGroup.children is ReadonlyArray but this
163
+ // function mutates the array (insertAfter splices into it).
163
164
  traverseNonSpaceNodes(partialGroup.children as HtmlDomNode[],
164
165
  callback, prev, null, isRoot);
165
166
  continue;
@@ -266,8 +267,8 @@ export const buildGroup = function(
266
267
  }
267
268
 
268
269
  if (groupBuilders[group.type]) {
269
- // Call the groupBuilders function
270
- // TODO(ts)
270
+ // TODO(ts): groupBuilders is Record<string, HtmlBuilder<any>>;
271
+ // a type-safe registry would need a mapped type keyed by NodeType.
271
272
  let groupNode: HtmlDomNode = groupBuilders[group.type](group, options);
272
273
 
273
274
  // If the size changed between the parent and the current group, account
@@ -13,9 +13,10 @@ import {MathNode, TextNode} from "./mathMLTree";
13
13
 
14
14
  import type Options from "./Options";
15
15
  import type {AnyParseNode, SymbolParseNode} from "./parseNode";
16
- import type {DomSpan} from "./domTree";
16
+ import type {DomSpan, HtmlDomNode} from "./domTree";
17
17
  import type {MathDomNode} from "./mathMLTree";
18
- import type {FontVariant, Mode} from "./types";
18
+ import type {Mode} from "./types";
19
+ import type {FontVariant, MathFont} from "./types/fonts";
19
20
 
20
21
  const noVariantSymbols = new Set(["\\imath", "\\jmath"]);
21
22
  const rowLikeTypes = new Set(["mrow", "mtable"]);
@@ -52,36 +53,53 @@ export const makeRow = function(body: MathDomNode[]): MathDomNode {
52
53
  }
53
54
  };
54
55
 
56
+ const mathFontVariants: Partial<
57
+ Record<MathFont, FontVariant | ((group: SymbolParseNode) => FontVariant)>
58
+ > = {
59
+ mathit: "italic",
60
+ boldsymbol: (group) => (group.type === "textord" ? "bold" : "bold-italic"),
61
+ mathbf: "bold",
62
+ mathbb: "double-struck",
63
+ mathsfit: "sans-serif-italic",
64
+ mathfrak: "fraktur",
65
+ mathscr: "script",
66
+ mathcal: "script",
67
+ mathsf: "sans-serif",
68
+ mathtt: "monospace",
69
+ };
70
+
55
71
  /**
56
72
  * Returns the math variant as a string or null if none is required.
57
73
  */
58
- export const getVariant = function(
74
+ export const getVariant = (
59
75
  group: SymbolParseNode,
60
76
  options: Options,
61
- ): FontVariant | null | undefined {
77
+ ): FontVariant | null | undefined => {
62
78
  // Handle \text... font specifiers as best we can.
63
79
  // MathML has a limited list of allowable mathvariant specifiers; see
64
80
  // https://www.w3.org/TR/MathML3/chapter3.html#presm.commatt
65
- if (options.fontFamily === "texttt") {
66
- return "monospace";
67
- } else if (options.fontFamily === "textsf") {
68
- if (options.fontShape === "textit" &&
69
- options.fontWeight === "textbf") {
70
- return "sans-serif-bold-italic";
81
+ if (group.mode === "text") {
82
+ if (options.fontFamily === "texttt") {
83
+ return "monospace";
84
+ } else if (options.fontFamily === "textsf") {
85
+ if (options.fontShape === "textit" &&
86
+ options.fontWeight === "textbf") {
87
+ return "sans-serif-bold-italic";
88
+ } else if (options.fontShape === "textit") {
89
+ return "sans-serif-italic";
90
+ } else if (options.fontWeight === "textbf") {
91
+ return "bold-sans-serif";
92
+ } else {
93
+ return "sans-serif";
94
+ }
95
+ } else if (options.fontShape === "textit" &&
96
+ options.fontWeight === "textbf") {
97
+ return "bold-italic";
71
98
  } else if (options.fontShape === "textit") {
72
- return "sans-serif-italic";
99
+ return "italic";
73
100
  } else if (options.fontWeight === "textbf") {
74
- return "bold-sans-serif";
75
- } else {
76
- return "sans-serif";
101
+ return "bold";
77
102
  }
78
- } else if (options.fontShape === "textit" &&
79
- options.fontWeight === "textbf") {
80
- return "bold-italic";
81
- } else if (options.fontShape === "textit") {
82
- return "italic";
83
- } else if (options.fontWeight === "textbf") {
84
- return "bold";
85
103
  }
86
104
 
87
105
  const font = options.font;
@@ -90,25 +108,12 @@ export const getVariant = function(
90
108
  }
91
109
 
92
110
  const mode = group.mode;
93
- if (font === "mathit") {
94
- return "italic";
95
- } else if (font === "boldsymbol") {
96
- return group.type === "textord" ? "bold" : "bold-italic";
97
- } else if (font === "mathbf") {
98
- return "bold";
99
- } else if (font === "mathbb") {
100
- return "double-struck";
101
- } else if (font === "mathsfit") {
102
- return "sans-serif-italic";
103
- } else if (font === "mathfrak") {
104
- return "fraktur";
105
- } else if (font === "mathscr" || font === "mathcal") {
106
- // MathML makes no distinction between script and calligraphic
107
- return "script";
108
- } else if (font === "mathsf") {
109
- return "sans-serif";
110
- } else if (font === "mathtt") {
111
- return "monospace";
111
+ const mathVariant = mathFontVariants[font];
112
+
113
+ if (mathVariant) {
114
+ return typeof mathVariant === "function"
115
+ ? mathVariant(group)
116
+ : mathVariant;
112
117
  }
113
118
 
114
119
  let text = group.text;
@@ -257,11 +262,10 @@ export const buildGroup = function(
257
262
  }
258
263
 
259
264
  if (groupBuilders[group.type]) {
260
- // Call the groupBuilders function
261
- // TODO(ts)
262
- const result: MathDomNode = groupBuilders[group.type](group, options);
263
- // TODO(ts)
264
- return result as MathNode;
265
+ // TODO(ts): MathMLBuilder returns MathDomNode but all concrete
266
+ // builders return MathNode. Widening the return type here would
267
+ // require updating all callers that assume MathNode.
268
+ return groupBuilders[group.type](group, options) as MathNode;
265
269
  } else {
266
270
  throw new ParseError(
267
271
  "Got group of unknown type: '" + group.type + "'");
@@ -319,7 +323,10 @@ export default function buildMathML(
319
323
  // NOTE: The span class is not typed to have <math> nodes as children, and
320
324
  // we don't want to make the children type more generic since the children
321
325
  // of span are expected to have more fields in `buildHtml` contexts.
326
+ // The MathNode implements VirtualNode (toNode/toMarkup) which is all that
327
+ // Span needs from its children for rendering.
328
+ // TODO(ts): Span's child type is HtmlDomNode, but MathNode only implements
329
+ // VirtualNode. The double-cast acknowledges this architectural limitation.
322
330
  const wrapperClass = forMathmlOnly ? "katex" : "katex-mathml";
323
- // TODO(ts)
324
- return makeSpan([wrapperClass], [math as any]);
331
+ return makeSpan([wrapperClass], [math as unknown as HtmlDomNode]);
325
332
  }
@@ -62,7 +62,7 @@ export type EnvSpec<NODETYPE extends NodeType> = {
62
62
  * `environments.js` exports this same dictionary again and makes it public.
63
63
  * `Parser.js` requires this dictionary via `environments.js`.
64
64
  */
65
- export const _environments: Record<string, EnvSpec<any>> = {};
65
+ export const _environments: Record<string, EnvSpec<NodeType>> = {};
66
66
 
67
67
  type EnvDefSpec<NODETYPE extends NodeType> = {
68
68
  // Unique string to differentiate parse nodes.
@@ -130,7 +130,7 @@ export type FunctionSpec<NODETYPE extends NodeType> = {
130
130
  // _functions is typed FunctionSpec<*> (it stores all TeX function specs).
131
131
 
132
132
  // Must be specified unless it's handled directly in the parser.
133
- handler: FunctionHandler<any> | null | undefined;
133
+ handler: FunctionHandler<NodeType> | null | undefined;
134
134
  };
135
135
 
136
136
  /**
@@ -138,18 +138,25 @@ export type FunctionSpec<NODETYPE extends NodeType> = {
138
138
  * `functions.js` just exports this same dictionary again and makes it public.
139
139
  * `Parser.js` requires this dictionary.
140
140
  */
141
- export const _functions: Record<string, FunctionSpec<any>> = {};
141
+ export const _functions: Record<string, FunctionSpec<NodeType>> = {};
142
142
 
143
143
  /**
144
144
  * All HTML builders. Should be only used in the `define*` and the `build*ML`
145
145
  * functions.
146
+ *
147
+ * Builders for different node types are stored side by side, but
148
+ * `HtmlBuilder<T>` is contravariant in `T`, so there is no single type
149
+ * argument that makes storing/retrieving them typecheck. `any` is used
150
+ * as an existential-quantifier escape hatch.
146
151
  */
152
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
147
153
  export const _htmlGroupBuilders: Record<string, HtmlBuilder<any>> = {};
148
154
 
149
155
  /**
150
156
  * All MathML builders. Should be only used in the `define*` and the `build*ML`
151
- * functions.
157
+ * functions. See `_htmlGroupBuilders` above for the rationale behind `any`.
152
158
  */
159
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
153
160
  export const _mathmlGroupBuilders: Record<string, MathMLBuilder<any>> = {};
154
161
 
155
162
  export default function defineFunction<NODETYPE extends NodeType>({
package/src/delimiter.ts CHANGED
@@ -32,11 +32,11 @@ import {makeEm} from "./units";
32
32
  import fontMetricsData from "./fontMetricsData";
33
33
 
34
34
  import type Options from "./Options";
35
- import type {CharacterMetrics} from "./fontMetrics";
36
35
  import type {HtmlDomNode, DomSpan, SvgSpan} from "./domTree";
37
- import type {Mode} from "./types";
36
+ import type {DelimiterSize, Mode} from "./types";
38
37
  import type {StyleInterface} from "./Style";
39
38
  import type {VListElem} from "./buildCommon";
39
+ import type {CharacterMetrics, FontName} from "./types/fonts";
40
40
 
41
41
  type StackedDelimiterFont = "Size1-Regular" | "Size4-Regular";
42
42
 
@@ -125,12 +125,11 @@ const makeSmallDelim = function(
125
125
  */
126
126
  const mathrmSize = function(
127
127
  value: string,
128
- size: number,
128
+ size: DelimiterSize,
129
129
  mode: Mode,
130
130
  options: Options,
131
131
  ): SymbolNode {
132
- return makeSymbol(value, "Size" + size + "-Regular",
133
- mode, options);
132
+ return makeSymbol(value, `Size${size}-Regular`, mode, options);
134
133
  };
135
134
 
136
135
  /**
@@ -138,7 +137,7 @@ const mathrmSize = function(
138
137
  * Size3, or Size4 fonts. It is always rendered in textstyle.
139
138
  */
140
139
  const makeLargeDelim = function(delim: string,
141
- size: number,
140
+ size: DelimiterSize,
142
141
  center: boolean,
143
142
  options: Options,
144
143
  mode: Mode,
@@ -507,9 +506,9 @@ export const makeSqrtImage = function(
507
506
 
508
507
  // Create a span containing an SVG image of a sqrt symbol.
509
508
  let span;
510
- let spanHeight = 0;
511
- let texHeight = 0;
512
- let viewBoxHeight = 0;
509
+ let spanHeight;
510
+ let texHeight;
511
+ let viewBoxHeight;
513
512
  let advanceWidth;
514
513
 
515
514
  // We create viewBoxes with 80 units of "padding" above each surd.
@@ -608,7 +607,7 @@ export const sizeToMaxHeight = [0, 1.2, 1.8, 2.4, 3.0];
608
607
  */
609
608
  export const makeSizedDelim = function(
610
609
  delim: string,
611
- size: number,
610
+ size: DelimiterSize,
612
611
  options: Options,
613
612
  mode: Mode,
614
613
  classes: string[],
@@ -621,12 +620,17 @@ export const makeSizedDelim = function(
621
620
  }
622
621
 
623
622
  // Sized delimiters are never centered.
624
- if (stackLargeDelimiters.has(delim) ||
625
- stackNeverDelimiters.has(delim)) {
623
+ if (stackLargeDelimiters.has(delim) || stackNeverDelimiters.has(delim)) {
626
624
  return makeLargeDelim(delim, size, false, options, mode, classes);
627
625
  } else if (stackAlwaysDelimiters.has(delim)) {
628
626
  return makeStackedDelim(
629
- delim, sizeToMaxHeight[size], false, options, mode, classes);
627
+ delim,
628
+ sizeToMaxHeight[size],
629
+ false,
630
+ options,
631
+ mode,
632
+ classes,
633
+ );
630
634
  } else {
631
635
  throw new ParseError("Illegal delimiter: '" + delim + "'");
632
636
  }
package/src/domTree.ts CHANGED
@@ -28,6 +28,21 @@ export const createClass = function(classes: string[]): string {
28
28
  return classes.filter(cls => cls).join(" ");
29
29
  };
30
30
 
31
+ /**
32
+ * Serialize a CssStyle object into a semicolon-delimited inline-style string
33
+ * (hyphenating camelCase property names). Returns "" when no property is set.
34
+ */
35
+ const cssStyleToString = function(style: CssStyle): string {
36
+ let styles = "";
37
+ for (const key of Object.keys(style) as Array<keyof CssStyle>) {
38
+ const value = style[key];
39
+ if (value !== undefined) {
40
+ styles += `${hyphenate(key)}:${value};`;
41
+ }
42
+ }
43
+ return styles;
44
+ };
45
+
31
46
  type InitNodeData = {
32
47
  classes: string[];
33
48
  attributes: Record<string, string>;
@@ -74,9 +89,7 @@ const toNode = function(this: HtmlNodeData, tagName: string): HTMLElement {
74
89
  node.className = createClass(this.classes);
75
90
 
76
91
  // Apply inline styles
77
- for (const key of Object.keys(this.style) as Array<keyof CssStyle>) {
78
- (node.style as any)[key] = this.style[key];
79
- }
92
+ Object.assign(node.style, this.style);
80
93
 
81
94
  // Apply attributes
82
95
  for (const attr of Object.keys(this.attributes)) {
@@ -112,13 +125,7 @@ const toMarkup = function(this: HtmlNodeData, tagName: string): string {
112
125
  markup += ` class="${escape(createClass(this.classes))}"`;
113
126
  }
114
127
 
115
- let styles = "";
116
-
117
- // Add the styles, after hyphenation
118
- for (const key of Object.keys(this.style) as Array<keyof CssStyle>) {
119
- styles += `${hyphenate(key)}:${this.style[key]};`;
120
- }
121
-
128
+ const styles = cssStyleToString(this.style);
122
129
  if (styles) {
123
130
  markup += ` style="${escape(styles)}"`;
124
131
  }
@@ -211,6 +218,12 @@ export class Span<ChildType extends VirtualNode> implements HtmlDomNode {
211
218
  width: number | null | undefined;
212
219
  maxFontSize!: number;
213
220
  style!: CssStyle;
221
+ /**
222
+ * Italic correction carried over from a SymbolNode when the symbol is
223
+ * wrapped in a vlist (e.g. \oiint / \oiiint). Read by supsub to adjust
224
+ * subscript positioning. Only set when nonzero; use `?? 0` at read sites.
225
+ */
226
+ italic?: number;
214
227
 
215
228
  constructor(
216
229
  classes?: string[],
@@ -322,9 +335,7 @@ export class Img implements VirtualNode {
322
335
  node.className = "mord";
323
336
 
324
337
  // Apply inline styles
325
- for (const key of Object.keys(this.style) as Array<keyof CssStyle>) {
326
- (node.style as any)[key] = this.style[key];
327
- }
338
+ Object.assign(node.style, this.style);
328
339
 
329
340
  return node;
330
341
  }
@@ -333,11 +344,7 @@ export class Img implements VirtualNode {
333
344
  let markup = `<img src="${escape(this.src)}"` +
334
345
  ` alt="${escape(this.alt)}"`;
335
346
 
336
- // Add the styles, after hyphenation
337
- let styles = "";
338
- for (const key of Object.keys(this.style) as Array<keyof CssStyle>) {
339
- styles += `${hyphenate(key)}:${this.style[key]};`;
340
- }
347
+ const styles = cssStyleToString(this.style);
341
348
  if (styles) {
342
349
  markup += ` style="${escape(styles)}"`;
343
350
  }
@@ -430,9 +437,9 @@ export class SymbolNode implements HtmlDomNode {
430
437
  span.className = createClass(this.classes);
431
438
  }
432
439
 
433
- for (const key of Object.keys(this.style) as Array<keyof CssStyle>) {
440
+ if (Object.keys(this.style).length > 0) {
434
441
  span = span || document.createElement("span");
435
- (span.style as any)[key] = this.style[key];
442
+ Object.assign(span.style, this.style);
436
443
  }
437
444
 
438
445
  if (span) {
@@ -465,9 +472,7 @@ export class SymbolNode implements HtmlDomNode {
465
472
  if (this.italic > 0) {
466
473
  styles += `margin-right:${makeEm(this.italic)};`;
467
474
  }
468
- for (const key of Object.keys(this.style) as Array<keyof CssStyle>) {
469
- styles += hyphenate(key) + ":" + this.style[key] + ";";
470
- }
475
+ styles += cssStyleToString(this.style);
471
476
 
472
477
  if (styles) {
473
478
  needsSpan = true;
@@ -168,7 +168,7 @@ function parseArray(
168
168
  // Test for \hline at the top of the array.
169
169
  hLinesBeforeRow.push(getHLines(parser));
170
170
 
171
- while (true) { // eslint-disable-line no-constant-condition
171
+ while (true) {
172
172
  // Parse each cell in its own group (namespace)
173
173
  const cellBody = parser.parseExpression(false, singleRow ? "\\end" : "\\\\");
174
174
  parser.gullet.endGroup();
@@ -183,6 +183,7 @@ function parseArray(
183
183
  type: "styling",
184
184
  mode: parser.mode,
185
185
  style,
186
+ resetFont: true,
186
187
  body: [cell],
187
188
  };
188
189
  }
@@ -275,7 +276,7 @@ function dCellStyle(envName: string): StyleStr {
275
276
  }
276
277
 
277
278
  type Outrow = {
278
- [idx: number]: any;
279
+ cells: HtmlDomNode[];
279
280
  height: number;
280
281
  depth: number;
281
282
  pos: number;
@@ -287,7 +288,7 @@ const htmlBuilder: HtmlBuilder<"array"> = function(group, options) {
287
288
  const nr = group.body.length;
288
289
  const hLinesBeforeRow = group.hLinesBeforeRow;
289
290
  let nc = 0;
290
- const body = new Array(nr);
291
+ const body: Outrow[] = new Array(nr);
291
292
  const hlines: Array<{pos: number; isDashed: boolean}> = [];
292
293
  const ruleThickness = Math.max(
293
294
  // From LaTeX \showthe\arrayrulewidth. Equals 0.04 em.
@@ -341,7 +342,12 @@ const htmlBuilder: HtmlBuilder<"array"> = function(group, options) {
341
342
  nc = inrow.length;
342
343
  }
343
344
 
344
- const outrow: Outrow = (new Array(inrow.length) as any);
345
+ const outrow: Outrow = {
346
+ cells: new Array<HtmlDomNode>(inrow.length),
347
+ height: 0,
348
+ depth: 0,
349
+ pos: 0,
350
+ };
345
351
  for (c = 0; c < inrow.length; ++c) {
346
352
  const elt = html.buildGroup(inrow[c], options);
347
353
  if (depth < elt.depth) {
@@ -350,7 +356,7 @@ const htmlBuilder: HtmlBuilder<"array"> = function(group, options) {
350
356
  if (height < elt.height) {
351
357
  height = elt.height;
352
358
  }
353
- outrow[c] = elt;
359
+ outrow.cells[c] = elt;
354
360
  }
355
361
 
356
362
  const rowGap = group.rowGaps[r];
@@ -480,7 +486,7 @@ const htmlBuilder: HtmlBuilder<"array"> = function(group, options) {
480
486
  }> = [];
481
487
  for (r = 0; r < nr; ++r) {
482
488
  const row = body[r];
483
- const elem = row[c];
489
+ const elem = row.cells[c];
484
490
  if (!elem) {
485
491
  continue;
486
492
  }