mirror of
https://github.com/Smaug123/KaTeX
synced 2025-10-10 13:38:39 +00:00
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:
@@ -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": {|
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
|
@@ -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,
|
||||
});
|
||||
|
@@ -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 |
@@ -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: |
|
||||
|
Reference in New Issue
Block a user