Add support for \phantom
Summary: Using \phantom with non-phantom math in Perseus doesn't render to be the same size because \phantom uses MathJax and the non-phantom math uses KaTeX. Implementing \phantom in KaTeX should solve this alignment issue. Test Plan: [x] write (and run) unit tests [x] create (and run) screenshotter tests Reviewers: emily Reviewed By: emily Differential Revision: https://phabricator.khanacademy.org/D16720
This commit is contained in:
parent
51d751f96d
commit
39f5bcb042
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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 <mphantom> node for \\phantom", function() {
|
||||
var phantom = getMathML("\\phantom{x}").children[0].children[0];
|
||||
expect(phantom.children[0].type).toEqual("mphantom");
|
||||
});
|
||||
});
|
||||
|
|
BIN
test/screenshotter/images/Phantom-firefox.png
Normal file
BIN
test/screenshotter/images/Phantom-firefox.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
|
@ -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}",
|
||||
|
|
Loading…
Reference in New Issue
Block a user