Port delimiter.js to @flow. (#1177)

* Port delimiter.js to @flow.

* Responded to comments.
This commit is contained in:
Ashish Myles
2018-03-01 22:57:10 -05:00
committed by Kevin Barabash
parent 56cfc7cf86
commit 9e22012619
4 changed files with 139 additions and 38 deletions

View File

@@ -377,7 +377,7 @@ const makeFragment = function(
// These are exact object types to catch typos in the names of the optional fields.
type VListElem = {|
export type VListElem = {|
type: "elem",
elem: DomChildNode,
marginLeft?: string,

View File

@@ -1,3 +1,4 @@
// @flow
/**
* This file deals with creating delimiters of various sizes. The TeXbook
* discusses these routines on page 441-442, in the "Another subroutine sets box
@@ -29,40 +30,61 @@ import fontMetrics from "./fontMetrics";
import symbols from "./symbols";
import utils from "./utils";
import type Options from "./Options";
import type {CharacterMetrics} from "./fontMetrics";
import type {DomChildNode} from "./domTree";
import type {Mode} from "./types";
import type {StyleInterface} from "./Style";
import type {VListElem} from "./buildCommon";
/**
* Get the metrics for a given symbol and font, after transformation (i.e.
* after following replacement from symbols.js)
*/
const getMetrics = function(symbol, font, mode) {
if (symbols.math[symbol] && symbols.math[symbol].replace) {
return fontMetrics.getCharacterMetrics(
symbols.math[symbol].replace, font, mode);
} else {
return fontMetrics.getCharacterMetrics(
symbol, font, mode);
const getMetrics = function(
symbol: string,
font: string,
mode: Mode,
): CharacterMetrics {
const replace = symbols.math[symbol] && symbols.math[symbol].replace;
const metrics =
fontMetrics.getCharacterMetrics(replace || symbol, font, mode);
if (!metrics) {
throw new Error(`Unsupported symbol ${symbol} and font size ${font}.`);
}
return metrics;
};
/**
* Puts a delimiter span in a given style, and adds appropriate height, depth,
* and maxFontSizes.
*/
const styleWrap = function(delim, toStyle, options, classes) {
const styleWrap = function(
delim: DomChildNode,
toStyle: StyleInterface,
options: Options,
classes: string[],
): domTree.span {
const newOptions = options.havingBaseStyle(toStyle);
const span = buildCommon.makeSpan(
(classes || []).concat(newOptions.sizingClasses(options)),
classes.concat(newOptions.sizingClasses(options)),
[delim], options);
span.delimSizeMultiplier = newOptions.sizeMultiplier / options.sizeMultiplier;
span.height *= span.delimSizeMultiplier;
span.depth *= span.delimSizeMultiplier;
const delimSizeMultiplier =
newOptions.sizeMultiplier / options.sizeMultiplier;
span.height *= delimSizeMultiplier;
span.depth *= delimSizeMultiplier;
span.maxFontSize = newOptions.sizeMultiplier;
return span;
};
const centerSpan = function(span, options, style) {
const centerSpan = function(
span: domTree.span,
options: Options,
style: StyleInterface,
) {
const newOptions = options.havingBaseStyle(style);
const shift =
(1 - options.sizeMultiplier / newOptions.sizeMultiplier) *
@@ -79,7 +101,14 @@ const centerSpan = function(span, options, style) {
* font, but is restyled to either be in textstyle, scriptstyle, or
* scriptscriptstyle.
*/
const makeSmallDelim = function(delim, style, center, options, mode, classes) {
const makeSmallDelim = function(
delim: string,
style: StyleInterface,
center: boolean,
options: Options,
mode: Mode,
classes: string[],
): domTree.span {
const text = buildCommon.makeSymbol(delim, "Main-Regular", mode, options);
const span = styleWrap(text, style, options, classes);
if (center) {
@@ -91,7 +120,12 @@ const makeSmallDelim = function(delim, style, center, options, mode, classes) {
/**
* Builds a symbol in the given font size (note size is an integer)
*/
const mathrmSize = function(value, size, mode, options) {
const mathrmSize = function(
value: string,
size: number,
mode: Mode,
options: Options,
): domTree.symbolNode {
return buildCommon.makeSymbol(value, "Size" + size + "-Regular",
mode, options);
};
@@ -100,7 +134,13 @@ const mathrmSize = function(value, size, mode, options) {
* Makes a large delimiter. This is a delimiter that comes in the Size1, Size2,
* Size3, or Size4 fonts. It is always rendered in textstyle.
*/
const makeLargeDelim = function(delim, size, center, options, mode, classes) {
const makeLargeDelim = function(delim,
size: number,
center: boolean,
options: Options,
mode: Mode,
classes: string[],
): domTree.span {
const inner = mathrmSize(delim, size, mode, options);
const span = styleWrap(
buildCommon.makeSpan(["delimsizing", "size" + size], [inner], options),
@@ -115,12 +155,16 @@ const makeLargeDelim = function(delim, size, center, options, mode, classes) {
* 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.
*/
const makeInner = function(symbol, font, mode) {
const makeInner = function(
symbol: string,
font: "Size1-Regular" | "Size4-Regular",
mode: Mode,
): VListElem {
let sizeClass;
// Apply the correct CSS class to choose the right font.
if (font === "Size1-Regular") {
sizeClass = "delim-size1";
} else if (font === "Size4-Regular") {
} else /* if (font === "Size4-Regular") */ {
sizeClass = "delim-size4";
}
@@ -137,8 +181,14 @@ const makeInner = function(symbol, font, mode) {
* 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.
*/
const makeStackedDelim = function(delim, heightTotal, center, options, mode,
classes) {
const makeStackedDelim = function(
delim: string,
heightTotal: number,
center: boolean,
options: Options,
mode: Mode,
classes: string[],
): domTree.span {
// There are four parts, the top, an optional middle, a repeated part, and a
// bottom.
let top;
@@ -323,7 +373,12 @@ const makeStackedDelim = function(delim, heightTotal, center, options, mode,
const vbPad = 80; // padding above the surd, measured inside the viewBox.
const emPad = 0.08; // padding, in ems, measured in the document.
const sqrtSvg = function(sqrtName, height, viewBoxHeight, options) {
const sqrtSvg = function(
sqrtName: string,
height: number,
viewBoxHeight: number,
options: Options,
): domTree.span {
let alternate;
if (sqrtName === "sqrtTall") {
// sqrtTall is from glyph U23B7 in the font KaTeX_Size4-Regular
@@ -352,7 +407,14 @@ const sqrtSvg = function(sqrtName, height, viewBoxHeight, options) {
/**
* Make a sqrt image of the given height,
*/
const makeSqrtImage = function(height, options) {
const makeSqrtImage = function(
height: number,
options: Options,
): {
span: domTree.span,
ruleWidth: number,
advanceWidth: number,
} {
const delim =
traverseSequence("\\surd", height, stackLargeDelimiterSequence, options);
@@ -362,6 +424,7 @@ const makeSqrtImage = function(height, options) {
let spanHeight = 0;
let texHeight = 0;
let viewBoxHeight = 0;
let advanceWidth;
// We create viewBoxes with 80 units of "padding" above each surd.
// Then browser rounding error on the parent span height will not
@@ -378,7 +441,7 @@ const makeSqrtImage = function(height, options) {
texHeight = 1.00 * sizeMultiplier;
span = sqrtSvg("sqrtMain", spanHeight, viewBoxHeight, options);
span.style.minWidth = "0.853em";
span.advanceWidth = 0.833 * sizeMultiplier; // from the font.
advanceWidth = 0.833 * sizeMultiplier; // from the font.
} else if (delim.type === "large") {
// These SVGs come from fonts: KaTeX_Size1, _Size2, etc.
@@ -387,7 +450,7 @@ const makeSqrtImage = function(height, options) {
spanHeight = (sizeToMaxHeight[delim.size] + emPad) / sizeMultiplier;
span = sqrtSvg("sqrtSize" + delim.size, spanHeight, viewBoxHeight, options);
span.style.minWidth = "1.02em";
span.advanceWidth = 1.0 / sizeMultiplier; // from the font
advanceWidth = 1.0 / sizeMultiplier; // from the font.
} else {
// Tall sqrt. In TeX, this would be stacked using multiple glyphs.
@@ -397,7 +460,7 @@ const makeSqrtImage = function(height, options) {
viewBoxHeight = Math.floor(1000 * height) + vbPad;
span = sqrtSvg("sqrtTall", spanHeight, viewBoxHeight, options);
span.style.minWidth = "0.742em";
span.advanceWidth = 1.056 / sizeMultiplier;
advanceWidth = 1.056 / sizeMultiplier;
}
span.height = texHeight;
@@ -405,6 +468,7 @@ const makeSqrtImage = function(height, options) {
return {
span,
advanceWidth,
// Calculate the actual line width.
// This actually should depend on the chosen font -- e.g. \boldmath
// should use the thicker surd symbols from e.g. KaTeX_Main-Bold, and
@@ -444,7 +508,13 @@ const 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.
*/
const makeSizedDelim = function(delim, size, options, mode, classes) {
const makeSizedDelim = function(
delim: string,
size: number,
options: Options,
mode: Mode,
classes: string[],
): domTree.span {
// < and > turn into \langle and \rangle in delimiters
if (delim === "<" || delim === "\\lt" || delim === "\u27e8") {
delim = "\\langle";
@@ -476,6 +546,11 @@ const makeSizedDelim = function(delim, size, options, mode, classes) {
* them explicitly here.
*/
type Delimiter =
{type: "small", style: StyleInterface} |
{type: "large", size: 1 | 2 | 3 | 4} |
{type: "stack"};
// Delimiters that never stack try small delimiters and large delimiters only
const stackNeverDelimiterSequence = [
{type: "small", style: Style.SCRIPTSCRIPT},
@@ -510,14 +585,17 @@ const stackLargeDelimiterSequence = [
/**
* Get the font used in a delimiter based on what kind of delimiter it is.
* TODO(#963) Use more specific font family return type once that is introduced.
*/
const delimTypeToFont = function(type) {
const delimTypeToFont = function(type: Delimiter): string {
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";
} else {
throw new Error(`Add support for delim type '${type.type}' here.`);
}
};
@@ -525,7 +603,12 @@ const delimTypeToFont = function(type) {
* 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.
*/
const traverseSequence = function(delim, height, sequence, options) {
const traverseSequence = function(
delim: string,
height: number,
sequence: Delimiter[],
options: Options,
): Delimiter {
// 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
@@ -562,8 +645,14 @@ const traverseSequence = function(delim, height, sequence, options) {
* 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.
*/
const makeCustomSizedDelim = function(delim, height, center, options, mode,
classes) {
const makeCustomSizedDelim = function(
delim: string,
height: number,
center: boolean,
options: Options,
mode: Mode,
classes: string[],
): domTree.span {
if (delim === "<" || delim === "\\lt" || delim === "\u27e8") {
delim = "\\langle";
} else if (delim === ">" || delim === "\\gt" || delim === "\u27e9") {
@@ -602,8 +691,14 @@ const makeCustomSizedDelim = function(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.
*/
const makeLeftRightDelim = function(delim, height, depth, options, mode,
classes) {
const makeLeftRightDelim = function(
delim: string,
height: number,
depth: number,
options: Options,
mode: Mode,
classes: string[],
): domTree.span {
// We always center \left/\right delimiters, so the axis is always shifted
const axisHeight =
options.fontMetrics().axisHeight * options.sizeMultiplier;
@@ -630,8 +725,7 @@ const makeLeftRightDelim = function(delim, height, depth, options, mode,
// Finally, we defer to `makeCustomSizedDelim` with our calculated total
// height
return makeCustomSizedDelim(delim, totalHeight, true, options, mode,
classes);
return makeCustomSizedDelim(delim, totalHeight, true, options, mode, classes);
};
export default {

View File

@@ -275,7 +275,14 @@ defineFunction({
middleDelim = delimiter.sizedDelim(
group.value.value, 1, options,
group.mode, []);
middleDelim.isMiddle = {value: group.value.value, options: options};
// Property `isMiddle` not defined on `span`. It is only used in
// this file above. Fixing this correctly requires refactoring the
// htmlBuilder return type to support passing additional data.
// An easier, but unideal option would be to add `isMiddle` to
// `span` just for this case.
// $FlowFixMe
middleDelim.isMiddle = {value: group.value.value, options};
}
return middleDelim;
},

View File

@@ -58,7 +58,7 @@ defineFunction({
lineClearance + theta) * options.sizeMultiplier;
// Create a sqrt SVG of the required minimum size
const {span: img, ruleWidth} =
const {span: img, ruleWidth, advanceWidth} =
delimiter.sqrtImage(minDelimiterHeight, options);
const delimDepth = img.height - ruleWidth;
@@ -72,7 +72,7 @@ defineFunction({
// Shift the sqrt image
const imgShift = img.height - inner.height - lineClearance - ruleWidth;
inner.style.paddingLeft = img.advanceWidth + "em";
inner.style.paddingLeft = advanceWidth + "em";
// Overlay the image and the argument.
const body = buildCommon.makeVList({