From 14367f8e41c87183027390833eaab58f03a006d3 Mon Sep 17 00:00:00 2001 From: Benjamin Woodruff Date: Wed, 21 Mar 2018 21:03:36 -0700 Subject: [PATCH] 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 | 45 +-- src/Viewport.ts | 2 +- src/renderer/BaseRenderLayer.ts | 10 +- src/renderer/ColorManager.test.ts | 509 +++++++++++++------------ src/renderer/ColorManager.ts | 187 +++++---- 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 | 17 +- src/shared/atlas/CharAtlasGenerator.ts | 12 +- 12 files changed, 406 insertions(+), 402 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index b27d93d623..594c69973d 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -2233,38 +2233,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 @@ -2285,17 +2253,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 4d135b5a75..c951d6b53e 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 281b3ee939..fbffd54748 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 2dc604086c..8d8988c795 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 ddb928a591..8517a3554f 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' -]; - -/** - * 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(); - +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 +}; + +// 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; - } - - private _isColorValid(color: string): boolean { - const litmus = 'red'; - const d = this._document.createElement('div'); - d.style.color = litmus; - d.style.color = color; + this._ctx.fillRect(0, 0, 1, 1); + const data = this._ctx.getImageData(0, 0, 1, 1).data; - // 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 c63c2f14a4..2480d8af46 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 29a5036b08..1e9731f1a6 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 7a6a5af865..e2212246bf 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 27ce9f790f..2487a294a3 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 89c735eb8e..24ed5a488f 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 0407c2eff3..0cded8a850 100644 --- a/src/shared/Types.ts +++ b/src/shared/Types.ts @@ -3,11 +3,16 @@ * @license MIT */ +export interface IColor { + css: string; + rgba: number; // 32-bit int with rgba in each byte +} + export interface IColorSet { - foreground: string; - background: string; - cursor: string; - cursorAccent: string; - selection: string; - ansi: string[]; + 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 10112efa7f..fc83c7ce3f 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);