Add an 'implicit group' parser, use with sizing
Summary: Implicit groups are objects that act like groups but don't have brackets around them. This is used for things like sizing functions or font-change functions that can occur in the middle of the group, but act like they apply to a group after them which stops when the current group stops. E.g. `Hello {world \Large hello} world` produces normal, normal, Large, normal text. (Note, I just came up with the name implicit group, I don't think this is actually how it is parsed in LaTeX but it fits nicely with the current parsing scheme and seems to work well). For now, apply this to the sizing functions (we don't have any other functions that act like this). Also note that this doesn't really do much practically because we limit sizing functions to only be on the top level of the expression, but it does let people do `\Large x` and get a large `x`, without having to add braces. Test Plan: - Run the tests, see they work - Make sure `abc \Large abc` looks correct Reviewers: alpert Reviewed By: alpert Differential Revision: http://phabricator.khanacademy.org/D10876
This commit is contained in:
parent
e46dd418b3
commit
02935f7dde
27
Options.js
27
Options.js
|
@ -1,42 +1,47 @@
|
|||
function Options(style, size, color, depth, parentStyle, parentSize) {
|
||||
function Options(style, size, color, deep, parentStyle, parentSize) {
|
||||
this.style = style;
|
||||
this.color = color;
|
||||
this.size = size;
|
||||
|
||||
// TODO(emily): Get rid of depth when we can actually use sizing everywhere
|
||||
if (!depth) {
|
||||
depth = 0;
|
||||
// TODO(emily): Get rid of deep when we can actually use sizing everywhere
|
||||
if (deep === undefined) {
|
||||
deep = false;
|
||||
}
|
||||
this.depth = depth;
|
||||
this.deep = deep;
|
||||
|
||||
if (!parentStyle) {
|
||||
if (parentStyle === undefined) {
|
||||
parentStyle = style;
|
||||
}
|
||||
this.parentStyle = parentStyle;
|
||||
|
||||
if (!parentSize) {
|
||||
if (parentSize === undefined) {
|
||||
parentSize = size;
|
||||
}
|
||||
this.parentSize = parentSize;
|
||||
}
|
||||
|
||||
Options.prototype.withStyle = function(style) {
|
||||
return new Options(style, this.size, this.color, this.depth + 1,
|
||||
return new Options(style, this.size, this.color, this.deep,
|
||||
this.style, this.size);
|
||||
};
|
||||
|
||||
Options.prototype.withSize = function(size) {
|
||||
return new Options(this.style, size, this.color, this.depth + 1,
|
||||
return new Options(this.style, size, this.color, this.deep,
|
||||
this.style, this.size);
|
||||
};
|
||||
|
||||
Options.prototype.withColor = function(color) {
|
||||
return new Options(this.style, this.size, color, this.depth + 1,
|
||||
return new Options(this.style, this.size, color, this.deep,
|
||||
this.style, this.size);
|
||||
};
|
||||
|
||||
Options.prototype.deepen = function() {
|
||||
return new Options(this.style, this.size, this.color, true,
|
||||
this.parentStyle, this.parentSize);
|
||||
};
|
||||
|
||||
Options.prototype.reset = function() {
|
||||
return new Options(this.style, this.size, this.color, this.depth + 1,
|
||||
return new Options(this.style, this.size, this.color, this.deep,
|
||||
this.style, this.size);
|
||||
};
|
||||
|
||||
|
|
35
Parser.js
35
Parser.js
|
@ -201,6 +201,20 @@ Parser.prototype.parseGroup = function(pos, mode) {
|
|||
}
|
||||
};
|
||||
|
||||
// Parses an implicit group, which is a group that starts where you want it, and
|
||||
// ends right before a higher explicit group ends, or at EOL. It is used for
|
||||
// functions that appear to affect the current style, like \Large or \textrm,
|
||||
// where instead of keeping a style we just pretend that there is an implicit
|
||||
// grouping after it until the end of the group.
|
||||
Parser.prototype.parseImplicitGroup = function(pos, mode) {
|
||||
// Since parseExpression already ends where we want it to, we just need to
|
||||
// call that and it does what we want.
|
||||
var expression = this.parseExpression(pos, mode);
|
||||
return new ParseResult(
|
||||
new ParseNode("ordgroup", expression.result, mode),
|
||||
expression.position);
|
||||
};
|
||||
|
||||
// Parses a custom color group, which looks like "{#ffffff}"
|
||||
Parser.prototype.parseColorGroup = function(pos, mode) {
|
||||
var start = this.lexer.lex(pos, mode);
|
||||
|
@ -322,20 +336,13 @@ Parser.prototype.parseNucleus = function(pos, mode) {
|
|||
}
|
||||
} else if (mode === "math" && utils.contains(sizeFuncs, nucleus.type)) {
|
||||
// If this is a size function, parse its argument and return
|
||||
var group = this.parseGroup(nucleus.position, mode);
|
||||
if (group) {
|
||||
return new ParseResult(
|
||||
new ParseNode("sizing", {
|
||||
size: "size" + (utils.indexOf(sizeFuncs, nucleus.type) + 1),
|
||||
value: group.result
|
||||
}, mode),
|
||||
group.position);
|
||||
} else {
|
||||
throw new ParseError(
|
||||
"Expected group after '" + nucleus.text + "'",
|
||||
this.lexer, nucleus.position
|
||||
);
|
||||
}
|
||||
var group = this.parseImplicitGroup(nucleus.position, mode);
|
||||
return new ParseResult(
|
||||
new ParseNode("sizing", {
|
||||
size: "size" + (utils.indexOf(sizeFuncs, nucleus.type) + 1),
|
||||
value: group.result
|
||||
}, mode),
|
||||
group.position);
|
||||
} else if (mode === "math" && utils.contains(namedFns, nucleus.type)) {
|
||||
// If this is a named function, just return it plain
|
||||
return new ParseResult(
|
||||
|
|
16
buildTree.js
16
buildTree.js
|
@ -139,7 +139,7 @@ var groupTypes = {
|
|||
|
||||
text: function(group, options, prev) {
|
||||
return makeSpan(["text mord", options.style.cls()],
|
||||
[buildGroup(group.value, options.reset())]
|
||||
[buildGroup(group.value, options.deepen())]
|
||||
);
|
||||
},
|
||||
|
||||
|
@ -148,7 +148,7 @@ var groupTypes = {
|
|||
|
||||
if (group.value.sup) {
|
||||
var sup = buildGroup(group.value.sup,
|
||||
options.withStyle(options.style.sup()));
|
||||
options.withStyle(options.style.sup()).deepen());
|
||||
var supmid = makeSpan(
|
||||
[options.style.reset(), options.style.sup().cls()], [sup]);
|
||||
var supwrap = makeSpan(["msup", options.style.reset()], [supmid]);
|
||||
|
@ -156,7 +156,7 @@ var groupTypes = {
|
|||
|
||||
if (group.value.sub) {
|
||||
var sub = buildGroup(group.value.sub,
|
||||
options.withStyle(options.style.sub()));
|
||||
options.withStyle(options.style.sub()).deepen());
|
||||
var submid = makeSpan(
|
||||
[options.style.reset(), options.style.sub().cls()], [sub]);
|
||||
var subwrap = makeSpan(["msub"], [submid]);
|
||||
|
@ -260,13 +260,13 @@ var groupTypes = {
|
|||
var nstyle = fstyle.fracNum();
|
||||
var dstyle = fstyle.fracDen();
|
||||
|
||||
var numer = buildGroup(group.value.numer, options.withStyle(nstyle));
|
||||
var numer = buildGroup(group.value.numer, options.withStyle(nstyle).deepen());
|
||||
var numernumer = makeSpan([fstyle.reset(), nstyle.cls()], [numer]);
|
||||
var numerrow = makeSpan(["mfracnum"], [numernumer]);
|
||||
|
||||
var mid = makeSpan(["mfracmid"], [makeSpan()]);
|
||||
|
||||
var denom = buildGroup(group.value.denom, options.withStyle(dstyle));
|
||||
var denom = buildGroup(group.value.denom, options.withStyle(dstyle).deepen());
|
||||
var denomdenom = makeSpan([fstyle.reset(), dstyle.cls()], [denom])
|
||||
var denomrow = makeSpan(["mfracden"], [denomdenom]);
|
||||
|
||||
|
@ -363,14 +363,14 @@ var groupTypes = {
|
|||
|
||||
llap: function(group, options, prev) {
|
||||
var inner = makeSpan(
|
||||
["inner"], [buildGroup(group.value, options.reset())]);
|
||||
["inner"], [buildGroup(group.value, options.deepen())]);
|
||||
var fix = makeSpan(["fix"], []);
|
||||
return makeSpan(["llap", options.style.cls()], [inner, fix]);
|
||||
},
|
||||
|
||||
rlap: function(group, options, prev) {
|
||||
var inner = makeSpan(
|
||||
["inner"], [buildGroup(group.value, options.reset())]);
|
||||
["inner"], [buildGroup(group.value, options.deepen())]);
|
||||
var fix = makeSpan(["fix"], []);
|
||||
return makeSpan(["rlap", options.style.cls()], [inner, fix]);
|
||||
},
|
||||
|
@ -461,7 +461,7 @@ var buildGroup = function(group, options, prev) {
|
|||
var multiplier = sizingMultiplier[options.size] /
|
||||
sizingMultiplier[options.parentSize];
|
||||
|
||||
if (options.depth > 1) {
|
||||
if (options.deep) {
|
||||
throw new ParseError(
|
||||
"Can't use sizing outside of the root node");
|
||||
}
|
||||
|
|
|
@ -310,14 +310,36 @@ big parens
|
|||
|
||||
.sizing { display: inline-block; }
|
||||
|
||||
.reset-size5.size1 { font-size: 0.5em; }
|
||||
.reset-size5.size2 { font-size: 0.7em; }
|
||||
.reset-size5.size3 { font-size: 0.8em; }
|
||||
.reset-size5.size4 { font-size: 0.9em; }
|
||||
.reset-size5.size5 { font-size: 1.0em; }
|
||||
.reset-size5.size6 { font-size: 1.2em; }
|
||||
.reset-size5.size7 { font-size: 1.44em; }
|
||||
.reset-size5.size8 { font-size: 1.73em; }
|
||||
.reset-size5.size9 { font-size: 2.07em; }
|
||||
.reset-size5.size10 { font-size: 2.49em; }
|
||||
@size-1: 0.5;
|
||||
@size-2: 0.7;
|
||||
@size-3: 0.8;
|
||||
@size-4: 0.9;
|
||||
@size-5: 1.0;
|
||||
@size-6: 1.2;
|
||||
@size-7: 1.44;
|
||||
@size-8: 1.73;
|
||||
@size-9: 2.07;
|
||||
@size-10: 2.49;
|
||||
|
||||
.generate-size-change(@from, @to) {
|
||||
.reset-size@{from}.size@{to} {
|
||||
@sizeFromVariable: ~"size-@{from}";
|
||||
@sizeToVariable: ~"size-@{to}";
|
||||
font-size: (@@sizeToVariable / @@sizeFromVariable) * 1em;
|
||||
}
|
||||
}
|
||||
|
||||
.generate-to-size-change(@from, @currTo) when (@currTo =< 10) {
|
||||
.generate-size-change(@from, @currTo);
|
||||
|
||||
.generate-to-size-change(@from, (@currTo + 1));
|
||||
}
|
||||
|
||||
.generate-from-size-change(@currFrom) when (@currFrom =< 10) {
|
||||
.generate-to-size-change(@currFrom, 1);
|
||||
|
||||
.generate-from-size-change((@currFrom + 1));
|
||||
}
|
||||
|
||||
.generate-from-size-change(1);
|
||||
}
|
||||
|
|
|
@ -326,6 +326,47 @@ describe("A group parser", function() {
|
|||
});
|
||||
});
|
||||
|
||||
describe("An implicit group parser", function() {
|
||||
it("should not fail", function() {
|
||||
expect(function() {
|
||||
parseTree("\\Large x");
|
||||
parseTree("abc {abc \Large xyz} abc");
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
it("should produce a single object", function() {
|
||||
var parse = parseTree("\\Large abc");
|
||||
|
||||
expect(parse.length).toBe(1);
|
||||
|
||||
var sizing = parse[0];
|
||||
|
||||
expect(sizing.type).toMatch("sizing");
|
||||
expect(sizing.value).toBeTruthy();
|
||||
});
|
||||
|
||||
it("should apply only after the function", function() {
|
||||
var parse = parseTree("a \\Large abc");
|
||||
|
||||
expect(parse.length).toBe(2);
|
||||
|
||||
var sizing = parse[1];
|
||||
|
||||
expect(sizing.type).toMatch("sizing");
|
||||
expect(sizing.value.value.value.length).toBe(3);
|
||||
});
|
||||
|
||||
it("should stop at the ends of groups", function() {
|
||||
var parse = parseTree("a { b \\Large c } d");
|
||||
|
||||
var group = parse[1];
|
||||
var sizing = group.value[1];
|
||||
|
||||
expect(sizing.type).toMatch("sizing");
|
||||
expect(sizing.value.value.value.length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("A function parser", function() {
|
||||
it("should parse no argument functions", function() {
|
||||
expect(function() {
|
||||
|
|
Loading…
Reference in New Issue
Block a user