diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index 4ef6bada..e266e52a 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -4,12 +4,12 @@ */ import { IRenderLayer, IColorSet, IRenderDimensions } from './Types'; -import { ITerminal } from '../Types'; -import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, IGlyphIdentifier } from './atlas/Types'; +import { ITerminal, ICellData } from '../Types'; +import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, IGlyphIdentifier, DEFAULT_COLOR } from './atlas/Types'; import BaseCharAtlas from './atlas/BaseCharAtlas'; import { acquireCharAtlas } from './atlas/CharAtlasCache'; -import { is256Color } from './atlas/CharAtlasUtils'; import { CellData, AttributeData } from '../BufferLine'; +import { WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE } from '../Buffer'; export abstract class BaseRenderLayer implements IRenderLayer { private _canvas: HTMLCanvasElement; @@ -258,22 +258,34 @@ export abstract class BaseRenderLayer implements IRenderLayer { * This is used to validate whether a cached image can be used. * @param bold Whether the text is bold. */ - protected drawChars(terminal: ITerminal, chars: string, code: number, width: number, x: number, y: number, fg: number, bg: number, bold: boolean, dim: boolean, italic: boolean, cell: CellData): void { - const drawInBrightColor = terminal.options.drawBoldTextInBrightColors && bold && fg < 8 && fg !== INVERTED_DEFAULT_COLOR; + protected drawChars(terminal: ITerminal, cell: ICellData, x: number, y: number): void { - if (cell.isFgRGB()) { - this._drawUncachedChars(terminal, chars, width, fg, x, y, bold && terminal.options.enableBold, dim, italic, cell); + // skip cache right away if we draw in RGB + if (cell.isFgRGB() || (cell.isInverse() && cell.isBgRGB())) { + this._drawUncachedChars(terminal, cell, x, y); return; } + let fg; + let bg; + if (cell.isInverse()) { + fg = (cell.isBgDefault()) ? INVERTED_DEFAULT_COLOR : cell.getBgColor(); + bg = (cell.isFgDefault()) ? INVERTED_DEFAULT_COLOR : cell.getFgColor(); + } else { + bg = (cell.isBgDefault()) ? DEFAULT_COLOR : cell.getBgColor(); + fg = (cell.isFgDefault()) ? DEFAULT_COLOR : cell.getFgColor(); + } + + const drawInBrightColor = terminal.options.drawBoldTextInBrightColors && cell.isBold() && fg < 8 && fg !== INVERTED_DEFAULT_COLOR; + fg += drawInBrightColor ? 8 : 0; - this._currentGlyphIdentifier.chars = chars; - this._currentGlyphIdentifier.code = code; + this._currentGlyphIdentifier.chars = cell.chars || WHITESPACE_CELL_CHAR; + this._currentGlyphIdentifier.code = cell.code || WHITESPACE_CELL_CODE; this._currentGlyphIdentifier.bg = bg; this._currentGlyphIdentifier.fg = fg; - this._currentGlyphIdentifier.bold = bold && terminal.options.enableBold; - this._currentGlyphIdentifier.dim = dim; - this._currentGlyphIdentifier.italic = italic; + this._currentGlyphIdentifier.bold = cell.isBold() && terminal.options.enableBold; + this._currentGlyphIdentifier.dim = !!cell.isDim(); + this._currentGlyphIdentifier.italic = !!cell.isItalic(); const atlasDidDraw = this._charAtlas && this._charAtlas.draw( this._ctx, this._currentGlyphIdentifier, @@ -282,7 +294,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { ); if (!atlasDidDraw) { - this._drawUncachedChars(terminal, chars, width, fg, x, y, bold && terminal.options.enableBold, dim, italic); + this._drawUncachedChars(terminal, cell, x, y); } } @@ -297,32 +309,34 @@ export abstract class BaseRenderLayer implements IRenderLayer { * @param x The column to draw at. * @param y The row to draw at. */ - private _drawUncachedChars(terminal: ITerminal, chars: string, width: number, fg: number, x: number, y: number, bold: boolean, dim: boolean, italic: boolean, cell?: CellData): void { + private _drawUncachedChars(terminal: ITerminal, cell: ICellData, x: number, y: number): void { this._ctx.save(); - this._ctx.font = this._getFont(terminal, bold, italic); + this._ctx.font = this._getFont(terminal, cell.isBold() && terminal.options.enableBold, !!cell.isItalic()); this._ctx.textBaseline = 'middle'; - if (fg === INVERTED_DEFAULT_COLOR) { - this._ctx.fillStyle = this._colors.background.css; - } else if (is256Color(fg)) { - // 256 color support - this._ctx.fillStyle = this._colors.ansi[fg].css; - if (cell && cell.isFgRGB()) { - this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getFgColor()).join(',')})`; + if (cell.isInverse()) { + if (cell.isBgDefault()) { + this._ctx.fillStyle = this._colors.background.css; + } else if (cell.isBgRGB()) { + this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getBgColor()).join(',')})`; + } else { + this._ctx.fillStyle = this._colors.ansi[cell.getBgColor()].css; } - } else { - this._ctx.fillStyle = this._colors.foreground.css; + } else if (cell.isFgRGB()) { + this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getFgColor()).join(',')})`; + } else if (cell.isFgPalette()) { + this._ctx.fillStyle = this._colors.ansi[cell.getFgColor()].css; } this._clipRow(terminal, y); // Apply alpha to dim the character - if (dim) { + if (cell.isDim()) { this._ctx.globalAlpha = DIM_OPACITY; } // Draw the character this._ctx.fillText( - chars, + cell.chars, x * this._scaledCellWidth + this._scaledCharLeft, (y + 0.5) * this._scaledCellHeight + this._scaledCharTop); this._ctx.restore(); diff --git a/src/renderer/TextRenderLayer.ts b/src/renderer/TextRenderLayer.ts index e0ef0f2d..7ff2d002 100644 --- a/src/renderer/TextRenderLayer.ts +++ b/src/renderer/TextRenderLayer.ts @@ -3,14 +3,12 @@ * @license MIT */ -import { NULL_CELL_CODE, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE } from '../Buffer'; -import { FLAGS, IColorSet, IRenderDimensions, ICharacterJoinerRegistry } from './Types'; -import { CharData, ITerminal } from '../Types'; -import { INVERTED_DEFAULT_COLOR, DEFAULT_COLOR } from './atlas/Types'; +import { NULL_CELL_CODE } from '../Buffer'; +import { IColorSet, IRenderDimensions, ICharacterJoinerRegistry } from './Types'; +import { CharData, ITerminal, ICellData } from '../Types'; import { GridCache } from './GridCache'; import { BaseRenderLayer } from './BaseRenderLayer'; -import { is256Color } from './atlas/CharAtlasUtils'; -import { CellData, AttributeData } from '../BufferLine'; +import { CellData, AttributeData, Content } from '../BufferLine'; /** * This CharData looks like a null character, which will forc a clear and render @@ -59,14 +57,9 @@ export class TextRenderLayer extends BaseRenderLayer { lastRow: number, joinerRegistry: ICharacterJoinerRegistry | null, callback: ( - code: number, - chars: string, - width: number, + cell: ICellData, x: number, - y: number, - fg: number, - bg: number, - flags: number + y: number ) => void ): void { for (let y = firstRow; y <= lastRow; y++) { @@ -74,13 +67,8 @@ export class TextRenderLayer extends BaseRenderLayer { const line = terminal.buffer.lines.get(row); const joinedRanges = joinerRegistry ? joinerRegistry.getJoinedCharacters(row) : []; for (let x = 0; x < terminal.cols; x++) { - (line as any).loadCell(x, this._cell); - let code: number = this._cell.code || WHITESPACE_CELL_CODE; - - // Can either represent character(s) for a single cell or multiple cells - // if indicated by a character joiner. - let chars = this._cell.chars || WHITESPACE_CELL_CHAR; - let width = this._cell.width; + line.loadCell(x, this._cell); + let cell = this._cell; // If true, indicates that the current character(s) to draw were joined. let isJoined = false; @@ -88,7 +76,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) { + if (cell.width === 0) { continue; } @@ -101,14 +89,15 @@ export class TextRenderLayer extends BaseRenderLayer { // We already know the exact start and end column of the joined range, // so we get the string and width representing it directly - chars = terminal.buffer.translateBufferLineToString( - row, - true, - range[0], - range[1] - ); - width = range[1] - range[0]; - code = Infinity; + cell = CellData.fromCharData([ + 0, + line.translateToString(true, range[0], range[1]), + range[1] - range[0], + 0xFFFFFF + ]); + // hacky: patch attrs + cell.fg = this._cell.fg; + cell.bg = this._cell.bg; // Skip over the cells occupied by this range in the loop lastCharX = range[1] - 1; @@ -118,7 +107,7 @@ export class TextRenderLayer extends BaseRenderLayer { // right is a space, take ownership of the cell to the right. We skip // this check for joined characters because their rendering likely won't // yield the same result as rendering the last character individually. - if (!isJoined && this._isOverlapping(chars, width, code)) { + if (!isJoined && this._isOverlapping(cell)) { // If the character is overlapping, we want to force a re-render on every // frame. This is specifically to work around the case where two // overlaping chars `a` and `b` are adjacent, the cursor is moved to b and a @@ -127,7 +116,9 @@ export class TextRenderLayer extends BaseRenderLayer { // already in the correct state. // this._state.cache[x][y] = OVERLAP_OWNED_CHAR_DATA; if (lastCharX < line.length - 1 && line.getCodePoint(lastCharX + 1) === NULL_CELL_CODE) { - width = 2; + // patch width to 2 + cell.content &= ~Content.WIDTH_MASK; + cell.content |= 2 << Content.WIDTH_SHIFT; // 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 @@ -136,32 +127,10 @@ export class TextRenderLayer extends BaseRenderLayer { } } - const flags = this._cell.getOldFlags(); - let bg = this._cell.getOldBgColor(); - let fg = this._cell.getOldFgColor(); - - // If inverse flag is on, the foreground should become the background. - if (flags & FLAGS.INVERSE) { - const temp = bg; - bg = fg; - fg = temp; - if (fg === DEFAULT_COLOR) { - fg = INVERTED_DEFAULT_COLOR; - } - if (bg === DEFAULT_COLOR) { - bg = INVERTED_DEFAULT_COLOR; - } - } - callback( - code, - chars, - width, + cell, x, - y, - fg, - bg, - flags + y ); x = lastCharX; @@ -182,18 +151,23 @@ export class TextRenderLayer extends BaseRenderLayer { ctx.save(); - this._forEachCell(terminal, firstRow, lastRow, null, (code, chars, width, x, y, fg, bg, flags) => { + this._forEachCell(terminal, firstRow, lastRow, null, (cell, x, y) => { // libvte and xterm both draw the background (but not foreground) of invisible characters, // so we should too. let nextFillStyle = null; // null represents default background color - if (bg === INVERTED_DEFAULT_COLOR) { - nextFillStyle = this._colors.foreground.css; - } else if (is256Color(bg)) { - if (this._cell.isBgRGB()) { - nextFillStyle = `rgb(${AttributeData.toColorRGB(this._cell.getBgColor()).join(',')})`; + + if (cell.isInverse()) { + if (cell.isFgDefault()) { + nextFillStyle = this._colors.foreground.css; + } else if (cell.isFgRGB()) { + nextFillStyle = `rgb(${AttributeData.toColorRGB(cell.getFgColor()).join(',')})`; } else { - nextFillStyle = this._colors.ansi[bg].css; + nextFillStyle = this._colors.ansi[cell.getFgColor()].css; } + } else if (cell.isBgRGB()) { + nextFillStyle = `rgb(${AttributeData.toColorRGB(cell.getBgColor()).join(',')})`; + } else if (cell.isBgPalette()) { + nextFillStyle = this._colors.ansi[cell.getBgColor()].css; } if (prevFillStyle === null) { @@ -228,30 +202,31 @@ export class TextRenderLayer extends BaseRenderLayer { } private _drawForeground(terminal: ITerminal, firstRow: number, lastRow: number): void { - this._forEachCell(terminal, firstRow, lastRow, this._characterJoinerRegistry, (code, chars, width, x, y, fg, bg, flags) => { - if (flags & FLAGS.INVISIBLE) { + this._forEachCell(terminal, firstRow, lastRow, this._characterJoinerRegistry, (cell, x, y) => { + if (cell.isInvisible()) { return; } - if (flags & FLAGS.UNDERLINE) { + if (cell.isUnderline()) { this._ctx.save(); - if (fg === INVERTED_DEFAULT_COLOR) { - this._ctx.fillStyle = this._colors.background.css; - } else if (is256Color(fg)) { - // 256 color support - this._ctx.fillStyle = this._colors.ansi[fg].css; - } else { - this._ctx.fillStyle = this._colors.foreground.css; + + if (cell.isInverse()) { + if (cell.isBgDefault()) { + this._ctx.fillStyle = this._colors.background.css; + } else if (cell.isBgRGB()) { + this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getBgColor()).join(',')})`; + } else { + this._ctx.fillStyle = this._colors.ansi[cell.getBgColor()].css; + } + } else if (cell.isFgRGB()) { + this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getFgColor()).join(',')})`; + } else if (cell.isFgPalette()) { + this._ctx.fillStyle = this._colors.ansi[cell.getFgColor()].css; } - this.fillBottomLineAtCells(x, y, width); + + this.fillBottomLineAtCells(x, y, cell.width); this._ctx.restore(); } - this.drawChars( - terminal, chars, code, - width, x, y, - fg, bg, - !!(flags & FLAGS.BOLD), !!(flags & FLAGS.DIM), !!(flags & FLAGS.ITALIC), - this._cell - ); + this.drawChars(terminal, cell, x, y); }); } @@ -277,21 +252,21 @@ export class TextRenderLayer extends BaseRenderLayer { /** * Whether a character is overlapping to the next cell. */ - private _isOverlapping(char: string, width: number, code: number): boolean { + private _isOverlapping(cell: ICellData): boolean { // Only single cell characters can be overlapping, rendering issues can // occur without this check - if (width !== 1) { + if (cell.width !== 1) { return false; } // We assume that any ascii character will not overlap - if (code < 256) { + if (cell.code < 256) { return false; } // Deliver from cache if available - if (this._characterOverlapCache.hasOwnProperty(char)) { - return this._characterOverlapCache[char]; + if (this._characterOverlapCache.hasOwnProperty(cell.chars)) { + return this._characterOverlapCache[cell.chars]; } // Setup the font @@ -301,13 +276,13 @@ export class TextRenderLayer extends BaseRenderLayer { // Measure the width of the character, but Math.floor it // because that is what the renderer does when it calculates // the character dimensions we are comparing against - const overlaps = Math.floor(this._ctx.measureText(char).width) > this._characterWidth; + const overlaps = Math.floor(this._ctx.measureText(cell.chars).width) > this._characterWidth; // Restore the original context this._ctx.restore(); // Cache and return - this._characterOverlapCache[char] = overlaps; + this._characterOverlapCache[cell.chars] = overlaps; return overlaps; }