diff --git a/src/environments/array.js b/src/environments/array.js index 27b91361..4be385dd 100644 --- a/src/environments/array.js +++ b/src/environments/array.js @@ -33,7 +33,7 @@ export type ArrayEnvNodeData = {| cols?: AlignSpec[], body: AnyParseNode[][], // List of rows in the (2D) array. rowGaps: (?ParseNode<"size">)[], - numHLinesBeforeRow: number[], + hLinesBeforeRow: Array, |}; // Same as above but with some fields not yet filled. type ArrayEnvNodeDataIncomplete = {| @@ -45,18 +45,22 @@ type ArrayEnvNodeDataIncomplete = {| // Before these fields are filled. body?: AnyParseNode[][], rowGaps?: (?ParseNode<"size">)[], - numHLinesBeforeRow?: number[], + hLinesBeforeRow?: Array, |}; -function getNumHLines(parser: Parser): number { - let n = 0; +function getHLines(parser: Parser): boolean[] { + // Return an array. The array length = number of hlines. + // Each element in the array tells if the line is dashed. + const hlineInfo = []; parser.consumeSpaces(); - while (parser.nextToken.text === "\\hline") { + let nxt = parser.nextToken.text; + while (nxt === "\\hline" || nxt === "\\hdashline") { parser.consume(); - n++; + hlineInfo.push(nxt === "\\hdashline"); parser.consumeSpaces(); + nxt = parser.nextToken.text; } - return n; + return hlineInfo; } /** @@ -91,10 +95,10 @@ function parseArray( let row = []; const body = [row]; const rowGaps = []; - const numHLinesBeforeRow = []; + const hLinesBeforeRow = []; // 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 let cell = parser.parseExpression(false, "\\cr"); @@ -117,6 +121,9 @@ function parseArray( if (row.length === 1 && cell.value.value[0].value.length === 0) { body.pop(); } + if (hLinesBeforeRow.length < body.length + 1) { + hLinesBeforeRow.push([]); + } break; } else if (next === "\\cr") { const cr = parser.parseFunction(); @@ -126,7 +133,7 @@ function parseArray( rowGaps.push(assertNodeType(cr, "cr").value.size); // check for \hline(s) following the row separator - numHLinesBeforeRow.push(getNumHLines(parser)); + hLinesBeforeRow.push(getHLines(parser)); row = []; body.push(row); @@ -137,7 +144,7 @@ function parseArray( } result.body = body; result.rowGaps = rowGaps; - result.numHLinesBeforeRow = numHLinesBeforeRow; + result.hLinesBeforeRow = hLinesBeforeRow; // $FlowFixMe: The required fields were added immediately above. const res: ArrayEnvNodeData = result; parser.gullet.endGroup(); @@ -166,10 +173,10 @@ const htmlBuilder: HtmlBuilder<"array"> = function(group, options) { let r; let c; const nr = group.value.body.length; - const numHLinesBeforeRow = group.value.numHLinesBeforeRow; + const hLinesBeforeRow = group.value.hLinesBeforeRow; let nc = 0; let body = new Array(nr); - const hlinePos = []; + const hlines = []; // Horizontal spacing const pt = 1 / options.fontMetrics().ptPerEm; @@ -187,12 +194,15 @@ const htmlBuilder: HtmlBuilder<"array"> = function(group, options) { 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; + function setHLinePos(hlinesInGap: boolean[]) { + for (let i = 0; i < hlinesInGap.length; ++i) { + 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) { const inrow = group.value.body[r]; @@ -242,12 +252,7 @@ const htmlBuilder: HtmlBuilder<"array"> = function(group, options) { 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); - } + setHLinePos(hLinesBeforeRow[r + 1]); } const offset = totalHeight / 2 + options.fontMetrics().axisHeight; @@ -350,16 +355,22 @@ const htmlBuilder: HtmlBuilder<"array"> = function(group, options) { body = buildCommon.makeSpan(["mtable"], cols); // Add \hline(s), if any. - if (hlinePos.length > 0) { + if (hlines.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}); + const dashes = buildCommon.makeLineSpan("hdashline", options, 0.05); + const vListElems = [{type: "elem", elem: body, shift: 0}]; + while (hlines.length > 0) { + 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({ positionType: "individualShift", - children: vListChildren, + children: vListElems, }, options); } diff --git a/src/katex.less b/src/katex.less index 4e76a7f9..62bd398e 100644 --- a/src/katex.less +++ b/src/katex.less @@ -12,7 +12,8 @@ .mfrac .frac-line, .overline .overline-line, .underline .underline-line, - .hline { + .hline, + .hdashline { min-height: 1px; } } @@ -289,6 +290,12 @@ border-bottom-style: solid; } + .hdashline { + display: inline-block; + width: 100%; + border-bottom-style: dashed; + } + .sqrt { > .root { // These values are taken from the definition of `\r@@t`, diff --git a/test/__snapshots__/katex-spec.js.snap b/test/__snapshots__/katex-spec.js.snap index 627988d5..29cfc01a 100644 --- a/test/__snapshots__/katex-spec.js.snap +++ b/test/__snapshots__/katex-spec.js.snap @@ -130,11 +130,15 @@ exports[`A begin/end parser should grab \\arraystretch 1`] = ` } ] ], - "hskipBeforeAndAfter": false, - "numHLinesBeforeRow": [ - 0, - 0 + "hLinesBeforeRow": [ + [ + ], + [ + ], + [ + ] ], + "hskipBeforeAndAfter": false, "rowGaps": [ null ] diff --git a/test/katex-spec.js b/test/katex-spec.js index 89a4f052..59780496 100644 --- a/test/katex-spec.js +++ b/test/katex-spec.js @@ -1137,6 +1137,7 @@ describe("A begin/end parser", function() { it("should parse an environment with hlines", function() { 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() { diff --git a/test/screenshotter/images/Arrays-chrome.png b/test/screenshotter/images/Arrays-chrome.png index 0921a668..1517b41c 100644 Binary files a/test/screenshotter/images/Arrays-chrome.png and b/test/screenshotter/images/Arrays-chrome.png differ diff --git a/test/screenshotter/images/Arrays-firefox.png b/test/screenshotter/images/Arrays-firefox.png index b4a89529..fd553a75 100644 Binary files a/test/screenshotter/images/Arrays-firefox.png and b/test/screenshotter/images/Arrays-firefox.png differ diff --git a/test/screenshotter/images/Hline-chrome.png b/test/screenshotter/images/Hline-chrome.png deleted file mode 100644 index 6a8ebb06..00000000 Binary files a/test/screenshotter/images/Hline-chrome.png and /dev/null differ diff --git a/test/screenshotter/images/Hline-firefox.png b/test/screenshotter/images/Hline-firefox.png deleted file mode 100644 index 6f3e5656..00000000 Binary files a/test/screenshotter/images/Hline-firefox.png and /dev/null differ diff --git a/test/screenshotter/ss_data.yaml b/test/screenshotter/ss_data.yaml index c84290f7..d521e34a 100644 --- a/test/screenshotter/ss_data.yaml +++ b/test/screenshotter/ss_data.yaml @@ -34,8 +34,8 @@ Alignedat: | \end{alignedat} Arrays: | \left(\begin{array}{|rl:c||} - 1&2&3\\ - 1+1&2+1&3+1\cr1\over2&\scriptstyle 1/2&\frac12\\[1ex] + 1&2&3\\ \hline + 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} \end{array}\right] ArrayMode: @@ -129,7 +129,6 @@ GroupMacros: \startExp: e^\bgroup \endExp: \egroup 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}} KaTeX: tex: \KaTeX