mirror of
https://github.com/Smaug123/KaTeX
synced 2025-10-06 11:48:41 +00:00
Support \hdashline (#1407)
* Support \hdashline Support `\hdashline` from package `arydshln`. This PR acts as a complement to PR #1395. Similarly to #1395, the dashed line is rendered as `border-bottom-style: dashed;`. That does not exactly match the `dashsegment` and `dashgap` lengths in `arydshln`, but it does render black lines with sharp edges. * Fixed top line position * Add screenshots * Fix lint error * Fix another lint error * Deleted HLine screenshots * Pick up comments
This commit is contained in:
@@ -33,7 +33,7 @@ export type ArrayEnvNodeData = {|
|
|||||||
cols?: AlignSpec[],
|
cols?: AlignSpec[],
|
||||||
body: AnyParseNode[][], // List of rows in the (2D) array.
|
body: AnyParseNode[][], // List of rows in the (2D) array.
|
||||||
rowGaps: (?ParseNode<"size">)[],
|
rowGaps: (?ParseNode<"size">)[],
|
||||||
numHLinesBeforeRow: number[],
|
hLinesBeforeRow: Array<boolean[]>,
|
||||||
|};
|
|};
|
||||||
// Same as above but with some fields not yet filled.
|
// Same as above but with some fields not yet filled.
|
||||||
type ArrayEnvNodeDataIncomplete = {|
|
type ArrayEnvNodeDataIncomplete = {|
|
||||||
@@ -45,18 +45,22 @@ type ArrayEnvNodeDataIncomplete = {|
|
|||||||
// Before these fields are filled.
|
// Before these fields are filled.
|
||||||
body?: AnyParseNode[][],
|
body?: AnyParseNode[][],
|
||||||
rowGaps?: (?ParseNode<"size">)[],
|
rowGaps?: (?ParseNode<"size">)[],
|
||||||
numHLinesBeforeRow?: number[],
|
hLinesBeforeRow?: Array<boolean[]>,
|
||||||
|};
|
|};
|
||||||
|
|
||||||
function getNumHLines(parser: Parser): number {
|
function getHLines(parser: Parser): boolean[] {
|
||||||
let n = 0;
|
// Return an array. The array length = number of hlines.
|
||||||
|
// Each element in the array tells if the line is dashed.
|
||||||
|
const hlineInfo = [];
|
||||||
parser.consumeSpaces();
|
parser.consumeSpaces();
|
||||||
while (parser.nextToken.text === "\\hline") {
|
let nxt = parser.nextToken.text;
|
||||||
|
while (nxt === "\\hline" || nxt === "\\hdashline") {
|
||||||
parser.consume();
|
parser.consume();
|
||||||
n++;
|
hlineInfo.push(nxt === "\\hdashline");
|
||||||
parser.consumeSpaces();
|
parser.consumeSpaces();
|
||||||
|
nxt = parser.nextToken.text;
|
||||||
}
|
}
|
||||||
return n;
|
return hlineInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -91,10 +95,10 @@ function parseArray(
|
|||||||
let row = [];
|
let row = [];
|
||||||
const body = [row];
|
const body = [row];
|
||||||
const rowGaps = [];
|
const rowGaps = [];
|
||||||
const numHLinesBeforeRow = [];
|
const hLinesBeforeRow = [];
|
||||||
|
|
||||||
// Test for \hline at the top of the array.
|
// Test for \hline at the top of the array.
|
||||||
numHLinesBeforeRow.push(getNumHLines(parser));
|
hLinesBeforeRow.push(getHLines(parser));
|
||||||
|
|
||||||
while (true) { // eslint-disable-line no-constant-condition
|
while (true) { // eslint-disable-line no-constant-condition
|
||||||
let cell = parser.parseExpression(false, "\\cr");
|
let cell = parser.parseExpression(false, "\\cr");
|
||||||
@@ -117,6 +121,9 @@ function parseArray(
|
|||||||
if (row.length === 1 && cell.value.value[0].value.length === 0) {
|
if (row.length === 1 && cell.value.value[0].value.length === 0) {
|
||||||
body.pop();
|
body.pop();
|
||||||
}
|
}
|
||||||
|
if (hLinesBeforeRow.length < body.length + 1) {
|
||||||
|
hLinesBeforeRow.push([]);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
} else if (next === "\\cr") {
|
} else if (next === "\\cr") {
|
||||||
const cr = parser.parseFunction();
|
const cr = parser.parseFunction();
|
||||||
@@ -126,7 +133,7 @@ function parseArray(
|
|||||||
rowGaps.push(assertNodeType(cr, "cr").value.size);
|
rowGaps.push(assertNodeType(cr, "cr").value.size);
|
||||||
|
|
||||||
// check for \hline(s) following the row separator
|
// check for \hline(s) following the row separator
|
||||||
numHLinesBeforeRow.push(getNumHLines(parser));
|
hLinesBeforeRow.push(getHLines(parser));
|
||||||
|
|
||||||
row = [];
|
row = [];
|
||||||
body.push(row);
|
body.push(row);
|
||||||
@@ -137,7 +144,7 @@ function parseArray(
|
|||||||
}
|
}
|
||||||
result.body = body;
|
result.body = body;
|
||||||
result.rowGaps = rowGaps;
|
result.rowGaps = rowGaps;
|
||||||
result.numHLinesBeforeRow = numHLinesBeforeRow;
|
result.hLinesBeforeRow = hLinesBeforeRow;
|
||||||
// $FlowFixMe: The required fields were added immediately above.
|
// $FlowFixMe: The required fields were added immediately above.
|
||||||
const res: ArrayEnvNodeData = result;
|
const res: ArrayEnvNodeData = result;
|
||||||
parser.gullet.endGroup();
|
parser.gullet.endGroup();
|
||||||
@@ -166,10 +173,10 @@ const htmlBuilder: HtmlBuilder<"array"> = 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;
|
const hLinesBeforeRow = group.value.hLinesBeforeRow;
|
||||||
let nc = 0;
|
let nc = 0;
|
||||||
let body = new Array(nr);
|
let body = new Array(nr);
|
||||||
const hlinePos = [];
|
const hlines = [];
|
||||||
|
|
||||||
// Horizontal spacing
|
// Horizontal spacing
|
||||||
const pt = 1 / options.fontMetrics().ptPerEm;
|
const pt = 1 / options.fontMetrics().ptPerEm;
|
||||||
@@ -187,12 +194,15 @@ const htmlBuilder: HtmlBuilder<"array"> = function(group, options) {
|
|||||||
let totalHeight = 0;
|
let totalHeight = 0;
|
||||||
|
|
||||||
// Set a position for \hline(s) at the top of the array, if any.
|
// Set a position for \hline(s) at the top of the array, if any.
|
||||||
for (let i = 1; i <= numHLinesBeforeRow[0]; i++) {
|
function setHLinePos(hlinesInGap: boolean[]) {
|
||||||
if (i > 1) { // The first \hline doesn't add to height.
|
for (let i = 0; i < hlinesInGap.length; ++i) {
|
||||||
totalHeight += 0.25;
|
if (i > 0) {
|
||||||
|
totalHeight += 0.25;
|
||||||
|
}
|
||||||
|
hlines.push({pos: totalHeight, isDashed: hlinesInGap[i]});
|
||||||
}
|
}
|
||||||
hlinePos.push(totalHeight);
|
|
||||||
}
|
}
|
||||||
|
setHLinePos(hLinesBeforeRow[0]);
|
||||||
|
|
||||||
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];
|
||||||
@@ -242,12 +252,7 @@ const htmlBuilder: HtmlBuilder<"array"> = function(group, options) {
|
|||||||
body[r] = outrow;
|
body[r] = outrow;
|
||||||
|
|
||||||
// Set a position for \hline(s), if any.
|
// Set a position for \hline(s), if any.
|
||||||
for (let i = 1; i <= numHLinesBeforeRow[r + 1]; i++) {
|
setHLinePos(hLinesBeforeRow[r + 1]);
|
||||||
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;
|
||||||
@@ -350,16 +355,22 @@ const htmlBuilder: HtmlBuilder<"array"> = function(group, options) {
|
|||||||
body = buildCommon.makeSpan(["mtable"], cols);
|
body = buildCommon.makeSpan(["mtable"], cols);
|
||||||
|
|
||||||
// Add \hline(s), if any.
|
// Add \hline(s), if any.
|
||||||
if (hlinePos.length > 0) {
|
if (hlines.length > 0) {
|
||||||
const line = buildCommon.makeLineSpan("hline", options, 0.05);
|
const line = buildCommon.makeLineSpan("hline", options, 0.05);
|
||||||
const vListChildren = [{type: "elem", elem: body, shift: 0}];
|
const dashes = buildCommon.makeLineSpan("hdashline", options, 0.05);
|
||||||
while (hlinePos.length > 0) {
|
const vListElems = [{type: "elem", elem: body, shift: 0}];
|
||||||
const lineShift = hlinePos.pop() - offset;
|
while (hlines.length > 0) {
|
||||||
vListChildren.push({type: "elem", elem: line, shift: lineShift});
|
const hline = hlines.pop();
|
||||||
|
const lineShift = hline.pos - offset;
|
||||||
|
if (hline.isDashed) {
|
||||||
|
vListElems.push({type: "elem", elem: dashes, shift: lineShift});
|
||||||
|
} else {
|
||||||
|
vListElems.push({type: "elem", elem: line, shift: lineShift});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
body = buildCommon.makeVList({
|
body = buildCommon.makeVList({
|
||||||
positionType: "individualShift",
|
positionType: "individualShift",
|
||||||
children: vListChildren,
|
children: vListElems,
|
||||||
}, options);
|
}, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -12,7 +12,8 @@
|
|||||||
.mfrac .frac-line,
|
.mfrac .frac-line,
|
||||||
.overline .overline-line,
|
.overline .overline-line,
|
||||||
.underline .underline-line,
|
.underline .underline-line,
|
||||||
.hline {
|
.hline,
|
||||||
|
.hdashline {
|
||||||
min-height: 1px;
|
min-height: 1px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -289,6 +290,12 @@
|
|||||||
border-bottom-style: solid;
|
border-bottom-style: solid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hdashline {
|
||||||
|
display: inline-block;
|
||||||
|
width: 100%;
|
||||||
|
border-bottom-style: dashed;
|
||||||
|
}
|
||||||
|
|
||||||
.sqrt {
|
.sqrt {
|
||||||
> .root {
|
> .root {
|
||||||
// These values are taken from the definition of `\r@@t`,
|
// These values are taken from the definition of `\r@@t`,
|
||||||
|
@@ -130,11 +130,15 @@ exports[`A begin/end parser should grab \\arraystretch 1`] = `
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
"hskipBeforeAndAfter": false,
|
"hLinesBeforeRow": [
|
||||||
"numHLinesBeforeRow": [
|
[
|
||||||
0,
|
],
|
||||||
0
|
[
|
||||||
|
],
|
||||||
|
[
|
||||||
|
]
|
||||||
],
|
],
|
||||||
|
"hskipBeforeAndAfter": false,
|
||||||
"rowGaps": [
|
"rowGaps": [
|
||||||
null
|
null
|
||||||
]
|
]
|
||||||
|
@@ -1137,6 +1137,7 @@ describe("A begin/end parser", function() {
|
|||||||
|
|
||||||
it("should parse an environment with hlines", function() {
|
it("should parse an environment with hlines", function() {
|
||||||
expect("\\begin{matrix}\\hline a&b\\\\ \\hline c&d\\end{matrix}").toParse();
|
expect("\\begin{matrix}\\hline a&b\\\\ \\hline c&d\\end{matrix}").toParse();
|
||||||
|
expect("\\begin{matrix}\\hdashline a&b\\\\ \\hdashline c&d\\end{matrix}").toParse();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should error when name is mismatched", function() {
|
it("should error when name is mismatched", function() {
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
Binary file not shown.
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 21 KiB |
Binary file not shown.
Before Width: | Height: | Size: 16 KiB |
Binary file not shown.
Before Width: | Height: | Size: 16 KiB |
@@ -34,8 +34,8 @@ Alignedat: |
|
|||||||
\end{alignedat}
|
\end{alignedat}
|
||||||
Arrays: |
|
Arrays: |
|
||||||
\left(\begin{array}{|rl:c||}
|
\left(\begin{array}{|rl:c||}
|
||||||
1&2&3\\
|
1&2&3\\ \hline
|
||||||
1+1&2+1&3+1\cr1\over2&\scriptstyle 1/2&\frac12\\[1ex]
|
1+1&2+1&3+1\cr1\over2&\scriptstyle 1/2&\frac12\\[1ex] \hline \hdashline
|
||||||
\begin{pmatrix}x\\y\end{pmatrix}&0&\begin{vmatrix}a&b\\c&d\end{vmatrix}
|
\begin{pmatrix}x\\y\end{pmatrix}&0&\begin{vmatrix}a&b\\c&d\end{vmatrix}
|
||||||
\end{array}\right]
|
\end{array}\right]
|
||||||
ArrayMode:
|
ArrayMode:
|
||||||
@@ -129,7 +129,6 @@ 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