From db1cccdeab45b376e5043e11ef2a80a596624359 Mon Sep 17 00:00:00 2001 From: Ron Kok Date: Fri, 15 Sep 2017 21:55:13 -0700 Subject: [PATCH] Support \colorbox and \fcolorbox (#886) * Support \colorbox and \\fcolorbox These are functions from the `color` package. They accept text, not math. They also have padding similar to `\fbox`. Because of the padding, the code in `buildHTML` is intermixed with the `\fbox` code. I have not (yet) created a new file in the functions folder. This way, the reviewer gets a cleaner diff. * Fix lint error * colorbox screenshots * Pick up review comments --- src/buildHTML.js | 48 ++++++++---- src/buildMathML.js | 24 ++++-- src/functions.js | 36 +++++++++ src/stretchy.js | 6 +- static/katex.less | 4 + test/__snapshots__/mathml-spec.js.snap | 21 +++++ test/katex-spec.js | 72 ++++++++++++++++++ test/mathml-spec.js | 4 + test/screenshotter/images/Colorbox-chrome.png | Bin 0 -> 11205 bytes .../screenshotter/images/Colorbox-firefox.png | Bin 0 -> 11210 bytes test/screenshotter/ss_data.yaml | 1 + 11 files changed, 194 insertions(+), 22 deletions(-) create mode 100644 test/screenshotter/images/Colorbox-chrome.png create mode 100644 test/screenshotter/images/Colorbox-firefox.png diff --git a/src/buildHTML.js b/src/buildHTML.js index 631f5f19..e9556ec8 100644 --- a/src/buildHTML.js +++ b/src/buildHTML.js @@ -1533,43 +1533,63 @@ groupTypes.accentUnder = function(group, options) { }; groupTypes.enclose = function(group, options) { - // \cancel, \bcancel, \xcancel, \sout, \fbox + // \cancel, \bcancel, \xcancel, \sout, \fbox, \colorbox, \fcolorbox const inner = buildGroup(group.value.body, options); const label = group.value.label.substr(1); const scale = options.sizeMultiplier; let img; - let pad = 0; let imgShift = 0; + const isColorbox = /color/.test(label); if (label === "sout") { img = makeSpan(["stretchy", "sout"]); img.height = options.fontMetrics().defaultRuleThickness / scale; imgShift = -0.5 * options.fontMetrics().xHeight; + } else { // Add horizontal padding - inner.classes.push((label === "fbox" ? "boxpad" : "cancel-pad")); + inner.classes.push(/cancel/.test(label) ? "cancel-pad" : "boxpad"); // Add vertical padding - const isCharBox = (isCharacterBox(group.value.body)); + let vertPad = 0; // ref: LaTeX source2e: \fboxsep = 3pt; \fboxrule = .4pt // ref: cancel package: \advance\totalheight2\p@ % "+2" - pad = (label === "fbox" ? 0.34 : (isCharBox ? 0.2 : 0)); - imgShift = inner.depth + pad; + if (/box/.test(label)) { + vertPad = label === "colorbox" ? 0.3 : 0.34; + } else { + vertPad = isCharacterBox(group.value.body) ? 0.2 : 0; + } - img = stretchy.encloseSpan(inner, label, pad, options); + img = stretchy.encloseSpan(inner, label, vertPad, options); + imgShift = inner.depth + vertPad; + + if (isColorbox) { + img.style.backgroundColor = group.value.backgroundColor.value; + if (label === "fcolorbox") { + img.style.borderColor = group.value.borderColor.value; + } + } } - const vlist = buildCommon.makeVList([ - {type: "elem", elem: inner, shift: 0}, - {type: "elem", elem: img, shift: imgShift}, - ], "individualShift", null, options); - - if (label !== "fbox") { - vlist.children[0].children[0].children[1].classes.push("svg-align"); + let vlist; + if (isColorbox) { + vlist = buildCommon.makeVList([ + // Put the color background behind inner; + {type: "elem", elem: img, shift: imgShift}, + {type: "elem", elem: inner, shift: 0}, + ], "individualShift", null, options); + } else { + vlist = buildCommon.makeVList([ + // Write the \cancel stroke on top of inner. + {type: "elem", elem: inner, shift: 0}, + {type: "elem", elem: img, shift: imgShift}, + ], "individualShift", null, options); } if (/cancel/.test(label)) { + vlist.children[0].children[0].children[1].classes.push("svg-align"); + // cancel does not create horiz space for its line extension. // That is, not when adjacent to a mord. return makeSpan(["mord", "cancel-lap"], [vlist], options); diff --git a/src/buildMathML.js b/src/buildMathML.js index c2607b13..c632b93c 100644 --- a/src/buildMathML.js +++ b/src/buildMathML.js @@ -505,21 +505,33 @@ groupTypes.accentUnder = function(group, options) { groupTypes.enclose = function(group, options) { const node = new mathMLTree.MathNode( "menclose", [buildGroup(group.value.body, options)]); - let notation = ""; switch (group.value.label) { + case "\\cancel": + node.setAttribute("notation", "updiagonalstrike"); + break; case "\\bcancel": - notation = "downdiagonalstrike"; + node.setAttribute("notation", "downdiagonalstrike"); break; case "\\sout": - notation = "horizontalstrike"; + node.setAttribute("notation", "horizontalstrike"); break; case "\\fbox": - notation = "box"; + node.setAttribute("notation", "box"); + break; + case "\\colorbox": + node.setAttribute("mathbackground", + group.value.backgroundColor.value); + break; + case "\\fcolorbox": + node.setAttribute("mathbackground", + group.value.backgroundColor.value); + // TODO(ron): I don't know any way to set the border color. + node.setAttribute("notation", "box"); break; default: - notation = "updiagonalstrike"; + // xcancel + node.setAttribute("notation", "updiagonalstrike downdiagonalstrike"); } - node.setAttribute("notation", notation); return node; }; diff --git a/src/functions.js b/src/functions.js index 91c6a542..fc293e7d 100644 --- a/src/functions.js +++ b/src/functions.js @@ -88,6 +88,42 @@ defineFunction(["\\color"], { argTypes: ["color"], }, null); +// colorbox +defineFunction(["\\colorbox"], { + numArgs: 2, + allowedInText: true, + greediness: 3, + argTypes: ["color", "text"], +}, function(context, args) { + const color = args[0]; + const body = args[1]; + return { + type: "enclose", + label: context.funcName, + backgroundColor: color, + body: body, + }; +}); + +// fcolorbox +defineFunction(["\\fcolorbox"], { + numArgs: 3, + allowedInText: true, + greediness: 3, + argTypes: ["color", "color", "text"], +}, function(context, args) { + const borderColor = args[0]; + const backgroundColor = args[1]; + const body = args[2]; + return { + type: "enclose", + label: context.funcName, + backgroundColor: backgroundColor, + borderColor: borderColor, + body: body, + }; +}); + // An overline defineFunction(["\\overline"], { numArgs: 1, diff --git a/src/stretchy.js b/src/stretchy.js index 3e2e16f1..7a3e3c21 100644 --- a/src/stretchy.js +++ b/src/stretchy.js @@ -254,11 +254,13 @@ const encloseSpan = function(inner, label, pad, options) { let img; const totalHeight = inner.height + inner.depth + 2 * pad; - if (label === "fbox") { + if (/(fbox)|(color)/.test(label)) { img = buildCommon.makeSpan(["stretchy", label], [], options); - if (options.color) { + + if (label === "fbox" && options.color) { img.style.borderColor = options.getColor(); } + } else { // \cancel, \bcancel, or \xcancel // Since \cancel's SVG is inline and it omits the viewBox attribute, diff --git a/static/katex.less b/static/katex.less index b009b7bf..f7fad196 100644 --- a/static/katex.less +++ b/static/katex.less @@ -593,6 +593,10 @@ box-sizing: border-box; border: 0.04em solid black; // \fboxrule = 0.4pt } + .fcolorbox { + box-sizing: border-box; + border: 0.04em solid; // \fboxrule = 0.4pt + } .cancel-pad { padding: 0 0.2em 0 0.2em; // ref: cancel package \advance\dimen@ 2\p@ % "+2" } diff --git a/test/__snapshots__/mathml-spec.js.snap b/test/__snapshots__/mathml-spec.js.snap index 2b52ca14..3edca120 100644 --- a/test/__snapshots__/mathml-spec.js.snap +++ b/test/__snapshots__/mathml-spec.js.snap @@ -74,6 +74,27 @@ exports[`A MathML builder should make prime operators into nodes 1`] = ` `; +exports[`A MathML builder should use for colorbox 1`] = ` + + + + + + + + b + + + + + + \\colorbox{red}{b} + + + + +`; + exports[`A MathML builder should use for raisebox 1`] = ` diff --git a/test/katex-spec.js b/test/katex-spec.js index feb5d2bf..882a452c 100644 --- a/test/katex-spec.js +++ b/test/katex-spec.js @@ -2046,6 +2046,78 @@ describe("A boxed builder", function() { }); }); +describe("A colorbox parser", function() { + it("should not fail, given a text argument", function() { + expect("\\colorbox{red}{a b}").toParse(); + expect("\\colorbox{red}{x}^2").toParse(); + expect("\\colorbox{red} x").toParse(); + }); + + it("should fail, given a math argument", function() { + expect("\\colorbox{red}{\\alpha}").toNotParse(); + expect("\\colorbox{red}{\\frac{a}{b}}").toNotParse(); + }); + + it("should parse a color", function() { + expect("\\colorbox{red}{a b}").toParse(); + expect("\\colorbox{#197}{a b}").toParse(); + expect("\\colorbox{#1a9b7c}{a b}").toParse(); + }); + + it("should produce enclose", function() { + const parse = getParsed("\\colorbox{red} x")[0]; + expect(parse.type).toEqual("enclose"); + }); +}); + +describe("A colorbox builder", function() { + it("should not fail", function() { + expect("\\colorbox{red}{a b}").toBuild(); + expect("\\colorbox{red}{a b}^2").toBuild(); + expect("\\colorbox{red} x").toBuild(); + }); + + it("should produce mords", function() { + expect(getBuilt("\\colorbox{red}{a b}")[0].classes).toContain("mord"); + }); +}); + +describe("An fcolorbox parser", function() { + it("should not fail, given a text argument", function() { + expect("\\fcolorbox{blue}{yellow}{a b}").toParse(); + expect("\\fcolorbox{blue}{yellow}{x}^2").toParse(); + expect("\\fcolorbox{blue}{yellow} x").toParse(); + }); + + it("should fail, given a math argument", function() { + expect("\\fcolorbox{blue}{yellow}{\\alpha}").toNotParse(); + expect("\\fcolorbox{blue}{yellow}{\\frac{a}{b}}").toNotParse(); + }); + + it("should parse a color", function() { + expect("\\fcolorbox{blue}{yellow}{a b}").toParse(); + expect("\\fcolorbox{blue}{#197}{a b}").toParse(); + expect("\\fcolorbox{blue}{#1a9b7c}{a b}").toParse(); + }); + + it("should produce enclose", function() { + const parse = getParsed("\\fcolorbox{blue}{yellow} x")[0]; + expect(parse.type).toEqual("enclose"); + }); +}); + +describe("A fcolorbox builder", function() { + it("should not fail", function() { + expect("\\fcolorbox{blue}{yellow}{a b}").toBuild(); + expect("\\fcolorbox{blue}{yellow}{a b}^2").toBuild(); + expect("\\fcolorbox{blue}{yellow} x").toBuild(); + }); + + it("should produce mords", function() { + expect(getBuilt("\\colorbox{red}{a b}")[0].classes).toContain("mord"); + }); +}); + describe("A strike-through parser", function() { it("should not fail", function() { expect("\\cancel{x}").toParse(); diff --git a/test/mathml-spec.js b/test/mathml-spec.js index 6565c099..5c6c61fc 100644 --- a/test/mathml-spec.js +++ b/test/mathml-spec.js @@ -54,4 +54,8 @@ describe("A MathML builder", function() { it('should use for raisebox', () => { expect(getMathML("\\raisebox{0.25em}{b}")).toMatchSnapshot(); }); + + it('should use for colorbox', () => { + expect(getMathML("\\colorbox{red}{b}")).toMatchSnapshot(); + }); }); diff --git a/test/screenshotter/images/Colorbox-chrome.png b/test/screenshotter/images/Colorbox-chrome.png new file mode 100644 index 0000000000000000000000000000000000000000..6e68c395502b556b5b297828922f687601ef9ae2 GIT binary patch literal 11205 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