diff --git a/src/browser/renderer/BaseRenderLayer.ts b/src/browser/renderer/BaseRenderLayer.ts index 0a9b8057..759c3129 100644 --- a/src/browser/renderer/BaseRenderLayer.ts +++ b/src/browser/renderer/BaseRenderLayer.ts @@ -188,14 +188,77 @@ export abstract class BaseRenderLayer implements IRenderLayer { * @param x The column to fill. * @param y The row to fill. */ - protected _fillBottomLineAtCells(x: number, y: number, width: number = 1): void { + protected _fillBottomLineAtCells(x: number, y: number, width: number = 1, pixelOffset: number = 0): void { this._ctx.fillRect( x * this._scaledCellWidth, - (y + 1) * this._scaledCellHeight - window.devicePixelRatio - 1 /* Ensure it's drawn within the cell */, + (y + 1) * this._scaledCellHeight + pixelOffset - window.devicePixelRatio - 1 /* Ensure it's drawn within the cell */, width * this._scaledCellWidth, window.devicePixelRatio); } + protected _curlyUnderlineAtCell(x: number, y: number, width: number = 1): void { + this._ctx.save(); + this._ctx.beginPath(); + this._ctx.strokeStyle = this._ctx.fillStyle; + this._ctx.lineWidth = window.devicePixelRatio; + for (let xOffset = 0; xOffset < width; xOffset++) { + const xLeft = (x + xOffset) * this._scaledCellWidth; + const xMid = (x + xOffset + 0.5) * this._scaledCellWidth; + const xRight = (x + xOffset + 1) * this._scaledCellWidth; + const yMid = (y + 1) * this._scaledCellHeight - window.devicePixelRatio - 1; + const yMidBot = yMid - window.devicePixelRatio; + const yMidTop = yMid + window.devicePixelRatio; + this._ctx.moveTo(xLeft, yMid); + this._ctx.bezierCurveTo( + xLeft, yMidBot, + xMid, yMidBot, + xMid, yMid + ); + this._ctx.bezierCurveTo( + xMid, yMidTop, + xRight, yMidTop, + xRight, yMid + ); + } + this._ctx.stroke(); + this._ctx.restore(); + } + + protected _dottedUnderlineAtCell(x: number, y: number, width: number = 1): void { + this._ctx.save(); + this._ctx.beginPath(); + this._ctx.strokeStyle = this._ctx.fillStyle; + this._ctx.lineWidth = window.devicePixelRatio; + this._ctx.setLineDash([window.devicePixelRatio * 2, window.devicePixelRatio]); + const xLeft = x * this._scaledCellWidth; + const yMid = (y + 1) * this._scaledCellHeight - window.devicePixelRatio - 1; + this._ctx.moveTo(xLeft, yMid); + for (let xOffset = 0; xOffset < width; xOffset++) { + // const xLeft = x * this._scaledCellWidth; + const xRight = (x + width + xOffset) * this._scaledCellWidth; + this._ctx.lineTo(xRight, yMid); + } + this._ctx.stroke(); + this._ctx.closePath(); + this._ctx.restore(); + } + + protected _dashedUnderlineAtCell(x: number, y: number, width: number = 1): void { + this._ctx.save(); + this._ctx.beginPath(); + this._ctx.strokeStyle = this._ctx.fillStyle; + this._ctx.lineWidth = window.devicePixelRatio; + this._ctx.setLineDash([window.devicePixelRatio * 4, window.devicePixelRatio * 3]); + const xLeft = x * this._scaledCellWidth; + const xRight = (x + width) * this._scaledCellWidth; + const yMid = (y + 1) * this._scaledCellHeight - window.devicePixelRatio - 1; + this._ctx.moveTo(xLeft, yMid); + this._ctx.lineTo(xRight, yMid); + this._ctx.stroke(); + this._ctx.closePath(); + this._ctx.restore(); + } + /** * Fills a 1px line (2px on HDPI) at the left of the cell. This uses the * existing fillStyle on the context. diff --git a/src/browser/renderer/TextRenderLayer.ts b/src/browser/renderer/TextRenderLayer.ts index a6fba850..d4173e1f 100644 --- a/src/browser/renderer/TextRenderLayer.ts +++ b/src/browser/renderer/TextRenderLayer.ts @@ -264,12 +264,17 @@ export class TextRenderLayer extends BaseRenderLayer { if (cell.isUnderline()) { switch (cell.extended.underlineStyle) { case UnderlineStyle.DOUBLE: + this._fillBottomLineAtCells(x, y, cell.getWidth(), -window.devicePixelRatio); + this._fillBottomLineAtCells(x, y, cell.getWidth(), window.devicePixelRatio); break; case UnderlineStyle.CURLY: + this._curlyUnderlineAtCell(x, y, cell.getWidth()); break; case UnderlineStyle.DOTTED: + this._dottedUnderlineAtCell(x, y, cell.getWidth()); break; case UnderlineStyle.DASHED: + this._dashedUnderlineAtCell(x, y, cell.getWidth()); break; case UnderlineStyle.SINGLE: default: