apply CellData to canvas renderer

This commit is contained in:
Jörg Breitbart
2019-01-17 01:25:16 +01:00
parent ae50d84c5a
commit 8d850d6c8b
2 changed files with 102 additions and 113 deletions
+40 -26
View File
@@ -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();
+62 -87
View File
@@ -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;
}