From 0cb6138cd0a81dd01c230505b33f0178ed93ace3 Mon Sep 17 00:00:00 2001 From: Benjamin Woodruff Date: Wed, 21 Mar 2018 21:03:36 -0700 Subject: [PATCH 1/2] Allow theming with arbitrary CSS color strings This mostly worked before, but clearColor made the assumption that the colors were always in #RRBBGG format. See the discussion here: https://github.com/xtermjs/xterm.js/pull/1327#issuecomment-374812842 This changes the internal representation of a color so that we hold onto into an object containing the original css string along with an RGBA value encoded as a 32-bit uint. clearColor can then use that RGBA representation. Additionally, this deduplicates some code between ColorManager and Terminal. Terminal was generating the 256 ansi colors the same way ColorManager was, so this just makes Terminal use the same constant. --- src/Terminal.ts | 47 +-- src/Viewport.ts | 2 +- src/renderer/BaseRenderLayer.ts | 10 +- src/renderer/ColorManager.test.ts | 509 +++++++++++++------------ src/renderer/ColorManager.ts | 183 +++++---- src/renderer/CursorRenderLayer.ts | 12 +- src/renderer/LinkRenderLayer.ts | 2 +- src/renderer/SelectionRenderLayer.ts | 2 +- src/renderer/TextRenderLayer.ts | 8 +- src/renderer/atlas/CharAtlasUtils.ts | 2 +- src/shared/Types.ts | 19 +- src/shared/atlas/CharAtlasGenerator.ts | 12 +- 12 files changed, 406 insertions(+), 402 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index d1a266b2e..6d52970c1 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -622,7 +622,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT // Create main element container this.element = this._document.createElement('div'); - this.element.dir = 'ltr'; //xterm.css assumes LTR + this.element.dir = 'ltr'; // xterm.css assumes LTR this.element.classList.add('terminal'); this.element.classList.add('xterm'); this.element.setAttribute('tabindex', '0'); @@ -2235,38 +2235,6 @@ function wasMondifierKeyOnlyEvent(ev: KeyboardEvent): boolean { * ANSI color code. */ -// Colors 0-15 + 16-255 -// Much thanks to TooTallNate for writing this. -const vcolors: number[][] = (function(): number[][] { - const result = DEFAULT_ANSI_COLORS.map(c => { - c = c.substring(1); - return [ - parseInt(c.substring(0, 2), 16), - parseInt(c.substring(2, 4), 16), - parseInt(c.substring(4, 6), 16) - ]; - }); - const r = [0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff]; - - // 16-231 - for (let i = 0; i < 216; i++) { - result.push([ - r[(i / 36) % 6 | 0], - r[(i / 6) % 6 | 0], - r[i % 6] - ]); - } - - // 232-255 (grey) - let c: number; - for (let i = 0; i < 24; i++) { - c = 8 + i * 10; - result.push([c, c, c]); - } - - return result; -})(); - const matchColorCache: {[colorRGBHash: number]: number} = {}; // http://stackoverflow.com/questions/1633828 @@ -2287,17 +2255,18 @@ function matchColor_(r1: number, g1: number, b1: number): number { let ldiff = Infinity; let li = -1; let i = 0; - let c: number[]; + let c: number; let r2: number; let g2: number; let b2: number; let diff: number; - for (; i < vcolors.length; i++) { - c = vcolors[i]; - r2 = c[0]; - g2 = c[1]; - b2 = c[2]; + for (; i < DEFAULT_ANSI_COLORS.length; i++) { + c = DEFAULT_ANSI_COLORS[i].rgba; + r2 = c >>> 24; + g2 = c >>> 16 & 0xFF; + b2 = c >>> 8 & 0xFF; + // assume that alpha is 0xFF diff = matchColorDistance(r1, g1, b1, r2, g2, b2); diff --git a/src/Viewport.ts b/src/Viewport.ts index 4d135b5a7..c951d6b53 100644 --- a/src/Viewport.ts +++ b/src/Viewport.ts @@ -50,7 +50,7 @@ export class Viewport implements IViewport { } public onThemeChanged(colors: IColorSet): void { - this._viewportElement.style.backgroundColor = colors.background; + this._viewportElement.style.backgroundColor = colors.background.css; } /** diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index 281b3ee93..fbffd5474 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -179,7 +179,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { if (this._alpha) { this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); } else { - this._ctx.fillStyle = this._colors.background; + this._ctx.fillStyle = this._colors.background.css; this._ctx.fillRect(0, 0, this._canvas.width, this._canvas.height); } } @@ -199,7 +199,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { width * this._scaledCellWidth, height * this._scaledCellHeight); } else { - this._ctx.fillStyle = this._colors.background; + this._ctx.fillStyle = this._colors.background.css; this._ctx.fillRect( x * this._scaledCellWidth, y * this._scaledCellHeight, @@ -311,12 +311,12 @@ export abstract class BaseRenderLayer implements IRenderLayer { this._ctx.textBaseline = 'top'; if (fg === INVERTED_DEFAULT_COLOR) { - this._ctx.fillStyle = this._colors.background; + this._ctx.fillStyle = this._colors.background.css; } else if (fg < 256) { // 256 color support - this._ctx.fillStyle = this._colors.ansi[fg]; + this._ctx.fillStyle = this._colors.ansi[fg].css; } else { - this._ctx.fillStyle = this._colors.foreground; + this._ctx.fillStyle = this._colors.foreground.css; } this._clipRow(terminal, y); diff --git a/src/renderer/ColorManager.test.ts b/src/renderer/ColorManager.test.ts index 2dc604086..8d8988c79 100644 --- a/src/renderer/ColorManager.test.ts +++ b/src/renderer/ColorManager.test.ts @@ -17,261 +17,272 @@ describe('ColorManager', () => { dom = new jsdom.JSDOM(''); window = dom.window; document = window.document; + (window).HTMLCanvasElement.prototype.getContext = () => ({ + createLinearGradient(): any { + return null; + }, + + fillRect(): void { }, + + getImageData(): any { + return {data: [0, 0, 0, 0]}; + } + }); cm = new ColorManager(document); }); describe('constructor', () => { it('should fill all colors with values', () => { - for (let key in cm.colors) { - if (typeof key === 'string') { + for (let key of Object.keys(cm.colors)) { + if (key !== 'ansi') { // A #rrggbb or rgba(...) - assert.ok(cm.colors[key].length >= 7); + assert.ok(cm.colors[key].css.length >= 7); } } assert.equal(cm.colors.ansi.length, 256); }); it('should fill 240 colors with expected values', () => { - assert.equal(cm.colors.ansi[16], '#000000'); - assert.equal(cm.colors.ansi[17], '#00005f'); - assert.equal(cm.colors.ansi[18], '#000087'); - assert.equal(cm.colors.ansi[19], '#0000af'); - assert.equal(cm.colors.ansi[20], '#0000d7'); - assert.equal(cm.colors.ansi[21], '#0000ff'); - assert.equal(cm.colors.ansi[22], '#005f00'); - assert.equal(cm.colors.ansi[23], '#005f5f'); - assert.equal(cm.colors.ansi[24], '#005f87'); - assert.equal(cm.colors.ansi[25], '#005faf'); - assert.equal(cm.colors.ansi[26], '#005fd7'); - assert.equal(cm.colors.ansi[27], '#005fff'); - assert.equal(cm.colors.ansi[28], '#008700'); - assert.equal(cm.colors.ansi[29], '#00875f'); - assert.equal(cm.colors.ansi[30], '#008787'); - assert.equal(cm.colors.ansi[31], '#0087af'); - assert.equal(cm.colors.ansi[32], '#0087d7'); - assert.equal(cm.colors.ansi[33], '#0087ff'); - assert.equal(cm.colors.ansi[34], '#00af00'); - assert.equal(cm.colors.ansi[35], '#00af5f'); - assert.equal(cm.colors.ansi[36], '#00af87'); - assert.equal(cm.colors.ansi[37], '#00afaf'); - assert.equal(cm.colors.ansi[38], '#00afd7'); - assert.equal(cm.colors.ansi[39], '#00afff'); - assert.equal(cm.colors.ansi[40], '#00d700'); - assert.equal(cm.colors.ansi[41], '#00d75f'); - assert.equal(cm.colors.ansi[42], '#00d787'); - assert.equal(cm.colors.ansi[43], '#00d7af'); - assert.equal(cm.colors.ansi[44], '#00d7d7'); - assert.equal(cm.colors.ansi[45], '#00d7ff'); - assert.equal(cm.colors.ansi[46], '#00ff00'); - assert.equal(cm.colors.ansi[47], '#00ff5f'); - assert.equal(cm.colors.ansi[48], '#00ff87'); - assert.equal(cm.colors.ansi[49], '#00ffaf'); - assert.equal(cm.colors.ansi[50], '#00ffd7'); - assert.equal(cm.colors.ansi[51], '#00ffff'); - assert.equal(cm.colors.ansi[52], '#5f0000'); - assert.equal(cm.colors.ansi[53], '#5f005f'); - assert.equal(cm.colors.ansi[54], '#5f0087'); - assert.equal(cm.colors.ansi[55], '#5f00af'); - assert.equal(cm.colors.ansi[56], '#5f00d7'); - assert.equal(cm.colors.ansi[57], '#5f00ff'); - assert.equal(cm.colors.ansi[58], '#5f5f00'); - assert.equal(cm.colors.ansi[59], '#5f5f5f'); - assert.equal(cm.colors.ansi[60], '#5f5f87'); - assert.equal(cm.colors.ansi[61], '#5f5faf'); - assert.equal(cm.colors.ansi[62], '#5f5fd7'); - assert.equal(cm.colors.ansi[63], '#5f5fff'); - assert.equal(cm.colors.ansi[64], '#5f8700'); - assert.equal(cm.colors.ansi[65], '#5f875f'); - assert.equal(cm.colors.ansi[66], '#5f8787'); - assert.equal(cm.colors.ansi[67], '#5f87af'); - assert.equal(cm.colors.ansi[68], '#5f87d7'); - assert.equal(cm.colors.ansi[69], '#5f87ff'); - assert.equal(cm.colors.ansi[70], '#5faf00'); - assert.equal(cm.colors.ansi[71], '#5faf5f'); - assert.equal(cm.colors.ansi[72], '#5faf87'); - assert.equal(cm.colors.ansi[73], '#5fafaf'); - assert.equal(cm.colors.ansi[74], '#5fafd7'); - assert.equal(cm.colors.ansi[75], '#5fafff'); - assert.equal(cm.colors.ansi[76], '#5fd700'); - assert.equal(cm.colors.ansi[77], '#5fd75f'); - assert.equal(cm.colors.ansi[78], '#5fd787'); - assert.equal(cm.colors.ansi[79], '#5fd7af'); - assert.equal(cm.colors.ansi[80], '#5fd7d7'); - assert.equal(cm.colors.ansi[81], '#5fd7ff'); - assert.equal(cm.colors.ansi[82], '#5fff00'); - assert.equal(cm.colors.ansi[83], '#5fff5f'); - assert.equal(cm.colors.ansi[84], '#5fff87'); - assert.equal(cm.colors.ansi[85], '#5fffaf'); - assert.equal(cm.colors.ansi[86], '#5fffd7'); - assert.equal(cm.colors.ansi[87], '#5fffff'); - assert.equal(cm.colors.ansi[88], '#870000'); - assert.equal(cm.colors.ansi[89], '#87005f'); - assert.equal(cm.colors.ansi[90], '#870087'); - assert.equal(cm.colors.ansi[91], '#8700af'); - assert.equal(cm.colors.ansi[92], '#8700d7'); - assert.equal(cm.colors.ansi[93], '#8700ff'); - assert.equal(cm.colors.ansi[94], '#875f00'); - assert.equal(cm.colors.ansi[95], '#875f5f'); - assert.equal(cm.colors.ansi[96], '#875f87'); - assert.equal(cm.colors.ansi[97], '#875faf'); - assert.equal(cm.colors.ansi[98], '#875fd7'); - assert.equal(cm.colors.ansi[99], '#875fff'); - assert.equal(cm.colors.ansi[100], '#878700'); - assert.equal(cm.colors.ansi[101], '#87875f'); - assert.equal(cm.colors.ansi[102], '#878787'); - assert.equal(cm.colors.ansi[103], '#8787af'); - assert.equal(cm.colors.ansi[104], '#8787d7'); - assert.equal(cm.colors.ansi[105], '#8787ff'); - assert.equal(cm.colors.ansi[106], '#87af00'); - assert.equal(cm.colors.ansi[107], '#87af5f'); - assert.equal(cm.colors.ansi[108], '#87af87'); - assert.equal(cm.colors.ansi[109], '#87afaf'); - assert.equal(cm.colors.ansi[110], '#87afd7'); - assert.equal(cm.colors.ansi[111], '#87afff'); - assert.equal(cm.colors.ansi[112], '#87d700'); - assert.equal(cm.colors.ansi[113], '#87d75f'); - assert.equal(cm.colors.ansi[114], '#87d787'); - assert.equal(cm.colors.ansi[115], '#87d7af'); - assert.equal(cm.colors.ansi[116], '#87d7d7'); - assert.equal(cm.colors.ansi[117], '#87d7ff'); - assert.equal(cm.colors.ansi[118], '#87ff00'); - assert.equal(cm.colors.ansi[119], '#87ff5f'); - assert.equal(cm.colors.ansi[120], '#87ff87'); - assert.equal(cm.colors.ansi[121], '#87ffaf'); - assert.equal(cm.colors.ansi[122], '#87ffd7'); - assert.equal(cm.colors.ansi[123], '#87ffff'); - assert.equal(cm.colors.ansi[124], '#af0000'); - assert.equal(cm.colors.ansi[125], '#af005f'); - assert.equal(cm.colors.ansi[126], '#af0087'); - assert.equal(cm.colors.ansi[127], '#af00af'); - assert.equal(cm.colors.ansi[128], '#af00d7'); - assert.equal(cm.colors.ansi[129], '#af00ff'); - assert.equal(cm.colors.ansi[130], '#af5f00'); - assert.equal(cm.colors.ansi[131], '#af5f5f'); - assert.equal(cm.colors.ansi[132], '#af5f87'); - assert.equal(cm.colors.ansi[133], '#af5faf'); - assert.equal(cm.colors.ansi[134], '#af5fd7'); - assert.equal(cm.colors.ansi[135], '#af5fff'); - assert.equal(cm.colors.ansi[136], '#af8700'); - assert.equal(cm.colors.ansi[137], '#af875f'); - assert.equal(cm.colors.ansi[138], '#af8787'); - assert.equal(cm.colors.ansi[139], '#af87af'); - assert.equal(cm.colors.ansi[140], '#af87d7'); - assert.equal(cm.colors.ansi[141], '#af87ff'); - assert.equal(cm.colors.ansi[142], '#afaf00'); - assert.equal(cm.colors.ansi[143], '#afaf5f'); - assert.equal(cm.colors.ansi[144], '#afaf87'); - assert.equal(cm.colors.ansi[145], '#afafaf'); - assert.equal(cm.colors.ansi[146], '#afafd7'); - assert.equal(cm.colors.ansi[147], '#afafff'); - assert.equal(cm.colors.ansi[148], '#afd700'); - assert.equal(cm.colors.ansi[149], '#afd75f'); - assert.equal(cm.colors.ansi[150], '#afd787'); - assert.equal(cm.colors.ansi[151], '#afd7af'); - assert.equal(cm.colors.ansi[152], '#afd7d7'); - assert.equal(cm.colors.ansi[153], '#afd7ff'); - assert.equal(cm.colors.ansi[154], '#afff00'); - assert.equal(cm.colors.ansi[155], '#afff5f'); - assert.equal(cm.colors.ansi[156], '#afff87'); - assert.equal(cm.colors.ansi[157], '#afffaf'); - assert.equal(cm.colors.ansi[158], '#afffd7'); - assert.equal(cm.colors.ansi[159], '#afffff'); - assert.equal(cm.colors.ansi[160], '#d70000'); - assert.equal(cm.colors.ansi[161], '#d7005f'); - assert.equal(cm.colors.ansi[162], '#d70087'); - assert.equal(cm.colors.ansi[163], '#d700af'); - assert.equal(cm.colors.ansi[164], '#d700d7'); - assert.equal(cm.colors.ansi[165], '#d700ff'); - assert.equal(cm.colors.ansi[166], '#d75f00'); - assert.equal(cm.colors.ansi[167], '#d75f5f'); - assert.equal(cm.colors.ansi[168], '#d75f87'); - assert.equal(cm.colors.ansi[169], '#d75faf'); - assert.equal(cm.colors.ansi[170], '#d75fd7'); - assert.equal(cm.colors.ansi[171], '#d75fff'); - assert.equal(cm.colors.ansi[172], '#d78700'); - assert.equal(cm.colors.ansi[173], '#d7875f'); - assert.equal(cm.colors.ansi[174], '#d78787'); - assert.equal(cm.colors.ansi[175], '#d787af'); - assert.equal(cm.colors.ansi[176], '#d787d7'); - assert.equal(cm.colors.ansi[177], '#d787ff'); - assert.equal(cm.colors.ansi[178], '#d7af00'); - assert.equal(cm.colors.ansi[179], '#d7af5f'); - assert.equal(cm.colors.ansi[180], '#d7af87'); - assert.equal(cm.colors.ansi[181], '#d7afaf'); - assert.equal(cm.colors.ansi[182], '#d7afd7'); - assert.equal(cm.colors.ansi[183], '#d7afff'); - assert.equal(cm.colors.ansi[184], '#d7d700'); - assert.equal(cm.colors.ansi[185], '#d7d75f'); - assert.equal(cm.colors.ansi[186], '#d7d787'); - assert.equal(cm.colors.ansi[187], '#d7d7af'); - assert.equal(cm.colors.ansi[188], '#d7d7d7'); - assert.equal(cm.colors.ansi[189], '#d7d7ff'); - assert.equal(cm.colors.ansi[190], '#d7ff00'); - assert.equal(cm.colors.ansi[191], '#d7ff5f'); - assert.equal(cm.colors.ansi[192], '#d7ff87'); - assert.equal(cm.colors.ansi[193], '#d7ffaf'); - assert.equal(cm.colors.ansi[194], '#d7ffd7'); - assert.equal(cm.colors.ansi[195], '#d7ffff'); - assert.equal(cm.colors.ansi[196], '#ff0000'); - assert.equal(cm.colors.ansi[197], '#ff005f'); - assert.equal(cm.colors.ansi[198], '#ff0087'); - assert.equal(cm.colors.ansi[199], '#ff00af'); - assert.equal(cm.colors.ansi[200], '#ff00d7'); - assert.equal(cm.colors.ansi[201], '#ff00ff'); - assert.equal(cm.colors.ansi[202], '#ff5f00'); - assert.equal(cm.colors.ansi[203], '#ff5f5f'); - assert.equal(cm.colors.ansi[204], '#ff5f87'); - assert.equal(cm.colors.ansi[205], '#ff5faf'); - assert.equal(cm.colors.ansi[206], '#ff5fd7'); - assert.equal(cm.colors.ansi[207], '#ff5fff'); - assert.equal(cm.colors.ansi[208], '#ff8700'); - assert.equal(cm.colors.ansi[209], '#ff875f'); - assert.equal(cm.colors.ansi[210], '#ff8787'); - assert.equal(cm.colors.ansi[211], '#ff87af'); - assert.equal(cm.colors.ansi[212], '#ff87d7'); - assert.equal(cm.colors.ansi[213], '#ff87ff'); - assert.equal(cm.colors.ansi[214], '#ffaf00'); - assert.equal(cm.colors.ansi[215], '#ffaf5f'); - assert.equal(cm.colors.ansi[216], '#ffaf87'); - assert.equal(cm.colors.ansi[217], '#ffafaf'); - assert.equal(cm.colors.ansi[218], '#ffafd7'); - assert.equal(cm.colors.ansi[219], '#ffafff'); - assert.equal(cm.colors.ansi[220], '#ffd700'); - assert.equal(cm.colors.ansi[221], '#ffd75f'); - assert.equal(cm.colors.ansi[222], '#ffd787'); - assert.equal(cm.colors.ansi[223], '#ffd7af'); - assert.equal(cm.colors.ansi[224], '#ffd7d7'); - assert.equal(cm.colors.ansi[225], '#ffd7ff'); - assert.equal(cm.colors.ansi[226], '#ffff00'); - assert.equal(cm.colors.ansi[227], '#ffff5f'); - assert.equal(cm.colors.ansi[228], '#ffff87'); - assert.equal(cm.colors.ansi[229], '#ffffaf'); - assert.equal(cm.colors.ansi[230], '#ffffd7'); - assert.equal(cm.colors.ansi[231], '#ffffff'); - assert.equal(cm.colors.ansi[232], '#080808'); - assert.equal(cm.colors.ansi[233], '#121212'); - assert.equal(cm.colors.ansi[234], '#1c1c1c'); - assert.equal(cm.colors.ansi[235], '#262626'); - assert.equal(cm.colors.ansi[236], '#303030'); - assert.equal(cm.colors.ansi[237], '#3a3a3a'); - assert.equal(cm.colors.ansi[238], '#444444'); - assert.equal(cm.colors.ansi[239], '#4e4e4e'); - assert.equal(cm.colors.ansi[240], '#585858'); - assert.equal(cm.colors.ansi[241], '#626262'); - assert.equal(cm.colors.ansi[242], '#6c6c6c'); - assert.equal(cm.colors.ansi[243], '#767676'); - assert.equal(cm.colors.ansi[244], '#808080'); - assert.equal(cm.colors.ansi[245], '#8a8a8a'); - assert.equal(cm.colors.ansi[246], '#949494'); - assert.equal(cm.colors.ansi[247], '#9e9e9e'); - assert.equal(cm.colors.ansi[248], '#a8a8a8'); - assert.equal(cm.colors.ansi[249], '#b2b2b2'); - assert.equal(cm.colors.ansi[250], '#bcbcbc'); - assert.equal(cm.colors.ansi[251], '#c6c6c6'); - assert.equal(cm.colors.ansi[252], '#d0d0d0'); - assert.equal(cm.colors.ansi[253], '#dadada'); - assert.equal(cm.colors.ansi[254], '#e4e4e4'); - assert.equal(cm.colors.ansi[255], '#eeeeee'); + assert.equal(cm.colors.ansi[16].css, '#000000'); + assert.equal(cm.colors.ansi[17].css, '#00005f'); + assert.equal(cm.colors.ansi[18].css, '#000087'); + assert.equal(cm.colors.ansi[19].css, '#0000af'); + assert.equal(cm.colors.ansi[20].css, '#0000d7'); + assert.equal(cm.colors.ansi[21].css, '#0000ff'); + assert.equal(cm.colors.ansi[22].css, '#005f00'); + assert.equal(cm.colors.ansi[23].css, '#005f5f'); + assert.equal(cm.colors.ansi[24].css, '#005f87'); + assert.equal(cm.colors.ansi[25].css, '#005faf'); + assert.equal(cm.colors.ansi[26].css, '#005fd7'); + assert.equal(cm.colors.ansi[27].css, '#005fff'); + assert.equal(cm.colors.ansi[28].css, '#008700'); + assert.equal(cm.colors.ansi[29].css, '#00875f'); + assert.equal(cm.colors.ansi[30].css, '#008787'); + assert.equal(cm.colors.ansi[31].css, '#0087af'); + assert.equal(cm.colors.ansi[32].css, '#0087d7'); + assert.equal(cm.colors.ansi[33].css, '#0087ff'); + assert.equal(cm.colors.ansi[34].css, '#00af00'); + assert.equal(cm.colors.ansi[35].css, '#00af5f'); + assert.equal(cm.colors.ansi[36].css, '#00af87'); + assert.equal(cm.colors.ansi[37].css, '#00afaf'); + assert.equal(cm.colors.ansi[38].css, '#00afd7'); + assert.equal(cm.colors.ansi[39].css, '#00afff'); + assert.equal(cm.colors.ansi[40].css, '#00d700'); + assert.equal(cm.colors.ansi[41].css, '#00d75f'); + assert.equal(cm.colors.ansi[42].css, '#00d787'); + assert.equal(cm.colors.ansi[43].css, '#00d7af'); + assert.equal(cm.colors.ansi[44].css, '#00d7d7'); + assert.equal(cm.colors.ansi[45].css, '#00d7ff'); + assert.equal(cm.colors.ansi[46].css, '#00ff00'); + assert.equal(cm.colors.ansi[47].css, '#00ff5f'); + assert.equal(cm.colors.ansi[48].css, '#00ff87'); + assert.equal(cm.colors.ansi[49].css, '#00ffaf'); + assert.equal(cm.colors.ansi[50].css, '#00ffd7'); + assert.equal(cm.colors.ansi[51].css, '#00ffff'); + assert.equal(cm.colors.ansi[52].css, '#5f0000'); + assert.equal(cm.colors.ansi[53].css, '#5f005f'); + assert.equal(cm.colors.ansi[54].css, '#5f0087'); + assert.equal(cm.colors.ansi[55].css, '#5f00af'); + assert.equal(cm.colors.ansi[56].css, '#5f00d7'); + assert.equal(cm.colors.ansi[57].css, '#5f00ff'); + assert.equal(cm.colors.ansi[58].css, '#5f5f00'); + assert.equal(cm.colors.ansi[59].css, '#5f5f5f'); + assert.equal(cm.colors.ansi[60].css, '#5f5f87'); + assert.equal(cm.colors.ansi[61].css, '#5f5faf'); + assert.equal(cm.colors.ansi[62].css, '#5f5fd7'); + assert.equal(cm.colors.ansi[63].css, '#5f5fff'); + assert.equal(cm.colors.ansi[64].css, '#5f8700'); + assert.equal(cm.colors.ansi[65].css, '#5f875f'); + assert.equal(cm.colors.ansi[66].css, '#5f8787'); + assert.equal(cm.colors.ansi[67].css, '#5f87af'); + assert.equal(cm.colors.ansi[68].css, '#5f87d7'); + assert.equal(cm.colors.ansi[69].css, '#5f87ff'); + assert.equal(cm.colors.ansi[70].css, '#5faf00'); + assert.equal(cm.colors.ansi[71].css, '#5faf5f'); + assert.equal(cm.colors.ansi[72].css, '#5faf87'); + assert.equal(cm.colors.ansi[73].css, '#5fafaf'); + assert.equal(cm.colors.ansi[74].css, '#5fafd7'); + assert.equal(cm.colors.ansi[75].css, '#5fafff'); + assert.equal(cm.colors.ansi[76].css, '#5fd700'); + assert.equal(cm.colors.ansi[77].css, '#5fd75f'); + assert.equal(cm.colors.ansi[78].css, '#5fd787'); + assert.equal(cm.colors.ansi[79].css, '#5fd7af'); + assert.equal(cm.colors.ansi[80].css, '#5fd7d7'); + assert.equal(cm.colors.ansi[81].css, '#5fd7ff'); + assert.equal(cm.colors.ansi[82].css, '#5fff00'); + assert.equal(cm.colors.ansi[83].css, '#5fff5f'); + assert.equal(cm.colors.ansi[84].css, '#5fff87'); + assert.equal(cm.colors.ansi[85].css, '#5fffaf'); + assert.equal(cm.colors.ansi[86].css, '#5fffd7'); + assert.equal(cm.colors.ansi[87].css, '#5fffff'); + assert.equal(cm.colors.ansi[88].css, '#870000'); + assert.equal(cm.colors.ansi[89].css, '#87005f'); + assert.equal(cm.colors.ansi[90].css, '#870087'); + assert.equal(cm.colors.ansi[91].css, '#8700af'); + assert.equal(cm.colors.ansi[92].css, '#8700d7'); + assert.equal(cm.colors.ansi[93].css, '#8700ff'); + assert.equal(cm.colors.ansi[94].css, '#875f00'); + assert.equal(cm.colors.ansi[95].css, '#875f5f'); + assert.equal(cm.colors.ansi[96].css, '#875f87'); + assert.equal(cm.colors.ansi[97].css, '#875faf'); + assert.equal(cm.colors.ansi[98].css, '#875fd7'); + assert.equal(cm.colors.ansi[99].css, '#875fff'); + assert.equal(cm.colors.ansi[100].css, '#878700'); + assert.equal(cm.colors.ansi[101].css, '#87875f'); + assert.equal(cm.colors.ansi[102].css, '#878787'); + assert.equal(cm.colors.ansi[103].css, '#8787af'); + assert.equal(cm.colors.ansi[104].css, '#8787d7'); + assert.equal(cm.colors.ansi[105].css, '#8787ff'); + assert.equal(cm.colors.ansi[106].css, '#87af00'); + assert.equal(cm.colors.ansi[107].css, '#87af5f'); + assert.equal(cm.colors.ansi[108].css, '#87af87'); + assert.equal(cm.colors.ansi[109].css, '#87afaf'); + assert.equal(cm.colors.ansi[110].css, '#87afd7'); + assert.equal(cm.colors.ansi[111].css, '#87afff'); + assert.equal(cm.colors.ansi[112].css, '#87d700'); + assert.equal(cm.colors.ansi[113].css, '#87d75f'); + assert.equal(cm.colors.ansi[114].css, '#87d787'); + assert.equal(cm.colors.ansi[115].css, '#87d7af'); + assert.equal(cm.colors.ansi[116].css, '#87d7d7'); + assert.equal(cm.colors.ansi[117].css, '#87d7ff'); + assert.equal(cm.colors.ansi[118].css, '#87ff00'); + assert.equal(cm.colors.ansi[119].css, '#87ff5f'); + assert.equal(cm.colors.ansi[120].css, '#87ff87'); + assert.equal(cm.colors.ansi[121].css, '#87ffaf'); + assert.equal(cm.colors.ansi[122].css, '#87ffd7'); + assert.equal(cm.colors.ansi[123].css, '#87ffff'); + assert.equal(cm.colors.ansi[124].css, '#af0000'); + assert.equal(cm.colors.ansi[125].css, '#af005f'); + assert.equal(cm.colors.ansi[126].css, '#af0087'); + assert.equal(cm.colors.ansi[127].css, '#af00af'); + assert.equal(cm.colors.ansi[128].css, '#af00d7'); + assert.equal(cm.colors.ansi[129].css, '#af00ff'); + assert.equal(cm.colors.ansi[130].css, '#af5f00'); + assert.equal(cm.colors.ansi[131].css, '#af5f5f'); + assert.equal(cm.colors.ansi[132].css, '#af5f87'); + assert.equal(cm.colors.ansi[133].css, '#af5faf'); + assert.equal(cm.colors.ansi[134].css, '#af5fd7'); + assert.equal(cm.colors.ansi[135].css, '#af5fff'); + assert.equal(cm.colors.ansi[136].css, '#af8700'); + assert.equal(cm.colors.ansi[137].css, '#af875f'); + assert.equal(cm.colors.ansi[138].css, '#af8787'); + assert.equal(cm.colors.ansi[139].css, '#af87af'); + assert.equal(cm.colors.ansi[140].css, '#af87d7'); + assert.equal(cm.colors.ansi[141].css, '#af87ff'); + assert.equal(cm.colors.ansi[142].css, '#afaf00'); + assert.equal(cm.colors.ansi[143].css, '#afaf5f'); + assert.equal(cm.colors.ansi[144].css, '#afaf87'); + assert.equal(cm.colors.ansi[145].css, '#afafaf'); + assert.equal(cm.colors.ansi[146].css, '#afafd7'); + assert.equal(cm.colors.ansi[147].css, '#afafff'); + assert.equal(cm.colors.ansi[148].css, '#afd700'); + assert.equal(cm.colors.ansi[149].css, '#afd75f'); + assert.equal(cm.colors.ansi[150].css, '#afd787'); + assert.equal(cm.colors.ansi[151].css, '#afd7af'); + assert.equal(cm.colors.ansi[152].css, '#afd7d7'); + assert.equal(cm.colors.ansi[153].css, '#afd7ff'); + assert.equal(cm.colors.ansi[154].css, '#afff00'); + assert.equal(cm.colors.ansi[155].css, '#afff5f'); + assert.equal(cm.colors.ansi[156].css, '#afff87'); + assert.equal(cm.colors.ansi[157].css, '#afffaf'); + assert.equal(cm.colors.ansi[158].css, '#afffd7'); + assert.equal(cm.colors.ansi[159].css, '#afffff'); + assert.equal(cm.colors.ansi[160].css, '#d70000'); + assert.equal(cm.colors.ansi[161].css, '#d7005f'); + assert.equal(cm.colors.ansi[162].css, '#d70087'); + assert.equal(cm.colors.ansi[163].css, '#d700af'); + assert.equal(cm.colors.ansi[164].css, '#d700d7'); + assert.equal(cm.colors.ansi[165].css, '#d700ff'); + assert.equal(cm.colors.ansi[166].css, '#d75f00'); + assert.equal(cm.colors.ansi[167].css, '#d75f5f'); + assert.equal(cm.colors.ansi[168].css, '#d75f87'); + assert.equal(cm.colors.ansi[169].css, '#d75faf'); + assert.equal(cm.colors.ansi[170].css, '#d75fd7'); + assert.equal(cm.colors.ansi[171].css, '#d75fff'); + assert.equal(cm.colors.ansi[172].css, '#d78700'); + assert.equal(cm.colors.ansi[173].css, '#d7875f'); + assert.equal(cm.colors.ansi[174].css, '#d78787'); + assert.equal(cm.colors.ansi[175].css, '#d787af'); + assert.equal(cm.colors.ansi[176].css, '#d787d7'); + assert.equal(cm.colors.ansi[177].css, '#d787ff'); + assert.equal(cm.colors.ansi[178].css, '#d7af00'); + assert.equal(cm.colors.ansi[179].css, '#d7af5f'); + assert.equal(cm.colors.ansi[180].css, '#d7af87'); + assert.equal(cm.colors.ansi[181].css, '#d7afaf'); + assert.equal(cm.colors.ansi[182].css, '#d7afd7'); + assert.equal(cm.colors.ansi[183].css, '#d7afff'); + assert.equal(cm.colors.ansi[184].css, '#d7d700'); + assert.equal(cm.colors.ansi[185].css, '#d7d75f'); + assert.equal(cm.colors.ansi[186].css, '#d7d787'); + assert.equal(cm.colors.ansi[187].css, '#d7d7af'); + assert.equal(cm.colors.ansi[188].css, '#d7d7d7'); + assert.equal(cm.colors.ansi[189].css, '#d7d7ff'); + assert.equal(cm.colors.ansi[190].css, '#d7ff00'); + assert.equal(cm.colors.ansi[191].css, '#d7ff5f'); + assert.equal(cm.colors.ansi[192].css, '#d7ff87'); + assert.equal(cm.colors.ansi[193].css, '#d7ffaf'); + assert.equal(cm.colors.ansi[194].css, '#d7ffd7'); + assert.equal(cm.colors.ansi[195].css, '#d7ffff'); + assert.equal(cm.colors.ansi[196].css, '#ff0000'); + assert.equal(cm.colors.ansi[197].css, '#ff005f'); + assert.equal(cm.colors.ansi[198].css, '#ff0087'); + assert.equal(cm.colors.ansi[199].css, '#ff00af'); + assert.equal(cm.colors.ansi[200].css, '#ff00d7'); + assert.equal(cm.colors.ansi[201].css, '#ff00ff'); + assert.equal(cm.colors.ansi[202].css, '#ff5f00'); + assert.equal(cm.colors.ansi[203].css, '#ff5f5f'); + assert.equal(cm.colors.ansi[204].css, '#ff5f87'); + assert.equal(cm.colors.ansi[205].css, '#ff5faf'); + assert.equal(cm.colors.ansi[206].css, '#ff5fd7'); + assert.equal(cm.colors.ansi[207].css, '#ff5fff'); + assert.equal(cm.colors.ansi[208].css, '#ff8700'); + assert.equal(cm.colors.ansi[209].css, '#ff875f'); + assert.equal(cm.colors.ansi[210].css, '#ff8787'); + assert.equal(cm.colors.ansi[211].css, '#ff87af'); + assert.equal(cm.colors.ansi[212].css, '#ff87d7'); + assert.equal(cm.colors.ansi[213].css, '#ff87ff'); + assert.equal(cm.colors.ansi[214].css, '#ffaf00'); + assert.equal(cm.colors.ansi[215].css, '#ffaf5f'); + assert.equal(cm.colors.ansi[216].css, '#ffaf87'); + assert.equal(cm.colors.ansi[217].css, '#ffafaf'); + assert.equal(cm.colors.ansi[218].css, '#ffafd7'); + assert.equal(cm.colors.ansi[219].css, '#ffafff'); + assert.equal(cm.colors.ansi[220].css, '#ffd700'); + assert.equal(cm.colors.ansi[221].css, '#ffd75f'); + assert.equal(cm.colors.ansi[222].css, '#ffd787'); + assert.equal(cm.colors.ansi[223].css, '#ffd7af'); + assert.equal(cm.colors.ansi[224].css, '#ffd7d7'); + assert.equal(cm.colors.ansi[225].css, '#ffd7ff'); + assert.equal(cm.colors.ansi[226].css, '#ffff00'); + assert.equal(cm.colors.ansi[227].css, '#ffff5f'); + assert.equal(cm.colors.ansi[228].css, '#ffff87'); + assert.equal(cm.colors.ansi[229].css, '#ffffaf'); + assert.equal(cm.colors.ansi[230].css, '#ffffd7'); + assert.equal(cm.colors.ansi[231].css, '#ffffff'); + assert.equal(cm.colors.ansi[232].css, '#080808'); + assert.equal(cm.colors.ansi[233].css, '#121212'); + assert.equal(cm.colors.ansi[234].css, '#1c1c1c'); + assert.equal(cm.colors.ansi[235].css, '#262626'); + assert.equal(cm.colors.ansi[236].css, '#303030'); + assert.equal(cm.colors.ansi[237].css, '#3a3a3a'); + assert.equal(cm.colors.ansi[238].css, '#444444'); + assert.equal(cm.colors.ansi[239].css, '#4e4e4e'); + assert.equal(cm.colors.ansi[240].css, '#585858'); + assert.equal(cm.colors.ansi[241].css, '#626262'); + assert.equal(cm.colors.ansi[242].css, '#6c6c6c'); + assert.equal(cm.colors.ansi[243].css, '#767676'); + assert.equal(cm.colors.ansi[244].css, '#808080'); + assert.equal(cm.colors.ansi[245].css, '#8a8a8a'); + assert.equal(cm.colors.ansi[246].css, '#949494'); + assert.equal(cm.colors.ansi[247].css, '#9e9e9e'); + assert.equal(cm.colors.ansi[248].css, '#a8a8a8'); + assert.equal(cm.colors.ansi[249].css, '#b2b2b2'); + assert.equal(cm.colors.ansi[250].css, '#bcbcbc'); + assert.equal(cm.colors.ansi[251].css, '#c6c6c6'); + assert.equal(cm.colors.ansi[252].css, '#d0d0d0'); + assert.equal(cm.colors.ansi[253].css, '#dadada'); + assert.equal(cm.colors.ansi[254].css, '#e4e4e4'); + assert.equal(cm.colors.ansi[255].css, '#eeeeee'); }); }); @@ -283,20 +294,20 @@ describe('ColorManager', () => { }); it('should set a partial set of colors, using the default if not present', () => { - assert.equal(cm.colors.background, '#000000'); - assert.equal(cm.colors.foreground, '#ffffff'); + assert.equal(cm.colors.background.css, '#000000'); + assert.equal(cm.colors.foreground.css, '#ffffff'); cm.setTheme({ background: '#FF0000', foreground: '#00FF00' }); - assert.equal(cm.colors.background, '#FF0000'); - assert.equal(cm.colors.foreground, '#00FF00'); + assert.equal(cm.colors.background.css, '#FF0000'); + assert.equal(cm.colors.foreground.css, '#00FF00'); cm.setTheme({ background: '#0000FF' }); - assert.equal(cm.colors.background, '#0000FF'); + assert.equal(cm.colors.background.css, '#0000FF'); // FG reverts back to default - assert.equal(cm.colors.foreground, '#ffffff'); + assert.equal(cm.colors.foreground.css, '#ffffff'); }); }); }); diff --git a/src/renderer/ColorManager.ts b/src/renderer/ColorManager.ts index ddb928a59..8517a3554 100644 --- a/src/renderer/ColorManager.ts +++ b/src/renderer/ColorManager.ts @@ -3,58 +3,75 @@ * @license MIT */ -import { IColorSet, IColorManager } from './Types'; +import { IColorManager } from './Types'; +import { IColor, IColorSet } from '../shared/Types'; import { ITheme } from 'xterm'; -const DEFAULT_FOREGROUND = '#ffffff'; -const DEFAULT_BACKGROUND = '#000000'; -const DEFAULT_CURSOR = '#ffffff'; -const DEFAULT_CURSOR_ACCENT = '#000000'; -const DEFAULT_SELECTION = 'rgba(255, 255, 255, 0.3)'; -export const DEFAULT_ANSI_COLORS = [ - // dark: - '#2e3436', - '#cc0000', - '#4e9a06', - '#c4a000', - '#3465a4', - '#75507b', - '#06989a', - '#d3d7cf', - // bright: - '#555753', - '#ef2929', - '#8ae234', - '#fce94f', - '#729fcf', - '#ad7fa8', - '#34e2e2', - '#eeeeec' -]; +const DEFAULT_FOREGROUND = fromHex('#ffffff'); +const DEFAULT_BACKGROUND = fromHex('#000000'); +const DEFAULT_CURSOR = fromHex('#ffffff'); +const DEFAULT_CURSOR_ACCENT = fromHex('#000000'); +const DEFAULT_SELECTION = { + css: 'rgba(255, 255, 255, 0.3)', + rgba: 0xFFFFFF77 +}; -/** - * Fills an existing 16 length string with the remaining 240 ANSI colors. - * @param first16Colors The first 16 ANSI colors. - */ -function generate256Colors(first16Colors: string[]): string[] { - let colors = first16Colors.slice(); +// An IIFE to generate DEFAULT_ANSI_COLORS. Do not mutate DEFAULT_ANSI_COLORS, instead make a copy +// and mutate that. +export const DEFAULT_ANSI_COLORS = (() => { + const colors = [ + // dark: + fromHex('#2e3436'), + fromHex('#cc0000'), + fromHex('#4e9a06'), + fromHex('#c4a000'), + fromHex('#3465a4'), + fromHex('#75507b'), + fromHex('#06989a'), + fromHex('#d3d7cf'), + // bright: + fromHex('#555753'), + fromHex('#ef2929'), + fromHex('#8ae234'), + fromHex('#fce94f'), + fromHex('#729fcf'), + fromHex('#ad7fa8'), + fromHex('#34e2e2'), + fromHex('#eeeeec') + ]; + // Fill in the remaining 240 ANSI colors. // Generate colors (16-231) let v = [0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff]; for (let i = 0; i < 216; i++) { - const r = toPaddedHex(v[(i / 36) % 6 | 0]); - const g = toPaddedHex(v[(i / 6) % 6 | 0]); - const b = toPaddedHex(v[i % 6]); - colors.push(`#${r}${g}${b}`); + const r = v[(i / 36) % 6 | 0]; + const g = v[(i / 6) % 6 | 0]; + const b = v[i % 6]; + colors.push({ + css: `#${toPaddedHex(r)}${toPaddedHex(g)}${toPaddedHex(b)}`, + // Use >>> 0 to force a conversion to an unsigned int + rgba: ((r << 24) | (g << 16) | (b << 8) | 0xFF) >>> 0 + }); } // Generate greys (232-255) for (let i = 0; i < 24; i++) { - const c = toPaddedHex(8 + i * 10); - colors.push(`#${c}${c}${c}`); + const c = 8 + i * 10; + const ch = toPaddedHex(c); + colors.push({ + css: `#${ch}${ch}${ch}`, + rgba: ((c << 24) | (c << 16) | (c << 8) | 0xFF) >>> 0 + }); } return colors; +})(); + +function fromHex(css: string): IColor { + return { + css, + rgba: parseInt(css.slice(1), 16) << 8 | 0xFF + }; } function toPaddedHex(c: number): string { @@ -67,17 +84,23 @@ function toPaddedHex(c: number): string { */ export class ColorManager implements IColorManager { public colors: IColorSet; - private _document: Document; + private _ctx: CanvasRenderingContext2D; + private _litmusColor: CanvasGradient; constructor(document: Document) { - this._document = document; + const canvas = document.createElement('canvas'); + canvas.width = 1; + canvas.height = 1; + this._ctx = canvas.getContext('2d'); + this._ctx.globalCompositeOperation = 'copy'; + this._litmusColor = this._ctx.createLinearGradient(0, 0, 1, 1); this.colors = { foreground: DEFAULT_FOREGROUND, background: DEFAULT_BACKGROUND, cursor: DEFAULT_CURSOR, cursorAccent: DEFAULT_CURSOR_ACCENT, selection: DEFAULT_SELECTION, - ansi: generate256Colors(DEFAULT_ANSI_COLORS) + ansi: DEFAULT_ANSI_COLORS.slice() }; } @@ -87,54 +110,50 @@ export class ColorManager implements IColorManager { * colors will be used where colors are not defined. */ public setTheme(theme: ITheme): void { - this.colors.foreground = this._validateColor(theme.foreground, DEFAULT_FOREGROUND); - this.colors.background = this._validateColor(theme.background, DEFAULT_BACKGROUND); - this.colors.cursor = this._validateColor(theme.cursor, DEFAULT_CURSOR); - this.colors.cursorAccent = this._validateColor(theme.cursorAccent, DEFAULT_CURSOR_ACCENT); - this.colors.selection = this._validateColor(theme.selection, DEFAULT_SELECTION); - this.colors.ansi[0] = this._validateColor(theme.black, DEFAULT_ANSI_COLORS[0]); - this.colors.ansi[1] = this._validateColor(theme.red, DEFAULT_ANSI_COLORS[1]); - this.colors.ansi[2] = this._validateColor(theme.green, DEFAULT_ANSI_COLORS[2]); - this.colors.ansi[3] = this._validateColor(theme.yellow, DEFAULT_ANSI_COLORS[3]); - this.colors.ansi[4] = this._validateColor(theme.blue, DEFAULT_ANSI_COLORS[4]); - this.colors.ansi[5] = this._validateColor(theme.magenta, DEFAULT_ANSI_COLORS[5]); - this.colors.ansi[6] = this._validateColor(theme.cyan, DEFAULT_ANSI_COLORS[6]); - this.colors.ansi[7] = this._validateColor(theme.white, DEFAULT_ANSI_COLORS[7]); - this.colors.ansi[8] = this._validateColor(theme.brightBlack, DEFAULT_ANSI_COLORS[8]); - this.colors.ansi[9] = this._validateColor(theme.brightRed, DEFAULT_ANSI_COLORS[9]); - this.colors.ansi[10] = this._validateColor(theme.brightGreen, DEFAULT_ANSI_COLORS[10]); - this.colors.ansi[11] = this._validateColor(theme.brightYellow, DEFAULT_ANSI_COLORS[11]); - this.colors.ansi[12] = this._validateColor(theme.brightBlue, DEFAULT_ANSI_COLORS[12]); - this.colors.ansi[13] = this._validateColor(theme.brightMagenta, DEFAULT_ANSI_COLORS[13]); - this.colors.ansi[14] = this._validateColor(theme.brightCyan, DEFAULT_ANSI_COLORS[14]); - this.colors.ansi[15] = this._validateColor(theme.brightWhite, DEFAULT_ANSI_COLORS[15]); + this.colors.foreground = this._parseColor(theme.foreground, DEFAULT_FOREGROUND); + this.colors.background = this._parseColor(theme.background, DEFAULT_BACKGROUND); + this.colors.cursor = this._parseColor(theme.cursor, DEFAULT_CURSOR); + this.colors.cursorAccent = this._parseColor(theme.cursorAccent, DEFAULT_CURSOR_ACCENT); + this.colors.selection = this._parseColor(theme.selection, DEFAULT_SELECTION); + this.colors.ansi[0] = this._parseColor(theme.black, DEFAULT_ANSI_COLORS[0]); + this.colors.ansi[1] = this._parseColor(theme.red, DEFAULT_ANSI_COLORS[1]); + this.colors.ansi[2] = this._parseColor(theme.green, DEFAULT_ANSI_COLORS[2]); + this.colors.ansi[3] = this._parseColor(theme.yellow, DEFAULT_ANSI_COLORS[3]); + this.colors.ansi[4] = this._parseColor(theme.blue, DEFAULT_ANSI_COLORS[4]); + this.colors.ansi[5] = this._parseColor(theme.magenta, DEFAULT_ANSI_COLORS[5]); + this.colors.ansi[6] = this._parseColor(theme.cyan, DEFAULT_ANSI_COLORS[6]); + this.colors.ansi[7] = this._parseColor(theme.white, DEFAULT_ANSI_COLORS[7]); + this.colors.ansi[8] = this._parseColor(theme.brightBlack, DEFAULT_ANSI_COLORS[8]); + this.colors.ansi[9] = this._parseColor(theme.brightRed, DEFAULT_ANSI_COLORS[9]); + this.colors.ansi[10] = this._parseColor(theme.brightGreen, DEFAULT_ANSI_COLORS[10]); + this.colors.ansi[11] = this._parseColor(theme.brightYellow, DEFAULT_ANSI_COLORS[11]); + this.colors.ansi[12] = this._parseColor(theme.brightBlue, DEFAULT_ANSI_COLORS[12]); + this.colors.ansi[13] = this._parseColor(theme.brightMagenta, DEFAULT_ANSI_COLORS[13]); + this.colors.ansi[14] = this._parseColor(theme.brightCyan, DEFAULT_ANSI_COLORS[14]); + this.colors.ansi[15] = this._parseColor(theme.brightWhite, DEFAULT_ANSI_COLORS[15]); } - private _validateColor(color: string, fallback: string): string { - if (!color) { + private _parseColor(css: string, fallback: IColor): IColor { + if (!css) { return fallback; } - const isColorValid = this._isColorValid(color); - - if (!isColorValid) { - console.warn(`Color: ${color} is invalid using fallback ${fallback}`); + // If parsing the value results in failure, then it must be ignored, and the attribute must + // retain its previous value. + // -- https://html.spec.whatwg.org/multipage/canvas.html#fill-and-stroke-styles + this._ctx.fillStyle = this._litmusColor; + this._ctx.fillStyle = css; + if (typeof this._ctx.fillStyle !== 'string') { + console.warn(`Color: ${css} is invalid using fallback ${fallback.css}`); + return fallback; } - return isColorValid ? color : fallback; - } + this._ctx.fillRect(0, 0, 1, 1); + const data = this._ctx.getImageData(0, 0, 1, 1).data; - private _isColorValid(color: string): boolean { - const litmus = 'red'; - const d = this._document.createElement('div'); - d.style.color = litmus; - d.style.color = color; - - // Element's style.color will be reverted to litmus or set to '' if an invalid color is given - if (color !== litmus && (d.style.color === litmus || d.style.color === '')) { - return false; - } - - return true; + return { + css, + rgba: data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3] + }; } } diff --git a/src/renderer/CursorRenderLayer.ts b/src/renderer/CursorRenderLayer.ts index c63c2f14a..2480d8af4 100644 --- a/src/renderer/CursorRenderLayer.ts +++ b/src/renderer/CursorRenderLayer.ts @@ -135,7 +135,7 @@ export class CursorRenderLayer extends BaseRenderLayer { if (!terminal.isFocused) { this._clearCursor(); this._ctx.save(); - this._ctx.fillStyle = this._colors.cursor; + this._ctx.fillStyle = this._colors.cursor.css; this._renderBlurCursor(terminal, terminal.buffer.x, viewportRelativeCursorY, charData); this._ctx.restore(); this._state.x = terminal.buffer.x; @@ -190,30 +190,30 @@ export class CursorRenderLayer extends BaseRenderLayer { private _renderBarCursor(terminal: ITerminal, x: number, y: number, charData: CharData): void { this._ctx.save(); - this._ctx.fillStyle = this._colors.cursor; + this._ctx.fillStyle = this._colors.cursor.css; this.fillLeftLineAtCell(x, y); this._ctx.restore(); } private _renderBlockCursor(terminal: ITerminal, x: number, y: number, charData: CharData): void { this._ctx.save(); - this._ctx.fillStyle = this._colors.cursor; + this._ctx.fillStyle = this._colors.cursor.css; this.fillCells(x, y, charData[CHAR_DATA_WIDTH_INDEX], 1); - this._ctx.fillStyle = this._colors.cursorAccent; + this._ctx.fillStyle = this._colors.cursorAccent.css; this.fillCharTrueColor(terminal, charData, x, y); this._ctx.restore(); } private _renderUnderlineCursor(terminal: ITerminal, x: number, y: number, charData: CharData): void { this._ctx.save(); - this._ctx.fillStyle = this._colors.cursor; + this._ctx.fillStyle = this._colors.cursor.css; this.fillBottomLineAtCells(x, y); this._ctx.restore(); } private _renderBlurCursor(terminal: ITerminal, x: number, y: number, charData: CharData): void { this._ctx.save(); - this._ctx.strokeStyle = this._colors.cursor; + this._ctx.strokeStyle = this._colors.cursor.css; this.strokeRectAtCell(x, y, charData[CHAR_DATA_WIDTH_INDEX], 1); this._ctx.restore(); } diff --git a/src/renderer/LinkRenderLayer.ts b/src/renderer/LinkRenderLayer.ts index 29a5036b0..1e9731f1a 100644 --- a/src/renderer/LinkRenderLayer.ts +++ b/src/renderer/LinkRenderLayer.ts @@ -39,7 +39,7 @@ export class LinkRenderLayer extends BaseRenderLayer { } private _onLinkHover(e: ILinkHoverEvent): void { - this._ctx.fillStyle = this._colors.foreground; + this._ctx.fillStyle = this._colors.foreground.css; if (e.y1 === e.y2) { // Single line link this.fillBottomLineAtCells(e.x1, e.y1, e.x2 - e.x1); diff --git a/src/renderer/SelectionRenderLayer.ts b/src/renderer/SelectionRenderLayer.ts index 7a6a5af86..e2212246b 100644 --- a/src/renderer/SelectionRenderLayer.ts +++ b/src/renderer/SelectionRenderLayer.ts @@ -65,7 +65,7 @@ export class SelectionRenderLayer extends BaseRenderLayer { // Draw first row const startCol = viewportStartRow === viewportCappedStartRow ? start[0] : 0; const startRowEndCol = viewportCappedStartRow === viewportCappedEndRow ? end[0] : terminal.cols; - this._ctx.fillStyle = this._colors.selection; + this._ctx.fillStyle = this._colors.selection.css; this.fillCells(startCol, viewportCappedStartRow, startRowEndCol - startCol, 1); // Draw middle rows diff --git a/src/renderer/TextRenderLayer.ts b/src/renderer/TextRenderLayer.ts index 27ce9f790..2487a294a 100644 --- a/src/renderer/TextRenderLayer.ts +++ b/src/renderer/TextRenderLayer.ts @@ -158,7 +158,7 @@ export class TextRenderLayer extends BaseRenderLayer { // Draw background if (bg < 256) { this._ctx.save(); - this._ctx.fillStyle = (bg === INVERTED_DEFAULT_COLOR ? this._colors.foreground : this._colors.ansi[bg]); + this._ctx.fillStyle = (bg === INVERTED_DEFAULT_COLOR ? this._colors.foreground.css : this._colors.ansi[bg].css); this.fillCells(x, y, width, 1); this._ctx.restore(); } @@ -174,12 +174,12 @@ export class TextRenderLayer extends BaseRenderLayer { if (flags & FLAGS.UNDERLINE) { if (fg === INVERTED_DEFAULT_COLOR) { - this._ctx.fillStyle = this._colors.background; + this._ctx.fillStyle = this._colors.background.css; } else if (fg < 256) { // 256 color support - this._ctx.fillStyle = this._colors.ansi[fg]; + this._ctx.fillStyle = this._colors.ansi[fg].css; } else { - this._ctx.fillStyle = this._colors.foreground; + this._ctx.fillStyle = this._colors.foreground.css; } this.fillBottomLineAtCells(x, y); } diff --git a/src/renderer/atlas/CharAtlasUtils.ts b/src/renderer/atlas/CharAtlasUtils.ts index 89c735eb8..24ed5a488 100644 --- a/src/renderer/atlas/CharAtlasUtils.ts +++ b/src/renderer/atlas/CharAtlasUtils.ts @@ -31,7 +31,7 @@ export function generateConfig(scaledCharWidth: number, scaledCharHeight: number export function configEquals(a: ICharAtlasConfig, b: ICharAtlasConfig): boolean { for (let i = 0; i < a.colors.ansi.length; i++) { - if (a.colors.ansi[i] !== b.colors.ansi[i]) { + if (a.colors.ansi[i].rgba !== b.colors.ansi[i].rgba) { return false; } } diff --git a/src/shared/Types.ts b/src/shared/Types.ts index 0407c2eff..0cded8a85 100644 --- a/src/shared/Types.ts +++ b/src/shared/Types.ts @@ -3,11 +3,16 @@ * @license MIT */ -export interface IColorSet { - foreground: string; - background: string; - cursor: string; - cursorAccent: string; - selection: string; - ansi: string[]; +export interface IColor { + css: string; + rgba: number; // 32-bit int with rgba in each byte +} + +export interface IColorSet { + foreground: IColor; + background: IColor; + cursor: IColor; + cursorAccent: IColor; + selection: IColor; + ansi: IColor[]; } diff --git a/src/shared/atlas/CharAtlasGenerator.ts b/src/shared/atlas/CharAtlasGenerator.ts index 10112efa7..fc83c7ce3 100644 --- a/src/shared/atlas/CharAtlasGenerator.ts +++ b/src/shared/atlas/CharAtlasGenerator.ts @@ -31,11 +31,11 @@ export function generateCharAtlas(context: Window, canvasFactory: (width: number ); const ctx = canvas.getContext('2d', {alpha: config.allowTransparency}); - ctx.fillStyle = config.colors.background; + ctx.fillStyle = config.colors.background.css; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.save(); - ctx.fillStyle = config.colors.foreground; + ctx.fillStyle = config.colors.foreground.css; ctx.font = getFont(config.fontWeight, config); ctx.textBaseline = 'top'; @@ -75,7 +75,7 @@ export function generateCharAtlas(context: Window, canvasFactory: (width: number ctx.beginPath(); ctx.rect(i * cellWidth, y, cellWidth, cellHeight); ctx.clip(); - ctx.fillStyle = config.colors.ansi[colorIndex]; + ctx.fillStyle = config.colors.ansi[colorIndex].css; ctx.fillText(String.fromCharCode(i), i * cellWidth, y); ctx.restore(); } @@ -100,9 +100,9 @@ export function generateCharAtlas(context: Window, canvasFactory: (width: number const charAtlasImageData = ctx.getImageData(0, 0, canvas.width, canvas.height); // Remove the background color from the image so characters may overlap - const r = parseInt(config.colors.background.substr(1, 2), 16); - const g = parseInt(config.colors.background.substr(3, 2), 16); - const b = parseInt(config.colors.background.substr(5, 2), 16); + const r = config.colors.background.rgba >>> 24; + const g = config.colors.background.rgba >>> 16 & 0xFF; + const b = config.colors.background.rgba >>> 8 & 0xFF; clearColor(charAtlasImageData, r, g, b); return context.createImageBitmap(charAtlasImageData); From b66b0aae182d9540ddffb213e83e24ac4dfdd276 Mon Sep 17 00:00:00 2001 From: Benjamin Woodruff Date: Thu, 29 Mar 2018 00:18:31 -0700 Subject: [PATCH 2/2] Block transparent colors without allowTransparency Fixes xterm/xterm.js#1357. --- src/renderer/ColorManager.test.ts | 4 ++-- src/renderer/ColorManager.ts | 40 ++++++++++++++++++++++++++----- src/renderer/Renderer.ts | 5 ++-- 3 files changed, 39 insertions(+), 10 deletions(-) diff --git a/src/renderer/ColorManager.test.ts b/src/renderer/ColorManager.test.ts index 8d8988c79..724bc7582 100644 --- a/src/renderer/ColorManager.test.ts +++ b/src/renderer/ColorManager.test.ts @@ -25,10 +25,10 @@ describe('ColorManager', () => { fillRect(): void { }, getImageData(): any { - return {data: [0, 0, 0, 0]}; + return {data: [0, 0, 0, 0xFF]}; } }); - cm = new ColorManager(document); + cm = new ColorManager(document, false); }); describe('constructor', () => { diff --git a/src/renderer/ColorManager.ts b/src/renderer/ColorManager.ts index 8517a3554..9ab60750a 100644 --- a/src/renderer/ColorManager.ts +++ b/src/renderer/ColorManager.ts @@ -87,7 +87,7 @@ export class ColorManager implements IColorManager { private _ctx: CanvasRenderingContext2D; private _litmusColor: CanvasGradient; - constructor(document: Document) { + constructor(document: Document, public allowTransparency: boolean) { const canvas = document.createElement('canvas'); canvas.width = 1; canvas.height = 1; @@ -112,9 +112,9 @@ export class ColorManager implements IColorManager { public setTheme(theme: ITheme): void { this.colors.foreground = this._parseColor(theme.foreground, DEFAULT_FOREGROUND); this.colors.background = this._parseColor(theme.background, DEFAULT_BACKGROUND); - this.colors.cursor = this._parseColor(theme.cursor, DEFAULT_CURSOR); - this.colors.cursorAccent = this._parseColor(theme.cursorAccent, DEFAULT_CURSOR_ACCENT); - this.colors.selection = this._parseColor(theme.selection, DEFAULT_SELECTION); + this.colors.cursor = this._parseColor(theme.cursor, DEFAULT_CURSOR, true); + this.colors.cursorAccent = this._parseColor(theme.cursorAccent, DEFAULT_CURSOR_ACCENT, true); + this.colors.selection = this._parseColor(theme.selection, DEFAULT_SELECTION, true); this.colors.ansi[0] = this._parseColor(theme.black, DEFAULT_ANSI_COLORS[0]); this.colors.ansi[1] = this._parseColor(theme.red, DEFAULT_ANSI_COLORS[1]); this.colors.ansi[2] = this._parseColor(theme.green, DEFAULT_ANSI_COLORS[2]); @@ -133,7 +133,11 @@ export class ColorManager implements IColorManager { this.colors.ansi[15] = this._parseColor(theme.brightWhite, DEFAULT_ANSI_COLORS[15]); } - private _parseColor(css: string, fallback: IColor): IColor { + private _parseColor( + css: string, + fallback: IColor, + allowTransparency: boolean = this.allowTransparency + ): IColor { if (!css) { return fallback; } @@ -151,9 +155,33 @@ export class ColorManager implements IColorManager { this._ctx.fillRect(0, 0, 1, 1); const data = this._ctx.getImageData(0, 0, 1, 1).data; + if (!allowTransparency && data[3] !== 0xFF) { + // Ideally we'd just ignore the alpha channel, but... + // + // Browsers may not give back exactly the same RGB values we put in, because most/all + // convert the color to a pre-multiplied representation. getImageData converts that back to + // a un-premultipled representation, but the precision loss may make the RGB channels unuable + // on their own. + // + // E.g. In Chrome #12345610 turns into #10305010, and in the extreme case, 0xFFFFFF00 turns + // into 0x00000000. + // + // "Note: Due to the lossy nature of converting to and from premultiplied alpha color values, + // pixels that have just been set using putImageData() might be returned to an equivalent + // getImageData() as different values." + // -- https://html.spec.whatwg.org/multipage/canvas.html#pixel-manipulation + // + // So let's just use the fallback color in this case instead. + console.warn( + `Color: ${css} is using transparency, but allowTransparency is false. ` + + `Using fallback ${fallback.css}.` + ); + return fallback; + } + return { css, - rgba: data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3] + rgba: (data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3]) >>> 0 }; } } diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index 1ce2c9b49..9404a461a 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -29,13 +29,14 @@ export class Renderer extends EventEmitter implements IRenderer { constructor(private _terminal: ITerminal, theme: ITheme) { super(); - this.colorManager = new ColorManager(document); + const allowTransparency = this._terminal.options.allowTransparency; + this.colorManager = new ColorManager(document, allowTransparency); if (theme) { this.colorManager.setTheme(theme); } this._renderLayers = [ - new TextRenderLayer(this._terminal.screenElement, 0, this.colorManager.colors, this._terminal.options.allowTransparency), + new TextRenderLayer(this._terminal.screenElement, 0, this.colorManager.colors, allowTransparency), new SelectionRenderLayer(this._terminal.screenElement, 1, this.colorManager.colors), new LinkRenderLayer(this._terminal.screenElement, 2, this.colorManager.colors, this._terminal), new CursorRenderLayer(this._terminal.screenElement, 3, this.colorManager.colors)