Merge branch 'master' of https://github.com/xtermjs/xterm.js into linkParser
This commit is contained in:
@@ -1,28 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { CellData } from 'common/buffer/CellData';
|
||||
import { FLAGS } from './Constants';
|
||||
import { IBufferLine } from 'common/Types';
|
||||
|
||||
export function getCompatAttr(bufferLine: IBufferLine, index: number): number {
|
||||
// TODO: Need to move WebGL over to the new system and remove this block
|
||||
const cell = new CellData();
|
||||
bufferLine.loadCell(index, cell);
|
||||
const oldBg = cell.getBgColor() === -1 ? 256 : cell.getBgColor();
|
||||
const oldFg = cell.getFgColor() === -1 ? 256 : cell.getFgColor();
|
||||
const oldAttr =
|
||||
(cell.isBold() ? FLAGS.BOLD : 0) |
|
||||
(cell.isUnderline() ? FLAGS.UNDERLINE : 0) |
|
||||
(cell.isBlink() ? FLAGS.BLINK : 0) |
|
||||
(cell.isInverse() ? FLAGS.INVERSE : 0) |
|
||||
(cell.isDim() ? FLAGS.DIM : 0) |
|
||||
(cell.isItalic() ? FLAGS.ITALIC : 0);
|
||||
const attrCompat =
|
||||
oldBg |
|
||||
(oldFg << 9) |
|
||||
(oldAttr << 18);
|
||||
return attrCompat;
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
/**
|
||||
* @license MIT
|
||||
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
|
||||
*/
|
||||
|
||||
import { IColor } from 'browser/Types';
|
||||
|
||||
export function getLuminance(color: IColor): number {
|
||||
// Coefficients taken from: https://www.w3.org/TR/AERT/#color-contrast
|
||||
const r = color.rgba >> 24 & 0xff;
|
||||
const g = color.rgba >> 16 & 0xff;
|
||||
const b = color.rgba >> 8 & 0xff;
|
||||
return (0.299 * r + 0.587 * g + 0.114 * b) / 255;
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
// TODO: Should be removed after chardata workaround is fixed
|
||||
export const enum FLAGS {
|
||||
BOLD = 1,
|
||||
UNDERLINE = 2,
|
||||
BLINK = 4,
|
||||
INVERSE = 8,
|
||||
INVISIBLE = 16,
|
||||
DIM = 32,
|
||||
ITALIC = 64
|
||||
}
|
||||
@@ -6,15 +6,14 @@
|
||||
import { createProgram, PROJECTION_MATRIX, throwIfFalsy } from './WebglUtils';
|
||||
import { WebglCharAtlas } from './atlas/WebglCharAtlas';
|
||||
import { IWebGL2RenderingContext, IWebGLVertexArrayObject, IRenderModel, IRasterizedGlyph } from './Types';
|
||||
import { INDICIES_PER_CELL } from './WebglRenderer';
|
||||
import { COMBINED_CHAR_BIT_MASK } from './RenderModel';
|
||||
import { COMBINED_CHAR_BIT_MASK, RENDER_MODEL_INDICIES_PER_CELL, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_BG_OFFSET } from './RenderModel';
|
||||
import { fill } from 'common/TypedArrayUtils';
|
||||
import { slice } from './TypedArray';
|
||||
import { NULL_CELL_CODE, WHITESPACE_CELL_CODE } from 'common/buffer/Constants';
|
||||
import { getLuminance } from './ColorUtils';
|
||||
import { NULL_CELL_CODE, WHITESPACE_CELL_CODE, Attributes, FgFlags } from 'common/buffer/Constants';
|
||||
import { Terminal, IBufferLine } from 'xterm';
|
||||
import { IColorSet } from 'browser/Types';
|
||||
import { IColorSet, IColor } from 'browser/Types';
|
||||
import { IRenderDimensions } from 'browser/renderer/Types';
|
||||
import { AttributeData } from 'common/buffer/AttributeData';
|
||||
|
||||
interface IVertices {
|
||||
attributes: Float32Array;
|
||||
@@ -103,7 +102,6 @@ export class GlyphRenderer {
|
||||
private _dimensions: IRenderDimensions
|
||||
) {
|
||||
const gl = this._gl;
|
||||
|
||||
const program = throwIfFalsy(createProgram(gl, vertexShaderSource, fragmentShaderSource));
|
||||
this._program = program;
|
||||
|
||||
@@ -169,11 +167,11 @@ export class GlyphRenderer {
|
||||
return this._atlas ? this._atlas.beginFrame() : true;
|
||||
}
|
||||
|
||||
public updateCell(x: number, y: number, code: number, attr: number, bg: number, fg: number, chars: string): void {
|
||||
this._updateCell(this._vertices.attributes, x, y, code, attr, bg, fg, chars);
|
||||
public updateCell(x: number, y: number, code: number, bg: number, fg: number, chars: string): void {
|
||||
this._updateCell(this._vertices.attributes, x, y, code, bg, fg, chars);
|
||||
}
|
||||
|
||||
private _updateCell(array: Float32Array, x: number, y: number, code: number | undefined, attr: number, bg: number, fg: number, chars?: string): void {
|
||||
private _updateCell(array: Float32Array, x: number, y: number, code: number | undefined, bg: number, fg: number, chars?: string): void {
|
||||
const terminal = this._terminal;
|
||||
|
||||
const i = (y * terminal.cols + x) * INDICES_PER_CELL;
|
||||
@@ -186,12 +184,12 @@ export class GlyphRenderer {
|
||||
|
||||
let rasterizedGlyph: IRasterizedGlyph;
|
||||
if (!this._atlas) {
|
||||
throw new Error('atlas must be set before updating cell');
|
||||
return;
|
||||
}
|
||||
if (chars && chars.length > 1) {
|
||||
rasterizedGlyph = this._atlas.getRasterizedGlyphCombinedChar(chars, attr, bg, fg);
|
||||
rasterizedGlyph = this._atlas.getRasterizedGlyphCombinedChar(chars, bg, fg);
|
||||
} else {
|
||||
rasterizedGlyph = this._atlas.getRasterizedGlyph(code, attr, bg, fg);
|
||||
rasterizedGlyph = this._atlas.getRasterizedGlyph(code, bg, fg);
|
||||
}
|
||||
|
||||
// Fill empty if no glyph was found
|
||||
@@ -220,62 +218,86 @@ export class GlyphRenderer {
|
||||
|
||||
this._vertices.selectionAttributes = slice(this._vertices.attributes, 0);
|
||||
|
||||
// TODO: Make fg and bg configurable, currently since the buffer doesn't
|
||||
// support truecolor the char atlas cannot store it.
|
||||
const lumi = getLuminance(this._colors.background);
|
||||
const fg = lumi > 0.5 ? 7 : 0;
|
||||
const bg = lumi > 0.5 ? 0 : 7;
|
||||
const bg = (this._colors.selectionOpaque.rgba >>> 8) | Attributes.CM_RGB;
|
||||
|
||||
if (columnSelectMode) {
|
||||
const startCol = model.selection.startCol;
|
||||
const width = model.selection.endCol - startCol;
|
||||
const height = model.selection.viewportCappedEndRow - model.selection.viewportCappedStartRow + 1;
|
||||
for (let y = model.selection.viewportCappedStartRow; y < model.selection.viewportCappedStartRow + height; y++) {
|
||||
this._updateSelectionRange(startCol, startCol + width, y, model, bg, fg);
|
||||
this._updateSelectionRange(startCol, startCol + width, y, model, bg);
|
||||
}
|
||||
} else {
|
||||
// Draw first row
|
||||
const startCol = model.selection.viewportStartRow === model.selection.viewportCappedStartRow ? model.selection.startCol : 0;
|
||||
const startRowEndCol = model.selection.viewportCappedStartRow === model.selection.viewportCappedEndRow ? model.selection.endCol : terminal.cols;
|
||||
this._updateSelectionRange(startCol, startRowEndCol, model.selection.viewportCappedStartRow, model, bg, fg);
|
||||
this._updateSelectionRange(startCol, startRowEndCol, model.selection.viewportCappedStartRow, model, bg);
|
||||
|
||||
// Draw middle rows
|
||||
const middleRowsCount = Math.max(model.selection.viewportCappedEndRow - model.selection.viewportCappedStartRow - 1, 0);
|
||||
for (let y = model.selection.viewportCappedStartRow + 1; y <= model.selection.viewportCappedStartRow + middleRowsCount; y++) {
|
||||
this._updateSelectionRange(0, startRowEndCol, y, model, bg, fg);
|
||||
this._updateSelectionRange(0, startRowEndCol, y, model, bg);
|
||||
}
|
||||
|
||||
// Draw final row
|
||||
if (model.selection.viewportCappedStartRow !== model.selection.viewportCappedEndRow) {
|
||||
// Only draw viewportEndRow if it's not the same as viewportStartRow
|
||||
const endCol = model.selection.viewportEndRow === model.selection.viewportCappedEndRow ? model.selection.endCol : terminal.cols;
|
||||
this._updateSelectionRange(0, endCol, model.selection.viewportCappedEndRow, model, bg, fg);
|
||||
this._updateSelectionRange(0, endCol, model.selection.viewportCappedEndRow, model, bg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _updateSelectionRange(startCol: number, endCol: number, y: number, model: IRenderModel, bg: number, fg: number): void {
|
||||
private _updateSelectionRange(startCol: number, endCol: number, y: number, model: IRenderModel, bg: number): void {
|
||||
const terminal = this._terminal;
|
||||
const row = y + terminal.buffer.viewportY;
|
||||
let line: IBufferLine | undefined;
|
||||
for (let x = startCol; x < endCol; x++) {
|
||||
const offset = (y * this._terminal.cols + x) * INDICIES_PER_CELL;
|
||||
// Because the cache uses attr as a lookup key it needs to contain the selection colors as well
|
||||
let attr = model.cells[offset + 1];
|
||||
attr = attr & ~0x3ffff | bg << 9 | fg;
|
||||
const offset = (y * this._terminal.cols + x) * RENDER_MODEL_INDICIES_PER_CELL;
|
||||
const code = model.cells[offset];
|
||||
let fg = model.cells[offset + RENDER_MODEL_FG_OFFSET];
|
||||
if (fg & FgFlags.INVERSE) {
|
||||
const workCell = new AttributeData();
|
||||
workCell.fg = fg;
|
||||
workCell.bg = model.cells[offset + RENDER_MODEL_BG_OFFSET];
|
||||
// Get attributes from fg (excluding inverse) and resolve inverse by pullibng rgb colors
|
||||
// from bg. This is needed since the inverse fg color should be based on the original bg
|
||||
// color, not on the selection color
|
||||
fg = (fg & ~(Attributes.CM_MASK | Attributes.RGB_MASK | FgFlags.INVERSE));
|
||||
switch (workCell.getBgColorMode()) {
|
||||
case Attributes.CM_P16:
|
||||
case Attributes.CM_P256:
|
||||
const c = this._getColorFromAnsiIndex(workCell.getBgColor()).rgba;
|
||||
fg |= (c >> 8) & Attributes.RED_MASK | (c >> 8) & Attributes.GREEN_MASK | (c >> 8) & Attributes.BLUE_MASK;
|
||||
case Attributes.CM_RGB:
|
||||
const arr = AttributeData.toColorRGB(workCell.getBgColor());
|
||||
fg |= arr[0] << Attributes.RED_SHIFT | arr[1] << Attributes.GREEN_SHIFT | arr[2] << Attributes.BLUE_SHIFT;
|
||||
case Attributes.CM_DEFAULT:
|
||||
default:
|
||||
const c2 = this._colors.background.rgba;
|
||||
fg |= (c2 >> 8) & Attributes.RED_MASK | (c2 >> 8) & Attributes.GREEN_MASK | (c2 >> 8) & Attributes.BLUE_MASK;
|
||||
}
|
||||
fg |= Attributes.CM_RGB;
|
||||
}
|
||||
if (code & COMBINED_CHAR_BIT_MASK) {
|
||||
if (!line) {
|
||||
line = terminal.buffer.getLine(row);
|
||||
}
|
||||
const chars = line!.getCell(x)!.char;
|
||||
this._updateCell(this._vertices.selectionAttributes, x, y, model.cells[offset], attr, bg, fg, chars);
|
||||
const chars = line!.getCell(x)!.getChars();
|
||||
this._updateCell(this._vertices.selectionAttributes, x, y, model.cells[offset], bg, fg, chars);
|
||||
} else {
|
||||
this._updateCell(this._vertices.selectionAttributes, x, y, model.cells[offset], attr, bg, fg);
|
||||
this._updateCell(this._vertices.selectionAttributes, x, y, model.cells[offset], bg, fg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _getColorFromAnsiIndex(idx: number): IColor {
|
||||
if (idx >= this._colors.ansi.length) {
|
||||
throw new Error('No color found for idx ' + idx);
|
||||
}
|
||||
return this._colors.ansi[idx];
|
||||
}
|
||||
|
||||
public onResize(): void {
|
||||
const terminal = this._terminal;
|
||||
const gl = this._gl;
|
||||
|
||||
@@ -6,12 +6,11 @@
|
||||
import { createProgram, expandFloat32Array, PROJECTION_MATRIX, throwIfFalsy } from './WebglUtils';
|
||||
import { IRenderModel, IWebGLVertexArrayObject, IWebGL2RenderingContext, ISelectionRenderModel } from './Types';
|
||||
import { fill } from 'common/TypedArrayUtils';
|
||||
import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants';
|
||||
import { is256Color } from './atlas/CharAtlasUtils';
|
||||
import { DEFAULT_COLOR } from 'common/buffer/Constants';
|
||||
import { Attributes, FgFlags } from 'common/buffer/Constants';
|
||||
import { Terminal } from 'xterm';
|
||||
import { IColorSet, IColor } from 'browser/Types';
|
||||
import { IRenderDimensions } from 'browser/renderer/Types';
|
||||
import { RENDER_MODEL_BG_OFFSET, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_INDICIES_PER_CELL } from './RenderModel';
|
||||
|
||||
const enum VertexAttribLocations {
|
||||
POSITION = 0,
|
||||
@@ -23,13 +22,13 @@ const enum VertexAttribLocations {
|
||||
const vertexShaderSource = `#version 300 es
|
||||
layout (location = ${VertexAttribLocations.POSITION}) in vec2 a_position;
|
||||
layout (location = ${VertexAttribLocations.SIZE}) in vec2 a_size;
|
||||
layout (location = ${VertexAttribLocations.COLOR}) in vec3 a_color;
|
||||
layout (location = ${VertexAttribLocations.COLOR}) in vec4 a_color;
|
||||
layout (location = ${VertexAttribLocations.UNIT_QUAD}) in vec2 a_unitquad;
|
||||
|
||||
uniform mat4 u_projection;
|
||||
uniform vec2 u_resolution;
|
||||
|
||||
out vec3 v_color;
|
||||
out vec4 v_color;
|
||||
|
||||
void main() {
|
||||
vec2 zeroToOne = (a_position + (a_unitquad * a_size)) / u_resolution;
|
||||
@@ -40,12 +39,12 @@ void main() {
|
||||
const fragmentShaderSource = `#version 300 es
|
||||
precision lowp float;
|
||||
|
||||
in vec3 v_color;
|
||||
in vec4 v_color;
|
||||
|
||||
out vec4 outColor;
|
||||
|
||||
void main() {
|
||||
outColor = vec4(v_color, 1);
|
||||
outColor = v_color;
|
||||
}`;
|
||||
|
||||
interface IVertices {
|
||||
@@ -156,7 +155,7 @@ export class RectangleRenderer {
|
||||
|
||||
private _updateCachedColors(): void {
|
||||
this._bgFloat = this._colorToFloat32Array(this._colors.background);
|
||||
this._selectionFloat = this._colorToFloat32Array(this._colors.selection);
|
||||
this._selectionFloat = this._colorToFloat32Array(this._colors.selectionOpaque);
|
||||
}
|
||||
|
||||
private _updateViewportRectangle(): void {
|
||||
@@ -247,47 +246,73 @@ export class RectangleRenderer {
|
||||
|
||||
for (let y = 0; y < terminal.rows; y++) {
|
||||
let currentStartX = -1;
|
||||
let currentBg = DEFAULT_COLOR;
|
||||
let currentBg = 0;
|
||||
let currentFg = 0;
|
||||
let currentInverse = false;
|
||||
for (let x = 0; x < terminal.cols; x++) {
|
||||
const modelIndex = ((y * terminal.cols) + x) * 4;
|
||||
const bg = model.cells[modelIndex + 2];
|
||||
if (bg !== currentBg) {
|
||||
const modelIndex = ((y * terminal.cols) + x) * RENDER_MODEL_INDICIES_PER_CELL;
|
||||
const bg = model.cells[modelIndex + RENDER_MODEL_BG_OFFSET];
|
||||
const fg = model.cells[modelIndex + RENDER_MODEL_FG_OFFSET];
|
||||
const inverse = !!(fg & FgFlags.INVERSE);
|
||||
if (bg !== currentBg || (fg !== currentFg && (currentInverse || inverse))) {
|
||||
// A rectangle needs to be drawn if going from non-default to another color
|
||||
if (currentBg !== DEFAULT_COLOR) {
|
||||
if (currentBg !== 0 || (currentInverse && currentFg !== 0)) {
|
||||
const offset = rectangleCount++ * INDICES_PER_RECTANGLE;
|
||||
this._updateRectangle(vertices, offset, currentBg, currentStartX, x, y);
|
||||
this._updateRectangle(vertices, offset, currentFg, currentBg, currentStartX, x, y);
|
||||
}
|
||||
currentStartX = x;
|
||||
currentBg = bg;
|
||||
currentFg = fg;
|
||||
currentInverse = inverse;
|
||||
}
|
||||
}
|
||||
// Finish rectangle if it's still going
|
||||
if (currentBg !== DEFAULT_COLOR) {
|
||||
if (currentBg !== 0 || (currentInverse && currentFg !== 0)) {
|
||||
const offset = rectangleCount++ * INDICES_PER_RECTANGLE;
|
||||
this._updateRectangle(vertices, offset, currentBg, currentStartX, terminal.cols, y);
|
||||
this._updateRectangle(vertices, offset, currentFg, currentBg, currentStartX, terminal.cols, y);
|
||||
}
|
||||
}
|
||||
vertices.count = rectangleCount;
|
||||
}
|
||||
|
||||
private _updateRectangle(vertices: IVertices, offset: number, bg: number, startX: number, endX: number, y: number): void {
|
||||
let color: IColor | null = null;
|
||||
if (bg === INVERTED_DEFAULT_COLOR) {
|
||||
color = this._colors.foreground;
|
||||
} else if (is256Color(bg)) {
|
||||
color = this._colors.ansi[bg];
|
||||
private _updateRectangle(vertices: IVertices, offset: number, fg: number, bg: number, startX: number, endX: number, y: number): void {
|
||||
let rgba: number | undefined;
|
||||
if (fg & FgFlags.INVERSE) {
|
||||
switch (fg & Attributes.CM_MASK) {
|
||||
case Attributes.CM_P16:
|
||||
case Attributes.CM_P256:
|
||||
rgba = this._colors.ansi[fg & Attributes.PCOLOR_MASK].rgba;
|
||||
break;
|
||||
case Attributes.CM_RGB:
|
||||
rgba = (fg & Attributes.RGB_MASK) << 8;
|
||||
break;
|
||||
case Attributes.CM_DEFAULT:
|
||||
default:
|
||||
rgba = this._colors.foreground.rgba;
|
||||
}
|
||||
} else {
|
||||
// TODO: Add support for true color
|
||||
color = this._colors.foreground;
|
||||
switch (bg & Attributes.CM_MASK) {
|
||||
case Attributes.CM_P16:
|
||||
case Attributes.CM_P256:
|
||||
rgba = this._colors.ansi[bg & Attributes.PCOLOR_MASK].rgba;
|
||||
break;
|
||||
case Attributes.CM_RGB:
|
||||
rgba = (bg & Attributes.RGB_MASK) << 8;
|
||||
break;
|
||||
case Attributes.CM_DEFAULT:
|
||||
default:
|
||||
rgba = this._colors.background.rgba;
|
||||
}
|
||||
}
|
||||
|
||||
if (vertices.attributes.length < offset + 4) {
|
||||
vertices.attributes = expandFloat32Array(vertices.attributes, this._terminal.rows * this._terminal.cols * INDICES_PER_RECTANGLE);
|
||||
}
|
||||
const x1 = startX * this._dimensions.scaledCellWidth;
|
||||
const y1 = y * this._dimensions.scaledCellHeight;
|
||||
const r = ((color.rgba >> 24) & 0xFF) / 255;
|
||||
const g = ((color.rgba >> 16) & 0xFF) / 255;
|
||||
const b = ((color.rgba >> 8 ) & 0xFF) / 255;
|
||||
const r = ((rgba >> 24) & 0xFF) / 255;
|
||||
const g = ((rgba >> 16) & 0xFF) / 255;
|
||||
const b = ((rgba >> 8 ) & 0xFF) / 255;
|
||||
|
||||
this._addRectangle(vertices.attributes, offset, x1, y1, (endX - startX) * this._dimensions.scaledCellWidth, this._dimensions.scaledCellHeight, r, g, b, 1);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,9 @@
|
||||
import { IRenderModel, ISelectionRenderModel } from './Types';
|
||||
import { fill } from 'common/TypedArrayUtils';
|
||||
|
||||
export const RENDER_MODEL_INDICIES_PER_CELL = 4;
|
||||
export const RENDER_MODEL_INDICIES_PER_CELL = 3;
|
||||
export const RENDER_MODEL_BG_OFFSET = 1;
|
||||
export const RENDER_MODEL_FG_OFFSET = 2;
|
||||
|
||||
export const COMBINED_CHAR_BIT_MASK = 0x80000000;
|
||||
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
export interface IRasterizedGlyphSet {
|
||||
[flags: number]: IRasterizedGlyph;
|
||||
[bg: number]: { [fg: number]: IRasterizedGlyph } | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,20 +9,35 @@ import { IRenderService } from 'browser/services/Services';
|
||||
import { IColorSet } from 'browser/Types';
|
||||
|
||||
export class WebglAddon implements ITerminalAddon {
|
||||
private _terminal?: Terminal;
|
||||
private _renderer?: WebglRenderer;
|
||||
|
||||
constructor(
|
||||
private _preserveDrawingBuffer?: boolean
|
||||
) {}
|
||||
|
||||
public activate(terminal: Terminal): void {
|
||||
if (!terminal.element) {
|
||||
throw new Error('Cannot activate WebglRendererAddon before Terminal.open');
|
||||
throw new Error('Cannot activate WebglAddon before Terminal.open');
|
||||
}
|
||||
this._terminal = terminal;
|
||||
const renderService: IRenderService = (<any>terminal)._core._renderService;
|
||||
const colors: IColorSet = (<any>terminal)._core._colorManager.colors;
|
||||
renderService.setRenderer(new WebglRenderer(terminal, colors, this._preserveDrawingBuffer));
|
||||
this._renderer = new WebglRenderer(terminal, colors, this._preserveDrawingBuffer);
|
||||
renderService.setRenderer(this._renderer);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
throw new Error('WebglRendererAddon.dispose Not yet implemented');
|
||||
if (!this._terminal) {
|
||||
throw new Error('Cannot dispose WebglAddon because it is activated');
|
||||
}
|
||||
const renderService: IRenderService = (<any>this._terminal)._core._renderService;
|
||||
renderService.setRenderer((<any>this._terminal)._core._createRenderer());
|
||||
renderService.onResize(this._terminal.cols, this._terminal.rows);
|
||||
this._renderer = undefined;
|
||||
}
|
||||
|
||||
public get textureAtlas(): HTMLCanvasElement | undefined {
|
||||
return this._renderer?.textureAtlas;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,10 @@
|
||||
*/
|
||||
|
||||
import * as puppeteer from 'puppeteer';
|
||||
import { assert } from 'chai';
|
||||
import { ITerminalOptions } from '../../../src/Types';
|
||||
import { ITheme } from 'xterm';
|
||||
import { assert } from 'chai';
|
||||
import deepEqual = require('deep-equal');
|
||||
|
||||
const APP = 'http://127.0.0.1:3000/test';
|
||||
|
||||
@@ -16,31 +17,20 @@ const width = 800;
|
||||
const height = 600;
|
||||
|
||||
describe('WebGL Renderer Integration Tests', function(): void {
|
||||
this.timeout(20000);
|
||||
|
||||
before(async function(): Promise<any> {
|
||||
browser = await puppeteer.launch({
|
||||
headless: process.argv.indexOf('--headless') !== -1,
|
||||
slowMo: 80,
|
||||
args: [`--window-size=${width},${height}`, `--no-sandbox`]
|
||||
});
|
||||
page = (await browser.pages())[0];
|
||||
await page.setViewport({ width, height });
|
||||
await page.goto(APP);
|
||||
await openTerminal();
|
||||
await page.evaluate(`window.term.loadAddon(new WebglAddon(true));`);
|
||||
it('dispose removes renderer canvases', async () => {
|
||||
await setupBrowser();
|
||||
assert.equal(await page.evaluate(`document.querySelectorAll('.xterm canvas').length`), 3);
|
||||
await page.evaluate(`addon.dispose()`);
|
||||
assert.equal(await page.evaluate(`document.querySelectorAll('.xterm canvas').length`), 0);
|
||||
await browser.close();
|
||||
});
|
||||
|
||||
after(() => {
|
||||
browser.close();
|
||||
});
|
||||
describe('colors', () => {
|
||||
before(async () => setupBrowser());
|
||||
after(async () => browser.close());
|
||||
beforeEach(async () => page.evaluate(`window.term.reset()`));
|
||||
|
||||
beforeEach(async () => {
|
||||
await page.evaluate(`window.term.reset()`);
|
||||
});
|
||||
|
||||
describe('WebGL Renderer', () => {
|
||||
it('foreground colors normal', async function(): Promise<any> {
|
||||
it('foreground 0-15', async () => {
|
||||
const theme: ITheme = {
|
||||
black: '#010203',
|
||||
red: '#040506',
|
||||
@@ -53,17 +43,17 @@ describe('WebGL Renderer Integration Tests', function(): void {
|
||||
};
|
||||
await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`);
|
||||
await writeSync(`\\x1b[30m█\\x1b[31m█\\x1b[32m█\\x1b[33m█\\x1b[34m█\\x1b[35m█\\x1b[36m█\\x1b[37m█`);
|
||||
assert.deepEqual(await getCellColor(1, 1), [1, 2, 3, 255]);
|
||||
assert.deepEqual(await getCellColor(2, 1), [4, 5, 6, 255]);
|
||||
assert.deepEqual(await getCellColor(3, 1), [7, 8, 9, 255]);
|
||||
assert.deepEqual(await getCellColor(4, 1), [10, 11, 12, 255]);
|
||||
assert.deepEqual(await getCellColor(5, 1), [13, 14, 15, 255]);
|
||||
assert.deepEqual(await getCellColor(6, 1), [16, 17, 18, 255]);
|
||||
assert.deepEqual(await getCellColor(7, 1), [19, 20, 21, 255]);
|
||||
assert.deepEqual(await getCellColor(8, 1), [22, 23, 24, 255]);
|
||||
await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]);
|
||||
await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]);
|
||||
await pollFor(page, () => getCellColor(3, 1), [7, 8, 9, 255]);
|
||||
await pollFor(page, () => getCellColor(4, 1), [10, 11, 12, 255]);
|
||||
await pollFor(page, () => getCellColor(5, 1), [13, 14, 15, 255]);
|
||||
await pollFor(page, () => getCellColor(6, 1), [16, 17, 18, 255]);
|
||||
await pollFor(page, () => getCellColor(7, 1), [19, 20, 21, 255]);
|
||||
await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]);
|
||||
});
|
||||
|
||||
it('foreground colors bright', async function(): Promise<any> {
|
||||
it('foreground 0-7 drawBoldTextInBrightColors', async () => {
|
||||
const theme: ITheme = {
|
||||
brightBlack: '#010203',
|
||||
brightRed: '#040506',
|
||||
@@ -74,19 +64,22 @@ describe('WebGL Renderer Integration Tests', function(): void {
|
||||
brightCyan: '#131415',
|
||||
brightWhite: '#161718'
|
||||
};
|
||||
await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`);
|
||||
await writeSync(`\\x1b[90m█\\x1b[91m█\\x1b[92m█\\x1b[93m█\\x1b[94m█\\x1b[95m█\\x1b[96m█\\x1b[97m█`);
|
||||
assert.deepEqual(await getCellColor(1, 1), [1, 2, 3, 255]);
|
||||
assert.deepEqual(await getCellColor(2, 1), [4, 5, 6, 255]);
|
||||
assert.deepEqual(await getCellColor(3, 1), [7, 8, 9, 255]);
|
||||
assert.deepEqual(await getCellColor(4, 1), [10, 11, 12, 255]);
|
||||
assert.deepEqual(await getCellColor(5, 1), [13, 14, 15, 255]);
|
||||
assert.deepEqual(await getCellColor(6, 1), [16, 17, 18, 255]);
|
||||
assert.deepEqual(await getCellColor(7, 1), [19, 20, 21, 255]);
|
||||
assert.deepEqual(await getCellColor(8, 1), [22, 23, 24, 255]);
|
||||
await page.evaluate(`
|
||||
window.term.setOption('theme', ${JSON.stringify(theme)});
|
||||
window.term.setOption('drawBoldTextInBrightColors', true);
|
||||
`);
|
||||
await writeSync(`\\x1b[1;30m█\\x1b[1;31m█\\x1b[1;32m█\\x1b[1;33m█\\x1b[1;34m█\\x1b[1;35m█\\x1b[1;36m█\\x1b[1;37m█`);
|
||||
await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]);
|
||||
await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]);
|
||||
await pollFor(page, () => getCellColor(3, 1), [7, 8, 9, 255]);
|
||||
await pollFor(page, () => getCellColor(4, 1), [10, 11, 12, 255]);
|
||||
await pollFor(page, () => getCellColor(5, 1), [13, 14, 15, 255]);
|
||||
await pollFor(page, () => getCellColor(6, 1), [16, 17, 18, 255]);
|
||||
await pollFor(page, () => getCellColor(7, 1), [19, 20, 21, 255]);
|
||||
await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]);
|
||||
});
|
||||
|
||||
it('background colors normal', async function(): Promise<any> {
|
||||
it('background 0-15', async () => {
|
||||
const theme: ITheme = {
|
||||
black: '#010203',
|
||||
red: '#040506',
|
||||
@@ -99,17 +92,132 @@ describe('WebGL Renderer Integration Tests', function(): void {
|
||||
};
|
||||
await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`);
|
||||
await writeSync(`\\x1b[40m \\x1b[41m \\x1b[42m \\x1b[43m \\x1b[44m \\x1b[45m \\x1b[46m \\x1b[47m `);
|
||||
assert.deepEqual(await getCellColor(1, 1), [1, 2, 3, 255]);
|
||||
assert.deepEqual(await getCellColor(2, 1), [4, 5, 6, 255]);
|
||||
assert.deepEqual(await getCellColor(3, 1), [7, 8, 9, 255]);
|
||||
assert.deepEqual(await getCellColor(4, 1), [10, 11, 12, 255]);
|
||||
assert.deepEqual(await getCellColor(5, 1), [13, 14, 15, 255]);
|
||||
assert.deepEqual(await getCellColor(6, 1), [16, 17, 18, 255]);
|
||||
assert.deepEqual(await getCellColor(7, 1), [19, 20, 21, 255]);
|
||||
assert.deepEqual(await getCellColor(8, 1), [22, 23, 24, 255]);
|
||||
await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]);
|
||||
await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]);
|
||||
await pollFor(page, () => getCellColor(3, 1), [7, 8, 9, 255]);
|
||||
await pollFor(page, () => getCellColor(4, 1), [10, 11, 12, 255]);
|
||||
await pollFor(page, () => getCellColor(5, 1), [13, 14, 15, 255]);
|
||||
await pollFor(page, () => getCellColor(6, 1), [16, 17, 18, 255]);
|
||||
await pollFor(page, () => getCellColor(7, 1), [19, 20, 21, 255]);
|
||||
await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]);
|
||||
});
|
||||
|
||||
it('background colors bright', async function(): Promise<any> {
|
||||
it('foreground 0-15 inverse', async () => {
|
||||
const theme: ITheme = {
|
||||
black: '#010203',
|
||||
red: '#040506',
|
||||
green: '#070809',
|
||||
yellow: '#0a0b0c',
|
||||
blue: '#0d0e0f',
|
||||
magenta: '#101112',
|
||||
cyan: '#131415',
|
||||
white: '#161718'
|
||||
};
|
||||
await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`);
|
||||
await writeSync(`\\x1b[7;30m \\x1b[7;31m \\x1b[7;32m \\x1b[7;33m \\x1b[7;34m \\x1b[7;35m \\x1b[7;36m \\x1b[7;37m `);
|
||||
await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]);
|
||||
await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]);
|
||||
await pollFor(page, () => getCellColor(3, 1), [7, 8, 9, 255]);
|
||||
await pollFor(page, () => getCellColor(4, 1), [10, 11, 12, 255]);
|
||||
await pollFor(page, () => getCellColor(5, 1), [13, 14, 15, 255]);
|
||||
await pollFor(page, () => getCellColor(6, 1), [16, 17, 18, 255]);
|
||||
await pollFor(page, () => getCellColor(7, 1), [19, 20, 21, 255]);
|
||||
await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]);
|
||||
});
|
||||
|
||||
it('background 0-15 inverse', async () => {
|
||||
const theme: ITheme = {
|
||||
black: '#010203',
|
||||
red: '#040506',
|
||||
green: '#070809',
|
||||
yellow: '#0a0b0c',
|
||||
blue: '#0d0e0f',
|
||||
magenta: '#101112',
|
||||
cyan: '#131415',
|
||||
white: '#161718'
|
||||
};
|
||||
await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`);
|
||||
await writeSync(`\\x1b[7;40m█\\x1b[7;41m█\\x1b[7;42m█\\x1b[7;43m█\\x1b[7;44m█\\x1b[7;45m█\\x1b[7;46m█\\x1b[7;47m█`);
|
||||
await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]);
|
||||
await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]);
|
||||
await pollFor(page, () => getCellColor(3, 1), [7, 8, 9, 255]);
|
||||
await pollFor(page, () => getCellColor(4, 1), [10, 11, 12, 255]);
|
||||
await pollFor(page, () => getCellColor(5, 1), [13, 14, 15, 255]);
|
||||
await pollFor(page, () => getCellColor(6, 1), [16, 17, 18, 255]);
|
||||
await pollFor(page, () => getCellColor(7, 1), [19, 20, 21, 255]);
|
||||
await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]);
|
||||
});
|
||||
|
||||
it('foreground 0-15 inivisible', async () => {
|
||||
const theme: ITheme = {
|
||||
black: '#010203',
|
||||
red: '#040506',
|
||||
green: '#070809',
|
||||
yellow: '#0a0b0c',
|
||||
blue: '#0d0e0f',
|
||||
magenta: '#101112',
|
||||
cyan: '#131415',
|
||||
white: '#161718'
|
||||
};
|
||||
await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`);
|
||||
await writeSync(`\\x1b[8;30m \\x1b[8;31m \\x1b[8;32m \\x1b[8;33m \\x1b[8;34m \\x1b[8;35m \\x1b[8;36m \\x1b[8;37m `);
|
||||
await pollFor(page, () => getCellColor(1, 1), [0, 0, 0, 255]);
|
||||
await pollFor(page, () => getCellColor(2, 1), [0, 0, 0, 255]);
|
||||
await pollFor(page, () => getCellColor(3, 1), [0, 0, 0, 255]);
|
||||
await pollFor(page, () => getCellColor(4, 1), [0, 0, 0, 255]);
|
||||
await pollFor(page, () => getCellColor(5, 1), [0, 0, 0, 255]);
|
||||
await pollFor(page, () => getCellColor(6, 1), [0, 0, 0, 255]);
|
||||
await pollFor(page, () => getCellColor(7, 1), [0, 0, 0, 255]);
|
||||
await pollFor(page, () => getCellColor(8, 1), [0, 0, 0, 255]);
|
||||
});
|
||||
|
||||
it('background 0-15 inivisible', async () => {
|
||||
const theme: ITheme = {
|
||||
black: '#010203',
|
||||
red: '#040506',
|
||||
green: '#070809',
|
||||
yellow: '#0a0b0c',
|
||||
blue: '#0d0e0f',
|
||||
magenta: '#101112',
|
||||
cyan: '#131415',
|
||||
white: '#161718'
|
||||
};
|
||||
await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`);
|
||||
await writeSync(`\\x1b[8;40m█\\x1b[8;41m█\\x1b[8;42m█\\x1b[8;43m█\\x1b[8;44m█\\x1b[8;45m█\\x1b[8;46m█\\x1b[8;47m█`);
|
||||
await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]);
|
||||
await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]);
|
||||
await pollFor(page, () => getCellColor(3, 1), [7, 8, 9, 255]);
|
||||
await pollFor(page, () => getCellColor(4, 1), [10, 11, 12, 255]);
|
||||
await pollFor(page, () => getCellColor(5, 1), [13, 14, 15, 255]);
|
||||
await pollFor(page, () => getCellColor(6, 1), [16, 17, 18, 255]);
|
||||
await pollFor(page, () => getCellColor(7, 1), [19, 20, 21, 255]);
|
||||
await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]);
|
||||
});
|
||||
|
||||
it('foreground 0-15 bright', async () => {
|
||||
const theme: ITheme = {
|
||||
brightBlack: '#010203',
|
||||
brightRed: '#040506',
|
||||
brightGreen: '#070809',
|
||||
brightYellow: '#0a0b0c',
|
||||
brightBlue: '#0d0e0f',
|
||||
brightMagenta: '#101112',
|
||||
brightCyan: '#131415',
|
||||
brightWhite: '#161718'
|
||||
};
|
||||
await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`);
|
||||
await writeSync(`\\x1b[90m█\\x1b[91m█\\x1b[92m█\\x1b[93m█\\x1b[94m█\\x1b[95m█\\x1b[96m█\\x1b[97m█`);
|
||||
await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]);
|
||||
await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]);
|
||||
await pollFor(page, () => getCellColor(3, 1), [7, 8, 9, 255]);
|
||||
await pollFor(page, () => getCellColor(4, 1), [10, 11, 12, 255]);
|
||||
await pollFor(page, () => getCellColor(5, 1), [13, 14, 15, 255]);
|
||||
await pollFor(page, () => getCellColor(6, 1), [16, 17, 18, 255]);
|
||||
await pollFor(page, () => getCellColor(7, 1), [19, 20, 21, 255]);
|
||||
await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]);
|
||||
});
|
||||
|
||||
it('background 0-15 bright', async () => {
|
||||
const theme: ITheme = {
|
||||
brightBlack: '#010203',
|
||||
brightRed: '#040506',
|
||||
@@ -122,14 +230,638 @@ describe('WebGL Renderer Integration Tests', function(): void {
|
||||
};
|
||||
await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`);
|
||||
await writeSync(`\\x1b[100m \\x1b[101m \\x1b[102m \\x1b[103m \\x1b[104m \\x1b[105m \\x1b[106m \\x1b[107m `);
|
||||
assert.deepEqual(await getCellColor(1, 1), [1, 2, 3, 255]);
|
||||
assert.deepEqual(await getCellColor(2, 1), [4, 5, 6, 255]);
|
||||
assert.deepEqual(await getCellColor(3, 1), [7, 8, 9, 255]);
|
||||
assert.deepEqual(await getCellColor(4, 1), [10, 11, 12, 255]);
|
||||
assert.deepEqual(await getCellColor(5, 1), [13, 14, 15, 255]);
|
||||
assert.deepEqual(await getCellColor(6, 1), [16, 17, 18, 255]);
|
||||
assert.deepEqual(await getCellColor(7, 1), [19, 20, 21, 255]);
|
||||
assert.deepEqual(await getCellColor(8, 1), [22, 23, 24, 255]);
|
||||
await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]);
|
||||
await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]);
|
||||
await pollFor(page, () => getCellColor(3, 1), [7, 8, 9, 255]);
|
||||
await pollFor(page, () => getCellColor(4, 1), [10, 11, 12, 255]);
|
||||
await pollFor(page, () => getCellColor(5, 1), [13, 14, 15, 255]);
|
||||
await pollFor(page, () => getCellColor(6, 1), [16, 17, 18, 255]);
|
||||
await pollFor(page, () => getCellColor(7, 1), [19, 20, 21, 255]);
|
||||
await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]);
|
||||
});
|
||||
|
||||
it('foreground 16-255', async () => {
|
||||
let data = '';
|
||||
for (let y = 0; y < 240 / 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
data += `\\x1b[38;5;${16 + y * 16 + x}m█\x1b[0m`;
|
||||
}
|
||||
data += '\\r\\n';
|
||||
}
|
||||
await writeSync(data);
|
||||
for (let y = 0; y < 240 / 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const cssColor = COLORS_16_TO_255[y * 16 + x];
|
||||
const r = parseInt(cssColor.substr(1, 2), 16);
|
||||
const g = parseInt(cssColor.substr(3, 2), 16);
|
||||
const b = parseInt(cssColor.substr(5, 2), 16);
|
||||
await pollFor(page, () => getCellColor(x + 1, y + 1), [r, g, b, 255]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('background 16-255', async () => {
|
||||
let data = '';
|
||||
for (let y = 0; y < 240 / 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
data += `\\x1b[48;5;${16 + y * 16 + x}m \x1b[0m`;
|
||||
}
|
||||
data += '\\r\\n';
|
||||
}
|
||||
await writeSync(data);
|
||||
for (let y = 0; y < 240 / 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const cssColor = COLORS_16_TO_255[y * 16 + x];
|
||||
const r = parseInt(cssColor.substr(1, 2), 16);
|
||||
const g = parseInt(cssColor.substr(3, 2), 16);
|
||||
const b = parseInt(cssColor.substr(5, 2), 16);
|
||||
await pollFor(page, () => getCellColor(x + 1, y + 1), [r, g, b, 255]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('foreground 16-255 inverse', async () => {
|
||||
let data = '';
|
||||
for (let y = 0; y < 240 / 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
data += `\\x1b[7;38;5;${16 + y * 16 + x}m \x1b[0m`;
|
||||
}
|
||||
data += '\\r\\n';
|
||||
}
|
||||
await writeSync(data);
|
||||
for (let y = 0; y < 240 / 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const cssColor = COLORS_16_TO_255[y * 16 + x];
|
||||
const r = parseInt(cssColor.substr(1, 2), 16);
|
||||
const g = parseInt(cssColor.substr(3, 2), 16);
|
||||
const b = parseInt(cssColor.substr(5, 2), 16);
|
||||
await pollFor(page, () => getCellColor(x + 1, y + 1), [r, g, b, 255]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('background 16-255 inverse', async () => {
|
||||
let data = '';
|
||||
for (let y = 0; y < 240 / 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
data += `\\x1b[7;48;5;${16 + y * 16 + x}m█\x1b[0m`;
|
||||
}
|
||||
data += '\\r\\n';
|
||||
}
|
||||
await writeSync(data);
|
||||
for (let y = 0; y < 240 / 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const cssColor = COLORS_16_TO_255[y * 16 + x];
|
||||
const r = parseInt(cssColor.substr(1, 2), 16);
|
||||
const g = parseInt(cssColor.substr(3, 2), 16);
|
||||
const b = parseInt(cssColor.substr(5, 2), 16);
|
||||
await pollFor(page, () => getCellColor(x + 1, y + 1), [r, g, b, 255]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('foreground 16-255 invisible', async () => {
|
||||
let data = '';
|
||||
for (let y = 0; y < 240 / 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
data += `\\x1b[8;38;5;${16 + y * 16 + x}m \x1b[0m`;
|
||||
}
|
||||
data += '\\r\\n';
|
||||
}
|
||||
await writeSync(data);
|
||||
for (let y = 0; y < 240 / 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const cssColor = COLORS_16_TO_255[y * 16 + x];
|
||||
const r = parseInt(cssColor.substr(1, 2), 16);
|
||||
const g = parseInt(cssColor.substr(3, 2), 16);
|
||||
const b = parseInt(cssColor.substr(5, 2), 16);
|
||||
await pollFor(page, () => getCellColor(x + 1, y + 1), [0, 0, 0, 255]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('background 16-255 invisible', async () => {
|
||||
let data = '';
|
||||
for (let y = 0; y < 240 / 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
data += `\\x1b[8;48;5;${16 + y * 16 + x}m█\x1b[0m`;
|
||||
}
|
||||
data += '\\r\\n';
|
||||
}
|
||||
await writeSync(data);
|
||||
for (let y = 0; y < 240 / 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const cssColor = COLORS_16_TO_255[y * 16 + x];
|
||||
const r = parseInt(cssColor.substr(1, 2), 16);
|
||||
const g = parseInt(cssColor.substr(3, 2), 16);
|
||||
const b = parseInt(cssColor.substr(5, 2), 16);
|
||||
await pollFor(page, () => getCellColor(x + 1, y + 1), [r, g, b, 255]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('foreground true color red', async () => {
|
||||
let data = '';
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
data += `\\x1b[38;2;${i};0;0m█\x1b[0m`;
|
||||
}
|
||||
data += '\\r\\n';
|
||||
}
|
||||
await writeSync(data);
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
await pollFor(page, () => getCellColor(x + 1, y + 1), [i, 0, 0, 255]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('background true color red', async () => {
|
||||
let data = '';
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
data += `\\x1b[48;2;${i};0;0m \x1b[0m`;
|
||||
}
|
||||
data += '\\r\\n';
|
||||
}
|
||||
await writeSync(data);
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
await pollFor(page, () => getCellColor(x + 1, y + 1), [i, 0, 0, 255]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('foreground true color green', async () => {
|
||||
let data = '';
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
data += `\\x1b[38;2;0;${i};0m█\x1b[0m`;
|
||||
}
|
||||
data += '\\r\\n';
|
||||
}
|
||||
await writeSync(data);
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
await pollFor(page, () => getCellColor(x + 1, y + 1), [0, i, 0, 255]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('background true color green', async () => {
|
||||
let data = '';
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
data += `\\x1b[48;2;0;${i};0m \x1b[0m`;
|
||||
}
|
||||
data += '\\r\\n';
|
||||
}
|
||||
await writeSync(data);
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
await pollFor(page, () => getCellColor(x + 1, y + 1), [0, i, 0, 255]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('foreground true color blue', async () => {
|
||||
let data = '';
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
data += `\\x1b[38;2;0;0;${i}m█\x1b[0m`;
|
||||
}
|
||||
data += '\\r\\n';
|
||||
}
|
||||
await writeSync(data);
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
await pollFor(page, () => getCellColor(x + 1, y + 1), [0, 0, i, 255]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('background true color blue', async () => {
|
||||
let data = '';
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
data += `\\x1b[48;2;0;0;${i}m \x1b[0m`;
|
||||
}
|
||||
data += '\\r\\n';
|
||||
}
|
||||
await writeSync(data);
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
await pollFor(page, () => getCellColor(x + 1, y + 1), [0, 0, i, 255]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('foreground true color grey', async () => {
|
||||
let data = '';
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
data += `\\x1b[38;2;${i};${i};${i}m█\x1b[0m`;
|
||||
}
|
||||
data += '\\r\\n';
|
||||
}
|
||||
await writeSync(data);
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
await pollFor(page, () => getCellColor(x + 1, y + 1), [i, i, i, 255]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('background true color grey', async () => {
|
||||
let data = '';
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
data += `\\x1b[48;2;${i};${i};${i}m \x1b[0m`;
|
||||
}
|
||||
data += '\\r\\n';
|
||||
}
|
||||
await writeSync(data);
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
await pollFor(page, () => getCellColor(x + 1, y + 1), [i, i, i, 255]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('foreground true color red inverse', async function(): Promise<void> {
|
||||
let data = '';
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
data += `\x1b[7;38;2;${i};0;0m \x1b[0m`;
|
||||
}
|
||||
data += '\\r\\n';
|
||||
}
|
||||
await writeSync(data);
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
await pollFor(page, () => getCellColor(x + 1, y + 1), [i, 0, 0, 255]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('background true color red inverse', async function(): Promise<void> {
|
||||
let data = '';
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
data += `\\x1b[7;48;2;${i};0;0m█\\x1b[0m`;
|
||||
}
|
||||
data += '\\r\\n';
|
||||
}
|
||||
await writeSync(data);
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
await pollFor(page, () => getCellColor(x + 1, y + 1), [i, 0, 0, 255]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('foreground true color green inverse', async () => {
|
||||
let data = '';
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
data += `\\x1b[7;38;2;0;${i};0m \x1b[0m`;
|
||||
}
|
||||
data += '\\r\\n';
|
||||
}
|
||||
await writeSync(data);
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
await pollFor(page, () => getCellColor(x + 1, y + 1), [0, i, 0, 255]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('background true color green inverse', async () => {
|
||||
let data = '';
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
data += `\\x1b[7;48;2;0;${i};0m█\x1b[0m`;
|
||||
}
|
||||
data += '\\r\\n';
|
||||
}
|
||||
await writeSync(data);
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
await pollFor(page, () => getCellColor(x + 1, y + 1), [0, i, 0, 255]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('foreground true color blue inverse', async () => {
|
||||
let data = '';
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
data += `\\x1b[7;38;2;0;0;${i}m \x1b[0m`;
|
||||
}
|
||||
data += '\\r\\n';
|
||||
}
|
||||
await writeSync(data);
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
await pollFor(page, () => getCellColor(x + 1, y + 1), [0, 0, i, 255]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('background true color blue inverse', async () => {
|
||||
let data = '';
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
data += `\\x1b[7;48;2;0;0;${i}m█\x1b[0m`;
|
||||
}
|
||||
data += '\\r\\n';
|
||||
}
|
||||
await writeSync(data);
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
await pollFor(page, () => getCellColor(x + 1, y + 1), [0, 0, i, 255]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('foreground true color grey inverse', async () => {
|
||||
let data = '';
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
data += `\\x1b[7;38;2;${i};${i};${i}m \x1b[0m`;
|
||||
}
|
||||
data += '\\r\\n';
|
||||
}
|
||||
await writeSync(data);
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
await pollFor(page, () => getCellColor(x + 1, y + 1), [i, i, i, 255]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('background true color grey inverse', async () => {
|
||||
let data = '';
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
data += `\\x1b[7;48;2;${i};${i};${i}m█\x1b[0m`;
|
||||
}
|
||||
data += '\\r\\n';
|
||||
}
|
||||
await writeSync(data);
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
await pollFor(page, () => getCellColor(x + 1, y + 1), [i, i, i, 255]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('foreground true color grey invisible', async () => {
|
||||
let data = '';
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
data += `\\x1b[8;38;2;${i};${i};${i}m \x1b[0m`;
|
||||
}
|
||||
data += '\\r\\n';
|
||||
}
|
||||
await writeSync(data);
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
await pollFor(page, () => getCellColor(x + 1, y + 1), [0, 0, 0, 255]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('background true color grey invisible', async () => {
|
||||
let data = '';
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
data += `\\x1b[8;48;2;${i};${i};${i}m█\x1b[0m`;
|
||||
}
|
||||
data += '\\r\\n';
|
||||
}
|
||||
await writeSync(data);
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
await pollFor(page, () => getCellColor(x + 1, y + 1), [i, i, i, 255]);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('minimumContrastRatio', async () => {
|
||||
before(async () => setupBrowser());
|
||||
after(async () => browser.close());
|
||||
beforeEach(async () => page.evaluate(`window.term.reset()`));
|
||||
|
||||
it('should adjust 0-15 colors on black background', async () => {
|
||||
const theme: ITheme = {
|
||||
black: '#2e3436',
|
||||
red: '#cc0000',
|
||||
green: '#4e9a06',
|
||||
yellow: '#c4a000',
|
||||
blue: '#3465a4',
|
||||
magenta: '#75507b',
|
||||
cyan: '#06989a',
|
||||
white: '#d3d7cf',
|
||||
brightBlack: '#555753',
|
||||
brightRed: '#ef2929',
|
||||
brightGreen: '#8ae234',
|
||||
brightYellow: '#fce94f',
|
||||
brightBlue: '#729fcf',
|
||||
brightMagenta: '#ad7fa8',
|
||||
brightCyan: '#34e2e2',
|
||||
brightWhite: '#eeeeec'
|
||||
};
|
||||
await page.evaluate(`
|
||||
window.term.setOption('theme', ${JSON.stringify(theme)});
|
||||
window.term.setOption('minimumContrastRatio', 1);
|
||||
`);
|
||||
await writeSync(
|
||||
`\\x1b[30m█\\x1b[31m█\\x1b[32m█\\x1b[33m█\\x1b[34m█\\x1b[35m█\\x1b[36m█\\x1b[37m█\\r\\n` +
|
||||
`\\x1b[90m█\\x1b[91m█\\x1b[92m█\\x1b[93m█\\x1b[94m█\\x1b[95m█\\x1b[96m█\\x1b[97m█`
|
||||
);
|
||||
// Validate before minimumContrastRatio is applied
|
||||
await pollFor(page, () => getCellColor(1, 1), [0x2e, 0x34, 0x36, 255]);
|
||||
await pollFor(page, () => getCellColor(2, 1), [0xcc, 0x00, 0x00, 255]);
|
||||
await pollFor(page, () => getCellColor(3, 1), [0x4e, 0x9a, 0x06, 255]);
|
||||
await pollFor(page, () => getCellColor(4, 1), [0xc4, 0xa0, 0x00, 255]);
|
||||
await pollFor(page, () => getCellColor(5, 1), [0x34, 0x65, 0xa4, 255]);
|
||||
await pollFor(page, () => getCellColor(6, 1), [0x75, 0x50, 0x7b, 255]);
|
||||
await pollFor(page, () => getCellColor(7, 1), [0x06, 0x98, 0x9a, 255]);
|
||||
await pollFor(page, () => getCellColor(8, 1), [0xd3, 0xd7, 0xcf, 255]);
|
||||
await pollFor(page, () => getCellColor(1, 2), [0x55, 0x57, 0x53, 255]);
|
||||
await pollFor(page, () => getCellColor(2, 2), [0xef, 0x29, 0x29, 255]);
|
||||
await pollFor(page, () => getCellColor(3, 2), [0x8a, 0xe2, 0x34, 255]);
|
||||
await pollFor(page, () => getCellColor(4, 2), [0xfc, 0xe9, 0x4f, 255]);
|
||||
await pollFor(page, () => getCellColor(5, 2), [0x72, 0x9f, 0xcf, 255]);
|
||||
await pollFor(page, () => getCellColor(6, 2), [0xad, 0x7f, 0xa8, 255]);
|
||||
await pollFor(page, () => getCellColor(7, 2), [0x34, 0xe2, 0xe2, 255]);
|
||||
await pollFor(page, () => getCellColor(8, 2), [0xee, 0xee, 0xec, 255]);
|
||||
// Setting and check for minimum contrast values, note that these are note
|
||||
// exact to the contrast ratio, if the increase luminance algorithm
|
||||
// changes then these will probably fail
|
||||
await page.evaluate(`window.term.setOption('minimumContrastRatio', 10);`);
|
||||
await pollFor(page, () => getCellColor(1, 1), [176, 180, 180, 255]);
|
||||
await pollFor(page, () => getCellColor(2, 1), [238, 158, 158, 255]);
|
||||
await pollFor(page, () => getCellColor(3, 1), [197, 223, 171, 255]);
|
||||
await pollFor(page, () => getCellColor(4, 1), [235, 221, 158, 255]);
|
||||
await pollFor(page, () => getCellColor(5, 1), [124, 156, 198, 255]);
|
||||
await pollFor(page, () => getCellColor(6, 1), [183, 165, 187, 255]);
|
||||
await pollFor(page, () => getCellColor(7, 1), [110, 197, 198, 255]);
|
||||
await pollFor(page, () => getCellColor(8, 1), [211, 215, 207, 255]);
|
||||
await pollFor(page, () => getCellColor(1, 2), [183, 185, 183, 255]);
|
||||
await pollFor(page, () => getCellColor(2, 2), [249, 156, 156, 255]);
|
||||
await pollFor(page, () => getCellColor(3, 2), [138, 226, 52, 255]);
|
||||
await pollFor(page, () => getCellColor(4, 2), [252, 233, 79, 255]);
|
||||
await pollFor(page, () => getCellColor(5, 2), [114, 159, 207, 255]);
|
||||
await pollFor(page, () => getCellColor(6, 2), [190, 152, 185, 255]);
|
||||
// Unchanged
|
||||
await pollFor(page, () => getCellColor(7, 2), [0x34, 0xe2, 0xe2, 255]);
|
||||
await pollFor(page, () => getCellColor(8, 2), [0xee, 0xee, 0xec, 255]);
|
||||
});
|
||||
|
||||
it('should adjust 0-15 colors on white background', async () => {
|
||||
const theme: ITheme = {
|
||||
background: '#ffffff',
|
||||
black: '#2e3436',
|
||||
red: '#cc0000',
|
||||
green: '#4e9a06',
|
||||
yellow: '#c4a000',
|
||||
blue: '#3465a4',
|
||||
magenta: '#75507b',
|
||||
cyan: '#06989a',
|
||||
white: '#d3d7cf',
|
||||
brightBlack: '#555753',
|
||||
brightRed: '#ef2929',
|
||||
brightGreen: '#8ae234',
|
||||
brightYellow: '#fce94f',
|
||||
brightBlue: '#729fcf',
|
||||
brightMagenta: '#ad7fa8',
|
||||
brightCyan: '#34e2e2',
|
||||
brightWhite: '#eeeeec'
|
||||
};
|
||||
await page.evaluate(`
|
||||
window.term.setOption('theme', ${JSON.stringify(theme)});
|
||||
window.term.setOption('minimumContrastRatio', 1);
|
||||
`);
|
||||
await writeSync(
|
||||
`\\x1b[30m█\\x1b[31m█\\x1b[32m█\\x1b[33m█\\x1b[34m█\\x1b[35m█\\x1b[36m█\\x1b[37m█\\r\\n` +
|
||||
`\\x1b[90m█\\x1b[91m█\\x1b[92m█\\x1b[93m█\\x1b[94m█\\x1b[95m█\\x1b[96m█\\x1b[97m█`
|
||||
);
|
||||
// Validate before minimumContrastRatio is applied
|
||||
await pollFor(page, () => getCellColor(1, 1), [0x2e, 0x34, 0x36, 255]);
|
||||
await pollFor(page, () => getCellColor(2, 1), [0xcc, 0x00, 0x00, 255]);
|
||||
await pollFor(page, () => getCellColor(3, 1), [0x4e, 0x9a, 0x06, 255]);
|
||||
await pollFor(page, () => getCellColor(4, 1), [0xc4, 0xa0, 0x00, 255]);
|
||||
await pollFor(page, () => getCellColor(5, 1), [0x34, 0x65, 0xa4, 255]);
|
||||
await pollFor(page, () => getCellColor(6, 1), [0x75, 0x50, 0x7b, 255]);
|
||||
await pollFor(page, () => getCellColor(7, 1), [0x06, 0x98, 0x9a, 255]);
|
||||
await pollFor(page, () => getCellColor(8, 1), [0xd3, 0xd7, 0xcf, 255]);
|
||||
await pollFor(page, () => getCellColor(1, 2), [0x55, 0x57, 0x53, 255]);
|
||||
await pollFor(page, () => getCellColor(2, 2), [0xef, 0x29, 0x29, 255]);
|
||||
await pollFor(page, () => getCellColor(3, 2), [0x8a, 0xe2, 0x34, 255]);
|
||||
await pollFor(page, () => getCellColor(4, 2), [0xfc, 0xe9, 0x4f, 255]);
|
||||
await pollFor(page, () => getCellColor(5, 2), [0x72, 0x9f, 0xcf, 255]);
|
||||
await pollFor(page, () => getCellColor(6, 2), [0xad, 0x7f, 0xa8, 255]);
|
||||
await pollFor(page, () => getCellColor(7, 2), [0x34, 0xe2, 0xe2, 255]);
|
||||
await pollFor(page, () => getCellColor(8, 2), [0xee, 0xee, 0xec, 255]);
|
||||
// Setting and check for minimum contrast values, note that these are note
|
||||
// exact to the contrast ratio, if the increase luminance algorithm
|
||||
// changes then these will probably fail
|
||||
await page.evaluate(`window.term.setOption('minimumContrastRatio', 10);`);
|
||||
await pollFor(page, () => getCellColor(1, 1), [46, 52, 54, 255]);
|
||||
await pollFor(page, () => getCellColor(2, 1), [132, 0, 0, 255]);
|
||||
await pollFor(page, () => getCellColor(3, 1), [78, 154, 6, 255]);
|
||||
await pollFor(page, () => getCellColor(4, 1), [114, 93, 0, 255]);
|
||||
await pollFor(page, () => getCellColor(5, 1), [19, 40, 68, 255]);
|
||||
await pollFor(page, () => getCellColor(6, 1), [60, 40, 64, 255]);
|
||||
await pollFor(page, () => getCellColor(7, 1), [0, 71, 72, 255]);
|
||||
await pollFor(page, () => getCellColor(8, 1), [64, 64, 63, 255]);
|
||||
await pollFor(page, () => getCellColor(1, 2), [61, 63, 59, 255]);
|
||||
await pollFor(page, () => getCellColor(2, 2), [125, 19, 19, 255]);
|
||||
await pollFor(page, () => getCellColor(3, 2), [89, 146, 32, 255]);
|
||||
await pollFor(page, () => getCellColor(4, 2), [105, 98, 32, 255]);
|
||||
await pollFor(page, () => getCellColor(5, 2), [36, 52, 70, 255]);
|
||||
await pollFor(page, () => getCellColor(6, 2), [64, 45, 63, 255]);
|
||||
await pollFor(page, () => getCellColor(7, 2), [13, 67, 67, 255]);
|
||||
await pollFor(page, () => getCellColor(8, 2), [64, 64, 64, 255]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('selection', async () => {
|
||||
before(async () => setupBrowser());
|
||||
after(async () => browser.close());
|
||||
beforeEach(async () => page.evaluate(`window.term.reset()`));
|
||||
|
||||
it('should resolve the inverse foreground color based on the original background color, not the selection', async () => {
|
||||
const theme: ITheme = {
|
||||
foreground: '#FF0000',
|
||||
background: '#00FF00',
|
||||
selection: '#0000FF'
|
||||
};
|
||||
await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`);
|
||||
await writeSync(` █\\x1b[7m█\\x1b[0m`);
|
||||
await pollFor(page, () => getCellColor(1, 1), [0, 255, 0, 255]);
|
||||
await pollFor(page, () => getCellColor(2, 1), [255, 0, 0, 255]);
|
||||
await pollFor(page, () => getCellColor(3, 1), [0, 255, 0, 255]);
|
||||
await page.evaluate(`window.term.selectAll()`);
|
||||
// Selection only cell needs to be first to ensure renderer has kicked in
|
||||
await pollFor(page, () => getCellColor(1, 1), [0, 0, 255, 255]);
|
||||
await pollFor(page, () => getCellColor(2, 1), [255, 0, 0, 255]);
|
||||
await pollFor(page, () => getCellColor(3, 1), [0, 255, 0, 255]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('allowTransparency', async () => {
|
||||
before(async () => setupBrowser({ rendererType: 'dom', allowTransparency: true}));
|
||||
after(async () => browser.close());
|
||||
beforeEach(async () => page.evaluate(`window.term.reset()`));
|
||||
it('transparent background inverse', async () => {
|
||||
const theme: ITheme = {
|
||||
background: '#ff000080'
|
||||
};
|
||||
await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`);
|
||||
const data = `\\x1b[7m█\x1b[0m`;
|
||||
await writeSync(data);
|
||||
// Inverse background should be opaque
|
||||
await pollFor(page, () => getCellColor(1, 1), [255, 0, 0, 255]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -161,3 +893,48 @@ async function getCellColor(col: number, row: number): Promise<number[]> {
|
||||
`);
|
||||
return await page.evaluate(`Array.from(window.result)`);
|
||||
}
|
||||
|
||||
async function setupBrowser(options: ITerminalOptions = { rendererType: 'dom' }): Promise<void> {
|
||||
browser = await puppeteer.launch({
|
||||
headless: process.argv.indexOf('--headless') !== -1,
|
||||
args: [`--window-size=${width},${height}`, `--no-sandbox`]
|
||||
});
|
||||
page = (await browser.pages())[0];
|
||||
await page.setViewport({ width, height });
|
||||
await page.goto(APP);
|
||||
await openTerminal(options);
|
||||
await page.evaluate(`
|
||||
window.addon = new WebglAddon(true);
|
||||
window.term.loadAddon(window.addon);
|
||||
`);
|
||||
}
|
||||
|
||||
export async function pollFor<T>(page: puppeteer.Page, evalOrFn: string | (() => Promise<T>), val: T, preFn?: () => Promise<void>): Promise<void> {
|
||||
if (preFn) {
|
||||
await preFn();
|
||||
}
|
||||
const result = typeof evalOrFn === 'string' ? await page.evaluate(evalOrFn) : await evalOrFn();
|
||||
if (!deepEqual(result, val)) {
|
||||
return new Promise<void>(r => {
|
||||
setTimeout(() => r(pollFor(page, evalOrFn, val, preFn)), 1);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const COLORS_16_TO_255 = [
|
||||
'#000000', '#00005f', '#000087', '#0000af', '#0000d7', '#0000ff', '#005f00', '#005f5f', '#005f87', '#005faf', '#005fd7', '#005fff', '#008700', '#00875f', '#008787', '#0087af',
|
||||
'#0087d7', '#0087ff', '#00af00', '#00af5f', '#00af87', '#00afaf', '#00afd7', '#00afff', '#00d700', '#00d75f', '#00d787', '#00d7af', '#00d7d7', '#00d7ff', '#00ff00', '#00ff5f',
|
||||
'#00ff87', '#00ffaf', '#00ffd7', '#00ffff', '#5f0000', '#5f005f', '#5f0087', '#5f00af', '#5f00d7', '#5f00ff', '#5f5f00', '#5f5f5f', '#5f5f87', '#5f5faf', '#5f5fd7', '#5f5fff',
|
||||
'#5f8700', '#5f875f', '#5f8787', '#5f87af', '#5f87d7', '#5f87ff', '#5faf00', '#5faf5f', '#5faf87', '#5fafaf', '#5fafd7', '#5fafff', '#5fd700', '#5fd75f', '#5fd787', '#5fd7af',
|
||||
'#5fd7d7', '#5fd7ff', '#5fff00', '#5fff5f', '#5fff87', '#5fffaf', '#5fffd7', '#5fffff', '#870000', '#87005f', '#870087', '#8700af', '#8700d7', '#8700ff', '#875f00', '#875f5f',
|
||||
'#875f87', '#875faf', '#875fd7', '#875fff', '#878700', '#87875f', '#878787', '#8787af', '#8787d7', '#8787ff', '#87af00', '#87af5f', '#87af87', '#87afaf', '#87afd7', '#87afff',
|
||||
'#87d700', '#87d75f', '#87d787', '#87d7af', '#87d7d7', '#87d7ff', '#87ff00', '#87ff5f', '#87ff87', '#87ffaf', '#87ffd7', '#87ffff', '#af0000', '#af005f', '#af0087', '#af00af',
|
||||
'#af00d7', '#af00ff', '#af5f00', '#af5f5f', '#af5f87', '#af5faf', '#af5fd7', '#af5fff', '#af8700', '#af875f', '#af8787', '#af87af', '#af87d7', '#af87ff', '#afaf00', '#afaf5f',
|
||||
'#afaf87', '#afafaf', '#afafd7', '#afafff', '#afd700', '#afd75f', '#afd787', '#afd7af', '#afd7d7', '#afd7ff', '#afff00', '#afff5f', '#afff87', '#afffaf', '#afffd7', '#afffff',
|
||||
'#d70000', '#d7005f', '#d70087', '#d700af', '#d700d7', '#d700ff', '#d75f00', '#d75f5f', '#d75f87', '#d75faf', '#d75fd7', '#d75fff', '#d78700', '#d7875f', '#d78787', '#d787af',
|
||||
'#d787d7', '#d787ff', '#d7af00', '#d7af5f', '#d7af87', '#d7afaf', '#d7afd7', '#d7afff', '#d7d700', '#d7d75f', '#d7d787', '#d7d7af', '#d7d7d7', '#d7d7ff', '#d7ff00', '#d7ff5f',
|
||||
'#d7ff87', '#d7ffaf', '#d7ffd7', '#d7ffff', '#ff0000', '#ff005f', '#ff0087', '#ff00af', '#ff00d7', '#ff00ff', '#ff5f00', '#ff5f5f', '#ff5f87', '#ff5faf', '#ff5fd7', '#ff5fff',
|
||||
'#ff8700', '#ff875f', '#ff8787', '#ff87af', '#ff87d7', '#ff87ff', '#ffaf00', '#ffaf5f', '#ffaf87', '#ffafaf', '#ffafd7', '#ffafff', '#ffd700', '#ffd75f', '#ffd787', '#ffd7af',
|
||||
'#ffd7d7', '#ffd7ff', '#ffff00', '#ffff5f', '#ffff87', '#ffffaf', '#ffffd7', '#ffffff', '#080808', '#121212', '#1c1c1c', '#262626', '#303030', '#3a3a3a', '#444444', '#4e4e4e',
|
||||
'#585858', '#626262', '#6c6c6c', '#767676', '#808080', '#8a8a8a', '#949494', '#9e9e9e', '#a8a8a8', '#b2b2b2', '#bcbcbc', '#c6c6c6', '#d0d0d0', '#dadada', '#e4e4e4', '#eeeeee'
|
||||
];
|
||||
|
||||
@@ -11,20 +11,15 @@ import { acquireCharAtlas } from './atlas/CharAtlasCache';
|
||||
import { WebglCharAtlas } from './atlas/WebglCharAtlas';
|
||||
import { RectangleRenderer } from './RectangleRenderer';
|
||||
import { IWebGL2RenderingContext } from './Types';
|
||||
import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants';
|
||||
import { RenderModel, COMBINED_CHAR_BIT_MASK } from './RenderModel';
|
||||
import { RenderModel, COMBINED_CHAR_BIT_MASK, RENDER_MODEL_BG_OFFSET, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_INDICIES_PER_CELL } from './RenderModel';
|
||||
import { Disposable } from 'common/Lifecycle';
|
||||
import { DEFAULT_COLOR, CHAR_DATA_CHAR_INDEX, CHAR_DATA_CODE_INDEX, NULL_CELL_CODE } from 'common/buffer/Constants';
|
||||
import { NULL_CELL_CODE } from 'common/buffer/Constants';
|
||||
import { Terminal, IEvent } from 'xterm';
|
||||
import { getLuminance } from './ColorUtils';
|
||||
import { IRenderLayer } from './renderLayer/Types';
|
||||
import { IRenderDimensions, IRenderer, IRequestRefreshRowsEvent } from 'browser/renderer/Types';
|
||||
import { IColorSet } from 'browser/Types';
|
||||
import { FLAGS } from './Constants';
|
||||
import { getCompatAttr } from './CharDataCompat';
|
||||
import { EventEmitter } from 'common/EventEmitter';
|
||||
|
||||
export const INDICIES_PER_CELL = 4;
|
||||
import { CellData } from 'common/buffer/CellData';
|
||||
|
||||
export class WebglRenderer extends Disposable implements IRenderer {
|
||||
private _renderLayers: IRenderLayer[];
|
||||
@@ -32,6 +27,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
private _devicePixelRatio: number;
|
||||
|
||||
private _model: RenderModel = new RenderModel();
|
||||
private _workCell: CellData = new CellData();
|
||||
|
||||
private _canvas: HTMLCanvasElement;
|
||||
private _gl: IWebGL2RenderingContext;
|
||||
@@ -41,6 +37,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
public dimensions: IRenderDimensions;
|
||||
|
||||
private _core: ITerminal;
|
||||
private _isAttached: boolean;
|
||||
|
||||
private _onRequestRefreshRows = new EventEmitter<IRequestRefreshRowsEvent>();
|
||||
public get onRequestRefreshRows(): IEvent<IRequestRefreshRowsEvent> { return this._onRequestRefreshRows.event; }
|
||||
@@ -54,8 +51,6 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
|
||||
this._core = (<any>this._terminal)._core;
|
||||
|
||||
this._applyBgLuminanceBasedSelection();
|
||||
|
||||
this._renderLayers = [
|
||||
new LinkRenderLayer(this._core.screenElement, 2, this._colors, this._core),
|
||||
new CursorRenderLayer(this._core.screenElement, 3, this._colors, this._onRequestRefreshRows)
|
||||
@@ -95,6 +90,8 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
|
||||
// Update dimensions and acquire char atlas
|
||||
this.onCharSizeChanged();
|
||||
|
||||
this._isAttached = document.body.contains(this._core.screenElement);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
@@ -103,20 +100,12 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
private _applyBgLuminanceBasedSelection(): void {
|
||||
// HACK: This is needed until webgl renderer adds support for selection colors
|
||||
if (getLuminance(this._colors.background) > 0.5) {
|
||||
this._colors.selection = { css: '#000', rgba: 255 };
|
||||
} else {
|
||||
this._colors.selection = { css: '#fff', rgba: 4294967295 };
|
||||
}
|
||||
public get textureAtlas(): HTMLCanvasElement | undefined {
|
||||
return this._charAtlas?.cacheCanvas;
|
||||
}
|
||||
|
||||
public setColors(colors: IColorSet): void {
|
||||
this._colors = colors;
|
||||
|
||||
this._applyBgLuminanceBasedSelection();
|
||||
|
||||
// Clear layers and force a full render
|
||||
this._renderLayers.forEach(l => {
|
||||
l.setColors(this._terminal, this._colors);
|
||||
@@ -189,7 +178,6 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
this._rectangleRenderer.updateSelection(this._model.selection, columnSelectMode);
|
||||
this._glyphRenderer.updateSelection(this._model, columnSelectMode);
|
||||
|
||||
// TODO: #2102 Should this move to RenderCoordinator?
|
||||
this._onRequestRefreshRows.fire({ start: 0, end: this._terminal.rows - 1 });
|
||||
}
|
||||
|
||||
@@ -210,6 +198,8 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
*/
|
||||
private _refreshCharAtlas(): void {
|
||||
if (this.dimensions.scaledCharWidth <= 0 && this.dimensions.scaledCharHeight <= 0) {
|
||||
// Mark as not attached so char atlas gets refreshed on next render
|
||||
this._isAttached = false;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -235,6 +225,16 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
}
|
||||
|
||||
public renderRows(start: number, end: number): void {
|
||||
if (!this._isAttached) {
|
||||
if (document.body.contains(this._core.screenElement) && (<any>this._core)._charSizeService.width && (<any>this._core)._charSizeService.height) {
|
||||
this._updateDimensions();
|
||||
this._refreshCharAtlas();
|
||||
this._isAttached = true;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Update render layers
|
||||
this._renderLayers.forEach(l => l.onGridChanged(this._terminal, start, end));
|
||||
|
||||
@@ -259,52 +259,34 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
const line = terminal.buffer.lines.get(row)!;
|
||||
this._model.lineLengths[y] = 0;
|
||||
for (let x = 0; x < terminal.cols; x++) {
|
||||
const charData = line.get(x);
|
||||
const chars = charData[CHAR_DATA_CHAR_INDEX];
|
||||
let code = charData[CHAR_DATA_CODE_INDEX];
|
||||
const attr = getCompatAttr(line, x); // charData[CHAR_DATA_ATTR_INDEX];
|
||||
const i = ((y * terminal.cols) + x) * INDICIES_PER_CELL;
|
||||
line.loadCell(x, this._workCell);
|
||||
|
||||
const chars = this._workCell.getChars();
|
||||
let code = this._workCell.getCode();
|
||||
const i = ((y * terminal.cols) + x) * RENDER_MODEL_INDICIES_PER_CELL;
|
||||
|
||||
if (code !== NULL_CELL_CODE) {
|
||||
this._model.lineLengths[y] = x + 1;
|
||||
}
|
||||
|
||||
// Nothing has changed, no updates needed
|
||||
if (this._model.cells[i] === code && this._model.cells[i + 1] === attr) {
|
||||
if (this._model.cells[i] === code &&
|
||||
this._model.cells[i + RENDER_MODEL_BG_OFFSET] === this._workCell.bg &&
|
||||
this._model.cells[i + RENDER_MODEL_FG_OFFSET] === this._workCell.fg) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Resolve bg and fg and cache in the model
|
||||
const flags = attr >> 18;
|
||||
let bg = attr & 0x1ff;
|
||||
let fg = (attr >> 9) & 0x1ff;
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
const drawInBrightColor = terminal.options.drawBoldTextInBrightColors && !!(flags & FLAGS.BOLD) && fg < 8 && fg !== INVERTED_DEFAULT_COLOR;
|
||||
fg += drawInBrightColor ? 8 : 0;
|
||||
|
||||
// Flag combined chars with a bit mask so they're easily identifiable
|
||||
if (chars.length > 1) {
|
||||
code = code | COMBINED_CHAR_BIT_MASK;
|
||||
}
|
||||
|
||||
this._model.cells[i ] = code;
|
||||
this._model.cells[i + 1] = attr;
|
||||
this._model.cells[i + 2] = bg;
|
||||
this._model.cells[i + 3] = fg;
|
||||
// Cache the results in the model
|
||||
this._model.cells[i] = code;
|
||||
this._model.cells[i + RENDER_MODEL_BG_OFFSET] = this._workCell.bg;
|
||||
this._model.cells[i + RENDER_MODEL_FG_OFFSET] = this._workCell.fg;
|
||||
|
||||
this._glyphRenderer.updateCell(x, y, code, attr, bg, fg, chars);
|
||||
this._glyphRenderer.updateCell(x, y, code, this._workCell.bg, this._workCell.fg, chars);
|
||||
}
|
||||
}
|
||||
this._rectangleRenderer.updateBackgrounds(this._model);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import { ICharAtlasConfig } from './Types';
|
||||
import { DEFAULT_COLOR } from 'common/buffer/Constants';
|
||||
import { Attributes } from 'common/buffer/Constants';
|
||||
import { Terminal, FontWeight } from 'xterm';
|
||||
import { IColorSet, IColor } from 'browser/Types';
|
||||
|
||||
@@ -21,9 +21,11 @@ export function generateConfig(scaledCharWidth: number, scaledCharHeight: number
|
||||
cursor: NULL_COLOR,
|
||||
cursorAccent: NULL_COLOR,
|
||||
selection: NULL_COLOR,
|
||||
selectionOpaque: NULL_COLOR,
|
||||
// For the static char atlas, we only use the first 16 colors, but we need all 256 for the
|
||||
// dynamic character atlas.
|
||||
ansi: colors.ansi.slice()
|
||||
ansi: colors.ansi.slice(),
|
||||
contrastCache: colors.contrastCache
|
||||
};
|
||||
return {
|
||||
devicePixelRatio: window.devicePixelRatio,
|
||||
@@ -34,6 +36,8 @@ export function generateConfig(scaledCharWidth: number, scaledCharHeight: number
|
||||
fontWeight: terminal.getOption('fontWeight') as FontWeight,
|
||||
fontWeightBold: terminal.getOption('fontWeightBold') as FontWeight,
|
||||
allowTransparency: terminal.getOption('allowTransparency'),
|
||||
drawBoldTextInBrightColors: terminal.getOption('drawBoldTextInBrightColors'),
|
||||
minimumContrastRatio: terminal.getOption('minimumContrastRatio'),
|
||||
colors: clonedColors
|
||||
};
|
||||
}
|
||||
@@ -52,10 +56,12 @@ export function configEquals(a: ICharAtlasConfig, b: ICharAtlasConfig): boolean
|
||||
a.allowTransparency === b.allowTransparency &&
|
||||
a.scaledCharWidth === b.scaledCharWidth &&
|
||||
a.scaledCharHeight === b.scaledCharHeight &&
|
||||
a.drawBoldTextInBrightColors === b.drawBoldTextInBrightColors &&
|
||||
a.minimumContrastRatio === b.minimumContrastRatio &&
|
||||
a.colors.foreground === b.colors.foreground &&
|
||||
a.colors.background === b.colors.background;
|
||||
}
|
||||
|
||||
export function is256Color(colorCode: number): boolean {
|
||||
return colorCode < DEFAULT_COLOR;
|
||||
return (colorCode & Attributes.CM_MASK) === Attributes.CM_P16 || (colorCode & Attributes.CM_MASK) === Attributes.CM_P256;
|
||||
}
|
||||
|
||||
@@ -25,5 +25,7 @@ export interface ICharAtlasConfig {
|
||||
scaledCharWidth: number;
|
||||
scaledCharHeight: number;
|
||||
allowTransparency: boolean;
|
||||
drawBoldTextInBrightColors: boolean;
|
||||
minimumContrastRatio: number;
|
||||
colors: IColorSet;
|
||||
}
|
||||
|
||||
@@ -4,14 +4,14 @@
|
||||
*/
|
||||
|
||||
import { ICharAtlasConfig } from './Types';
|
||||
import { DIM_OPACITY, INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants';
|
||||
import { DIM_OPACITY } from 'browser/renderer/atlas/Constants';
|
||||
import { IRasterizedGlyph, IBoundingBox, IRasterizedGlyphSet } from '../Types';
|
||||
import { DEFAULT_COLOR, DEFAULT_ATTR } from 'common/buffer/Constants';
|
||||
import { is256Color } from './CharAtlasUtils';
|
||||
import { DEFAULT_COLOR, Attributes } from 'common/buffer/Constants';
|
||||
import { throwIfFalsy } from '../WebglUtils';
|
||||
import { IColor } from 'browser/Types';
|
||||
import { FLAGS } from '../Constants';
|
||||
import { IDisposable } from 'xterm';
|
||||
import { AttributeData } from 'common/buffer/AttributeData';
|
||||
import { channels, rgba } from 'browser/Color';
|
||||
|
||||
// In practice we're probably never going to exhaust a texture this large. For debugging purposes,
|
||||
// however, it can be useful to set this to a really tiny value, to verify that LRU eviction works.
|
||||
@@ -68,8 +68,12 @@ export class WebglCharAtlas implements IDisposable {
|
||||
public hasCanvasChanged = false;
|
||||
|
||||
private _workBoundingBox: IBoundingBox = { top: 0, left: 0, bottom: 0, right: 0 };
|
||||
private _workAttributeData: AttributeData = new AttributeData();
|
||||
|
||||
constructor(document: Document, private _config: ICharAtlasConfig) {
|
||||
constructor(
|
||||
document: Document,
|
||||
private _config: ICharAtlasConfig
|
||||
) {
|
||||
this.cacheCanvas = document.createElement('canvas');
|
||||
this.cacheCanvas.width = TEXTURE_WIDTH;
|
||||
this.cacheCanvas.height = TEXTURE_HEIGHT;
|
||||
@@ -82,9 +86,6 @@ export class WebglCharAtlas implements IDisposable {
|
||||
this._tmpCanvas.width = this._config.scaledCharWidth * 2 + TMP_CANVAS_GLYPH_PADDING * 2;
|
||||
this._tmpCanvas.height = this._config.scaledCharHeight + TMP_CANVAS_GLYPH_PADDING * 2;
|
||||
this._tmpCtx = throwIfFalsy(this._tmpCanvas.getContext('2d', {alpha: this._config.allowTransparency}));
|
||||
|
||||
// This is useful for debugging
|
||||
document.body.appendChild(this.cacheCanvas);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
@@ -103,9 +104,11 @@ export class WebglCharAtlas implements IDisposable {
|
||||
protected _doWarmUp(): void {
|
||||
// Pre-fill with ASCII 33-126
|
||||
for (let i = 33; i < 126; i++) {
|
||||
const rasterizedGlyph = this._drawToCache(i, DEFAULT_ATTR, DEFAULT_COLOR, DEFAULT_COLOR);
|
||||
const rasterizedGlyph = this._drawToCache(i, DEFAULT_COLOR, DEFAULT_COLOR);
|
||||
this._cacheMap[i] = {
|
||||
[DEFAULT_ATTR]: rasterizedGlyph
|
||||
[DEFAULT_COLOR]: {
|
||||
[DEFAULT_COLOR]: rasterizedGlyph
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -123,16 +126,23 @@ export class WebglCharAtlas implements IDisposable {
|
||||
return false;
|
||||
}
|
||||
|
||||
public getRasterizedGlyphCombinedChar(chars: string, attr: number, bg: number, fg: number): IRasterizedGlyph {
|
||||
public getRasterizedGlyphCombinedChar(chars: string, bg: number, fg: number): IRasterizedGlyph {
|
||||
let rasterizedGlyphSet = this._cacheMapCombined[chars];
|
||||
if (!rasterizedGlyphSet) {
|
||||
rasterizedGlyphSet = {};
|
||||
this._cacheMapCombined[chars] = rasterizedGlyphSet;
|
||||
}
|
||||
let rasterizedGlyph = rasterizedGlyphSet[attr];
|
||||
let rasterizedGlyph: IRasterizedGlyph | undefined;
|
||||
const rasterizedGlyphSetBg = rasterizedGlyphSet[bg];
|
||||
if (rasterizedGlyphSetBg) {
|
||||
rasterizedGlyph = rasterizedGlyphSetBg[fg];
|
||||
}
|
||||
if (!rasterizedGlyph) {
|
||||
rasterizedGlyph = this._drawToCache(chars, attr, bg, fg);
|
||||
rasterizedGlyphSet[attr] = rasterizedGlyph;
|
||||
rasterizedGlyph = this._drawToCache(chars, bg, fg);
|
||||
if (!rasterizedGlyphSet[bg]) {
|
||||
rasterizedGlyphSet[bg] = {};
|
||||
}
|
||||
rasterizedGlyphSet[bg]![fg] = rasterizedGlyph;
|
||||
}
|
||||
return rasterizedGlyph;
|
||||
}
|
||||
@@ -140,16 +150,23 @@ export class WebglCharAtlas implements IDisposable {
|
||||
/**
|
||||
* Gets the glyphs texture coords, drawing the texture if it's not already
|
||||
*/
|
||||
public getRasterizedGlyph(code: number, attr: number, bg: number, fg: number): IRasterizedGlyph {
|
||||
public getRasterizedGlyph(code: number, bg: number, fg: number): IRasterizedGlyph {
|
||||
let rasterizedGlyphSet = this._cacheMap[code];
|
||||
if (!rasterizedGlyphSet) {
|
||||
rasterizedGlyphSet = {};
|
||||
this._cacheMap[code] = rasterizedGlyphSet;
|
||||
}
|
||||
let rasterizedGlyph = rasterizedGlyphSet[attr];
|
||||
let rasterizedGlyph: IRasterizedGlyph | undefined;
|
||||
const rasterizedGlyphSetBg = rasterizedGlyphSet[bg];
|
||||
if (rasterizedGlyphSetBg) {
|
||||
rasterizedGlyph = rasterizedGlyphSetBg[fg];
|
||||
}
|
||||
if (!rasterizedGlyph) {
|
||||
rasterizedGlyph = this._drawToCache(code, attr, bg, fg);
|
||||
rasterizedGlyphSet[attr] = rasterizedGlyph;
|
||||
rasterizedGlyph = this._drawToCache(code, bg, fg);
|
||||
if (!rasterizedGlyphSet[bg]) {
|
||||
rasterizedGlyphSet[bg] = {};
|
||||
}
|
||||
rasterizedGlyphSet[bg]![fg] = rasterizedGlyph;
|
||||
}
|
||||
return rasterizedGlyph;
|
||||
}
|
||||
@@ -161,48 +178,165 @@ export class WebglCharAtlas implements IDisposable {
|
||||
return this._config.colors.ansi[idx];
|
||||
}
|
||||
|
||||
private _getBackgroundColor(bg: number): IColor {
|
||||
private _getBackgroundColor(bgColorMode: number, bgColor: number, inverse: boolean): IColor {
|
||||
if (this._config.allowTransparency) {
|
||||
// The background color might have some transparency, so we need to render it as fully
|
||||
// transparent in the atlas. Otherwise we'd end up drawing the transparent background twice
|
||||
// around the anti-aliased edges of the glyph, and it would look too dark.
|
||||
return TRANSPARENT_COLOR;
|
||||
} else if (bg === INVERTED_DEFAULT_COLOR) {
|
||||
return this._config.colors.foreground;
|
||||
} else if (is256Color(bg)) {
|
||||
return this._getColorFromAnsiIndex(bg);
|
||||
}
|
||||
// TODO: Support true color
|
||||
return this._config.colors.background;
|
||||
|
||||
switch (bgColorMode) {
|
||||
case Attributes.CM_P16:
|
||||
case Attributes.CM_P256:
|
||||
return this._getColorFromAnsiIndex(bgColor);
|
||||
case Attributes.CM_RGB:
|
||||
const arr = AttributeData.toColorRGB(bgColor);
|
||||
// TODO: This object creation is slow
|
||||
return {
|
||||
rgba: bgColor << 8,
|
||||
css: `#${toPaddedHex(arr[0])}${toPaddedHex(arr[1])}${toPaddedHex(arr[2])}`
|
||||
};
|
||||
case Attributes.CM_DEFAULT:
|
||||
default:
|
||||
if (inverse) {
|
||||
return this._config.colors.foreground;
|
||||
}
|
||||
return this._config.colors.background;
|
||||
}
|
||||
}
|
||||
|
||||
private _getForegroundColor(fg: number): IColor {
|
||||
if (fg === INVERTED_DEFAULT_COLOR) {
|
||||
return this._config.colors.background;
|
||||
} else if (is256Color(fg)) {
|
||||
return this._getColorFromAnsiIndex(fg);
|
||||
private _getForegroundCss(bg: number, bgColorMode: number, bgColor: number, fg: number, fgColorMode: number, fgColor: number, inverse: boolean, bold: boolean): string {
|
||||
const minimumContrastCss = this._getMinimumContrastCss(bg, bgColorMode, bgColor, fg, fgColorMode, fgColor, inverse, bold);
|
||||
if (minimumContrastCss) {
|
||||
return minimumContrastCss;
|
||||
}
|
||||
|
||||
switch (fgColorMode) {
|
||||
case Attributes.CM_P16:
|
||||
case Attributes.CM_P256:
|
||||
if (this._config.drawBoldTextInBrightColors && bold && fgColor < 8) {
|
||||
fgColor += 8;
|
||||
}
|
||||
return this._getColorFromAnsiIndex(fgColor).css;
|
||||
case Attributes.CM_RGB:
|
||||
const arr = AttributeData.toColorRGB(fgColor);
|
||||
return channels.toCss(arr[0], arr[1], arr[2]);
|
||||
case Attributes.CM_DEFAULT:
|
||||
default:
|
||||
if (inverse) {
|
||||
const bg = this._config.colors.background.css;
|
||||
if (bg.length === 9) {
|
||||
// Remove bg alpha channel if present
|
||||
return bg.substr(0, 7);
|
||||
}
|
||||
return bg;
|
||||
}
|
||||
return this._config.colors.foreground.css;
|
||||
}
|
||||
// TODO: Support true color
|
||||
return this._config.colors.foreground;
|
||||
}
|
||||
|
||||
private _drawToCache(code: number, attr: number, bg: number, fg: number): IRasterizedGlyph;
|
||||
private _drawToCache(chars: string, attr: number, bg: number, fg: number): IRasterizedGlyph;
|
||||
private _drawToCache(codeOrChars: number | string, attr: number, bg: number, fg: number): IRasterizedGlyph {
|
||||
private _resolveBackgroundRgba(bgColorMode: number, bgColor: number, inverse: boolean): number {
|
||||
switch (bgColorMode) {
|
||||
case Attributes.CM_P16:
|
||||
case Attributes.CM_P256:
|
||||
return this._getColorFromAnsiIndex(bgColor).rgba;
|
||||
case Attributes.CM_RGB:
|
||||
return bgColor << 8;
|
||||
case Attributes.CM_DEFAULT:
|
||||
default:
|
||||
if (inverse) {
|
||||
return this._config.colors.foreground.rgba;
|
||||
}
|
||||
return this._config.colors.background.rgba;
|
||||
}
|
||||
}
|
||||
|
||||
private _resolveForegroundRgba(fgColorMode: number, fgColor: number, inverse: boolean, bold: boolean): number {
|
||||
switch (fgColorMode) {
|
||||
case Attributes.CM_P16:
|
||||
case Attributes.CM_P256:
|
||||
if (this._config.drawBoldTextInBrightColors && bold && fgColor < 8) {
|
||||
fgColor += 8;
|
||||
}
|
||||
return this._getColorFromAnsiIndex(fgColor).rgba;
|
||||
case Attributes.CM_RGB:
|
||||
return fgColor << 8;
|
||||
case Attributes.CM_DEFAULT:
|
||||
default:
|
||||
if (inverse) {
|
||||
return this._config.colors.background.rgba;
|
||||
}
|
||||
return this._config.colors.foreground.rgba;
|
||||
}
|
||||
}
|
||||
|
||||
private _getMinimumContrastCss(bg: number, bgColorMode: number, bgColor: number, fg: number, fgColorMode: number, fgColor: number, inverse: boolean, bold: boolean): string | undefined {
|
||||
if (this._config.minimumContrastRatio === 1) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Try get from cache first
|
||||
const adjustedColor = this._config.colors.contrastCache.getCss(bg, fg);
|
||||
if (adjustedColor !== undefined) {
|
||||
return adjustedColor || undefined;
|
||||
}
|
||||
|
||||
const bgRgba = this._resolveBackgroundRgba(bgColorMode, bgColor, inverse);
|
||||
const fgRgba = this._resolveForegroundRgba(fgColorMode, fgColor, inverse, bold);
|
||||
const result = rgba.ensureContrastRatio(bgRgba, fgRgba, this._config.minimumContrastRatio);
|
||||
|
||||
if (!result) {
|
||||
this._config.colors.contrastCache.setCss(bg, fg, null);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const css = channels.toCss(
|
||||
(result >> 24) & 0xFF,
|
||||
(result >> 16) & 0xFF,
|
||||
(result >> 8) & 0xFF
|
||||
);
|
||||
this._config.colors.contrastCache.setCss(bg, fg, css);
|
||||
|
||||
return css;
|
||||
}
|
||||
|
||||
private _drawToCache(code: number, bg: number, fg: number): IRasterizedGlyph;
|
||||
private _drawToCache(chars: string, bg: number, fg: number): IRasterizedGlyph;
|
||||
private _drawToCache(codeOrChars: number | string, bg: number, fg: number): IRasterizedGlyph {
|
||||
const chars = typeof codeOrChars === 'number' ? String.fromCharCode(codeOrChars) : codeOrChars;
|
||||
|
||||
this.hasCanvasChanged = true;
|
||||
|
||||
const flags = attr >> 18;
|
||||
|
||||
const bold = !!(flags & FLAGS.BOLD);
|
||||
const dim = !!(flags & FLAGS.DIM);
|
||||
const italic = !!(flags & FLAGS.ITALIC);
|
||||
|
||||
this._tmpCtx.save();
|
||||
|
||||
this._workAttributeData.fg = fg;
|
||||
this._workAttributeData.bg = bg;
|
||||
|
||||
const invisible = !!this._workAttributeData.isInvisible();
|
||||
if (invisible) {
|
||||
return NULL_RASTERIZED_GLYPH;
|
||||
}
|
||||
|
||||
const bold = !!this._workAttributeData.isBold();
|
||||
const inverse = !!this._workAttributeData.isInverse();
|
||||
const dim = !!this._workAttributeData.isDim();
|
||||
const italic = !!this._workAttributeData.isItalic();
|
||||
let fgColor = this._workAttributeData.getFgColor();
|
||||
let fgColorMode = this._workAttributeData.getFgColorMode();
|
||||
let bgColor = this._workAttributeData.getBgColor();
|
||||
let bgColorMode = this._workAttributeData.getBgColorMode();
|
||||
if (inverse) {
|
||||
const temp = fgColor;
|
||||
fgColor = bgColor;
|
||||
bgColor = temp;
|
||||
const temp2 = fgColorMode;
|
||||
fgColorMode = bgColorMode;
|
||||
bgColorMode = temp2;
|
||||
}
|
||||
|
||||
// draw the background
|
||||
const backgroundColor = this._getBackgroundColor(bg);
|
||||
const backgroundColor = this._getBackgroundColor(bgColorMode, bgColor, inverse);
|
||||
// Use a 'copy' composite operation to clear any existing glyph out of _tmpCtxWithAlpha, regardless of
|
||||
// transparency in backgroundColor
|
||||
this._tmpCtx.globalCompositeOperation = 'copy';
|
||||
@@ -217,7 +351,7 @@ export class WebglCharAtlas implements IDisposable {
|
||||
`${fontStyle} ${fontWeight} ${this._config.fontSize * this._config.devicePixelRatio}px ${this._config.fontFamily}`;
|
||||
this._tmpCtx.textBaseline = 'top';
|
||||
|
||||
this._tmpCtx.fillStyle = this._getForegroundColor(fg).css;
|
||||
this._tmpCtx.fillStyle = this._getForegroundCss(bg, bgColorMode, bgColor, fg, fgColorMode, fgColor, inverse, bold);
|
||||
|
||||
// Apply alpha to dim the character
|
||||
if (dim) {
|
||||
@@ -398,3 +532,8 @@ function clearColor(imageData: ImageData, color: IColor): boolean {
|
||||
}
|
||||
return isEmpty;
|
||||
}
|
||||
|
||||
function toPaddedHex(c: number): string {
|
||||
const s = c.toString(16);
|
||||
return s.length < 2 ? '0' + s : s;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
*/
|
||||
|
||||
import { IRenderLayer } from './Types';
|
||||
import { IGlyphIdentifier } from '../atlas/Types';
|
||||
import { acquireCharAtlas } from '../atlas/CharAtlasCache';
|
||||
import { Terminal } from 'xterm';
|
||||
import { IColorSet } from 'browser/Types';
|
||||
@@ -25,19 +24,6 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
|
||||
protected _charAtlas: WebglCharAtlas | undefined;
|
||||
|
||||
/**
|
||||
* An object that's reused when drawing glyphs in order to reduce GC.
|
||||
*/
|
||||
private _currentGlyphIdentifier: IGlyphIdentifier = {
|
||||
chars: '',
|
||||
code: 0,
|
||||
bg: 0,
|
||||
fg: 0,
|
||||
bold: false,
|
||||
dim: false,
|
||||
italic: false
|
||||
};
|
||||
|
||||
constructor(
|
||||
private _container: HTMLElement,
|
||||
id: string,
|
||||
@@ -167,11 +153,11 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
* @param x The column to fill.
|
||||
* @param y The row to fill.
|
||||
*/
|
||||
protected _fillLeftLineAtCell(x: number, y: number): void {
|
||||
protected _fillLeftLineAtCell(x: number, y: number, width: number): void {
|
||||
this._ctx.fillRect(
|
||||
x * this._scaledCellWidth,
|
||||
y * this._scaledCellHeight,
|
||||
window.devicePixelRatio,
|
||||
window.devicePixelRatio * width,
|
||||
this._scaledCellHeight);
|
||||
}
|
||||
|
||||
|
||||
@@ -204,7 +204,7 @@ export class CursorRenderLayer extends BaseRenderLayer {
|
||||
private _renderBarCursor(terminal: Terminal, x: number, y: number, cell: ICellData): void {
|
||||
this._ctx.save();
|
||||
this._ctx.fillStyle = this._colors.cursor.css;
|
||||
this._fillLeftLineAtCell(x, y);
|
||||
this._fillLeftLineAtCell(x, y, terminal.getOption('cursorWidth'));
|
||||
this._ctx.restore();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user