Add square roots (\sqrt)

Summary:
Follow the TeXbook instructions on how to construct square roots. Using
makeCustomSizedDelim, this becomes nearly trivial.

Test Plan:
 - Make sure normal tests work
 - Make sure the new huxley test looks good, and other huxley tests haven't changed.

Reviewers: alpert

Reviewed By: alpert

Differential Revision: http://phabricator.khanacademy.org/D12918
This commit is contained in:
Emily Eisenberg 2014-09-06 15:08:23 -07:00
parent 5a94faac9e
commit 925c96dbe2
9 changed files with 164 additions and 2 deletions

View File

@ -535,6 +535,19 @@ Parser.prototype.parseNucleus = function(pos, mode) {
this.lexer, nucleus.position
);
}
} else if (mode === "math" && nucleus.type === "\\sqrt") {
// If this is a square root, parse its argument and return
var group = this.parseGroup(nucleus.position, mode);
if (group) {
return new ParseResult(
new ParseNode("sqrt", group, mode),
group.position);
} else {
throw new ParseError("Expected group after '" +
nucleus.type + "'",
this.lexer, nucleus.position
);
}
} else if (mode === "math" && nucleus.type === "\\rule") {
// Parse the width of the rule
var widthGroup = this.parseSizeGroup(nucleus.position, mode);

View File

@ -38,7 +38,8 @@ var groupToType = {
katex: "mord",
overline: "mord",
rule: "mord",
leftright: "minner"
leftright: "minner",
sqrt: "mord"
};
var getTypeOfGroup = function(group) {
@ -429,6 +430,68 @@ var groupTypes = {
["katex-logo"], [k, a, t, e, x], options.getColor());
},
sqrt: function(group, options, prev) {
var innerGroup = buildGroup(group.value.result,
options.withStyle(options.style.cramp()));
var fontSizer = buildCommon.makeFontSizer(
options, Math.max(innerGroup.maxFontSize, 1.0));
// The theta variable in the TeXbook
var lineWidth = fontMetrics.metrics.defaultRuleThickness;
var lineInner =
makeSpan([options.style.reset(), Style.TEXT.cls(), "line"]);
lineInner.maxFontSize = 1.0;
var line = makeSpan(["sqrt-line"], [fontSizer, lineInner]);
var inner = makeSpan(["sqrt-inner"], [fontSizer, innerGroup]);
var fixIE = makeSpan(
["fix-ie"], [fontSizer, new domTree.textNode("\u00a0")]);
var theta = fontMetrics.metrics.defaultRuleThickness /
options.style.sizeMultiplier;
var phi = theta;
if (options.style.id < Style.TEXT.id) {
phi = fontMetrics.metrics.xHeight;
}
var psi = theta + phi / 4;
var innerHeight =
(inner.height + inner.depth) * options.style.sizeMultiplier;
var minDelimiterHeight = innerHeight + psi + theta;
var delim = makeSpan(["sqrt-sign"], [
delimiter.customSizedDelim("\\surd", minDelimiterHeight,
false, options, group.mode)]);
var delimDepth = delim.height + delim.depth;
if (delimDepth > inner.height + inner.depth + psi) {
psi = (psi + delimDepth - inner.height - inner.depth) / 2;
}
delim.style.top = (-inner.height - psi + delim.height - theta) + "em";
line.style.top = (-inner.height - psi) + "em";
line.height = inner.height + psi + 2 * theta;
// We add a special case here, because even when `inner` is empty, we
// still get a line. So, we use a simple heuristic to decide if we
// should omit the body entirely. (note this doesn't work for something
// like `\sqrt{\rlap{x}}`, but if someone is doing that they deserve for
// it not to work.
var body;
if (inner.height === 0 && inner.depth === 0) {
body = makeSpan();
} else {
body = makeSpan(["sqrt-body"], [line, inner, fixIE]);
}
return makeSpan(["sqrt", "mord"], [delim, body]);
},
overline: function(group, options, prev) {
var innerGroup = buildGroup(group.value.result,
options.withStyle(options.style.cramp()));

View File

@ -191,6 +191,12 @@ var makeStackedDelim = function(delim, heightTotal, center, options, mode) {
bottom = "\u23ad";
repeat = "\u23aa";
font = "Size4-Regular";
} else if (delim === "\\surd") {
top = "\ue001";
bottom = "\u23b7";
repeat = "\ue000";
font = "Size4-Regular";
overlap = true;
}
// Get the metrics of the three sections
@ -312,7 +318,8 @@ var normalDelimiters = [
"(", ")", "[", "\\lbrack", "]", "\\rbrack",
"\\{", "\\lbrace", "\\}", "\\rbrace",
"\\lfloor", "\\rfloor", "\\lceil", "\\rceil",
"<", ">", "\\langle", "\\rangle", "/", "\\backslash"
"<", ">", "\\langle", "\\rangle", "/", "\\backslash",
"\\surd"
];
var stackDelimiters = [

View File

@ -350,6 +350,46 @@ big parens
}
}
.sqrt {
> .sqrt-sign {
position: relative;
}
> .sqrt-body {
.baseline-align-hack-outer;
> .sqrt-line,
> .sqrt-inner,
> .fix-ie {
.baseline-align-hack-middle;
position: relative;
> span {
.baseline-align-hack-inner;
}
}
> .sqrt-line > .line {
width: 100%;
&:before {
border-bottom-style: solid;
border-bottom-width: 1px;
content: "";
display: block;
}
&:after {
border-bottom-style: solid;
border-bottom-width: 0.04em;
content: "";
display: block;
margin-top: -1px;
}
}
}
}
.sizing, .fontsize-ensurer {
display: inline-block;

View File

@ -298,6 +298,11 @@ var symbols = {
group: "bin",
replace: "\u00d7"
},
"\\surd": {
font: "main",
group: "textord",
replace: "\u221a"
},
"(": {
font: "main",
group: "open"

View File

@ -153,5 +153,11 @@
"name": "NullDelimiterInteraction",
"screenSize": [1024, 768],
"url": "http://localhost:7936/test/huxley/test.html?m=a \\bigl. + 2 \\quad \\left. + a \\right)"
},
{
"name": "Sqrt",
"screenSize": [1024, 768],
"url": "http://localhost:7936/test/huxley/test.html?m=\\sqrt{\\sqrt{\\sqrt{x}}}_{\\sqrt{\\sqrt{x}}}^{\\sqrt{\\sqrt{\\sqrt{x}}}^{\\sqrt{\\sqrt{\\sqrt{x}}}}}"
}
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View File

@ -0,0 +1,5 @@
[
{
"action": "screenshot"
}
]

View File

@ -798,3 +798,26 @@ describe("A left/right parser", function() {
}).not.toThrow();
});
});
describe("A sqrt parser", function() {
var sqrt = "\\sqrt{x}";
var missingGroup = "\\sqrt";
it("should parse square roots", function() {
expect(function() {
parseTree(sqrt);
}).not.toThrow();
});
it("should error when there is no group", function() {
expect(function() {
parseTree(missingGroup);
}).toThrow();
});
it("should produce sqrts", function() {
var parse = parseTree(sqrt)[0];
expect(parse.type).toMatch("sqrt");
});
});