You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
145 lines
5.8 KiB
JavaScript
145 lines
5.8 KiB
JavaScript
// @flow
|
|
import defineFunction from "../defineFunction";
|
|
import buildCommon from "../buildCommon";
|
|
import mathMLTree from "../mathMLTree";
|
|
import stretchy from "../stretchy";
|
|
|
|
import * as html from "../buildHTML";
|
|
import * as mml from "../buildMathML";
|
|
|
|
import type {ParseNode} from "../parseNode";
|
|
|
|
// Helper function
|
|
const paddedNode = group => {
|
|
const node = new mathMLTree.MathNode("mpadded", group ? [group] : []);
|
|
node.setAttribute("width", "+0.6em");
|
|
node.setAttribute("lspace", "0.3em");
|
|
return node;
|
|
};
|
|
|
|
// Stretchy arrows with an optional argument
|
|
defineFunction({
|
|
type: "xArrow",
|
|
names: [
|
|
"\\xleftarrow", "\\xrightarrow", "\\xLeftarrow", "\\xRightarrow",
|
|
"\\xleftrightarrow", "\\xLeftrightarrow", "\\xhookleftarrow",
|
|
"\\xhookrightarrow", "\\xmapsto", "\\xrightharpoondown",
|
|
"\\xrightharpoonup", "\\xleftharpoondown", "\\xleftharpoonup",
|
|
"\\xrightleftharpoons", "\\xleftrightharpoons", "\\xlongequal",
|
|
"\\xtwoheadrightarrow", "\\xtwoheadleftarrow", "\\xtofrom",
|
|
// The next 3 functions are here to support the mhchem extension.
|
|
// Direct use of these functions is discouraged and may break someday.
|
|
"\\xrightleftarrows", "\\xrightequilibrium", "\\xleftequilibrium",
|
|
// The next 3 functions are here only to support the {CD} environment.
|
|
"\\\\cdrightarrow", "\\\\cdleftarrow", "\\\\cdlongequal",
|
|
],
|
|
props: {
|
|
numArgs: 1,
|
|
numOptionalArgs: 1,
|
|
},
|
|
handler({parser, funcName}, args, optArgs) {
|
|
return {
|
|
type: "xArrow",
|
|
mode: parser.mode,
|
|
label: funcName,
|
|
body: args[0],
|
|
below: optArgs[0],
|
|
};
|
|
},
|
|
// Flow is unable to correctly infer the type of `group`, even though it's
|
|
// unambiguously determined from the passed-in `type` above.
|
|
htmlBuilder(group: ParseNode<"xArrow">, options) {
|
|
const style = options.style;
|
|
|
|
// Build the argument groups in the appropriate style.
|
|
// Ref: amsmath.dtx: \hbox{$\scriptstyle\mkern#3mu{#6}\mkern#4mu$}%
|
|
|
|
// Some groups can return document fragments. Handle those by wrapping
|
|
// them in a span.
|
|
let newOptions = options.havingStyle(style.sup());
|
|
const upperGroup = buildCommon.wrapFragment(
|
|
html.buildGroup(group.body, newOptions, options), options);
|
|
const arrowPrefix = group.label.slice(0, 2) === "\\x" ? "x" : "cd";
|
|
upperGroup.classes.push(arrowPrefix + "-arrow-pad");
|
|
|
|
let lowerGroup;
|
|
if (group.below) {
|
|
// Build the lower group
|
|
newOptions = options.havingStyle(style.sub());
|
|
lowerGroup = buildCommon.wrapFragment(
|
|
html.buildGroup(group.below, newOptions, options), options);
|
|
lowerGroup.classes.push(arrowPrefix + "-arrow-pad");
|
|
}
|
|
|
|
const arrowBody = stretchy.svgSpan(group, options);
|
|
|
|
// Re shift: Note that stretchy.svgSpan returned arrowBody.depth = 0.
|
|
// The point we want on the math axis is at 0.5 * arrowBody.height.
|
|
const arrowShift = -options.fontMetrics().axisHeight +
|
|
0.5 * arrowBody.height;
|
|
// 2 mu kern. Ref: amsmath.dtx: #7\if0#2\else\mkern#2mu\fi
|
|
let upperShift = -options.fontMetrics().axisHeight
|
|
- 0.5 * arrowBody.height - 0.111; // 0.111 em = 2 mu
|
|
if (upperGroup.depth > 0.25 || group.label === "\\xleftequilibrium") {
|
|
upperShift -= upperGroup.depth; // shift up if depth encroaches
|
|
}
|
|
|
|
// Generate the vlist
|
|
let vlist;
|
|
if (lowerGroup) {
|
|
const lowerShift = -options.fontMetrics().axisHeight
|
|
+ lowerGroup.height + 0.5 * arrowBody.height
|
|
+ 0.111;
|
|
vlist = buildCommon.makeVList({
|
|
positionType: "individualShift",
|
|
children: [
|
|
{type: "elem", elem: upperGroup, shift: upperShift},
|
|
{type: "elem", elem: arrowBody, shift: arrowShift},
|
|
{type: "elem", elem: lowerGroup, shift: lowerShift},
|
|
],
|
|
}, options);
|
|
} else {
|
|
vlist = buildCommon.makeVList({
|
|
positionType: "individualShift",
|
|
children: [
|
|
{type: "elem", elem: upperGroup, shift: upperShift},
|
|
{type: "elem", elem: arrowBody, shift: arrowShift},
|
|
],
|
|
}, options);
|
|
}
|
|
|
|
// $FlowFixMe: Replace this with passing "svg-align" into makeVList.
|
|
vlist.children[0].children[0].children[1].classes.push("svg-align");
|
|
|
|
return buildCommon.makeSpan(["mrel", "x-arrow"], [vlist], options);
|
|
},
|
|
mathmlBuilder(group, options) {
|
|
const arrowNode = stretchy.mathMLnode(group.label);
|
|
arrowNode.setAttribute(
|
|
"minsize", group.label.charAt(0) === "x" ? "1.75em" : "3.0em"
|
|
);
|
|
let node;
|
|
|
|
if (group.body) {
|
|
const upperNode = paddedNode(mml.buildGroup(group.body, options));
|
|
if (group.below) {
|
|
const lowerNode = paddedNode(mml.buildGroup(group.below, options));
|
|
node = new mathMLTree.MathNode(
|
|
"munderover", [arrowNode, lowerNode, upperNode]
|
|
);
|
|
} else {
|
|
node = new mathMLTree.MathNode("mover", [arrowNode, upperNode]);
|
|
}
|
|
} else if (group.below) {
|
|
const lowerNode = paddedNode(mml.buildGroup(group.below, options));
|
|
node = new mathMLTree.MathNode("munder", [arrowNode, lowerNode]);
|
|
} else {
|
|
// This should never happen.
|
|
// Parser.js throws an error if there is no argument.
|
|
node = paddedNode();
|
|
node = new mathMLTree.MathNode("mover", [arrowNode, node]);
|
|
}
|
|
return node;
|
|
},
|
|
});
|