mirror of
https://github.com/Smaug123/KaTeX
synced 2025-10-05 11:18:39 +00:00
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
This commit is contained in:
@@ -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);
|
||||
|
@@ -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;
|
||||
};
|
||||
|
||||
|
@@ -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,
|
||||
|
@@ -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,
|
||||
|
@@ -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"
|
||||
}
|
||||
|
@@ -74,6 +74,27 @@ exports[`A MathML builder should make prime operators into <mo> nodes 1`] = `
|
||||
|
||||
`;
|
||||
|
||||
exports[`A MathML builder should use <menclose> for colorbox 1`] = `
|
||||
|
||||
<math>
|
||||
<semantics>
|
||||
<mrow>
|
||||
<menclose mathbackground="red">
|
||||
<mrow>
|
||||
<mtext>
|
||||
b
|
||||
</mtext>
|
||||
</mrow>
|
||||
</menclose>
|
||||
</mrow>
|
||||
<annotation encoding="application/x-tex">
|
||||
\\colorbox{red}{b}
|
||||
</annotation>
|
||||
</semantics>
|
||||
</math>
|
||||
|
||||
`;
|
||||
|
||||
exports[`A MathML builder should use <mpadded> for raisebox 1`] = `
|
||||
|
||||
<math>
|
||||
|
@@ -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();
|
||||
|
@@ -54,4 +54,8 @@ describe("A MathML builder", function() {
|
||||
it('should use <mpadded> for raisebox', () => {
|
||||
expect(getMathML("\\raisebox{0.25em}{b}")).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should use <menclose> for colorbox', () => {
|
||||
expect(getMathML("\\colorbox{red}{b}")).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
BIN
test/screenshotter/images/Colorbox-chrome.png
Normal file
BIN
test/screenshotter/images/Colorbox-chrome.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
BIN
test/screenshotter/images/Colorbox-firefox.png
Normal file
BIN
test/screenshotter/images/Colorbox-firefox.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
@@ -59,6 +59,7 @@ Colors:
|
||||
nolatex: different syntax and different scope
|
||||
ColorImplicit: bl{ack\color{red}red\textcolor{green}{green}red\color{blue}blue}black
|
||||
ColorSpacing: \textcolor{red}{\displaystyle \int x} + 1
|
||||
Colorbox: a \colorbox{teal} B \fcolorbox{blue}{red}{C} e+\colorbox{teal}x
|
||||
DashesAndQuotes: \text{``a'' b---c -- d----`e'-{-}-f}--``x''
|
||||
DeepFontSizing:
|
||||
tex: |
|
||||
|
Reference in New Issue
Block a user