mirror of
https://github.com/wavetermdev/xterm.js.git
synced 2026-08-05 13:43:48 -07:00
Merge pull request #4703 from tisilent/underline-dotted
Use variants for underline dotted.
This commit is contained in:
@@ -58,7 +58,7 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer
|
||||
protected readonly _coreBrowserService: ICoreBrowserService
|
||||
) {
|
||||
super();
|
||||
this._cellColorResolver = new CellColorResolver(this._terminal, this._selectionModel, this._decorationService, this._coreBrowserService, this._themeService);
|
||||
this._cellColorResolver = new CellColorResolver(this._terminal, this._optionsService, this._selectionModel, this._decorationService, this._coreBrowserService, this._themeService);
|
||||
this._canvas = this._coreBrowserService.mainDocument.createElement('canvas');
|
||||
this._canvas.classList.add(`xterm-${id}-layer`);
|
||||
this._canvas.style.zIndex = zIndex.toString();
|
||||
@@ -365,7 +365,7 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer
|
||||
*/
|
||||
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.resolve(cell, x, this._bufferService.buffer.ydisp + y, this._deviceCellWidth);
|
||||
|
||||
if (!this._charAtlas) {
|
||||
return;
|
||||
|
||||
@@ -53,6 +53,18 @@ class ExtendedAttrsImage implements IExtendedAttrsImage {
|
||||
this._ext |= value & (Attributes.CM_MASK | Attributes.RGB_MASK);
|
||||
}
|
||||
|
||||
public get underlineVariantOffset(): number {
|
||||
const val = (this._ext & ExtFlags.VARIANT_OFFSET) >> 29;
|
||||
if (val < 0) {
|
||||
return val ^ 0xFFFFFFF8;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
public set underlineVariantOffset(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;
|
||||
|
||||
@@ -75,7 +75,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._optionsService, this._model.selection, this._decorationService, this._coreBrowserService, this._themeService);
|
||||
|
||||
this._core = (this._terminal as any)._core;
|
||||
|
||||
@@ -442,7 +442,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
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) {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ISelectionRenderModel } from 'browser/renderer/shared/Types';
|
||||
import { ICoreBrowserService, IThemeService } from 'browser/services/Services';
|
||||
import { 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, IOptionsService } from 'common/services/Services';
|
||||
import { ICellData } from 'common/Types';
|
||||
import { Terminal } from '@xterm/xterm';
|
||||
|
||||
@@ -13,6 +13,7 @@ let $hasFg = false;
|
||||
let $hasBg = false;
|
||||
let $isSelected = false;
|
||||
let $colors: ReadonlyColorSet | undefined;
|
||||
let $variantOffset = 0;
|
||||
|
||||
export class CellColorResolver {
|
||||
/**
|
||||
@@ -27,6 +28,7 @@ export class CellColorResolver {
|
||||
|
||||
constructor(
|
||||
private readonly _terminal: Terminal,
|
||||
private readonly _optionService: IOptionsService,
|
||||
private readonly _selectionRenderModel: ISelectionRenderModel,
|
||||
private readonly _decorationService: IDecorationService,
|
||||
private readonly _coreBrowserService: ICoreBrowserService,
|
||||
@@ -38,7 +40,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;
|
||||
@@ -52,6 +54,13 @@ export class CellColorResolver {
|
||||
$hasFg = false;
|
||||
$isSelected = false;
|
||||
$colors = this._themeService.colors;
|
||||
$variantOffset = 0;
|
||||
|
||||
const code = cell.getCode();
|
||||
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
|
||||
this._decorationService.forEachDecorationAtCell(x, y, 'bottom', d => {
|
||||
@@ -133,5 +142,9 @@ 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;
|
||||
|
||||
// Reset overrides variantOffset
|
||||
this.result.ext &= ~ExtFlags.VARIANT_OFFSET;
|
||||
this.result.ext |= ($variantOffset << 29) & ExtFlags.VARIANT_OFFSET;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* Copyright (c) 2023 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { computeNextVariantOffset } from 'browser/renderer/shared/RendererUtils';
|
||||
import { assert } from 'chai';
|
||||
|
||||
describe('RendererUtils', () => {
|
||||
it('computeNextVariantOffset', () => {
|
||||
const cellWidth = 11;
|
||||
const doubleCellWidth = 22;
|
||||
let line = 1;
|
||||
let variantOffset = 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];
|
||||
variantOffset = computeNextVariantOffset(cell, line, variantOffset);
|
||||
assert.equal(variantOffset, result[index]);
|
||||
}
|
||||
|
||||
// should line 2
|
||||
// ==__==__==_,_==__==__==,__==__==__==__==__==__,==__==__==__==__==__==,
|
||||
line = 2;
|
||||
variantOffset = 0;
|
||||
cells = [cellWidth, cellWidth, doubleCellWidth, doubleCellWidth];
|
||||
result = [3, 2, 0 ,2];
|
||||
for (let index = 0; index < cells.length; index++) {
|
||||
const cell = cells[index];
|
||||
variantOffset = computeNextVariantOffset(cell, line, variantOffset);
|
||||
assert.equal(variantOffset, result[index]);
|
||||
}
|
||||
|
||||
// should line 3
|
||||
// ===___===__,_===___===_,__===___===___===___==,=___===___===___===___,
|
||||
line = 3;
|
||||
variantOffset = 0;
|
||||
cells = [cellWidth, cellWidth, doubleCellWidth, doubleCellWidth];
|
||||
result = [5, 4, 2, 0];
|
||||
for (let index = 0; index < cells.length; index++) {
|
||||
const cell = cells[index];
|
||||
variantOffset = computeNextVariantOffset(cell, line, variantOffset);
|
||||
assert.equal(variantOffset, result[index]);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -56,3 +56,7 @@ function createDimension(): IDimensions {
|
||||
height: 0
|
||||
};
|
||||
}
|
||||
|
||||
export function computeNextVariantOffset(cellWidth: number, lineWidth: number, currentOffset: number = 0): number {
|
||||
return (cellWidth - (Math.round(lineWidth) * 2 - currentOffset)) % (Math.round(lineWidth) * 2);
|
||||
}
|
||||
|
||||
@@ -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 { 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,6 +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.getUnderlineVariantOffset();
|
||||
|
||||
for (let i = 0; i < chWidth; i++) {
|
||||
this._tmpCtx.save();
|
||||
@@ -594,9 +595,22 @@ 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 === 0 ? 0 :
|
||||
(nextOffset >= lineWidth ? lineWidth * 2 - nextOffset : lineWidth - nextOffset);
|
||||
// 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 + 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 = computeNextVariantOffset(xChRight - xChLeft, lineWidth, nextOffset);
|
||||
break;
|
||||
case UnderlineStyle.DASHED:
|
||||
this._tmpCtx.setLineDash([this._config.devicePixelRatio * 4, this._config.devicePixelRatio * 3]);
|
||||
|
||||
Vendored
+2
@@ -119,6 +119,7 @@ export interface IExtendedAttrs {
|
||||
ext: number;
|
||||
underlineStyle: UnderlineStyle;
|
||||
underlineColor: number;
|
||||
underlineVariantOffset: number;
|
||||
urlId: number;
|
||||
clone(): IExtendedAttrs;
|
||||
isEmpty(): boolean;
|
||||
@@ -209,6 +210,7 @@ export interface IAttributeData {
|
||||
isUnderlineColorPalette(): boolean;
|
||||
isUnderlineColorDefault(): boolean;
|
||||
getUnderlineStyle(): number;
|
||||
getUnderlineVariantOffset(): number;
|
||||
}
|
||||
|
||||
/** Cell data */
|
||||
|
||||
@@ -126,6 +126,9 @@ export class AttributeData implements IAttributeData {
|
||||
? (this.bg & BgFlags.HAS_EXTENDED ? this.extended.underlineStyle : UnderlineStyle.SINGLE)
|
||||
: UnderlineStyle.NONE;
|
||||
}
|
||||
public getUnderlineVariantOffset(): number {
|
||||
return this.extended.underlineVariantOffset;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -174,6 +177,18 @@ export class ExtendedAttrs implements IExtendedAttrs {
|
||||
this._urlId = value;
|
||||
}
|
||||
|
||||
public get underlineVariantOffset(): number {
|
||||
const val = (this._ext & ExtFlags.VARIANT_OFFSET) >> 29;
|
||||
if (val < 0) {
|
||||
return val ^ 0xFFFFFFF8;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
public set underlineVariantOffset(value: number) {
|
||||
this._ext &= ~ExtFlags.VARIANT_OFFSET;
|
||||
this._ext |= (value << 29) & ExtFlags.VARIANT_OFFSET;
|
||||
}
|
||||
|
||||
constructor(
|
||||
ext: number = 0,
|
||||
urlId: number = 0
|
||||
|
||||
@@ -119,6 +119,18 @@ describe('AttributeData', () => {
|
||||
attrs.fg &= ~FgFlags.UNDERLINE;
|
||||
assert.equal(attrs.getUnderlineStyle(), UnderlineStyle.NONE);
|
||||
});
|
||||
it('getUnderlineVariantOffset', () => {
|
||||
const attrs = new AttributeData();
|
||||
|
||||
// defaults to no offset
|
||||
assert.equal(attrs.getUnderlineVariantOffset(), 0);
|
||||
|
||||
// should return 0 - 7
|
||||
for (let i = 0; i < 8; ++i) {
|
||||
attrs.extended.underlineVariantOffset = i;
|
||||
assert.equal(attrs.getUnderlineVariantOffset(), i);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -134,9 +134,17 @@ 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
|
||||
*
|
||||
* 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
|
||||
}
|
||||
|
||||
export const enum UnderlineStyle {
|
||||
|
||||
Reference in New Issue
Block a user