From cb175e513dd7afdef70f1d2879afb915eabfdb1f Mon Sep 17 00:00:00 2001 From: tisilent Date: Mon, 21 Aug 2023 19:45:38 +0800 Subject: [PATCH] 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 {