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

Fix powerlines, custom glyph offset and improve demo #3985

Merged
merged 8 commits into from
Jul 31, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 4 additions & 3 deletions addons/xterm-addon-webgl/src/GlyphRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,10 +203,11 @@ export class GlyphRenderer extends Disposable {
return;
}

if (bg !== lastBg && rasterizedGlyph.offset.x > 0) {
const clippedPixels = rasterizedGlyph.offset.x;
const leftCellPadding = Math.floor((this._dimensions.scaledCellWidth - this._dimensions.scaledCharWidth) / 2);
if (bg !== lastBg && rasterizedGlyph.offset.x > leftCellPadding) {
const clippedPixels = rasterizedGlyph.offset.x - leftCellPadding;
// a_origin
array[i ] = this._dimensions.scaledCharLeft;
array[i ] = -(rasterizedGlyph.offset.x - clippedPixels) + this._dimensions.scaledCharLeft;
array[i + 1] = -rasterizedGlyph.offset.y + this._dimensions.scaledCharTop;
// a_size
array[i + 2] = (rasterizedGlyph.size.x - clippedPixels) / this._dimensions.scaledCanvasWidth;
Expand Down
8 changes: 6 additions & 2 deletions addons/xterm-addon-webgl/src/WebglAddon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,16 @@ import { Terminal, ITerminalAddon, IEvent } from 'xterm';
import { WebglRenderer } from './WebglRenderer';
import { ICharacterJoinerService, ICoreBrowserService, IRenderService } from 'browser/services/Services';
import { IColorSet } from 'browser/Types';
import { EventEmitter } from 'common/EventEmitter';
import { EventEmitter, forwardEvent } from 'common/EventEmitter';
import { isSafari } from 'common/Platform';
import { ICoreService, IDecorationService } from 'common/services/Services';

export class WebglAddon implements ITerminalAddon {
private _terminal?: Terminal;
private _renderer?: WebglRenderer;

private _onChangeTextureAtlas = new EventEmitter<HTMLElement>();
public get onChangeTextureAtlas(): IEvent<HTMLElement> { return this._onChangeTextureAtlas.event; }
private _onContextLoss = new EventEmitter<void>();
public get onContextLoss(): IEvent<void> { return this._onContextLoss.event; }

Expand All @@ -36,7 +39,8 @@ export class WebglAddon implements ITerminalAddon {
const decorationService: IDecorationService = (terminal as any)._core._decorationService;
const colors: IColorSet = (terminal as any)._core._colorManager.colors;
this._renderer = new WebglRenderer(terminal, colors, characterJoinerService, coreBrowserService, coreService, decorationService, this._preserveDrawingBuffer);
this._renderer.onContextLoss(() => this._onContextLoss.fire());
forwardEvent(this._renderer.onContextLoss, this._onContextLoss);
forwardEvent(this._renderer.onChangeTextureAtlas, this._onChangeTextureAtlas);
renderService.setRenderer(this._renderer);
}

Expand Down
8 changes: 6 additions & 2 deletions addons/xterm-addon-webgl/src/WebglRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import { ICharacterJoinerService, ICoreBrowserService } from 'browser/services/S
import { CharData, ICellData } from 'common/Types';
import { AttributeData } from 'common/buffer/AttributeData';
import { ICoreService, IDecorationService } from 'common/services/Services';
import { color, rgba as rgbaNs } from 'common/Color';

export class WebglRenderer extends Disposable implements IRenderer {
private _renderLayers: IRenderLayer[];
Expand All @@ -46,6 +45,8 @@ export class WebglRenderer extends Disposable implements IRenderer {
private _core: ITerminal;
private _isAttached: boolean;

private _onChangeTextureAtlas = new EventEmitter<HTMLCanvasElement>();
public get onChangeTextureAtlas(): IEvent<HTMLCanvasElement> { return this._onChangeTextureAtlas.event; }
private _onRequestRedraw = new EventEmitter<IRequestRedrawEvent>();
public get onRequestRedraw(): IEvent<IRequestRedrawEvent> { return this._onRequestRedraw.event; }

Expand Down Expand Up @@ -240,7 +241,10 @@ export class WebglRenderer extends Disposable implements IRenderer {
if (!('getRasterizedGlyph' in atlas)) {
throw new Error('The webgl renderer only works with the webgl char atlas');
}
this._charAtlas = atlas as WebglCharAtlas;
if (this._charAtlas !== atlas) {
this._onChangeTextureAtlas.fire(atlas.cacheCanvas);
}
this._charAtlas = atlas;
this._charAtlas.warmUp();
this._glyphRenderer.setAtlas(this._charAtlas);
}
Expand Down
29 changes: 12 additions & 17 deletions addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -417,23 +417,23 @@ export class WebglCharAtlas implements IDisposable {
`${fontStyle} ${fontWeight} ${this._config.fontSize * this._config.devicePixelRatio}px ${this._config.fontFamily}`;
this._tmpCtx.textBaseline = TEXT_BASELINE;

const powerLineGlyph = chars.length === 1 && isPowerlineGlyph(chars.charCodeAt(0));
const powerlineGlyph = chars.length === 1 && isPowerlineGlyph(chars.charCodeAt(0));
const foregroundColor = this._getForegroundColor(bg, bgColorMode, bgColor, fg, fgColorMode, fgColor, inverse, dim, bold, excludeFromContrastRatioDemands(chars.charCodeAt(0)));
this._tmpCtx.fillStyle = foregroundColor.css;

// For powerline glyphs left/top padding is excluded (https://github.com/microsoft/vscode/issues/120129)
const padding = powerLineGlyph ? 0 : TMP_CANVAS_GLYPH_PADDING * 2;
const padding = powerlineGlyph ? 0 : TMP_CANVAS_GLYPH_PADDING * 2;

// Draw custom characters if applicable
let drawSuccess = false;
let customGlyph = false;
if (this._config.customGlyphs !== false) {
drawSuccess = tryDrawCustomChar(this._tmpCtx, chars, padding, padding, this._config.scaledCellWidth, this._config.scaledCellHeight);
customGlyph = tryDrawCustomChar(this._tmpCtx, chars, padding, padding, this._config.scaledCellWidth, this._config.scaledCellHeight);
}

// Whether to clear pixels based on a threshold difference between the glyph color and the
// background color. This should be disabled when the glyph contains multiple colors such as
// underline colors to prevent important colors could get cleared.
let enableClearThresholdCheck = true;
let enableClearThresholdCheck = !powerlineGlyph;

// Draw underline
if (underline) {
Expand Down Expand Up @@ -529,7 +529,7 @@ export class WebglCharAtlas implements IDisposable {

// Draw stroke in the background color for non custom characters in order to give an outline
// between the text and the underline
if (!drawSuccess) {
if (!customGlyph) {
// This only works when transparency is disabled because it's not clear how to clear stroked
// text
if (!this._config.allowTransparency && chars !== ' ') {
Expand All @@ -550,7 +550,7 @@ export class WebglCharAtlas implements IDisposable {
}

// Draw the character
if (!drawSuccess) {
if (!customGlyph) {
this._tmpCtx.fillText(chars, padding, padding + this._config.scaledCharHeight);
}

Expand Down Expand Up @@ -603,7 +603,7 @@ export class WebglCharAtlas implements IDisposable {
return NULL_RASTERIZED_GLYPH;
}

const rasterizedGlyph = this._findGlyphBoundingBox(imageData, this._workBoundingBox, allowedWidth, powerLineGlyph, drawSuccess);
const rasterizedGlyph = this._findGlyphBoundingBox(imageData, this._workBoundingBox, allowedWidth, powerlineGlyph, customGlyph, padding);
const clippedImageData = this._clipImageData(imageData, this._workBoundingBox);

// Find the best atlas row to use
Expand Down Expand Up @@ -680,10 +680,10 @@ export class WebglCharAtlas implements IDisposable {
* @param imageData The image data to read.
* @param boundingBox An IBoundingBox to put the clipped bounding box values.
*/
private _findGlyphBoundingBox(imageData: ImageData, boundingBox: IBoundingBox, allowedWidth: number, restrictedGlyph: boolean, customGlyph: boolean): IRasterizedGlyph {
private _findGlyphBoundingBox(imageData: ImageData, boundingBox: IBoundingBox, allowedWidth: number, restrictedGlyph: boolean, customGlyph: boolean, padding: number): IRasterizedGlyph {
boundingBox.top = 0;
const height = restrictedGlyph ? this._config.scaledCellHeight : this._tmpCanvas.height;
const width = restrictedGlyph ? this._config.scaledCharWidth : allowedWidth;
const width = restrictedGlyph ? this._config.scaledCellWidth : allowedWidth;
let found = false;
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
Expand Down Expand Up @@ -755,8 +755,8 @@ export class WebglCharAtlas implements IDisposable {
y: (boundingBox.bottom - boundingBox.top + 1) / TEXTURE_HEIGHT
},
offset: {
x: -boundingBox.left + (restrictedGlyph ? 0 : TMP_CANVAS_GLYPH_PADDING) + (customGlyph ? Math.floor(this._config.letterSpacing / 2) : 0),
y: -boundingBox.top + (restrictedGlyph ? 0 : TMP_CANVAS_GLYPH_PADDING) + (customGlyph ? this._config.lineHeight === 1 ? 0 : Math.round((this._config.scaledCellHeight - this._config.scaledCharHeight) / 2) : 0)
x: -boundingBox.left + padding + ((restrictedGlyph || customGlyph) ? Math.floor((this._config.scaledCellWidth - this._config.scaledCharWidth) / 2) : 0),
y: -boundingBox.top + padding + ((restrictedGlyph || customGlyph) ? this._config.lineHeight === 1 ? 0 : Math.round((this._config.scaledCellHeight - this._config.scaledCharHeight) / 2) : 0)
}
};
}
Expand Down Expand Up @@ -833,8 +833,3 @@ function checkCompletelyTransparent(imageData: ImageData): boolean {
}
return true;
}

function toPaddedHex(c: number): string {
const s = c.toString(16);
return s.length < 2 ? '0' + s : s;
}
15 changes: 10 additions & 5 deletions addons/xterm-addon-webgl/typings/xterm-addon-webgl.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,16 @@ declare module 'xterm-addon-webgl' {
export class WebglAddon implements ITerminalAddon {
public textureAtlas?: HTMLCanvasElement;

/**
* An event that is fired when the renderer loses its canvas context.
*/
public get onContextLoss(): IEvent<void>;

/**
* An event that is fired when the texture atlas of the renderer changes.
*/
public get onChangeTextureAtlas(): IEvent<HTMLCanvasElement>;

constructor(preserveDrawingBuffer?: boolean);

/**
Expand All @@ -29,10 +39,5 @@ declare module 'xterm-addon-webgl' {
* Clears the terminal's texture atlas and triggers a redraw.
*/
public clearTextureAtlas(): void;

/**
* Fired when the WebglRenderer loses context
*/
public get onContextLoss(): IEvent<void>;
}
}
26 changes: 13 additions & 13 deletions demo/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,8 @@ function createTerminal(): void {
addons.fit.instance!.fit();
typedTerm.loadAddon(addons.webgl.instance);
setTimeout(() => {
document.body.appendChild(addons.webgl.instance.textureAtlas);
addTextureAtlas(addons.webgl.instance.textureAtlas);
addons.webgl.instance.onChangeTextureAtlas(e => addTextureAtlas(e));
}, 0);
term.focus();

Expand Down Expand Up @@ -399,20 +400,18 @@ function initOptions(term: TerminalType): void {
const input = <HTMLInputElement>document.getElementById(`opt-${o}`);
addDomListener(input, 'change', () => {
console.log('change', o, input.value);
if (o === 'cols' || o === 'rows') {
updateTerminalSize();
} else if (o === 'lineHeight') {
if (o === 'lineHeight') {
term.options.lineHeight = parseFloat(input.value);
updateTerminalSize();
} else if (o === 'scrollSensitivity') {
term.options.scrollSensitivity = parseFloat(input.value);
updateTerminalSize();
} else if (o === 'scrollback') {
term.options.scrollback = parseInt(input.value);
setTimeout(() => updateTerminalSize(), 5);
} else {
term.options[o] = parseInt(input.value);
}
// Always update terminal size in case the option changes the dimensions
updateTerminalSize();
});
});
Object.keys(stringOptions).forEach(o => {
Expand Down Expand Up @@ -504,9 +503,7 @@ function initAddons(term: TerminalType): void {
addon.instance = new addon.ctor();
term.loadAddon(addon.instance);
if (name === 'webgl') {
setTimeout(() => {
document.body.appendChild((addon.instance as WebglAddon).textureAtlas);
}, 0);
(addon.instance as WebglAddon).onChangeTextureAtlas(e => addTextureAtlas(e));
} else if (name === 'unicode11') {
term.unicode.activeVersion = '11';
} else if (name === 'search') {
Expand Down Expand Up @@ -590,6 +587,9 @@ function htmlSerializeButtonHandler(): void {
document.getElementById("htmlserialize-output-result").innerText = "Copied to clipboard";
}

function addTextureAtlas(e: HTMLCanvasElement) {
document.querySelector('#texture-atlas').appendChild(e);
}

function writeCustomGlyphHandler() {
term.write('\n\r');
Expand Down Expand Up @@ -688,7 +688,7 @@ function powerlineSymbolTest() {
` 3 \ue0b1 \x1b[33;44m\ue0b0\x1b[39m` +
` 4 \ue0b1 \x1b[34;45m\ue0b0\x1b[39m` +
` 5 \ue0b1 \x1b[35;46m\ue0b0\x1b[39m` +
` 6 \ue0b1 \x1b[36;47m\ue0b0\x1b[39m` +
` 6 \ue0b1 \x1b[36;47m\ue0b0\x1b[30m` +
` 7 \ue0b1 \x1b[37;49m\ue0b0\x1b[0m`
);
term.writeln('');
Expand All @@ -701,7 +701,7 @@ function powerlineSymbolTest() {
` 3 \ue0b3 \x1b[7;33;44m\ue0b2\x1b[27;39m` +
` 4 \ue0b3 \x1b[7;34;45m\ue0b2\x1b[27;39m` +
` 5 \ue0b3 \x1b[7;35;46m\ue0b2\x1b[27;39m` +
` 6 \ue0b3 \x1b[7;36;47m\ue0b2\x1b[27;39m` +
` 6 \ue0b3 \x1b[7;36;47m\ue0b2\x1b[27;30m` +
` 7 \ue0b3 \x1b[7;37;49m\ue0b2\x1b[0m`
);
term.writeln('');
Expand All @@ -714,7 +714,7 @@ function powerlineSymbolTest() {
` 3 \ue0b5 \x1b[33;44m\ue0b4\x1b[39m` +
` 4 \ue0b5 \x1b[34;45m\ue0b4\x1b[39m` +
` 5 \ue0b5 \x1b[35;46m\ue0b4\x1b[39m` +
` 6 \ue0b5 \x1b[36;47m\ue0b4\x1b[39m` +
` 6 \ue0b5 \x1b[36;47m\ue0b4\x1b[30m` +
` 7 \ue0b5 \x1b[37;49m\ue0b4\x1b[0m`
);
term.writeln('');
Expand All @@ -727,7 +727,7 @@ function powerlineSymbolTest() {
` 3 \ue0b7 \x1b[7;33;44m\ue0b6\x1b[27;39m` +
` 4 \ue0b7 \x1b[7;34;45m\ue0b6\x1b[27;39m` +
` 5 \ue0b7 \x1b[7;35;46m\ue0b6\x1b[27;39m` +
` 6 \ue0b7 \x1b[7;36;47m\ue0b6\x1b[27;39m` +
` 6 \ue0b7 \x1b[7;36;47m\ue0b6\x1b[27;30m` +
` 7 \ue0b7 \x1b[7;37;49m\ue0b6\x1b[0m`
);
term.writeln('');
Expand Down
1 change: 1 addition & 0 deletions demo/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ <h3>Test</h3>
</div>
</div>
</div>
<div id="texture-atlas"></div>
<script src="dist/client-bundle.js" defer ></script>
<script>
var tab = localStorage.getItem("tab");
Expand Down
14 changes: 14 additions & 0 deletions demo/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,17 @@ pre {
max-height: 100vh;
overflow-y: auto;
}

#texture-atlas {
width: 100%;
height: 600px;
overflow: scroll;
}
#texture-atlas canvas {
image-rendering: pixelated;
}
#texture-atlas canvas:hover {
/* zoom to 4x on hover */
width: 4096px;
height: 4096px;
}