UNPKG

5.54 kBJavaScriptView Raw
1// @flow
2import defineFunction from "../defineFunction";
3import buildCommon from "../buildCommon";
4import mathMLTree from "../mathMLTree";
5import stretchy from "../stretchy";
6
7import * as html from "../buildHTML";
8import * as mml from "../buildMathML";
9
10import type {ParseNode} from "../parseNode";
11
12// Helper function
13const paddedNode = group => {
14 const node = new mathMLTree.MathNode("mpadded", group ? [group] : []);
15 node.setAttribute("width", "+0.6em");
16 node.setAttribute("lspace", "0.3em");
17 return node;
18};
19
20// Stretchy arrows with an optional argument
21defineFunction({
22 type: "xArrow",
23 names: [
24 "\\xleftarrow", "\\xrightarrow", "\\xLeftarrow", "\\xRightarrow",
25 "\\xleftrightarrow", "\\xLeftrightarrow", "\\xhookleftarrow",
26 "\\xhookrightarrow", "\\xmapsto", "\\xrightharpoondown",
27 "\\xrightharpoonup", "\\xleftharpoondown", "\\xleftharpoonup",
28 "\\xrightleftharpoons", "\\xleftrightharpoons", "\\xlongequal",
29 "\\xtwoheadrightarrow", "\\xtwoheadleftarrow", "\\xtofrom",
30 // The next 3 functions are here to support the mhchem extension.
31 // Direct use of these functions is discouraged and may break someday.
32 "\\xrightleftarrows", "\\xrightequilibrium", "\\xleftequilibrium",
33 ],
34 props: {
35 numArgs: 1,
36 numOptionalArgs: 1,
37 },
38 handler({parser, funcName}, args, optArgs) {
39 return {
40 type: "xArrow",
41 mode: parser.mode,
42 label: funcName,
43 body: args[0],
44 below: optArgs[0],
45 };
46 },
47 // Flow is unable to correctly infer the type of `group`, even though it's
48 // unamibiguously determined from the passed-in `type` above.
49 htmlBuilder(group: ParseNode<"xArrow">, options) {
50 const style = options.style;
51
52 // Build the argument groups in the appropriate style.
53 // Ref: amsmath.dtx: \hbox{$\scriptstyle\mkern#3mu{#6}\mkern#4mu$}%
54
55 // Some groups can return document fragments. Handle those by wrapping
56 // them in a span.
57 let newOptions = options.havingStyle(style.sup());
58 const upperGroup = buildCommon.wrapFragment(
59 html.buildGroup(group.body, newOptions, options), options);
60 upperGroup.classes.push("x-arrow-pad");
61
62 let lowerGroup;
63 if (group.below) {
64 // Build the lower group
65 newOptions = options.havingStyle(style.sub());
66 lowerGroup = buildCommon.wrapFragment(
67 html.buildGroup(group.below, newOptions, options), options);
68 lowerGroup.classes.push("x-arrow-pad");
69 }
70
71 const arrowBody = stretchy.svgSpan(group, options);
72
73 // Re shift: Note that stretchy.svgSpan returned arrowBody.depth = 0.
74 // The point we want on the math axis is at 0.5 * arrowBody.height.
75 const arrowShift = -options.fontMetrics().axisHeight +
76 0.5 * arrowBody.height;
77 // 2 mu kern. Ref: amsmath.dtx: #7\if0#2\else\mkern#2mu\fi
78 let upperShift = -options.fontMetrics().axisHeight
79 - 0.5 * arrowBody.height - 0.111; // 0.111 em = 2 mu
80 if (upperGroup.depth > 0.25 || group.label === "\\xleftequilibrium") {
81 upperShift -= upperGroup.depth; // shift up if depth encroaches
82 }
83
84 // Generate the vlist
85 let vlist;
86 if (lowerGroup) {
87 const lowerShift = -options.fontMetrics().axisHeight
88 + lowerGroup.height + 0.5 * arrowBody.height
89 + 0.111;
90 vlist = buildCommon.makeVList({
91 positionType: "individualShift",
92 children: [
93 {type: "elem", elem: upperGroup, shift: upperShift},
94 {type: "elem", elem: arrowBody, shift: arrowShift},
95 {type: "elem", elem: lowerGroup, shift: lowerShift},
96 ],
97 }, options);
98 } else {
99 vlist = buildCommon.makeVList({
100 positionType: "individualShift",
101 children: [
102 {type: "elem", elem: upperGroup, shift: upperShift},
103 {type: "elem", elem: arrowBody, shift: arrowShift},
104 ],
105 }, options);
106 }
107
108 // $FlowFixMe: Replace this with passing "svg-align" into makeVList.
109 vlist.children[0].children[0].children[1].classes.push("svg-align");
110
111 return buildCommon.makeSpan(["mrel", "x-arrow"], [vlist], options);
112 },
113 mathmlBuilder(group, options) {
114 const arrowNode = stretchy.mathMLnode(group.label);
115 let node;
116
117 if (group.body) {
118 const upperNode = paddedNode(mml.buildGroup(group.body, options));
119 if (group.below) {
120 const lowerNode = paddedNode(mml.buildGroup(group.below, options));
121 node = new mathMLTree.MathNode(
122 "munderover", [arrowNode, lowerNode, upperNode]
123 );
124 } else {
125 node = new mathMLTree.MathNode("mover", [arrowNode, upperNode]);
126 }
127 } else if (group.below) {
128 const lowerNode = paddedNode(mml.buildGroup(group.below, options));
129 node = new mathMLTree.MathNode("munder", [arrowNode, lowerNode]);
130 } else {
131 // This should never happen.
132 // Parser.js throws an error if there is no argument.
133 node = paddedNode();
134 node = new mathMLTree.MathNode("mover", [arrowNode, node]);
135 }
136 return node;
137 },
138});