UNPKG

26.4 kBJavaScriptView Raw
1// @flow
2/**
3 * This file deals with creating delimiters of various sizes. The TeXbook
4 * discusses these routines on page 441-442, in the "Another subroutine sets box
5 * x to a specified variable delimiter" paragraph.
6 *
7 * There are three main routines here. `makeSmallDelim` makes a delimiter in the
8 * normal font, but in either text, script, or scriptscript style.
9 * `makeLargeDelim` makes a delimiter in textstyle, but in one of the Size1,
10 * Size2, Size3, or Size4 fonts. `makeStackedDelim` makes a delimiter out of
11 * smaller pieces that are stacked on top of one another.
12 *
13 * The functions take a parameter `center`, which determines if the delimiter
14 * should be centered around the axis.
15 *
16 * Then, there are three exposed functions. `sizedDelim` makes a delimiter in
17 * one of the given sizes. This is used for things like `\bigl`.
18 * `customSizedDelim` makes a delimiter with a given total height+depth. It is
19 * called in places like `\sqrt`. `leftRightDelim` makes an appropriate
20 * delimiter which surrounds an expression of a given height an depth. It is
21 * used in `\left` and `\right`.
22 */
23
24import ParseError from "./ParseError";
25import Style from "./Style";
26
27import {PathNode, SvgNode, SymbolNode} from "./domTree";
28import buildCommon from "./buildCommon";
29import {getCharacterMetrics} from "./fontMetrics";
30import symbols from "./symbols";
31import utils from "./utils";
32
33import type Options from "./Options";
34import type {CharacterMetrics} from "./fontMetrics";
35import type {HtmlDomNode, DomSpan, SvgSpan} from "./domTree";
36import type {Mode} from "./types";
37import type {StyleInterface} from "./Style";
38import type {VListElem} from "./buildCommon";
39
40/**
41 * Get the metrics for a given symbol and font, after transformation (i.e.
42 * after following replacement from symbols.js)
43 */
44const getMetrics = function(
45 symbol: string,
46 font: string,
47 mode: Mode,
48): CharacterMetrics {
49 const replace = symbols.math[symbol] && symbols.math[symbol].replace;
50 const metrics =
51 getCharacterMetrics(replace || symbol, font, mode);
52 if (!metrics) {
53 throw new Error(`Unsupported symbol ${symbol} and font size ${font}.`);
54 }
55 return metrics;
56};
57
58/**
59 * Puts a delimiter span in a given style, and adds appropriate height, depth,
60 * and maxFontSizes.
61 */
62const styleWrap = function(
63 delim: HtmlDomNode,
64 toStyle: StyleInterface,
65 options: Options,
66 classes: string[],
67): DomSpan {
68 const newOptions = options.havingBaseStyle(toStyle);
69
70 const span = buildCommon.makeSpan(
71 classes.concat(newOptions.sizingClasses(options)),
72 [delim], options);
73
74 const delimSizeMultiplier =
75 newOptions.sizeMultiplier / options.sizeMultiplier;
76 span.height *= delimSizeMultiplier;
77 span.depth *= delimSizeMultiplier;
78 span.maxFontSize = newOptions.sizeMultiplier;
79
80 return span;
81};
82
83const centerSpan = function(
84 span: DomSpan,
85 options: Options,
86 style: StyleInterface,
87) {
88 const newOptions = options.havingBaseStyle(style);
89 const shift =
90 (1 - options.sizeMultiplier / newOptions.sizeMultiplier) *
91 options.fontMetrics().axisHeight;
92
93 span.classes.push("delimcenter");
94 span.style.top = shift + "em";
95 span.height -= shift;
96 span.depth += shift;
97};
98
99/**
100 * Makes a small delimiter. This is a delimiter that comes in the Main-Regular
101 * font, but is restyled to either be in textstyle, scriptstyle, or
102 * scriptscriptstyle.
103 */
104const makeSmallDelim = function(
105 delim: string,
106 style: StyleInterface,
107 center: boolean,
108 options: Options,
109 mode: Mode,
110 classes: string[],
111): DomSpan {
112 const text = buildCommon.makeSymbol(delim, "Main-Regular", mode, options);
113 const span = styleWrap(text, style, options, classes);
114 if (center) {
115 centerSpan(span, options, style);
116 }
117 return span;
118};
119
120/**
121 * Builds a symbol in the given font size (note size is an integer)
122 */
123const mathrmSize = function(
124 value: string,
125 size: number,
126 mode: Mode,
127 options: Options,
128): SymbolNode {
129 return buildCommon.makeSymbol(value, "Size" + size + "-Regular",
130 mode, options);
131};
132
133/**
134 * Makes a large delimiter. This is a delimiter that comes in the Size1, Size2,
135 * Size3, or Size4 fonts. It is always rendered in textstyle.
136 */
137const makeLargeDelim = function(delim,
138 size: number,
139 center: boolean,
140 options: Options,
141 mode: Mode,
142 classes: string[],
143): DomSpan {
144 const inner = mathrmSize(delim, size, mode, options);
145 const span = styleWrap(
146 buildCommon.makeSpan(["delimsizing", "size" + size], [inner], options),
147 Style.TEXT, options, classes);
148 if (center) {
149 centerSpan(span, options, Style.TEXT);
150 }
151 return span;
152};
153
154/**
155 * Make an inner span with the given offset and in the given font. This is used
156 * in `makeStackedDelim` to make the stacking pieces for the delimiter.
157 */
158const makeInner = function(
159 symbol: string,
160 font: "Size1-Regular" | "Size4-Regular",
161 mode: Mode,
162): VListElem {
163 let sizeClass;
164 // Apply the correct CSS class to choose the right font.
165 if (font === "Size1-Regular") {
166 sizeClass = "delim-size1";
167 } else /* if (font === "Size4-Regular") */ {
168 sizeClass = "delim-size4";
169 }
170
171 const inner = buildCommon.makeSpan(
172 ["delimsizinginner", sizeClass],
173 [buildCommon.makeSpan([], [buildCommon.makeSymbol(symbol, font, mode)])]);
174
175 // Since this will be passed into `makeVList` in the end, wrap the element
176 // in the appropriate tag that VList uses.
177 return {type: "elem", elem: inner};
178};
179
180/**
181 * Make a stacked delimiter out of a given delimiter, with the total height at
182 * least `heightTotal`. This routine is mentioned on page 442 of the TeXbook.
183 */
184const makeStackedDelim = function(
185 delim: string,
186 heightTotal: number,
187 center: boolean,
188 options: Options,
189 mode: Mode,
190 classes: string[],
191): DomSpan {
192 // There are four parts, the top, an optional middle, a repeated part, and a
193 // bottom.
194 let top;
195 let middle;
196 let repeat;
197 let bottom;
198 top = repeat = bottom = delim;
199 middle = null;
200 // Also keep track of what font the delimiters are in
201 let font = "Size1-Regular";
202
203 // We set the parts and font based on the symbol. Note that we use
204 // '\u23d0' instead of '|' and '\u2016' instead of '\\|' for the
205 // repeats of the arrows
206 if (delim === "\\uparrow") {
207 repeat = bottom = "\u23d0";
208 } else if (delim === "\\Uparrow") {
209 repeat = bottom = "\u2016";
210 } else if (delim === "\\downarrow") {
211 top = repeat = "\u23d0";
212 } else if (delim === "\\Downarrow") {
213 top = repeat = "\u2016";
214 } else if (delim === "\\updownarrow") {
215 top = "\\uparrow";
216 repeat = "\u23d0";
217 bottom = "\\downarrow";
218 } else if (delim === "\\Updownarrow") {
219 top = "\\Uparrow";
220 repeat = "\u2016";
221 bottom = "\\Downarrow";
222 } else if (delim === "[" || delim === "\\lbrack") {
223 top = "\u23a1";
224 repeat = "\u23a2";
225 bottom = "\u23a3";
226 font = "Size4-Regular";
227 } else if (delim === "]" || delim === "\\rbrack") {
228 top = "\u23a4";
229 repeat = "\u23a5";
230 bottom = "\u23a6";
231 font = "Size4-Regular";
232 } else if (delim === "\\lfloor" || delim === "\u230a") {
233 repeat = top = "\u23a2";
234 bottom = "\u23a3";
235 font = "Size4-Regular";
236 } else if (delim === "\\lceil" || delim === "\u2308") {
237 top = "\u23a1";
238 repeat = bottom = "\u23a2";
239 font = "Size4-Regular";
240 } else if (delim === "\\rfloor" || delim === "\u230b") {
241 repeat = top = "\u23a5";
242 bottom = "\u23a6";
243 font = "Size4-Regular";
244 } else if (delim === "\\rceil" || delim === "\u2309") {
245 top = "\u23a4";
246 repeat = bottom = "\u23a5";
247 font = "Size4-Regular";
248 } else if (delim === "(" || delim === "\\lparen") {
249 top = "\u239b";
250 repeat = "\u239c";
251 bottom = "\u239d";
252 font = "Size4-Regular";
253 } else if (delim === ")" || delim === "\\rparen") {
254 top = "\u239e";
255 repeat = "\u239f";
256 bottom = "\u23a0";
257 font = "Size4-Regular";
258 } else if (delim === "\\{" || delim === "\\lbrace") {
259 top = "\u23a7";
260 middle = "\u23a8";
261 bottom = "\u23a9";
262 repeat = "\u23aa";
263 font = "Size4-Regular";
264 } else if (delim === "\\}" || delim === "\\rbrace") {
265 top = "\u23ab";
266 middle = "\u23ac";
267 bottom = "\u23ad";
268 repeat = "\u23aa";
269 font = "Size4-Regular";
270 } else if (delim === "\\lgroup" || delim === "\u27ee") {
271 top = "\u23a7";
272 bottom = "\u23a9";
273 repeat = "\u23aa";
274 font = "Size4-Regular";
275 } else if (delim === "\\rgroup" || delim === "\u27ef") {
276 top = "\u23ab";
277 bottom = "\u23ad";
278 repeat = "\u23aa";
279 font = "Size4-Regular";
280 } else if (delim === "\\lmoustache" || delim === "\u23b0") {
281 top = "\u23a7";
282 bottom = "\u23ad";
283 repeat = "\u23aa";
284 font = "Size4-Regular";
285 } else if (delim === "\\rmoustache" || delim === "\u23b1") {
286 top = "\u23ab";
287 bottom = "\u23a9";
288 repeat = "\u23aa";
289 font = "Size4-Regular";
290 }
291
292 // Get the metrics of the four sections
293 const topMetrics = getMetrics(top, font, mode);
294 const topHeightTotal = topMetrics.height + topMetrics.depth;
295 const repeatMetrics = getMetrics(repeat, font, mode);
296 const repeatHeightTotal = repeatMetrics.height + repeatMetrics.depth;
297 const bottomMetrics = getMetrics(bottom, font, mode);
298 const bottomHeightTotal = bottomMetrics.height + bottomMetrics.depth;
299 let middleHeightTotal = 0;
300 let middleFactor = 1;
301 if (middle !== null) {
302 const middleMetrics = getMetrics(middle, font, mode);
303 middleHeightTotal = middleMetrics.height + middleMetrics.depth;
304 middleFactor = 2; // repeat symmetrically above and below middle
305 }
306
307 // Calcuate the minimal height that the delimiter can have.
308 // It is at least the size of the top, bottom, and optional middle combined.
309 const minHeight = topHeightTotal + bottomHeightTotal + middleHeightTotal;
310
311 // Compute the number of copies of the repeat symbol we will need
312 const repeatCount = Math.ceil(
313 (heightTotal - minHeight) / (middleFactor * repeatHeightTotal));
314
315 // Compute the total height of the delimiter including all the symbols
316 const realHeightTotal =
317 minHeight + repeatCount * middleFactor * repeatHeightTotal;
318
319 // The center of the delimiter is placed at the center of the axis. Note
320 // that in this context, "center" means that the delimiter should be
321 // centered around the axis in the current style, while normally it is
322 // centered around the axis in textstyle.
323 let axisHeight = options.fontMetrics().axisHeight;
324 if (center) {
325 axisHeight *= options.sizeMultiplier;
326 }
327 // Calculate the depth
328 const depth = realHeightTotal / 2 - axisHeight;
329
330 // Now, we start building the pieces that will go into the vlist
331
332 // Keep a list of the inner pieces
333 const inners = [];
334
335 // Add the bottom symbol
336 inners.push(makeInner(bottom, font, mode));
337
338 if (middle === null) {
339 // Add that many symbols
340 for (let i = 0; i < repeatCount; i++) {
341 inners.push(makeInner(repeat, font, mode));
342 }
343 } else {
344 // When there is a middle bit, we need the middle part and two repeated
345 // sections
346 for (let i = 0; i < repeatCount; i++) {
347 inners.push(makeInner(repeat, font, mode));
348 }
349 inners.push(makeInner(middle, font, mode));
350 for (let i = 0; i < repeatCount; i++) {
351 inners.push(makeInner(repeat, font, mode));
352 }
353 }
354
355 // Add the top symbol
356 inners.push(makeInner(top, font, mode));
357
358 // Finally, build the vlist
359 const newOptions = options.havingBaseStyle(Style.TEXT);
360 const inner = buildCommon.makeVList({
361 positionType: "bottom",
362 positionData: depth,
363 children: inners,
364 }, newOptions);
365
366 return styleWrap(
367 buildCommon.makeSpan(["delimsizing", "mult"], [inner], newOptions),
368 Style.TEXT, options, classes);
369};
370
371// All surds have 0.08em padding above the viniculum inside the SVG.
372// That keeps browser span height rounding error from pinching the line.
373const vbPad = 80; // padding above the surd, measured inside the viewBox.
374const emPad = 0.08; // padding, in ems, measured in the document.
375
376const sqrtSvg = function(
377 sqrtName: string,
378 height: number,
379 viewBoxHeight: number,
380 options: Options,
381): SvgSpan {
382 let alternate;
383 if (sqrtName === "sqrtTall") {
384 // sqrtTall is from glyph U23B7 in the font KaTeX_Size4-Regular
385 // One path edge has a variable length. It runs from the viniculumn
386 // to a point near (14 units) the bottom of the surd. The viniculum
387 // is 40 units thick. So the length of the line in question is:
388 const vertSegment = viewBoxHeight - 54 - vbPad;
389 alternate = `M702 ${vbPad}H400000v40H742v${vertSegment}l-4 4-4 4c-.667.7
390-2 1.5-4 2.5s-4.167 1.833-6.5 2.5-5.5 1-9.5 1h-12l-28-84c-16.667-52-96.667
391-294.333-240-727l-212 -643 -85 170c-4-3.333-8.333-7.667-13 -13l-13-13l77-155
392 77-156c66 199.333 139 419.667 219 661 l218 661zM702 ${vbPad}H400000v40H742z`;
393 }
394 const pathNode = new PathNode(sqrtName, alternate);
395
396 const svg = new SvgNode([pathNode], {
397 // Note: 1000:1 ratio of viewBox to document em width.
398 "width": "400em",
399 "height": height + "em",
400 "viewBox": "0 0 400000 " + viewBoxHeight,
401 "preserveAspectRatio": "xMinYMin slice",
402 });
403
404 return buildCommon.makeSvgSpan(["hide-tail"], [svg], options);
405};
406
407/**
408 * Make a sqrt image of the given height,
409 */
410const makeSqrtImage = function(
411 height: number,
412 options: Options,
413): {
414 span: SvgSpan,
415 ruleWidth: number,
416 advanceWidth: number,
417} {
418 // Define a newOptions that removes the effect of size changes such as \Huge.
419 // We don't pick different a height surd for \Huge. For it, we scale up.
420 const newOptions = options.havingBaseSizing();
421
422 // Pick the desired surd glyph from a sequence of surds.
423 const delim = traverseSequence("\\surd", height * newOptions.sizeMultiplier,
424 stackLargeDelimiterSequence, newOptions);
425
426 let sizeMultiplier = newOptions.sizeMultiplier; // default
427
428 // Create a span containing an SVG image of a sqrt symbol.
429 let span;
430 let spanHeight = 0;
431 let texHeight = 0;
432 let viewBoxHeight = 0;
433 let advanceWidth;
434
435 // We create viewBoxes with 80 units of "padding" above each surd.
436 // Then browser rounding error on the parent span height will not
437 // encroach on the ink of the viniculum. But that padding is not
438 // included in the TeX-like `height` used for calculation of
439 // vertical alignment. So texHeight = span.height < span.style.height.
440
441 if (delim.type === "small") {
442 // Get an SVG that is derived from glyph U+221A in font KaTeX-Main.
443 viewBoxHeight = 1000 + vbPad; // 1000 unit glyph height.
444 if (height < 1.0) {
445 sizeMultiplier = 1.0; // mimic a \textfont radical
446 } else if (height < 1.4) {
447 sizeMultiplier = 0.7; // mimic a \scriptfont radical
448 }
449 spanHeight = (1.0 + emPad) / sizeMultiplier;
450 texHeight = 1.00 / sizeMultiplier;
451 span = sqrtSvg("sqrtMain", spanHeight, viewBoxHeight, options);
452 span.style.minWidth = "0.853em";
453 advanceWidth = 0.833 / sizeMultiplier; // from the font.
454
455 } else if (delim.type === "large") {
456 // These SVGs come from fonts: KaTeX_Size1, _Size2, etc.
457 viewBoxHeight = (1000 + vbPad) * sizeToMaxHeight[delim.size];
458 texHeight = sizeToMaxHeight[delim.size] / sizeMultiplier;
459 spanHeight = (sizeToMaxHeight[delim.size] + emPad) / sizeMultiplier;
460 span = sqrtSvg("sqrtSize" + delim.size, spanHeight, viewBoxHeight, options);
461 span.style.minWidth = "1.02em";
462 advanceWidth = 1.0 / sizeMultiplier; // 1.0 from the font.
463
464 } else {
465 // Tall sqrt. In TeX, this would be stacked using multiple glyphs.
466 // We'll use a single SVG to accomplish the same thing.
467 spanHeight = height + emPad;
468 texHeight = height;
469 viewBoxHeight = Math.floor(1000 * height) + vbPad;
470 span = sqrtSvg("sqrtTall", spanHeight, viewBoxHeight, options);
471 span.style.minWidth = "0.742em";
472 advanceWidth = 1.056;
473 }
474
475 span.height = texHeight;
476 span.style.height = spanHeight + "em";
477
478 return {
479 span,
480 advanceWidth,
481 // Calculate the actual line width.
482 // This actually should depend on the chosen font -- e.g. \boldmath
483 // should use the thicker surd symbols from e.g. KaTeX_Main-Bold, and
484 // have thicker rules.
485 ruleWidth: options.fontMetrics().sqrtRuleThickness * sizeMultiplier,
486 };
487};
488
489// There are three kinds of delimiters, delimiters that stack when they become
490// too large
491const stackLargeDelimiters = [
492 "(", "\\lparen", ")", "\\rparen",
493 "[", "\\lbrack", "]", "\\rbrack",
494 "\\{", "\\lbrace", "\\}", "\\rbrace",
495 "\\lfloor", "\\rfloor", "\u230a", "\u230b",
496 "\\lceil", "\\rceil", "\u2308", "\u2309",
497 "\\surd",
498];
499
500// delimiters that always stack
501const stackAlwaysDelimiters = [
502 "\\uparrow", "\\downarrow", "\\updownarrow",
503 "\\Uparrow", "\\Downarrow", "\\Updownarrow",
504 "|", "\\|", "\\vert", "\\Vert",
505 "\\lvert", "\\rvert", "\\lVert", "\\rVert",
506 "\\lgroup", "\\rgroup", "\u27ee", "\u27ef",
507 "\\lmoustache", "\\rmoustache", "\u23b0", "\u23b1",
508];
509
510// and delimiters that never stack
511const stackNeverDelimiters = [
512 "<", ">", "\\langle", "\\rangle", "/", "\\backslash", "\\lt", "\\gt",
513];
514
515// Metrics of the different sizes. Found by looking at TeX's output of
516// $\bigl| // \Bigl| \biggl| \Biggl| \showlists$
517// Used to create stacked delimiters of appropriate sizes in makeSizedDelim.
518const sizeToMaxHeight = [0, 1.2, 1.8, 2.4, 3.0];
519
520/**
521 * Used to create a delimiter of a specific size, where `size` is 1, 2, 3, or 4.
522 */
523const makeSizedDelim = function(
524 delim: string,
525 size: number,
526 options: Options,
527 mode: Mode,
528 classes: string[],
529): DomSpan {
530 // < and > turn into \langle and \rangle in delimiters
531 if (delim === "<" || delim === "\\lt" || delim === "\u27e8") {
532 delim = "\\langle";
533 } else if (delim === ">" || delim === "\\gt" || delim === "\u27e9") {
534 delim = "\\rangle";
535 }
536
537 // Sized delimiters are never centered.
538 if (utils.contains(stackLargeDelimiters, delim) ||
539 utils.contains(stackNeverDelimiters, delim)) {
540 return makeLargeDelim(delim, size, false, options, mode, classes);
541 } else if (utils.contains(stackAlwaysDelimiters, delim)) {
542 return makeStackedDelim(
543 delim, sizeToMaxHeight[size], false, options, mode, classes);
544 } else {
545 throw new ParseError("Illegal delimiter: '" + delim + "'");
546 }
547};
548
549/**
550 * There are three different sequences of delimiter sizes that the delimiters
551 * follow depending on the kind of delimiter. This is used when creating custom
552 * sized delimiters to decide whether to create a small, large, or stacked
553 * delimiter.
554 *
555 * In real TeX, these sequences aren't explicitly defined, but are instead
556 * defined inside the font metrics. Since there are only three sequences that
557 * are possible for the delimiters that TeX defines, it is easier to just encode
558 * them explicitly here.
559 */
560
561type Delimiter =
562 {type: "small", style: StyleInterface} |
563 {type: "large", size: 1 | 2 | 3 | 4} |
564 {type: "stack"};
565
566// Delimiters that never stack try small delimiters and large delimiters only
567const stackNeverDelimiterSequence = [
568 {type: "small", style: Style.SCRIPTSCRIPT},
569 {type: "small", style: Style.SCRIPT},
570 {type: "small", style: Style.TEXT},
571 {type: "large", size: 1},
572 {type: "large", size: 2},
573 {type: "large", size: 3},
574 {type: "large", size: 4},
575];
576
577// Delimiters that always stack try the small delimiters first, then stack
578const stackAlwaysDelimiterSequence = [
579 {type: "small", style: Style.SCRIPTSCRIPT},
580 {type: "small", style: Style.SCRIPT},
581 {type: "small", style: Style.TEXT},
582 {type: "stack"},
583];
584
585// Delimiters that stack when large try the small and then large delimiters, and
586// stack afterwards
587const stackLargeDelimiterSequence = [
588 {type: "small", style: Style.SCRIPTSCRIPT},
589 {type: "small", style: Style.SCRIPT},
590 {type: "small", style: Style.TEXT},
591 {type: "large", size: 1},
592 {type: "large", size: 2},
593 {type: "large", size: 3},
594 {type: "large", size: 4},
595 {type: "stack"},
596];
597
598/**
599 * Get the font used in a delimiter based on what kind of delimiter it is.
600 * TODO(#963) Use more specific font family return type once that is introduced.
601 */
602const delimTypeToFont = function(type: Delimiter): string {
603 if (type.type === "small") {
604 return "Main-Regular";
605 } else if (type.type === "large") {
606 return "Size" + type.size + "-Regular";
607 } else if (type.type === "stack") {
608 return "Size4-Regular";
609 } else {
610 throw new Error(`Add support for delim type '${type.type}' here.`);
611 }
612};
613
614/**
615 * Traverse a sequence of types of delimiters to decide what kind of delimiter
616 * should be used to create a delimiter of the given height+depth.
617 */
618const traverseSequence = function(
619 delim: string,
620 height: number,
621 sequence: Delimiter[],
622 options: Options,
623): Delimiter {
624 // Here, we choose the index we should start at in the sequences. In smaller
625 // sizes (which correspond to larger numbers in style.size) we start earlier
626 // in the sequence. Thus, scriptscript starts at index 3-3=0, script starts
627 // at index 3-2=1, text starts at 3-1=2, and display starts at min(2,3-0)=2
628 const start = Math.min(2, 3 - options.style.size);
629 for (let i = start; i < sequence.length; i++) {
630 if (sequence[i].type === "stack") {
631 // This is always the last delimiter, so we just break the loop now.
632 break;
633 }
634
635 const metrics = getMetrics(delim, delimTypeToFont(sequence[i]), "math");
636 let heightDepth = metrics.height + metrics.depth;
637
638 // Small delimiters are scaled down versions of the same font, so we
639 // account for the style change size.
640
641 if (sequence[i].type === "small") {
642 const newOptions = options.havingBaseStyle(sequence[i].style);
643 heightDepth *= newOptions.sizeMultiplier;
644 }
645
646 // Check if the delimiter at this size works for the given height.
647 if (heightDepth > height) {
648 return sequence[i];
649 }
650 }
651
652 // If we reached the end of the sequence, return the last sequence element.
653 return sequence[sequence.length - 1];
654};
655
656/**
657 * Make a delimiter of a given height+depth, with optional centering. Here, we
658 * traverse the sequences, and create a delimiter that the sequence tells us to.
659 */
660const makeCustomSizedDelim = function(
661 delim: string,
662 height: number,
663 center: boolean,
664 options: Options,
665 mode: Mode,
666 classes: string[],
667): DomSpan {
668 if (delim === "<" || delim === "\\lt" || delim === "\u27e8") {
669 delim = "\\langle";
670 } else if (delim === ">" || delim === "\\gt" || delim === "\u27e9") {
671 delim = "\\rangle";
672 }
673
674 // Decide what sequence to use
675 let sequence;
676 if (utils.contains(stackNeverDelimiters, delim)) {
677 sequence = stackNeverDelimiterSequence;
678 } else if (utils.contains(stackLargeDelimiters, delim)) {
679 sequence = stackLargeDelimiterSequence;
680 } else {
681 sequence = stackAlwaysDelimiterSequence;
682 }
683
684 // Look through the sequence
685 const delimType = traverseSequence(delim, height, sequence, options);
686
687 // Get the delimiter from font glyphs.
688 // Depending on the sequence element we decided on, call the
689 // appropriate function.
690 if (delimType.type === "small") {
691 return makeSmallDelim(delim, delimType.style, center, options,
692 mode, classes);
693 } else if (delimType.type === "large") {
694 return makeLargeDelim(delim, delimType.size, center, options, mode,
695 classes);
696 } else /* if (delimType.type === "stack") */ {
697 return makeStackedDelim(delim, height, center, options, mode,
698 classes);
699 }
700};
701
702/**
703 * Make a delimiter for use with `\left` and `\right`, given a height and depth
704 * of an expression that the delimiters surround.
705 */
706const makeLeftRightDelim = function(
707 delim: string,
708 height: number,
709 depth: number,
710 options: Options,
711 mode: Mode,
712 classes: string[],
713): DomSpan {
714 // We always center \left/\right delimiters, so the axis is always shifted
715 const axisHeight =
716 options.fontMetrics().axisHeight * options.sizeMultiplier;
717
718 // Taken from TeX source, tex.web, function make_left_right
719 const delimiterFactor = 901;
720 const delimiterExtend = 5.0 / options.fontMetrics().ptPerEm;
721
722 const maxDistFromAxis = Math.max(
723 height - axisHeight, depth + axisHeight);
724
725 const totalHeight = Math.max(
726 // In real TeX, calculations are done using integral values which are
727 // 65536 per pt, or 655360 per em. So, the division here truncates in
728 // TeX but doesn't here, producing different results. If we wanted to
729 // exactly match TeX's calculation, we could do
730 // Math.floor(655360 * maxDistFromAxis / 500) *
731 // delimiterFactor / 655360
732 // (To see the difference, compare
733 // x^{x^{\left(\rule{0.1em}{0.68em}\right)}}
734 // in TeX and KaTeX)
735 maxDistFromAxis / 500 * delimiterFactor,
736 2 * maxDistFromAxis - delimiterExtend);
737
738 // Finally, we defer to `makeCustomSizedDelim` with our calculated total
739 // height
740 return makeCustomSizedDelim(delim, totalHeight, true, options, mode, classes);
741};
742
743export default {
744 sqrtImage: makeSqrtImage,
745 sizedDelim: makeSizedDelim,
746 customSizedDelim: makeCustomSizedDelim,
747 leftRightDelim: makeLeftRightDelim,
748};