commit 925c96dbe24080bfc7b98a08ecdc4110a9fa01a6
parent 5a94faac9eefc5f0b1bd21806a0994a8366c4ae9
Author: Emily Eisenberg <emily@khanacademy.org>
Date: Sat, 6 Sep 2014 15:08:23 -0700
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
Diffstat:
9 files changed, 164 insertions(+), 2 deletions(-)
diff --git a/Parser.js b/Parser.js
@@ -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);
diff --git a/buildTree.js b/buildTree.js
@@ -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()));
diff --git a/delimiter.js b/delimiter.js
@@ -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 = [
diff --git a/static/katex.less b/static/katex.less
@@ -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;
diff --git a/symbols.js b/symbols.js
@@ -298,6 +298,11 @@ var symbols = {
group: "bin",
replace: "\u00d7"
},
+ "\\surd": {
+ font: "main",
+ group: "textord",
+ replace: "\u221a"
+ },
"(": {
font: "main",
group: "open"
diff --git a/test/huxley/Huxleyfile.json b/test/huxley/Huxleyfile.json
@@ -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}}}}}"
}
]
diff --git a/test/huxley/Sqrt.hux/firefox-1.png b/test/huxley/Sqrt.hux/firefox-1.png
Binary files differ.
diff --git a/test/huxley/Sqrt.hux/record.json b/test/huxley/Sqrt.hux/record.json
@@ -0,0 +1,5 @@
+[
+ {
+ "action": "screenshot"
+ }
+]
diff --git a/test/katex-tests.js b/test/katex-tests.js
@@ -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");
+ });
+});