From 1e629a0310fe9e6f5f48b56c2ef5b781ca116ac3 Mon Sep 17 00:00:00 2001 From: Ashish Myles Date: Thu, 24 May 2018 23:28:58 -0400 Subject: [PATCH] Export htmlBuilder from accent, horizBrace, and op for supsub. (#1349) * Export htmlBuilder from accent, horizBrace, and op for supsub. * Addressed review comments. --- src/ParseNode.js | 11 ++++++---- src/defineFunction.js | 6 ++++++ src/functions/accent.js | 41 ++++++++++++++++++++++--------------- src/functions/horizBrace.js | 29 ++++++++++++-------------- src/functions/op.js | 22 ++++++++++++++------ 5 files changed, 67 insertions(+), 42 deletions(-) diff --git a/src/ParseNode.js b/src/ParseNode.js index 9e41460c..37d4f173 100644 --- a/src/ParseNode.js +++ b/src/ParseNode.js @@ -290,13 +290,15 @@ type ParseNodeTypes = { * typing. Throws if the node's type does not match. */ export function assertNodeType( - node: ParseNode<*>, + // The union allows either ParseNode<*> or the union of two specific nodes. + node: ?ParseNode<*> | ParseNode<*>, type: NODETYPE, ): ParseNode { const typedNode = checkNodeType(node, type); if (!typedNode) { throw new Error( - `Expected node of type ${type}, but got node of type ${node.type}`); + `Expected node of type ${type}, but got ` + + (node ? `node of type ${node.type}` : String(node))); } return typedNode; } @@ -306,10 +308,11 @@ export function assertNodeType( * returns null. */ export function checkNodeType( - node: ParseNode<*>, + // The union allows either ParseNode<*> or the union of two specific nodes. + node: ?ParseNode<*> | ParseNode<*>, type: NODETYPE, ): ?ParseNode { - return node.type === type ? + return node && node.type === type ? (node: ParseNode) : null; } diff --git a/src/defineFunction.js b/src/defineFunction.js index 58e54b0b..ce2059fa 100644 --- a/src/defineFunction.js +++ b/src/defineFunction.js @@ -32,6 +32,12 @@ export type MathMLBuilder = ( options: Options, ) => MathNode | TextNode | domTree.documentFragment; +// More general version of `HtmlBuilder` for nodes (e.g. \sum, accent types) +// whose presence impacts super/subscripting. In this case, ParseNode<"supsub"> +// delegates its HTML building to the HtmlBuilder corresponding to these nodes. +export type HtmlBuilderSupSub = + (ParseNode<"supsub"> | ParseNode, Options) => HtmlDomNode; + export type FunctionPropSpec = { // The number of arguments the function takes. numArgs: number, diff --git a/src/functions/accent.js b/src/functions/accent.js index d62378ab..95c18759 100644 --- a/src/functions/accent.js +++ b/src/functions/accent.js @@ -4,16 +4,24 @@ import buildCommon from "../buildCommon"; import mathMLTree from "../mathMLTree"; import utils from "../utils"; import stretchy from "../stretchy"; +import {assertNodeType, checkNodeType} from "../ParseNode"; import * as html from "../buildHTML"; import * as mml from "../buildMathML"; -const htmlBuilder = (group, options) => { - // Accents are handled in the TeXbook pg. 443, rule 12. - let base = group.value.base; +import type ParseNode from "../ParseNode"; +import type {HtmlBuilderSupSub, MathMLBuilder} from "../defineFunction"; - let supsubGroup; - if (group.type === "supsub") { +// NOTE: Unlike most `htmlBuilder`s, this one handles not only "accent", but +// also "supsub" since an accent can affect super/subscripting. +export const htmlBuilder: HtmlBuilderSupSub<"accent"> = (grp, options) => { + // Accents are handled in the TeXbook pg. 443, rule 12. + let base: ParseNode<*>; + let group: ParseNode<"accent">; + + const supSub = checkNodeType(grp, "supsub"); + let supSubGroup; + if (supSub) { // If our base is a character box, and we have superscripts and // subscripts, the supsub will defer to us. In particular, we want // to attach the superscripts and subscripts to the inner body (so @@ -22,18 +30,19 @@ const htmlBuilder = (group, options) => { // sticking the base of the accent into the base of the supsub, and // rendering that, while keeping track of where the accent is. - // The supsub group is the group that was passed in - const supsub = group; // The real accent group is the base of the supsub group - group = supsub.value.base; + group = assertNodeType(supSub.value.base, "accent"); // The character box is the base of the accent group base = group.value.base; // Stick the character box into the base of the supsub group - supsub.value.base = base; + supSub.value.base = base; // Rerender the supsub group with its new base, and store that // result. - supsubGroup = html.buildGroup(supsub, options); + supSubGroup = html.buildGroup(supSub, options); + } else { + group = assertNodeType(grp, "accent"); + base = group.value.base; } // Build the base group @@ -153,25 +162,25 @@ const htmlBuilder = (group, options) => { const accentWrap = buildCommon.makeSpan(["mord", "accent"], [accentBody], options); - if (supsubGroup) { + if (supSubGroup) { // Here, we replace the "base" child of the supsub with our newly // generated accent. - supsubGroup.children[0] = accentWrap; + supSubGroup.children[0] = accentWrap; // Since we don't rerun the height calculation after replacing the // accent, we manually recalculate height. - supsubGroup.height = Math.max(accentWrap.height, supsubGroup.height); + supSubGroup.height = Math.max(accentWrap.height, supSubGroup.height); // Accents should always be ords, even when their innards are not. - supsubGroup.classes[0] = "mord"; + supSubGroup.classes[0] = "mord"; - return supsubGroup; + return supSubGroup; } else { return accentWrap; } }; -const mathmlBuilder = (group, options) => { +const mathmlBuilder: MathMLBuilder<"accent"> = (group, options) => { const groupValue = group.value; let accentNode; if (groupValue.isStretchy) { diff --git a/src/functions/horizBrace.js b/src/functions/horizBrace.js index b3e04d3d..48bc0ad6 100644 --- a/src/functions/horizBrace.js +++ b/src/functions/horizBrace.js @@ -10,10 +10,11 @@ import * as html from "../buildHTML"; import * as mml from "../buildMathML"; import type ParseNode from "../ParseNode"; +import type {HtmlBuilderSupSub, MathMLBuilder} from "../defineFunction"; // NOTE: Unlike most `htmlBuilder`s, this one handles not only "horizBrace", but // also "supsub" since an over/underbrace can affect super/subscripting. -function htmlBuilder(grp: ParseNode<*>, options) { +export const htmlBuilder: HtmlBuilderSupSub<"horizBrace"> = (grp, options) => { const style = options.style; // Pull out the `ParseNode<"horizBrace">` if `grp` is a "supsub" node. @@ -29,11 +30,7 @@ function htmlBuilder(grp: ParseNode<*>, options) { supSub.value.sup, options.havingStyle(style.sup()), options) : html.buildGroup( supSub.value.sub, options.havingStyle(style.sub()), options); - // The supsub `base` must be non-null in this context. Otherwise, - // this `htmlBuilder` handler wouldn't have been invoked. - // $FlowFixMe - const base: ParseNode<*> = supSub.value.base; - group = assertNodeType(base, "horizBrace"); + group = assertNodeType(supSub.value.base, "horizBrace"); } else { group = assertNodeType(grp, "horizBrace"); } @@ -112,7 +109,15 @@ function htmlBuilder(grp: ParseNode<*>, options) { return buildCommon.makeSpan( ["mord", (group.value.isOver ? "mover" : "munder")], [vlist], options); -} +}; + +const mathmlBuilder: MathMLBuilder<"horizBrace"> = (group, options) => { + const accentNode = stretchy.mathMLnode(group.value.label); + return new mathMLTree.MathNode( + (group.value.isOver ? "mover" : "munder"), + [mml.buildGroup(group.value.base, options), accentNode] + ); +}; // Horizontal stretchy braces defineFunction({ @@ -130,13 +135,5 @@ defineFunction({ }; }, htmlBuilder, - mathmlBuilder(group, options) { - const accentNode = stretchy.mathMLnode(group.value.label); - return new mathMLTree.MathNode( - (group.value.isOver ? "mover" : "munder"), - [mml.buildGroup(group.value.base, options), accentNode] - ); - }, + mathmlBuilder, }); - - diff --git a/src/functions/op.js b/src/functions/op.js index 326ee2fb..4727c9f9 100644 --- a/src/functions/op.js +++ b/src/functions/op.js @@ -6,23 +6,33 @@ import domTree from "../domTree"; import mathMLTree from "../mathMLTree"; import utils from "../utils"; import Style from "../Style"; +import {assertNodeType, checkNodeType} from "../ParseNode"; import * as html from "../buildHTML"; import * as mml from "../buildMathML"; -const htmlBuilder = (group, options) => { +import type ParseNode from "../ParseNode"; +import type {HtmlBuilderSupSub, MathMLBuilder} from "../defineFunction"; + +// NOTE: Unlike most `htmlBuilder`s, this one handles not only "op", but also +// "supsub" since some of them (like \int) can affect super/subscripting. +export const htmlBuilder: HtmlBuilderSupSub<"op"> = (grp, options) => { // Operators are handled in the TeXbook pg. 443-444, rule 13(a). let supGroup; let subGroup; let hasLimits = false; - if (group.type === "supsub") { + let group: ParseNode<"op">; + const supSub = checkNodeType(grp, "supsub"); + if (supSub) { // If we have limits, supsub will pass us its group to handle. Pull // out the superscript and subscript and set the group to the op in // its base. - supGroup = group.value.sup; - subGroup = group.value.sub; - group = group.value.base; + supGroup = supSub.value.sup; + subGroup = supSub.value.sub; + group = assertNodeType(supSub.value.base, "op"); hasLimits = true; + } else { + group = assertNodeType(grp, "op"); } const style = options.style; @@ -190,7 +200,7 @@ const htmlBuilder = (group, options) => { } }; -const mathmlBuilder = (group, options) => { +const mathmlBuilder: MathMLBuilder<"op"> = (group, options) => { let node; // TODO(emily): handle big operators using the `largeop` attribute