Move "xArrow" into functions/arrow.js and add flow types. (#1327)

* Move "xArrow" into functions/arrow.js.

* Add flow types to functions/arrow.js.

* Address review comments.
This commit is contained in:
Ashish Myles
2018-05-19 22:41:39 -04:00
committed by Kevin Barabash
parent 35d6181a95
commit 28dfa91fb5
5 changed files with 132 additions and 111 deletions

View File

@@ -509,66 +509,6 @@ export const groupTypes = {
return makeSpan(["mord", (group.value.isOver ? "mover" : "munder")], return makeSpan(["mord", (group.value.isOver ? "mover" : "munder")],
[vlist], options); [vlist], options);
}, },
xArrow(group, options) {
const style = options.style;
// Build the argument groups in the appropriate style.
// Ref: amsmath.dtx: \hbox{$\scriptstyle\mkern#3mu{#6}\mkern#4mu$}%
let newOptions = options.havingStyle(style.sup());
const upperGroup = buildGroup(group.value.body, newOptions, options);
upperGroup.classes.push("x-arrow-pad");
let lowerGroup;
if (group.value.below) {
// Build the lower group
newOptions = options.havingStyle(style.sub());
lowerGroup = buildGroup(group.value.below, newOptions, options);
lowerGroup.classes.push("x-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.value.label === "\\xleftequilibrium") {
upperShift -= upperGroup.depth; // shift up if depth encroaches
}
// Generate the vlist
let vlist;
if (group.value.below) {
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);
}
vlist.children[0].children[0].children[1].classes.push("svg-align");
return makeSpan(["mrel", "x-arrow"], [vlist], options);
},
}; };
/** /**

View File

@@ -280,30 +280,6 @@ groupTypes.horizBrace = function(group, options) {
); );
}; };
groupTypes.xArrow = function(group, options) {
const arrowNode = stretchy.mathMLnode(group.value.label);
let node;
let lowerNode;
if (group.value.body) {
const upperNode = buildGroup(group.value.body, options);
if (group.value.below) {
lowerNode = buildGroup(group.value.below, options);
node = new mathMLTree.MathNode(
"munderover", [arrowNode, lowerNode, upperNode]
);
} else {
node = new mathMLTree.MathNode("mover", [arrowNode, upperNode]);
}
} else if (group.value.below) {
lowerNode = buildGroup(group.value.below, options);
node = new mathMLTree.MathNode("munder", [arrowNode, lowerNode]);
} else {
node = new mathMLTree.MathNode("mover", [arrowNode]);
}
return node;
};
groupTypes.tag = function(group, options) { groupTypes.tag = function(group, options) {
const table = new mathMLTree.MathNode("mtable", [ const table = new mathMLTree.MathNode("mtable", [
new mathMLTree.MathNode("mlabeledtr", [ new mathMLTree.MathNode("mlabeledtr", [

View File

@@ -90,31 +90,8 @@ defineFunction("horizBrace", [
// Stretchy accents under the body // Stretchy accents under the body
import "./functions/accentunder"; import "./functions/accentunder";
// Stretchy arrows with an optional argument // Stretch arrows
defineFunction("xArrow", [ import "./functions/arrow";
"\\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",
], {
numArgs: 1,
numOptionalArgs: 1,
}, function(context, args, optArgs) {
const below = optArgs[0];
const body = args[0];
return {
type: "xArrow", // x for extensible
label: context.funcName,
body: body,
below: below,
};
});
// Row and line breaks // Row and line breaks
import "./functions/cr"; import "./functions/cr";

124
src/functions/arrow.js Normal file
View File

@@ -0,0 +1,124 @@
// @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.js";
// 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",
],
props: {
numArgs: 1,
numOptionalArgs: 1,
},
handler(context, args, optArgs) {
return {
type: "xArrow", // x for extensible
label: context.funcName,
body: args[0],
below: optArgs[0],
};
},
// Flow is unable to correctly infer the type of `group`, even though it's
// unamibiguously 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$}%
let newOptions = options.havingStyle(style.sup());
const upperGroup = html.buildGroup(group.value.body, newOptions, options);
upperGroup.classes.push("x-arrow-pad");
let lowerGroup;
if (group.value.below) {
// Build the lower group
newOptions = options.havingStyle(style.sub());
lowerGroup = html.buildGroup(group.value.below, newOptions, options);
lowerGroup.classes.push("x-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.value.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.value.label);
let node;
let lowerNode;
if (group.value.body) {
const upperNode = mml.buildGroup(group.value.body, options);
if (group.value.below) {
lowerNode = mml.buildGroup(group.value.below, options);
node = new mathMLTree.MathNode(
"munderover", [arrowNode, lowerNode, upperNode]
);
} else {
node = new mathMLTree.MathNode("mover", [arrowNode, upperNode]);
}
} else if (group.value.below) {
lowerNode = mml.buildGroup(group.value.below, options);
node = new mathMLTree.MathNode("munder", [arrowNode, lowerNode]);
} else {
node = new mathMLTree.MathNode("mover", [arrowNode]);
}
return node;
},
});

View File

@@ -168,7 +168,7 @@ const groupLength = function(arg: ParseNode<*>): number {
}; };
const svgSpan = function( const svgSpan = function(
group: ParseNode<"accent"> | ParseNode<"accentUnder">, group: ParseNode<"accent"> | ParseNode<"accentUnder"> | ParseNode<"xArrow">,
options: Options, options: Options,
): DomSpan | SvgSpan { ): DomSpan | SvgSpan {
// Create a span with inline SVG for the element. // Create a span with inline SVG for the element.
@@ -180,9 +180,13 @@ const svgSpan = function(
let viewBoxWidth = 400000; // default let viewBoxWidth = 400000; // default
const label = group.value.label.substr(1); const label = group.value.label.substr(1);
if (utils.contains(["widehat", "widetilde", "utilde"], label)) { if (utils.contains(["widehat", "widetilde", "utilde"], label)) {
// Each type in the `if` statement corresponds to one of the ParseNode
// types below. This narrowing is required to access `grp.value.base`.
// $FlowFixMe
const grp: ParseNode<"accent"> | ParseNode<"accentUnder"> = group;
// There are four SVG images available for each function. // There are four SVG images available for each function.
// Choose a taller image when there are more characters. // Choose a taller image when there are more characters.
const numChars = groupLength(group.value.base); const numChars = groupLength(grp.value.base);
let viewBoxHeight; let viewBoxHeight;
let pathName; let pathName;
let height; let height;