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:
Ron Kok
2018-06-06 18:30:29 -07:00
committed by Erik Demaine
parent 89e180c5a6
commit 93904c51ab
9 changed files with 59 additions and 37 deletions

View File

@@ -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);
} }

View File

@@ -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`,

View File

@@ -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
] ]

View File

@@ -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

View File

@@ -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