commit e449b2d61ac985739d620538225cec0d674e9dc2
parent 7433638fdaccdfbe0c5b661844c8e99c393a7d25
Author: Eddie Kohler <ekohler@gmail.com>
Date: Thu, 8 Dec 2016 11:28:48 -0500
Handle \middle.
Diffstat:
8 files changed, 106 insertions(+), 20 deletions(-)
diff --git a/src/Parser.js b/src/Parser.js
@@ -52,6 +52,8 @@ function Parser(input, settings) {
this.gullet = new MacroExpander(input, settings.macros);
// Store the settings for use in parsing
this.settings = settings;
+ // Count leftright depth (for \middle errors)
+ this.leftrightDepth = 0;
}
var ParseNode = parseData.ParseNode;
@@ -411,7 +413,9 @@ Parser.prototype.parseImplicitGroup = function() {
// Parse the entire left function (including the delimiter)
var left = this.parseFunction(start);
// Parse out the implicit body
+ ++this.leftrightDepth;
body = this.parseExpression(false);
+ --this.leftrightDepth;
// Check the next token
this.expect("\\right", false);
var right = this.parseFunction();
diff --git a/src/buildHTML.js b/src/buildHTML.js
@@ -1186,11 +1186,16 @@ groupTypes.leftright = function(group, options) {
var innerHeight = 0;
var innerDepth = 0;
+ var hadMiddle = false;
// Calculate its height and depth
for (var i = 0; i < inner.length; i++) {
- innerHeight = Math.max(inner[i].height, innerHeight);
- innerDepth = Math.max(inner[i].depth, innerDepth);
+ if (inner[i].isMiddle) {
+ hadMiddle = true;
+ } else {
+ innerHeight = Math.max(inner[i].height, innerHeight);
+ innerDepth = Math.max(inner[i].depth, innerDepth);
+ }
}
var style = options.style;
@@ -1215,6 +1220,18 @@ groupTypes.leftright = function(group, options) {
// Add it to the beginning of the expression
inner.unshift(leftDelim);
+ // Handle middle delimiters
+ if (hadMiddle) {
+ for (i = 1; i < inner.length; i++) {
+ if (inner[i].isMiddle) {
+ // Apply the options that were active when \middle was called
+ inner[i] = delimiter.leftRightDelim(
+ inner[i].isMiddle.value, innerHeight, innerDepth,
+ inner[i].isMiddle.options, group.mode, []);
+ }
+ }
+ }
+
var rightDelim;
// Same for the right delimiter
if (group.value.right === ".") {
@@ -1231,6 +1248,19 @@ groupTypes.leftright = function(group, options) {
["minner", style.cls()], inner, options);
};
+groupTypes.middle = function(group, options) {
+ var middleDelim;
+ if (group.value.value === ".") {
+ middleDelim = makeNullDelimiter(options, []);
+ } else {
+ middleDelim = delimiter.sizedDelim(
+ group.value.value, 1, options,
+ group.mode, []);
+ middleDelim.isMiddle = {value: group.value.value, options: options};
+ }
+ return middleDelim;
+};
+
groupTypes.rule = function(group, options) {
// Make an empty span for the rule
var rule = makeSpan(["mord", "rule"], [], options);
diff --git a/src/buildMathML.js b/src/buildMathML.js
@@ -285,6 +285,13 @@ groupTypes.leftright = function(group, options) {
return outerNode;
};
+groupTypes.middle = function(group, options) {
+ var middleNode = new mathMLTree.MathNode(
+ "mo", [makeText(group.value.middle, group.mode)]);
+ middleNode.setAttribute("fence", "true");
+ return middleNode;
+};
+
groupTypes.accent = function(group, options) {
var accentNode = new mathMLTree.MathNode(
"mo", [makeText(group.value.accent, group.mode)]);
diff --git a/src/functions.js b/src/functions.js
@@ -496,37 +496,61 @@ defineFunction(["\\llap", "\\rlap"], {
});
// Delimiter functions
+var checkDelimiter = function(delim, context) {
+ if (utils.contains(delimiters, delim.value)) {
+ return delim;
+ } else {
+ throw new ParseError(
+ "Invalid delimiter: '" + delim.value + "' after '" +
+ context.funcName + "'", delim);
+ }
+};
+
defineFunction([
"\\bigl", "\\Bigl", "\\biggl", "\\Biggl",
"\\bigr", "\\Bigr", "\\biggr", "\\Biggr",
"\\bigm", "\\Bigm", "\\biggm", "\\Biggm",
"\\big", "\\Big", "\\bigg", "\\Bigg",
+], {
+ numArgs: 1,
+}, function(context, args) {
+ var delim = checkDelimiter(args[0], context);
+
+ return {
+ type: "delimsizing",
+ size: delimiterSizes[context.funcName].size,
+ mclass: delimiterSizes[context.funcName].mclass,
+ value: delim.value,
+ };
+});
+
+defineFunction([
"\\left", "\\right",
], {
numArgs: 1,
}, function(context, args) {
- var delim = args[0];
- if (!utils.contains(delimiters, delim.value)) {
- throw new ParseError(
- "Invalid delimiter: '" + delim.value + "' after '" +
- context.funcName + "'", delim);
- }
+ var delim = checkDelimiter(args[0], context);
// \left and \right are caught somewhere in Parser.js, which is
// why this data doesn't match what is in buildHTML.
- if (context.funcName === "\\left" || context.funcName === "\\right") {
- return {
- type: "leftright",
- value: delim.value,
- };
- } else {
- return {
- type: "delimsizing",
- size: delimiterSizes[context.funcName].size,
- mclass: delimiterSizes[context.funcName].mclass,
- value: delim.value,
- };
+ return {
+ type: "leftright",
+ value: delim.value,
+ };
+});
+
+defineFunction("\\middle", {
+ numArgs: 1,
+}, function(context, args) {
+ var delim = checkDelimiter(args[0], context);
+ if (!context.parser.leftrightDepth) {
+ throw new ParseError("\\middle without preceding \\left", delim);
}
+
+ return {
+ type: "middle",
+ value: delim.value,
+ };
});
// Sizing functions (handled in Parser.js explicitly, hence no handler)
diff --git a/test/katex-spec.js b/test/katex-spec.js
@@ -1042,6 +1042,26 @@ describe("A left/right parser", function() {
var normalEmpty = "\\Bigl .";
expect(normalEmpty).toParse();
});
+
+ it("should handle \\middle", function() {
+ var normalMiddle = "\\left( \\dfrac{x}{y} \\middle| \\dfrac{y}{z} \\right)";
+ expect(normalMiddle).toParse();
+ });
+
+ it("should handle multiple \\middles", function() {
+ var multiMiddle = "\\left( \\dfrac{x}{y} \\middle| \\dfrac{y}{z} \\middle/ \\dfrac{z}{q} \\right)";
+ expect(multiMiddle).toParse();
+ });
+
+ it("should handle nested \\middles", function() {
+ var nestedMiddle = "\\left( a^2 \\middle| \\left( b \\middle/ c \\right) \\right)";
+ expect(nestedMiddle).toParse();
+ });
+
+ it("should error when \\middle is not in \\left...\\right", function() {
+ var unmatchedMiddle = "(\\middle|\\dfrac{x}{y})";
+ expect(unmatchedMiddle).toNotParse();
+ });
});
describe("A begin/end parser", function() {
diff --git a/test/screenshotter/images/LeftRightMiddle-chrome.png b/test/screenshotter/images/LeftRightMiddle-chrome.png
Binary files differ.
diff --git a/test/screenshotter/images/LeftRightMiddle-firefox.png b/test/screenshotter/images/LeftRightMiddle-firefox.png
Binary files differ.
diff --git a/test/screenshotter/ss_data.yaml b/test/screenshotter/ss_data.yaml
@@ -72,6 +72,7 @@ Kern:
Lap: ab\llap{f}cd\rlap{g}h
LeftRight: \left( x^2 \right) \left\{ x^{x^{x^{x^x}}} \right.
LeftRightListStyling: a+\left(x+y\right)-x
+LeftRightMiddle: \left( x^2 \middle/ \right) \left\{ x^{x^{x^{x^x}}} \middle/ y \right.
LeftRightStyleSizing: |
+\left\{\rule{0.1em}{1em}\right.
x^{+\left\{\rule{0.1em}{1em}\right.