Support \genfrac and \above (#1458)

* Support \genfrac and \above

* Accommodate delimiters inside groups

* Update screenshots take one`

* Update screenshots take two

* Fix flow error

* Fix lint error
This commit is contained in:
Ron Kok
2018-07-07 17:01:37 -07:00
committed by Kevin Barabash
parent 0fb71c8bf8
commit db585fae54
7 changed files with 207 additions and 7 deletions

View File

@@ -145,6 +145,7 @@ export type ParseNodeTypes = {
"size": {|
type: "size",
value: Measurement,
isBlank: boolean,
|},
"styling": {|
type: "styling",
@@ -244,6 +245,7 @@ export type ParseNodeTypes = {
leftDelim: ?string,
rightDelim: ?string,
size: StyleStr | "auto",
barSize: Measurement | null,
|},
"horizBrace": {|
type: "horizBrace",
@@ -259,6 +261,7 @@ export type ParseNodeTypes = {
"infix": {|
type: "infix",
replaceWith: string,
sizeNode?: ParseNode<"size">,
token: ?Token,
|},
"kern": {|

View File

@@ -248,7 +248,13 @@ export default class Parser {
denomNode = new ParseNode("ordgroup", denomBody, this.mode);
}
const node = this.callFunction(funcName, [numerNode, denomNode], []);
let node;
if (funcName === "\\\\abovefrac") {
node = this.callFunction(funcName,
[numerNode, body[overIndex], denomNode], []);
} else {
node = this.callFunction(funcName, [numerNode, denomNode], []);
}
return [node];
} else {
return body;
@@ -821,6 +827,7 @@ export default class Parser {
*/
parseSizeGroup(optional: boolean): ?ParsedArg {
let res;
let isBlank = false;
if (!optional && this.nextToken.text !== "{") {
res = this.parseRegexGroup(
/^[-+]? *(?:$|\d+|\d+\.\d*|\.\d*) *[a-z]{0,2} *$/, "size");
@@ -830,6 +837,13 @@ export default class Parser {
if (!res) {
return null;
}
if (!optional && res.text.length === 0) {
// Because we've tested for what is !optional, this block won't
// affect \kern, \hspace, etc. It will capture the mandatory arguments
// to \genfrac and \above.
res.text = "0pt"; // Enable \above{}
isBlank = true; // This is here specifically for \genfrac
}
const match = (/([-+]?) *(\d+(?:\.\d*)?|\.\d+) *([a-z]{2})/).exec(res.text);
if (!match) {
throw new ParseError("Invalid size: '" + res.text + "'", res);
@@ -844,6 +858,7 @@ export default class Parser {
return newArgument(new ParseNode("size", {
type: "size",
value: data,
isBlank: isBlank,
}, this.mode), res);
}

View File

@@ -4,10 +4,11 @@ import buildCommon from "../buildCommon";
import delimiter from "../delimiter";
import mathMLTree from "../mathMLTree";
import Style from "../Style";
import ParseNode from "../ParseNode";
import ParseNode, {assertNodeType, checkNodeType} from "../ParseNode";
import * as html from "../buildHTML";
import * as mml from "../buildMathML";
import {calculateSize} from "../units";
const htmlBuilder = (group, options) => {
// Fractions are handled in the TeXbook on pages 444-445, rules 15(a-e).
@@ -20,6 +21,10 @@ const htmlBuilder = (group, options) => {
style.size === Style.DISPLAY.size) {
// We're in a \tfrac but incoming style is displaystyle, so:
style = Style.TEXT;
} else if (group.value.size === "script") {
style = Style.SCRIPT;
} else if (group.value.size === "scriptscript") {
style = Style.SCRIPTSCRIPT;
}
const nstyle = style.fracNum();
@@ -45,7 +50,12 @@ const htmlBuilder = (group, options) => {
let ruleWidth;
let ruleSpacing;
if (group.value.hasBarLine) {
rule = buildCommon.makeLineSpan("frac-line", options);
if (group.value.barSize) {
ruleWidth = calculateSize(group.value.barSize, options);
rule = buildCommon.makeLineSpan("frac-line", options, ruleWidth);
} else {
rule = buildCommon.makeLineSpan("frac-line", options);
}
ruleWidth = rule.height;
ruleSpacing = rule.height;
} else {
@@ -174,6 +184,9 @@ const mathmlBuilder = (group, options) => {
if (!group.value.hasBarLine) {
node.setAttribute("linethickness", "0px");
} else if (group.value.barSize) {
const ruleWidth = calculateSize(group.value.barSize, options);
node.setAttribute("linethickness", ruleWidth + "em");
}
if (group.value.leftDelim != null || group.value.rightDelim != null) {
@@ -277,6 +290,7 @@ defineFunction({
leftDelim: leftDelim,
rightDelim: rightDelim,
size: size,
barSize: null,
}, parser.mode);
},
@@ -321,3 +335,137 @@ defineFunction({
}, parser.mode);
},
});
const stylArray = ["display", "text", "script", "scriptscript"];
const delimFromValue = function(delimString: string): string | null {
let delim = null;
if (delimString.length > 0) {
delim = delimString;
delim = delim === "." ? null : delim;
}
return delim;
};
defineFunction({
type: "genfrac",
names: ["\\genfrac"],
props: {
numArgs: 6,
greediness: 6,
argTypes: ["math", "math", "size", "text", "math", "math"],
},
handler: ({parser}, args) => {
const numer = args[4];
const denom = args[5];
// Look into the parse nodes to get the desired delimiters.
let leftNode = checkNodeType(args[0], "ordgroup");
if (leftNode) {
leftNode = assertNodeType(leftNode.value[0], "open");
} else {
leftNode = assertNodeType(args[0], "open");
}
const leftDelim = delimFromValue(leftNode.value);
let rightNode = checkNodeType(args[1], "ordgroup");
if (rightNode) {
rightNode = assertNodeType(rightNode.value[0], "close");
} else {
rightNode = assertNodeType(args[1], "close");
}
const rightDelim = delimFromValue(rightNode.value);
const barNode = assertNodeType(args[2], "size");
let hasBarLine;
let barSize = null;
if (barNode.value.isBlank) {
// \genfrac acts differently than \above.
// \genfrac treats an empty size group as a signal to use a
// standard bar size. \above would see size = 0 and omit the bar.
hasBarLine = true;
} else {
barSize = barNode.value.value;
hasBarLine = barSize.number > 0;
}
// Find out if we want displaystyle, textstyle, etc.
let size = "auto";
let styl = checkNodeType(args[3], "ordgroup");
if (styl) {
if (styl.value.length > 0) {
size = stylArray[Number(styl.value[0].value)];
}
} else {
styl = assertNodeType(args[3], "textord");
size = stylArray[Number(styl.value)];
}
return new ParseNode("genfrac", {
type: "genfrac",
numer: numer,
denom: denom,
continued: false,
hasBarLine: hasBarLine,
barSize: barSize,
leftDelim: leftDelim,
rightDelim: rightDelim,
size: size,
}, parser.mode);
},
htmlBuilder,
mathmlBuilder,
});
// \above is an infix fraction that also defines a fraction bar size.
defineFunction({
type: "infix",
names: ["\\above"],
props: {
numArgs: 1,
argTypes: ["size"],
infix: true,
},
handler({parser, funcName, token}, args) {
const sizeNode = assertNodeType(args[0], "size");
return new ParseNode("infix", {
type: "infix",
replaceWith: "\\\\abovefrac",
sizeNode: sizeNode,
token: token,
}, parser.mode);
},
});
defineFunction({
type: "genfrac",
names: ["\\\\abovefrac"],
props: {
numArgs: 3,
argTypes: ["math", "size", "math"],
},
handler: ({parser, funcName}, args) => {
const numer = args[0];
const infixNode = assertNodeType(args[1], "infix");
const sizeNode = assertNodeType(infixNode.value.sizeNode, "size");
const denom = args[2];
const barSize = sizeNode.value.value;
const hasBarLine = barSize.number > 0;
return new ParseNode("genfrac", {
type: "genfrac",
numer: numer,
denom: denom,
continued: false,
hasBarLine: hasBarLine,
barSize: barSize,
leftDelim: null,
rightDelim: null,
size: "auto",
}, parser.mode);
},
htmlBuilder,
mathmlBuilder,
});

View File

@@ -428,6 +428,8 @@ describe("A frac parser", function() {
const dfracExpression = "\\dfrac{x}{y}";
const tfracExpression = "\\tfrac{x}{y}";
const cfracExpression = "\\cfrac{x}{y}";
const genfrac1 = "\\genfrac ( ] {0.06em}{0}{a}{b+c}";
const genfrac2 = "\\genfrac ( ] {0.8pt}{}{a}{b+c}";
it("should not fail", function() {
expect(expression).toParse();
@@ -441,13 +443,15 @@ describe("A frac parser", function() {
expect(parse.value.denom).toBeDefined();
});
it("should also parse dfrac and tfrac", function() {
it("should also parse cfrac, dfrac, tfrac, and genfrac", function() {
expect(cfracExpression).toParse();
expect(dfracExpression).toParse();
expect(tfracExpression).toParse();
expect(genfrac1).toParse();
expect(genfrac2).toParse();
});
it("should parse dfrac and tfrac as fracs", function() {
it("should parse cfrac, dfrac, tfrac, and genfrac as fracs", function() {
const dfracParse = getParsed(dfracExpression)[0];
expect(dfracParse.type).toEqual("genfrac");
@@ -465,6 +469,24 @@ describe("A frac parser", function() {
expect(cfracParse.type).toEqual("genfrac");
expect(cfracParse.value.numer).toBeDefined();
expect(cfracParse.value.denom).toBeDefined();
const genfracParse = getParsed(genfrac1)[0];
expect(genfracParse.type).toEqual("genfrac");
expect(genfracParse.value.numer).toBeDefined();
expect(genfracParse.value.denom).toBeDefined();
expect(genfracParse.value.leftDelim).toBeDefined();
expect(genfracParse.value.rightDelim).toBeDefined();
});
it("should fail, given math as a line thickness to genfrac", function() {
const badGenFrac = "\\genfrac ( ] {b+c}{0}{a}{b+c}";
expect(badGenFrac).toNotParse();
});
it("should fail if genfrac is given less than 6 arguments", function() {
const badGenFrac = "\\genfrac ( ] {0.06em}{0}{a}";
expect(badGenFrac).toNotParse();
});
it("should parse atop", function() {
@@ -587,6 +609,17 @@ describe("An over/brace/brack parser", function() {
});
});
describe("A genfrac builder", function() {
it("should not fail", function() {
expect("\\frac{x}{y}").toBuild();
expect("\\dfrac{x}{y}").toBuild();
expect("\\tfrac{x}{y}").toBuild();
expect("\\cfrac{x}{y}").toBuild();
expect("\\genfrac ( ] {0.06em}{0}{a}{b+c}").toBuild();
expect("\\genfrac ( ] {0.8pt}{}{a}{b+c}").toBuild();
});
});
describe("A infix builder", function() {
it("should not fail", function() {
expect("a \\over b").toBuild();

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -116,7 +116,8 @@ ExtensibleArrows: |
FractionTest: |
\begin{array}{l}
\dfrac{a}{b}\frac{a}{b}\tfrac{a}{b}\;-\dfrac12\;1\tfrac12\;{1 \atop 2}\; \cfrac{1}{1+\cfrac{1}{x}} \\[2.5em]
{a \brace b} \; {a \brack b}
{a \brace b} \; {a \brack b} \; \genfrac \{ ]{0.8pt}{0}{a}{b}
\; {a \above1.0pt b}
\end{array}
Functions: \sin\cos\tan\ln\log
Gathered: |