diff --git a/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts b/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts index e8e19095..ad25bf87 100644 --- a/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts +++ b/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts @@ -24,7 +24,8 @@ export function generateConfig(scaledCharWidth: number, scaledCharHeight: number selectionOpaque: NULL_COLOR, // For the static char atlas, we only use the first 16 colors, but we need all 256 for the // dynamic character atlas. - ansi: colors.ansi.slice() + ansi: colors.ansi.slice(), + contrastCache: {} as any }; return { devicePixelRatio: window.devicePixelRatio, diff --git a/src/Terminal.ts b/src/Terminal.ts index b08270fd..927ff47b 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -546,6 +546,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this._theme = this.options.theme || this._theme; this.options.theme = undefined; this._colorManager = new ColorManager(document, this.options.allowTransparency); + this.optionsService.onOptionChange(e => this._colorManager.onOptionsChange(e)); this._colorManager.setTheme(this._theme); const renderer = this._createRenderer(); diff --git a/src/browser/ColorContrastCache.ts b/src/browser/ColorContrastCache.ts new file mode 100644 index 00000000..3198ad83 --- /dev/null +++ b/src/browser/ColorContrastCache.ts @@ -0,0 +1,38 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { IColor, IColorContrastCache } from 'browser/Types'; + +export class ColorContrastCache implements IColorContrastCache { + private _color: { [bg: number]: { [fg: number]: IColor | null | undefined } | undefined } = {}; + private _rgba: { [bg: number]: { [fg: number]: number | null | undefined } | undefined } = {}; + + public clear(): void { + this._color = {}; + this._rgba = {}; + } + + public setRgba(bg: number, fg: number, value: number | null): void { + if (!this._rgba[bg]) { + this._rgba[bg] = {}; + } + this._rgba[bg]![fg] = value; + } + + public getRgba(bg: number, fg: number): number | null | undefined { + return this._rgba[bg] ? this._rgba[bg]![fg] : undefined; + } + + public setColor(bg: number, fg: number, value: IColor | null): void { + if (!this._color[bg]) { + this._color[bg] = {}; + } + this._color[bg]![fg] = value; + } + + public getColor(bg: number, fg: number): IColor | null | undefined { + return this._color[bg] ? this._color[bg]![fg] : undefined; + } +} diff --git a/src/browser/ColorManager.ts b/src/browser/ColorManager.ts index c6354f71..92994cfc 100644 --- a/src/browser/ColorManager.ts +++ b/src/browser/ColorManager.ts @@ -3,9 +3,10 @@ * @license MIT */ -import { IColorManager, IColor, IColorSet } from 'browser/Types'; +import { IColorManager, IColor, IColorSet, IColorContrastCache } from 'browser/Types'; import { ITheme } from 'common/services/Services'; import { fromCss, toCss, blend, toRgba } from 'browser/Color'; +import { ColorContrastCache } from 'browser/ColorContrastCache'; const DEFAULT_FOREGROUND = fromCss('#ffffff'); const DEFAULT_BACKGROUND = fromCss('#000000'); @@ -72,6 +73,7 @@ export class ColorManager implements IColorManager { public colors: IColorSet; private _ctx: CanvasRenderingContext2D; private _litmusColor: CanvasGradient; + private _contrastCache: IColorContrastCache; constructor(document: Document, public allowTransparency: boolean) { const canvas = document.createElement('canvas'); @@ -84,6 +86,7 @@ export class ColorManager implements IColorManager { this._ctx = ctx; this._ctx.globalCompositeOperation = 'copy'; this._litmusColor = this._ctx.createLinearGradient(0, 0, 1, 1); + this._contrastCache = new ColorContrastCache(); this.colors = { foreground: DEFAULT_FOREGROUND, background: DEFAULT_BACKGROUND, @@ -91,10 +94,17 @@ export class ColorManager implements IColorManager { cursorAccent: DEFAULT_CURSOR_ACCENT, selection: DEFAULT_SELECTION, selectionOpaque: blend(DEFAULT_BACKGROUND, DEFAULT_SELECTION), - ansi: DEFAULT_ANSI_COLORS.slice() + ansi: DEFAULT_ANSI_COLORS.slice(), + contrastCache: this._contrastCache }; } + public onOptionsChange(key: string): void { + if (key === 'minimumContrastRatio') { + this._contrastCache.clear(); + } + } + /** * Sets the terminal's theme. * @param theme The theme to use. If a partial theme is provided then default @@ -123,6 +133,8 @@ export class ColorManager implements IColorManager { 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]); + // Clear our the cache + this._contrastCache.clear(); } private _parseColor( diff --git a/src/browser/Types.d.ts b/src/browser/Types.d.ts index ca852b6a..06c3fe38 100644 --- a/src/browser/Types.d.ts +++ b/src/browser/Types.d.ts @@ -8,6 +8,7 @@ import { IDisposable } from 'common/Types'; export interface IColorManager { colors: IColorSet; + onOptionsChange(key: string): void; } export interface IColor { @@ -24,6 +25,15 @@ export interface IColorSet { /** The selection blended on top of background. */ selectionOpaque: IColor; ansi: IColor[]; + contrastCache: IColorContrastCache +} + +export interface IColorContrastCache { + clear(): void; + setRgba(bg: number, fg: number, value: number | null): void; + getRgba(bg: number, fg: number): number | null | undefined; + setColor(bg: number, fg: number, value: IColor | null): void; + getColor(bg: number, fg: number): IColor | null | undefined; } export interface IPartialColorSet { diff --git a/src/browser/renderer/dom/DomRendererRowFactory.ts b/src/browser/renderer/dom/DomRendererRowFactory.ts index b5caccaf..bb96fb72 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.ts @@ -167,9 +167,17 @@ export class DomRendererRowFactory { return false; } - const adustedColor = ensureContrastRatio(bg, fg, this._optionsService.options.minimumContrastRatio); - if (adustedColor) { - element.setAttribute('style', `${element.getAttribute('style') || ''}color:${adustedColor.css}`); + // Try get from cache first + let adjustedColor = this._colors.contrastCache.getColor(this._workCell.bg, this._workCell.fg); + + // Calculate and store in cache + if (adjustedColor === undefined) { + adjustedColor = ensureContrastRatio(bg, fg, this._optionsService.options.minimumContrastRatio); + this._colors.contrastCache.setColor(this._workCell.bg, this._workCell.fg, adjustedColor ?? null); + } + + if (adjustedColor) { + element.setAttribute('style', `${element.getAttribute('style') || ''}color:${adjustedColor.css}`); return true; }