mirror of
https://github.com/Smaug123/KaTeX
synced 2025-10-06 19:58:40 +00:00
Flatten ParseNodes: "supsub" and related ("accent", "accentUnder", "horizBrace", "xarrow", "op"). (#1542)
This commit is contained in:
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@@ -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,
|
||||
|
@@ -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;
|
||||
|
@@ -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]);
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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");
|
||||
|
@@ -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,
|
||||
|
@@ -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 {
|
||||
|
@@ -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>⁡</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,
|
||||
|
@@ -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 {
|
||||
|
@@ -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") {
|
||||
|
@@ -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";
|
||||
|
@@ -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,
|
||||
|},
|
||||
};
|
||||
|
||||
|
@@ -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;
|
||||
|
@@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
|
Reference in New Issue
Block a user