\color affects following \right, put array cells in their own groups (#1845)

* \color affects following \right

Fix #1844 by giving `leftright` nodes a `rightColor` attribute for how
to color the right bracket.  Now `\color` sets the macro
`\current@color` in the current environment (in particular, resetting
after `\right`), just like `color.sty` does in LaTeX.  This is used to
specially pass the current color into any following `\right` and then
into the `leftright` parse node.

* Add test

* Put each array cell in its own group/namespace

* Improve cell group isolation, add test and TODO

* Improve comments
This commit is contained in:
Erik Demaine
2019-07-05 00:27:55 -04:00
committed by Kevin Barabash
parent 39da3f5119
commit 19d9d83ad3
8 changed files with 58 additions and 4 deletions

View File

@@ -79,6 +79,9 @@ function parseArray(
} }
} }
// Start group for first cell
parser.gullet.beginGroup();
let row = []; let row = [];
const body = [row]; const body = [row];
const rowGaps = []; const rowGaps = [];
@@ -88,7 +91,11 @@ function parseArray(
hLinesBeforeRow.push(getHLines(parser)); hLinesBeforeRow.push(getHLines(parser));
while (true) { // eslint-disable-line no-constant-condition while (true) { // eslint-disable-line no-constant-condition
// Parse each cell in its own group (namespace)
let cell = parser.parseExpression(false, "\\cr"); let cell = parser.parseExpression(false, "\\cr");
parser.gullet.endGroup();
parser.gullet.beginGroup();
cell = { cell = {
type: "ordgroup", type: "ordgroup",
mode: parser.mode, mode: parser.mode,
@@ -132,7 +139,12 @@ function parseArray(
parser.nextToken); parser.nextToken);
} }
} }
// End cell group
parser.gullet.endGroup(); parser.gullet.endGroup();
// End array group defining \\
parser.gullet.endGroup();
return { return {
type: "array", type: "array",
mode: parser.mode, mode: parser.mode,
@@ -671,6 +683,7 @@ defineEnvironment({
body: [res], body: [res],
left: delimiters[0], left: delimiters[0],
right: delimiters[1], right: delimiters[1],
rightColor: undefined, // \right uninfluenced by \color in array
} : res; } : res;
}, },
htmlBuilder, htmlBuilder,
@@ -775,6 +788,7 @@ defineEnvironment({
body: [res], body: [res],
left: "\\{", left: "\\{",
right: ".", right: ".",
rightColor: undefined,
}; };
}, },
htmlBuilder, htmlBuilder,

View File

@@ -67,7 +67,13 @@ defineFunction({
handler({parser, breakOnTokenText}, args) { handler({parser, breakOnTokenText}, args) {
const color = assertNodeType(args[0], "color-token").color; const color = assertNodeType(args[0], "color-token").color;
// If we see a styling function, parse out the implicit body // Set macro \current@color in current namespace to store the current
// color, mimicking the behavior of color.sty.
// This is currently used just to correctly color a \right
// that follows a \color command.
parser.gullet.macros.set("\\current@color", color);
// Parse out the implicit body that should be colored.
const body = parser.parseExpression(true, breakOnTokenText); const body = parser.parseExpression(true, breakOnTokenText);
return { return {

View File

@@ -145,10 +145,16 @@ defineFunction({
// \left case below triggers parsing of \right in // \left case below triggers parsing of \right in
// `const right = parser.parseFunction();` // `const right = parser.parseFunction();`
// uses this return value. // uses this return value.
const color = context.parser.gullet.macros.get("\\current@color");
if (color && typeof color !== "string") {
throw new ParseError(
"\\current@color set to non-string in \\right");
}
return { return {
type: "leftright-right", type: "leftright-right",
mode: context.parser.mode, mode: context.parser.mode,
delim: checkDelimiter(args[0], context).text, delim: checkDelimiter(args[0], context).text,
color, // undefined if not set via \color
}; };
}, },
}); });
@@ -178,6 +184,7 @@ defineFunction({
body, body,
left: delim.text, left: delim.text,
right: right.delim, right: right.delim,
rightColor: right.color,
}; };
}, },
htmlBuilder: (group, options) => { htmlBuilder: (group, options) => {
@@ -241,12 +248,14 @@ defineFunction({
} }
let rightDelim; let rightDelim;
// Same for the right delimiter // Same for the right delimiter, but using color specified by \color
if (group.right === ".") { if (group.right === ".") {
rightDelim = html.makeNullDelimiter(options, ["mclose"]); rightDelim = html.makeNullDelimiter(options, ["mclose"]);
} else { } else {
const colorOptions = group.rightColor ?
options.withColor(group.rightColor) : options;
rightDelim = delimiter.leftRightDelim( rightDelim = delimiter.leftRightDelim(
group.right, innerHeight, innerDepth, options, group.right, innerHeight, innerDepth, colorOptions,
group.mode, ["mclose"]); group.mode, ["mclose"]);
} }
// Add it to the end of the expression. // Add it to the end of the expression.
@@ -273,6 +282,10 @@ defineFunction({
rightNode.setAttribute("fence", "true"); rightNode.setAttribute("fence", "true");
if (group.rightColor) {
rightNode.setAttribute("mathcolor", group.rightColor);
}
inner.push(rightNode); inner.push(rightNode);
} }

View File

@@ -318,12 +318,14 @@ type ParseNodeTypes = {
body: AnyParseNode[], body: AnyParseNode[],
left: string, left: string,
right: string, right: string,
rightColor: ?string, // undefined means "inherit"
|}, |},
"leftright-right": {| "leftright-right": {|
type: "leftright-right", type: "leftright-right",
mode: Mode, mode: Mode,
loc?: ?SourceLocation, loc?: ?SourceLocation,
delim: string, delim: string,
color: ?string, // undefined means "inherit"
|}, |},
"mathchoice": {| "mathchoice": {|
type: "mathchoice", type: "mathchoice",

View File

@@ -3123,6 +3123,21 @@ describe("A macro expander", function() {
.toParseLike`11\sqrt[2]{2}11`; .toParseLike`11\sqrt[2]{2}11`;
}); });
it("array cells generate groups", () => {
expect`\def\x{1}\begin{matrix}\x&\def\x{2}\x&\x\end{matrix}`
.toParseLike`\begin{matrix}1&2&1\end{matrix}`;
});
// TODO: This doesn't yet work; before the environment gets called,
// {matrix} gets consumed which means that the \def gets executed, before
// we can create a group. :-( Issue #1989
/*
it("array cells generate groups", () => {
expect`\def\x{1}\begin{matrix}\def\x{2}&\x\end{matrix}`
.toParseLike`\begin{matrix}&1\end{matrix}`;
});
*/
it("\\gdef changes settings.macros", () => { it("\\gdef changes settings.macros", () => {
const macros = {}; const macros = {};
expect`\gdef\foo{1}`.toParse(new Settings({macros})); expect`\gdef\foo{1}`.toParse(new Settings({macros}));

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

@@ -77,7 +77,11 @@ Cases: |
Colors: Colors:
tex: \blue{a}\textcolor{#0f0}{b}\textcolor{red}{c} tex: \blue{a}\textcolor{#0f0}{b}\textcolor{red}{c}
nolatex: different syntax and different scope nolatex: different syntax and different scope
ColorImplicit: bl{ack\color{red}red\textcolor{green}{green}red\color{blue}blue}black ColorImplicit:
\begin{array}{l}
bl{ack\color{red}red\textcolor{green}{green}red\color{blue}blue}black \\
black\left(black\color{red}red\right)black
\end{array}
ColorSpacing: \textcolor{red}{\displaystyle \int x} + 1 ColorSpacing: \textcolor{red}{\displaystyle \int x} + 1
Colorbox: a \colorbox{teal} B \fcolorbox{blue}{red}{C} e+\colorbox{teal}x Colorbox: a \colorbox{teal} B \fcolorbox{blue}{red}{C} e+\colorbox{teal}x
DashesAndQuotes: | DashesAndQuotes: |