diff --git a/Options.js b/Options.js index 5e741df0c..8d2a8d032 100644 --- a/Options.js +++ b/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); }; diff --git a/Parser.js b/Parser.js index 3329ccae5..740ed4f2f 100644 --- a/Parser.js +++ b/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( diff --git a/buildTree.js b/buildTree.js index d98623f81..f8fe16238 100644 --- a/buildTree.js +++ b/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"); } diff --git a/static/katex.less b/static/katex.less index a1144c290..c274eb5cc 100644 --- a/static/katex.less +++ b/static/katex.less @@ -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); } diff --git a/test/katex-tests.js b/test/katex-tests.js index 6c2759c39..c9e2540f4 100644 --- a/test/katex-tests.js +++ b/test/katex-tests.js @@ -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() {