diff --git a/demo/client.ts b/demo/client.ts index 2590d4b0..09359905 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -559,7 +559,7 @@ function addDecoration() { const decoration = term.registerDecoration({ marker, backgroundColor: '#00FF00', - foregroundColor: '#000000', + foregroundColor: '#00FE00', overviewRulerOptions: { color: '#ef292980', position: 'left' } }); decoration.onRender((e: HTMLElement) => { diff --git a/src/browser/renderer/BaseRenderLayer.ts b/src/browser/renderer/BaseRenderLayer.ts index 67edcb02..b815c672 100644 --- a/src/browser/renderer/BaseRenderLayer.ts +++ b/src/browser/renderer/BaseRenderLayer.ts @@ -13,7 +13,7 @@ import { acquireCharAtlas } from 'browser/renderer/atlas/CharAtlasCache'; import { AttributeData } from 'common/buffer/AttributeData'; import { IColorSet } from 'browser/Types'; import { CellData } from 'common/buffer/CellData'; -import { IBufferService, IOptionsService } from 'common/services/Services'; +import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services'; import { isPowerlineGlyph, throwIfFalsy } from 'browser/renderer/RendererUtils'; import { channels, color, rgba } from 'common/Color'; import { removeElementFromParent } from 'browser/Dom'; @@ -52,7 +52,8 @@ export abstract class BaseRenderLayer implements IRenderLayer { protected _colors: IColorSet, private _rendererId: number, protected readonly _bufferService: IBufferService, - protected readonly _optionsService: IOptionsService + protected readonly _optionsService: IOptionsService, + protected readonly _decorationService: IDecorationService ) { this._canvas = document.createElement('canvas'); this._canvas.classList.add(`xterm-${id}-layer`); @@ -294,7 +295,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { * @param bold Whether the text is bold. */ protected _drawChars(cell: ICellData, x: number, y: number): void { - const contrastColor = this._getContrastColor(cell); + const contrastColor = this._getContrastColor(cell, x, y); // skip cache right away if we draw in RGB // Note: to avoid bad runtime JoinedCellData will be skipped @@ -427,15 +428,35 @@ export abstract class BaseRenderLayer implements IRenderLayer { return `${fontStyle} ${fontWeight} ${this._optionsService.rawOptions.fontSize * window.devicePixelRatio}px ${this._optionsService.rawOptions.fontFamily}`; } - private _getContrastColor(cell: CellData): IColor | undefined { - if (this._optionsService.rawOptions.minimumContrastRatio === 1 || isPowerlineGlyph(cell.getCode())) { + private _getContrastColor(cell: CellData, x: number, y: number): IColor | undefined { + // Get any decoration foreground/background overrides, this must be fetched before the early + // exist but applied after inverse + const decorations = this._decorationService.getDecorationsOnLine(y); + let bgOverride: number | undefined; + let fgOverride: number | undefined; + for (const d of decorations) { + const xmin = d.options.x ?? 0; + const xmax = xmin + (d.options.width ?? 1); + if (x >= xmin && x < xmax) { + if (d.backgroundColorRGB) { + bgOverride = d.backgroundColorRGB.rgba; + } + if (d.foregroundColorRGB) { + fgOverride = d.foregroundColorRGB.rgba; + } + } + } + + if (!bgOverride && !fgOverride && (this._optionsService.rawOptions.minimumContrastRatio === 1 || isPowerlineGlyph(cell.getCode()))) { return undefined; } - // Try get from cache first - const adjustedColor = this._colors.contrastCache.getColor(cell.bg, cell.fg); - if (adjustedColor !== undefined) { - return adjustedColor || undefined; + if (!bgOverride && !fgOverride) { + // Try get from cache + const adjustedColor = this._colors.contrastCache.getColor(cell.bg, cell.fg); + if (adjustedColor !== undefined) { + return adjustedColor || undefined; + } } let fgColor = cell.getFgColor(); @@ -453,13 +474,18 @@ export abstract class BaseRenderLayer implements IRenderLayer { bgColorMode = temp2; } - const bgRgba = this._resolveBackgroundRgba(bgColorMode, bgColor, isInverse); + const bgRgba = this._resolveBackgroundRgba(bgOverride !== undefined ? Attributes.CM_RGB : bgColorMode, bgOverride ?? bgColor, isInverse); const fgRgba = this._resolveForegroundRgba(fgColorMode, fgColor, isInverse, isBold); - const result = rgba.ensureContrastRatio(bgRgba, fgRgba, this._optionsService.rawOptions.minimumContrastRatio); + let result = rgba.ensureContrastRatio(bgOverride ?? bgRgba, fgOverride ?? fgRgba, this._optionsService.rawOptions.minimumContrastRatio); if (!result) { - this._colors.contrastCache.setColor(cell.bg, cell.fg, null); - return undefined; + if (!bgOverride && !fgOverride) { + this._colors.contrastCache.setColor(cell.bg, cell.fg, null); + return undefined; + } + // If it was an override and there was no contrast change, set as the result + // TODO: This is white when it should be green + result = fgRgba; } const color: IColor = { @@ -470,7 +496,9 @@ export abstract class BaseRenderLayer implements IRenderLayer { ), rgba: result }; - this._colors.contrastCache.setColor(cell.bg, cell.fg, color); + if (!bgOverride && !fgOverride) { + this._colors.contrastCache.setColor(cell.bg, cell.fg, color); + } return color; } diff --git a/src/browser/renderer/CursorRenderLayer.ts b/src/browser/renderer/CursorRenderLayer.ts index ea419cb2..3fa576a9 100644 --- a/src/browser/renderer/CursorRenderLayer.ts +++ b/src/browser/renderer/CursorRenderLayer.ts @@ -8,7 +8,7 @@ import { BaseRenderLayer } from 'browser/renderer/BaseRenderLayer'; import { ICellData } from 'common/Types'; import { CellData } from 'common/buffer/CellData'; import { IColorSet } from 'browser/Types'; -import { IBufferService, IOptionsService, ICoreService } from 'common/services/Services'; +import { IBufferService, IOptionsService, ICoreService, IDecorationService } from 'common/services/Services'; import { IEventEmitter } from 'common/EventEmitter'; import { ICoreBrowserService } from 'browser/services/Services'; @@ -40,9 +40,10 @@ export class CursorRenderLayer extends BaseRenderLayer { @IBufferService bufferService: IBufferService, @IOptionsService optionsService: IOptionsService, @ICoreService private readonly _coreService: ICoreService, - @ICoreBrowserService private readonly _coreBrowserService: ICoreBrowserService + @ICoreBrowserService private readonly _coreBrowserService: ICoreBrowserService, + @IDecorationService decorationService: IDecorationService ) { - super(container, 'cursor', zIndex, true, colors, rendererId, bufferService, optionsService); + super(container, 'cursor', zIndex, true, colors, rendererId, bufferService, optionsService, decorationService); this._state = { x: 0, y: 0, diff --git a/src/browser/renderer/LinkRenderLayer.ts b/src/browser/renderer/LinkRenderLayer.ts index 2492f921..15086d9a 100644 --- a/src/browser/renderer/LinkRenderLayer.ts +++ b/src/browser/renderer/LinkRenderLayer.ts @@ -8,7 +8,7 @@ import { BaseRenderLayer } from './BaseRenderLayer'; import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants'; import { is256Color } from 'browser/renderer/atlas/CharAtlasUtils'; import { IColorSet, ILinkifierEvent, ILinkifier, ILinkifier2 } from 'browser/Types'; -import { IBufferService, IOptionsService } from 'common/services/Services'; +import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services'; export class LinkRenderLayer extends BaseRenderLayer { private _state: ILinkifierEvent | undefined; @@ -21,9 +21,10 @@ export class LinkRenderLayer extends BaseRenderLayer { linkifier: ILinkifier, linkifier2: ILinkifier2, @IBufferService bufferService: IBufferService, - @IOptionsService optionsService: IOptionsService + @IOptionsService optionsService: IOptionsService, + @IDecorationService decorationService: IDecorationService ) { - super(container, 'link', zIndex, true, colors, rendererId, bufferService, optionsService); + super(container, 'link', zIndex, true, colors, rendererId, bufferService, optionsService, decorationService); linkifier.onShowLinkUnderline(e => this._onShowLinkUnderline(e)); linkifier.onHideLinkUnderline(e => this._onHideLinkUnderline(e)); diff --git a/src/browser/renderer/SelectionRenderLayer.ts b/src/browser/renderer/SelectionRenderLayer.ts index 9054e3ca..be911eb9 100644 --- a/src/browser/renderer/SelectionRenderLayer.ts +++ b/src/browser/renderer/SelectionRenderLayer.ts @@ -6,7 +6,7 @@ import { IRenderDimensions } from 'browser/renderer/Types'; import { BaseRenderLayer } from 'browser/renderer/BaseRenderLayer'; import { IColorSet } from 'browser/Types'; -import { IBufferService, IOptionsService } from 'common/services/Services'; +import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services'; interface ISelectionState { start?: [number, number]; @@ -24,9 +24,10 @@ export class SelectionRenderLayer extends BaseRenderLayer { colors: IColorSet, rendererId: number, @IBufferService bufferService: IBufferService, - @IOptionsService optionsService: IOptionsService + @IOptionsService optionsService: IOptionsService, + @IDecorationService decorationService: IDecorationService ) { - super(container, 'selection', zIndex, true, colors, rendererId, bufferService, optionsService); + super(container, 'selection', zIndex, true, colors, rendererId, bufferService, optionsService, decorationService); this._clearState(); } diff --git a/src/browser/renderer/TextRenderLayer.ts b/src/browser/renderer/TextRenderLayer.ts index 33d942ff..e0f6d831 100644 --- a/src/browser/renderer/TextRenderLayer.ts +++ b/src/browser/renderer/TextRenderLayer.ts @@ -11,7 +11,7 @@ import { AttributeData } from 'common/buffer/AttributeData'; import { NULL_CELL_CODE, Content } from 'common/buffer/Constants'; import { IColorSet } from 'browser/Types'; import { CellData } from 'common/buffer/CellData'; -import { IOptionsService, IBufferService } from 'common/services/Services'; +import { IOptionsService, IBufferService, IDecorationService } from 'common/services/Services'; import { ICharacterJoinerService } from 'browser/services/Services'; import { JoinedCellData } from 'browser/services/CharacterJoinerService'; @@ -37,9 +37,10 @@ export class TextRenderLayer extends BaseRenderLayer { rendererId: number, @IBufferService bufferService: IBufferService, @IOptionsService optionsService: IOptionsService, - @ICharacterJoinerService private readonly _characterJoinerService: ICharacterJoinerService + @ICharacterJoinerService private readonly _characterJoinerService: ICharacterJoinerService, + @IDecorationService decorationService: IDecorationService ) { - super(container, 'text', zIndex, alpha, colors, rendererId, bufferService, optionsService); + super(container, 'text', zIndex, alpha, colors, rendererId, bufferService, optionsService, decorationService); this._state = new GridCache(); } @@ -176,6 +177,19 @@ export class TextRenderLayer extends BaseRenderLayer { nextFillStyle = this._colors.ansi[cell.getBgColor()].css; } + // Get any decoration foreground/background overrides, this must be fetched before the early + // exist but applied after inverse + const decorations = this._decorationService.getDecorationsOnLine(y); + for (const d of decorations) { + const xmin = d.options.x ?? 0; + const xmax = xmin + (d.options.width ?? 1); + if (x >= xmin && x < xmax) { + if (d.backgroundColorRGB) { + nextFillStyle = d.backgroundColorRGB.css; + } + } + } + if (prevFillStyle === null) { // This is either the first iteration, or the default background was set. Either way, we // don't need to draw anything. diff --git a/src/browser/renderer/atlas/DynamicCharAtlas.ts b/src/browser/renderer/atlas/DynamicCharAtlas.ts index 88194615..678f8b70 100644 --- a/src/browser/renderer/atlas/DynamicCharAtlas.ts +++ b/src/browser/renderer/atlas/DynamicCharAtlas.ts @@ -105,7 +105,7 @@ export class DynamicCharAtlas extends BaseCharAtlas { this._cacheMap.prealloc(capacity); // This is useful for debugging - // document.body.appendChild(this._cacheCanvas); + document.body.appendChild(this._cacheCanvas); } public dispose(): void { diff --git a/src/browser/renderer/dom/DomRendererRowFactory.ts b/src/browser/renderer/dom/DomRendererRowFactory.ts index 7a20db5a..3a8bf87c 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.ts @@ -173,7 +173,8 @@ export class DomRendererRowFactory { bgColorMode = temp2; } - // Apply any decoration foreground/background overrides + // Apply any decoration foreground/background overrides, this must happen after inverse has + // been applied const decorations = this._decorationService.getDecorationsOnLine(row); let bgOverride: IColor | undefined; let fgOverride: IColor | undefined;