Flatten ParseNodes: "supsub" and related ("accent", "accentUnder", "horizBrace", "xarrow", "op"). (#1542)

This commit is contained in:
Ashish Myles
2018-08-03 01:06:02 -04:00
committed by ylemkimon
parent c7145f0ff0
commit fd6690b988
16 changed files with 215 additions and 278 deletions

View File

@@ -368,8 +368,8 @@ export default class Parser {
const opNode = checkNodeType(base, "op");
if (opNode) {
const limits = lex.text === "\\limits";
opNode.value.limits = limits;
opNode.value.alwaysHandleSupSub = true;
opNode.limits = limits;
opNode.alwaysHandleSupSub = true;
} else {
throw new ParseError(
"Limit controls must follow a math operator",
@@ -424,12 +424,9 @@ export default class Parser {
return {
type: "supsub",
mode: this.mode,
value: {
type: "supsub",
base: base,
sup: superscript,
sub: subscript,
},
base: base,
sup: superscript,
sub: subscript,
};
} else {
// Otherwise return the original body
@@ -872,6 +869,7 @@ export default class Parser {
let n = group.length - 1;
for (let i = 0; i < n; ++i) {
const a = group[i];
// $FlowFixMe: Not every node type has a `value` property.
const v = a.value;
if (v === "-" && group[i + 1].value === "-") {
if (i + 1 < n && group[i + 2].value === "-") {
@@ -1085,13 +1083,10 @@ export default class Parser {
type: "accent",
mode: this.mode,
loc: SourceLocation.range(nucleus),
value: {
type: "accent",
label: command,
isStretchy: false,
isShifty: true,
base: symbol,
},
label: command,
isStretchy: false,
isShifty: true,
base: symbol,
};
}
}

View File

@@ -32,28 +32,28 @@ export const htmlBuilder: HtmlBuilderSupSub<"accent"> = (grp, options) => {
// rendering that, while keeping track of where the accent is.
// The real accent group is the base of the supsub group
group = assertNodeType(supSub.value.base, "accent");
group = assertNodeType(supSub.base, "accent");
// The character box is the base of the accent group
base = group.value.base;
base = group.base;
// Stick the character box into the base of the supsub group
supSub.value.base = base;
supSub.base = base;
// Rerender the supsub group with its new base, and store that
// result.
supSubGroup = assertSpan(html.buildGroup(supSub, options));
// reset original base
supSub.value.base = group;
supSub.base = group;
} else {
group = assertNodeType(grp, "accent");
base = group.value.base;
base = group.base;
}
// Build the base group
const body = html.buildGroup(base, options.havingCrampedStyle());
// Does the accent need to shift for the skew of a character?
const mustShift = group.value.isShifty && utils.isCharacterBox(base);
const mustShift = group.isShifty && utils.isCharacterBox(base);
// Calculate the skew of the accent. This is based on the line "If the
// nucleus is not a single character, let s = 0; otherwise set s to the
@@ -82,10 +82,10 @@ export const htmlBuilder: HtmlBuilderSupSub<"accent"> = (grp, options) => {
// Build the accent
let accentBody;
if (!group.value.isStretchy) {
if (!group.isStretchy) {
let accent;
let width: number;
if (group.value.label === "\\vec") {
if (group.label === "\\vec") {
// Before version 0.9, \vec used the combining font glyph U+20D7.
// But browsers, especially Safari, are not consistent in how they
// render combining characters when not preceded by a character.
@@ -95,7 +95,7 @@ export const htmlBuilder: HtmlBuilderSupSub<"accent"> = (grp, options) => {
width = buildCommon.svgData.vec[1];
} else {
accent = buildCommon.makeSymbol(
group.value.label, "Main-Regular", group.mode, options);
group.label, "Main-Regular", group.mode, options);
// Remove the italic correction of the accent, because it only serves to
// shift the accent over to a place we don't want.
accent.italic = 0;
@@ -107,7 +107,7 @@ export const htmlBuilder: HtmlBuilderSupSub<"accent"> = (grp, options) => {
// "Full" accents expand the width of the resulting symbol to be
// at least the width of the accent, and overlap directly onto the
// character without any vertical offset.
const accentFull = (group.value.label === "\\textcircled");
const accentFull = (group.label === "\\textcircled");
if (accentFull) {
accentBody.classes.push('accent-full');
clearance = body.height;
@@ -128,7 +128,7 @@ export const htmlBuilder: HtmlBuilderSupSub<"accent"> = (grp, options) => {
// \textcircled uses the \bigcirc glyph, so it needs some
// vertical adjustment to match LaTeX.
if (group.value.label === "\\textcircled") {
if (group.label === "\\textcircled") {
accentBody.style.top = ".2em";
}
@@ -185,18 +185,14 @@ export const htmlBuilder: HtmlBuilderSupSub<"accent"> = (grp, options) => {
};
const mathmlBuilder: MathMLBuilder<"accent"> = (group, options) => {
const groupValue = group.value;
let accentNode;
if (groupValue.isStretchy) {
accentNode = stretchy.mathMLnode(groupValue.label);
} else {
accentNode = new mathMLTree.MathNode(
"mo", [mml.makeText(groupValue.label, group.mode)]);
}
const accentNode =
group.isStretchy ?
stretchy.mathMLnode(group.label) :
new mathMLTree.MathNode("mo", [mml.makeText(group.label, group.mode)]);
const node = new mathMLTree.MathNode(
"mover",
[mml.buildGroup(groupValue.base, options), accentNode]);
[mml.buildGroup(group.base, options), accentNode]);
node.setAttribute("accent", "true");
@@ -233,13 +229,10 @@ defineFunction({
return {
type: "accent",
mode: context.parser.mode,
value: {
type: "accent",
label: context.funcName,
isStretchy: isStretchy,
isShifty: isShifty,
base: base,
},
label: context.funcName,
isStretchy: isStretchy,
isShifty: isShifty,
base: base,
};
},
htmlBuilder,
@@ -264,13 +257,10 @@ defineFunction({
return {
type: "accent",
mode: context.parser.mode,
value: {
type: "accent",
label: context.funcName,
isStretchy: false,
isShifty: true,
base: base,
},
label: context.funcName,
isStretchy: false,
isShifty: true,
base: base,
};
},
htmlBuilder,

View File

@@ -24,19 +24,16 @@ defineFunction({
return {
type: "accentUnder",
mode: parser.mode,
value: {
type: "accentUnder",
label: funcName,
base: base,
},
label: funcName,
base: base,
};
},
htmlBuilder: (group: ParseNode<"accentUnder">, options) => {
// Treat under accents much like underlines.
const innerGroup = html.buildGroup(group.value.base, options);
const innerGroup = html.buildGroup(group.base, options);
const accentBody = stretchy.svgSpan(group, options);
const kern = group.value.label === "\\utilde" ? 0.12 : 0;
const kern = group.label === "\\utilde" ? 0.12 : 0;
// Generate the vlist, with the appropriate kerns
const vlist = buildCommon.makeVList({
@@ -52,10 +49,10 @@ defineFunction({
return buildCommon.makeSpan(["mord", "accentunder"], [vlist], options);
},
mathmlBuilder: (group, options) => {
const accentNode = stretchy.mathMLnode(group.value.label);
const accentNode = stretchy.mathMLnode(group.label);
const node = new mathMLTree.MathNode(
"munder",
[mml.buildGroup(group.value.base, options), accentNode]
[mml.buildGroup(group.base, options), accentNode]
);
node.setAttribute("accentunder", "true");
return node;

View File

@@ -31,12 +31,9 @@ defineFunction({
return {
type: "xArrow",
mode: parser.mode,
value: {
type: "xArrow", // x for extensible
label: funcName,
body: args[0],
below: optArgs[0],
},
label: funcName,
body: args[0],
below: optArgs[0],
};
},
// Flow is unable to correctly infer the type of `group`, even though it's
@@ -48,14 +45,14 @@ defineFunction({
// 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);
const upperGroup = html.buildGroup(group.body, newOptions, options);
upperGroup.classes.push("x-arrow-pad");
let lowerGroup;
if (group.value.below) {
if (group.below) {
// Build the lower group
newOptions = options.havingStyle(style.sub());
lowerGroup = html.buildGroup(group.value.below, newOptions, options);
lowerGroup = html.buildGroup(group.below, newOptions, options);
lowerGroup.classes.push("x-arrow-pad");
}
@@ -68,7 +65,7 @@ defineFunction({
// 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") {
if (upperGroup.depth > 0.25 || group.label === "\\xleftequilibrium") {
upperShift -= upperGroup.depth; // shift up if depth encroaches
}
@@ -102,22 +99,22 @@ defineFunction({
return buildCommon.makeSpan(["mrel", "x-arrow"], [vlist], options);
},
mathmlBuilder(group, options) {
const arrowNode = stretchy.mathMLnode(group.value.label);
const arrowNode = stretchy.mathMLnode(group.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);
if (group.body) {
const upperNode = mml.buildGroup(group.body, options);
if (group.below) {
lowerNode = mml.buildGroup(group.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);
} else if (group.below) {
lowerNode = mml.buildGroup(group.below, options);
node = new mathMLTree.MathNode("munder", [arrowNode, lowerNode]);
} else {
node = new mathMLTree.MathNode("mover", [arrowNode]);

View File

@@ -64,8 +64,9 @@ function checkDelimiter(
return symDelim;
} else {
throw new ParseError(
"Invalid delimiter: '" + String(delim.value) + "' after '" +
context.funcName + "'", delim);
"Invalid delimiter: '" +
(symDelim ? symDelim.value : JSON.stringify(delim)) +
"' after '" + context.funcName + "'", delim);
}
}

View File

@@ -402,7 +402,8 @@ defineFunction({
let styl = checkNodeType(args[3], "ordgroup");
if (styl) {
if (styl.value.length > 0) {
size = stylArray[Number(styl.value[0].value)];
const textOrd = assertNodeType(styl.value[0], "textord");
size = stylArray[Number(textOrd.value)];
}
} else {
styl = assertNodeType(args[3], "textord");

View File

@@ -25,19 +25,17 @@ export const htmlBuilder: HtmlBuilderSupSub<"horizBrace"> = (grp, options) => {
// 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);
group = assertNodeType(supSub.value.base, "horizBrace");
supSubGroup = supSub.sup ?
html.buildGroup(supSub.sup, options.havingStyle(style.sup()), options) :
html.buildGroup(supSub.sub, options.havingStyle(style.sub()), options);
group = assertNodeType(supSub.base, "horizBrace");
} else {
group = assertNodeType(grp, "horizBrace");
}
// Build the base group
const body = html.buildGroup(
group.value.base, options.havingBaseStyle(Style.DISPLAY));
group.base, options.havingBaseStyle(Style.DISPLAY));
// Create the stretchy element
const braceBody = stretchy.svgSpan(group, options);
@@ -45,7 +43,7 @@ export const htmlBuilder: HtmlBuilderSupSub<"horizBrace"> = (grp, options) => {
// Generate the vlist, with the appropriate kerns ┏━━━━━━━━┓
// This first vlist contains the content and the brace: equation
let vlist;
if (group.value.isOver) {
if (group.isOver) {
vlist = buildCommon.makeVList({
positionType: "firstBaseline",
children: [
@@ -81,10 +79,10 @@ export const htmlBuilder: HtmlBuilderSupSub<"horizBrace"> = (grp, options) => {
// equation eqn eqn
const vSpan = buildCommon.makeSpan(
["mord", (group.value.isOver ? "mover" : "munder")],
["mord", (group.isOver ? "mover" : "munder")],
[vlist], options);
if (group.value.isOver) {
if (group.isOver) {
vlist = buildCommon.makeVList({
positionType: "firstBaseline",
children: [
@@ -108,14 +106,14 @@ export const htmlBuilder: HtmlBuilderSupSub<"horizBrace"> = (grp, options) => {
}
return buildCommon.makeSpan(
["mord", (group.value.isOver ? "mover" : "munder")], [vlist], options);
["mord", (group.isOver ? "mover" : "munder")], [vlist], options);
};
const mathmlBuilder: MathMLBuilder<"horizBrace"> = (group, options) => {
const accentNode = stretchy.mathMLnode(group.value.label);
const accentNode = stretchy.mathMLnode(group.label);
return new mathMLTree.MathNode(
(group.value.isOver ? "mover" : "munder"),
[mml.buildGroup(group.value.base, options), accentNode]
(group.isOver ? "mover" : "munder"),
[mml.buildGroup(group.base, options), accentNode]
);
};
@@ -130,12 +128,9 @@ defineFunction({
return {
type: "horizBrace",
mode: parser.mode,
value: {
type: "horizBrace",
label: funcName,
isOver: /^\\over/.test(funcName),
base: args[0],
},
label: funcName,
isOver: /^\\over/.test(funcName),
base: args[0],
};
},
htmlBuilder,

View File

@@ -101,25 +101,19 @@ defineFunction({
const baseOp = {
type: "op",
mode: baseArg.mode,
value: {
type: "op",
limits: true,
alwaysHandleSupSub: true,
symbol: false,
suppressBaseShift: funcName !== "\\stackrel",
value: ordargument(baseArg),
},
limits: true,
alwaysHandleSupSub: true,
symbol: false,
suppressBaseShift: funcName !== "\\stackrel",
value: ordargument(baseArg),
};
const supsub = {
type: "supsub",
mode: shiftedArg.mode,
value: {
type: "supsub",
base: baseOp,
sup: funcName === "\\underset" ? null : shiftedArg,
sub: funcName === "\\underset" ? shiftedArg : null,
},
base: baseOp,
sup: funcName === "\\underset" ? null : shiftedArg,
sub: funcName === "\\underset" ? shiftedArg : null,
};
return {

View File

@@ -27,9 +27,9 @@ export const htmlBuilder: HtmlBuilderSupSub<"op"> = (grp, options) => {
// If we have limits, supsub will pass us its group to handle. Pull
// out the superscript and subscript and set the group to the op in
// its base.
supGroup = supSub.value.sup;
subGroup = supSub.value.sub;
group = assertNodeType(supSub.value.base, "op");
supGroup = supSub.sup;
subGroup = supSub.sub;
group = assertNodeType(supSub.base, "op");
hasLimits = true;
} else {
group = assertNodeType(grp, "op");
@@ -44,29 +44,29 @@ export const htmlBuilder: HtmlBuilderSupSub<"op"> = (grp, options) => {
let large = false;
if (style.size === Style.DISPLAY.size &&
group.value.symbol &&
!utils.contains(noSuccessor, group.value.body)) {
group.symbol &&
!utils.contains(noSuccessor, group.body)) {
// Most symbol operators get larger in displaystyle (rule 13)
large = true;
}
let base;
if (group.value.symbol) {
if (group.symbol) {
// If this is a symbol, create the symbol.
const fontName = large ? "Size2-Regular" : "Size1-Regular";
let stash = "";
if (group.value.body === "\\oiint" || group.value.body === "\\oiiint") {
if (group.body === "\\oiint" || group.body === "\\oiiint") {
// No font glyphs yet, so use a glyph w/o the oval.
// TODO: When font glyphs are available, delete this code.
stash = group.value.body.substr(1);
stash = group.body.substr(1);
// $FlowFixMe
group.value.body = stash === "oiint" ? "\\iint" : "\\iiint";
group.body = stash === "oiint" ? "\\iint" : "\\iiint";
}
base = buildCommon.makeSymbol(
group.value.body, fontName, "math", options,
group.body, fontName, "math", options,
["mop", "op-symbol", large ? "large-op" : "small-op"]);
if (stash.length > 0) {
@@ -83,14 +83,14 @@ export const htmlBuilder: HtmlBuilderSupSub<"op"> = (grp, options) => {
],
}, options);
// $FlowFixMe
group.value.body = "\\" + stash;
group.body = "\\" + stash;
base.classes.unshift("mop");
// $FlowFixMe
base.italic = italic;
}
} else if (group.value.value) {
} else if (group.value) {
// If this is a list, compose that list.
const inner = html.buildExpression(group.value.value, options, true);
const inner = html.buildExpression(group.value, options, true);
if (inner.length === 1 && inner[0] instanceof domTree.symbolNode) {
base = inner[0];
base.classes[0] = "mop"; // replace old mclass
@@ -103,8 +103,8 @@ export const htmlBuilder: HtmlBuilderSupSub<"op"> = (grp, options) => {
// TODO(emily): Add a space in the middle of some of these
// operators, like \limsup
const output = [];
for (let i = 1; i < group.value.body.length; i++) {
output.push(buildCommon.mathsym(group.value.body[i], group.mode));
for (let i = 1; i < group.body.length; i++) {
output.push(buildCommon.mathsym(group.body[i], group.mode));
}
base = buildCommon.makeSpan(["mop"], output, options);
}
@@ -113,8 +113,8 @@ export const htmlBuilder: HtmlBuilderSupSub<"op"> = (grp, options) => {
let baseShift = 0;
let slant = 0;
if ((base instanceof domTree.symbolNode
|| group.value.body === "\\oiint" || group.value.body === "\\oiiint")
&& !group.value.suppressBaseShift) {
|| group.body === "\\oiint" || group.body === "\\oiiint")
&& !group.suppressBaseShift) {
// We suppress the shift of the base of \overset and \underset. Otherwise,
// shift the symbol so its center lies on the axis (rule 13). It
// appears that our fonts have the centers of the symbols already
@@ -238,21 +238,21 @@ const mathmlBuilder: MathMLBuilder<"op"> = (group, options) => {
// TODO(emily): handle big operators using the `largeop` attribute
if (group.value.symbol) {
if (group.symbol) {
// This is a symbol. Just add the symbol.
node = new mathMLTree.MathNode(
"mo", [mml.makeText(group.value.body, group.mode)]);
} else if (group.value.value) {
"mo", [mml.makeText(group.body, group.mode)]);
} else if (group.value) {
// This is an operator with children. Add them.
node = new mathMLTree.MathNode(
"mo", mml.buildExpression(group.value.value, options));
"mo", mml.buildExpression(group.value, options));
} else {
// This is a text operator. Add all of the characters from the
// operator's name.
// TODO(emily): Add a space in the middle of some of these
// operators, like \limsup.
node = new mathMLTree.MathNode(
"mi", [new mathMLTree.TextNode(group.value.body.slice(1))]);
"mi", [new mathMLTree.TextNode(group.body.slice(1))]);
// Append an <mo>&ApplyFunction;</mo>.
// ref: https://www.w3.org/TR/REC-MathML/chap3_2.html#sec3.2.4
@@ -300,12 +300,9 @@ defineFunction({
return {
type: "op",
mode: parser.mode,
value: {
type: "op",
limits: true,
symbol: true,
body: fName,
},
limits: true,
symbol: true,
body: fName,
};
},
htmlBuilder,
@@ -325,12 +322,9 @@ defineFunction({
return {
type: "op",
mode: parser.mode,
value: {
type: "op",
limits: false,
symbol: false,
value: ordargument(body),
},
limits: false,
symbol: false,
value: ordargument(body),
};
},
htmlBuilder,
@@ -361,12 +355,9 @@ defineFunction({
return {
type: "op",
mode: parser.mode,
value: {
type: "op",
limits: false,
symbol: false,
value: ordargument(body),
},
limits: false,
symbol: false,
value: ordargument(body),
};
},
htmlBuilder,
@@ -390,12 +381,9 @@ defineFunction({
return {
type: "op",
mode: parser.mode,
value: {
type: "op",
limits: false,
symbol: false,
body: funcName,
},
limits: false,
symbol: false,
body: funcName,
};
},
htmlBuilder,
@@ -415,12 +403,9 @@ defineFunction({
return {
type: "op",
mode: parser.mode,
value: {
type: "op",
limits: true,
symbol: false,
body: funcName,
},
limits: true,
symbol: false,
body: funcName,
};
},
htmlBuilder,
@@ -445,12 +430,9 @@ defineFunction({
return {
type: "op",
mode: parser.mode,
value: {
type: "op",
limits: false,
symbol: true,
body: fName,
},
limits: false,
symbol: true,
body: fName,
};
},
htmlBuilder,

View File

@@ -30,6 +30,7 @@ defineFunction({
htmlBuilder: (group, options) => {
if (group.value.value.length > 0) {
const groupValue = group.value.value.map(child => {
// $FlowFixMe: Check if the node has a string `value` property.
const childValue = child.value;
if (typeof childValue === "string") {
return {

View File

@@ -26,7 +26,9 @@ defineFunction({
// def\mb@t{\ht}\def\mb@b{\dp}\def\mb@tb{\ht\z@\z@\dp}%
let letter = "";
for (let i = 0; i < tbArg.value.length; ++i) {
letter = tbArg.value[i].value;
const node = tbArg.value[i];
// $FlowFixMe: Not every node type has a `value` property.
letter = node.value;
if (letter === "t") {
smashHeight = true;
} else if (letter === "b") {

View File

@@ -29,21 +29,21 @@ const htmlBuilderDelegate = function(
group: ParseNode<"supsub">,
options: Options,
): ?HtmlBuilder<*> {
const base = group.value.base;
const base = group.base;
if (!base) {
return null;
} else if (base.type === "op") {
// Operators handle supsubs differently when they have limits
// (e.g. `\displaystyle\sum_2^3`)
const delegate = base.value.limits &&
const delegate = base.limits &&
(options.style.size === Style.DISPLAY.size ||
base.value.alwaysHandleSupSub);
base.alwaysHandleSupSub);
return delegate ? op.htmlBuilder : null;
} else if (base.type === "accent") {
return utils.isCharacterBox(base.value.base) ? accent.htmlBuilder : null;
return utils.isCharacterBox(base.base) ? accent.htmlBuilder : null;
} else if (base.type === "horizBrace") {
const isSup = !group.value.sub;
return isSup === base.value.isOver ? horizBrace.htmlBuilder : null;
const isSup = !group.sub;
return isSup === base.isOver ? horizBrace.htmlBuilder : null;
} else {
return null;
}
@@ -64,7 +64,7 @@ defineFunctionBuilders({
return builderDelegate(group, options);
}
const {base: valueBase, sup: valueSup, sub: valueSub} = group.value;
const {base: valueBase, sup: valueSup, sub: valueSub} = group;
const base = html.buildGroup(valueBase, options);
let supm;
let subm;
@@ -114,11 +114,9 @@ defineFunctionBuilders({
// Subscripts shouldn't be shifted by the base's italic correction.
// Account for that by shifting the subscript back the appropriate
// amount. Note we only do this when the base is a single symbol.
let isOiint = false;
if (group.value.base) {
isOiint = group.value.base.value.body === "\\oiint" ||
group.value.base.value.body === "\\oiiint";
}
const isOiint =
group.base && group.base.type === "op" && group.base.body &&
(group.base.body === "\\oiint" || group.base.body === "\\oiiint");
if (base instanceof domTree.symbolNode || isOiint) {
// $FlowFixMe
marginLeft = -base.italic + "em";
@@ -194,45 +192,48 @@ defineFunctionBuilders({
let isOver;
let isSup;
const horizBrace = checkNodeType(group.value.base, "horizBrace");
const horizBrace = checkNodeType(group.base, "horizBrace");
if (horizBrace) {
isSup = !!group.value.sup;
if (isSup === horizBrace.value.isOver) {
isSup = !!group.sup;
if (isSup === horizBrace.isOver) {
isBrace = true;
isOver = horizBrace.value.isOver;
isOver = horizBrace.isOver;
}
}
const children = [mml.buildGroup(group.value.base, options)];
const children = [mml.buildGroup(group.base, options)];
if (group.value.sub) {
children.push(mml.buildGroup(group.value.sub, options));
if (group.sub) {
children.push(mml.buildGroup(group.sub, options));
}
if (group.value.sup) {
children.push(mml.buildGroup(group.value.sup, options));
if (group.sup) {
children.push(mml.buildGroup(group.sup, options));
}
let nodeType: MathNodeType;
if (isBrace) {
nodeType = (isOver ? "mover" : "munder");
} else if (!group.value.sub) {
const base = group.value.base;
if (base && base.value.limits && options.style === Style.DISPLAY) {
} else if (!group.sub) {
const base = group.base;
if (base && base.type === "op" && base.limits &&
options.style === Style.DISPLAY) {
nodeType = "mover";
} else {
nodeType = "msup";
}
} else if (!group.value.sup) {
const base = group.value.base;
if (base && base.value.limits && options.style === Style.DISPLAY) {
} else if (!group.sup) {
const base = group.base;
if (base && base.type === "op" && base.limits &&
options.style === Style.DISPLAY) {
nodeType = "munder";
} else {
nodeType = "msub";
}
} else {
const base = group.value.base;
if (base && base.value.limits && options.style === Style.DISPLAY) {
const base = group.base;
if (base && base.type === "op" && base.limits &&
options.style === Style.DISPLAY) {
nodeType = "munderover";
} else {
nodeType = "msubsup";

View File

@@ -61,23 +61,22 @@ type ParseNodeTypes = {
type: "op",
mode: Mode,
loc?: ?SourceLocation,
value: {|
type: "op",
limits: boolean,
alwaysHandleSupSub?: boolean,
suppressBaseShift?: boolean,
symbol: boolean,
body: string,
value?: void,
|} | {|
type: "op",
limits: boolean,
alwaysHandleSupSub?: boolean,
suppressBaseShift?: boolean,
symbol: false, // If 'symbol' is true, `body` *must* be set.
body?: void,
value: AnyParseNode[],
|},
limits: boolean,
alwaysHandleSupSub?: boolean,
suppressBaseShift?: boolean,
symbol: boolean,
body: string,
value?: void,
|} | {|
type: "op",
mode: Mode,
loc?: ?SourceLocation,
limits: boolean,
alwaysHandleSupSub?: boolean,
suppressBaseShift?: boolean,
symbol: false, // If 'symbol' is true, `body` *must* be set.
body?: void,
value: AnyParseNode[],
|},
"ordgroup": {|
type: "ordgroup",
@@ -109,12 +108,9 @@ type ParseNodeTypes = {
type: "supsub",
mode: Mode,
loc?: ?SourceLocation,
value: {|
type: "supsub",
base: ?AnyParseNode,
sup?: ?AnyParseNode,
sub?: ?AnyParseNode,
|},
base: ?AnyParseNode,
sup?: ?AnyParseNode,
sub?: ?AnyParseNode,
|},
"tag": {|
type: "tag",
@@ -202,25 +198,19 @@ type ParseNodeTypes = {
type: "accent",
mode: Mode,
loc?: ?SourceLocation,
value: {|
type: "accent",
label: string,
isStretchy?: boolean,
isShifty?: boolean,
base: AnyParseNode,
|},
label: string,
isStretchy?: boolean,
isShifty?: boolean,
base: AnyParseNode,
|},
"accentUnder": {|
type: "accentUnder",
mode: Mode,
loc?: ?SourceLocation,
value: {|
type: "accentUnder",
label: string,
isStretchy?: boolean,
isShifty?: boolean,
base: AnyParseNode,
|},
label: string,
isStretchy?: boolean,
isShifty?: boolean,
base: AnyParseNode,
|},
"cr": {|
type: "cr",
@@ -296,12 +286,9 @@ type ParseNodeTypes = {
type: "horizBrace",
mode: Mode,
loc?: ?SourceLocation,
value: {|
type: "horizBrace",
label: string,
isOver: boolean,
base: AnyParseNode,
|},
label: string,
isOver: boolean,
base: AnyParseNode,
|},
"href": {|
type: "href",
@@ -522,12 +509,9 @@ type ParseNodeTypes = {
type: "xArrow",
mode: Mode,
loc?: ?SourceLocation,
value: {|
type: "xArrow",
label: string,
body: AnyParseNode,
below: ?AnyParseNode,
|},
label: string,
body: AnyParseNode,
below: ?AnyParseNode,
|},
};

View File

@@ -180,16 +180,16 @@ const svgSpan = function(
height: number,
} {
let viewBoxWidth = 400000; // default
const label = group.value.label.substr(1);
const label = group.label.substr(1);
if (utils.contains(["widehat", "widecheck", "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`.
// types below. This narrowing is required to access `grp.base`.
// $FlowFixMe
const grp: ParseNode<"accent"> | ParseNode<"accentUnder"> = group;
// There are four SVG images available for each function.
// Choose a taller image when there are more characters.
const numChars = groupLength(grp.value.base);
const numChars = groupLength(grp.base);
let viewBoxHeight;
let pathName;
let height;

View File

@@ -581,19 +581,16 @@ exports[`A parse tree generator generates a tree 1`] = `
[
{
"type": "supsub",
"base": {
"type": "mathord",
"mode": "math",
"value": "\\\\sigma"
},
"mode": "math",
"value": {
"type": "supsub",
"base": {
"type": "mathord",
"mode": "math",
"value": "\\\\sigma"
},
"sup": {
"type": "textord",
"mode": "math",
"value": "2"
}
"sup": {
"type": "textord",
"mode": "math",
"value": "2"
}
}
]

View File

@@ -191,36 +191,36 @@ describe("A subscript and superscript parser", function() {
const parse = getParsed`x^2`[0];
expect(parse.type).toBe("supsub");
expect(parse.value.base).toBeDefined();
expect(parse.value.sup).toBeDefined();
expect(parse.value.sub).toBeUndefined();
expect(parse.base).toBeDefined();
expect(parse.sup).toBeDefined();
expect(parse.sub).toBeUndefined();
});
it("should produce supsubs for subscript", function() {
const parse = getParsed`x_3`[0];
expect(parse.type).toBe("supsub");
expect(parse.value.base).toBeDefined();
expect(parse.value.sub).toBeDefined();
expect(parse.value.sup).toBeUndefined();
expect(parse.base).toBeDefined();
expect(parse.sub).toBeDefined();
expect(parse.sup).toBeUndefined();
});
it("should produce supsubs for ^_", function() {
const parse = getParsed`x^2_3`[0];
expect(parse.type).toBe("supsub");
expect(parse.value.base).toBeDefined();
expect(parse.value.sup).toBeDefined();
expect(parse.value.sub).toBeDefined();
expect(parse.base).toBeDefined();
expect(parse.sup).toBeDefined();
expect(parse.sub).toBeDefined();
});
it("should produce supsubs for _^", function() {
const parse = getParsed`x_3^2`[0];
expect(parse.type).toBe("supsub");
expect(parse.value.base).toBeDefined();
expect(parse.value.sup).toBeDefined();
expect(parse.value.sub).toBeDefined();
expect(parse.base).toBeDefined();
expect(parse.sup).toBeDefined();
expect(parse.sub).toBeDefined();
});
it("should produce the same thing regardless of order", function() {
@@ -304,10 +304,10 @@ describe("A parser with limit controls", function() {
"of the preceding op node", function() {
let parsedInput = getParsed`\int\nolimits\limits_2^2`;
expect(parsedInput[0].value.base.value.limits).toBe(true);
expect(parsedInput[0].base.limits).toBe(true);
parsedInput = getParsed`\int\limits_2\nolimits^2`;
expect(parsedInput[0].value.base.value.limits).toBe(false);
expect(parsedInput[0].base.limits).toBe(false);
});
});