mirror of
https://github.com/wavetermdev/xterm.js.git
synced 2026-08-05 13:43:48 -07:00
Add letterSpacing option
This provides an option for fonts to be more spaced out. This is particularly useful for fonts that can seem crammed due to not allowing floating point numbers when drawing, such as Monoid or Operator Mono.
This commit is contained in:
@@ -139,6 +139,7 @@ export interface ITerminalOptions {
|
||||
fontFamily?: string;
|
||||
geometry?: [number, number];
|
||||
handler?: (data: string) => void;
|
||||
letterSpacing?: number;
|
||||
lineHeight?: number;
|
||||
rows?: number;
|
||||
screenKeys?: boolean;
|
||||
|
||||
@@ -80,6 +80,7 @@ const DEFAULT_OPTIONS: ITerminalOptions = {
|
||||
fontFamily: 'courier-new, courier, monospace',
|
||||
fontSize: 15,
|
||||
lineHeight: 1.0,
|
||||
letterSpacing: 0,
|
||||
scrollback: 1000,
|
||||
screenKeys: false,
|
||||
debug: false,
|
||||
@@ -410,6 +411,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
this.renderer.clear();
|
||||
this.charMeasure.measure(this.options);
|
||||
break;
|
||||
case 'letterSpacing':
|
||||
case 'lineHeight':
|
||||
// When the font changes the size of the cells may change which requires a renderer clear
|
||||
this.renderer.clear();
|
||||
|
||||
+2
-2
@@ -47,7 +47,7 @@ export class Viewport implements IViewport {
|
||||
*/
|
||||
private refresh(): void {
|
||||
if (this.charMeasure.height > 0) {
|
||||
this.currentRowHeight = this.terminal.renderer.dimensions.scaledLineHeight / window.devicePixelRatio;
|
||||
this.currentRowHeight = this.terminal.renderer.dimensions.scaledCellHeight / window.devicePixelRatio;
|
||||
|
||||
if (this.lastRecordedViewportHeight !== this.terminal.renderer.dimensions.canvasHeight) {
|
||||
this.lastRecordedViewportHeight = this.terminal.renderer.dimensions.canvasHeight;
|
||||
@@ -75,7 +75,7 @@ export class Viewport implements IViewport {
|
||||
this.refresh();
|
||||
} else {
|
||||
// If size has changed, refresh viewport
|
||||
if (this.terminal.renderer.dimensions.scaledLineHeight / window.devicePixelRatio !== this.currentRowHeight) {
|
||||
if (this.terminal.renderer.dimensions.scaledCellHeight / window.devicePixelRatio !== this.currentRowHeight) {
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,8 +16,10 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
protected _ctx: CanvasRenderingContext2D;
|
||||
private _scaledCharWidth: number;
|
||||
private _scaledCharHeight: number;
|
||||
private _scaledLineHeight: number;
|
||||
private _scaledLineDrawY: number;
|
||||
private _scaledCellWidth: number;
|
||||
private _scaledCellHeight: number;
|
||||
private _scaledCharLeft: number;
|
||||
private _scaledCharTop: number;
|
||||
|
||||
private _charAtlas: HTMLCanvasElement | ImageBitmap;
|
||||
|
||||
@@ -70,10 +72,12 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
}
|
||||
|
||||
public resize(terminal: ITerminal, dim: IRenderDimensions, charSizeChanged: boolean): void {
|
||||
this._scaledCellWidth = dim.scaledCellWidth;
|
||||
this._scaledCellHeight = dim.scaledCellHeight;
|
||||
this._scaledCharWidth = dim.scaledCharWidth;
|
||||
this._scaledCharHeight = dim.scaledCharHeight;
|
||||
this._scaledLineHeight = dim.scaledLineHeight;
|
||||
this._scaledLineDrawY = dim.scaledLineDrawY;
|
||||
this._scaledCharLeft = dim.scaledCharLeft;
|
||||
this._scaledCharTop = dim.scaledCharTop;
|
||||
this._canvas.width = dim.scaledCanvasWidth;
|
||||
this._canvas.height = dim.scaledCanvasHeight;
|
||||
this._canvas.style.width = `${dim.canvasWidth}px`;
|
||||
@@ -100,10 +104,10 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
*/
|
||||
protected fillCells(x: number, y: number, width: number, height: number): void {
|
||||
this._ctx.fillRect(
|
||||
x * this._scaledCharWidth,
|
||||
y * this._scaledLineHeight,
|
||||
width * this._scaledCharWidth,
|
||||
height * this._scaledLineHeight);
|
||||
x * this._scaledCellWidth,
|
||||
y * this._scaledCellHeight,
|
||||
width * this._scaledCellWidth,
|
||||
height * this._scaledCellHeight);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -114,9 +118,9 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
*/
|
||||
protected fillBottomLineAtCells(x: number, y: number, width: number = 1): void {
|
||||
this._ctx.fillRect(
|
||||
x * this._scaledCharWidth,
|
||||
(y + 1) * this._scaledLineHeight - window.devicePixelRatio - 1 /* Ensure it's drawn within the cell */,
|
||||
width * this._scaledCharWidth,
|
||||
x * this._scaledCellWidth,
|
||||
(y + 1) * this._scaledCellHeight - window.devicePixelRatio - 1 /* Ensure it's drawn within the cell */,
|
||||
width * this._scaledCellWidth,
|
||||
window.devicePixelRatio);
|
||||
}
|
||||
|
||||
@@ -128,10 +132,10 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
*/
|
||||
protected fillLeftLineAtCell(x: number, y: number): void {
|
||||
this._ctx.fillRect(
|
||||
x * this._scaledCharWidth,
|
||||
y * this._scaledLineHeight,
|
||||
x * this._scaledCellWidth,
|
||||
y * this._scaledCellHeight,
|
||||
window.devicePixelRatio,
|
||||
this._scaledLineHeight);
|
||||
this._scaledCellHeight);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -143,10 +147,10 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
protected strokeRectAtCell(x: number, y: number, width: number, height: number): void {
|
||||
this._ctx.lineWidth = window.devicePixelRatio;
|
||||
this._ctx.strokeRect(
|
||||
x * this._scaledCharWidth + window.devicePixelRatio / 2,
|
||||
y * this._scaledLineHeight + (window.devicePixelRatio / 2),
|
||||
width * this._scaledCharWidth - window.devicePixelRatio,
|
||||
(height * this._scaledLineHeight) - window.devicePixelRatio);
|
||||
x * this._scaledCellWidth + window.devicePixelRatio / 2,
|
||||
y * this._scaledCellHeight + (window.devicePixelRatio / 2),
|
||||
width * this._scaledCellWidth - window.devicePixelRatio,
|
||||
(height * this._scaledCellHeight) - window.devicePixelRatio);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -171,17 +175,17 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
protected clearCells(x: number, y: number, width: number, height: number): void {
|
||||
if (this._alpha) {
|
||||
this._ctx.clearRect(
|
||||
x * this._scaledCharWidth,
|
||||
y * this._scaledLineHeight,
|
||||
width * this._scaledCharWidth,
|
||||
height * this._scaledLineHeight);
|
||||
x * this._scaledCellWidth,
|
||||
y * this._scaledCellHeight,
|
||||
width * this._scaledCellWidth,
|
||||
height * this._scaledCellHeight);
|
||||
} else {
|
||||
this._ctx.fillStyle = this._colors.background;
|
||||
this._ctx.fillRect(
|
||||
x * this._scaledCharWidth,
|
||||
y * this._scaledLineHeight,
|
||||
width * this._scaledCharWidth,
|
||||
height * this._scaledLineHeight);
|
||||
x * this._scaledCellWidth,
|
||||
y * this._scaledCellHeight,
|
||||
width * this._scaledCellWidth,
|
||||
height * this._scaledCellHeight);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,9 +208,17 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
// can bleed into other cells. This code will clip the following fillText,
|
||||
// ensuring that its contents don't go beyond the cell bounds.
|
||||
this._ctx.beginPath();
|
||||
this._ctx.rect(x * this._scaledCharWidth, y * this._scaledLineHeight + this._scaledLineDrawY, charData[CHAR_DATA_WIDTH_INDEX] * this._scaledCharWidth, this._scaledCharHeight);
|
||||
// TODO: Make clip rect use cell size?
|
||||
this._ctx.rect(
|
||||
x * this._scaledCellWidth + this._scaledCharLeft,
|
||||
y * this._scaledCellHeight + this._scaledCharTop,
|
||||
charData[CHAR_DATA_WIDTH_INDEX] * this._scaledCharWidth,
|
||||
this._scaledCharHeight);
|
||||
this._ctx.clip();
|
||||
this._ctx.fillText(charData[CHAR_DATA_CHAR_INDEX], x * this._scaledCharWidth, y * this._scaledCharHeight);
|
||||
this._ctx.fillText(
|
||||
charData[CHAR_DATA_CHAR_INDEX],
|
||||
x * this._scaledCellWidth + this._scaledCharLeft,
|
||||
y * this._scaledCellHeight + this._scaledCharTop);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -242,8 +254,14 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
const charAtlasCellWidth = this._scaledCharWidth + CHAR_ATLAS_CELL_SPACING;
|
||||
const charAtlasCellHeight = this._scaledCharHeight + CHAR_ATLAS_CELL_SPACING;
|
||||
this._ctx.drawImage(this._charAtlas,
|
||||
code * charAtlasCellWidth, colorIndex * charAtlasCellHeight, charAtlasCellWidth, this._scaledCharHeight,
|
||||
x * this._scaledCharWidth, y * this._scaledLineHeight + this._scaledLineDrawY, this._scaledCharWidth, this._scaledCharHeight);
|
||||
code * charAtlasCellWidth,
|
||||
colorIndex * charAtlasCellHeight,
|
||||
charAtlasCellWidth,
|
||||
this._scaledCharHeight,
|
||||
x * this._scaledCellWidth + this._scaledCharLeft,
|
||||
y * this._scaledCellHeight + this._scaledCharTop,
|
||||
this._scaledCharWidth,
|
||||
this._scaledCharHeight);
|
||||
} else {
|
||||
this._drawUncachedChar(terminal, char, width, fg, x, y, bold);
|
||||
}
|
||||
@@ -285,11 +303,19 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
// can bleed into other cells. This code will clip the following fillText,
|
||||
// ensuring that its contents don't go beyond the cell bounds.
|
||||
this._ctx.beginPath();
|
||||
this._ctx.rect(0, y * this._scaledLineHeight + this._scaledLineDrawY, terminal.cols * this._scaledCharWidth, this._scaledCharHeight);
|
||||
// TODO: Why is this be clipped at char top?
|
||||
this._ctx.rect(
|
||||
0,
|
||||
y * this._scaledCellHeight + this._scaledCharTop,
|
||||
terminal.cols * this._scaledCharWidth,
|
||||
this._scaledCharHeight);
|
||||
this._ctx.clip();
|
||||
|
||||
// Draw the character
|
||||
this._ctx.fillText(char, x * this._scaledCharWidth, y * this._scaledLineHeight + this._scaledLineDrawY);
|
||||
this._ctx.fillText(
|
||||
char,
|
||||
x * this._scaledCellWidth + this._scaledCharLeft,
|
||||
y * this._scaledCellHeight + this._scaledCharTop);
|
||||
this._ctx.restore();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,8 +86,10 @@ export interface IColorSet {
|
||||
export interface IRenderDimensions {
|
||||
scaledCharWidth: number;
|
||||
scaledCharHeight: number;
|
||||
scaledLineHeight: number;
|
||||
scaledLineDrawY: number;
|
||||
scaledCellWidth: number;
|
||||
scaledCellHeight: number;
|
||||
scaledCharLeft: number;
|
||||
scaledCharTop: number;
|
||||
scaledCanvasWidth: number;
|
||||
scaledCanvasHeight: number;
|
||||
canvasWidth: number;
|
||||
|
||||
@@ -40,8 +40,8 @@ export class Renderer extends EventEmitter implements IRenderer {
|
||||
this.dimensions = {
|
||||
scaledCharWidth: null,
|
||||
scaledCharHeight: null,
|
||||
scaledLineHeight: null,
|
||||
scaledLineDrawY: null,
|
||||
scaledCellHeight: null,
|
||||
scaledCharTop: null,
|
||||
scaledCanvasWidth: null,
|
||||
scaledCanvasHeight: null,
|
||||
canvasWidth: null,
|
||||
@@ -91,20 +91,27 @@ export class Renderer extends EventEmitter implements IRenderer {
|
||||
// enough space to draw the character to the cell.
|
||||
this.dimensions.scaledCharHeight = Math.ceil(this._terminal.charMeasure.height * window.devicePixelRatio);
|
||||
|
||||
// Calculate the scaled line height, if lineHeight is not 1 then the value
|
||||
// Calculate the scaled cell height, if lineHeight is not 1 then the value
|
||||
// will be floored because since lineHeight can never be lower then 1, there
|
||||
// is a guarentee that the scaled line height will always be larger than
|
||||
// scaled char height.
|
||||
this.dimensions.scaledLineHeight = Math.floor(this.dimensions.scaledCharHeight * this._terminal.options.lineHeight);
|
||||
this.dimensions.scaledCellHeight = Math.floor(this.dimensions.scaledCharHeight * this._terminal.options.lineHeight);
|
||||
|
||||
// Calculate the y coordinate within a cell that text should draw from in
|
||||
// order to draw in the center of a cell.
|
||||
this.dimensions.scaledLineDrawY = this._terminal.options.lineHeight === 1 ? 0 : Math.round((this.dimensions.scaledLineHeight - this.dimensions.scaledCharHeight) / 2);
|
||||
this.dimensions.scaledCharTop = this._terminal.options.lineHeight === 1 ? 0 : Math.round((this.dimensions.scaledCellHeight - this.dimensions.scaledCharHeight) / 2);
|
||||
|
||||
// Calculate the scaled cell width, taking the letterSpacing into account.
|
||||
this.dimensions.scaledCellWidth = this.dimensions.scaledCharWidth + Math.round(this._terminal.options.letterSpacing);
|
||||
|
||||
// Calculate the x coordinate with a cell that text should draw from in
|
||||
// order to draw in the center of a cell.
|
||||
this.dimensions.scaledCharLeft = Math.floor(this._terminal.options.letterSpacing / 2);
|
||||
|
||||
// Recalculate the canvas dimensions; scaled* define the actual number of
|
||||
// pixel in the canvas
|
||||
this.dimensions.scaledCanvasHeight = this._terminal.rows * this.dimensions.scaledLineHeight;
|
||||
this.dimensions.scaledCanvasWidth = this._terminal.cols * this.dimensions.scaledCharWidth;
|
||||
this.dimensions.scaledCanvasHeight = this._terminal.rows * this.dimensions.scaledCellHeight;
|
||||
this.dimensions.scaledCanvasWidth = this._terminal.cols * this.dimensions.scaledCellWidth;
|
||||
|
||||
// The the size of the canvas on the page. It's very important that this
|
||||
// rounds to nearest integer and not ceils as browsers often set
|
||||
|
||||
Reference in New Issue
Block a user