xterm
[VSoRC/.git] / node_modules / xterm / src / browser / ColorManager.ts
1 /**
2  * Copyright (c) 2017 The xterm.js authors. All rights reserved.
3  * @license MIT
4  */
5
6 import { IColorManager, IColor, IColorSet } from 'browser/Types';
7 import { ITheme } from 'common/services/Services';
8
9 const DEFAULT_FOREGROUND = fromHex('#ffffff');
10 const DEFAULT_BACKGROUND = fromHex('#000000');
11 const DEFAULT_CURSOR = fromHex('#ffffff');
12 const DEFAULT_CURSOR_ACCENT = fromHex('#000000');
13 const DEFAULT_SELECTION = {
14   css: 'rgba(255, 255, 255, 0.3)',
15   rgba: 0xFFFFFF77
16 };
17
18 // An IIFE to generate DEFAULT_ANSI_COLORS. Do not mutate DEFAULT_ANSI_COLORS, instead make a copy
19 // and mutate that.
20 export const DEFAULT_ANSI_COLORS = (() => {
21   const colors = [
22     // dark:
23     fromHex('#2e3436'),
24     fromHex('#cc0000'),
25     fromHex('#4e9a06'),
26     fromHex('#c4a000'),
27     fromHex('#3465a4'),
28     fromHex('#75507b'),
29     fromHex('#06989a'),
30     fromHex('#d3d7cf'),
31     // bright:
32     fromHex('#555753'),
33     fromHex('#ef2929'),
34     fromHex('#8ae234'),
35     fromHex('#fce94f'),
36     fromHex('#729fcf'),
37     fromHex('#ad7fa8'),
38     fromHex('#34e2e2'),
39     fromHex('#eeeeec')
40   ];
41
42   // Fill in the remaining 240 ANSI colors.
43   // Generate colors (16-231)
44   const v = [0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff];
45   for (let i = 0; i < 216; i++) {
46     const r = v[(i / 36) % 6 | 0];
47     const g = v[(i / 6) % 6 | 0];
48     const b = v[i % 6];
49     colors.push({
50       css: `#${toPaddedHex(r)}${toPaddedHex(g)}${toPaddedHex(b)}`,
51       // Use >>> 0 to force a conversion to an unsigned int
52       rgba: ((r << 24) | (g << 16) | (b << 8) | 0xFF) >>> 0
53     });
54   }
55
56   // Generate greys (232-255)
57   for (let i = 0; i < 24; i++) {
58     const c = 8 + i * 10;
59     const ch = toPaddedHex(c);
60     colors.push({
61       css: `#${ch}${ch}${ch}`,
62       rgba: ((c << 24) | (c << 16) | (c << 8) | 0xFF) >>> 0
63     });
64   }
65
66   return colors;
67 })();
68
69 function fromHex(css: string): IColor {
70   return {
71     css,
72     rgba: parseInt(css.slice(1), 16) << 8 | 0xFF
73   };
74 }
75
76 function toPaddedHex(c: number): string {
77   const s = c.toString(16);
78   return s.length < 2 ? '0' + s : s;
79 }
80
81 /**
82  * Manages the source of truth for a terminal's colors.
83  */
84 export class ColorManager implements IColorManager {
85   public colors: IColorSet;
86   private _ctx: CanvasRenderingContext2D;
87   private _litmusColor: CanvasGradient;
88
89   constructor(document: Document, public allowTransparency: boolean) {
90     const canvas = document.createElement('canvas');
91     canvas.width = 1;
92     canvas.height = 1;
93     const ctx = canvas.getContext('2d');
94     if (!ctx) {
95       throw new Error('Could not get rendering context');
96     }
97     this._ctx = ctx;
98     this._ctx.globalCompositeOperation = 'copy';
99     this._litmusColor = this._ctx.createLinearGradient(0, 0, 1, 1);
100     this.colors = {
101       foreground: DEFAULT_FOREGROUND,
102       background: DEFAULT_BACKGROUND,
103       cursor: DEFAULT_CURSOR,
104       cursorAccent: DEFAULT_CURSOR_ACCENT,
105       selection: DEFAULT_SELECTION,
106       ansi: DEFAULT_ANSI_COLORS.slice()
107     };
108   }
109
110   /**
111    * Sets the terminal's theme.
112    * @param theme The  theme to use. If a partial theme is provided then default
113    * colors will be used where colors are not defined.
114    */
115   public setTheme(theme: ITheme = {}): void {
116     this.colors.foreground = this._parseColor(theme.foreground, DEFAULT_FOREGROUND);
117     this.colors.background = this._parseColor(theme.background, DEFAULT_BACKGROUND);
118     this.colors.cursor = this._parseColor(theme.cursor, DEFAULT_CURSOR, true);
119     this.colors.cursorAccent = this._parseColor(theme.cursorAccent, DEFAULT_CURSOR_ACCENT, true);
120     this.colors.selection = this._parseColor(theme.selection, DEFAULT_SELECTION, true);
121     this.colors.ansi[0] = this._parseColor(theme.black, DEFAULT_ANSI_COLORS[0]);
122     this.colors.ansi[1] = this._parseColor(theme.red, DEFAULT_ANSI_COLORS[1]);
123     this.colors.ansi[2] = this._parseColor(theme.green, DEFAULT_ANSI_COLORS[2]);
124     this.colors.ansi[3] = this._parseColor(theme.yellow, DEFAULT_ANSI_COLORS[3]);
125     this.colors.ansi[4] = this._parseColor(theme.blue, DEFAULT_ANSI_COLORS[4]);
126     this.colors.ansi[5] = this._parseColor(theme.magenta, DEFAULT_ANSI_COLORS[5]);
127     this.colors.ansi[6] = this._parseColor(theme.cyan, DEFAULT_ANSI_COLORS[6]);
128     this.colors.ansi[7] = this._parseColor(theme.white, DEFAULT_ANSI_COLORS[7]);
129     this.colors.ansi[8] = this._parseColor(theme.brightBlack, DEFAULT_ANSI_COLORS[8]);
130     this.colors.ansi[9] = this._parseColor(theme.brightRed, DEFAULT_ANSI_COLORS[9]);
131     this.colors.ansi[10] = this._parseColor(theme.brightGreen, DEFAULT_ANSI_COLORS[10]);
132     this.colors.ansi[11] = this._parseColor(theme.brightYellow, DEFAULT_ANSI_COLORS[11]);
133     this.colors.ansi[12] = this._parseColor(theme.brightBlue, DEFAULT_ANSI_COLORS[12]);
134     this.colors.ansi[13] = this._parseColor(theme.brightMagenta, DEFAULT_ANSI_COLORS[13]);
135     this.colors.ansi[14] = this._parseColor(theme.brightCyan, DEFAULT_ANSI_COLORS[14]);
136     this.colors.ansi[15] = this._parseColor(theme.brightWhite, DEFAULT_ANSI_COLORS[15]);
137   }
138
139   private _parseColor(
140     css: string | undefined,
141     fallback: IColor,
142     allowTransparency: boolean = this.allowTransparency
143   ): IColor {
144     if (css === undefined) {
145       return fallback;
146     }
147
148     // If parsing the value results in failure, then it must be ignored, and the attribute must
149     // retain its previous value.
150     // -- https://html.spec.whatwg.org/multipage/canvas.html#fill-and-stroke-styles
151     this._ctx.fillStyle = this._litmusColor;
152     this._ctx.fillStyle = css;
153     if (typeof this._ctx.fillStyle !== 'string') {
154       console.warn(`Color: ${css} is invalid using fallback ${fallback.css}`);
155       return fallback;
156     }
157
158     this._ctx.fillRect(0, 0, 1, 1);
159     const data = this._ctx.getImageData(0, 0, 1, 1).data;
160
161     if (!allowTransparency && data[3] !== 0xFF) {
162       // Ideally we'd just ignore the alpha channel, but...
163       //
164       // Browsers may not give back exactly the same RGB values we put in, because most/all
165       // convert the color to a pre-multiplied representation. getImageData converts that back to
166       // a un-premultipled representation, but the precision loss may make the RGB channels unuable
167       // on their own.
168       //
169       // E.g. In Chrome #12345610 turns into #10305010, and in the extreme case, 0xFFFFFF00 turns
170       // into 0x00000000.
171       //
172       // "Note: Due to the lossy nature of converting to and from premultiplied alpha color values,
173       // pixels that have just been set using putImageData() might be returned to an equivalent
174       // getImageData() as different values."
175       // -- https://html.spec.whatwg.org/multipage/canvas.html#pixel-manipulation
176       //
177       // So let's just use the fallback color in this case instead.
178       console.warn(
179         `Color: ${css} is using transparency, but allowTransparency is false. ` +
180         `Using fallback ${fallback.css}.`
181       );
182       return fallback;
183     }
184
185     return {
186       css,
187       rgba: (data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3]) >>> 0
188     };
189   }
190 }