Skip to content
Open
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
12 changes: 7 additions & 5 deletions addons/addon-webgl/src/WebglRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -623,14 +623,16 @@ export class WebglRenderer extends Disposable implements IRenderer {
// cell.
this.dimensions.device.char.height = Math.ceil(this._charSizeService.height * this._devicePixelRatio);

// Calculate the device cell height, if lineHeight is _not_ 1, the resulting value will be
// floored since lineHeight can never be lower then 1, this guarentees the device cell height
// will always be larger than device char height.
this.dimensions.device.cell.height = Math.floor(this.dimensions.device.char.height * this._optionsService.rawOptions.lineHeight);
// Calculate the device cell height. Values >= 8 are treated as absolute pixel heights
// (Monaco convention), values < 8 are treated as multipliers of the character height.
const lineHeight = this._optionsService.rawOptions.lineHeight;
this.dimensions.device.cell.height = lineHeight >= 8
? Math.max(Math.floor(lineHeight * this._devicePixelRatio), this.dimensions.device.char.height)
: Math.floor(this.dimensions.device.char.height * lineHeight);

// Calculate the y offset within a cell that glyph should draw at in order for it to be centered
// correctly within the cell.
this.dimensions.device.char.top = this._optionsService.rawOptions.lineHeight === 1 ? 0 : Math.round((this.dimensions.device.cell.height - this.dimensions.device.char.height) / 2);
this.dimensions.device.char.top = this.dimensions.device.cell.height === this.dimensions.device.char.height ? 0 : Math.round((this.dimensions.device.cell.height - this.dimensions.device.char.height) / 2);

// Calculate the device cell width, taking the letterSpacing into account.
this.dimensions.device.cell.width = this.dimensions.device.char.width + Math.round(this._optionsService.rawOptions.letterSpacing);
Expand Down
7 changes: 6 additions & 1 deletion src/browser/renderer/dom/DomRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,12 @@ export class DomRenderer extends Disposable implements IRenderer {
this.dimensions.device.char.width = this._charSizeService.width * dpr;
this.dimensions.device.char.height = Math.ceil(this._charSizeService.height * dpr);
this.dimensions.device.cell.width = this.dimensions.device.char.width + Math.round(this._optionsService.rawOptions.letterSpacing);
this.dimensions.device.cell.height = Math.floor(this.dimensions.device.char.height * this._optionsService.rawOptions.lineHeight);
// Values >= 8 are treated as absolute pixel heights (Monaco convention),
// values < 8 are treated as multipliers of the character height.
const lineHeight = this._optionsService.rawOptions.lineHeight;
this.dimensions.device.cell.height = lineHeight >= 8
? Math.max(Math.floor(lineHeight * dpr), this.dimensions.device.char.height)
: Math.floor(this.dimensions.device.char.height * lineHeight);
this.dimensions.device.char.left = 0;
this.dimensions.device.char.top = 0;
this.dimensions.device.canvas.width = this.dimensions.device.cell.width * this._bufferService.cols;
Expand Down
30 changes: 30 additions & 0 deletions src/common/services/OptionsService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,36 @@ describe('OptionsService', () => {
});
});
});
describe('lineHeight', () => {
let service: OptionsService;
beforeEach(() => {
service = new OptionsService({});
});
it('should default to 1', () => {
assert.strictEqual(service.options.lineHeight, 1);
});
it('should accept multiplier values (< 8)', () => {
service.options.lineHeight = 1.5;
assert.strictEqual(service.options.lineHeight, 1.5);
});
it('should accept pixel values (>= 8)', () => {
service.options.lineHeight = 20;
assert.strictEqual(service.options.lineHeight, 20);
});
it('should accept boundary value 8 as pixel height', () => {
service.options.lineHeight = 8;
assert.strictEqual(service.options.lineHeight, 8);
});
it('should accept 7.9 as multiplier', () => {
service.options.lineHeight = 7.9;
assert.strictEqual(service.options.lineHeight, 7.9);
});
it('should reject values less than 1', () => {
assert.throws(() => { service.options.lineHeight = 0.5; });
assert.throws(() => { service.options.lineHeight = 0; });
assert.throws(() => { service.options.lineHeight = -1; });
});
});
describe('onMultipleOptionChange', () => {
let service: OptionsService;
beforeEach(() => {
Expand Down
8 changes: 7 additions & 1 deletion src/common/services/OptionsService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,12 +178,18 @@ export class OptionsService extends Disposable implements IOptionsService {
case 'cursorWidth':
value = Math.floor(value);
// Fall through for bounds check
case 'lineHeight':
case 'tabStopWidth':
if (value < 1) {
throw new Error(`${key} cannot be less than 1, value: ${value}`);
}
break;
case 'lineHeight':
// Values >= 8 are treated as absolute pixel heights (Monaco convention),
// values < 8 are treated as multipliers and must be >= 1.
if (value < 1) {
throw new Error(`${key} cannot be less than 1, value: ${value}`);
}
break;
case 'minimumContrastRatio':
value = Math.max(1, Math.min(21, Math.round(value * 10) / 10));
break;
Expand Down
5 changes: 4 additions & 1 deletion typings/xterm.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,10 @@ declare module '@xterm/xterm' {
letterSpacing?: number;

/**
* The line height used to render text.
* The line height used to render text. When the value is less than 8, it is
* treated as a multiplier of the character height (e.g. `1.2`). When the
* value is 8 or greater, it is treated as an absolute pixel height. This
* follows the same convention as Monaco editor's `lineHeight` option.
*/
lineHeight?: number;

Expand Down