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); + } + }); }); });