Horiz brace (#1332)

* Move "horizBrace" to functions/horizBrace.js.

* Add flow types to functions/horizBrace.js.

* Remove blank lines and warning comment in functions.js.

* Remove import comments and alphabetize functions/ imports in functions.js.
This commit is contained in:
Ashish Myles
2018-05-20 17:33:20 -04:00
committed by Kevin Barabash
parent 28dfa91fb5
commit ff8ca60584
5 changed files with 169 additions and 201 deletions

View File

@@ -13,7 +13,6 @@ import Style from "./Style";
import buildCommon from "./buildCommon";
import domTree from "./domTree";
import utils from "./utils";
import stretchy from "./stretchy";
import {spacings, tightSpacings} from "./spacingData";
const makeSpan = buildCommon.makeSpan;
@@ -415,100 +414,6 @@ export const groupTypes = {
[], options);
}
},
horizBrace(group, options) {
const style = options.style;
const hasSupSub = (group.type === "supsub");
let supSubGroup;
let newOptions;
if (hasSupSub) {
// 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.
if (group.value.sup) {
newOptions = options.havingStyle(style.sup());
supSubGroup = buildGroup(group.value.sup, newOptions, options);
} else {
newOptions = options.havingStyle(style.sub());
supSubGroup = buildGroup(group.value.sub, newOptions, options);
}
group = group.value.base;
}
// Build the base group
const body = buildGroup(
group.value.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.value.isOver) {
vlist = buildCommon.makeVList({
positionType: "firstBaseline",
children: [
{type: "elem", elem: body},
{type: "kern", size: 0.1},
{type: "elem", elem: braceBody},
],
}, options);
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);
vlist.children[0].children[0].children[0].classes.push("svg-align");
}
if (hasSupSub) {
// 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 = makeSpan(
["mord", (group.value.isOver ? "mover" : "munder")],
[vlist], options);
if (group.value.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 makeSpan(["mord", (group.value.isOver ? "mover" : "munder")],
[vlist], options);
},
};
/**

View File

@@ -13,7 +13,6 @@ import ParseError from "./ParseError";
import Style from "./Style";
import symbols from "./symbols";
import utils from "./utils";
import stretchy from "./stretchy";
/**
* Takes a symbol and converts it into a MathML text node after performing
@@ -272,14 +271,6 @@ groupTypes.spacing = function(group) {
return node;
};
groupTypes.horizBrace = function(group, options) {
const accentNode = stretchy.mathMLnode(group.value.label);
return new mathMLTree.MathNode(
(group.value.isOver ? "mover" : "munder"),
[buildGroup(group.value.base, options), accentNode]
);
};
groupTypes.tag = function(group, options) {
const table = new mathMLTree.MathNode("mtable", [
new mathMLTree.MathNode("mlabeledtr", [

View File

@@ -1,111 +1,40 @@
// @flow
/** Include this to ensure that all functions are defined. */
import {
default as _defineFunction,
_functions,
} from "./defineFunction";
import type {FunctionPropSpec, FunctionHandler} from "./defineFunction";
import type {NodeType} from "./ParseNode";
// WARNING: New functions should be added to src/functions and imported here.
import {_functions} from "./defineFunction";
const functions = _functions;
export default functions;
// Define a convenience function that mimcs the old semantics of defineFunction
// to support existing code so that we can migrate it a little bit at a time.
const defineFunction = function<NODETYPE: NodeType>(
// Type of node data output by the function handler. This is required to aid
// type inference of the actual function output.
type: NODETYPE,
names: string[],
props: FunctionPropSpec,
handler: ?FunctionHandler<NODETYPE>, // null only if handled in parser
) {
_defineFunction({type, names, props, handler});
};
// TODO(kevinb): have functions return an object and call defineFunction with
// that object in this file instead of relying on side-effects.
import "./functions/sqrt";
import "./functions/color";
import "./functions/text";
import "./functions/math";
import "./functions/enclose";
import "./functions/overline";
import "./functions/underline";
import "./functions/rule";
import "./functions/kern";
import "./functions/phantom";
import "./functions/mclass";
import "./functions/mod";
import "./functions/op";
import "./functions/operatorname";
import "./functions/genfrac";
import "./functions/lap";
import "./functions/smash";
import "./functions/delimsizing";
import "./functions/sizing";
import "./functions/styling";
import "./functions/font";
import "./functions/accent";
// Horizontal stretchy braces
defineFunction("horizBrace", [
"\\overbrace", "\\underbrace",
], {
numArgs: 1,
}, function(context, args) {
const base = args[0];
return {
type: "horizBrace",
label: context.funcName,
isOver: /^\\over/.test(context.funcName),
base: base,
};
});
// Stretchy accents under the body
import "./functions/accentunder";
// Stretch arrows
import "./functions/arrow";
// Row and line breaks
import "./functions/color";
import "./functions/cr";
// Environment delimiters
import "./functions/delimsizing";
import "./functions/enclose";
import "./functions/environment";
// Box manipulation
import "./functions/raisebox";
import "./functions/verb";
// Hyperlinks
import "./functions/font";
import "./functions/genfrac";
import "./functions/horizBrace";
import "./functions/href";
// MathChoice
import "./functions/kern";
import "./functions/lap";
import "./functions/math";
import "./functions/mathchoice";
import "./functions/mclass";
import "./functions/mod";
import "./functions/op";
import "./functions/operatorname";
import "./functions/overline";
import "./functions/phantom";
import "./functions/raisebox";
import "./functions/rule";
import "./functions/sizing";
import "./functions/smash";
import "./functions/sqrt";
import "./functions/styling";
import "./functions/text";
import "./functions/underline";
import "./functions/verb";

142
src/functions/horizBrace.js Normal file
View File

@@ -0,0 +1,142 @@
// @flow
import defineFunction from "../defineFunction";
import buildCommon from "../buildCommon";
import mathMLTree from "../mathMLTree";
import stretchy from "../stretchy";
import Style from "../Style";
import {assertNodeType, checkNodeType} from "../ParseNode";
import * as html from "../buildHTML";
import * as mml from "../buildMathML";
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.
function htmlBuilder(grp: ParseNode<*>, options) {
const style = options.style;
// Pull out the `ParseNode<"horizBrace">` if `grp` is a "supsub" node.
let supSubGroup;
let group: ParseNode<"horizBrace">;
const supSub = checkNodeType(grp, "supsub");
if (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 = supSub.value.sup ?
html.buildGroup(
supSub.value.sup, options.havingStyle(style.sup()), options) :
html.buildGroup(
supSub.value.sub, options.havingStyle(style.sub()), options);
// The supsub `base` must be non-null in this context. Otherwise,
// this `htmlBuilder` handler wouldn't have been invoked.
// $FlowFixMe
const base: ParseNode<*> = supSub.value.base;
group = assertNodeType(base, "horizBrace");
} else {
group = assertNodeType(grp, "horizBrace");
}
// Build the base group
const body = html.buildGroup(
group.value.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.value.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.value.isOver ? "mover" : "munder")],
[vlist], options);
if (group.value.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.value.isOver ? "mover" : "munder")], [vlist], options);
}
// Horizontal stretchy braces
defineFunction({
type: "horizBrace",
names: ["\\overbrace", "\\underbrace"],
props: {
numArgs: 1,
},
handler(context, args) {
return {
type: "horizBrace",
label: context.funcName,
isOver: /^\\over/.test(context.funcName),
base: args[0],
};
},
htmlBuilder,
mathmlBuilder(group, options) {
const accentNode = stretchy.mathMLnode(group.value.label);
return new mathMLTree.MathNode(
(group.value.isOver ? "mover" : "munder"),
[mml.buildGroup(group.value.base, options), accentNode]
);
},
});

View File

@@ -168,7 +168,8 @@ const groupLength = function(arg: ParseNode<*>): number {
};
const svgSpan = function(
group: ParseNode<"accent"> | ParseNode<"accentUnder"> | ParseNode<"xArrow">,
group: ParseNode<"accent"> | ParseNode<"accentUnder"> | ParseNode<"xArrow">
| ParseNode<"horizBrace">,
options: Options,
): DomSpan | SvgSpan {
// Create a span with inline SVG for the element.