mirror of
https://github.com/Smaug123/KaTeX
synced 2025-10-05 03:08:40 +00:00
Support \hline (#1306)
* Support \hline Support `\hline`. * Fix CSS * Fix lint errors * Add double \hline. Code Cleanup * Reduce width of screenshot test * Add screenshots * Use consumeSpaces, read multiple \hlines * Code cleanup * Update screenshots
This commit is contained in:
@@ -30,8 +30,20 @@ export type ArrayEnvNodeData = {
|
|||||||
// initialization.
|
// initialization.
|
||||||
body?: ParseNode<*>[][], // List of rows in the (2D) array.
|
body?: ParseNode<*>[][], // List of rows in the (2D) array.
|
||||||
rowGaps?: number[],
|
rowGaps?: number[],
|
||||||
|
numHLinesBeforeRow?: number[],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function getNumHLines(parser: Parser): number {
|
||||||
|
let n = 0;
|
||||||
|
parser.consumeSpaces();
|
||||||
|
while (parser.nextToken.text === "\\hline") {
|
||||||
|
parser.consume();
|
||||||
|
n++;
|
||||||
|
parser.consumeSpaces();
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse the body of the environment, with rows delimited by \\ and
|
* Parse the body of the environment, with rows delimited by \\ and
|
||||||
* columns delimited by &, and create a nested list in row-major order
|
* columns delimited by &, and create a nested list in row-major order
|
||||||
@@ -46,6 +58,11 @@ function parseArray(
|
|||||||
let row = [];
|
let row = [];
|
||||||
const body = [row];
|
const body = [row];
|
||||||
const rowGaps = [];
|
const rowGaps = [];
|
||||||
|
const numHLinesBeforeRow = [];
|
||||||
|
|
||||||
|
// Test for \hline at the top of the array.
|
||||||
|
numHLinesBeforeRow.push(getNumHLines(parser));
|
||||||
|
|
||||||
while (true) { // eslint-disable-line no-constant-condition
|
while (true) { // eslint-disable-line no-constant-condition
|
||||||
let cell = parser.parseExpression(false, undefined);
|
let cell = parser.parseExpression(false, undefined);
|
||||||
cell = new ParseNode("ordgroup", cell, parser.mode);
|
cell = new ParseNode("ordgroup", cell, parser.mode);
|
||||||
@@ -76,6 +93,10 @@ function parseArray(
|
|||||||
throw new ParseError(`Failed to parse function after ${next}`);
|
throw new ParseError(`Failed to parse function after ${next}`);
|
||||||
}
|
}
|
||||||
rowGaps.push(cr.value.size);
|
rowGaps.push(cr.value.size);
|
||||||
|
|
||||||
|
// check for \hline(s) following the row separator
|
||||||
|
numHLinesBeforeRow.push(getNumHLines(parser));
|
||||||
|
|
||||||
row = [];
|
row = [];
|
||||||
body.push(row);
|
body.push(row);
|
||||||
} else {
|
} else {
|
||||||
@@ -85,6 +106,7 @@ function parseArray(
|
|||||||
}
|
}
|
||||||
result.body = body;
|
result.body = body;
|
||||||
result.rowGaps = rowGaps;
|
result.rowGaps = rowGaps;
|
||||||
|
result.numHLinesBeforeRow = numHLinesBeforeRow;
|
||||||
return new ParseNode("array", result, parser.mode);
|
return new ParseNode("array", result, parser.mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,8 +132,10 @@ const htmlBuilder = function(group, options) {
|
|||||||
let r;
|
let r;
|
||||||
let c;
|
let c;
|
||||||
const nr = group.value.body.length;
|
const nr = group.value.body.length;
|
||||||
|
const numHLinesBeforeRow = group.value.numHLinesBeforeRow;
|
||||||
let nc = 0;
|
let nc = 0;
|
||||||
let body = new Array(nr);
|
let body = new Array(nr);
|
||||||
|
const hlinePos = [];
|
||||||
|
|
||||||
// Horizontal spacing
|
// Horizontal spacing
|
||||||
const pt = 1 / options.fontMetrics().ptPerEm;
|
const pt = 1 / options.fontMetrics().ptPerEm;
|
||||||
@@ -130,6 +154,15 @@ const htmlBuilder = function(group, options) {
|
|||||||
const arstrutDepth = 0.3 * arrayskip; // \@arstrutbox in lttab.dtx
|
const arstrutDepth = 0.3 * arrayskip; // \@arstrutbox in lttab.dtx
|
||||||
|
|
||||||
let totalHeight = 0;
|
let totalHeight = 0;
|
||||||
|
|
||||||
|
// Set a position for \hline(s) at the top of the array, if any.
|
||||||
|
for (let i = 1; i <= numHLinesBeforeRow[0]; i++) {
|
||||||
|
if (i > 1) { // The first \hline doesn't add to height.
|
||||||
|
totalHeight += 0.25;
|
||||||
|
}
|
||||||
|
hlinePos.push(totalHeight);
|
||||||
|
}
|
||||||
|
|
||||||
for (r = 0; r < group.value.body.length; ++r) {
|
for (r = 0; r < group.value.body.length; ++r) {
|
||||||
const inrow = group.value.body[r];
|
const inrow = group.value.body[r];
|
||||||
let height = arstrutHeight; // \@array adds an \@arstrut
|
let height = arstrutHeight; // \@array adds an \@arstrut
|
||||||
@@ -175,6 +208,14 @@ const htmlBuilder = function(group, options) {
|
|||||||
outrow.pos = totalHeight;
|
outrow.pos = totalHeight;
|
||||||
totalHeight += depth + gap; // \@yargarraycr
|
totalHeight += depth + gap; // \@yargarraycr
|
||||||
body[r] = outrow;
|
body[r] = outrow;
|
||||||
|
|
||||||
|
// Set a position for \hline(s), if any.
|
||||||
|
for (let i = 1; i <= numHLinesBeforeRow[r + 1]; i++) {
|
||||||
|
if (i > 1) { // the first \hline doesn't add height
|
||||||
|
totalHeight += 0.25;
|
||||||
|
}
|
||||||
|
hlinePos.push(totalHeight);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const offset = totalHeight / 2 + options.fontMetrics().axisHeight;
|
const offset = totalHeight / 2 + options.fontMetrics().axisHeight;
|
||||||
@@ -266,6 +307,21 @@ const htmlBuilder = function(group, options) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
body = buildCommon.makeSpan(["mtable"], cols);
|
body = buildCommon.makeSpan(["mtable"], cols);
|
||||||
|
|
||||||
|
// Add \hline(s), if any.
|
||||||
|
if (hlinePos.length > 0) {
|
||||||
|
const line = buildCommon.makeLineSpan("hline", options, 0.05);
|
||||||
|
const vListChildren = [{type: "elem", elem: body, shift: 0}];
|
||||||
|
while (hlinePos.length > 0) {
|
||||||
|
const lineShift = hlinePos.pop() - offset;
|
||||||
|
vListChildren.push({type: "elem", elem: line, shift: lineShift});
|
||||||
|
}
|
||||||
|
body = buildCommon.makeVList({
|
||||||
|
positionType: "individualShift",
|
||||||
|
children: vListChildren,
|
||||||
|
}, options);
|
||||||
|
}
|
||||||
|
|
||||||
return buildCommon.makeSpan(["mord"], [body], options);
|
return buildCommon.makeSpan(["mord"], [body], options);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -11,7 +11,8 @@
|
|||||||
|
|
||||||
.mfrac .frac-line,
|
.mfrac .frac-line,
|
||||||
.overline .overline-line,
|
.overline .overline-line,
|
||||||
.underline .underline-line {
|
.underline .underline-line,
|
||||||
|
.hline {
|
||||||
min-height: 1px;
|
min-height: 1px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -317,7 +318,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.overline .overline-line,
|
.overline .overline-line,
|
||||||
.underline .underline-line {
|
.underline .underline-line,
|
||||||
|
.hline {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-bottom-style: solid;
|
border-bottom-style: solid;
|
||||||
|
@@ -1356,6 +1356,10 @@ describe("A begin/end parser", function() {
|
|||||||
expect("\\begin{array}{cc}a&b\\\\c&d\\end{array}").toParse();
|
expect("\\begin{array}{cc}a&b\\\\c&d\\end{array}").toParse();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should parse an environment with hlines", function() {
|
||||||
|
expect("\\begin{matrix}\\hline a&b\\\\ \\hline c&d\\end{matrix}").toParse();
|
||||||
|
});
|
||||||
|
|
||||||
it("should error when name is mismatched", function() {
|
it("should error when name is mismatched", function() {
|
||||||
expect("\\begin{matrix}a&b\\\\c&d\\end{pmatrix}").toNotParse();
|
expect("\\begin{matrix}a&b\\\\c&d\\end{pmatrix}").toNotParse();
|
||||||
});
|
});
|
||||||
|
BIN
test/screenshotter/images/Hline-chrome.png
Normal file
BIN
test/screenshotter/images/Hline-chrome.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
BIN
test/screenshotter/images/Hline-firefox.png
Normal file
BIN
test/screenshotter/images/Hline-firefox.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
@@ -124,6 +124,7 @@ GroupMacros:
|
|||||||
\startExp: e^\bgroup
|
\startExp: e^\bgroup
|
||||||
\endExp: \egroup
|
\endExp: \egroup
|
||||||
tex: \startExp a+b\endExp
|
tex: \startExp a+b\endExp
|
||||||
|
Hline: \begin{array}{c|c}\hline \hline \hline a & b \\ \hline \hline \hline c & d \end{array} \quad \frac{\begin{pmatrix} a & b \\ \hline c & d \\ \hline \hline \hline\end{pmatrix}} 2 \quad \begin{bmatrix}\hline a & b \\ \hline c & d \\ \hline e & f \end{bmatrix}
|
||||||
HorizontalBraces: \overbrace{\displaystyle{\oint_S{\vec E\cdot\hat n\,\mathrm d a}}}^\text{emf} = \underbrace{\frac{q_{\text{enc}}}{\varepsilon_0}}_{\text{charge}}
|
HorizontalBraces: \overbrace{\displaystyle{\oint_S{\vec E\cdot\hat n\,\mathrm d a}}}^\text{emf} = \underbrace{\frac{q_{\text{enc}}}{\varepsilon_0}}_{\text{charge}}
|
||||||
KaTeX:
|
KaTeX:
|
||||||
tex: \KaTeX
|
tex: \KaTeX
|
||||||
|
Reference in New Issue
Block a user