mirror of
https://github.com/wavetermdev/xterm.js.git
synced 2026-08-05 13:43:48 -07:00
apply CellData to canvas renderer
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user