// @flow import defineFunction from "../defineFunction"; import buildCommon from "../buildCommon"; import mathMLTree from "../mathMLTree"; import stretchy from "../stretchy"; import Style from "../Style"; import {assertNodeType} from "../parseNode"; import * as html from "../buildHTML"; import * as mml from "../buildMathML"; import type {HtmlBuilderSupSub, MathMLBuilder} from "../defineFunction"; import type {ParseNode} from "../parseNode"; // NOTE: Unlike most `htmlBuilder`s, this one handles not only "horizBrace", but // also "supsub" since an over/underbrace can affect super/subscripting. export const htmlBuilder: HtmlBuilderSupSub<"horizBrace"> = (grp, options) => { const style = options.style; // Pull out the `ParseNode<"horizBrace">` if `grp` is a "supsub" node. let supSubGroup; let group: ParseNode<"horizBrace">; if (grp.type === "supsub") { // Ref: LaTeX source2e: }}}}\limits} // i.e. LaTeX treats the brace similar to an op and passes it // with \limits, so we need to assign supsub style. supSubGroup = grp.sup ? html.buildGroup(grp.sup, options.havingStyle(style.sup()), options) : html.buildGroup(grp.sub, options.havingStyle(style.sub()), options); group = assertNodeType(grp.base, "horizBrace"); } else { group = assertNodeType(grp, "horizBrace"); } // Build the base group const body = html.buildGroup( group.base, options.havingBaseStyle(Style.DISPLAY)); // Create the stretchy element const braceBody = stretchy.svgSpan(group, options); // Generate the vlist, with the appropriate kerns ┏━━━━━━━━┓ // This first vlist contains the content and the brace: equation let vlist; if (group.isOver) { vlist = buildCommon.makeVList({ positionType: "firstBaseline", children: [ {type: "elem", elem: body}, {type: "kern", size: 0.1}, {type: "elem", elem: braceBody}, ], }, options); // $FlowFixMe: Replace this with passing "svg-align" into makeVList. vlist.children[0].children[0].children[1].classes.push("svg-align"); } else { vlist = buildCommon.makeVList({ positionType: "bottom", positionData: body.depth + 0.1 + braceBody.height, children: [ {type: "elem", elem: braceBody}, {type: "kern", size: 0.1}, {type: "elem", elem: body}, ], }, options); // $FlowFixMe: Replace this with passing "svg-align" into makeVList. vlist.children[0].children[0].children[0].classes.push("svg-align"); } if (supSubGroup) { // To write the supsub, wrap the first vlist in another vlist: // They can't all go in the same vlist, because the note might be // wider than the equation. We want the equation to control the // brace width. // note long note long note // ┏━━━━━━━━┓ or ┏━━━┓ not ┏━━━━━━━━━┓ // equation eqn eqn const vSpan = buildCommon.makeSpan( ["mord", (group.isOver ? "mover" : "munder")], [vlist], options); if (group.isOver) { vlist = buildCommon.makeVList({ positionType: "firstBaseline", children: [ {type: "elem", elem: vSpan}, {type: "kern", size: 0.2}, {type: "elem", elem: supSubGroup}, ], }, options); } else { vlist = buildCommon.makeVList({ positionType: "bottom", positionData: vSpan.depth + 0.2 + supSubGroup.height + supSubGroup.depth, children: [ {type: "elem", elem: supSubGroup}, {type: "kern", size: 0.2}, {type: "elem", elem: vSpan}, ], }, options); } } return buildCommon.makeSpan( ["mord", (group.isOver ? "mover" : "munder")], [vlist], options); }; const mathmlBuilder: MathMLBuilder<"horizBrace"> = (group, options) => { const accentNode = stretchy.mathMLnode(group.label); return new mathMLTree.MathNode( (group.isOver ? "mover" : "munder"), [mml.buildGroup(group.base, options), accentNode] ); }; // Horizontal stretchy braces defineFunction({ type: "horizBrace", names: ["\\overbrace", "\\underbrace"], props: { numArgs: 1, }, handler({parser, funcName}, args) { return { type: "horizBrace", mode: parser.mode, label: funcName, isOver: /^\\over/.test(funcName), base: args[0], }; }, htmlBuilder, mathmlBuilder, });