diff --git a/src/Options.js b/src/Options.js index 00dcf4f67..a3febb45b 100644 --- a/src/Options.js +++ b/src/Options.js @@ -14,41 +14,82 @@ * as the parentStyle and parentSize of the new options class, so parent * handling is taken care of automatically. */ -function Options(style, size, color, parentStyle, parentSize) { - this.style = style; - this.color = color; - this.size = size; +function Options(data) { + this.style = data.style; + this.color = data.color; + this.size = data.size; + this.phantom = data.phantom; - if (parentStyle === undefined) { - parentStyle = style; + if (data.parentStyle === undefined) { + this.parentStyle = data.style; + } else { + this.parentStyle = data.parentStyle; } - this.parentStyle = parentStyle; - if (parentSize === undefined) { - parentSize = size; + if (data.parentSize === undefined) { + this.parentSize = data.size; + } else { + this.parentSize = data.parentSize; } - this.parentSize = parentSize; } +/** + * Returns a new options object with the same properties as "this". Properties + * from "extension" will be copied to the new options object. + */ +Options.prototype.extend = function(extension) { + var data = { + style: this.style, + size: this.size, + color: this.color, + parentStyle: this.style, + parentSize: this.size, + phantom: this.phantom + }; + + for (var key in extension) { + if (extension.hasOwnProperty(key)) { + data[key] = extension[key]; + } + } + + return new Options(data); +}; + /** * Create a new options object with the given style. */ Options.prototype.withStyle = function(style) { - return new Options(style, this.size, this.color, this.style, this.size); + return this.extend({ + style: style + }); }; /** * Create a new options object with the given size. */ Options.prototype.withSize = function(size) { - return new Options(this.style, size, this.color, this.style, this.size); + return this.extend({ + size: size + }); }; /** * Create a new options object with the given color. */ Options.prototype.withColor = function(color) { - return new Options(this.style, this.size, color, this.style, this.size); + return this.extend({ + color: color + }); +}; + +/** + * Create a new options object with "phantom" set to true. + */ +Options.prototype.withPhantom = function() { + return this.extend({ + phantom: true + }); }; /** @@ -56,8 +97,7 @@ Options.prototype.withColor = function(color) { * used so that parent style and size changes are handled correctly. */ Options.prototype.reset = function() { - return new Options( - this.style, this.size, this.color, this.style, this.size); + return this.extend({}); }; /** @@ -79,7 +119,11 @@ var colorMap = { * `colorMap`. */ Options.prototype.getColor = function() { - return colorMap[this.color] || this.color; + if (this.phantom) { + return "transparent"; + } else { + return colorMap[this.color] || this.color; + } }; module.exports = Options; diff --git a/src/buildHTML.js b/src/buildHTML.js index 0512767b7..8a4fe9e4a 100644 --- a/src/buildHTML.js +++ b/src/buildHTML.js @@ -175,7 +175,7 @@ var groupTypes = { // things at the end of a \color group. Note that we don't use the same // logic for ordgroups (which count as ords). var prevAtom = prev; - while (prevAtom && prevAtom.type == "color") { + while (prevAtom && prevAtom.type === "color") { var atoms = prevAtom.value.value; prevAtom = atoms[atoms.length - 1]; } @@ -433,15 +433,15 @@ var groupTypes = { // Rule 15d var axisHeight = fontMetrics.metrics.axisHeight; - if ((numShift - numer.depth) - (axisHeight + 0.5 * ruleWidth) - < clearance) { + if ((numShift - numer.depth) - (axisHeight + 0.5 * ruleWidth) < + clearance) { numShift += clearance - ((numShift - numer.depth) - (axisHeight + 0.5 * ruleWidth)); } - if ((axisHeight - 0.5 * ruleWidth) - (denom.height - denomShift) - < clearance) { + if ((axisHeight - 0.5 * ruleWidth) - (denom.height - denomShift) < + clearance) { denomShift += clearance - ((axisHeight - 0.5 * ruleWidth) - (denom.height - denomShift)); @@ -1065,6 +1065,18 @@ var groupTypes = { } else { return accentWrap; } + }, + + phantom: function(group, options, prev) { + var elements = buildExpression( + group.value.value, + options.withPhantom(), + prev + ); + + // \phantom isn't supposed to affect the elements it contains. + // See "color" for more details. + return new buildCommon.makeFragment(elements); } }; @@ -1121,7 +1133,10 @@ var buildHTML = function(tree, settings) { } // Setup the default options - var options = new Options(startStyle, "size5", ""); + var options = new Options({ + style: startStyle, + size: "size5" + }); // Build the expression contained in the tree var expression = buildExpression(tree, options); diff --git a/src/buildMathML.js b/src/buildMathML.js index 3fb89b047..129b26d60 100644 --- a/src/buildMathML.js +++ b/src/buildMathML.js @@ -374,6 +374,11 @@ var groupTypes = { node.setAttribute("width", "0px"); return node; + }, + + phantom: function(group, options, prev) { + var inner = buildExpression(group.value.value); + return new mathMLTree.MathNode("mphantom", inner); } }; diff --git a/src/functions.js b/src/functions.js index 1c3e9b0de..aa8b8eb53 100644 --- a/src/functions.js +++ b/src/functions.js @@ -164,6 +164,23 @@ var functions = { type: "katex" }; } + }, + + "\\phantom": { + numArgs: 1, + handler: function(func, body) { + var inner; + if (body.type === "ordgroup") { + inner = body.value; + } else { + inner = [body]; + } + + return { + type: "phantom", + value: inner + }; + } } }; diff --git a/test/katex-spec.js b/test/katex-spec.js index 6aab20d7d..2042db983 100644 --- a/test/katex-spec.js +++ b/test/katex-spec.js @@ -569,7 +569,6 @@ describe("An over parser", function() { describe("A sizing parser", function() { var sizeExpression = "\\Huge{x}\\small{x}"; - var nestedSizeExpression = "\\Huge{\\small{x}}"; it("should not fail", function() { expect(sizeExpression).toParse(); @@ -1146,6 +1145,45 @@ describe("An accent builder", function() { }); }); +describe("A phantom parser", function() { + it("should not fail", function() { + expect("\\phantom{x}").toParse(); + expect("\\phantom{x^2}").toParse(); + expect("\\phantom{x}^2").toParse(); + expect("\\phantom x").toParse(); + }); + + it("should build a phantom node", function() { + var parse = getParsed("\\phantom{x}")[0]; + + expect(parse.type).toMatch("phantom"); + expect(parse.value.value).toBeDefined(); + }); +}); + +describe("A phantom builder", function() { + it("should not fail", function() { + expect("\\phantom{x}").toBuild(); + expect("\\phantom{x^2}").toBuild(); + expect("\\phantom{x}^2").toBuild(); + expect("\\phantom x").toBuild(); + }); + + it("should make the children transparent", function() { + var children = getBuilt("\\phantom{x+1}")[0].children; + expect(children[0].style.color).toBe("transparent"); + expect(children[1].style.color).toBe("transparent"); + expect(children[2].style.color).toBe("transparent"); + }); + + it("should make all descendants transparent", function() { + var children = getBuilt("\\phantom{x+\\blue{1}}")[0].children; + expect(children[0].style.color).toBe("transparent"); + expect(children[1].style.color).toBe("transparent"); + expect(children[2].children[0].style.color).toBe("transparent"); + }); +}); + describe("A parser error", function () { it("should report the position of an error", function () { try { @@ -1218,4 +1256,9 @@ describe("A MathML builder", function() { var textop = getMathML("\\sin").children[0].children[0]; expect(textop.children[0].type).toEqual("mi"); }); + + it("should generate a node for \\phantom", function() { + var phantom = getMathML("\\phantom{x}").children[0].children[0]; + expect(phantom.children[0].type).toEqual("mphantom"); + }); }); diff --git a/test/screenshotter/images/Phantom-firefox.png b/test/screenshotter/images/Phantom-firefox.png new file mode 100644 index 000000000..363cd2463 Binary files /dev/null and b/test/screenshotter/images/Phantom-firefox.png differ diff --git a/test/screenshotter/ss_data.json b/test/screenshotter/ss_data.json index 54cac70ae..62ff0cb68 100644 --- a/test/screenshotter/ss_data.json +++ b/test/screenshotter/ss_data.json @@ -21,6 +21,7 @@ "NullDelimiterInteraction": "http://localhost:7936/test/screenshotter/test.html?m=a \\bigl. + 2 \\quad \\left. + a \\right)", "OpLimits": "http://localhost:7936/test/screenshotter/test.html?m={\\sin_2^2 \\lim_2^2 \\int_2^2 \\sum_2^2}{\\displaystyle \\lim_2^2 \\int_2^2 \\intop_2^2 \\sum_2^2}", "Overline": "http://localhost:7936/test/screenshotter/test.html?m=\\overline{x}\\overline{x}\\overline{x^{x^{x^x}}} \\blue{\\overline{y}}", + "Phantom": "http://localhost:7936/test/screenshotter/test.html?m=\\dfrac{1+\\phantom{x^{\\blue{2}}} = x}{1+x^{\\blue{2}} = x}", "PrimeSpacing": "http://localhost:7936/test/screenshotter/test.html?m=f'+f_2'+f^{f'}", "RlapBug": "http://localhost:7936/test/screenshotter/test.html?m=\\frac{\\rlap{x}}{2}", "Rule": "http://localhost:7936/test/screenshotter/test.html?m=\\rule{1em}{0.5em}\\rule{1ex}{2ex}\\rule{1em}{1ex}\\rule{1em}{0.431ex}",