UNPKG

5.05 kBJavaScriptView Raw
1// @flow
2import defineFunction from "../defineFunction";
3import buildCommon from "../buildCommon";
4import mathMLTree from "../mathMLTree";
5import stretchy from "../stretchy";
6import Style from "../Style";
7import {assertNodeType, checkNodeType} from "../parseNode";
8
9import * as html from "../buildHTML";
10import * as mml from "../buildMathML";
11
12import type {HtmlBuilderSupSub, MathMLBuilder} from "../defineFunction";
13import type {ParseNode} from "../parseNode";
14
15// NOTE: Unlike most `htmlBuilder`s, this one handles not only "horizBrace", but
16// also "supsub" since an over/underbrace can affect super/subscripting.
17export const htmlBuilder: HtmlBuilderSupSub<"horizBrace"> = (grp, options) => {
18 const style = options.style;
19
20 // Pull out the `ParseNode<"horizBrace">` if `grp` is a "supsub" node.
21 let supSubGroup;
22 let group: ParseNode<"horizBrace">;
23 const supSub = checkNodeType(grp, "supsub");
24 if (supSub) {
25 // Ref: LaTeX source2e: }}}}\limits}
26 // i.e. LaTeX treats the brace similar to an op and passes it
27 // with \limits, so we need to assign supsub style.
28 supSubGroup = supSub.sup ?
29 html.buildGroup(supSub.sup, options.havingStyle(style.sup()), options) :
30 html.buildGroup(supSub.sub, options.havingStyle(style.sub()), options);
31 group = assertNodeType(supSub.base, "horizBrace");
32 } else {
33 group = assertNodeType(grp, "horizBrace");
34 }
35
36 // Build the base group
37 const body = html.buildGroup(
38 group.base, options.havingBaseStyle(Style.DISPLAY));
39
40 // Create the stretchy element
41 const braceBody = stretchy.svgSpan(group, options);
42
43 // Generate the vlist, with the appropriate kerns ┏━━━━━━━━┓
44 // This first vlist contains the content and the brace: equation
45 let vlist;
46 if (group.isOver) {
47 vlist = buildCommon.makeVList({
48 positionType: "firstBaseline",
49 children: [
50 {type: "elem", elem: body},
51 {type: "kern", size: 0.1},
52 {type: "elem", elem: braceBody},
53 ],
54 }, options);
55 // $FlowFixMe: Replace this with passing "svg-align" into makeVList.
56 vlist.children[0].children[0].children[1].classes.push("svg-align");
57 } else {
58 vlist = buildCommon.makeVList({
59 positionType: "bottom",
60 positionData: body.depth + 0.1 + braceBody.height,
61 children: [
62 {type: "elem", elem: braceBody},
63 {type: "kern", size: 0.1},
64 {type: "elem", elem: body},
65 ],
66 }, options);
67 // $FlowFixMe: Replace this with passing "svg-align" into makeVList.
68 vlist.children[0].children[0].children[0].classes.push("svg-align");
69 }
70
71 if (supSubGroup) {
72 // To write the supsub, wrap the first vlist in another vlist:
73 // They can't all go in the same vlist, because the note might be
74 // wider than the equation. We want the equation to control the
75 // brace width.
76
77 // note long note long note
78 // ┏━━━━━━━━┓ or ┏━━━┓ not ┏━━━━━━━━━┓
79 // equation eqn eqn
80
81 const vSpan = buildCommon.makeSpan(
82 ["mord", (group.isOver ? "mover" : "munder")],
83 [vlist], options);
84
85 if (group.isOver) {
86 vlist = buildCommon.makeVList({
87 positionType: "firstBaseline",
88 children: [
89 {type: "elem", elem: vSpan},
90 {type: "kern", size: 0.2},
91 {type: "elem", elem: supSubGroup},
92 ],
93 }, options);
94 } else {
95 vlist = buildCommon.makeVList({
96 positionType: "bottom",
97 positionData: vSpan.depth + 0.2 + supSubGroup.height +
98 supSubGroup.depth,
99 children: [
100 {type: "elem", elem: supSubGroup},
101 {type: "kern", size: 0.2},
102 {type: "elem", elem: vSpan},
103 ],
104 }, options);
105 }
106 }
107
108 return buildCommon.makeSpan(
109 ["mord", (group.isOver ? "mover" : "munder")], [vlist], options);
110};
111
112const mathmlBuilder: MathMLBuilder<"horizBrace"> = (group, options) => {
113 const accentNode = stretchy.mathMLnode(group.label);
114 return new mathMLTree.MathNode(
115 (group.isOver ? "mover" : "munder"),
116 [mml.buildGroup(group.base, options), accentNode]
117 );
118};
119
120// Horizontal stretchy braces
121defineFunction({
122 type: "horizBrace",
123 names: ["\\overbrace", "\\underbrace"],
124 props: {
125 numArgs: 1,
126 },
127 handler({parser, funcName}, args) {
128 return {
129 type: "horizBrace",
130 mode: parser.mode,
131 label: funcName,
132 isOver: /^\\over/.test(funcName),
133 base: args[0],
134 };
135 },
136 htmlBuilder,
137 mathmlBuilder,
138});