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

Underline style and color rendering #3921

Merged
merged 26 commits into from
Jul 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
1207acc
Add underline test button to demo
Tyriar Jul 23, 2022
cc87483
Expose underline attrs as getter/setter
Tyriar Jul 23, 2022
34cd596
Webgl 3 key cache map
Tyriar Jul 23, 2022
8b4e819
Get underline style affecting webgl rendering
Tyriar Jul 23, 2022
c116fa6
Render underline style on webgl
Tyriar Jul 23, 2022
baa8a1e
Stub out canvas renderer underline style
Tyriar Jul 23, 2022
1d9b4d4
Canvas renderer underline style
Tyriar Jul 23, 2022
72d18f7
DOM renderer underline style
Tyriar Jul 23, 2022
8184e08
Add tests for dom underline styles
Tyriar Jul 23, 2022
8287e78
Underline color canvas
Tyriar Jul 23, 2022
1f89223
DOM renderer underline color
Tyriar Jul 23, 2022
8cd01dc
Add underline color test to demo
Tyriar Jul 23, 2022
03f15a7
Store extended attributes in single number
Tyriar Jul 23, 2022
d9703aa
Webgl underline color rendering
Tyriar Jul 23, 2022
70b5e02
Underline demo formatting
Tyriar Jul 23, 2022
19fb806
Fix tests
Tyriar Jul 23, 2022
fd8fc18
Clear stroke around text in underline
Tyriar Jul 23, 2022
43bbea8
Set webgl as default demo renderer
Tyriar Jul 23, 2022
759a592
Continuous curly underline
Tyriar Jul 23, 2022
4087284
Fix ext caching issues
Tyriar Jul 23, 2022
d2be5f9
Merge remote-tracking branch 'upstream/master' into underline
Tyriar Jul 28, 2022
1fe0ec8
Merge branch 'master' into underline
Tyriar Jul 29, 2022
e0d4384
Merge branch 'master' into underline
Tyriar Jul 30, 2022
941cc51
Increase curly height when dpr <= 1
Tyriar Jul 30, 2022
d0ec9f2
Support letter spacing in webgl
Tyriar Jul 30, 2022
f0ddaf4
Disable threshold check for glyphs with colored underlines
Tyriar Jul 30, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 65 additions & 2 deletions addons/xterm-addon-canvas/src/BaseRenderLayer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,14 +191,77 @@ export abstract class BaseRenderLayer implements IRenderLayer {
* @param x The column to fill.
* @param y The row to fill.
*/
protected _fillBottomLineAtCells(x: number, y: number, width: number = 1): void {
protected _fillBottomLineAtCells(x: number, y: number, width: number = 1, pixelOffset: number = 0): void {
this._ctx.fillRect(
x * this._scaledCellWidth,
(y + 1) * this._scaledCellHeight - window.devicePixelRatio - 1 /* Ensure it's drawn within the cell */,
(y + 1) * this._scaledCellHeight + pixelOffset - window.devicePixelRatio - 1 /* Ensure it's drawn within the cell */,
width * this._scaledCellWidth,
window.devicePixelRatio);
}

protected _curlyUnderlineAtCell(x: number, y: number, width: number = 1): void {
this._ctx.save();
this._ctx.beginPath();
this._ctx.strokeStyle = this._ctx.fillStyle;
this._ctx.lineWidth = window.devicePixelRatio;
for (let xOffset = 0; xOffset < width; xOffset++) {
const xLeft = (x + xOffset) * this._scaledCellWidth;
const xMid = (x + xOffset + 0.5) * this._scaledCellWidth;
const xRight = (x + xOffset + 1) * this._scaledCellWidth;
const yMid = (y + 1) * this._scaledCellHeight - window.devicePixelRatio - 1;
const yMidBot = yMid - window.devicePixelRatio;
const yMidTop = yMid + window.devicePixelRatio;
this._ctx.moveTo(xLeft, yMid);
this._ctx.bezierCurveTo(
xLeft, yMidBot,
xMid, yMidBot,
xMid, yMid
);
this._ctx.bezierCurveTo(
xMid, yMidTop,
xRight, yMidTop,
xRight, yMid
);
}
this._ctx.stroke();
this._ctx.restore();
}

protected _dottedUnderlineAtCell(x: number, y: number, width: number = 1): void {
this._ctx.save();
this._ctx.beginPath();
this._ctx.strokeStyle = this._ctx.fillStyle;
this._ctx.lineWidth = window.devicePixelRatio;
this._ctx.setLineDash([window.devicePixelRatio * 2, window.devicePixelRatio]);
const xLeft = x * this._scaledCellWidth;
const yMid = (y + 1) * this._scaledCellHeight - window.devicePixelRatio - 1;
this._ctx.moveTo(xLeft, yMid);
for (let xOffset = 0; xOffset < width; xOffset++) {
// const xLeft = x * this._scaledCellWidth;
const xRight = (x + width + xOffset) * this._scaledCellWidth;
this._ctx.lineTo(xRight, yMid);
}
this._ctx.stroke();
this._ctx.closePath();
this._ctx.restore();
}

protected _dashedUnderlineAtCell(x: number, y: number, width: number = 1): void {
this._ctx.save();
this._ctx.beginPath();
this._ctx.strokeStyle = this._ctx.fillStyle;
this._ctx.lineWidth = window.devicePixelRatio;
this._ctx.setLineDash([window.devicePixelRatio * 4, window.devicePixelRatio * 3]);
const xLeft = x * this._scaledCellWidth;
const xRight = (x + width) * this._scaledCellWidth;
const yMid = (y + 1) * this._scaledCellHeight - window.devicePixelRatio - 1;
this._ctx.moveTo(xLeft, yMid);
this._ctx.lineTo(xRight, yMid);
this._ctx.stroke();
this._ctx.closePath();
this._ctx.restore();
}

/**
* Fills a 1px line (2px on HDPI) at the left of the cell. This uses the
* existing fillStyle on the context.
Expand Down
33 changes: 31 additions & 2 deletions addons/xterm-addon-canvas/src/TextRenderLayer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { CharData, ICellData } from 'common/Types';
import { GridCache } from './GridCache';
import { BaseRenderLayer } from './BaseRenderLayer';
import { AttributeData } from 'common/buffer/AttributeData';
import { NULL_CELL_CODE, Content } from 'common/buffer/Constants';
import { NULL_CELL_CODE, Content, UnderlineStyle } from 'common/buffer/Constants';
import { IColorSet } from 'browser/Types';
import { CellData } from 'common/buffer/CellData';
import { IOptionsService, IBufferService, IDecorationService } from 'common/services/Services';
Expand Down Expand Up @@ -269,7 +269,36 @@ export class TextRenderLayer extends BaseRenderLayer {
this._fillMiddleLineAtCells(x, y, cell.getWidth());
}
if (cell.isUnderline()) {
this._fillBottomLineAtCells(x, y, cell.getWidth());
if (!cell.isUnderlineColorDefault()) {
if (cell.isUnderlineColorRGB()) {
this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getUnderlineColor()).join(',')})`;
} else {
let fg = cell.getUnderlineColor();
if (this._optionsService.rawOptions.drawBoldTextInBrightColors && cell.isBold() && fg < 8) {
fg += 8;
}
this._ctx.fillStyle = this._colors.ansi[fg].css;
}
}
switch (cell.extended.underlineStyle) {
case UnderlineStyle.DOUBLE:
this._fillBottomLineAtCells(x, y, cell.getWidth(), -window.devicePixelRatio);
this._fillBottomLineAtCells(x, y, cell.getWidth(), window.devicePixelRatio);
break;
case UnderlineStyle.CURLY:
this._curlyUnderlineAtCell(x, y, cell.getWidth());
break;
case UnderlineStyle.DOTTED:
this._dottedUnderlineAtCell(x, y, cell.getWidth());
break;
case UnderlineStyle.DASHED:
this._dashedUnderlineAtCell(x, y, cell.getWidth());
break;
case UnderlineStyle.SINGLE:
default:
this._fillBottomLineAtCells(x, y, cell.getWidth());
break;
}
}
this._ctx.restore();
}
Expand Down
12 changes: 6 additions & 6 deletions addons/xterm-addon-webgl/src/GlyphRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,11 +169,11 @@ export class GlyphRenderer extends Disposable {
return this._atlas ? this._atlas.beginFrame() : true;
}

public updateCell(x: number, y: number, code: number, bg: number, fg: number, chars: string, lastBg: number): void {
this._updateCell(this._vertices.attributes, x, y, code, bg, fg, chars, lastBg);
public updateCell(x: number, y: number, code: number, bg: number, fg: number, ext: number, chars: string, lastBg: number): void {
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, 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): void {
const terminal = this._terminal;

const i = (y * terminal.cols + x) * INDICES_PER_CELL;
Expand All @@ -185,16 +185,16 @@ export class GlyphRenderer extends Disposable {
return;
}

let rasterizedGlyph: IRasterizedGlyph;
if (!this._atlas) {
return;
}

// Get the glyph
let rasterizedGlyph: IRasterizedGlyph;
if (chars && chars.length > 1) {
rasterizedGlyph = this._atlas.getRasterizedGlyphCombinedChar(chars, bg, fg);
rasterizedGlyph = this._atlas.getRasterizedGlyphCombinedChar(chars, bg, fg, ext);
} else {
rasterizedGlyph = this._atlas.getRasterizedGlyph(code, bg, fg);
rasterizedGlyph = this._atlas.getRasterizedGlyph(code, bg, fg, ext);
}

// Fill empty if no glyph was found
Expand Down
3 changes: 2 additions & 1 deletion addons/xterm-addon-webgl/src/RenderModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@
import { IRenderModel, ISelectionRenderModel } from './Types';
import { fill } from 'common/TypedArrayUtils';

export const RENDER_MODEL_INDICIES_PER_CELL = 3;
export const RENDER_MODEL_INDICIES_PER_CELL = 4;
export const RENDER_MODEL_BG_OFFSET = 1;
export const RENDER_MODEL_FG_OFFSET = 2;
export const RENDER_MODEL_EXT_OFFSET = 3;

export const COMBINED_CHAR_BIT_MASK = 0x80000000;

Expand Down
2 changes: 1 addition & 1 deletion addons/xterm-addon-webgl/src/Types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/

export interface IRasterizedGlyphSet {
[bg: number]: { [fg: number]: IRasterizedGlyph } | undefined;
[bg: number]: { [fg: number]: { [ext: number]: IRasterizedGlyph } } | undefined;
}

/**
Expand Down
15 changes: 9 additions & 6 deletions addons/xterm-addon-webgl/src/WebglRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { acquireCharAtlas, removeTerminalFromCache } from './atlas/CharAtlasCach
import { WebglCharAtlas } from './atlas/WebglCharAtlas';
import { RectangleRenderer } from './RectangleRenderer';
import { IWebGL2RenderingContext } from './Types';
import { RenderModel, COMBINED_CHAR_BIT_MASK, RENDER_MODEL_BG_OFFSET, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_INDICIES_PER_CELL } from './RenderModel';
import { RenderModel, COMBINED_CHAR_BIT_MASK, RENDER_MODEL_BG_OFFSET, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_EXT_OFFSET, RENDER_MODEL_INDICIES_PER_CELL } from './RenderModel';
import { Disposable, toDisposable } from 'common/Lifecycle';
import { Attributes, BgFlags, Content, FgFlags, NULL_CELL_CHAR, NULL_CELL_CODE } from 'common/buffer/Constants';
import { Terminal, IEvent } from 'xterm';
Expand All @@ -34,7 +34,7 @@ export class WebglRenderer extends Disposable implements IRenderer {

private _model: RenderModel = new RenderModel();
private _workCell: CellData = new CellData();
private _workColors: { fg: number, bg: number } = { fg: 0, bg: 0 };
private _workColors: { fg: number, bg: number, ext: number } = { fg: 0, bg: 0, ext: 0 };

private _canvas: HTMLCanvasElement;
private _gl: IWebGL2RenderingContext;
Expand Down Expand Up @@ -353,7 +353,8 @@ export class WebglRenderer extends Disposable implements IRenderer {
// Nothing has changed, no updates needed
if (this._model.cells[i] === code &&
this._model.cells[i + RENDER_MODEL_BG_OFFSET] === this._workColors.bg &&
this._model.cells[i + RENDER_MODEL_FG_OFFSET] === this._workColors.fg) {
this._model.cells[i + RENDER_MODEL_FG_OFFSET] === this._workColors.fg &&
this._model.cells[i + RENDER_MODEL_EXT_OFFSET] === this._workColors.ext) {
continue;
}

Expand All @@ -366,8 +367,9 @@ export class WebglRenderer extends Disposable implements IRenderer {
this._model.cells[i] = code;
this._model.cells[i + RENDER_MODEL_BG_OFFSET] = this._workColors.bg;
this._model.cells[i + RENDER_MODEL_FG_OFFSET] = this._workColors.fg;
this._model.cells[i + RENDER_MODEL_EXT_OFFSET] = this._workColors.ext;

this._glyphRenderer.updateCell(x, y, code, this._workColors.bg, this._workColors.fg, chars, lastBg);
this._glyphRenderer.updateCell(x, y, code, this._workColors.bg, this._workColors.fg, this._workColors.ext, chars, lastBg);

if (isJoined) {
// Restore work cell
Expand All @@ -376,10 +378,11 @@ export class WebglRenderer extends Disposable implements IRenderer {
// Null out non-first cells
for (x++; x < lastCharX; x++) {
const j = ((y * terminal.cols) + x) * RENDER_MODEL_INDICIES_PER_CELL;
this._glyphRenderer.updateCell(x, y, NULL_CELL_CODE, 0, 0, NULL_CELL_CHAR, 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._workColors.bg;
this._model.cells[j + RENDER_MODEL_FG_OFFSET] = this._workColors.fg;
this._model.cells[j + RENDER_MODEL_EXT_OFFSET] = this._workColors.ext;
}
}
}
Expand All @@ -394,7 +397,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
private _loadColorsForCell(x: number, y: number): void {
this._workColors.bg = this._workCell.bg;
this._workColors.fg = this._workCell.fg;

this._workColors.ext = this._workCell.bg & BgFlags.HAS_EXTENDED ? this._workCell.extended.ext : 0;
// Get any foreground/background overrides, this happens on the model to avoid spreading
// override logic throughout the different sub-renderers
let bgOverride: number | undefined;
Expand Down
Loading