UNPKG

28.1 kBJavaScriptView Raw
1// @flow
2/* eslint no-console:0 */
3/**
4 * This module contains general functions that can be used for building
5 * different kinds of domTree nodes in a consistent manner.
6 */
7
8import {SymbolNode, Anchor, Span, PathNode, SvgNode, createClass} from "./domTree";
9import {getCharacterMetrics} from "./fontMetrics";
10import symbols, {ligatures} from "./symbols";
11import utils from "./utils";
12import {wideCharacterFont} from "./wide-character";
13import {calculateSize} from "./units";
14import {DocumentFragment} from "./tree";
15
16import type Options from "./Options";
17import type {ParseNode} from "./parseNode";
18import type {CharacterMetrics} from "./fontMetrics";
19import type {FontVariant, Mode} from "./types";
20import type {documentFragment as HtmlDocumentFragment} from "./domTree";
21import type {HtmlDomNode, DomSpan, SvgSpan, CssStyle} from "./domTree";
22import type {Measurement} from "./units";
23
24// The following have to be loaded from Main-Italic font, using class mathit
25const mathitLetters = [
26 "\\imath", "ı", // dotless i
27 "\\jmath", "ȷ", // dotless j
28 "\\pounds", "\\mathsterling", "\\textsterling", "£", // pounds symbol
29];
30
31/**
32 * Looks up the given symbol in fontMetrics, after applying any symbol
33 * replacements defined in symbol.js
34 */
35const lookupSymbol = function(
36 value: string,
37 // TODO(#963): Use a union type for this.
38 fontName: string,
39 mode: Mode,
40): {value: string, metrics: ?CharacterMetrics} {
41 // Replace the value with its replaced value from symbol.js
42 if (symbols[mode][value] && symbols[mode][value].replace) {
43 value = symbols[mode][value].replace;
44 }
45 return {
46 value: value,
47 metrics: getCharacterMetrics(value, fontName, mode),
48 };
49};
50
51/**
52 * Makes a symbolNode after translation via the list of symbols in symbols.js.
53 * Correctly pulls out metrics for the character, and optionally takes a list of
54 * classes to be attached to the node.
55 *
56 * TODO: make argument order closer to makeSpan
57 * TODO: add a separate argument for math class (e.g. `mop`, `mbin`), which
58 * should if present come first in `classes`.
59 * TODO(#953): Make `options` mandatory and always pass it in.
60 */
61const makeSymbol = function(
62 value: string,
63 fontName: string,
64 mode: Mode,
65 options?: Options,
66 classes?: string[],
67): SymbolNode {
68 const lookup = lookupSymbol(value, fontName, mode);
69 const metrics = lookup.metrics;
70 value = lookup.value;
71
72 let symbolNode;
73 if (metrics) {
74 let italic = metrics.italic;
75 if (mode === "text" || (options && options.font === "mathit")) {
76 italic = 0;
77 }
78 symbolNode = new SymbolNode(
79 value, metrics.height, metrics.depth, italic, metrics.skew,
80 metrics.width, classes);
81 } else {
82 // TODO(emily): Figure out a good way to only print this in development
83 typeof console !== "undefined" && console.warn(
84 "No character metrics for '" + value + "' in style '" +
85 fontName + "'");
86 symbolNode = new SymbolNode(value, 0, 0, 0, 0, 0, classes);
87 }
88
89 if (options) {
90 symbolNode.maxFontSize = options.sizeMultiplier;
91 if (options.style.isTight()) {
92 symbolNode.classes.push("mtight");
93 }
94 const color = options.getColor();
95 if (color) {
96 symbolNode.style.color = color;
97 }
98 }
99
100 return symbolNode;
101};
102
103/**
104 * Makes a symbol in Main-Regular or AMS-Regular.
105 * Used for rel, bin, open, close, inner, and punct.
106 *
107 * TODO(#953): Make `options` mandatory and always pass it in.
108 */
109const mathsym = function(
110 value: string,
111 mode: Mode,
112 options?: Options,
113 classes?: string[] = [],
114): SymbolNode {
115 // Decide what font to render the symbol in by its entry in the symbols
116 // table.
117 // Have a special case for when the value = \ because the \ is used as a
118 // textord in unsupported command errors but cannot be parsed as a regular
119 // text ordinal and is therefore not present as a symbol in the symbols
120 // table for text, as well as a special case for boldsymbol because it
121 // can be used for bold + and -
122 if ((options && options.font && options.font === "boldsymbol") &&
123 lookupSymbol(value, "Main-Bold", mode).metrics) {
124 return makeSymbol(value, "Main-Bold", mode, options,
125 classes.concat(["mathbf"]));
126 } else if (value === "\\" || symbols[mode][value].font === "main") {
127 return makeSymbol(value, "Main-Regular", mode, options, classes);
128 } else {
129 return makeSymbol(
130 value, "AMS-Regular", mode, options, classes.concat(["amsrm"]));
131 }
132};
133
134/**
135 * Determines which of the two font names (Main-Italic and Math-Italic) and
136 * corresponding style tags (maindefault or mathit) to use for default math font,
137 * depending on the symbol.
138 */
139const mathdefault = function(
140 value: string,
141 mode: Mode,
142 options: Options,
143 classes: string[],
144): {| fontName: string, fontClass: string |} {
145 if (/[0-9]/.test(value.charAt(0)) ||
146 // glyphs for \imath and \jmath do not exist in Math-Italic so we
147 // need to use Main-Italic instead
148 utils.contains(mathitLetters, value)) {
149 return {
150 fontName: "Main-Italic",
151 fontClass: "mathit",
152 };
153 } else {
154 return {
155 fontName: "Math-Italic",
156 fontClass: "mathdefault",
157 };
158 }
159};
160
161/**
162 * Determines which of the font names (Main-Italic, Math-Italic, and Caligraphic)
163 * and corresponding style tags (mathit, mathdefault, or mathcal) to use for font
164 * "mathnormal", depending on the symbol. Use this function instead of fontMap for
165 * font "mathnormal".
166 */
167const mathnormal = function(
168 value: string,
169 mode: Mode,
170 options: Options,
171 classes: string[],
172): {| fontName: string, fontClass: string |} {
173 if (utils.contains(mathitLetters, value)) {
174 return {
175 fontName: "Main-Italic",
176 fontClass: "mathit",
177 };
178 } else if (/[0-9]/.test(value.charAt(0))) {
179 return {
180 fontName: "Caligraphic-Regular",
181 fontClass: "mathcal",
182 };
183 } else {
184 return {
185 fontName: "Math-Italic",
186 fontClass: "mathdefault",
187 };
188 }
189};
190
191/**
192 * Determines which of the two font names (Main-Bold and Math-BoldItalic) and
193 * corresponding style tags (mathbf or boldsymbol) to use for font "boldsymbol",
194 * depending on the symbol. Use this function instead of fontMap for font
195 * "boldsymbol".
196 */
197const boldsymbol = function(
198 value: string,
199 mode: Mode,
200 options: Options,
201 classes: string[],
202): {| fontName: string, fontClass: string |} {
203 if (lookupSymbol(value, "Math-BoldItalic", mode).metrics) {
204 return {
205 fontName: "Math-BoldItalic",
206 fontClass: "boldsymbol",
207 };
208 } else {
209 // Some glyphs do not exist in Math-BoldItalic so we need to use
210 // Main-Bold instead.
211 return {
212 fontName: "Main-Bold",
213 fontClass: "mathbf",
214 };
215 }
216};
217
218/**
219 * Makes either a mathord or textord in the correct font and color.
220 */
221const makeOrd = function<NODETYPE: "spacing" | "mathord" | "textord">(
222 group: ParseNode<NODETYPE>,
223 options: Options,
224 type: "mathord" | "textord",
225): HtmlDocumentFragment | SymbolNode {
226 const mode = group.mode;
227 const text = group.text;
228
229 const classes = ["mord"];
230
231 // Math mode or Old font (i.e. \rm)
232 const isFont = mode === "math" || (mode === "text" && options.font);
233 const fontOrFamily = isFont ? options.font : options.fontFamily;
234 if (text.charCodeAt(0) === 0xD835) {
235 // surrogate pairs get special treatment
236 const [wideFontName, wideFontClass] = wideCharacterFont(text, mode);
237 return makeSymbol(text, wideFontName, mode, options,
238 classes.concat(wideFontClass));
239 } else if (fontOrFamily) {
240 let fontName;
241 let fontClasses;
242 if (fontOrFamily === "boldsymbol" || fontOrFamily === "mathnormal") {
243 const fontData = fontOrFamily === "boldsymbol"
244 ? boldsymbol(text, mode, options, classes)
245 : mathnormal(text, mode, options, classes);
246 fontName = fontData.fontName;
247 fontClasses = [fontData.fontClass];
248 } else if (utils.contains(mathitLetters, text)) {
249 fontName = "Main-Italic";
250 fontClasses = ["mathit"];
251 } else if (isFont) {
252 fontName = fontMap[fontOrFamily].fontName;
253 fontClasses = [fontOrFamily];
254 } else {
255 fontName = retrieveTextFontName(fontOrFamily, options.fontWeight,
256 options.fontShape);
257 fontClasses = [fontOrFamily, options.fontWeight, options.fontShape];
258 }
259
260 if (lookupSymbol(text, fontName, mode).metrics) {
261 return makeSymbol(text, fontName, mode, options,
262 classes.concat(fontClasses));
263 } else if (ligatures.hasOwnProperty(text) &&
264 fontName.substr(0, 10) === "Typewriter") {
265 // Deconstruct ligatures in monospace fonts (\texttt, \tt).
266 const parts = [];
267 for (let i = 0; i < text.length; i++) {
268 parts.push(makeSymbol(text[i], fontName, mode, options,
269 classes.concat(fontClasses)));
270 }
271 return makeFragment(parts);
272 }
273 }
274
275 // Makes a symbol in the default font for mathords and textords.
276 if (type === "mathord") {
277 const fontLookup = mathdefault(text, mode, options, classes);
278 return makeSymbol(text, fontLookup.fontName, mode, options,
279 classes.concat([fontLookup.fontClass]));
280 } else if (type === "textord") {
281 const font = symbols[mode][text] && symbols[mode][text].font;
282 if (font === "ams") {
283 const fontName = retrieveTextFontName("amsrm", options.fontWeight,
284 options.fontShape);
285 return makeSymbol(
286 text, fontName, mode, options,
287 classes.concat("amsrm", options.fontWeight, options.fontShape));
288 } else if (font === "main" || !font) {
289 const fontName = retrieveTextFontName("textrm", options.fontWeight,
290 options.fontShape);
291 return makeSymbol(
292 text, fontName, mode, options,
293 classes.concat(options.fontWeight, options.fontShape));
294 } else { // fonts added by plugins
295 const fontName = retrieveTextFontName(font, options.fontWeight,
296 options.fontShape);
297 // We add font name as a css class
298 return makeSymbol(
299 text, fontName, mode, options,
300 classes.concat(fontName, options.fontWeight, options.fontShape));
301 }
302 } else {
303 throw new Error("unexpected type: " + type + " in makeOrd");
304 }
305};
306
307/**
308 * Returns true if subsequent symbolNodes have the same classes, skew, maxFont,
309 * and styles.
310 */
311const canCombine = (prev: SymbolNode, next: SymbolNode) => {
312 if (createClass(prev.classes) !== createClass(next.classes)
313 || prev.skew !== next.skew
314 || prev.maxFontSize !== next.maxFontSize) {
315 return false;
316 }
317
318 for (const style in prev.style) {
319 if (prev.style.hasOwnProperty(style)
320 && prev.style[style] !== next.style[style]) {
321 return false;
322 }
323 }
324
325 for (const style in next.style) {
326 if (next.style.hasOwnProperty(style)
327 && prev.style[style] !== next.style[style]) {
328 return false;
329 }
330 }
331
332 return true;
333};
334
335/**
336 * Combine consequetive domTree.symbolNodes into a single symbolNode.
337 * Note: this function mutates the argument.
338 */
339const tryCombineChars = (chars: HtmlDomNode[]): HtmlDomNode[] => {
340 for (let i = 0; i < chars.length - 1; i++) {
341 const prev = chars[i];
342 const next = chars[i + 1];
343 if (prev instanceof SymbolNode
344 && next instanceof SymbolNode
345 && canCombine(prev, next)) {
346
347 prev.text += next.text;
348 prev.height = Math.max(prev.height, next.height);
349 prev.depth = Math.max(prev.depth, next.depth);
350 // Use the last character's italic correction since we use
351 // it to add padding to the right of the span created from
352 // the combined characters.
353 prev.italic = next.italic;
354 chars.splice(i + 1, 1);
355 i--;
356 }
357 }
358 return chars;
359};
360
361/**
362 * Calculate the height, depth, and maxFontSize of an element based on its
363 * children.
364 */
365const sizeElementFromChildren = function(
366 elem: DomSpan | Anchor | HtmlDocumentFragment,
367) {
368 let height = 0;
369 let depth = 0;
370 let maxFontSize = 0;
371
372 for (let i = 0; i < elem.children.length; i++) {
373 const child = elem.children[i];
374 if (child.height > height) {
375 height = child.height;
376 }
377 if (child.depth > depth) {
378 depth = child.depth;
379 }
380 if (child.maxFontSize > maxFontSize) {
381 maxFontSize = child.maxFontSize;
382 }
383 }
384
385 elem.height = height;
386 elem.depth = depth;
387 elem.maxFontSize = maxFontSize;
388};
389
390/**
391 * Makes a span with the given list of classes, list of children, and options.
392 *
393 * TODO(#953): Ensure that `options` is always provided (currently some call
394 * sites don't pass it) and make the type below mandatory.
395 * TODO: add a separate argument for math class (e.g. `mop`, `mbin`), which
396 * should if present come first in `classes`.
397 */
398const makeSpan = function(
399 classes?: string[],
400 children?: HtmlDomNode[],
401 options?: Options,
402 style?: CssStyle,
403): DomSpan {
404 const span = new Span(classes, children, options, style);
405
406 sizeElementFromChildren(span);
407
408 return span;
409};
410
411// SVG one is simpler -- doesn't require height, depth, max-font setting.
412// This is also a separate method for typesafety.
413const makeSvgSpan = (
414 classes?: string[],
415 children?: SvgNode[],
416 options?: Options,
417 style?: CssStyle,
418): SvgSpan => new Span(classes, children, options, style);
419
420const makeLineSpan = function(
421 className: string,
422 options: Options,
423 thickness?: number,
424) {
425 const line = makeSpan([className], [], options);
426 line.height = thickness || options.fontMetrics().defaultRuleThickness;
427 line.style.borderBottomWidth = line.height + "em";
428 line.maxFontSize = 1.0;
429 return line;
430};
431
432/**
433 * Makes an anchor with the given href, list of classes, list of children,
434 * and options.
435 */
436const makeAnchor = function(
437 href: string,
438 classes: string[],
439 children: HtmlDomNode[],
440 options: Options,
441) {
442 const anchor = new Anchor(href, classes, children, options);
443
444 sizeElementFromChildren(anchor);
445
446 return anchor;
447};
448
449/**
450 * Makes a document fragment with the given list of children.
451 */
452const makeFragment = function(
453 children: HtmlDomNode[],
454): HtmlDocumentFragment {
455 const fragment = new DocumentFragment(children);
456
457 sizeElementFromChildren(fragment);
458
459 return fragment;
460};
461
462/**
463 * Wraps group in a span if it's a document fragment, allowing to apply classes
464 * and styles
465 */
466const wrapFragment = function(
467 group: HtmlDomNode,
468 options: Options,
469): HtmlDomNode {
470 if (group instanceof DocumentFragment) {
471 return makeSpan([], [group], options);
472 }
473 return group;
474};
475
476
477// These are exact object types to catch typos in the names of the optional fields.
478export type VListElem = {|
479 type: "elem",
480 elem: HtmlDomNode,
481 marginLeft?: ?string,
482 marginRight?: string,
483 wrapperClasses?: string[],
484 wrapperStyle?: CssStyle,
485|};
486type VListElemAndShift = {|
487 type: "elem",
488 elem: HtmlDomNode,
489 shift: number,
490 marginLeft?: ?string,
491 marginRight?: string,
492 wrapperClasses?: string[],
493 wrapperStyle?: CssStyle,
494|};
495type VListKern = {| type: "kern", size: number |};
496
497// A list of child or kern nodes to be stacked on top of each other (i.e. the
498// first element will be at the bottom, and the last at the top).
499type VListChild = VListElem | VListKern;
500
501type VListParam = {|
502 // Each child contains how much it should be shifted downward.
503 positionType: "individualShift",
504 children: VListElemAndShift[],
505|} | {|
506 // "top": The positionData specifies the topmost point of the vlist (note this
507 // is expected to be a height, so positive values move up).
508 // "bottom": The positionData specifies the bottommost point of the vlist (note
509 // this is expected to be a depth, so positive values move down).
510 // "shift": The vlist will be positioned such that its baseline is positionData
511 // away from the baseline of the first child which MUST be an
512 // "elem". Positive values move downwards.
513 positionType: "top" | "bottom" | "shift",
514 positionData: number,
515 children: VListChild[],
516|} | {|
517 // The vlist is positioned so that its baseline is aligned with the baseline
518 // of the first child which MUST be an "elem". This is equivalent to "shift"
519 // with positionData=0.
520 positionType: "firstBaseline",
521 children: VListChild[],
522|};
523
524
525// Computes the updated `children` list and the overall depth.
526//
527// This helper function for makeVList makes it easier to enforce type safety by
528// allowing early exits (returns) in the logic.
529const getVListChildrenAndDepth = function(params: VListParam): {
530 children: (VListChild | VListElemAndShift)[] | VListChild[],
531 depth: number,
532} {
533 if (params.positionType === "individualShift") {
534 const oldChildren = params.children;
535 const children: (VListChild | VListElemAndShift)[] = [oldChildren[0]];
536
537 // Add in kerns to the list of params.children to get each element to be
538 // shifted to the correct specified shift
539 const depth = -oldChildren[0].shift - oldChildren[0].elem.depth;
540 let currPos = depth;
541 for (let i = 1; i < oldChildren.length; i++) {
542 const diff = -oldChildren[i].shift - currPos -
543 oldChildren[i].elem.depth;
544 const size = diff -
545 (oldChildren[i - 1].elem.height +
546 oldChildren[i - 1].elem.depth);
547
548 currPos = currPos + diff;
549
550 children.push({type: "kern", size});
551 children.push(oldChildren[i]);
552 }
553
554 return {children, depth};
555 }
556
557 let depth;
558 if (params.positionType === "top") {
559 // We always start at the bottom, so calculate the bottom by adding up
560 // all the sizes
561 let bottom = params.positionData;
562 for (let i = 0; i < params.children.length; i++) {
563 const child = params.children[i];
564 bottom -= child.type === "kern"
565 ? child.size
566 : child.elem.height + child.elem.depth;
567 }
568 depth = bottom;
569 } else if (params.positionType === "bottom") {
570 depth = -params.positionData;
571 } else {
572 const firstChild = params.children[0];
573 if (firstChild.type !== "elem") {
574 throw new Error('First child must have type "elem".');
575 }
576 if (params.positionType === "shift") {
577 depth = -firstChild.elem.depth - params.positionData;
578 } else if (params.positionType === "firstBaseline") {
579 depth = -firstChild.elem.depth;
580 } else {
581 throw new Error(`Invalid positionType ${params.positionType}.`);
582 }
583 }
584 return {children: params.children, depth};
585};
586
587/**
588 * Makes a vertical list by stacking elements and kerns on top of each other.
589 * Allows for many different ways of specifying the positioning method.
590 *
591 * See VListParam documentation above.
592 */
593const makeVList = function(params: VListParam, options: Options): DomSpan {
594 const {children, depth} = getVListChildrenAndDepth(params);
595
596 // Create a strut that is taller than any list item. The strut is added to
597 // each item, where it will determine the item's baseline. Since it has
598 // `overflow:hidden`, the strut's top edge will sit on the item's line box's
599 // top edge and the strut's bottom edge will sit on the item's baseline,
600 // with no additional line-height spacing. This allows the item baseline to
601 // be positioned precisely without worrying about font ascent and
602 // line-height.
603 let pstrutSize = 0;
604 for (let i = 0; i < children.length; i++) {
605 const child = children[i];
606 if (child.type === "elem") {
607 const elem = child.elem;
608 pstrutSize = Math.max(pstrutSize, elem.maxFontSize, elem.height);
609 }
610 }
611 pstrutSize += 2;
612 const pstrut = makeSpan(["pstrut"], []);
613 pstrut.style.height = pstrutSize + "em";
614
615 // Create a new list of actual children at the correct offsets
616 const realChildren = [];
617 let minPos = depth;
618 let maxPos = depth;
619 let currPos = depth;
620 for (let i = 0; i < children.length; i++) {
621 const child = children[i];
622 if (child.type === "kern") {
623 currPos += child.size;
624 } else {
625 const elem = child.elem;
626 const classes = child.wrapperClasses || [];
627 const style = child.wrapperStyle || {};
628
629 const childWrap = makeSpan(classes, [pstrut, elem], undefined, style);
630 childWrap.style.top = (-pstrutSize - currPos - elem.depth) + "em";
631 if (child.marginLeft) {
632 childWrap.style.marginLeft = child.marginLeft;
633 }
634 if (child.marginRight) {
635 childWrap.style.marginRight = child.marginRight;
636 }
637
638 realChildren.push(childWrap);
639 currPos += elem.height + elem.depth;
640 }
641 minPos = Math.min(minPos, currPos);
642 maxPos = Math.max(maxPos, currPos);
643 }
644
645 // The vlist contents go in a table-cell with `vertical-align:bottom`.
646 // This cell's bottom edge will determine the containing table's baseline
647 // without overly expanding the containing line-box.
648 const vlist = makeSpan(["vlist"], realChildren);
649 vlist.style.height = maxPos + "em";
650
651 // A second row is used if necessary to represent the vlist's depth.
652 let rows;
653 if (minPos < 0) {
654 // We will define depth in an empty span with display: table-cell.
655 // It should render with the height that we define. But Chrome, in
656 // contenteditable mode only, treats that span as if it contains some
657 // text content. And that min-height over-rides our desired height.
658 // So we put another empty span inside the depth strut span.
659 const emptySpan = makeSpan([], []);
660 const depthStrut = makeSpan(["vlist"], [emptySpan]);
661 depthStrut.style.height = -minPos + "em";
662
663 // Safari wants the first row to have inline content; otherwise it
664 // puts the bottom of the *second* row on the baseline.
665 const topStrut = makeSpan(["vlist-s"], [new SymbolNode("\u200b")]);
666
667 rows = [makeSpan(["vlist-r"], [vlist, topStrut]),
668 makeSpan(["vlist-r"], [depthStrut])];
669 } else {
670 rows = [makeSpan(["vlist-r"], [vlist])];
671 }
672
673 const vtable = makeSpan(["vlist-t"], rows);
674 if (rows.length === 2) {
675 vtable.classes.push("vlist-t2");
676 }
677 vtable.height = maxPos;
678 vtable.depth = -minPos;
679 return vtable;
680};
681
682// Glue is a concept from TeX which is a flexible space between elements in
683// either a vertical or horizontal list. In KaTeX, at least for now, it's
684// static space between elements in a horizontal layout.
685const makeGlue = (measurement: Measurement, options: Options): DomSpan => {
686 // Make an empty span for the space
687 const rule = makeSpan(["mspace"], [], options);
688 const size = calculateSize(measurement, options);
689 rule.style.marginRight = `${size}em`;
690 return rule;
691};
692
693// Takes font options, and returns the appropriate fontLookup name
694const retrieveTextFontName = function(
695 fontFamily: string,
696 fontWeight: string,
697 fontShape: string,
698): string {
699 let baseFontName = "";
700 switch (fontFamily) {
701 case "amsrm":
702 baseFontName = "AMS";
703 break;
704 case "textrm":
705 baseFontName = "Main";
706 break;
707 case "textsf":
708 baseFontName = "SansSerif";
709 break;
710 case "texttt":
711 baseFontName = "Typewriter";
712 break;
713 default:
714 baseFontName = fontFamily; // use fonts added by a plugin
715 }
716
717 let fontStylesName;
718 if (fontWeight === "textbf" && fontShape === "textit") {
719 fontStylesName = "BoldItalic";
720 } else if (fontWeight === "textbf") {
721 fontStylesName = "Bold";
722 } else if (fontWeight === "textit") {
723 fontStylesName = "Italic";
724 } else {
725 fontStylesName = "Regular";
726 }
727
728 return `${baseFontName}-${fontStylesName}`;
729};
730
731/**
732 * Maps TeX font commands to objects containing:
733 * - variant: string used for "mathvariant" attribute in buildMathML.js
734 * - fontName: the "style" parameter to fontMetrics.getCharacterMetrics
735 */
736// A map between tex font commands an MathML mathvariant attribute values
737const fontMap: {[string]: {| variant: FontVariant, fontName: string |}} = {
738 // styles
739 "mathbf": {
740 variant: "bold",
741 fontName: "Main-Bold",
742 },
743 "mathrm": {
744 variant: "normal",
745 fontName: "Main-Regular",
746 },
747 "textit": {
748 variant: "italic",
749 fontName: "Main-Italic",
750 },
751 "mathit": {
752 variant: "italic",
753 fontName: "Main-Italic",
754 },
755
756 // Default math font, "mathnormal" and "boldsymbol" are missing because they
757 // require the use of several fonts: Main-Italic and Math-Italic for default
758 // math font, Main-Italic, Math-Italic, Caligraphic for "mathnormal", and
759 // Math-BoldItalic and Main-Bold for "boldsymbol". This is handled by a
760 // special case in makeOrd which ends up calling mathdefault, mathnormal,
761 // and boldsymbol.
762
763 // families
764 "mathbb": {
765 variant: "double-struck",
766 fontName: "AMS-Regular",
767 },
768 "mathcal": {
769 variant: "script",
770 fontName: "Caligraphic-Regular",
771 },
772 "mathfrak": {
773 variant: "fraktur",
774 fontName: "Fraktur-Regular",
775 },
776 "mathscr": {
777 variant: "script",
778 fontName: "Script-Regular",
779 },
780 "mathsf": {
781 variant: "sans-serif",
782 fontName: "SansSerif-Regular",
783 },
784 "mathtt": {
785 variant: "monospace",
786 fontName: "Typewriter-Regular",
787 },
788};
789
790const svgData: {
791 [string]: ([string, number, number])
792} = {
793 // path, width, height
794 vec: ["vec", 0.471, 0.714], // values from the font glyph
795 oiintSize1: ["oiintSize1", 0.957, 0.499], // oval to overlay the integrand
796 oiintSize2: ["oiintSize2", 1.472, 0.659],
797 oiiintSize1: ["oiiintSize1", 1.304, 0.499],
798 oiiintSize2: ["oiiintSize2", 1.98, 0.659],
799};
800
801const staticSvg = function(value: string, options: Options): SvgSpan {
802 // Create a span with inline SVG for the element.
803 const [pathName, width, height] = svgData[value];
804 const path = new PathNode(pathName);
805 const svgNode = new SvgNode([path], {
806 "width": width + "em",
807 "height": height + "em",
808 // Override CSS rule `.katex svg { width: 100% }`
809 "style": "width:" + width + "em",
810 "viewBox": "0 0 " + 1000 * width + " " + 1000 * height,
811 "preserveAspectRatio": "xMinYMin",
812 });
813 const span = makeSvgSpan(["overlay"], [svgNode], options);
814 span.height = height;
815 span.style.height = height + "em";
816 span.style.width = width + "em";
817 return span;
818};
819
820export default {
821 fontMap,
822 makeSymbol,
823 mathsym,
824 makeSpan,
825 makeSvgSpan,
826 makeLineSpan,
827 makeAnchor,
828 makeFragment,
829 wrapFragment,
830 makeVList,
831 makeOrd,
832 makeGlue,
833 staticSvg,
834 svgData,
835 tryCombineChars,
836};