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:
parent
5a94faac9e
commit
925c96dbe2
13
Parser.js
13
Parser.js
|
@ -535,6 +535,19 @@ Parser.prototype.parseNucleus = function(pos, mode) {
|
||||||
this.lexer, nucleus.position
|
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") {
|
} else if (mode === "math" && nucleus.type === "\\rule") {
|
||||||
// Parse the width of the rule
|
// Parse the width of the rule
|
||||||
var widthGroup = this.parseSizeGroup(nucleus.position, mode);
|
var widthGroup = this.parseSizeGroup(nucleus.position, mode);
|
||||||
|
|
65
buildTree.js
65
buildTree.js
|
@ -38,7 +38,8 @@ var groupToType = {
|
||||||
katex: "mord",
|
katex: "mord",
|
||||||
overline: "mord",
|
overline: "mord",
|
||||||
rule: "mord",
|
rule: "mord",
|
||||||
leftright: "minner"
|
leftright: "minner",
|
||||||
|
sqrt: "mord"
|
||||||
};
|
};
|
||||||
|
|
||||||
var getTypeOfGroup = function(group) {
|
var getTypeOfGroup = function(group) {
|
||||||
|
@ -429,6 +430,68 @@ var groupTypes = {
|
||||||
["katex-logo"], [k, a, t, e, x], options.getColor());
|
["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) {
|
overline: function(group, options, prev) {
|
||||||
var innerGroup = buildGroup(group.value.result,
|
var innerGroup = buildGroup(group.value.result,
|
||||||
options.withStyle(options.style.cramp()));
|
options.withStyle(options.style.cramp()));
|
||||||
|
|
|
@ -191,6 +191,12 @@ var makeStackedDelim = function(delim, heightTotal, center, options, mode) {
|
||||||
bottom = "\u23ad";
|
bottom = "\u23ad";
|
||||||
repeat = "\u23aa";
|
repeat = "\u23aa";
|
||||||
font = "Size4-Regular";
|
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
|
// Get the metrics of the three sections
|
||||||
|
@ -312,7 +318,8 @@ var normalDelimiters = [
|
||||||
"(", ")", "[", "\\lbrack", "]", "\\rbrack",
|
"(", ")", "[", "\\lbrack", "]", "\\rbrack",
|
||||||
"\\{", "\\lbrace", "\\}", "\\rbrace",
|
"\\{", "\\lbrace", "\\}", "\\rbrace",
|
||||||
"\\lfloor", "\\rfloor", "\\lceil", "\\rceil",
|
"\\lfloor", "\\rfloor", "\\lceil", "\\rceil",
|
||||||
"<", ">", "\\langle", "\\rangle", "/", "\\backslash"
|
"<", ">", "\\langle", "\\rangle", "/", "\\backslash",
|
||||||
|
"\\surd"
|
||||||
];
|
];
|
||||||
|
|
||||||
var stackDelimiters = [
|
var stackDelimiters = [
|
||||||
|
|
|
@ -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 {
|
.sizing, .fontsize-ensurer {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
|
||||||
|
|
|
@ -298,6 +298,11 @@ var symbols = {
|
||||||
group: "bin",
|
group: "bin",
|
||||||
replace: "\u00d7"
|
replace: "\u00d7"
|
||||||
},
|
},
|
||||||
|
"\\surd": {
|
||||||
|
font: "main",
|
||||||
|
group: "textord",
|
||||||
|
replace: "\u221a"
|
||||||
|
},
|
||||||
"(": {
|
"(": {
|
||||||
font: "main",
|
font: "main",
|
||||||
group: "open"
|
group: "open"
|
||||||
|
|
|
@ -153,5 +153,11 @@
|
||||||
"name": "NullDelimiterInteraction",
|
"name": "NullDelimiterInteraction",
|
||||||
"screenSize": [1024, 768],
|
"screenSize": [1024, 768],
|
||||||
"url": "http://localhost:7936/test/huxley/test.html?m=a \\bigl. + 2 \\quad \\left. + a \\right)"
|
"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}}}}}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
BIN
test/huxley/Sqrt.hux/firefox-1.png
Normal file
BIN
test/huxley/Sqrt.hux/firefox-1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 30 KiB |
5
test/huxley/Sqrt.hux/record.json
Normal file
5
test/huxley/Sqrt.hux/record.json
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"action": "screenshot"
|
||||||
|
}
|
||||||
|
]
|
|
@ -798,3 +798,26 @@ describe("A left/right parser", function() {
|
||||||
}).not.toThrow();
|
}).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");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
Loading…
Reference in New Issue
Block a user