Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use variants for underline dotted. #4703

Merged
merged 27 commits into from
Nov 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
32da1d8
Use variants for underline dotted.
tisilent Aug 21, 2023
cb175e5
Variants to Canvas.
tisilent Aug 21, 2023
c81dfee
Using ext to store variants.
tisilent Aug 21, 2023
fe7f17f
clean
tisilent Aug 22, 2023
5f0647f
clean
tisilent Aug 22, 2023
68ded1a
Merge branch 'master' into underline-dotted
Tyriar Aug 22, 2023
25afc19
Merge branch 'underline-dotted' of https://github.com/tisilent/xterm.…
tisilent Aug 23, 2023
f5d128b
Merge remote-tracking branch 'upstream/master' into pr/tisilent/4703
Tyriar Aug 23, 2023
1f3143c
Remove unused member
Tyriar Aug 23, 2023
2b0106f
Logic move to CellColorResolver
tisilent Aug 24, 2023
e09ed98
Merge branch 'underline-dotted' of https://github.com/tisilent/xterm.…
tisilent Aug 24, 2023
564371e
Clean unused member
tisilent Aug 24, 2023
edfacbb
Adjusting the execution process
tisilent Aug 24, 2023
52c87bc
Add tests for computeVarinatOffset
tisilent Aug 24, 2023
6ace416
Add license header
tisilent Aug 24, 2023
f1f0191
Merge branch 'master' into underline-dotted
Tyriar Aug 24, 2023
f488187
Remove unused private prop
Tyriar Aug 24, 2023
d15abd7
Update src/browser/renderer/shared/CellColorResolver.ts
tisilent Aug 25, 2023
08f7b4c
Update src/browser/renderer/shared/CellColorResolver.ts
tisilent Aug 25, 2023
bec1642
Modify variantOffset calculation logic
tisilent Aug 25, 2023
810e626
Clean
tisilent Aug 25, 2023
42d6c2b
Use hex
tisilent Aug 25, 2023
7fa56fd
Merge branch 'master' into underline-dotted
tisilent Sep 1, 2023
bfa6f65
Merge branch 'master' into underline-dotted
Tyriar Sep 11, 2023
390e4f3
Merge remote-tracking branch 'upstream/master' into pr/tisilent/4703
Tyriar Nov 2, 2023
6214f4c
Varinat -> Variant
Tyriar Nov 2, 2023
2f074b8
Explain VARIANT_OFFSET
Tyriar Nov 2, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions addons/addon-canvas/src/BaseRenderLayer.ts
Expand Up @@ -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();
Expand Down Expand Up @@ -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;
Expand Down
12 changes: 12 additions & 0 deletions addons/addon-image/src/ImageStorage.ts
Expand Up @@ -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;
Expand Down
4 changes: 2 additions & 2 deletions addons/addon-webgl/src/WebglRenderer.ts
Expand Up @@ -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;

Expand Down Expand Up @@ -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) {
Expand Down
19 changes: 16 additions & 3 deletions src/browser/renderer/shared/CellColorResolver.ts
@@ -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';

Expand All @@ -13,6 +13,7 @@ let $hasFg = false;
let $hasBg = false;
let $isSelected = false;
let $colors: ReadonlyColorSet | undefined;
let $variantOffset = 0;

export class CellColorResolver {
/**
Expand All @@ -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,
Expand All @@ -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;
Expand All @@ -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 => {
Expand Down Expand Up @@ -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;
}
}
50 changes: 50 additions & 0 deletions src/browser/renderer/shared/RendererUtils.test.ts
@@ -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]);
}
});
});
4 changes: 4 additions & 0 deletions src/browser/renderer/shared/RendererUtils.ts
Expand Up @@ -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);
}
22 changes: 18 additions & 4 deletions src/browser/renderer/shared/TextureAtlas.ts
Expand Up @@ -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';
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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]);
Expand Down
2 changes: 2 additions & 0 deletions src/common/Types.d.ts
Expand Up @@ -119,6 +119,7 @@ export interface IExtendedAttrs {
ext: number;
underlineStyle: UnderlineStyle;
underlineColor: number;
underlineVariantOffset: number;
urlId: number;
clone(): IExtendedAttrs;
isEmpty(): boolean;
Expand Down Expand Up @@ -209,6 +210,7 @@ export interface IAttributeData {
isUnderlineColorPalette(): boolean;
isUnderlineColorDefault(): boolean;
getUnderlineStyle(): number;
getUnderlineVariantOffset(): number;
}

/** Cell data */
Expand Down
15 changes: 15 additions & 0 deletions src/common/buffer/AttributeData.ts
Expand Up @@ -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;
}
}


Expand Down Expand Up @@ -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
Expand Down
12 changes: 12 additions & 0 deletions src/common/buffer/BufferLine.test.ts
Expand Up @@ -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);
}
});
});
});

Expand Down
12 changes: 10 additions & 2 deletions src/common/buffer/Constants.ts
Expand Up @@ -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 {
Expand Down