From 06b9f1db873068b940502e1a88b30f2baa76cbb7 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 5 Oct 2017 23:24:44 -0700 Subject: [PATCH] Always draw entire line and allow overlapping State/diffing code is kept in comments as it may be reinstated soon. Fixes #1035 Fixes #1036 Fixes #1032 Fixes #1037 --- src/renderer/BaseRenderLayer.ts | 4 ++-- src/renderer/CharAtlas.ts | 35 +++++++++++++++++++++++++++++-- src/renderer/ColorManager.ts | 15 ++++++++++++- src/renderer/TextRenderLayer.ts | 37 +++++++++++++++++++-------------- 4 files changed, 70 insertions(+), 21 deletions(-) diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index aa6c7e92..d15f1ee7 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -242,7 +242,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { const charAtlasCellWidth = this._scaledCharWidth + CHAR_ATLAS_CELL_SPACING; const charAtlasCellHeight = this._scaledCharHeight + CHAR_ATLAS_CELL_SPACING; this._ctx.drawImage(this._charAtlas, - code * charAtlasCellWidth, colorIndex * charAtlasCellHeight, this._scaledCharWidth, this._scaledCharHeight, + code * charAtlasCellWidth, colorIndex * charAtlasCellHeight, charAtlasCellWidth, this._scaledCharHeight, x * this._scaledCharWidth, y * this._scaledLineHeight + this._scaledLineDrawY, this._scaledCharWidth, this._scaledCharHeight); } else { this._drawUncachedChar(terminal, char, width, fg, x, y, bold); @@ -285,7 +285,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { // can bleed into other cells. This code will clip the following fillText, // ensuring that its contents don't go beyond the cell bounds. this._ctx.beginPath(); - this._ctx.rect(x * this._scaledCharWidth, y * this._scaledLineHeight + this._scaledLineDrawY, width * this._scaledCharWidth, this._scaledCharHeight); + this._ctx.rect(0, y * this._scaledLineHeight + this._scaledLineDrawY, terminal.cols * this._scaledCharWidth, this._scaledCharHeight); this._ctx.clip(); // Draw the character diff --git a/src/renderer/CharAtlas.ts b/src/renderer/CharAtlas.ts index 6808f85e..9ac1e161 100644 --- a/src/renderer/CharAtlas.ts +++ b/src/renderer/CharAtlas.ts @@ -142,13 +142,23 @@ class CharAtlasGenerator { // Default color for (let i = 0; i < 256; i++) { + this._ctx.save(); + this._ctx.beginPath(); + this._ctx.rect(i * cellWidth, 0, cellWidth, cellHeight); + this._ctx.clip(); this._ctx.fillText(String.fromCharCode(i), i * cellWidth, 0); + this._ctx.restore(); } // Default color bold this._ctx.save(); this._ctx.font = `bold ${this._ctx.font}`; for (let i = 0; i < 256; i++) { + this._ctx.save(); + this._ctx.beginPath(); + this._ctx.rect(i * cellWidth, cellHeight, cellWidth, cellHeight); + this._ctx.clip(); this._ctx.fillText(String.fromCharCode(i), i * cellWidth, cellHeight); + this._ctx.restore(); } this._ctx.restore(); @@ -162,14 +172,17 @@ class CharAtlasGenerator { const y = (colorIndex + 2) * cellHeight; // Draw ascii characters for (let i = 0; i < 256; i++) { + this._ctx.save(); + this._ctx.beginPath(); + this._ctx.rect(i * cellWidth, y, cellWidth, cellHeight); + this._ctx.clip(); this._ctx.fillStyle = ansiColors[colorIndex]; this._ctx.fillText(String.fromCharCode(i), i * cellWidth, y); + this._ctx.restore(); } } this._ctx.restore(); - const charAtlasImageData = this._ctx.getImageData(0, 0, this._canvas.width, this._canvas.height); - // Support is patchy for createImageBitmap at the moment, pass a canvas back // if support is lacking as drawImage works there too. Firefox is also // included here as ImageBitmap appears both buggy and has horrible @@ -183,9 +196,27 @@ class CharAtlasGenerator { return result; } + const charAtlasImageData = this._ctx.getImageData(0, 0, this._canvas.width, this._canvas.height); + + // Remove the background color from the image so characters may overlap + const r = parseInt(background.substr(1, 2), 16); + const g = parseInt(background.substr(3, 2), 16); + const b = parseInt(background.substr(5, 2), 16); + this._clearColor(charAtlasImageData, r, g, b); + const promise = window.createImageBitmap(charAtlasImageData); // Clear the rect while the promise is in progress this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); return promise; } + + private _clearColor(imageData: ImageData, r: number, g: number, b: number): void { + for (let offset = 0; offset < imageData.data.length; offset += 4) { + if (imageData.data[offset] === r && + imageData.data[offset + 1] === g && + imageData.data[offset + 2] === b) { + imageData.data[offset + 3] = 0; + } + } + } } diff --git a/src/renderer/ColorManager.ts b/src/renderer/ColorManager.ts index ea5f1cb0..80daf31f 100644 --- a/src/renderer/ColorManager.ts +++ b/src/renderer/ColorManager.ts @@ -86,7 +86,7 @@ export class ColorManager implements IColorManager { */ public setTheme(theme: ITheme): void { this.colors.foreground = theme.foreground || DEFAULT_FOREGROUND; - this.colors.background = theme.background || DEFAULT_BACKGROUND; + this.colors.background = this._validateColor(theme.background, DEFAULT_BACKGROUND); this.colors.cursor = theme.cursor || DEFAULT_CURSOR; this.colors.cursorAccent = theme.cursorAccent || DEFAULT_CURSOR_ACCENT; this.colors.selection = theme.selection || DEFAULT_SELECTION; @@ -107,4 +107,17 @@ export class ColorManager implements IColorManager { this.colors.ansi[14] = theme.brightCyan || DEFAULT_ANSI_COLORS[14]; this.colors.ansi[15] = theme.brightWhite || DEFAULT_ANSI_COLORS[15]; } + + private _validateColor(color: string, fallback: string): string { + if (color.length === 7 && color.charAt(0) === '#') { + return color; + } + if (color.length === 4 && color.charAt(0) === '#') { + const r = color.charAt(1); + const g = color.charAt(2); + const b = color.charAt(3); + return `#${r}${r}${g}${g}${b}${b}`; + } + return fallback; + } } diff --git a/src/renderer/TextRenderLayer.ts b/src/renderer/TextRenderLayer.ts index 86159af3..75c1dae8 100644 --- a/src/renderer/TextRenderLayer.ts +++ b/src/renderer/TextRenderLayer.ts @@ -59,6 +59,11 @@ export class TextRenderLayer extends BaseRenderLayer { const row = y + terminal.buffer.ydisp; const line = terminal.buffer.lines.get(row); + this.clearCells(0, y, terminal.cols, 1); + // for (let x = 0; x < terminal.cols; x++) { + // this._state.cache[x][y] = null; + // } + for (let x = 0; x < terminal.cols; x++) { const charData = line[x]; const code: number = charData[CHAR_DATA_CODE_INDEX]; @@ -69,7 +74,7 @@ export class TextRenderLayer extends BaseRenderLayer { // The character to the left is a wide character, drawing is owned by // the char at x-1 if (width === 0) { - this._state.cache[x][y] = null; + // this._state.cache[x][y] = null; continue; } @@ -86,19 +91,19 @@ export class TextRenderLayer extends BaseRenderLayer { } // Skip rendering if the character is identical - const state = this._state.cache[x][y]; - if (state && state[CHAR_DATA_CHAR_INDEX] === char && state[CHAR_DATA_ATTR_INDEX] === attr) { - // Skip render, contents are identical - this._state.cache[x][y] = charData; - continue; - } + // const state = this._state.cache[x][y]; + // if (state && state[CHAR_DATA_CHAR_INDEX] === char && state[CHAR_DATA_ATTR_INDEX] === attr) { + // // Skip render, contents are identical + // this._state.cache[x][y] = charData; + // continue; + // } // Clear the old character was not a space with the default background - const wasInverted = !!(state && state[CHAR_DATA_ATTR_INDEX] && state[CHAR_DATA_ATTR_INDEX] >> 18 & FLAGS.INVERSE); - if (state && !(state[CHAR_DATA_CODE_INDEX] === 32 /*' '*/ && (state[CHAR_DATA_ATTR_INDEX] & 0x1ff) >= 256 && !wasInverted)) { - this._clearChar(x, y); - } - this._state.cache[x][y] = charData; + // const wasInverted = !!(state && state[CHAR_DATA_ATTR_INDEX] && state[CHAR_DATA_ATTR_INDEX] >> 18 & FLAGS.INVERSE); + // if (state && !(state[CHAR_DATA_CODE_INDEX] === 32 /*' '*/ && (state[CHAR_DATA_ATTR_INDEX] & 0x1ff) >= 256 && !wasInverted)) { + // this._clearChar(x, y); + // } + // this._state.cache[x][y] = charData; const flags = attr >> 18; let bg = attr & 0x1ff; @@ -120,14 +125,14 @@ export class TextRenderLayer extends BaseRenderLayer { // space is added. Without this, the first half of `b` would never // get removed, and `a` would not re-render because it thinks it's // already in the correct state. - this._state.cache[x][y] = OVERLAP_OWNED_CHAR_DATA; + // this._state.cache[x][y] = OVERLAP_OWNED_CHAR_DATA; if (x < line.length - 1 && line[x + 1][CHAR_DATA_CODE_INDEX] === 32 /*' '*/) { width = 2; - this._clearChar(x + 1, y); + // this._clearChar(x + 1, y); // The overlapping char's char data will force a clear and render when the // overlapping char is no longer to the left of the character and also when // the space changes to another character. - this._state.cache[x + 1][y] = OVERLAP_OWNED_CHAR_DATA; + // this._state.cache[x + 1][y] = OVERLAP_OWNED_CHAR_DATA; } } @@ -148,7 +153,7 @@ export class TextRenderLayer extends BaseRenderLayer { // Clear the cell next to this character if it's wide if (width === 2) { - this.clearCells(x + 1, y, 1, 1); + // this.clearCells(x + 1, y, 1, 1); } // Draw background