diff --git a/src/buildCommon.js b/src/buildCommon.js index b0cdc192..c3d94b6d 100644 --- a/src/buildCommon.js +++ b/src/buildCommon.js @@ -16,7 +16,7 @@ import type Options from "./Options"; import type ParseNode from "./ParseNode"; import type {CharacterMetrics} from "./fontMetrics"; import type {Mode} from "./types"; -import type {DomChildNode, CombinableDomNode, CssStyle} from "./domTree"; +import type {HtmlDomNode, DomSpan, SvgSpan, CssStyle} from "./domTree"; import type {Measurement} from "./units"; // The following have to be loaded from Main-Italic font, using class mainit @@ -269,9 +269,7 @@ const makeOrd = function( * Combine as many characters as possible in the given array of characters * via their tryCombine method. */ -const tryCombineChars = function( - chars: CombinableDomNode[], -): CombinableDomNode[] { +const tryCombineChars = function(chars: HtmlDomNode[]): HtmlDomNode[] { for (let i = 0; i < chars.length - 1; i++) { if (chars[i].tryCombine(chars[i + 1])) { chars.splice(i + 1, 1); @@ -286,7 +284,7 @@ const tryCombineChars = function( * children. */ const sizeElementFromChildren = function( - elem: domTree.span | domTree.anchor | domTree.documentFragment, + elem: DomSpan | domTree.anchor | domTree.documentFragment, ) { let height = 0; let depth = 0; @@ -319,10 +317,10 @@ const sizeElementFromChildren = function( */ const makeSpan = function( classes?: string[], - children?: DomChildNode[], + children?: HtmlDomNode[], options?: Options, style?: CssStyle, -): domTree.span { +): DomSpan { const span = new domTree.span(classes, children, options, style); sizeElementFromChildren(span); @@ -330,6 +328,15 @@ const makeSpan = function( return span; }; +// SVG one is simpler -- doesn't require height, depth, max-font setting. +// This is also a separate method for typesafety. +const makeSvgSpan = ( + classes?: string[], + children?: domTree.svgNode[], + options?: Options, + style?: CssStyle, +): SvgSpan => new domTree.span(classes, children, options, style); + const makeLineSpan = function( className: string, options: Options, @@ -352,7 +359,7 @@ const makeLineSpan = function( const makeAnchor = function( href: string, classes: string[], - children: DomChildNode[], + children: HtmlDomNode[], options: Options, ) { const anchor = new domTree.anchor(href, classes, children, options); @@ -366,7 +373,7 @@ const makeAnchor = function( * Makes a document fragment with the given list of children. */ const makeFragment = function( - children: DomChildNode[], + children: HtmlDomNode[], ): domTree.documentFragment { const fragment = new domTree.documentFragment(children); @@ -379,7 +386,7 @@ const makeFragment = function( // These are exact object types to catch typos in the names of the optional fields. export type VListElem = {| type: "elem", - elem: DomChildNode, + elem: HtmlDomNode, marginLeft?: string, marginRight?: string, wrapperClasses?: string[], @@ -387,7 +394,7 @@ export type VListElem = {| |}; type VListElemAndShift = {| type: "elem", - elem: DomChildNode, + elem: HtmlDomNode, shift: number, marginLeft?: string, marginRight?: string, @@ -491,7 +498,7 @@ const getVListChildrenAndDepth = function(params: VListParam): { * * See VListParam documentation above. */ -const makeVList = function(params: VListParam, options: Options): domTree.span { +const makeVList = function(params: VListParam, options: Options): DomSpan { const {children, depth} = getVListChildrenAndDepth(params); // Create a strut that is taller than any list item. The strut is added to @@ -595,7 +602,7 @@ const makeVerb = function(group: ParseNode, options: Options): string { // Glue is a concept from TeX which is a flexible space between elements in // either a vertical or horizontal list. In KaTeX, at least for now, it's // static space between elements in a horizontal layout. -const makeGlue = (measurement: Measurement, options: Options): domTree.span => { +const makeGlue = (measurement: Measurement, options: Options): DomSpan => { // Make an empty span for the rule const rule = makeSpan(["mord", "rule"], [], options); const size = calculateSize(measurement, options); @@ -752,7 +759,7 @@ const svgData: { vec: ["vec", 0.471, 0.714], // values from the font glyph }; -const staticSvg = function(value: string, options: Options): domTree.span { +const staticSvg = function(value: string, options: Options): SvgSpan { // Create a span with inline SVG for the element. const [pathName, width, height] = svgData[value]; const path = new domTree.pathNode(pathName); @@ -764,7 +771,7 @@ const staticSvg = function(value: string, options: Options): domTree.span { "viewBox": "0 0 " + 1000 * width + " " + 1000 * height, "preserveAspectRatio": "xMinYMin", }); - const span = makeSpan(["overlay"], [svgNode], options); + const span = makeSvgSpan(["overlay"], [svgNode], options); span.height = height; span.style.height = height + "em"; span.style.width = width + "em"; @@ -776,6 +783,7 @@ export default { makeSymbol, mathsym, makeSpan, + makeSvgSpan, makeLineSpan, makeAnchor, makeFragment, diff --git a/src/buildTree.js b/src/buildTree.js index 5d524d90..c0c34b75 100644 --- a/src/buildTree.js +++ b/src/buildTree.js @@ -7,7 +7,7 @@ import Settings from "./Settings"; import Style from "./Style"; import type ParseNode from "./ParseNode"; -import type domTree from "./domTree"; +import type {DomSpan} from "./domTree"; const optionsFromSettings = function(settings: Settings) { return new Options({ @@ -20,7 +20,7 @@ export const buildTree = function( tree: ParseNode[], expression: string, settings: Settings, -): domTree.span { +): DomSpan { const options = optionsFromSettings(settings); // `buildHTML` sometimes messes with the parse tree (like turning bins -> // ords), so we build the MathML version first. @@ -42,7 +42,7 @@ export const buildHTMLTree = function( tree: ParseNode[], expression: string, settings: Settings, -): domTree.span { +): DomSpan { const options = optionsFromSettings(settings); const htmlNode = buildHTML(tree, options); const katexNode = buildCommon.makeSpan(["katex"], [htmlNode]); diff --git a/src/delimiter.js b/src/delimiter.js index 82cdac5e..f7c88fe4 100644 --- a/src/delimiter.js +++ b/src/delimiter.js @@ -32,7 +32,7 @@ import utils from "./utils"; import type Options from "./Options"; import type {CharacterMetrics} from "./fontMetrics"; -import type {DomChildNode} from "./domTree"; +import type {HtmlDomNode, DomSpan, SvgSpan} from "./domTree"; import type {Mode} from "./types"; import type {StyleInterface} from "./Style"; import type {VListElem} from "./buildCommon"; @@ -60,11 +60,11 @@ const getMetrics = function( * and maxFontSizes. */ const styleWrap = function( - delim: DomChildNode, + delim: HtmlDomNode, toStyle: StyleInterface, options: Options, classes: string[], -): domTree.span { +): DomSpan { const newOptions = options.havingBaseStyle(toStyle); const span = buildCommon.makeSpan( @@ -81,7 +81,7 @@ const styleWrap = function( }; const centerSpan = function( - span: domTree.span, + span: DomSpan, options: Options, style: StyleInterface, ) { @@ -108,7 +108,7 @@ const makeSmallDelim = function( options: Options, mode: Mode, classes: string[], -): domTree.span { +): DomSpan { const text = buildCommon.makeSymbol(delim, "Main-Regular", mode, options); const span = styleWrap(text, style, options, classes); if (center) { @@ -140,7 +140,7 @@ const makeLargeDelim = function(delim, options: Options, mode: Mode, classes: string[], -): domTree.span { +): DomSpan { const inner = mathrmSize(delim, size, mode, options); const span = styleWrap( buildCommon.makeSpan(["delimsizing", "size" + size], [inner], options), @@ -188,7 +188,7 @@ const makeStackedDelim = function( options: Options, mode: Mode, classes: string[], -): domTree.span { +): DomSpan { // There are four parts, the top, an optional middle, a repeated part, and a // bottom. let top; @@ -378,7 +378,7 @@ const sqrtSvg = function( height: number, viewBoxHeight: number, options: Options, -): domTree.span { +): SvgSpan { let alternate; if (sqrtName === "sqrtTall") { // sqrtTall is from glyph U23B7 in the font KaTeX_Size4-Regular @@ -401,7 +401,7 @@ const sqrtSvg = function( "preserveAspectRatio": "xMinYMin slice", }); - return buildCommon.makeSpan(["hide-tail"], [svg], options); + return buildCommon.makeSvgSpan(["hide-tail"], [svg], options); }; /** @@ -411,7 +411,7 @@ const makeSqrtImage = function( height: number, options: Options, ): { - span: domTree.span, + span: SvgSpan, ruleWidth: number, advanceWidth: number, } { @@ -516,7 +516,7 @@ const makeSizedDelim = function( options: Options, mode: Mode, classes: string[], -): domTree.span { +): DomSpan { // < and > turn into \langle and \rangle in delimiters if (delim === "<" || delim === "\\lt" || delim === "\u27e8") { delim = "\\langle"; @@ -654,7 +654,7 @@ const makeCustomSizedDelim = function( options: Options, mode: Mode, classes: string[], -): domTree.span { +): DomSpan { if (delim === "<" || delim === "\\lt" || delim === "\u27e8") { delim = "\\langle"; } else if (delim === ">" || delim === "\\gt" || delim === "\u27e9") { @@ -700,7 +700,7 @@ const makeLeftRightDelim = function( options: Options, mode: Mode, classes: string[], -): domTree.span { +): DomSpan { // We always center \left/\right delimiters, so the axis is always shifted const axisHeight = options.fontMetrics().axisHeight * options.sizeMultiplier; diff --git a/src/domTree.js b/src/domTree.js index e79b9d04..efdd3a07 100644 --- a/src/domTree.js +++ b/src/domTree.js @@ -29,23 +29,31 @@ const createClass = function(classes: string[]): string { }; // To ensure that all nodes have compatible signatures for these methods. -interface VirtualDomNode { +interface VirtualNodeInterface { toNode(): Node; toMarkup(): string; } -export interface CombinableDomNode extends VirtualDomNode { - tryCombine(sibling: CombinableDomNode): boolean; +interface HtmlDomInterface extends VirtualNodeInterface { + classes: string[]; + height: number; + depth: number; + maxFontSize: number; + + tryCombine(sibling: HtmlDomNode): boolean; } /** - * All `DomChildNode`s MUST have `height`, `depth`, and `maxFontSize` numeric - * fields. + * All `HtmlDomNode`s must implement HtmlDomInterface. * - * `DomChildNode` is not defined as an interface since `documentFragment` also - * has these fields but should not be considered a `DomChildNode`. + * `HtmlDomNode` is not defined as an interface since `documentFragment` also + * has these fields but should not be considered a `HtmlDomNode`. */ -export type DomChildNode = span | anchor | svgNode | symbolNode; +export type HtmlDomNode = DomSpan | SvgSpan | anchor | symbolNode; +// Span wrapping other DOM nodes. +export type DomSpan = span; +// Span wrapping an SVG node. +export type SvgSpan = span; export type SvgChildNode = pathNode | lineNode; @@ -55,10 +63,14 @@ export type CssStyle = {[name: string]: string}; * This node represents a span node, with a className, a list of children, and * an inline style. It also contains information about its height, depth, and * maxFontSize. + * + * Represents two types with different uses: SvgSpan to wrap an SVG and DomSpan + * otherwise. This typesafety is important when HTML builders access a span's + * children. */ -class span implements CombinableDomNode { +class span implements HtmlDomInterface { classes: string[]; - children: DomChildNode[]; + children: ChildType[]; height: number; depth: number; width: ?number; @@ -68,7 +80,7 @@ class span implements CombinableDomNode { constructor( classes?: string[], - children?: DomChildNode[], + children?: ChildType[], options?: Options, style?: CssStyle, ) { @@ -99,7 +111,7 @@ class span implements CombinableDomNode { this.attributes[attribute] = value; } - tryCombine(sibling: CombinableDomNode): boolean { + tryCombine(sibling: HtmlDomNode): boolean { return false; } @@ -188,10 +200,10 @@ class span implements CombinableDomNode { * a list of children, and an inline style. It also contains information about its * height, depth, and maxFontSize. */ -class anchor implements CombinableDomNode { +class anchor implements HtmlDomInterface { href: string; classes: string[]; - children: DomChildNode[]; + children: HtmlDomNode[]; height: number; depth: number; maxFontSize: number; @@ -201,7 +213,7 @@ class anchor implements CombinableDomNode { constructor( href: string, classes: string[], - children: DomChildNode[], + children: HtmlDomNode[], options: Options, ) { this.href = href; @@ -230,7 +242,7 @@ class anchor implements CombinableDomNode { this.attributes[attribute] = value; } - tryCombine(sibling: CombinableDomNode): boolean { + tryCombine(sibling: HtmlDomNode): boolean { return false; } @@ -324,13 +336,13 @@ class anchor implements CombinableDomNode { * contains children and doesn't have any HTML properties. It also keeps track * of a height, depth, and maxFontSize. */ -class documentFragment implements VirtualDomNode { - children: DomChildNode[]; +class documentFragment implements VirtualNodeInterface { + children: HtmlDomNode[]; height: number; depth: number; maxFontSize: number; - constructor(children?: DomChildNode[]) { + constructor(children?: HtmlDomNode[]) { this.children = children || []; this.height = 0; this.depth = 0; @@ -380,7 +392,7 @@ const iCombinations = { * to a single text node, or a span with a single text node in it, depending on * whether it has CSS classes, styles, or needs italic correction. */ -class symbolNode implements CombinableDomNode { +class symbolNode implements HtmlDomInterface { value: string; height: number; depth: number; @@ -428,7 +440,7 @@ class symbolNode implements CombinableDomNode { } } - tryCombine(sibling: CombinableDomNode): boolean { + tryCombine(sibling: HtmlDomNode): boolean { if (!sibling || !(sibling instanceof symbolNode) || this.italic > 0 @@ -538,20 +550,13 @@ class symbolNode implements CombinableDomNode { /** * SVG nodes are used to render stretchy wide elements. */ -class svgNode implements VirtualDomNode { +class svgNode implements VirtualNodeInterface { children: SvgChildNode[]; attributes: {[string]: string}; - // Required for all `DomChildNode`s. Are always 0 for svgNode. - height: number; - depth: number; - maxFontSize: number; constructor(children?: SvgChildNode[], attributes?: {[string]: string}) { this.children = children || []; this.attributes = attributes || {}; - this.height = 0; - this.depth = 0; - this.maxFontSize = 0; } toNode(): Node { @@ -594,7 +599,7 @@ class svgNode implements VirtualDomNode { } } -class pathNode implements VirtualDomNode { +class pathNode implements VirtualNodeInterface { pathName: string; alternate: ?string; @@ -625,7 +630,7 @@ class pathNode implements VirtualDomNode { } } -class lineNode implements VirtualDomNode { +class lineNode implements VirtualNodeInterface { attributes: {[string]: string}; constructor(attributes?: {[string]: string}) { diff --git a/src/functions/verb.js b/src/functions/verb.js index 7e225daa..1e8c0f94 100644 --- a/src/functions/verb.js +++ b/src/functions/verb.js @@ -41,9 +41,6 @@ defineFunction({ buildCommon.tryCombineChars(body); return buildCommon.makeSpan( ["mord", "text"].concat(newOptions.sizingClasses(options)), - // tryCombinChars expects CombinableDomNode[] while makeSpan expects - // DomChildNode[]. - // $FlowFixMe: CombinableDomNode[] is not compatible with DomChildNode[] body, newOptions); }, mathmlBuilder(group, options) { diff --git a/src/stretchy.js b/src/stretchy.js index 6ab2e99c..ed469a7f 100644 --- a/src/stretchy.js +++ b/src/stretchy.js @@ -12,6 +12,7 @@ import utils from "./utils"; import type Options from "./Options"; import type ParseNode from "./ParseNode"; +import type {DomSpan, SvgSpan} from "./domTree"; const stretchyCodePoint: {[string]: string} = { widehat: "^", @@ -166,10 +167,10 @@ const groupLength = function(arg: ParseNode): number { } }; -const svgSpan = function(group: ParseNode, options: Options): domTree.span { +const svgSpan = function(group: ParseNode, options: Options): DomSpan | SvgSpan { // Create a span with inline SVG for the element. function buildSvgSpan_(): { - span: domTree.span, + span: DomSpan | SvgSpan, minWidth: number, height: number, } { @@ -211,7 +212,7 @@ const svgSpan = function(group: ParseNode, options: Options): domTree.span { "preserveAspectRatio": "none", }); return { - span: buildCommon.makeSpan([], [svgNode], options), + span: buildCommon.makeSvgSpan([], [svgNode], options), minWidth: 0, height, }; @@ -249,8 +250,8 @@ const svgSpan = function(group: ParseNode, options: Options): domTree.span { "preserveAspectRatio": aligns[i] + " slice", }); - const span = - buildCommon.makeSpan([widthClasses[i]], [svgNode], options); + const span = buildCommon.makeSvgSpan( + [widthClasses[i]], [svgNode], options); if (numSvgChildren === 1) { return {span, minWidth, height}; } else { @@ -280,11 +281,11 @@ const svgSpan = function(group: ParseNode, options: Options): domTree.span { }; const encloseSpan = function( - inner: domTree.span, + inner: DomSpan, label: string, pad: number, options: Options, -): domTree.span { +): DomSpan | SvgSpan { // Return an image span for \cancel, \bcancel, \xcancel, or \fbox let img; const totalHeight = inner.height + inner.depth + 2 * pad; @@ -330,7 +331,7 @@ const encloseSpan = function( "height": totalHeight + "em", }); - img = buildCommon.makeSpan([], [svgNode], options); + img = buildCommon.makeSvgSpan([], [svgNode], options); } img.height = totalHeight; @@ -339,8 +340,11 @@ const encloseSpan = function( return img; }; -const ruleSpan = function(className: string, lineThickness: number, - options: Options): domTree.span { +const ruleSpan = function( + className: string, + lineThickness: number, + options: Options, +): SvgSpan { // Get a span with an SVG path that fills the middle fifth of the span. // We're using an extra wide span so Chrome won't round it down to zero. @@ -378,7 +382,7 @@ const ruleSpan = function(className: string, lineThickness: number, }); } - return buildCommon.makeSpan([parentClass], [svgNode], options); + return buildCommon.makeSvgSpan([parentClass], [svgNode], options); }; export default {