From 12896bd23eeba79884245c1c29fdc568cf21a7fa Mon Sep 17 00:00:00 2001 From: Christopher Speller Date: Mon, 14 Mar 2016 08:50:46 -0400 Subject: Converting to Webpack. Stage 1. --- webapp/non_npm_dependencies/katex/src/delimiter.js | 542 +++++++++++++++++++++ 1 file changed, 542 insertions(+) create mode 100644 webapp/non_npm_dependencies/katex/src/delimiter.js (limited to 'webapp/non_npm_dependencies/katex/src/delimiter.js') diff --git a/webapp/non_npm_dependencies/katex/src/delimiter.js b/webapp/non_npm_dependencies/katex/src/delimiter.js new file mode 100644 index 000000000..168319d41 --- /dev/null +++ b/webapp/non_npm_dependencies/katex/src/delimiter.js @@ -0,0 +1,542 @@ +/** + * This file deals with creating delimiters of various sizes. The TeXbook + * discusses these routines on page 441-442, in the "Another subroutine sets box + * x to a specified variable delimiter" paragraph. + * + * There are three main routines here. `makeSmallDelim` makes a delimiter in the + * normal font, but in either text, script, or scriptscript style. + * `makeLargeDelim` makes a delimiter in textstyle, but in one of the Size1, + * Size2, Size3, or Size4 fonts. `makeStackedDelim` makes a delimiter out of + * smaller pieces that are stacked on top of one another. + * + * The functions take a parameter `center`, which determines if the delimiter + * should be centered around the axis. + * + * Then, there are three exposed functions. `sizedDelim` makes a delimiter in + * one of the given sizes. This is used for things like `\bigl`. + * `customSizedDelim` makes a delimiter with a given total height+depth. It is + * called in places like `\sqrt`. `leftRightDelim` makes an appropriate + * delimiter which surrounds an expression of a given height an depth. It is + * used in `\left` and `\right`. + */ + +var ParseError = require("./ParseError"); +var Style = require("./Style"); + +var buildCommon = require("./buildCommon"); +var fontMetrics = require("./fontMetrics"); +var symbols = require("./symbols"); +var utils = require("./utils"); + +var makeSpan = buildCommon.makeSpan; + +/** + * Get the metrics for a given symbol and font, after transformation (i.e. + * after following replacement from symbols.js) + */ +var getMetrics = function(symbol, font) { + if (symbols.math[symbol] && symbols.math[symbol].replace) { + return fontMetrics.getCharacterMetrics( + symbols.math[symbol].replace, font); + } else { + return fontMetrics.getCharacterMetrics( + symbol, font); + } +}; + +/** + * Builds a symbol in the given font size (note size is an integer) + */ +var mathrmSize = function(value, size, mode) { + return buildCommon.makeSymbol(value, "Size" + size + "-Regular", mode); +}; + +/** + * Puts a delimiter span in a given style, and adds appropriate height, depth, + * and maxFontSizes. + */ +var styleWrap = function(delim, toStyle, options) { + var span = makeSpan( + ["style-wrap", options.style.reset(), toStyle.cls()], [delim]); + + var multiplier = toStyle.sizeMultiplier / options.style.sizeMultiplier; + + span.height *= multiplier; + span.depth *= multiplier; + span.maxFontSize = toStyle.sizeMultiplier; + + return span; +}; + +/** + * Makes a small delimiter. This is a delimiter that comes in the Main-Regular + * font, but is restyled to either be in textstyle, scriptstyle, or + * scriptscriptstyle. + */ +var makeSmallDelim = function(delim, style, center, options, mode) { + var text = buildCommon.makeSymbol(delim, "Main-Regular", mode); + + var span = styleWrap(text, style, options); + + if (center) { + var shift = + (1 - options.style.sizeMultiplier / style.sizeMultiplier) * + fontMetrics.metrics.axisHeight; + + span.style.top = shift + "em"; + span.height -= shift; + span.depth += shift; + } + + return span; +}; + +/** + * Makes a large delimiter. This is a delimiter that comes in the Size1, Size2, + * Size3, or Size4 fonts. It is always rendered in textstyle. + */ +var makeLargeDelim = function(delim, size, center, options, mode) { + var inner = mathrmSize(delim, size, mode); + + var span = styleWrap( + makeSpan(["delimsizing", "size" + size], + [inner], options.getColor()), + Style.TEXT, options); + + if (center) { + var shift = (1 - options.style.sizeMultiplier) * + fontMetrics.metrics.axisHeight; + + span.style.top = shift + "em"; + span.height -= shift; + span.depth += shift; + } + + return span; +}; + +/** + * Make an inner span with the given offset and in the given font. This is used + * in `makeStackedDelim` to make the stacking pieces for the delimiter. + */ +var makeInner = function(symbol, font, mode) { + var sizeClass; + // Apply the correct CSS class to choose the right font. + if (font === "Size1-Regular") { + sizeClass = "delim-size1"; + } else if (font === "Size4-Regular") { + sizeClass = "delim-size4"; + } + + var inner = makeSpan( + ["delimsizinginner", sizeClass], + [makeSpan([], [buildCommon.makeSymbol(symbol, font, mode)])]); + + // Since this will be passed into `makeVList` in the end, wrap the element + // in the appropriate tag that VList uses. + return {type: "elem", elem: inner}; +}; + +/** + * Make a stacked delimiter out of a given delimiter, with the total height at + * least `heightTotal`. This routine is mentioned on page 442 of the TeXbook. + */ +var makeStackedDelim = function(delim, heightTotal, center, options, mode) { + // There are four parts, the top, an optional middle, a repeated part, and a + // bottom. + var top; + var middle; + var repeat; + var bottom; + top = repeat = bottom = delim; + middle = null; + // Also keep track of what font the delimiters are in + var font = "Size1-Regular"; + + // We set the parts and font based on the symbol. Note that we use + // '\u23d0' instead of '|' and '\u2016' instead of '\\|' for the + // repeats of the arrows + if (delim === "\\uparrow") { + repeat = bottom = "\u23d0"; + } else if (delim === "\\Uparrow") { + repeat = bottom = "\u2016"; + } else if (delim === "\\downarrow") { + top = repeat = "\u23d0"; + } else if (delim === "\\Downarrow") { + top = repeat = "\u2016"; + } else if (delim === "\\updownarrow") { + top = "\\uparrow"; + repeat = "\u23d0"; + bottom = "\\downarrow"; + } else if (delim === "\\Updownarrow") { + top = "\\Uparrow"; + repeat = "\u2016"; + bottom = "\\Downarrow"; + } else if (delim === "[" || delim === "\\lbrack") { + top = "\u23a1"; + repeat = "\u23a2"; + bottom = "\u23a3"; + font = "Size4-Regular"; + } else if (delim === "]" || delim === "\\rbrack") { + top = "\u23a4"; + repeat = "\u23a5"; + bottom = "\u23a6"; + font = "Size4-Regular"; + } else if (delim === "\\lfloor") { + repeat = top = "\u23a2"; + bottom = "\u23a3"; + font = "Size4-Regular"; + } else if (delim === "\\lceil") { + top = "\u23a1"; + repeat = bottom = "\u23a2"; + font = "Size4-Regular"; + } else if (delim === "\\rfloor") { + repeat = top = "\u23a5"; + bottom = "\u23a6"; + font = "Size4-Regular"; + } else if (delim === "\\rceil") { + top = "\u23a4"; + repeat = bottom = "\u23a5"; + font = "Size4-Regular"; + } else if (delim === "(") { + top = "\u239b"; + repeat = "\u239c"; + bottom = "\u239d"; + font = "Size4-Regular"; + } else if (delim === ")") { + top = "\u239e"; + repeat = "\u239f"; + bottom = "\u23a0"; + font = "Size4-Regular"; + } else if (delim === "\\{" || delim === "\\lbrace") { + top = "\u23a7"; + middle = "\u23a8"; + bottom = "\u23a9"; + repeat = "\u23aa"; + font = "Size4-Regular"; + } else if (delim === "\\}" || delim === "\\rbrace") { + top = "\u23ab"; + middle = "\u23ac"; + bottom = "\u23ad"; + repeat = "\u23aa"; + font = "Size4-Regular"; + } else if (delim === "\\lgroup") { + top = "\u23a7"; + bottom = "\u23a9"; + repeat = "\u23aa"; + font = "Size4-Regular"; + } else if (delim === "\\rgroup") { + top = "\u23ab"; + bottom = "\u23ad"; + repeat = "\u23aa"; + font = "Size4-Regular"; + } else if (delim === "\\lmoustache") { + top = "\u23a7"; + bottom = "\u23ad"; + repeat = "\u23aa"; + font = "Size4-Regular"; + } else if (delim === "\\rmoustache") { + top = "\u23ab"; + bottom = "\u23a9"; + repeat = "\u23aa"; + font = "Size4-Regular"; + } else if (delim === "\\surd") { + top = "\ue001"; + bottom = "\u23b7"; + repeat = "\ue000"; + font = "Size4-Regular"; + } + + // Get the metrics of the four sections + var topMetrics = getMetrics(top, font); + var topHeightTotal = topMetrics.height + topMetrics.depth; + var repeatMetrics = getMetrics(repeat, font); + var repeatHeightTotal = repeatMetrics.height + repeatMetrics.depth; + var bottomMetrics = getMetrics(bottom, font); + var bottomHeightTotal = bottomMetrics.height + bottomMetrics.depth; + var middleHeightTotal = 0; + var middleFactor = 1; + if (middle !== null) { + var middleMetrics = getMetrics(middle, font); + middleHeightTotal = middleMetrics.height + middleMetrics.depth; + middleFactor = 2; // repeat symmetrically above and below middle + } + + // Calcuate the minimal height that the delimiter can have. + // It is at least the size of the top, bottom, and optional middle combined. + var minHeight = topHeightTotal + bottomHeightTotal + middleHeightTotal; + + // Compute the number of copies of the repeat symbol we will need + var repeatCount = Math.ceil( + (heightTotal - minHeight) / (middleFactor * repeatHeightTotal)); + + // Compute the total height of the delimiter including all the symbols + var realHeightTotal = + minHeight + repeatCount * middleFactor * repeatHeightTotal; + + // The center of the delimiter is placed at the center of the axis. Note + // that in this context, "center" means that the delimiter should be + // centered around the axis in the current style, while normally it is + // centered around the axis in textstyle. + var axisHeight = fontMetrics.metrics.axisHeight; + if (center) { + axisHeight *= options.style.sizeMultiplier; + } + // Calculate the depth + var depth = realHeightTotal / 2 - axisHeight; + + // Now, we start building the pieces that will go into the vlist + + // Keep a list of the inner pieces + var inners = []; + + // Add the bottom symbol + inners.push(makeInner(bottom, font, mode)); + + var i; + if (middle === null) { + // Add that many symbols + for (i = 0; i < repeatCount; i++) { + inners.push(makeInner(repeat, font, mode)); + } + } else { + // When there is a middle bit, we need the middle part and two repeated + // sections + for (i = 0; i < repeatCount; i++) { + inners.push(makeInner(repeat, font, mode)); + } + inners.push(makeInner(middle, font, mode)); + for (i = 0; i < repeatCount; i++) { + inners.push(makeInner(repeat, font, mode)); + } + } + + // Add the top symbol + inners.push(makeInner(top, font, mode)); + + // Finally, build the vlist + var inner = buildCommon.makeVList(inners, "bottom", depth, options); + + return styleWrap( + makeSpan(["delimsizing", "mult"], [inner], options.getColor()), + Style.TEXT, options); +}; + +// There are three kinds of delimiters, delimiters that stack when they become +// too large +var stackLargeDelimiters = [ + "(", ")", "[", "\\lbrack", "]", "\\rbrack", + "\\{", "\\lbrace", "\\}", "\\rbrace", + "\\lfloor", "\\rfloor", "\\lceil", "\\rceil", + "\\surd", +]; + +// delimiters that always stack +var stackAlwaysDelimiters = [ + "\\uparrow", "\\downarrow", "\\updownarrow", + "\\Uparrow", "\\Downarrow", "\\Updownarrow", + "|", "\\|", "\\vert", "\\Vert", + "\\lvert", "\\rvert", "\\lVert", "\\rVert", + "\\lgroup", "\\rgroup", "\\lmoustache", "\\rmoustache", +]; + +// and delimiters that never stack +var stackNeverDelimiters = [ + "<", ">", "\\langle", "\\rangle", "/", "\\backslash", "\\lt", "\\gt", +]; + +// Metrics of the different sizes. Found by looking at TeX's output of +// $\bigl| // \Bigl| \biggl| \Biggl| \showlists$ +// Used to create stacked delimiters of appropriate sizes in makeSizedDelim. +var sizeToMaxHeight = [0, 1.2, 1.8, 2.4, 3.0]; + +/** + * Used to create a delimiter of a specific size, where `size` is 1, 2, 3, or 4. + */ +var makeSizedDelim = function(delim, size, options, mode) { + // < and > turn into \langle and \rangle in delimiters + if (delim === "<" || delim === "\\lt") { + delim = "\\langle"; + } else if (delim === ">" || delim === "\\gt") { + delim = "\\rangle"; + } + + // Sized delimiters are never centered. + if (utils.contains(stackLargeDelimiters, delim) || + utils.contains(stackNeverDelimiters, delim)) { + return makeLargeDelim(delim, size, false, options, mode); + } else if (utils.contains(stackAlwaysDelimiters, delim)) { + return makeStackedDelim( + delim, sizeToMaxHeight[size], false, options, mode); + } else { + throw new ParseError("Illegal delimiter: '" + delim + "'"); + } +}; + +/** + * There are three different sequences of delimiter sizes that the delimiters + * follow depending on the kind of delimiter. This is used when creating custom + * sized delimiters to decide whether to create a small, large, or stacked + * delimiter. + * + * In real TeX, these sequences aren't explicitly defined, but are instead + * defined inside the font metrics. Since there are only three sequences that + * are possible for the delimiters that TeX defines, it is easier to just encode + * them explicitly here. + */ + +// Delimiters that never stack try small delimiters and large delimiters only +var stackNeverDelimiterSequence = [ + {type: "small", style: Style.SCRIPTSCRIPT}, + {type: "small", style: Style.SCRIPT}, + {type: "small", style: Style.TEXT}, + {type: "large", size: 1}, + {type: "large", size: 2}, + {type: "large", size: 3}, + {type: "large", size: 4}, +]; + +// Delimiters that always stack try the small delimiters first, then stack +var stackAlwaysDelimiterSequence = [ + {type: "small", style: Style.SCRIPTSCRIPT}, + {type: "small", style: Style.SCRIPT}, + {type: "small", style: Style.TEXT}, + {type: "stack"}, +]; + +// Delimiters that stack when large try the small and then large delimiters, and +// stack afterwards +var stackLargeDelimiterSequence = [ + {type: "small", style: Style.SCRIPTSCRIPT}, + {type: "small", style: Style.SCRIPT}, + {type: "small", style: Style.TEXT}, + {type: "large", size: 1}, + {type: "large", size: 2}, + {type: "large", size: 3}, + {type: "large", size: 4}, + {type: "stack"}, +]; + +/** + * Get the font used in a delimiter based on what kind of delimiter it is. + */ +var delimTypeToFont = function(type) { + if (type.type === "small") { + return "Main-Regular"; + } else if (type.type === "large") { + return "Size" + type.size + "-Regular"; + } else if (type.type === "stack") { + return "Size4-Regular"; + } +}; + +/** + * Traverse a sequence of types of delimiters to decide what kind of delimiter + * should be used to create a delimiter of the given height+depth. + */ +var traverseSequence = function(delim, height, sequence, options) { + // Here, we choose the index we should start at in the sequences. In smaller + // sizes (which correspond to larger numbers in style.size) we start earlier + // in the sequence. Thus, scriptscript starts at index 3-3=0, script starts + // at index 3-2=1, text starts at 3-1=2, and display starts at min(2,3-0)=2 + var start = Math.min(2, 3 - options.style.size); + for (var i = start; i < sequence.length; i++) { + if (sequence[i].type === "stack") { + // This is always the last delimiter, so we just break the loop now. + break; + } + + var metrics = getMetrics(delim, delimTypeToFont(sequence[i])); + var heightDepth = metrics.height + metrics.depth; + + // Small delimiters are scaled down versions of the same font, so we + // account for the style change size. + + if (sequence[i].type === "small") { + heightDepth *= sequence[i].style.sizeMultiplier; + } + + // Check if the delimiter at this size works for the given height. + if (heightDepth > height) { + return sequence[i]; + } + } + + // If we reached the end of the sequence, return the last sequence element. + return sequence[sequence.length - 1]; +}; + +/** + * Make a delimiter of a given height+depth, with optional centering. Here, we + * traverse the sequences, and create a delimiter that the sequence tells us to. + */ +var makeCustomSizedDelim = function(delim, height, center, options, mode) { + if (delim === "<" || delim === "\\lt") { + delim = "\\langle"; + } else if (delim === ">" || delim === "\\gt") { + delim = "\\rangle"; + } + + // Decide what sequence to use + var sequence; + if (utils.contains(stackNeverDelimiters, delim)) { + sequence = stackNeverDelimiterSequence; + } else if (utils.contains(stackLargeDelimiters, delim)) { + sequence = stackLargeDelimiterSequence; + } else { + sequence = stackAlwaysDelimiterSequence; + } + + // Look through the sequence + var delimType = traverseSequence(delim, height, sequence, options); + + // Depending on the sequence element we decided on, call the appropriate + // function. + if (delimType.type === "small") { + return makeSmallDelim(delim, delimType.style, center, options, mode); + } else if (delimType.type === "large") { + return makeLargeDelim(delim, delimType.size, center, options, mode); + } else if (delimType.type === "stack") { + return makeStackedDelim(delim, height, center, options, mode); + } +}; + +/** + * Make a delimiter for use with `\left` and `\right`, given a height and depth + * of an expression that the delimiters surround. + */ +var makeLeftRightDelim = function(delim, height, depth, options, mode) { + // We always center \left/\right delimiters, so the axis is always shifted + var axisHeight = + fontMetrics.metrics.axisHeight * options.style.sizeMultiplier; + + // Taken from TeX source, tex.web, function make_left_right + var delimiterFactor = 901; + var delimiterExtend = 5.0 / fontMetrics.metrics.ptPerEm; + + var maxDistFromAxis = Math.max( + height - axisHeight, depth + axisHeight); + + var totalHeight = Math.max( + // In real TeX, calculations are done using integral values which are + // 65536 per pt, or 655360 per em. So, the division here truncates in + // TeX but doesn't here, producing different results. If we wanted to + // exactly match TeX's calculation, we could do + // Math.floor(655360 * maxDistFromAxis / 500) * + // delimiterFactor / 655360 + // (To see the difference, compare + // x^{x^{\left(\rule{0.1em}{0.68em}\right)}} + // in TeX and KaTeX) + maxDistFromAxis / 500 * delimiterFactor, + 2 * maxDistFromAxis - delimiterExtend); + + // Finally, we defer to `makeCustomSizedDelim` with our calculated total + // height + return makeCustomSizedDelim(delim, totalHeight, true, options, mode); +}; + +module.exports = { + sizedDelim: makeSizedDelim, + customSizedDelim: makeCustomSizedDelim, + leftRightDelim: makeLeftRightDelim, +}; -- cgit v1.2.3-1-g7c22