Skip to content

Commit face97d

Browse files
authored
Merge pull request #3985 from Tyriar/3969
Fix powerlines, custom glyph offset and improve demo
2 parents 889e3ad + 8d92f91 commit face97d

File tree

8 files changed

+66
-42
lines changed

8 files changed

+66
-42
lines changed

addons/xterm-addon-webgl/src/GlyphRenderer.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -203,10 +203,11 @@ export class GlyphRenderer extends Disposable {
203203
return;
204204
}
205205

206-
if (bg !== lastBg && rasterizedGlyph.offset.x > 0) {
207-
const clippedPixels = rasterizedGlyph.offset.x;
206+
const leftCellPadding = Math.floor((this._dimensions.scaledCellWidth - this._dimensions.scaledCharWidth) / 2);
207+
if (bg !== lastBg && rasterizedGlyph.offset.x > leftCellPadding) {
208+
const clippedPixels = rasterizedGlyph.offset.x - leftCellPadding;
208209
// a_origin
209-
array[i ] = this._dimensions.scaledCharLeft;
210+
array[i ] = -(rasterizedGlyph.offset.x - clippedPixels) + this._dimensions.scaledCharLeft;
210211
array[i + 1] = -rasterizedGlyph.offset.y + this._dimensions.scaledCharTop;
211212
// a_size
212213
array[i + 2] = (rasterizedGlyph.size.x - clippedPixels) / this._dimensions.scaledCanvasWidth;

addons/xterm-addon-webgl/src/WebglAddon.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,16 @@ import { Terminal, ITerminalAddon, IEvent } from 'xterm';
77
import { WebglRenderer } from './WebglRenderer';
88
import { ICharacterJoinerService, ICoreBrowserService, IRenderService } from 'browser/services/Services';
99
import { IColorSet } from 'browser/Types';
10-
import { EventEmitter } from 'common/EventEmitter';
10+
import { EventEmitter, forwardEvent } from 'common/EventEmitter';
1111
import { isSafari } from 'common/Platform';
1212
import { ICoreService, IDecorationService } from 'common/services/Services';
1313

1414
export class WebglAddon implements ITerminalAddon {
1515
private _terminal?: Terminal;
1616
private _renderer?: WebglRenderer;
17+
18+
private _onChangeTextureAtlas = new EventEmitter<HTMLElement>();
19+
public get onChangeTextureAtlas(): IEvent<HTMLElement> { return this._onChangeTextureAtlas.event; }
1720
private _onContextLoss = new EventEmitter<void>();
1821
public get onContextLoss(): IEvent<void> { return this._onContextLoss.event; }
1922

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

addons/xterm-addon-webgl/src/WebglRenderer.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import { ICharacterJoinerService, ICoreBrowserService } from 'browser/services/S
2525
import { CharData, ICellData } from 'common/Types';
2626
import { AttributeData } from 'common/buffer/AttributeData';
2727
import { ICoreService, IDecorationService } from 'common/services/Services';
28-
import { color, rgba as rgbaNs } from 'common/Color';
2928

3029
export class WebglRenderer extends Disposable implements IRenderer {
3130
private _renderLayers: IRenderLayer[];
@@ -46,6 +45,8 @@ export class WebglRenderer extends Disposable implements IRenderer {
4645
private _core: ITerminal;
4746
private _isAttached: boolean;
4847

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

@@ -240,7 +241,10 @@ export class WebglRenderer extends Disposable implements IRenderer {
240241
if (!('getRasterizedGlyph' in atlas)) {
241242
throw new Error('The webgl renderer only works with the webgl char atlas');
242243
}
243-
this._charAtlas = atlas as WebglCharAtlas;
244+
if (this._charAtlas !== atlas) {
245+
this._onChangeTextureAtlas.fire(atlas.cacheCanvas);
246+
}
247+
this._charAtlas = atlas;
244248
this._charAtlas.warmUp();
245249
this._glyphRenderer.setAtlas(this._charAtlas);
246250
}

addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -417,23 +417,23 @@ export class WebglCharAtlas implements IDisposable {
417417
`${fontStyle} ${fontWeight} ${this._config.fontSize * this._config.devicePixelRatio}px ${this._config.fontFamily}`;
418418
this._tmpCtx.textBaseline = TEXT_BASELINE;
419419

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

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

427427
// Draw custom characters if applicable
428-
let drawSuccess = false;
428+
let customGlyph = false;
429429
if (this._config.customGlyphs !== false) {
430-
drawSuccess = tryDrawCustomChar(this._tmpCtx, chars, padding, padding, this._config.scaledCellWidth, this._config.scaledCellHeight);
430+
customGlyph = tryDrawCustomChar(this._tmpCtx, chars, padding, padding, this._config.scaledCellWidth, this._config.scaledCellHeight);
431431
}
432432

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

438438
// Draw underline
439439
if (underline) {
@@ -529,7 +529,7 @@ export class WebglCharAtlas implements IDisposable {
529529

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

552552
// Draw the character
553-
if (!drawSuccess) {
553+
if (!customGlyph) {
554554
this._tmpCtx.fillText(chars, padding, padding + this._config.scaledCharHeight);
555555
}
556556

@@ -603,7 +603,7 @@ export class WebglCharAtlas implements IDisposable {
603603
return NULL_RASTERIZED_GLYPH;
604604
}
605605

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

609609
// Find the best atlas row to use
@@ -680,10 +680,10 @@ export class WebglCharAtlas implements IDisposable {
680680
* @param imageData The image data to read.
681681
* @param boundingBox An IBoundingBox to put the clipped bounding box values.
682682
*/
683-
private _findGlyphBoundingBox(imageData: ImageData, boundingBox: IBoundingBox, allowedWidth: number, restrictedGlyph: boolean, customGlyph: boolean): IRasterizedGlyph {
683+
private _findGlyphBoundingBox(imageData: ImageData, boundingBox: IBoundingBox, allowedWidth: number, restrictedGlyph: boolean, customGlyph: boolean, padding: number): IRasterizedGlyph {
684684
boundingBox.top = 0;
685685
const height = restrictedGlyph ? this._config.scaledCellHeight : this._tmpCanvas.height;
686-
const width = restrictedGlyph ? this._config.scaledCharWidth : allowedWidth;
686+
const width = restrictedGlyph ? this._config.scaledCellWidth : allowedWidth;
687687
let found = false;
688688
for (let y = 0; y < height; y++) {
689689
for (let x = 0; x < width; x++) {
@@ -755,8 +755,8 @@ export class WebglCharAtlas implements IDisposable {
755755
y: (boundingBox.bottom - boundingBox.top + 1) / TEXTURE_HEIGHT
756756
},
757757
offset: {
758-
x: -boundingBox.left + (restrictedGlyph ? 0 : TMP_CANVAS_GLYPH_PADDING) + (customGlyph ? Math.floor(this._config.letterSpacing / 2) : 0),
759-
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)
758+
x: -boundingBox.left + padding + ((restrictedGlyph || customGlyph) ? Math.floor((this._config.scaledCellWidth - this._config.scaledCharWidth) / 2) : 0),
759+
y: -boundingBox.top + padding + ((restrictedGlyph || customGlyph) ? this._config.lineHeight === 1 ? 0 : Math.round((this._config.scaledCellHeight - this._config.scaledCharHeight) / 2) : 0)
760760
}
761761
};
762762
}
@@ -833,8 +833,3 @@ function checkCompletelyTransparent(imageData: ImageData): boolean {
833833
}
834834
return true;
835835
}
836-
837-
function toPaddedHex(c: number): string {
838-
const s = c.toString(16);
839-
return s.length < 2 ? '0' + s : s;
840-
}

addons/xterm-addon-webgl/typings/xterm-addon-webgl.d.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,16 @@ declare module 'xterm-addon-webgl' {
1212
export class WebglAddon implements ITerminalAddon {
1313
public textureAtlas?: HTMLCanvasElement;
1414

15+
/**
16+
* An event that is fired when the renderer loses its canvas context.
17+
*/
18+
public get onContextLoss(): IEvent<void>;
19+
20+
/**
21+
* An event that is fired when the texture atlas of the renderer changes.
22+
*/
23+
public get onChangeTextureAtlas(): IEvent<HTMLCanvasElement>;
24+
1525
constructor(preserveDrawingBuffer?: boolean);
1626

1727
/**
@@ -29,10 +39,5 @@ declare module 'xterm-addon-webgl' {
2939
* Clears the terminal's texture atlas and triggers a redraw.
3040
*/
3141
public clearTextureAtlas(): void;
32-
33-
/**
34-
* Fired when the WebglRenderer loses context
35-
*/
36-
public get onContextLoss(): IEvent<void>;
3742
}
3843
}

demo/client.ts

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,8 @@ function createTerminal(): void {
239239
addons.fit.instance!.fit();
240240
typedTerm.loadAddon(addons.webgl.instance);
241241
setTimeout(() => {
242-
document.body.appendChild(addons.webgl.instance.textureAtlas);
242+
addTextureAtlas(addons.webgl.instance.textureAtlas);
243+
addons.webgl.instance.onChangeTextureAtlas(e => addTextureAtlas(e));
243244
}, 0);
244245
term.focus();
245246

@@ -399,20 +400,18 @@ function initOptions(term: TerminalType): void {
399400
const input = <HTMLInputElement>document.getElementById(`opt-${o}`);
400401
addDomListener(input, 'change', () => {
401402
console.log('change', o, input.value);
402-
if (o === 'cols' || o === 'rows') {
403-
updateTerminalSize();
404-
} else if (o === 'lineHeight') {
403+
if (o === 'lineHeight') {
405404
term.options.lineHeight = parseFloat(input.value);
406-
updateTerminalSize();
407405
} else if (o === 'scrollSensitivity') {
408406
term.options.scrollSensitivity = parseFloat(input.value);
409-
updateTerminalSize();
410407
} else if (o === 'scrollback') {
411408
term.options.scrollback = parseInt(input.value);
412409
setTimeout(() => updateTerminalSize(), 5);
413410
} else {
414411
term.options[o] = parseInt(input.value);
415412
}
413+
// Always update terminal size in case the option changes the dimensions
414+
updateTerminalSize();
416415
});
417416
});
418417
Object.keys(stringOptions).forEach(o => {
@@ -504,9 +503,7 @@ function initAddons(term: TerminalType): void {
504503
addon.instance = new addon.ctor();
505504
term.loadAddon(addon.instance);
506505
if (name === 'webgl') {
507-
setTimeout(() => {
508-
document.body.appendChild((addon.instance as WebglAddon).textureAtlas);
509-
}, 0);
506+
(addon.instance as WebglAddon).onChangeTextureAtlas(e => addTextureAtlas(e));
510507
} else if (name === 'unicode11') {
511508
term.unicode.activeVersion = '11';
512509
} else if (name === 'search') {
@@ -590,6 +587,9 @@ function htmlSerializeButtonHandler(): void {
590587
document.getElementById("htmlserialize-output-result").innerText = "Copied to clipboard";
591588
}
592589

590+
function addTextureAtlas(e: HTMLCanvasElement) {
591+
document.querySelector('#texture-atlas').appendChild(e);
592+
}
593593

594594
function writeCustomGlyphHandler() {
595595
term.write('\n\r');
@@ -688,7 +688,7 @@ function powerlineSymbolTest() {
688688
` 3 \ue0b1 \x1b[33;44m\ue0b0\x1b[39m` +
689689
` 4 \ue0b1 \x1b[34;45m\ue0b0\x1b[39m` +
690690
` 5 \ue0b1 \x1b[35;46m\ue0b0\x1b[39m` +
691-
` 6 \ue0b1 \x1b[36;47m\ue0b0\x1b[39m` +
691+
` 6 \ue0b1 \x1b[36;47m\ue0b0\x1b[30m` +
692692
` 7 \ue0b1 \x1b[37;49m\ue0b0\x1b[0m`
693693
);
694694
term.writeln('');
@@ -701,7 +701,7 @@ function powerlineSymbolTest() {
701701
` 3 \ue0b3 \x1b[7;33;44m\ue0b2\x1b[27;39m` +
702702
` 4 \ue0b3 \x1b[7;34;45m\ue0b2\x1b[27;39m` +
703703
` 5 \ue0b3 \x1b[7;35;46m\ue0b2\x1b[27;39m` +
704-
` 6 \ue0b3 \x1b[7;36;47m\ue0b2\x1b[27;39m` +
704+
` 6 \ue0b3 \x1b[7;36;47m\ue0b2\x1b[27;30m` +
705705
` 7 \ue0b3 \x1b[7;37;49m\ue0b2\x1b[0m`
706706
);
707707
term.writeln('');
@@ -714,7 +714,7 @@ function powerlineSymbolTest() {
714714
` 3 \ue0b5 \x1b[33;44m\ue0b4\x1b[39m` +
715715
` 4 \ue0b5 \x1b[34;45m\ue0b4\x1b[39m` +
716716
` 5 \ue0b5 \x1b[35;46m\ue0b4\x1b[39m` +
717-
` 6 \ue0b5 \x1b[36;47m\ue0b4\x1b[39m` +
717+
` 6 \ue0b5 \x1b[36;47m\ue0b4\x1b[30m` +
718718
` 7 \ue0b5 \x1b[37;49m\ue0b4\x1b[0m`
719719
);
720720
term.writeln('');
@@ -727,7 +727,7 @@ function powerlineSymbolTest() {
727727
` 3 \ue0b7 \x1b[7;33;44m\ue0b6\x1b[27;39m` +
728728
` 4 \ue0b7 \x1b[7;34;45m\ue0b6\x1b[27;39m` +
729729
` 5 \ue0b7 \x1b[7;35;46m\ue0b6\x1b[27;39m` +
730-
` 6 \ue0b7 \x1b[7;36;47m\ue0b6\x1b[27;39m` +
730+
` 6 \ue0b7 \x1b[7;36;47m\ue0b6\x1b[27;30m` +
731731
` 7 \ue0b7 \x1b[7;37;49m\ue0b6\x1b[0m`
732732
);
733733
term.writeln('');

demo/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ <h3>Test</h3>
8787
</div>
8888
</div>
8989
</div>
90+
<div id="texture-atlas"></div>
9091
<script src="dist/client-bundle.js" defer ></script>
9192
<script>
9293
var tab = localStorage.getItem("tab");

demo/style.css

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,3 +89,17 @@ pre {
8989
max-height: 100vh;
9090
overflow-y: auto;
9191
}
92+
93+
#texture-atlas {
94+
width: 100%;
95+
height: 600px;
96+
overflow: scroll;
97+
}
98+
#texture-atlas canvas {
99+
image-rendering: pixelated;
100+
}
101+
#texture-atlas canvas:hover {
102+
/* zoom to 4x on hover */
103+
width: 4096px;
104+
height: 4096px;
105+
}

0 commit comments

Comments
 (0)