From 32da1d81d5d5a4a877d0222216a190deb2ecb73d Mon Sep 17 00:00:00 2001 From: tisilent Date: Mon, 21 Aug 2023 19:09:02 +0800 Subject: [PATCH 01/19] Use variants for underline dotted. webgl --- .../xterm-addon-canvas/src/BaseRenderLayer.ts | 4 +- addons/xterm-addon-webgl/src/GlyphRenderer.ts | 10 ++-- addons/xterm-addon-webgl/src/WebglAddon.ts | 4 +- addons/xterm-addon-webgl/src/WebglRenderer.ts | 46 +++++++++++++-- src/browser/renderer/shared/TextureAtlas.ts | 59 +++++++++++++------ src/browser/renderer/shared/Types.d.ts | 6 +- src/common/MultiKeyMap.ts | 19 ++++++ src/common/buffer/Constants.ts | 9 ++- 8 files changed, 123 insertions(+), 34 deletions(-) diff --git a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts index ff29bbf1..30f6263c 100644 --- a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts @@ -375,9 +375,9 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer let glyph: IRasterizedGlyph; if (chars && chars.length > 1) { - glyph = this._charAtlas.getRasterizedGlyphCombinedChar(chars, this._cellColorResolver.result.bg, this._cellColorResolver.result.fg, this._cellColorResolver.result.ext, true); + glyph = this._charAtlas.getRasterizedGlyphCombinedChar(chars, this._cellColorResolver.result.bg, this._cellColorResolver.result.fg, this._cellColorResolver.result.ext, 0, true); } else { - glyph = this._charAtlas.getRasterizedGlyph(cell.getCode() || WHITESPACE_CELL_CODE, this._cellColorResolver.result.bg, this._cellColorResolver.result.fg, this._cellColorResolver.result.ext, true); + glyph = this._charAtlas.getRasterizedGlyph(cell.getCode() || WHITESPACE_CELL_CODE, this._cellColorResolver.result.bg, this._cellColorResolver.result.fg, this._cellColorResolver.result.ext, 0, true); } if (!glyph.size.x || !glyph.size.y) { return; diff --git a/addons/xterm-addon-webgl/src/GlyphRenderer.ts b/addons/xterm-addon-webgl/src/GlyphRenderer.ts index 269d9374..09523a9e 100644 --- a/addons/xterm-addon-webgl/src/GlyphRenderer.ts +++ b/addons/xterm-addon-webgl/src/GlyphRenderer.ts @@ -215,15 +215,15 @@ export class GlyphRenderer extends Disposable { } @traceCall - public updateCell(x: number, y: number, code: number, bg: number, fg: number, ext: number, chars: string, lastBg: number): void { + public updateCell(x: number, y: number, code: number, bg: number, fg: number, ext: number, chars: string, lastBg: number, variantOffset: number): void { // Since this function is called for every cell (`rows*cols`), it must be very optimized. It // should not instantiate any variables unless a new glyph is drawn to the cache where the // slight slowdown is acceptable for the developer ergonomics provided as it's a once of for // each glyph. - this._updateCell(this._vertices.attributes, x, y, code, bg, fg, ext, chars, lastBg); + this._updateCell(this._vertices.attributes, x, y, code, bg, fg, ext, chars, lastBg, variantOffset); } - private _updateCell(array: Float32Array, x: number, y: number, code: number | undefined, bg: number, fg: number, ext: number, chars: string, lastBg: number): void { + private _updateCell(array: Float32Array, x: number, y: number, code: number | undefined, bg: number, fg: number, ext: number, chars: string, lastBg: number, variantOffset: number): void { $i = (y * this._terminal.cols + x) * INDICES_PER_CELL; // Exit early if this is a null character, allow space character to continue as it may have @@ -239,9 +239,9 @@ export class GlyphRenderer extends Disposable { // Get the glyph if (chars && chars.length > 1) { - $glyph = this._atlas.getRasterizedGlyphCombinedChar(chars, bg, fg, ext, false); + $glyph = this._atlas.getRasterizedGlyphCombinedChar(chars, bg, fg, ext, variantOffset, false); } else { - $glyph = this._atlas.getRasterizedGlyph(code, bg, fg, ext, false); + $glyph = this._atlas.getRasterizedGlyph(code, bg, fg, ext, variantOffset, false); } $leftCellPadding = Math.floor((this._dimensions.device.cell.width - this._dimensions.device.char.width) / 2); diff --git a/addons/xterm-addon-webgl/src/WebglAddon.ts b/addons/xterm-addon-webgl/src/WebglAddon.ts index 3e091f0b..9e961cc6 100644 --- a/addons/xterm-addon-webgl/src/WebglAddon.ts +++ b/addons/xterm-addon-webgl/src/WebglAddon.ts @@ -8,7 +8,7 @@ import { ITerminal } from 'browser/Types'; import { EventEmitter, forwardEvent } from 'common/EventEmitter'; import { Disposable, toDisposable } from 'common/Lifecycle'; import { getSafariVersion, isSafari } from 'common/Platform'; -import { ICoreService, IDecorationService, ILogService, IOptionsService } from 'common/services/Services'; +import { ICoreService, IDecorationService, ILogService, IOptionsService, IUnicodeService } from 'common/services/Services'; import { ITerminalAddon, Terminal } from 'xterm'; import { WebglRenderer } from './WebglRenderer'; import { setTraceLogger } from 'common/services/LogService'; @@ -54,6 +54,7 @@ export class WebglAddon extends Disposable implements ITerminalAddon { const decorationService: IDecorationService = unsafeCore._decorationService; const logService: ILogService = unsafeCore._logService; const themeService: IThemeService = unsafeCore._themeService; + const unicodeService: IUnicodeService = unsafeCore.unicodeService; // Set trace logger just in case it hasn't been yet which could happen when the addon is // bundled separately to the core module @@ -68,6 +69,7 @@ export class WebglAddon extends Disposable implements ITerminalAddon { decorationService, optionsService, themeService, + unicodeService, this._preserveDrawingBuffer )); this.register(forwardEvent(this._renderer.onContextLoss, this._onContextLoss)); diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index ae770410..2091347c 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -14,10 +14,10 @@ import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService, IThemeS import { ITerminal } from 'browser/Types'; import { AttributeData } from 'common/buffer/AttributeData'; import { CellData } from 'common/buffer/CellData'; -import { Attributes, Content, NULL_CELL_CHAR, NULL_CELL_CODE } from 'common/buffer/Constants'; +import { Attributes, Content, ExtFlags, NULL_CELL_CHAR, NULL_CELL_CODE, UnderlineStyle } from 'common/buffer/Constants'; import { EventEmitter, forwardEvent } from 'common/EventEmitter'; import { Disposable, MutableDisposable, getDisposeArrayDisposable, toDisposable } from 'common/Lifecycle'; -import { ICoreService, IDecorationService, ILogService, IOptionsService } from 'common/services/Services'; +import { ICoreService, IDecorationService, ILogService, IOptionsService, IUnicodeService } from 'common/services/Services'; import { CharData, IBufferLine, ICellData } from 'common/Types'; import { IDisposable, Terminal } from 'xterm'; import { GlyphRenderer } from './GlyphRenderer'; @@ -71,6 +71,7 @@ export class WebglRenderer extends Disposable implements IRenderer { private readonly _decorationService: IDecorationService, private readonly _optionsService: IOptionsService, private readonly _themeService: IThemeService, + private readonly _unicodeService: IUnicodeService, preserveDrawingBuffer?: boolean ) { super(); @@ -395,6 +396,7 @@ export class WebglRenderer extends Disposable implements IRenderer { let i: number; let x: number; let j: number; + let variantOffset: number = -1; start = clamp(start, terminal.rows - 1, 0); end = clamp(end, terminal.rows - 1, 0); @@ -409,11 +411,18 @@ export class WebglRenderer extends Disposable implements IRenderer { this._model.cursor = undefined; let modelUpdated = false; + const fontSize = this._optionsService.rawOptions.fontSize; + const drp = this._coreBrowserService.dpr; + const lineWidth = Math.max(1, Math.floor(fontSize * drp / 15)); + const deviceCellWidth = this._charAtlas?.getDeviceCellWidth(); + for (y = start; y <= end; y++) { row = y + terminal.buffer.ydisp; line = terminal.buffer.lines.get(row)!; this._model.lineLengths[y] = 0; joinedRanges = this._characterJoinerService.getJoinedCharacters(row); + // row start variant init + variantOffset = 0; for (x = 0; x < terminal.cols; x++) { lastBg = this._cellColorResolver.result.bg; line.loadCell(x, cell); @@ -447,6 +456,14 @@ export class WebglRenderer extends Disposable implements IRenderer { chars = cell.getChars(); code = cell.getCode(); + + let chWidth: number; + if (typeof code === 'number') { + chWidth = this._unicodeService.wcwidth(code); + } else { + chWidth = this._unicodeService.getStringCellWidth(code); + } + i = ((y * terminal.cols) + x) * RENDER_MODEL_INDICIES_PER_CELL; // Load colors/resolve overrides into work colors @@ -480,13 +497,27 @@ export class WebglRenderer extends Disposable implements IRenderer { if (code !== NULL_CELL_CODE) { this._model.lineLengths[y] = x + 1; + } else { + if (cell.extended.underlineStyle !== UnderlineStyle.DOTTED) { + variantOffset = 0; + } } + this._cellColorResolver.result.ext &= ~ExtFlags.VARIANT_OFFSET; + this._cellColorResolver.result.ext |= (variantOffset << 29) & ExtFlags.VARIANT_OFFSET; + // Nothing has changed, no updates needed if (this._model.cells[i] === code && this._model.cells[i + RENDER_MODEL_BG_OFFSET] === this._cellColorResolver.result.bg && this._model.cells[i + RENDER_MODEL_FG_OFFSET] === this._cellColorResolver.result.fg && this._model.cells[i + RENDER_MODEL_EXT_OFFSET] === this._cellColorResolver.result.ext) { + if (cell.extended.underlineStyle === UnderlineStyle.DOTTED) { + if (code !== NULL_CELL_CODE) { + variantOffset = ((deviceCellWidth! * chWidth) - ((lineWidth * 2) - variantOffset)) % (lineWidth * 2); + } + } else { + variantOffset = 0; + } continue; } @@ -503,7 +534,14 @@ export class WebglRenderer extends Disposable implements IRenderer { this._model.cells[i + RENDER_MODEL_FG_OFFSET] = this._cellColorResolver.result.fg; this._model.cells[i + RENDER_MODEL_EXT_OFFSET] = this._cellColorResolver.result.ext; - this._glyphRenderer!.updateCell(x, y, code, this._cellColorResolver.result.bg, this._cellColorResolver.result.fg, this._cellColorResolver.result.ext, chars, lastBg); + this._glyphRenderer!.updateCell(x, y, code, this._cellColorResolver.result.bg, this._cellColorResolver.result.fg, this._cellColorResolver.result.ext, chars, lastBg, variantOffset); + if (cell.extended.underlineStyle === UnderlineStyle.DOTTED) { + if (code !== NULL_CELL_CODE) { + variantOffset = ((deviceCellWidth! * chWidth) - ((lineWidth * 2) - variantOffset)) % (lineWidth * 2); + } + } else { + variantOffset = 0; + } if (isJoined) { // Restore work cell @@ -512,7 +550,7 @@ export class WebglRenderer extends Disposable implements IRenderer { // Null out non-first cells for (x++; x < lastCharX; x++) { j = ((y * terminal.cols) + x) * RENDER_MODEL_INDICIES_PER_CELL; - this._glyphRenderer!.updateCell(x, y, NULL_CELL_CODE, 0, 0, 0, NULL_CELL_CHAR, 0); + this._glyphRenderer!.updateCell(x, y, NULL_CELL_CODE, 0, 0, 0, NULL_CELL_CHAR, 0, 0); this._model.cells[j] = NULL_CELL_CODE; this._model.cells[j + RENDER_MODEL_BG_OFFSET] = this._cellColorResolver.result.bg; this._model.cells[j + RENDER_MODEL_FG_OFFSET] = this._cellColorResolver.result.fg; diff --git a/src/browser/renderer/shared/TextureAtlas.ts b/src/browser/renderer/shared/TextureAtlas.ts index 3ee21519..bc8fd2dc 100644 --- a/src/browser/renderer/shared/TextureAtlas.ts +++ b/src/browser/renderer/shared/TextureAtlas.ts @@ -11,7 +11,7 @@ import { color, NULL_COLOR, rgba } from 'common/Color'; import { tryDrawCustomChar } from 'browser/renderer/shared/CustomGlyphs'; import { excludeFromContrastRatioDemands, isPowerlineGlyph, isRestrictedPowerlineGlyph, throwIfFalsy } from 'browser/renderer/shared/RendererUtils'; import { IUnicodeService } from 'common/services/Services'; -import { FourKeyMap } from 'common/MultiKeyMap'; +import { FiveKeyMap } from 'common/MultiKeyMap'; import { IdleTaskQueue } from 'common/TaskQueue'; import { IBoundingBox, ICharAtlasConfig, IRasterizedGlyph, IRequestRedrawEvent, ITextureAtlas } from 'browser/renderer/shared/Types'; import { EventEmitter } from 'common/EventEmitter'; @@ -58,8 +58,8 @@ let $glyph = undefined; export class TextureAtlas implements ITextureAtlas { private _didWarmUp: boolean = false; - private _cacheMap: FourKeyMap = new FourKeyMap(); - private _cacheMapCombined: FourKeyMap = new FourKeyMap(); + private _cacheMap: FiveKeyMap = new FiveKeyMap(); + private _cacheMapCombined: FiveKeyMap = new FiveKeyMap(); // The texture that the atlas is drawn to private _pages: AtlasPage[] = []; @@ -121,9 +121,9 @@ export class TextureAtlas implements ITextureAtlas { const queue = new IdleTaskQueue(); for (let i = 33; i < 126; i++) { queue.enqueue(() => { - if (!this._cacheMap.get(i, DEFAULT_COLOR, DEFAULT_COLOR, DEFAULT_EXT)) { - const rasterizedGlyph = this._drawToCache(i, DEFAULT_COLOR, DEFAULT_COLOR, DEFAULT_EXT); - this._cacheMap.set(i, DEFAULT_COLOR, DEFAULT_COLOR, DEFAULT_EXT, rasterizedGlyph); + if (!this._cacheMap.get(i, DEFAULT_COLOR, DEFAULT_COLOR, DEFAULT_EXT, 1)) { + const rasterizedGlyph = this._drawToCache(i, DEFAULT_COLOR, DEFAULT_COLOR, DEFAULT_EXT, 0); + this._cacheMap.set(i, DEFAULT_COLOR, DEFAULT_COLOR, DEFAULT_EXT, 1, rasterizedGlyph); } }); } @@ -244,29 +244,30 @@ export class TextureAtlas implements ITextureAtlas { } } - public getRasterizedGlyphCombinedChar(chars: string, bg: number, fg: number, ext: number, restrictToCellHeight: boolean): IRasterizedGlyph { - return this._getFromCacheMap(this._cacheMapCombined, chars, bg, fg, ext, restrictToCellHeight); + public getRasterizedGlyphCombinedChar(chars: string, bg: number, fg: number, ext: number, variantOffset: number, restrictToCellHeight: boolean): IRasterizedGlyph { + return this._getFromCacheMap(this._cacheMapCombined, chars, bg, fg, ext, variantOffset, restrictToCellHeight); } - public getRasterizedGlyph(code: number, bg: number, fg: number, ext: number, restrictToCellHeight: boolean): IRasterizedGlyph { - return this._getFromCacheMap(this._cacheMap, code, bg, fg, ext, restrictToCellHeight); + public getRasterizedGlyph(code: number, bg: number, fg: number, ext: number, variantOffset: number, restrictToCellHeight: boolean): IRasterizedGlyph { + return this._getFromCacheMap(this._cacheMap, code, bg, fg, ext, variantOffset, restrictToCellHeight); } /** * Gets the glyphs texture coords, drawing the texture if it's not already */ private _getFromCacheMap( - cacheMap: FourKeyMap, + cacheMap: FiveKeyMap, key: string | number, bg: number, fg: number, ext: number, + variantOffset: number, restrictToCellHeight: boolean = false ): IRasterizedGlyph { - $glyph = cacheMap.get(key, bg, fg, ext); + $glyph = cacheMap.get(key, bg, fg, ext, variantOffset); if (!$glyph) { - $glyph = this._drawToCache(key, bg, fg, ext, restrictToCellHeight); - cacheMap.set(key, bg, fg, ext, $glyph); + $glyph = this._drawToCache(key, bg, fg, ext, variantOffset, restrictToCellHeight); + cacheMap.set(key, bg, fg, ext, variantOffset, $glyph); } return $glyph; } @@ -427,7 +428,7 @@ export class TextureAtlas implements ITextureAtlas { } @traceCall - private _drawToCache(codeOrChars: number | string, bg: number, fg: number, ext: number, restrictToCellHeight: boolean = false): IRasterizedGlyph { + private _drawToCache(codeOrChars: number | string, bg: number, fg: number, ext: number,variantOffset: number = 0, restrictToCellHeight: boolean = false): IRasterizedGlyph { const chars = typeof codeOrChars === 'number' ? String.fromCharCode(codeOrChars) : codeOrChars; // Uncomment for debugging @@ -547,6 +548,7 @@ export class TextureAtlas implements ITextureAtlas { const yTop = Math.ceil(padding + this._config.deviceCharHeight) - yOffset - (restrictToCellHeight ? lineWidth * 2 : 0); const yMid = yTop + lineWidth; const yBot = yTop + lineWidth * 2; + let nextOffset = variantOffset; for (let i = 0; i < chWidth; i++) { this._tmpCtx.save(); @@ -596,9 +598,26 @@ export class TextureAtlas implements ITextureAtlas { ); break; case UnderlineStyle.DOTTED: - this._tmpCtx.setLineDash([Math.round(lineWidth), Math.round(lineWidth)]); - this._tmpCtx.moveTo(xChLeft, yTop); - this._tmpCtx.lineTo(xChRight, yTop); + const offsetWidth = nextOffset >= lineWidth ? lineWidth * 2 - nextOffset : lineWidth - nextOffset; + if (offsetWidth === 0) { + this._tmpCtx.setLineDash([Math.round(lineWidth), Math.round(lineWidth)]); + this._tmpCtx.moveTo(xChLeft, yTop); + this._tmpCtx.lineTo(xChRight, yTop); + } else { + const isFull = nextOffset >= lineWidth ? false : true; + if (isFull === false) { + this._tmpCtx.setLineDash([Math.round(lineWidth), Math.round(lineWidth)]); + this._tmpCtx.moveTo(xChLeft + offsetWidth, yTop); + this._tmpCtx.lineTo(xChRight, yTop); + } else { + this._tmpCtx.setLineDash([Math.round(lineWidth), Math.round(lineWidth)]); + this._tmpCtx.moveTo(xChLeft, yTop); + this._tmpCtx.lineTo(xChLeft + offsetWidth, yTop); + this._tmpCtx.moveTo(xChLeft + offsetWidth + lineWidth, yTop); + this._tmpCtx.lineTo(xChRight, yTop); + } + } + nextOffset = (xChRight - xChLeft - ((lineWidth * 2) - nextOffset)) % (Math.round(lineWidth) * 2); break; case UnderlineStyle.DASHED: this._tmpCtx.setLineDash([this._config.devicePixelRatio * 4, this._config.devicePixelRatio * 3]); @@ -940,6 +959,10 @@ export class TextureAtlas implements ITextureAtlas { } }; } + + public getDeviceCellWidth(): number { + return this._config.deviceCellWidth; + } } class AtlasPage { diff --git a/src/browser/renderer/shared/Types.d.ts b/src/browser/renderer/shared/Types.d.ts index a7e55e72..be910e37 100644 --- a/src/browser/renderer/shared/Types.d.ts +++ b/src/browser/renderer/shared/Types.d.ts @@ -103,12 +103,14 @@ export interface ITextureAtlas extends IDisposable { */ beginFrame(): boolean; + getDeviceCellWidth(): number; + /** * Clear all glyphs from the texture atlas. */ clearTexture(): void; - getRasterizedGlyph(code: number, bg: number, fg: number, ext: number, restrictToCellHeight: boolean): IRasterizedGlyph; - getRasterizedGlyphCombinedChar(chars: string, bg: number, fg: number, ext: number, restrictToCellHeight: boolean): IRasterizedGlyph; + getRasterizedGlyph(code: number, bg: number, fg: number, ext: number, variantOffset: number, restrictToCellHeight: boolean): IRasterizedGlyph; + getRasterizedGlyphCombinedChar(chars: string, bg: number, fg: number, ext: number, variantOffset: number, restrictToCellHeight: boolean): IRasterizedGlyph; } /** diff --git a/src/common/MultiKeyMap.ts b/src/common/MultiKeyMap.ts index 6287a8f2..edd5abb5 100644 --- a/src/common/MultiKeyMap.ts +++ b/src/common/MultiKeyMap.ts @@ -40,3 +40,22 @@ export class FourKeyMap { + private _data: { [bg: string | number]: FourKeyMap | undefined } = {}; + + public set(first: TFirst, second: TSecond, third: TThird, fourth: TFourth, five: TFive, value: TValue): void { + if (!this._data[first]) { + this._data[first] = new FourKeyMap(); + } + this._data[first as string | number]!.set(second, third, fourth, five, value); + } + + public get(first: TFirst, second: TSecond, third: TThird, fourth: TFourth, five: TFive): TValue | undefined { + return this._data[first as string | number] ? this._data[first as string | number]?.get(second, third, fourth, five) : undefined; + } + + public clear(): void { + this._data = {}; + } +} diff --git a/src/common/buffer/Constants.ts b/src/common/buffer/Constants.ts index f6a31be7..3bfd850a 100644 --- a/src/common/buffer/Constants.ts +++ b/src/common/buffer/Constants.ts @@ -134,9 +134,14 @@ export const enum BgFlags { export const enum ExtFlags { /** - * bit 27..32 (upper 3 unused) + * bit 27..29 */ - UNDERLINE_STYLE = 0x1C000000 + UNDERLINE_STYLE = 0x1C000000, + + /** + * bit 30..32 + */ + VARIANT_OFFSET = 0xE0000000 } export const enum UnderlineStyle { From cb175e513dd7afdef70f1d2879afb915eabfdb1f Mon Sep 17 00:00:00 2001 From: tisilent Date: Mon, 21 Aug 2023 19:45:38 +0800 Subject: [PATCH 02/19] Variants to Canvas. --- .../xterm-addon-canvas/src/BaseRenderLayer.ts | 6 +-- addons/xterm-addon-canvas/src/CanvasAddon.ts | 5 ++- .../xterm-addon-canvas/src/CanvasRenderer.ts | 7 ++-- .../xterm-addon-canvas/src/TextRenderLayer.ts | 38 ++++++++++++++++--- 4 files changed, 43 insertions(+), 13 deletions(-) diff --git a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts index 30f6263c..4622ab42 100644 --- a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts @@ -365,7 +365,7 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer * Draws one or more characters at a cell. If possible this will draw using * the character atlas to reduce draw time. */ - protected _drawChars(cell: ICellData, x: number, y: number): void { + protected _drawChars(cell: ICellData, x: number, y: number, variantOffset: number = 0): void { const chars = cell.getChars(); this._cellColorResolver.resolve(cell, x, this._bufferService.buffer.ydisp + y); @@ -375,9 +375,9 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer let glyph: IRasterizedGlyph; if (chars && chars.length > 1) { - glyph = this._charAtlas.getRasterizedGlyphCombinedChar(chars, this._cellColorResolver.result.bg, this._cellColorResolver.result.fg, this._cellColorResolver.result.ext, 0, true); + glyph = this._charAtlas.getRasterizedGlyphCombinedChar(chars, this._cellColorResolver.result.bg, this._cellColorResolver.result.fg, this._cellColorResolver.result.ext, variantOffset, true); } else { - glyph = this._charAtlas.getRasterizedGlyph(cell.getCode() || WHITESPACE_CELL_CODE, this._cellColorResolver.result.bg, this._cellColorResolver.result.fg, this._cellColorResolver.result.ext, 0, true); + glyph = this._charAtlas.getRasterizedGlyph(cell.getCode() || WHITESPACE_CELL_CODE, this._cellColorResolver.result.bg, this._cellColorResolver.result.fg, this._cellColorResolver.result.ext, variantOffset, true); } if (!glyph.size.x || !glyph.size.y) { return; diff --git a/addons/xterm-addon-canvas/src/CanvasAddon.ts b/addons/xterm-addon-canvas/src/CanvasAddon.ts index d6136174..f796cedc 100644 --- a/addons/xterm-addon-canvas/src/CanvasAddon.ts +++ b/addons/xterm-addon-canvas/src/CanvasAddon.ts @@ -6,7 +6,7 @@ import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService, IRenderService, ISelectionService, IThemeService } from 'browser/services/Services'; import { ITerminal } from 'browser/Types'; import { CanvasRenderer } from './CanvasRenderer'; -import { IBufferService, ICoreService, IDecorationService, IOptionsService } from 'common/services/Services'; +import { IBufferService, ICoreService, IDecorationService, IOptionsService, IUnicodeService } from 'common/services/Services'; import { ITerminalAddon, Terminal } from 'xterm'; import { EventEmitter, forwardEvent } from 'common/EventEmitter'; import { Disposable, toDisposable } from 'common/Lifecycle'; @@ -45,8 +45,9 @@ export class CanvasAddon extends Disposable implements ITerminalAddon { const coreBrowserService: ICoreBrowserService = unsafeCore._coreBrowserService; const decorationService: IDecorationService = unsafeCore._decorationService; const themeService: IThemeService = unsafeCore._themeService; + const unicodeService: IUnicodeService = unsafeCore.unicodeService; - this._renderer = new CanvasRenderer(terminal, screenElement, linkifier, bufferService, charSizeService, optionsService, characterJoinerService, coreService, coreBrowserService, decorationService, themeService); + this._renderer = new CanvasRenderer(terminal, screenElement, linkifier, bufferService, charSizeService, optionsService, characterJoinerService, coreService, coreBrowserService, decorationService, themeService, unicodeService); this.register(forwardEvent(this._renderer.onChangeTextureAtlas, this._onChangeTextureAtlas)); this.register(forwardEvent(this._renderer.onAddTextureAtlasCanvas, this._onAddTextureAtlasCanvas)); renderService.setRenderer(this._renderer); diff --git a/addons/xterm-addon-canvas/src/CanvasRenderer.ts b/addons/xterm-addon-canvas/src/CanvasRenderer.ts index 2109654e..bc65c31f 100644 --- a/addons/xterm-addon-canvas/src/CanvasRenderer.ts +++ b/addons/xterm-addon-canvas/src/CanvasRenderer.ts @@ -11,7 +11,7 @@ import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService, ISelect import { ILinkifier2 } from 'browser/Types'; import { EventEmitter, forwardEvent } from 'common/EventEmitter'; import { Disposable, toDisposable } from 'common/Lifecycle'; -import { IBufferService, ICoreService, IDecorationService, IOptionsService } from 'common/services/Services'; +import { IBufferService, ICoreService, IDecorationService, IOptionsService, IUnicodeService } from 'common/services/Services'; import { Terminal } from 'xterm'; import { CursorRenderLayer } from './CursorRenderLayer'; import { LinkRenderLayer } from './LinkRenderLayer'; @@ -43,12 +43,13 @@ export class CanvasRenderer extends Disposable implements IRenderer { coreService: ICoreService, private readonly _coreBrowserService: ICoreBrowserService, decorationService: IDecorationService, - private readonly _themeService: IThemeService + private readonly _themeService: IThemeService, + private readonly _unicodeService: IUnicodeService ) { super(); const allowTransparency = this._optionsService.rawOptions.allowTransparency; this._renderLayers = [ - new TextRenderLayer(this._terminal, this._screenElement, 0, allowTransparency, this._bufferService, this._optionsService, characterJoinerService, decorationService, this._coreBrowserService, _themeService), + new TextRenderLayer(this._terminal, this._screenElement, 0, allowTransparency, this._bufferService, this._optionsService, characterJoinerService, decorationService, this._coreBrowserService, _themeService, _unicodeService), new SelectionRenderLayer(this._terminal, this._screenElement, 1, this._bufferService, this._coreBrowserService, decorationService, this._optionsService, _themeService), new LinkRenderLayer(this._terminal, this._screenElement, 2, linkifier2, this._bufferService, this._optionsService, decorationService, this._coreBrowserService, _themeService), new CursorRenderLayer(this._terminal, this._screenElement, 3, this._onRequestRedraw, this._bufferService, this._optionsService, coreService, this._coreBrowserService, decorationService, _themeService) diff --git a/addons/xterm-addon-canvas/src/TextRenderLayer.ts b/addons/xterm-addon-canvas/src/TextRenderLayer.ts index 0066cc7d..802b514b 100644 --- a/addons/xterm-addon-canvas/src/TextRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/TextRenderLayer.ts @@ -11,7 +11,7 @@ import { AttributeData } from 'common/buffer/AttributeData'; import { NULL_CELL_CODE, Content, UnderlineStyle } from 'common/buffer/Constants'; import { IColorSet, ReadonlyColorSet } from 'browser/Types'; import { CellData } from 'common/buffer/CellData'; -import { IOptionsService, IBufferService, IDecorationService } from 'common/services/Services'; +import { IOptionsService, IBufferService, IDecorationService, IUnicodeService } from 'common/services/Services'; import { ICharacterJoinerService, ICoreBrowserService, IThemeService } from 'browser/services/Services'; import { JoinedCellData } from 'browser/services/CharacterJoinerService'; import { color, css } from 'common/Color'; @@ -41,7 +41,8 @@ export class TextRenderLayer extends BaseRenderLayer { private readonly _characterJoinerService: ICharacterJoinerService, decorationService: IDecorationService, coreBrowserService: ICoreBrowserService, - themeService: IThemeService + themeService: IThemeService, + private readonly _unicodeService: IUnicodeService ) { super(terminal, container, 'text', zIndex, alpha, themeService, bufferService, optionsService, decorationService, coreBrowserService); this._state = new GridCache(); @@ -74,13 +75,20 @@ export class TextRenderLayer extends BaseRenderLayer { callback: ( cell: ICellData, x: number, - y: number + y: number, + variantOffset: number ) => void ): void { + const fontSize = this._optionsService.rawOptions.fontSize; + const drp = this._coreBrowserService.dpr; + const lineWidth = Math.max(1, Math.floor(fontSize * drp / 15)); + const deviceCellWidth = this._charAtlas?.getDeviceCellWidth(); + let variantOffset: number = -1; for (let y = firstRow; y <= lastRow; y++) { const row = y + this._bufferService.buffer.ydisp; const line = this._bufferService.buffer.lines.get(row); const joinedRanges = this._characterJoinerService.getJoinedCharacters(row); + variantOffset = 0; for (let x = 0; x < this._bufferService.cols; x++) { line!.loadCell(x, this._workCell); let cell = this._workCell; @@ -92,9 +100,20 @@ export class TextRenderLayer extends BaseRenderLayer { // The character to the left is a wide character, drawing is owned by // the char at x-1 if (cell.getWidth() === 0) { + if (cell.extended.underlineStyle !== UnderlineStyle.DOTTED) { + variantOffset = 0; + } continue; } + const code = cell.getCode(); + let chWidth: number; + if (typeof code === 'number') { + chWidth = this._unicodeService.wcwidth(code); + } else { + chWidth = this._unicodeService.getStringCellWidth(code); + } + // exit early for NULL and SP // NOTE: commented out due to #4120 (needs a more clever patch to keep things performant) // const code = cell.getCode(); @@ -148,9 +167,18 @@ export class TextRenderLayer extends BaseRenderLayer { callback( cell, x, - y + y, + variantOffset ); + if (cell.extended.underlineStyle === UnderlineStyle.DOTTED) { + if (code !== NULL_CELL_CODE) { + variantOffset = ((deviceCellWidth! * chWidth) - ((lineWidth * 2) - variantOffset)) % (lineWidth * 2); + } + } else { + variantOffset = 0; + } + x = lastCharX; } } @@ -235,7 +263,7 @@ export class TextRenderLayer extends BaseRenderLayer { } private _drawForeground(firstRow: number, lastRow: number): void { - this._forEachCell(firstRow, lastRow, (cell, x, y) => this._drawChars(cell, x, y)); + this._forEachCell(firstRow, lastRow, (cell, x, y, variantOffset) => this._drawChars(cell, x, y, variantOffset ?? 0)); } public handleGridChanged(firstRow: number, lastRow: number): void { From c81dfee43c718f86155fd44444eca6b86c40be1b Mon Sep 17 00:00:00 2001 From: tisilent Date: Mon, 21 Aug 2023 20:54:16 +0800 Subject: [PATCH 03/19] Using ext to store variants. --- .../xterm-addon-canvas/src/BaseRenderLayer.ts | 9 +++-- addons/xterm-addon-image/src/ImageStorage.ts | 8 +++++ addons/xterm-addon-webgl/src/GlyphRenderer.ts | 4 +-- src/browser/renderer/shared/TextureAtlas.ts | 33 +++++++++---------- src/browser/renderer/shared/Types.d.ts | 4 +-- src/common/Types.d.ts | 2 ++ src/common/buffer/AttributeData.ts | 13 ++++++++ 7 files changed, 49 insertions(+), 24 deletions(-) diff --git a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts index 4622ab42..91d55353 100644 --- a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts @@ -12,7 +12,7 @@ import { createSelectionRenderModel } from 'browser/renderer/shared/SelectionRen import { ICoreBrowserService, IThemeService } from 'browser/services/Services'; import { ReadonlyColorSet } from 'browser/Types'; import { CellData } from 'common/buffer/CellData'; -import { WHITESPACE_CELL_CODE } from 'common/buffer/Constants'; +import { ExtFlags, WHITESPACE_CELL_CODE } from 'common/buffer/Constants'; import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services'; import { ICellData, IDisposable } from 'common/Types'; import { Terminal } from 'xterm'; @@ -369,15 +369,18 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer const chars = cell.getChars(); this._cellColorResolver.resolve(cell, x, this._bufferService.buffer.ydisp + y); + this._cellColorResolver.result.ext &= ~ExtFlags.VARIANT_OFFSET; + this._cellColorResolver.result.ext |= (variantOffset << 29) & ExtFlags.VARIANT_OFFSET; + if (!this._charAtlas) { return; } let glyph: IRasterizedGlyph; if (chars && chars.length > 1) { - glyph = this._charAtlas.getRasterizedGlyphCombinedChar(chars, this._cellColorResolver.result.bg, this._cellColorResolver.result.fg, this._cellColorResolver.result.ext, variantOffset, true); + glyph = this._charAtlas.getRasterizedGlyphCombinedChar(chars, this._cellColorResolver.result.bg, this._cellColorResolver.result.fg, this._cellColorResolver.result.ext, true); } else { - glyph = this._charAtlas.getRasterizedGlyph(cell.getCode() || WHITESPACE_CELL_CODE, this._cellColorResolver.result.bg, this._cellColorResolver.result.fg, this._cellColorResolver.result.ext, variantOffset, true); + glyph = this._charAtlas.getRasterizedGlyph(cell.getCode() || WHITESPACE_CELL_CODE, this._cellColorResolver.result.bg, this._cellColorResolver.result.fg, this._cellColorResolver.result.ext, true); } if (!glyph.size.x || !glyph.size.y) { return; diff --git a/addons/xterm-addon-image/src/ImageStorage.ts b/addons/xterm-addon-image/src/ImageStorage.ts index 97eef852..ef13ca11 100644 --- a/addons/xterm-addon-image/src/ImageStorage.ts +++ b/addons/xterm-addon-image/src/ImageStorage.ts @@ -53,6 +53,14 @@ class ExtendedAttrsImage implements IExtendedAttrsImage { this._ext |= value & (Attributes.CM_MASK | Attributes.RGB_MASK); } + public get underlineVarinatOffset(): number { + return (this._ext & ExtFlags.VARIANT_OFFSET) >> 29; + } + public set underlineVarinatOffset(value: number) { + this._ext &= ~ExtFlags.VARIANT_OFFSET; + this._ext |= (value << 29) & ExtFlags.VARIANT_OFFSET; + } + private _urlId: number = 0; public get urlId(): number { return this._urlId; diff --git a/addons/xterm-addon-webgl/src/GlyphRenderer.ts b/addons/xterm-addon-webgl/src/GlyphRenderer.ts index 09523a9e..cd204a1f 100644 --- a/addons/xterm-addon-webgl/src/GlyphRenderer.ts +++ b/addons/xterm-addon-webgl/src/GlyphRenderer.ts @@ -239,9 +239,9 @@ export class GlyphRenderer extends Disposable { // Get the glyph if (chars && chars.length > 1) { - $glyph = this._atlas.getRasterizedGlyphCombinedChar(chars, bg, fg, ext, variantOffset, false); + $glyph = this._atlas.getRasterizedGlyphCombinedChar(chars, bg, fg, ext, false); } else { - $glyph = this._atlas.getRasterizedGlyph(code, bg, fg, ext, variantOffset, false); + $glyph = this._atlas.getRasterizedGlyph(code, bg, fg, ext, false); } $leftCellPadding = Math.floor((this._dimensions.device.cell.width - this._dimensions.device.char.width) / 2); diff --git a/src/browser/renderer/shared/TextureAtlas.ts b/src/browser/renderer/shared/TextureAtlas.ts index bc8fd2dc..b063c14b 100644 --- a/src/browser/renderer/shared/TextureAtlas.ts +++ b/src/browser/renderer/shared/TextureAtlas.ts @@ -11,7 +11,7 @@ import { color, NULL_COLOR, rgba } from 'common/Color'; import { tryDrawCustomChar } from 'browser/renderer/shared/CustomGlyphs'; import { excludeFromContrastRatioDemands, isPowerlineGlyph, isRestrictedPowerlineGlyph, throwIfFalsy } from 'browser/renderer/shared/RendererUtils'; import { IUnicodeService } from 'common/services/Services'; -import { FiveKeyMap } from 'common/MultiKeyMap'; +import { FourKeyMap } from 'common/MultiKeyMap'; import { IdleTaskQueue } from 'common/TaskQueue'; import { IBoundingBox, ICharAtlasConfig, IRasterizedGlyph, IRequestRedrawEvent, ITextureAtlas } from 'browser/renderer/shared/Types'; import { EventEmitter } from 'common/EventEmitter'; @@ -58,8 +58,8 @@ let $glyph = undefined; export class TextureAtlas implements ITextureAtlas { private _didWarmUp: boolean = false; - private _cacheMap: FiveKeyMap = new FiveKeyMap(); - private _cacheMapCombined: FiveKeyMap = new FiveKeyMap(); + private _cacheMap: FourKeyMap = new FourKeyMap(); + private _cacheMapCombined: FourKeyMap = new FourKeyMap(); // The texture that the atlas is drawn to private _pages: AtlasPage[] = []; @@ -121,9 +121,9 @@ export class TextureAtlas implements ITextureAtlas { const queue = new IdleTaskQueue(); for (let i = 33; i < 126; i++) { queue.enqueue(() => { - if (!this._cacheMap.get(i, DEFAULT_COLOR, DEFAULT_COLOR, DEFAULT_EXT, 1)) { - const rasterizedGlyph = this._drawToCache(i, DEFAULT_COLOR, DEFAULT_COLOR, DEFAULT_EXT, 0); - this._cacheMap.set(i, DEFAULT_COLOR, DEFAULT_COLOR, DEFAULT_EXT, 1, rasterizedGlyph); + if (!this._cacheMap.get(i, DEFAULT_COLOR, DEFAULT_COLOR, DEFAULT_EXT)) { + const rasterizedGlyph = this._drawToCache(i, DEFAULT_COLOR, DEFAULT_COLOR, DEFAULT_EXT); + this._cacheMap.set(i, DEFAULT_COLOR, DEFAULT_COLOR, DEFAULT_EXT, rasterizedGlyph); } }); } @@ -244,30 +244,29 @@ export class TextureAtlas implements ITextureAtlas { } } - public getRasterizedGlyphCombinedChar(chars: string, bg: number, fg: number, ext: number, variantOffset: number, restrictToCellHeight: boolean): IRasterizedGlyph { - return this._getFromCacheMap(this._cacheMapCombined, chars, bg, fg, ext, variantOffset, restrictToCellHeight); + public getRasterizedGlyphCombinedChar(chars: string, bg: number, fg: number, ext: number, restrictToCellHeight: boolean): IRasterizedGlyph { + return this._getFromCacheMap(this._cacheMapCombined, chars, bg, fg, ext, restrictToCellHeight); } - public getRasterizedGlyph(code: number, bg: number, fg: number, ext: number, variantOffset: number, restrictToCellHeight: boolean): IRasterizedGlyph { - return this._getFromCacheMap(this._cacheMap, code, bg, fg, ext, variantOffset, restrictToCellHeight); + public getRasterizedGlyph(code: number, bg: number, fg: number, ext: number, restrictToCellHeight: boolean): IRasterizedGlyph { + return this._getFromCacheMap(this._cacheMap, code, bg, fg, ext, restrictToCellHeight); } /** * Gets the glyphs texture coords, drawing the texture if it's not already */ private _getFromCacheMap( - cacheMap: FiveKeyMap, + cacheMap: FourKeyMap, key: string | number, bg: number, fg: number, ext: number, - variantOffset: number, restrictToCellHeight: boolean = false ): IRasterizedGlyph { - $glyph = cacheMap.get(key, bg, fg, ext, variantOffset); + $glyph = cacheMap.get(key, bg, fg, ext); if (!$glyph) { - $glyph = this._drawToCache(key, bg, fg, ext, variantOffset, restrictToCellHeight); - cacheMap.set(key, bg, fg, ext, variantOffset, $glyph); + $glyph = this._drawToCache(key, bg, fg, ext, restrictToCellHeight); + cacheMap.set(key, bg, fg, ext, $glyph); } return $glyph; } @@ -428,7 +427,7 @@ export class TextureAtlas implements ITextureAtlas { } @traceCall - private _drawToCache(codeOrChars: number | string, bg: number, fg: number, ext: number,variantOffset: number = 0, restrictToCellHeight: boolean = false): IRasterizedGlyph { + private _drawToCache(codeOrChars: number | string, bg: number, fg: number, ext: number, restrictToCellHeight: boolean = false): IRasterizedGlyph { const chars = typeof codeOrChars === 'number' ? String.fromCharCode(codeOrChars) : codeOrChars; // Uncomment for debugging @@ -548,7 +547,7 @@ export class TextureAtlas implements ITextureAtlas { const yTop = Math.ceil(padding + this._config.deviceCharHeight) - yOffset - (restrictToCellHeight ? lineWidth * 2 : 0); const yMid = yTop + lineWidth; const yBot = yTop + lineWidth * 2; - let nextOffset = variantOffset; + let nextOffset = this._workAttributeData.getUnderlineVarinatOffset(); for (let i = 0; i < chWidth; i++) { this._tmpCtx.save(); diff --git a/src/browser/renderer/shared/Types.d.ts b/src/browser/renderer/shared/Types.d.ts index be910e37..66affd94 100644 --- a/src/browser/renderer/shared/Types.d.ts +++ b/src/browser/renderer/shared/Types.d.ts @@ -109,8 +109,8 @@ export interface ITextureAtlas extends IDisposable { * Clear all glyphs from the texture atlas. */ clearTexture(): void; - getRasterizedGlyph(code: number, bg: number, fg: number, ext: number, variantOffset: number, restrictToCellHeight: boolean): IRasterizedGlyph; - getRasterizedGlyphCombinedChar(chars: string, bg: number, fg: number, ext: number, variantOffset: number, restrictToCellHeight: boolean): IRasterizedGlyph; + getRasterizedGlyph(code: number, bg: number, fg: number, ext: number, restrictToCellHeight: boolean): IRasterizedGlyph; + getRasterizedGlyphCombinedChar(chars: string, bg: number, fg: number, ext: number, restrictToCellHeight: boolean): IRasterizedGlyph; } /** diff --git a/src/common/Types.d.ts b/src/common/Types.d.ts index ca9e25ff..fcd28d19 100644 --- a/src/common/Types.d.ts +++ b/src/common/Types.d.ts @@ -119,6 +119,7 @@ export interface IExtendedAttrs { ext: number; underlineStyle: UnderlineStyle; underlineColor: number; + underlineVarinatOffset: number; urlId: number; clone(): IExtendedAttrs; isEmpty(): boolean; @@ -209,6 +210,7 @@ export interface IAttributeData { isUnderlineColorPalette(): boolean; isUnderlineColorDefault(): boolean; getUnderlineStyle(): number; + getUnderlineVarinatOffset(): number; } /** Cell data */ diff --git a/src/common/buffer/AttributeData.ts b/src/common/buffer/AttributeData.ts index f4d12c2b..5333e108 100644 --- a/src/common/buffer/AttributeData.ts +++ b/src/common/buffer/AttributeData.ts @@ -126,6 +126,11 @@ export class AttributeData implements IAttributeData { ? (this.bg & BgFlags.HAS_EXTENDED ? this.extended.underlineStyle : UnderlineStyle.SINGLE) : UnderlineStyle.NONE; } + public getUnderlineVarinatOffset(): number { + return this.fg & FgFlags.UNDERLINE + ? (this.bg & BgFlags.HAS_EXTENDED ? this.extended.underlineVarinatOffset : 0) + : 0; + } } @@ -174,6 +179,14 @@ export class ExtendedAttrs implements IExtendedAttrs { this._urlId = value; } + public get underlineVarinatOffset(): number { + return (this._ext & ExtFlags.VARIANT_OFFSET) >> 29; + } + public set underlineVarinatOffset(value: number) { + this._ext &= ~ExtFlags.VARIANT_OFFSET; + this._ext |= (value << 29) & ExtFlags.VARIANT_OFFSET; + } + constructor( ext: number = 0, urlId: number = 0 From fe7f17f422096dfb1dc8ed75843faac24f532a5c Mon Sep 17 00:00:00 2001 From: tisilent Date: Tue, 22 Aug 2023 09:26:01 +0800 Subject: [PATCH 04/19] clean --- addons/xterm-addon-webgl/src/GlyphRenderer.ts | 6 +++--- src/common/MultiKeyMap.ts | 19 ------------------- 2 files changed, 3 insertions(+), 22 deletions(-) diff --git a/addons/xterm-addon-webgl/src/GlyphRenderer.ts b/addons/xterm-addon-webgl/src/GlyphRenderer.ts index cd204a1f..269d9374 100644 --- a/addons/xterm-addon-webgl/src/GlyphRenderer.ts +++ b/addons/xterm-addon-webgl/src/GlyphRenderer.ts @@ -215,15 +215,15 @@ export class GlyphRenderer extends Disposable { } @traceCall - public updateCell(x: number, y: number, code: number, bg: number, fg: number, ext: number, chars: string, lastBg: number, variantOffset: number): void { + public updateCell(x: number, y: number, code: number, bg: number, fg: number, ext: number, chars: string, lastBg: number): void { // Since this function is called for every cell (`rows*cols`), it must be very optimized. It // should not instantiate any variables unless a new glyph is drawn to the cache where the // slight slowdown is acceptable for the developer ergonomics provided as it's a once of for // each glyph. - this._updateCell(this._vertices.attributes, x, y, code, bg, fg, ext, chars, lastBg, variantOffset); + this._updateCell(this._vertices.attributes, x, y, code, bg, fg, ext, chars, lastBg); } - private _updateCell(array: Float32Array, x: number, y: number, code: number | undefined, bg: number, fg: number, ext: number, chars: string, lastBg: number, variantOffset: number): void { + private _updateCell(array: Float32Array, x: number, y: number, code: number | undefined, bg: number, fg: number, ext: number, chars: string, lastBg: number): void { $i = (y * this._terminal.cols + x) * INDICES_PER_CELL; // Exit early if this is a null character, allow space character to continue as it may have diff --git a/src/common/MultiKeyMap.ts b/src/common/MultiKeyMap.ts index edd5abb5..6287a8f2 100644 --- a/src/common/MultiKeyMap.ts +++ b/src/common/MultiKeyMap.ts @@ -40,22 +40,3 @@ export class FourKeyMap { - private _data: { [bg: string | number]: FourKeyMap | undefined } = {}; - - public set(first: TFirst, second: TSecond, third: TThird, fourth: TFourth, five: TFive, value: TValue): void { - if (!this._data[first]) { - this._data[first] = new FourKeyMap(); - } - this._data[first as string | number]!.set(second, third, fourth, five, value); - } - - public get(first: TFirst, second: TSecond, third: TThird, fourth: TFourth, five: TFive): TValue | undefined { - return this._data[first as string | number] ? this._data[first as string | number]?.get(second, third, fourth, five) : undefined; - } - - public clear(): void { - this._data = {}; - } -} From 5f0647f0ab2199f40125dff2d6945afbb81d4ebb Mon Sep 17 00:00:00 2001 From: tisilent Date: Tue, 22 Aug 2023 09:35:12 +0800 Subject: [PATCH 05/19] clean --- addons/xterm-addon-webgl/src/WebglRenderer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 2091347c..76a8a84b 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -534,7 +534,7 @@ export class WebglRenderer extends Disposable implements IRenderer { this._model.cells[i + RENDER_MODEL_FG_OFFSET] = this._cellColorResolver.result.fg; this._model.cells[i + RENDER_MODEL_EXT_OFFSET] = this._cellColorResolver.result.ext; - this._glyphRenderer!.updateCell(x, y, code, this._cellColorResolver.result.bg, this._cellColorResolver.result.fg, this._cellColorResolver.result.ext, chars, lastBg, variantOffset); + this._glyphRenderer!.updateCell(x, y, code, this._cellColorResolver.result.bg, this._cellColorResolver.result.fg, this._cellColorResolver.result.ext, chars, lastBg); if (cell.extended.underlineStyle === UnderlineStyle.DOTTED) { if (code !== NULL_CELL_CODE) { variantOffset = ((deviceCellWidth! * chWidth) - ((lineWidth * 2) - variantOffset)) % (lineWidth * 2); @@ -550,7 +550,7 @@ export class WebglRenderer extends Disposable implements IRenderer { // Null out non-first cells for (x++; x < lastCharX; x++) { j = ((y * terminal.cols) + x) * RENDER_MODEL_INDICIES_PER_CELL; - this._glyphRenderer!.updateCell(x, y, NULL_CELL_CODE, 0, 0, 0, NULL_CELL_CHAR, 0, 0); + this._glyphRenderer!.updateCell(x, y, NULL_CELL_CODE, 0, 0, 0, NULL_CELL_CHAR, 0); this._model.cells[j] = NULL_CELL_CODE; this._model.cells[j + RENDER_MODEL_BG_OFFSET] = this._cellColorResolver.result.bg; this._model.cells[j + RENDER_MODEL_FG_OFFSET] = this._cellColorResolver.result.fg; From 1f3143cfab758a5de2ebc4e174951a2fce46639f Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 23 Aug 2023 05:07:44 -0700 Subject: [PATCH 06/19] Remove unused member --- addons/xterm-addon-canvas/src/CanvasRenderer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/xterm-addon-canvas/src/CanvasRenderer.ts b/addons/xterm-addon-canvas/src/CanvasRenderer.ts index efadd104..0e695880 100644 --- a/addons/xterm-addon-canvas/src/CanvasRenderer.ts +++ b/addons/xterm-addon-canvas/src/CanvasRenderer.ts @@ -44,12 +44,12 @@ export class CanvasRenderer extends Disposable implements IRenderer { private readonly _coreBrowserService: ICoreBrowserService, decorationService: IDecorationService, private readonly _themeService: IThemeService, - private readonly _unicodeService: IUnicodeService + unicodeService: IUnicodeService ) { super(); const allowTransparency = this._optionsService.rawOptions.allowTransparency; this._renderLayers = [ - new TextRenderLayer(this._terminal, this._screenElement, 0, allowTransparency, this._bufferService, this._optionsService, characterJoinerService, decorationService, this._coreBrowserService, _themeService, _unicodeService), + new TextRenderLayer(this._terminal, this._screenElement, 0, allowTransparency, this._bufferService, this._optionsService, characterJoinerService, decorationService, this._coreBrowserService, _themeService, unicodeService), new SelectionRenderLayer(this._terminal, this._screenElement, 1, this._bufferService, this._coreBrowserService, decorationService, this._optionsService, _themeService), new LinkRenderLayer(this._terminal, this._screenElement, 2, linkifier2, this._bufferService, this._optionsService, decorationService, this._coreBrowserService, _themeService), new CursorRenderLayer(this._terminal, this._screenElement, 3, this._onRequestRedraw, this._bufferService, this._optionsService, coreService, this._coreBrowserService, decorationService, _themeService) From 2b0106f7e21a3d672a23b2c3feb2ebd52387991c Mon Sep 17 00:00:00 2001 From: tisilent Date: Thu, 24 Aug 2023 10:57:05 +0800 Subject: [PATCH 07/19] Logic move to CellColorResolver --- .../xterm-addon-canvas/src/BaseRenderLayer.ts | 14 +++--- addons/xterm-addon-canvas/src/CanvasAddon.ts | 2 +- .../xterm-addon-canvas/src/CanvasRenderer.ts | 6 +-- .../src/CursorRenderLayer.ts | 7 +-- .../xterm-addon-canvas/src/LinkRenderLayer.ts | 7 +-- .../src/SelectionRenderLayer.ts | 7 +-- .../xterm-addon-canvas/src/TextRenderLayer.ts | 38 +++------------- addons/xterm-addon-image/src/ImageStorage.ts | 6 ++- addons/xterm-addon-webgl/src/WebglRenderer.ts | 42 ++--------------- .../renderer/shared/CellColorResolver.ts | 45 +++++++++++++++++-- src/browser/renderer/shared/RendererUtils.ts | 4 ++ src/browser/renderer/shared/TextureAtlas.ts | 12 ++--- src/common/buffer/AttributeData.ts | 10 +++-- src/common/buffer/BufferLine.test.ts | 12 +++++ 14 files changed, 106 insertions(+), 106 deletions(-) diff --git a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts index 91d55353..58c01527 100644 --- a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts @@ -13,7 +13,7 @@ import { ICoreBrowserService, IThemeService } from 'browser/services/Services'; import { ReadonlyColorSet } from 'browser/Types'; import { CellData } from 'common/buffer/CellData'; import { ExtFlags, WHITESPACE_CELL_CODE } from 'common/buffer/Constants'; -import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services'; +import { IBufferService, IDecorationService, IOptionsService, IUnicodeService } from 'common/services/Services'; import { ICellData, IDisposable } from 'common/Types'; import { Terminal } from 'xterm'; import { IRenderLayer } from './Types'; @@ -55,10 +55,11 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer protected readonly _bufferService: IBufferService, protected readonly _optionsService: IOptionsService, protected readonly _decorationService: IDecorationService, - protected readonly _coreBrowserService: ICoreBrowserService + protected readonly _coreBrowserService: ICoreBrowserService, + private readonly _unicodeService: IUnicodeService ) { super(); - this._cellColorResolver = new CellColorResolver(this._terminal, this._selectionModel, this._decorationService, this._coreBrowserService, this._themeService); + this._cellColorResolver = new CellColorResolver(this._terminal, this._selectionModel, this._decorationService, this._coreBrowserService, this._themeService, _unicodeService); this._canvas = document.createElement('canvas'); this._canvas.classList.add(`xterm-${id}-layer`); this._canvas.style.zIndex = zIndex.toString(); @@ -365,12 +366,9 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer * Draws one or more characters at a cell. If possible this will draw using * the character atlas to reduce draw time. */ - protected _drawChars(cell: ICellData, x: number, y: number, variantOffset: number = 0): void { + protected _drawChars(cell: ICellData, x: number, y: number): void { const chars = cell.getChars(); - this._cellColorResolver.resolve(cell, x, this._bufferService.buffer.ydisp + y); - - this._cellColorResolver.result.ext &= ~ExtFlags.VARIANT_OFFSET; - this._cellColorResolver.result.ext |= (variantOffset << 29) & ExtFlags.VARIANT_OFFSET; + this._cellColorResolver.resolve(cell, x, this._bufferService.buffer.ydisp + y, this._deviceCellWidth); if (!this._charAtlas) { return; diff --git a/addons/xterm-addon-canvas/src/CanvasAddon.ts b/addons/xterm-addon-canvas/src/CanvasAddon.ts index 01ce5ca7..25019f8e 100644 --- a/addons/xterm-addon-canvas/src/CanvasAddon.ts +++ b/addons/xterm-addon-canvas/src/CanvasAddon.ts @@ -54,7 +54,7 @@ export class CanvasAddon extends Disposable implements ITerminalAddon { setTraceLogger(logService); this._renderer = new CanvasRenderer(terminal, screenElement, linkifier, bufferService, charSizeService, optionsService, characterJoinerService, coreService, coreBrowserService, decorationService, themeService, unicodeService); - + this.register(forwardEvent(this._renderer.onChangeTextureAtlas, this._onChangeTextureAtlas)); this.register(forwardEvent(this._renderer.onAddTextureAtlasCanvas, this._onAddTextureAtlasCanvas)); renderService.setRenderer(this._renderer); diff --git a/addons/xterm-addon-canvas/src/CanvasRenderer.ts b/addons/xterm-addon-canvas/src/CanvasRenderer.ts index bc65c31f..307dc496 100644 --- a/addons/xterm-addon-canvas/src/CanvasRenderer.ts +++ b/addons/xterm-addon-canvas/src/CanvasRenderer.ts @@ -50,9 +50,9 @@ export class CanvasRenderer extends Disposable implements IRenderer { const allowTransparency = this._optionsService.rawOptions.allowTransparency; this._renderLayers = [ new TextRenderLayer(this._terminal, this._screenElement, 0, allowTransparency, this._bufferService, this._optionsService, characterJoinerService, decorationService, this._coreBrowserService, _themeService, _unicodeService), - new SelectionRenderLayer(this._terminal, this._screenElement, 1, this._bufferService, this._coreBrowserService, decorationService, this._optionsService, _themeService), - new LinkRenderLayer(this._terminal, this._screenElement, 2, linkifier2, this._bufferService, this._optionsService, decorationService, this._coreBrowserService, _themeService), - new CursorRenderLayer(this._terminal, this._screenElement, 3, this._onRequestRedraw, this._bufferService, this._optionsService, coreService, this._coreBrowserService, decorationService, _themeService) + new SelectionRenderLayer(this._terminal, this._screenElement, 1, this._bufferService, this._coreBrowserService, decorationService, this._optionsService, _themeService, _unicodeService), + new LinkRenderLayer(this._terminal, this._screenElement, 2, linkifier2, this._bufferService, this._optionsService, decorationService, this._coreBrowserService, _themeService, _unicodeService), + new CursorRenderLayer(this._terminal, this._screenElement, 3, this._onRequestRedraw, this._bufferService, this._optionsService, coreService, this._coreBrowserService, decorationService, _themeService, _unicodeService) ]; for (const layer of this._renderLayers) { forwardEvent(layer.onAddTextureAtlasCanvas, this._onAddTextureAtlasCanvas); diff --git a/addons/xterm-addon-canvas/src/CursorRenderLayer.ts b/addons/xterm-addon-canvas/src/CursorRenderLayer.ts index 2ef1d072..9e158e32 100644 --- a/addons/xterm-addon-canvas/src/CursorRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/CursorRenderLayer.ts @@ -8,7 +8,7 @@ import { BaseRenderLayer } from './BaseRenderLayer'; import { ICellData } from 'common/Types'; import { CellData } from 'common/buffer/CellData'; import { IColorSet, ReadonlyColorSet } from 'browser/Types'; -import { IBufferService, IOptionsService, ICoreService, IDecorationService } from 'common/services/Services'; +import { IBufferService, IOptionsService, ICoreService, IDecorationService, IUnicodeService } from 'common/services/Services'; import { IEventEmitter } from 'common/EventEmitter'; import { ICoreBrowserService, IThemeService } from 'browser/services/Services'; import { Terminal } from 'xterm'; @@ -45,9 +45,10 @@ export class CursorRenderLayer extends BaseRenderLayer { private readonly _coreService: ICoreService, coreBrowserService: ICoreBrowserService, decorationService: IDecorationService, - themeService: IThemeService + themeService: IThemeService, + unicodeService: IUnicodeService ) { - super(terminal, container, 'cursor', zIndex, true, themeService, bufferService, optionsService, decorationService, coreBrowserService); + super(terminal, container, 'cursor', zIndex, true, themeService, bufferService, optionsService, decorationService, coreBrowserService, unicodeService); this._state = { x: 0, y: 0, diff --git a/addons/xterm-addon-canvas/src/LinkRenderLayer.ts b/addons/xterm-addon-canvas/src/LinkRenderLayer.ts index 2d4c217c..af0482ef 100644 --- a/addons/xterm-addon-canvas/src/LinkRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/LinkRenderLayer.ts @@ -8,7 +8,7 @@ import { BaseRenderLayer } from './BaseRenderLayer'; import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/shared/Constants'; import { ICoreBrowserService, IThemeService } from 'browser/services/Services'; import { IColorSet, ILinkifierEvent, ILinkifier2, ReadonlyColorSet } from 'browser/Types'; -import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services'; +import { IBufferService, IDecorationService, IOptionsService, IUnicodeService } from 'common/services/Services'; import { is256Color } from 'browser/renderer/shared/CharAtlasUtils'; import { Terminal } from 'xterm'; @@ -24,9 +24,10 @@ export class LinkRenderLayer extends BaseRenderLayer { optionsService: IOptionsService, decorationService: IDecorationService, coreBrowserService: ICoreBrowserService, - themeService: IThemeService + themeService: IThemeService, + unicodeService: IUnicodeService ) { - super(terminal, container, 'link', zIndex, true, themeService, bufferService, optionsService, decorationService, coreBrowserService); + super(terminal, container, 'link', zIndex, true, themeService, bufferService, optionsService, decorationService, coreBrowserService, unicodeService); this.register(linkifier2.onShowLinkUnderline(e => this._handleShowLinkUnderline(e))); this.register(linkifier2.onHideLinkUnderline(e => this._handleHideLinkUnderline(e))); diff --git a/addons/xterm-addon-canvas/src/SelectionRenderLayer.ts b/addons/xterm-addon-canvas/src/SelectionRenderLayer.ts index 10950868..0e397441 100644 --- a/addons/xterm-addon-canvas/src/SelectionRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/SelectionRenderLayer.ts @@ -5,7 +5,7 @@ import { IRenderDimensions } from 'browser/renderer/shared/Types'; import { BaseRenderLayer } from './BaseRenderLayer'; -import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services'; +import { IBufferService, IDecorationService, IOptionsService, IUnicodeService } from 'common/services/Services'; import { ICoreBrowserService, IThemeService } from 'browser/services/Services'; import { Terminal } from 'xterm'; @@ -27,9 +27,10 @@ export class SelectionRenderLayer extends BaseRenderLayer { coreBrowserService: ICoreBrowserService, decorationService: IDecorationService, optionsService: IOptionsService, - themeService: IThemeService + themeService: IThemeService, + unicodeService: IUnicodeService ) { - super(terminal, container, 'selection', zIndex, true, themeService, bufferService, optionsService, decorationService, coreBrowserService); + super(terminal, container, 'selection', zIndex, true, themeService, bufferService, optionsService, decorationService, coreBrowserService, unicodeService); this._clearState(); } diff --git a/addons/xterm-addon-canvas/src/TextRenderLayer.ts b/addons/xterm-addon-canvas/src/TextRenderLayer.ts index 802b514b..2f98f4c6 100644 --- a/addons/xterm-addon-canvas/src/TextRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/TextRenderLayer.ts @@ -16,6 +16,7 @@ import { ICharacterJoinerService, ICoreBrowserService, IThemeService } from 'bro import { JoinedCellData } from 'browser/services/CharacterJoinerService'; import { color, css } from 'common/Color'; import { Terminal } from 'xterm'; +import { computeVarinatOffset } from 'browser/renderer/shared/RendererUtils'; /** * This CharData looks like a null character, which will forc a clear and render @@ -42,9 +43,9 @@ export class TextRenderLayer extends BaseRenderLayer { decorationService: IDecorationService, coreBrowserService: ICoreBrowserService, themeService: IThemeService, - private readonly _unicodeService: IUnicodeService + unicodeService: IUnicodeService ) { - super(terminal, container, 'text', zIndex, alpha, themeService, bufferService, optionsService, decorationService, coreBrowserService); + super(terminal, container, 'text', zIndex, alpha, themeService, bufferService, optionsService, decorationService, coreBrowserService, unicodeService); this._state = new GridCache(); this.register(optionsService.onSpecificOptionChange('allowTransparency', value => this._setTransparency(value))); } @@ -75,20 +76,13 @@ export class TextRenderLayer extends BaseRenderLayer { callback: ( cell: ICellData, x: number, - y: number, - variantOffset: number + y: number ) => void ): void { - const fontSize = this._optionsService.rawOptions.fontSize; - const drp = this._coreBrowserService.dpr; - const lineWidth = Math.max(1, Math.floor(fontSize * drp / 15)); - const deviceCellWidth = this._charAtlas?.getDeviceCellWidth(); - let variantOffset: number = -1; for (let y = firstRow; y <= lastRow; y++) { const row = y + this._bufferService.buffer.ydisp; const line = this._bufferService.buffer.lines.get(row); const joinedRanges = this._characterJoinerService.getJoinedCharacters(row); - variantOffset = 0; for (let x = 0; x < this._bufferService.cols; x++) { line!.loadCell(x, this._workCell); let cell = this._workCell; @@ -100,20 +94,9 @@ export class TextRenderLayer extends BaseRenderLayer { // The character to the left is a wide character, drawing is owned by // the char at x-1 if (cell.getWidth() === 0) { - if (cell.extended.underlineStyle !== UnderlineStyle.DOTTED) { - variantOffset = 0; - } continue; } - const code = cell.getCode(); - let chWidth: number; - if (typeof code === 'number') { - chWidth = this._unicodeService.wcwidth(code); - } else { - chWidth = this._unicodeService.getStringCellWidth(code); - } - // exit early for NULL and SP // NOTE: commented out due to #4120 (needs a more clever patch to keep things performant) // const code = cell.getCode(); @@ -167,18 +150,9 @@ export class TextRenderLayer extends BaseRenderLayer { callback( cell, x, - y, - variantOffset + y ); - if (cell.extended.underlineStyle === UnderlineStyle.DOTTED) { - if (code !== NULL_CELL_CODE) { - variantOffset = ((deviceCellWidth! * chWidth) - ((lineWidth * 2) - variantOffset)) % (lineWidth * 2); - } - } else { - variantOffset = 0; - } - x = lastCharX; } } @@ -263,7 +237,7 @@ export class TextRenderLayer extends BaseRenderLayer { } private _drawForeground(firstRow: number, lastRow: number): void { - this._forEachCell(firstRow, lastRow, (cell, x, y, variantOffset) => this._drawChars(cell, x, y, variantOffset ?? 0)); + this._forEachCell(firstRow, lastRow, (cell, x, y) => this._drawChars(cell, x, y)); } public handleGridChanged(firstRow: number, lastRow: number): void { diff --git a/addons/xterm-addon-image/src/ImageStorage.ts b/addons/xterm-addon-image/src/ImageStorage.ts index ef13ca11..86d48a6d 100644 --- a/addons/xterm-addon-image/src/ImageStorage.ts +++ b/addons/xterm-addon-image/src/ImageStorage.ts @@ -54,7 +54,11 @@ class ExtendedAttrsImage implements IExtendedAttrsImage { } public get underlineVarinatOffset(): number { - return (this._ext & ExtFlags.VARIANT_OFFSET) >> 29; + const val = (this._ext & ExtFlags.VARIANT_OFFSET) >> 29; + if (val < 0) { + return val ^ 4294967288; + } + return val; } public set underlineVarinatOffset(value: number) { this._ext &= ~ExtFlags.VARIANT_OFFSET; diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 76a8a84b..31faa21e 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -14,7 +14,7 @@ import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService, IThemeS import { ITerminal } from 'browser/Types'; import { AttributeData } from 'common/buffer/AttributeData'; import { CellData } from 'common/buffer/CellData'; -import { Attributes, Content, ExtFlags, NULL_CELL_CHAR, NULL_CELL_CODE, UnderlineStyle } from 'common/buffer/Constants'; +import { Attributes, Content, NULL_CELL_CHAR, NULL_CELL_CODE, UnderlineStyle } from 'common/buffer/Constants'; import { EventEmitter, forwardEvent } from 'common/EventEmitter'; import { Disposable, MutableDisposable, getDisposeArrayDisposable, toDisposable } from 'common/Lifecycle'; import { ICoreService, IDecorationService, ILogService, IOptionsService, IUnicodeService } from 'common/services/Services'; @@ -78,7 +78,7 @@ export class WebglRenderer extends Disposable implements IRenderer { this.register(this._themeService.onChangeColors(() => this._handleColorChange())); - this._cellColorResolver = new CellColorResolver(this._terminal, this._model.selection, this._decorationService, this._coreBrowserService, this._themeService); + this._cellColorResolver = new CellColorResolver(this._terminal, this._model.selection, this._decorationService, this._coreBrowserService, this._themeService, this._unicodeService); this._core = (this._terminal as any)._core; @@ -396,7 +396,6 @@ export class WebglRenderer extends Disposable implements IRenderer { let i: number; let x: number; let j: number; - let variantOffset: number = -1; start = clamp(start, terminal.rows - 1, 0); end = clamp(end, terminal.rows - 1, 0); @@ -411,18 +410,11 @@ export class WebglRenderer extends Disposable implements IRenderer { this._model.cursor = undefined; let modelUpdated = false; - const fontSize = this._optionsService.rawOptions.fontSize; - const drp = this._coreBrowserService.dpr; - const lineWidth = Math.max(1, Math.floor(fontSize * drp / 15)); - const deviceCellWidth = this._charAtlas?.getDeviceCellWidth(); - for (y = start; y <= end; y++) { row = y + terminal.buffer.ydisp; line = terminal.buffer.lines.get(row)!; this._model.lineLengths[y] = 0; joinedRanges = this._characterJoinerService.getJoinedCharacters(row); - // row start variant init - variantOffset = 0; for (x = 0; x < terminal.cols; x++) { lastBg = this._cellColorResolver.result.bg; line.loadCell(x, cell); @@ -457,17 +449,10 @@ export class WebglRenderer extends Disposable implements IRenderer { chars = cell.getChars(); code = cell.getCode(); - let chWidth: number; - if (typeof code === 'number') { - chWidth = this._unicodeService.wcwidth(code); - } else { - chWidth = this._unicodeService.getStringCellWidth(code); - } - i = ((y * terminal.cols) + x) * RENDER_MODEL_INDICIES_PER_CELL; // Load colors/resolve overrides into work colors - this._cellColorResolver.resolve(cell, x, row); + this._cellColorResolver.resolve(cell, x, row, this.dimensions.device.cell.width); // Override colors for cursor cell if (isCursorVisible && row === cursorY) { @@ -497,27 +482,13 @@ export class WebglRenderer extends Disposable implements IRenderer { if (code !== NULL_CELL_CODE) { this._model.lineLengths[y] = x + 1; - } else { - if (cell.extended.underlineStyle !== UnderlineStyle.DOTTED) { - variantOffset = 0; - } } - this._cellColorResolver.result.ext &= ~ExtFlags.VARIANT_OFFSET; - this._cellColorResolver.result.ext |= (variantOffset << 29) & ExtFlags.VARIANT_OFFSET; - // Nothing has changed, no updates needed if (this._model.cells[i] === code && this._model.cells[i + RENDER_MODEL_BG_OFFSET] === this._cellColorResolver.result.bg && this._model.cells[i + RENDER_MODEL_FG_OFFSET] === this._cellColorResolver.result.fg && this._model.cells[i + RENDER_MODEL_EXT_OFFSET] === this._cellColorResolver.result.ext) { - if (cell.extended.underlineStyle === UnderlineStyle.DOTTED) { - if (code !== NULL_CELL_CODE) { - variantOffset = ((deviceCellWidth! * chWidth) - ((lineWidth * 2) - variantOffset)) % (lineWidth * 2); - } - } else { - variantOffset = 0; - } continue; } @@ -535,13 +506,6 @@ export class WebglRenderer extends Disposable implements IRenderer { this._model.cells[i + RENDER_MODEL_EXT_OFFSET] = this._cellColorResolver.result.ext; this._glyphRenderer!.updateCell(x, y, code, this._cellColorResolver.result.bg, this._cellColorResolver.result.fg, this._cellColorResolver.result.ext, chars, lastBg); - if (cell.extended.underlineStyle === UnderlineStyle.DOTTED) { - if (code !== NULL_CELL_CODE) { - variantOffset = ((deviceCellWidth! * chWidth) - ((lineWidth * 2) - variantOffset)) % (lineWidth * 2); - } - } else { - variantOffset = 0; - } if (isJoined) { // Restore work cell diff --git a/src/browser/renderer/shared/CellColorResolver.ts b/src/browser/renderer/shared/CellColorResolver.ts index ae5019e2..b034832f 100644 --- a/src/browser/renderer/shared/CellColorResolver.ts +++ b/src/browser/renderer/shared/CellColorResolver.ts @@ -1,8 +1,9 @@ +import { computeVarinatOffset } from 'browser/renderer/shared/RendererUtils'; import { ISelectionRenderModel } from 'browser/renderer/shared/Types'; import { ICoreBrowserService, IThemeService } from 'browser/services/Services'; import { IColorSet, ReadonlyColorSet } from 'browser/Types'; -import { Attributes, BgFlags, FgFlags } from 'common/buffer/Constants'; -import { IDecorationService } from 'common/services/Services'; +import { Attributes, BgFlags, ExtFlags, FgFlags, NULL_CELL_CODE, UnderlineStyle } from 'common/buffer/Constants'; +import { IDecorationService, IUnicodeService } from 'common/services/Services'; import { ICellData } from 'common/Types'; import { Terminal } from 'xterm'; @@ -13,6 +14,9 @@ let $hasFg = false; let $hasBg = false; let $isSelected = false; let $colors: ReadonlyColorSet | undefined; +let $x = -1; +let $y = -1; +let $variantOffset = 0; export class CellColorResolver { /** @@ -30,7 +34,8 @@ export class CellColorResolver { private readonly _selectionRenderModel: ISelectionRenderModel, private readonly _decorationService: IDecorationService, private readonly _coreBrowserService: ICoreBrowserService, - private readonly _themeService: IThemeService + private readonly _themeService: IThemeService, + private readonly _unicodeService: IUnicodeService ) { } @@ -38,7 +43,7 @@ export class CellColorResolver { * Resolves colors for the cell, putting the result into the shared {@link result}. This resolves * overrides, inverse and selection for the cell which can then be used to feed into the renderer. */ - public resolve(cell: ICellData, x: number, y: number): void { + public resolve(cell: ICellData, x: number, y: number,deviceCellWidth: number): void { this.result.bg = cell.bg; this.result.fg = cell.fg; this.result.ext = cell.bg & BgFlags.HAS_EXTENDED ? cell.extended.ext : 0; @@ -53,6 +58,26 @@ export class CellColorResolver { $isSelected = false; $colors = this._themeService.colors; + if ($y !== y) { + $variantOffset = 0; + } + $x = x; + $y = y; + + const code = cell.getCode(); + if (code === NULL_CELL_CODE && cell.extended.underlineStyle !== UnderlineStyle.DOTTED) { + $variantOffset = 0; + } + const fontSize = this._terminal.options.fontSize; + const drp = this._coreBrowserService.dpr; + const lineWidth = Math.max(1, Math.floor(fontSize! * drp / 15)); + let chWidth: number; + if (typeof code === 'number') { + chWidth = this._unicodeService.wcwidth(code); + } else { + chWidth = this._unicodeService.getStringCellWidth(code); + } + // Apply decorations on the bottom layer this._decorationService.forEachDecorationAtCell(x, y, 'bottom', d => { if (d.backgroundColorRGB) { @@ -133,5 +158,17 @@ export class CellColorResolver { // Use the override if it exists this.result.bg = $hasBg ? $bg : this.result.bg; this.result.fg = $hasFg ? $fg : this.result.fg; + + this.result.ext &= ~ExtFlags.VARIANT_OFFSET; + this.result.ext |= ($variantOffset << 29) & ExtFlags.VARIANT_OFFSET; + + // compute next varinatOffset + if (cell.extended.underlineStyle === UnderlineStyle.DOTTED) { + if (code !== NULL_CELL_CODE) { + $variantOffset = computeVarinatOffset(deviceCellWidth * chWidth, lineWidth, $variantOffset); + } + } else { + $variantOffset = 0; + } } } diff --git a/src/browser/renderer/shared/RendererUtils.ts b/src/browser/renderer/shared/RendererUtils.ts index 052f2894..839d8701 100644 --- a/src/browser/renderer/shared/RendererUtils.ts +++ b/src/browser/renderer/shared/RendererUtils.ts @@ -56,3 +56,7 @@ function createDimension(): IDimensions { height: 0 }; } + +export function computeVarinatOffset(cellWidth: number, lineWidth: number, currentOffset: number = 0): number { + return (cellWidth - (Math.round(lineWidth) * 2 - currentOffset)) % (Math.round(lineWidth) * 2); +} diff --git a/src/browser/renderer/shared/TextureAtlas.ts b/src/browser/renderer/shared/TextureAtlas.ts index b063c14b..0f2ee018 100644 --- a/src/browser/renderer/shared/TextureAtlas.ts +++ b/src/browser/renderer/shared/TextureAtlas.ts @@ -9,7 +9,7 @@ import { IColor } from 'common/Types'; import { AttributeData } from 'common/buffer/AttributeData'; import { color, NULL_COLOR, rgba } from 'common/Color'; import { tryDrawCustomChar } from 'browser/renderer/shared/CustomGlyphs'; -import { excludeFromContrastRatioDemands, isPowerlineGlyph, isRestrictedPowerlineGlyph, throwIfFalsy } from 'browser/renderer/shared/RendererUtils'; +import { computeVarinatOffset, excludeFromContrastRatioDemands, isPowerlineGlyph, isRestrictedPowerlineGlyph, throwIfFalsy } from 'browser/renderer/shared/RendererUtils'; import { IUnicodeService } from 'common/services/Services'; import { FourKeyMap } from 'common/MultiKeyMap'; import { IdleTaskQueue } from 'common/TaskQueue'; @@ -597,14 +597,16 @@ export class TextureAtlas implements ITextureAtlas { ); break; case UnderlineStyle.DOTTED: - const offsetWidth = nextOffset >= lineWidth ? lineWidth * 2 - nextOffset : lineWidth - nextOffset; + const offsetWidth = nextOffset === 0 ? 0 : + (nextOffset >= lineWidth ? lineWidth * 2 - nextOffset : lineWidth - nextOffset); if (offsetWidth === 0) { this._tmpCtx.setLineDash([Math.round(lineWidth), Math.round(lineWidth)]); this._tmpCtx.moveTo(xChLeft, yTop); this._tmpCtx.lineTo(xChRight, yTop); } else { - const isFull = nextOffset >= lineWidth ? false : true; - if (isFull === false) { + // a line and a gap. + const isLineStart = nextOffset >= lineWidth ? false : true; + if (isLineStart === false) { this._tmpCtx.setLineDash([Math.round(lineWidth), Math.round(lineWidth)]); this._tmpCtx.moveTo(xChLeft + offsetWidth, yTop); this._tmpCtx.lineTo(xChRight, yTop); @@ -616,7 +618,7 @@ export class TextureAtlas implements ITextureAtlas { this._tmpCtx.lineTo(xChRight, yTop); } } - nextOffset = (xChRight - xChLeft - ((lineWidth * 2) - nextOffset)) % (Math.round(lineWidth) * 2); + nextOffset = computeVarinatOffset(xChRight - xChLeft, lineWidth, nextOffset); break; case UnderlineStyle.DASHED: this._tmpCtx.setLineDash([this._config.devicePixelRatio * 4, this._config.devicePixelRatio * 3]); diff --git a/src/common/buffer/AttributeData.ts b/src/common/buffer/AttributeData.ts index 5333e108..91c5de61 100644 --- a/src/common/buffer/AttributeData.ts +++ b/src/common/buffer/AttributeData.ts @@ -127,9 +127,7 @@ export class AttributeData implements IAttributeData { : UnderlineStyle.NONE; } public getUnderlineVarinatOffset(): number { - return this.fg & FgFlags.UNDERLINE - ? (this.bg & BgFlags.HAS_EXTENDED ? this.extended.underlineVarinatOffset : 0) - : 0; + return this.extended.underlineVarinatOffset; } } @@ -180,7 +178,11 @@ export class ExtendedAttrs implements IExtendedAttrs { } public get underlineVarinatOffset(): number { - return (this._ext & ExtFlags.VARIANT_OFFSET) >> 29; + const val = (this._ext & ExtFlags.VARIANT_OFFSET) >> 29; + if (val < 0) { + return val ^ 4294967288; + } + return val; } public set underlineVarinatOffset(value: number) { this._ext &= ~ExtFlags.VARIANT_OFFSET; diff --git a/src/common/buffer/BufferLine.test.ts b/src/common/buffer/BufferLine.test.ts index 111aae03..3e115b4b 100644 --- a/src/common/buffer/BufferLine.test.ts +++ b/src/common/buffer/BufferLine.test.ts @@ -119,6 +119,18 @@ describe('AttributeData', () => { attrs.fg &= ~FgFlags.UNDERLINE; assert.equal(attrs.getUnderlineStyle(), UnderlineStyle.NONE); }); + it('getUnderlineVarinatOffset', () => { + const attrs = new AttributeData(); + + // defaults to no offset + assert.equal(attrs.getUnderlineVarinatOffset(), 0); + + // should return 0 - 7 + for (let i = 0; i < 8; ++i) { + attrs.extended.underlineVarinatOffset = i; + assert.equal(attrs.getUnderlineVarinatOffset(), i); + } + }); }); }); From 564371eae78007af8b6c6b387bacc35b70d2691a Mon Sep 17 00:00:00 2001 From: tisilent Date: Thu, 24 Aug 2023 11:38:51 +0800 Subject: [PATCH 08/19] Clean unused member --- addons/xterm-addon-canvas/src/BaseRenderLayer.ts | 2 +- addons/xterm-addon-webgl/src/WebglRenderer.ts | 2 +- src/browser/renderer/shared/CellColorResolver.ts | 4 +--- src/browser/renderer/shared/TextureAtlas.ts | 4 ---- src/browser/renderer/shared/Types.d.ts | 2 -- 5 files changed, 3 insertions(+), 11 deletions(-) diff --git a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts index 58c01527..d68923e8 100644 --- a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts @@ -12,7 +12,7 @@ import { createSelectionRenderModel } from 'browser/renderer/shared/SelectionRen import { ICoreBrowserService, IThemeService } from 'browser/services/Services'; import { ReadonlyColorSet } from 'browser/Types'; import { CellData } from 'common/buffer/CellData'; -import { ExtFlags, WHITESPACE_CELL_CODE } from 'common/buffer/Constants'; +import { WHITESPACE_CELL_CODE } from 'common/buffer/Constants'; import { IBufferService, IDecorationService, IOptionsService, IUnicodeService } from 'common/services/Services'; import { ICellData, IDisposable } from 'common/Types'; import { Terminal } from 'xterm'; diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 1cb36f46..6b7859eb 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -17,7 +17,7 @@ import { Disposable, MutableDisposable, getDisposeArrayDisposable, toDisposable import { CharData, IBufferLine, ICellData } from 'common/Types'; import { AttributeData } from 'common/buffer/AttributeData'; import { CellData } from 'common/buffer/CellData'; -import { Attributes, Content, ExtFlags, NULL_CELL_CHAR, NULL_CELL_CODE, UnderlineStyle } from 'common/buffer/Constants'; +import { Attributes, Content, NULL_CELL_CHAR, NULL_CELL_CODE } from 'common/buffer/Constants'; import { traceCall } from 'common/services/LogService'; import { ICoreService, IDecorationService, IOptionsService, IUnicodeService } from 'common/services/Services'; import { IDisposable, Terminal } from 'xterm'; diff --git a/src/browser/renderer/shared/CellColorResolver.ts b/src/browser/renderer/shared/CellColorResolver.ts index 1790ddb2..b2d66042 100644 --- a/src/browser/renderer/shared/CellColorResolver.ts +++ b/src/browser/renderer/shared/CellColorResolver.ts @@ -14,7 +14,6 @@ let $hasFg = false; let $hasBg = false; let $isSelected = false; let $colors: ReadonlyColorSet | undefined; -let $x = -1; let $y = -1; let $variantOffset = 0; @@ -43,7 +42,7 @@ export class CellColorResolver { * Resolves colors for the cell, putting the result into the shared {@link result}. This resolves * overrides, inverse and selection for the cell which can then be used to feed into the renderer. */ - public resolve(cell: ICellData, x: number, y: number,deviceCellWidth: number): void { + public resolve(cell: ICellData, x: number, y: number, deviceCellWidth: number): void { this.result.bg = cell.bg; this.result.fg = cell.fg; this.result.ext = cell.bg & BgFlags.HAS_EXTENDED ? cell.extended.ext : 0; @@ -61,7 +60,6 @@ export class CellColorResolver { if ($y !== y) { $variantOffset = 0; } - $x = x; $y = y; const code = cell.getCode(); diff --git a/src/browser/renderer/shared/TextureAtlas.ts b/src/browser/renderer/shared/TextureAtlas.ts index 8a70e252..b2d23a02 100644 --- a/src/browser/renderer/shared/TextureAtlas.ts +++ b/src/browser/renderer/shared/TextureAtlas.ts @@ -960,10 +960,6 @@ export class TextureAtlas implements ITextureAtlas { } }; } - - public getDeviceCellWidth(): number { - return this._config.deviceCellWidth; - } } class AtlasPage { diff --git a/src/browser/renderer/shared/Types.d.ts b/src/browser/renderer/shared/Types.d.ts index 66affd94..a7e55e72 100644 --- a/src/browser/renderer/shared/Types.d.ts +++ b/src/browser/renderer/shared/Types.d.ts @@ -103,8 +103,6 @@ export interface ITextureAtlas extends IDisposable { */ beginFrame(): boolean; - getDeviceCellWidth(): number; - /** * Clear all glyphs from the texture atlas. */ From edfacbb5c6ec6501e9ff4a8716040571f9bdf94e Mon Sep 17 00:00:00 2001 From: tisilent Date: Thu, 24 Aug 2023 11:54:18 +0800 Subject: [PATCH 09/19] Adjusting the execution process --- .../renderer/shared/CellColorResolver.ts | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/browser/renderer/shared/CellColorResolver.ts b/src/browser/renderer/shared/CellColorResolver.ts index b2d66042..76f482d2 100644 --- a/src/browser/renderer/shared/CellColorResolver.ts +++ b/src/browser/renderer/shared/CellColorResolver.ts @@ -66,15 +66,6 @@ export class CellColorResolver { if (code === NULL_CELL_CODE && cell.extended.underlineStyle !== UnderlineStyle.DOTTED) { $variantOffset = 0; } - const fontSize = this._terminal.options.fontSize; - const drp = this._coreBrowserService.dpr; - const lineWidth = Math.max(1, Math.floor(fontSize! * drp / 15)); - let chWidth: number; - if (typeof code === 'number') { - chWidth = this._unicodeService.wcwidth(code); - } else { - chWidth = this._unicodeService.getStringCellWidth(code); - } // Apply decorations on the bottom layer this._decorationService.forEachDecorationAtCell(x, y, 'bottom', d => { @@ -157,12 +148,22 @@ export class CellColorResolver { this.result.bg = $hasBg ? $bg : this.result.bg; this.result.fg = $hasFg ? $fg : this.result.fg; + // Reset overrides variantOffset this.result.ext &= ~ExtFlags.VARIANT_OFFSET; this.result.ext |= ($variantOffset << 29) & ExtFlags.VARIANT_OFFSET; - // compute next varinatOffset + // Compute next varinatOffset if (cell.extended.underlineStyle === UnderlineStyle.DOTTED) { if (code !== NULL_CELL_CODE) { + const fontSize = this._terminal.options.fontSize; + const drp = this._coreBrowserService.dpr; + const lineWidth = Math.max(1, Math.floor(fontSize! * drp / 15)); + let chWidth: number; + if (typeof code === 'number') { + chWidth = this._unicodeService.wcwidth(code); + } else { + chWidth = this._unicodeService.getStringCellWidth(code); + } $variantOffset = computeVarinatOffset(deviceCellWidth * chWidth, lineWidth, $variantOffset); } } else { From 52c87bc8b3fcdae933b62c1ae665c0249e558e7a Mon Sep 17 00:00:00 2001 From: tisilent Date: Thu, 24 Aug 2023 14:46:30 +0800 Subject: [PATCH 10/19] Add tests for computeVarinatOffset --- .../renderer/shared/RendererUtils.test.ts | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 src/browser/renderer/shared/RendererUtils.test.ts diff --git a/src/browser/renderer/shared/RendererUtils.test.ts b/src/browser/renderer/shared/RendererUtils.test.ts new file mode 100644 index 00000000..e933af52 --- /dev/null +++ b/src/browser/renderer/shared/RendererUtils.test.ts @@ -0,0 +1,45 @@ +import { computeVarinatOffset } from 'browser/renderer/shared/RendererUtils'; +import { assert } from 'chai'; + +describe('RendererUtils', () => { + it('computeVarinatOffset', () => { + const cellWidth = 11; + const doubleCellWidth = 22; + let line = 1; + let varinatOffset = 0; + + // should line 1 + // =,_,=_,=_, + let cells = [cellWidth, cellWidth, doubleCellWidth, doubleCellWidth]; + let result = [1, 0, 0, 0]; + for (let index = 0; index < cells.length; index++) { + const cell = cells[index]; + varinatOffset = computeVarinatOffset(cell, line, varinatOffset); + assert.equal(varinatOffset, result[index]); + } + + // should line 2 + // ==__==__==_,_==__==__==,__==__==__==__==__==__,==__==__==__==__==__==, + line = 2; + varinatOffset = 0; + cells = [cellWidth, cellWidth, doubleCellWidth, doubleCellWidth]; + result = [3, 2, 0 ,2]; + for (let index = 0; index < cells.length; index++) { + const cell = cells[index]; + varinatOffset = computeVarinatOffset(cell, line, varinatOffset); + assert.equal(varinatOffset, result[index]); + } + + // should line 3 + // ===___===__,_===___===_,__===___===___===___==,=___===___===___===___, + line = 3; + varinatOffset = 0; + cells = [cellWidth, cellWidth, doubleCellWidth, doubleCellWidth]; + result = [5, 4, 2, 0]; + for (let index = 0; index < cells.length; index++) { + const cell = cells[index]; + varinatOffset = computeVarinatOffset(cell, line, varinatOffset); + assert.equal(varinatOffset, result[index]); + } + }); +}); From 6ace4162242b9303f2d67a3bc71eb8cea205860b Mon Sep 17 00:00:00 2001 From: tisilent Date: Thu, 24 Aug 2023 15:55:24 +0800 Subject: [PATCH 11/19] Add license header --- src/browser/renderer/shared/RendererUtils.test.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/browser/renderer/shared/RendererUtils.test.ts b/src/browser/renderer/shared/RendererUtils.test.ts index e933af52..b1e90bca 100644 --- a/src/browser/renderer/shared/RendererUtils.test.ts +++ b/src/browser/renderer/shared/RendererUtils.test.ts @@ -1,3 +1,8 @@ +/** + * Copyright (c) 2023 The xterm.js authors. All rights reserved. + * @license MIT + */ + import { computeVarinatOffset } from 'browser/renderer/shared/RendererUtils'; import { assert } from 'chai'; From f4881872f7ba143fb9263f68429553cfe72ff559 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 24 Aug 2023 06:12:56 -0700 Subject: [PATCH 12/19] Remove unused private prop --- addons/xterm-addon-canvas/src/BaseRenderLayer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts index d68923e8..4ce5c068 100644 --- a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts @@ -56,10 +56,10 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer protected readonly _optionsService: IOptionsService, protected readonly _decorationService: IDecorationService, protected readonly _coreBrowserService: ICoreBrowserService, - private readonly _unicodeService: IUnicodeService + unicodeService: IUnicodeService ) { super(); - this._cellColorResolver = new CellColorResolver(this._terminal, this._selectionModel, this._decorationService, this._coreBrowserService, this._themeService, _unicodeService); + this._cellColorResolver = new CellColorResolver(this._terminal, this._selectionModel, this._decorationService, this._coreBrowserService, this._themeService, unicodeService); this._canvas = document.createElement('canvas'); this._canvas.classList.add(`xterm-${id}-layer`); this._canvas.style.zIndex = zIndex.toString(); From d15abd7f2899bfb18ceab08a43f47d54970cac21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?xie=20jialong=20=E5=8A=AA=E5=8A=9B=E9=B8=AD?= <48614781+tisilent@users.noreply.github.com> Date: Fri, 25 Aug 2023 10:33:22 +0800 Subject: [PATCH 13/19] Update src/browser/renderer/shared/CellColorResolver.ts Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com> --- src/browser/renderer/shared/CellColorResolver.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/renderer/shared/CellColorResolver.ts b/src/browser/renderer/shared/CellColorResolver.ts index 76f482d2..3066717a 100644 --- a/src/browser/renderer/shared/CellColorResolver.ts +++ b/src/browser/renderer/shared/CellColorResolver.ts @@ -152,7 +152,7 @@ export class CellColorResolver { this.result.ext &= ~ExtFlags.VARIANT_OFFSET; this.result.ext |= ($variantOffset << 29) & ExtFlags.VARIANT_OFFSET; - // Compute next varinatOffset + // Compute next variantOffset if (cell.extended.underlineStyle === UnderlineStyle.DOTTED) { if (code !== NULL_CELL_CODE) { const fontSize = this._terminal.options.fontSize; From 08f7b4c53c847a67b6baadbf9ca93b972aefbc4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?xie=20jialong=20=E5=8A=AA=E5=8A=9B=E9=B8=AD?= <48614781+tisilent@users.noreply.github.com> Date: Fri, 25 Aug 2023 10:34:31 +0800 Subject: [PATCH 14/19] Update src/browser/renderer/shared/CellColorResolver.ts Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com> --- src/browser/renderer/shared/CellColorResolver.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/browser/renderer/shared/CellColorResolver.ts b/src/browser/renderer/shared/CellColorResolver.ts index 3066717a..d5565f7f 100644 --- a/src/browser/renderer/shared/CellColorResolver.ts +++ b/src/browser/renderer/shared/CellColorResolver.ts @@ -156,8 +156,7 @@ export class CellColorResolver { if (cell.extended.underlineStyle === UnderlineStyle.DOTTED) { if (code !== NULL_CELL_CODE) { const fontSize = this._terminal.options.fontSize; - const drp = this._coreBrowserService.dpr; - const lineWidth = Math.max(1, Math.floor(fontSize! * drp / 15)); + const lineWidth = Math.max(1, Math.floor(fontSize! * this._coreBrowserService.dpr / 15)); let chWidth: number; if (typeof code === 'number') { chWidth = this._unicodeService.wcwidth(code); From bec1642123fa8e5a112e7824a7bacfbc7043448b Mon Sep 17 00:00:00 2001 From: tisilent Date: Fri, 25 Aug 2023 14:17:36 +0800 Subject: [PATCH 15/19] Modify variantOffset calculation logic --- .../xterm-addon-canvas/src/BaseRenderLayer.ts | 7 ++-- addons/xterm-addon-canvas/src/CanvasAddon.ts | 5 ++- .../xterm-addon-canvas/src/CanvasRenderer.ts | 13 ++++--- .../src/CursorRenderLayer.ts | 7 ++-- .../xterm-addon-canvas/src/LinkRenderLayer.ts | 7 ++-- .../src/SelectionRenderLayer.ts | 7 ++-- .../xterm-addon-canvas/src/TextRenderLayer.ts | 7 ++-- addons/xterm-addon-webgl/src/WebglAddon.ts | 4 +-- addons/xterm-addon-webgl/src/WebglRenderer.ts | 5 ++- .../renderer/shared/CellColorResolver.ts | 36 ++++--------------- .../renderer/shared/RendererUtils.test.ts | 10 +++--- src/browser/renderer/shared/RendererUtils.ts | 2 +- src/browser/renderer/shared/TextureAtlas.ts | 28 ++++++--------- src/common/buffer/AttributeData.ts | 2 +- 14 files changed, 51 insertions(+), 89 deletions(-) diff --git a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts index 4ce5c068..6c6694f5 100644 --- a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts @@ -13,7 +13,7 @@ import { ICoreBrowserService, IThemeService } from 'browser/services/Services'; import { ReadonlyColorSet } from 'browser/Types'; import { CellData } from 'common/buffer/CellData'; import { WHITESPACE_CELL_CODE } from 'common/buffer/Constants'; -import { IBufferService, IDecorationService, IOptionsService, IUnicodeService } from 'common/services/Services'; +import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services'; import { ICellData, IDisposable } from 'common/Types'; import { Terminal } from 'xterm'; import { IRenderLayer } from './Types'; @@ -55,11 +55,10 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer protected readonly _bufferService: IBufferService, protected readonly _optionsService: IOptionsService, protected readonly _decorationService: IDecorationService, - protected readonly _coreBrowserService: ICoreBrowserService, - unicodeService: IUnicodeService + protected readonly _coreBrowserService: ICoreBrowserService ) { super(); - this._cellColorResolver = new CellColorResolver(this._terminal, this._selectionModel, this._decorationService, this._coreBrowserService, this._themeService, unicodeService); + this._cellColorResolver = new CellColorResolver(this._terminal, this._optionsService, this._selectionModel, this._decorationService, this._coreBrowserService, this._themeService); this._canvas = document.createElement('canvas'); this._canvas.classList.add(`xterm-${id}-layer`); this._canvas.style.zIndex = zIndex.toString(); diff --git a/addons/xterm-addon-canvas/src/CanvasAddon.ts b/addons/xterm-addon-canvas/src/CanvasAddon.ts index 3b3a2a44..d2da19f0 100644 --- a/addons/xterm-addon-canvas/src/CanvasAddon.ts +++ b/addons/xterm-addon-canvas/src/CanvasAddon.ts @@ -8,7 +8,7 @@ import { ITerminal } from 'browser/Types'; import { EventEmitter, forwardEvent } from 'common/EventEmitter'; import { Disposable, toDisposable } from 'common/Lifecycle'; import { setTraceLogger } from 'common/services/LogService'; -import { IBufferService, IDecorationService, ILogService, IUnicodeService } from 'common/services/Services'; +import { IBufferService, IDecorationService, ILogService } from 'common/services/Services'; import { ITerminalAddon, Terminal } from 'xterm'; import { CanvasRenderer } from './CanvasRenderer'; @@ -47,13 +47,12 @@ export class CanvasAddon extends Disposable implements ITerminalAddon { const decorationService: IDecorationService = unsafeCore._decorationService; const logService: ILogService = unsafeCore._logService; const themeService: IThemeService = unsafeCore._themeService; - const unicodeService: IUnicodeService = unsafeCore.unicodeService; // Set trace logger just in case it hasn't been yet which could happen when the addon is // bundled separately to the core module setTraceLogger(logService); - this._renderer = new CanvasRenderer(terminal, screenElement, linkifier, bufferService, charSizeService, optionsService, characterJoinerService, coreService, coreBrowserService, decorationService, themeService, unicodeService); + this._renderer = new CanvasRenderer(terminal, screenElement, linkifier, bufferService, charSizeService, optionsService, characterJoinerService, coreService, coreBrowserService, decorationService, themeService); this.register(forwardEvent(this._renderer.onChangeTextureAtlas, this._onChangeTextureAtlas)); this.register(forwardEvent(this._renderer.onAddTextureAtlasCanvas, this._onAddTextureAtlasCanvas)); diff --git a/addons/xterm-addon-canvas/src/CanvasRenderer.ts b/addons/xterm-addon-canvas/src/CanvasRenderer.ts index e2f0b459..cd05f921 100644 --- a/addons/xterm-addon-canvas/src/CanvasRenderer.ts +++ b/addons/xterm-addon-canvas/src/CanvasRenderer.ts @@ -11,7 +11,7 @@ import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/rende import { ICharSizeService, ICharacterJoinerService, ICoreBrowserService, IThemeService } from 'browser/services/Services'; import { EventEmitter, forwardEvent } from 'common/EventEmitter'; import { Disposable, toDisposable } from 'common/Lifecycle'; -import { IBufferService, ICoreService, IDecorationService, IOptionsService, IUnicodeService } from 'common/services/Services'; +import { IBufferService, ICoreService, IDecorationService, IOptionsService } from 'common/services/Services'; import { Terminal } from 'xterm'; import { CursorRenderLayer } from './CursorRenderLayer'; import { LinkRenderLayer } from './LinkRenderLayer'; @@ -43,16 +43,15 @@ export class CanvasRenderer extends Disposable implements IRenderer { coreService: ICoreService, private readonly _coreBrowserService: ICoreBrowserService, decorationService: IDecorationService, - private readonly _themeService: IThemeService, - unicodeService: IUnicodeService + private readonly _themeService: IThemeService ) { super(); const allowTransparency = this._optionsService.rawOptions.allowTransparency; this._renderLayers = [ - new TextRenderLayer(this._terminal, this._screenElement, 0, allowTransparency, this._bufferService, this._optionsService, characterJoinerService, decorationService, this._coreBrowserService, _themeService, unicodeService), - new SelectionRenderLayer(this._terminal, this._screenElement, 1, this._bufferService, this._coreBrowserService, decorationService, this._optionsService, _themeService, unicodeService), - new LinkRenderLayer(this._terminal, this._screenElement, 2, linkifier2, this._bufferService, this._optionsService, decorationService, this._coreBrowserService, _themeService, unicodeService), - new CursorRenderLayer(this._terminal, this._screenElement, 3, this._onRequestRedraw, this._bufferService, this._optionsService, coreService, this._coreBrowserService, decorationService, _themeService, unicodeService) + new TextRenderLayer(this._terminal, this._screenElement, 0, allowTransparency, this._bufferService, this._optionsService, characterJoinerService, decorationService, this._coreBrowserService, _themeService), + new SelectionRenderLayer(this._terminal, this._screenElement, 1, this._bufferService, this._coreBrowserService, decorationService, this._optionsService, _themeService), + new LinkRenderLayer(this._terminal, this._screenElement, 2, linkifier2, this._bufferService, this._optionsService, decorationService, this._coreBrowserService, _themeService), + new CursorRenderLayer(this._terminal, this._screenElement, 3, this._onRequestRedraw, this._bufferService, this._optionsService, coreService, this._coreBrowserService, decorationService, _themeService) ]; for (const layer of this._renderLayers) { forwardEvent(layer.onAddTextureAtlasCanvas, this._onAddTextureAtlasCanvas); diff --git a/addons/xterm-addon-canvas/src/CursorRenderLayer.ts b/addons/xterm-addon-canvas/src/CursorRenderLayer.ts index 42383662..9f4ac896 100644 --- a/addons/xterm-addon-canvas/src/CursorRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/CursorRenderLayer.ts @@ -11,7 +11,7 @@ import { toDisposable } from 'common/Lifecycle'; import { isFirefox } from 'common/Platform'; import { ICellData } from 'common/Types'; import { CellData } from 'common/buffer/CellData'; -import { IBufferService, ICoreService, IDecorationService, IOptionsService, IUnicodeService } from 'common/services/Services'; +import { IBufferService, ICoreService, IDecorationService, IOptionsService } from 'common/services/Services'; import { Terminal } from 'xterm'; import { BaseRenderLayer } from './BaseRenderLayer'; @@ -39,10 +39,9 @@ export class CursorRenderLayer extends BaseRenderLayer { private readonly _coreService: ICoreService, coreBrowserService: ICoreBrowserService, decorationService: IDecorationService, - themeService: IThemeService, - unicodeService: IUnicodeService + themeService: IThemeService ) { - super(terminal, container, 'cursor', zIndex, true, themeService, bufferService, optionsService, decorationService, coreBrowserService, unicodeService); + super(terminal, container, 'cursor', zIndex, true, themeService, bufferService, optionsService, decorationService, coreBrowserService); this._state = { x: 0, y: 0, diff --git a/addons/xterm-addon-canvas/src/LinkRenderLayer.ts b/addons/xterm-addon-canvas/src/LinkRenderLayer.ts index 53040b85..882f7d8c 100644 --- a/addons/xterm-addon-canvas/src/LinkRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/LinkRenderLayer.ts @@ -8,7 +8,7 @@ import { is256Color } from 'browser/renderer/shared/CharAtlasUtils'; import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/shared/Constants'; import { IRenderDimensions } from 'browser/renderer/shared/Types'; import { ICoreBrowserService, IThemeService } from 'browser/services/Services'; -import { IBufferService, IDecorationService, IOptionsService, IUnicodeService } from 'common/services/Services'; +import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services'; import { Terminal } from 'xterm'; import { BaseRenderLayer } from './BaseRenderLayer'; @@ -24,10 +24,9 @@ export class LinkRenderLayer extends BaseRenderLayer { optionsService: IOptionsService, decorationService: IDecorationService, coreBrowserService: ICoreBrowserService, - themeService: IThemeService, - unicodeService: IUnicodeService + themeService: IThemeService ) { - super(terminal, container, 'link', zIndex, true, themeService, bufferService, optionsService, decorationService, coreBrowserService, unicodeService); + super(terminal, container, 'link', zIndex, true, themeService, bufferService, optionsService, decorationService, coreBrowserService); this.register(linkifier2.onShowLinkUnderline(e => this._handleShowLinkUnderline(e))); this.register(linkifier2.onHideLinkUnderline(e => this._handleHideLinkUnderline(e))); diff --git a/addons/xterm-addon-canvas/src/SelectionRenderLayer.ts b/addons/xterm-addon-canvas/src/SelectionRenderLayer.ts index 0e397441..10950868 100644 --- a/addons/xterm-addon-canvas/src/SelectionRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/SelectionRenderLayer.ts @@ -5,7 +5,7 @@ import { IRenderDimensions } from 'browser/renderer/shared/Types'; import { BaseRenderLayer } from './BaseRenderLayer'; -import { IBufferService, IDecorationService, IOptionsService, IUnicodeService } from 'common/services/Services'; +import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services'; import { ICoreBrowserService, IThemeService } from 'browser/services/Services'; import { Terminal } from 'xterm'; @@ -27,10 +27,9 @@ export class SelectionRenderLayer extends BaseRenderLayer { coreBrowserService: ICoreBrowserService, decorationService: IDecorationService, optionsService: IOptionsService, - themeService: IThemeService, - unicodeService: IUnicodeService + themeService: IThemeService ) { - super(terminal, container, 'selection', zIndex, true, themeService, bufferService, optionsService, decorationService, coreBrowserService, unicodeService); + super(terminal, container, 'selection', zIndex, true, themeService, bufferService, optionsService, decorationService, coreBrowserService); this._clearState(); } diff --git a/addons/xterm-addon-canvas/src/TextRenderLayer.ts b/addons/xterm-addon-canvas/src/TextRenderLayer.ts index 600105ab..46d052f3 100644 --- a/addons/xterm-addon-canvas/src/TextRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/TextRenderLayer.ts @@ -10,7 +10,7 @@ import { CharData, ICellData } from 'common/Types'; import { AttributeData } from 'common/buffer/AttributeData'; import { CellData } from 'common/buffer/CellData'; import { Content, NULL_CELL_CODE } from 'common/buffer/Constants'; -import { IBufferService, IDecorationService, IOptionsService, IUnicodeService } from 'common/services/Services'; +import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services'; import { Terminal } from 'xterm'; import { BaseRenderLayer } from './BaseRenderLayer'; import { GridCache } from './GridCache'; @@ -39,10 +39,9 @@ export class TextRenderLayer extends BaseRenderLayer { private readonly _characterJoinerService: ICharacterJoinerService, decorationService: IDecorationService, coreBrowserService: ICoreBrowserService, - themeService: IThemeService, - unicodeService: IUnicodeService + themeService: IThemeService ) { - super(terminal, container, 'text', zIndex, alpha, themeService, bufferService, optionsService, decorationService, coreBrowserService, unicodeService); + super(terminal, container, 'text', zIndex, alpha, themeService, bufferService, optionsService, decorationService, coreBrowserService); this._state = new GridCache(); this.register(optionsService.onSpecificOptionChange('allowTransparency', value => this._setTransparency(value))); } diff --git a/addons/xterm-addon-webgl/src/WebglAddon.ts b/addons/xterm-addon-webgl/src/WebglAddon.ts index 9e961cc6..3e091f0b 100644 --- a/addons/xterm-addon-webgl/src/WebglAddon.ts +++ b/addons/xterm-addon-webgl/src/WebglAddon.ts @@ -8,7 +8,7 @@ import { ITerminal } from 'browser/Types'; import { EventEmitter, forwardEvent } from 'common/EventEmitter'; import { Disposable, toDisposable } from 'common/Lifecycle'; import { getSafariVersion, isSafari } from 'common/Platform'; -import { ICoreService, IDecorationService, ILogService, IOptionsService, IUnicodeService } from 'common/services/Services'; +import { ICoreService, IDecorationService, ILogService, IOptionsService } from 'common/services/Services'; import { ITerminalAddon, Terminal } from 'xterm'; import { WebglRenderer } from './WebglRenderer'; import { setTraceLogger } from 'common/services/LogService'; @@ -54,7 +54,6 @@ export class WebglAddon extends Disposable implements ITerminalAddon { const decorationService: IDecorationService = unsafeCore._decorationService; const logService: ILogService = unsafeCore._logService; const themeService: IThemeService = unsafeCore._themeService; - const unicodeService: IUnicodeService = unsafeCore.unicodeService; // Set trace logger just in case it hasn't been yet which could happen when the addon is // bundled separately to the core module @@ -69,7 +68,6 @@ export class WebglAddon extends Disposable implements ITerminalAddon { decorationService, optionsService, themeService, - unicodeService, this._preserveDrawingBuffer )); this.register(forwardEvent(this._renderer.onContextLoss, this._onContextLoss)); diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 6b7859eb..5f4bf7ff 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -19,7 +19,7 @@ import { AttributeData } from 'common/buffer/AttributeData'; import { CellData } from 'common/buffer/CellData'; import { Attributes, Content, NULL_CELL_CHAR, NULL_CELL_CODE } from 'common/buffer/Constants'; import { traceCall } from 'common/services/LogService'; -import { ICoreService, IDecorationService, IOptionsService, IUnicodeService } from 'common/services/Services'; +import { ICoreService, IDecorationService, IOptionsService } from 'common/services/Services'; import { IDisposable, Terminal } from 'xterm'; import { GlyphRenderer } from './GlyphRenderer'; import { RectangleRenderer } from './RectangleRenderer'; @@ -70,14 +70,13 @@ export class WebglRenderer extends Disposable implements IRenderer { private readonly _decorationService: IDecorationService, private readonly _optionsService: IOptionsService, private readonly _themeService: IThemeService, - private readonly _unicodeService: IUnicodeService, preserveDrawingBuffer?: boolean ) { super(); this.register(this._themeService.onChangeColors(() => this._handleColorChange())); - this._cellColorResolver = new CellColorResolver(this._terminal, this._model.selection, this._decorationService, this._coreBrowserService, this._themeService, this._unicodeService); + this._cellColorResolver = new CellColorResolver(this._terminal, this._optionsService, this._model.selection, this._decorationService, this._coreBrowserService, this._themeService); this._core = (this._terminal as any)._core; diff --git a/src/browser/renderer/shared/CellColorResolver.ts b/src/browser/renderer/shared/CellColorResolver.ts index d5565f7f..fa0428b1 100644 --- a/src/browser/renderer/shared/CellColorResolver.ts +++ b/src/browser/renderer/shared/CellColorResolver.ts @@ -1,9 +1,8 @@ -import { computeVarinatOffset } from 'browser/renderer/shared/RendererUtils'; import { ISelectionRenderModel } from 'browser/renderer/shared/Types'; import { ICoreBrowserService, IThemeService } from 'browser/services/Services'; import { ReadonlyColorSet } from 'browser/Types'; import { Attributes, BgFlags, ExtFlags, FgFlags, NULL_CELL_CODE, UnderlineStyle } from 'common/buffer/Constants'; -import { IDecorationService, IUnicodeService } from 'common/services/Services'; +import { IDecorationService, IOptionsService } from 'common/services/Services'; import { ICellData } from 'common/Types'; import { Terminal } from 'xterm'; @@ -14,7 +13,6 @@ let $hasFg = false; let $hasBg = false; let $isSelected = false; let $colors: ReadonlyColorSet | undefined; -let $y = -1; let $variantOffset = 0; export class CellColorResolver { @@ -30,11 +28,11 @@ export class CellColorResolver { constructor( private readonly _terminal: Terminal, + private readonly _optionService: IOptionsService, private readonly _selectionRenderModel: ISelectionRenderModel, private readonly _decorationService: IDecorationService, private readonly _coreBrowserService: ICoreBrowserService, - private readonly _themeService: IThemeService, - private readonly _unicodeService: IUnicodeService + private readonly _themeService: IThemeService ) { } @@ -56,15 +54,12 @@ export class CellColorResolver { $hasFg = false; $isSelected = false; $colors = this._themeService.colors; - - if ($y !== y) { - $variantOffset = 0; - } - $y = y; + $variantOffset = 0; const code = cell.getCode(); - if (code === NULL_CELL_CODE && cell.extended.underlineStyle !== UnderlineStyle.DOTTED) { - $variantOffset = 0; + if (code !== NULL_CELL_CODE && cell.extended.underlineStyle === UnderlineStyle.DOTTED) { + const lineWidth = Math.max(1, Math.floor(this._optionService.rawOptions.fontSize * this._coreBrowserService.dpr / 15)); + $variantOffset = x * deviceCellWidth % (Math.round(lineWidth) * 2); } // Apply decorations on the bottom layer @@ -151,22 +146,5 @@ export class CellColorResolver { // Reset overrides variantOffset this.result.ext &= ~ExtFlags.VARIANT_OFFSET; this.result.ext |= ($variantOffset << 29) & ExtFlags.VARIANT_OFFSET; - - // Compute next variantOffset - if (cell.extended.underlineStyle === UnderlineStyle.DOTTED) { - if (code !== NULL_CELL_CODE) { - const fontSize = this._terminal.options.fontSize; - const lineWidth = Math.max(1, Math.floor(fontSize! * this._coreBrowserService.dpr / 15)); - let chWidth: number; - if (typeof code === 'number') { - chWidth = this._unicodeService.wcwidth(code); - } else { - chWidth = this._unicodeService.getStringCellWidth(code); - } - $variantOffset = computeVarinatOffset(deviceCellWidth * chWidth, lineWidth, $variantOffset); - } - } else { - $variantOffset = 0; - } } } diff --git a/src/browser/renderer/shared/RendererUtils.test.ts b/src/browser/renderer/shared/RendererUtils.test.ts index b1e90bca..e7e49c1d 100644 --- a/src/browser/renderer/shared/RendererUtils.test.ts +++ b/src/browser/renderer/shared/RendererUtils.test.ts @@ -3,11 +3,11 @@ * @license MIT */ -import { computeVarinatOffset } from 'browser/renderer/shared/RendererUtils'; +import { computeNextVarinatOffset } from 'browser/renderer/shared/RendererUtils'; import { assert } from 'chai'; describe('RendererUtils', () => { - it('computeVarinatOffset', () => { + it('computeNextVarinatOffset', () => { const cellWidth = 11; const doubleCellWidth = 22; let line = 1; @@ -19,7 +19,7 @@ describe('RendererUtils', () => { let result = [1, 0, 0, 0]; for (let index = 0; index < cells.length; index++) { const cell = cells[index]; - varinatOffset = computeVarinatOffset(cell, line, varinatOffset); + varinatOffset = computeNextVarinatOffset(cell, line, varinatOffset); assert.equal(varinatOffset, result[index]); } @@ -31,7 +31,7 @@ describe('RendererUtils', () => { result = [3, 2, 0 ,2]; for (let index = 0; index < cells.length; index++) { const cell = cells[index]; - varinatOffset = computeVarinatOffset(cell, line, varinatOffset); + varinatOffset = computeNextVarinatOffset(cell, line, varinatOffset); assert.equal(varinatOffset, result[index]); } @@ -43,7 +43,7 @@ describe('RendererUtils', () => { result = [5, 4, 2, 0]; for (let index = 0; index < cells.length; index++) { const cell = cells[index]; - varinatOffset = computeVarinatOffset(cell, line, varinatOffset); + varinatOffset = computeNextVarinatOffset(cell, line, varinatOffset); assert.equal(varinatOffset, result[index]); } }); diff --git a/src/browser/renderer/shared/RendererUtils.ts b/src/browser/renderer/shared/RendererUtils.ts index 73009f6a..b60fd011 100644 --- a/src/browser/renderer/shared/RendererUtils.ts +++ b/src/browser/renderer/shared/RendererUtils.ts @@ -57,6 +57,6 @@ function createDimension(): IDimensions { }; } -export function computeVarinatOffset(cellWidth: number, lineWidth: number, currentOffset: number = 0): number { +export function computeNextVarinatOffset(cellWidth: number, lineWidth: number, currentOffset: number = 0): number { return (cellWidth - (Math.round(lineWidth) * 2 - currentOffset)) % (Math.round(lineWidth) * 2); } diff --git a/src/browser/renderer/shared/TextureAtlas.ts b/src/browser/renderer/shared/TextureAtlas.ts index b2d23a02..f72fafdb 100644 --- a/src/browser/renderer/shared/TextureAtlas.ts +++ b/src/browser/renderer/shared/TextureAtlas.ts @@ -6,7 +6,7 @@ import { IColorContrastCache } from 'browser/Types'; import { DIM_OPACITY, TEXT_BASELINE } from 'browser/renderer/shared/Constants'; import { tryDrawCustomChar } from 'browser/renderer/shared/CustomGlyphs'; -import { computeVarinatOffset, excludeFromContrastRatioDemands, isPowerlineGlyph, isRestrictedPowerlineGlyph, throwIfFalsy } from 'browser/renderer/shared/RendererUtils'; +import { computeNextVarinatOffset, excludeFromContrastRatioDemands, isPowerlineGlyph, isRestrictedPowerlineGlyph, throwIfFalsy } from 'browser/renderer/shared/RendererUtils'; import { IBoundingBox, ICharAtlasConfig, IRasterizedGlyph, ITextureAtlas } from 'browser/renderer/shared/Types'; import { NULL_COLOR, color, rgba } from 'common/Color'; import { EventEmitter } from 'common/EventEmitter'; @@ -599,26 +599,20 @@ export class TextureAtlas implements ITextureAtlas { case UnderlineStyle.DOTTED: const offsetWidth = nextOffset === 0 ? 0 : (nextOffset >= lineWidth ? lineWidth * 2 - nextOffset : lineWidth - nextOffset); - if (offsetWidth === 0) { + // a line and a gap. + const isLineStart = nextOffset >= lineWidth ? false : true; + if (isLineStart === false || offsetWidth === 0) { this._tmpCtx.setLineDash([Math.round(lineWidth), Math.round(lineWidth)]); - this._tmpCtx.moveTo(xChLeft, yTop); + this._tmpCtx.moveTo(xChLeft + offsetWidth, yTop); this._tmpCtx.lineTo(xChRight, yTop); } else { - // a line and a gap. - const isLineStart = nextOffset >= lineWidth ? false : true; - if (isLineStart === false) { - this._tmpCtx.setLineDash([Math.round(lineWidth), Math.round(lineWidth)]); - this._tmpCtx.moveTo(xChLeft + offsetWidth, yTop); - this._tmpCtx.lineTo(xChRight, yTop); - } else { - this._tmpCtx.setLineDash([Math.round(lineWidth), Math.round(lineWidth)]); - this._tmpCtx.moveTo(xChLeft, yTop); - this._tmpCtx.lineTo(xChLeft + offsetWidth, yTop); - this._tmpCtx.moveTo(xChLeft + offsetWidth + lineWidth, yTop); - this._tmpCtx.lineTo(xChRight, yTop); - } + this._tmpCtx.setLineDash([Math.round(lineWidth), Math.round(lineWidth)]); + this._tmpCtx.moveTo(xChLeft, yTop); + this._tmpCtx.lineTo(xChLeft + offsetWidth, yTop); + this._tmpCtx.moveTo(xChLeft + offsetWidth + lineWidth, yTop); + this._tmpCtx.lineTo(xChRight, yTop); } - nextOffset = computeVarinatOffset(xChRight - xChLeft, lineWidth, nextOffset); + nextOffset = computeNextVarinatOffset(xChRight - xChLeft, lineWidth, nextOffset); break; case UnderlineStyle.DASHED: this._tmpCtx.setLineDash([this._config.devicePixelRatio * 4, this._config.devicePixelRatio * 3]); diff --git a/src/common/buffer/AttributeData.ts b/src/common/buffer/AttributeData.ts index 91c5de61..acc8a8c2 100644 --- a/src/common/buffer/AttributeData.ts +++ b/src/common/buffer/AttributeData.ts @@ -180,7 +180,7 @@ export class ExtendedAttrs implements IExtendedAttrs { public get underlineVarinatOffset(): number { const val = (this._ext & ExtFlags.VARIANT_OFFSET) >> 29; if (val < 0) { - return val ^ 4294967288; + return val ^ 0xFFFFFFF8; } return val; } From 810e62665fa97d49d346f5c393b4243fe049e2db Mon Sep 17 00:00:00 2001 From: tisilent Date: Fri, 25 Aug 2023 14:30:34 +0800 Subject: [PATCH 16/19] Clean --- addons/xterm-addon-canvas/src/CanvasAddon.ts | 1 - addons/xterm-addon-webgl/src/WebglRenderer.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/addons/xterm-addon-canvas/src/CanvasAddon.ts b/addons/xterm-addon-canvas/src/CanvasAddon.ts index d2da19f0..91438a4c 100644 --- a/addons/xterm-addon-canvas/src/CanvasAddon.ts +++ b/addons/xterm-addon-canvas/src/CanvasAddon.ts @@ -53,7 +53,6 @@ export class CanvasAddon extends Disposable implements ITerminalAddon { setTraceLogger(logService); this._renderer = new CanvasRenderer(terminal, screenElement, linkifier, bufferService, charSizeService, optionsService, characterJoinerService, coreService, coreBrowserService, decorationService, themeService); - this.register(forwardEvent(this._renderer.onChangeTextureAtlas, this._onChangeTextureAtlas)); this.register(forwardEvent(this._renderer.onAddTextureAtlasCanvas, this._onAddTextureAtlasCanvas)); renderService.setRenderer(this._renderer); diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 5f4bf7ff..516a4c27 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -446,7 +446,6 @@ export class WebglRenderer extends Disposable implements IRenderer { chars = cell.getChars(); code = cell.getCode(); - i = ((y * terminal.cols) + x) * RENDER_MODEL_INDICIES_PER_CELL; // Load colors/resolve overrides into work colors From 42d6c2bd6b34495a36c99cef747c7c24c8dfba37 Mon Sep 17 00:00:00 2001 From: tisilent Date: Fri, 25 Aug 2023 14:37:03 +0800 Subject: [PATCH 17/19] Use hex --- addons/xterm-addon-image/src/ImageStorage.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/xterm-addon-image/src/ImageStorage.ts b/addons/xterm-addon-image/src/ImageStorage.ts index 86d48a6d..2fb12c14 100644 --- a/addons/xterm-addon-image/src/ImageStorage.ts +++ b/addons/xterm-addon-image/src/ImageStorage.ts @@ -56,7 +56,7 @@ class ExtendedAttrsImage implements IExtendedAttrsImage { public get underlineVarinatOffset(): number { const val = (this._ext & ExtFlags.VARIANT_OFFSET) >> 29; if (val < 0) { - return val ^ 4294967288; + return val ^ 0xFFFFFFF8; } return val; } From 6214f4c2e67cb588fb2a1104ed06d0cb3000dfee Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 2 Nov 2023 10:43:56 -0700 Subject: [PATCH 18/19] Varinat -> Variant --- addons/addon-image/src/ImageStorage.ts | 4 ++-- .../renderer/shared/RendererUtils.test.ts | 22 +++++++++---------- src/browser/renderer/shared/RendererUtils.ts | 2 +- src/browser/renderer/shared/TextureAtlas.ts | 6 ++--- src/common/Types.d.ts | 4 ++-- src/common/buffer/AttributeData.ts | 8 +++---- src/common/buffer/BufferLine.test.ts | 8 +++---- 7 files changed, 27 insertions(+), 27 deletions(-) diff --git a/addons/addon-image/src/ImageStorage.ts b/addons/addon-image/src/ImageStorage.ts index 39de0959..f9b6eef6 100644 --- a/addons/addon-image/src/ImageStorage.ts +++ b/addons/addon-image/src/ImageStorage.ts @@ -53,14 +53,14 @@ class ExtendedAttrsImage implements IExtendedAttrsImage { this._ext |= value & (Attributes.CM_MASK | Attributes.RGB_MASK); } - public get underlineVarinatOffset(): number { + public get underlineVariantOffset(): number { const val = (this._ext & ExtFlags.VARIANT_OFFSET) >> 29; if (val < 0) { return val ^ 0xFFFFFFF8; } return val; } - public set underlineVarinatOffset(value: number) { + public set underlineVariantOffset(value: number) { this._ext &= ~ExtFlags.VARIANT_OFFSET; this._ext |= (value << 29) & ExtFlags.VARIANT_OFFSET; } diff --git a/src/browser/renderer/shared/RendererUtils.test.ts b/src/browser/renderer/shared/RendererUtils.test.ts index e7e49c1d..a050e8a9 100644 --- a/src/browser/renderer/shared/RendererUtils.test.ts +++ b/src/browser/renderer/shared/RendererUtils.test.ts @@ -3,15 +3,15 @@ * @license MIT */ -import { computeNextVarinatOffset } from 'browser/renderer/shared/RendererUtils'; +import { computeNextVariantOffset } from 'browser/renderer/shared/RendererUtils'; import { assert } from 'chai'; describe('RendererUtils', () => { - it('computeNextVarinatOffset', () => { + it('computeNextVariantOffset', () => { const cellWidth = 11; const doubleCellWidth = 22; let line = 1; - let varinatOffset = 0; + let variantOffset = 0; // should line 1 // =,_,=_,=_, @@ -19,32 +19,32 @@ describe('RendererUtils', () => { let result = [1, 0, 0, 0]; for (let index = 0; index < cells.length; index++) { const cell = cells[index]; - varinatOffset = computeNextVarinatOffset(cell, line, varinatOffset); - assert.equal(varinatOffset, result[index]); + variantOffset = computeNextVariantOffset(cell, line, variantOffset); + assert.equal(variantOffset, result[index]); } // should line 2 // ==__==__==_,_==__==__==,__==__==__==__==__==__,==__==__==__==__==__==, line = 2; - varinatOffset = 0; + variantOffset = 0; cells = [cellWidth, cellWidth, doubleCellWidth, doubleCellWidth]; result = [3, 2, 0 ,2]; for (let index = 0; index < cells.length; index++) { const cell = cells[index]; - varinatOffset = computeNextVarinatOffset(cell, line, varinatOffset); - assert.equal(varinatOffset, result[index]); + variantOffset = computeNextVariantOffset(cell, line, variantOffset); + assert.equal(variantOffset, result[index]); } // should line 3 // ===___===__,_===___===_,__===___===___===___==,=___===___===___===___, line = 3; - varinatOffset = 0; + variantOffset = 0; cells = [cellWidth, cellWidth, doubleCellWidth, doubleCellWidth]; result = [5, 4, 2, 0]; for (let index = 0; index < cells.length; index++) { const cell = cells[index]; - varinatOffset = computeNextVarinatOffset(cell, line, varinatOffset); - assert.equal(varinatOffset, result[index]); + variantOffset = computeNextVariantOffset(cell, line, variantOffset); + assert.equal(variantOffset, result[index]); } }); }); diff --git a/src/browser/renderer/shared/RendererUtils.ts b/src/browser/renderer/shared/RendererUtils.ts index b60fd011..59b87b0e 100644 --- a/src/browser/renderer/shared/RendererUtils.ts +++ b/src/browser/renderer/shared/RendererUtils.ts @@ -57,6 +57,6 @@ function createDimension(): IDimensions { }; } -export function computeNextVarinatOffset(cellWidth: number, lineWidth: number, currentOffset: number = 0): number { +export function computeNextVariantOffset(cellWidth: number, lineWidth: number, currentOffset: number = 0): number { return (cellWidth - (Math.round(lineWidth) * 2 - currentOffset)) % (Math.round(lineWidth) * 2); } diff --git a/src/browser/renderer/shared/TextureAtlas.ts b/src/browser/renderer/shared/TextureAtlas.ts index 66501255..d7f65d00 100644 --- a/src/browser/renderer/shared/TextureAtlas.ts +++ b/src/browser/renderer/shared/TextureAtlas.ts @@ -6,7 +6,7 @@ import { IColorContrastCache } from 'browser/Types'; import { DIM_OPACITY, TEXT_BASELINE } from 'browser/renderer/shared/Constants'; import { tryDrawCustomChar } from 'browser/renderer/shared/CustomGlyphs'; -import { computeNextVarinatOffset, excludeFromContrastRatioDemands, isPowerlineGlyph, isRestrictedPowerlineGlyph, throwIfFalsy } from 'browser/renderer/shared/RendererUtils'; +import { computeNextVariantOffset, excludeFromContrastRatioDemands, isPowerlineGlyph, isRestrictedPowerlineGlyph, throwIfFalsy } from 'browser/renderer/shared/RendererUtils'; import { IBoundingBox, ICharAtlasConfig, IRasterizedGlyph, ITextureAtlas } from 'browser/renderer/shared/Types'; import { NULL_COLOR, color, rgba } from 'common/Color'; import { EventEmitter } from 'common/EventEmitter'; @@ -545,7 +545,7 @@ export class TextureAtlas implements ITextureAtlas { const yTop = Math.ceil(padding + this._config.deviceCharHeight) - yOffset - (restrictToCellHeight ? lineWidth * 2 : 0); const yMid = yTop + lineWidth; const yBot = yTop + lineWidth * 2; - let nextOffset = this._workAttributeData.getUnderlineVarinatOffset(); + let nextOffset = this._workAttributeData.getUnderlineVariantOffset(); for (let i = 0; i < chWidth; i++) { this._tmpCtx.save(); @@ -610,7 +610,7 @@ export class TextureAtlas implements ITextureAtlas { this._tmpCtx.moveTo(xChLeft + offsetWidth + lineWidth, yTop); this._tmpCtx.lineTo(xChRight, yTop); } - nextOffset = computeNextVarinatOffset(xChRight - xChLeft, lineWidth, nextOffset); + nextOffset = computeNextVariantOffset(xChRight - xChLeft, lineWidth, nextOffset); break; case UnderlineStyle.DASHED: this._tmpCtx.setLineDash([this._config.devicePixelRatio * 4, this._config.devicePixelRatio * 3]); diff --git a/src/common/Types.d.ts b/src/common/Types.d.ts index 3e6476a3..bbe1c741 100644 --- a/src/common/Types.d.ts +++ b/src/common/Types.d.ts @@ -119,7 +119,7 @@ export interface IExtendedAttrs { ext: number; underlineStyle: UnderlineStyle; underlineColor: number; - underlineVarinatOffset: number; + underlineVariantOffset: number; urlId: number; clone(): IExtendedAttrs; isEmpty(): boolean; @@ -210,7 +210,7 @@ export interface IAttributeData { isUnderlineColorPalette(): boolean; isUnderlineColorDefault(): boolean; getUnderlineStyle(): number; - getUnderlineVarinatOffset(): number; + getUnderlineVariantOffset(): number; } /** Cell data */ diff --git a/src/common/buffer/AttributeData.ts b/src/common/buffer/AttributeData.ts index acc8a8c2..6221fb81 100644 --- a/src/common/buffer/AttributeData.ts +++ b/src/common/buffer/AttributeData.ts @@ -126,8 +126,8 @@ export class AttributeData implements IAttributeData { ? (this.bg & BgFlags.HAS_EXTENDED ? this.extended.underlineStyle : UnderlineStyle.SINGLE) : UnderlineStyle.NONE; } - public getUnderlineVarinatOffset(): number { - return this.extended.underlineVarinatOffset; + public getUnderlineVariantOffset(): number { + return this.extended.underlineVariantOffset; } } @@ -177,14 +177,14 @@ export class ExtendedAttrs implements IExtendedAttrs { this._urlId = value; } - public get underlineVarinatOffset(): number { + public get underlineVariantOffset(): number { const val = (this._ext & ExtFlags.VARIANT_OFFSET) >> 29; if (val < 0) { return val ^ 0xFFFFFFF8; } return val; } - public set underlineVarinatOffset(value: number) { + public set underlineVariantOffset(value: number) { this._ext &= ~ExtFlags.VARIANT_OFFSET; this._ext |= (value << 29) & ExtFlags.VARIANT_OFFSET; } diff --git a/src/common/buffer/BufferLine.test.ts b/src/common/buffer/BufferLine.test.ts index b8684b82..8b9ec63e 100644 --- a/src/common/buffer/BufferLine.test.ts +++ b/src/common/buffer/BufferLine.test.ts @@ -119,16 +119,16 @@ describe('AttributeData', () => { attrs.fg &= ~FgFlags.UNDERLINE; assert.equal(attrs.getUnderlineStyle(), UnderlineStyle.NONE); }); - it('getUnderlineVarinatOffset', () => { + it('getUnderlineVariantOffset', () => { const attrs = new AttributeData(); // defaults to no offset - assert.equal(attrs.getUnderlineVarinatOffset(), 0); + assert.equal(attrs.getUnderlineVariantOffset(), 0); // should return 0 - 7 for (let i = 0; i < 8; ++i) { - attrs.extended.underlineVarinatOffset = i; - assert.equal(attrs.getUnderlineVarinatOffset(), i); + attrs.extended.underlineVariantOffset = i; + assert.equal(attrs.getUnderlineVariantOffset(), i); } }); }); From 2f074b87f335014aa7c639a2cf9a7703a3d1899d Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 2 Nov 2023 10:45:46 -0700 Subject: [PATCH 19/19] Explain VARIANT_OFFSET --- src/common/buffer/Constants.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/common/buffer/Constants.ts b/src/common/buffer/Constants.ts index 3bfd850a..5ce075cf 100644 --- a/src/common/buffer/Constants.ts +++ b/src/common/buffer/Constants.ts @@ -140,6 +140,9 @@ export const enum ExtFlags { /** * bit 30..32 + * + * An optional variant for the glyph, this can be used for example to offset underlines by a + * number of pixels to create a perfect pattern. */ VARIANT_OFFSET = 0xE0000000 }