diff --git a/fixtures/typings-test/typings-test.ts b/fixtures/typings-test/typings-test.ts index 0a88bd37..4718110d 100644 --- a/fixtures/typings-test/typings-test.ts +++ b/fixtures/typings-test/typings-test.ts @@ -145,6 +145,8 @@ namespace methods_core { const r21: boolean = t.getOption('enableBold'); const r22: number = t.getOption('letterSpacing'); const r23: boolean = t.getOption('macOptionIsMeta'); + const r24: string = t.getOption('fontWeight'); + const r25: string = t.getOption('fontWeightBold'); } { const t: Terminal = new Terminal(); @@ -157,7 +159,10 @@ namespace methods_core { t.setOption('cursorBlink', true); t.setOption('debug', true); t.setOption('disableStdin', true); - t.setOption('enableBold', true); + t.setOption('fontWeight', 'normal'); + t.setOption('fontWeight', 'bold'); + t.setOption('fontWeightBold', 'normal'); + t.setOption('fontWeightBold', 'bold'); t.setOption('popOnBell', true); t.setOption('screenKeys', true); t.setOption('useFlowControl', true); diff --git a/src/Interfaces.ts b/src/Interfaces.ts index 70b8509f..6d1c36d3 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -4,7 +4,7 @@ */ import { ICharset, ILinkMatcherOptions } from './Interfaces'; -import { LinkMatcherHandler, LinkMatcherValidationCallback, LineData } from './Types'; +import { LinkMatcherHandler, LinkMatcherValidationCallback, LineData, FontWeight } from './Types'; import { IColorSet, IRenderer } from './renderer/Interfaces'; import { IMouseZoneManager } from './input/Interfaces'; @@ -137,9 +137,10 @@ export interface ITerminalOptions { cursorStyle?: string; debug?: boolean; disableStdin?: boolean; - enableBold?: boolean; fontSize?: number; fontFamily?: string; + fontWeight?: FontWeight; + fontWeightBold?: FontWeight; handler?: (data: string) => void; letterSpacing?: number; lineHeight?: number; diff --git a/src/Terminal.ts b/src/Terminal.ts index b56caf83..044f5351 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -72,9 +72,10 @@ const DEFAULT_OPTIONS: ITerminalOptions = { cursorStyle: 'block', bellSound: BELL_SOUND, bellStyle: 'none', - enableBold: true, fontFamily: 'courier-new, courier, monospace', fontSize: 15, + fontWeight: 'normal', + fontWeightBold: 'bold', lineHeight: 1.0, letterSpacing: 0, scrollback: 1000, @@ -363,6 +364,16 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT value = 'block'; } break; + case 'fontWeight': + if (!value) { + value = 'normal'; + } + break; + case 'fontWeightBold': + if (!value) { + value = 'bold'; + } + break; case 'lineHeight': if (value < 1) { console.warn(`${key} cannot be less than 1, value: ${value}`); @@ -413,14 +424,16 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.renderer.clear(); this.charMeasure.measure(this.options); break; - case 'enableBold': case 'letterSpacing': case 'lineHeight': + case 'fontWeight': + case 'fontWeightBold': + const didCharSizeChange = (key === 'fontWeight' || key === 'fontWeightBold'); + // When the font changes the size of the cells may change which requires a renderer clear this.renderer.clear(); - this.renderer.onResize(this.cols, this.rows, false); + this.renderer.onResize(this.cols, this.rows, didCharSizeChange); this.refresh(0, this.rows - 1); - // this.charMeasure.measure(this.options); case 'scrollback': this.buffers.resize(this.cols, this.rows); this.viewport.syncScrollArea(); diff --git a/src/Types.ts b/src/Types.ts index 3263282e..336ac5cc 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -16,3 +16,5 @@ export enum LinkHoverEventTypes { TOOLTIP = 'linktooltip', LEAVE = 'linkleave' } + +export type FontWeight = 'normal' | 'bold' | 'bolder' | 'lighter' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900'; diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index b4becbce..977cbf52 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -201,7 +201,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { * @param color The color of the character. */ protected fillCharTrueColor(terminal: ITerminal, charData: CharData, x: number, y: number): void { - this._ctx.font = `${terminal.options.fontSize * window.devicePixelRatio}px ${terminal.options.fontFamily}`; + this._ctx.font = this._getFont(terminal, false); this._ctx.textBaseline = 'top'; this._clipRow(terminal, y); this._ctx.fillText( @@ -230,7 +230,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { colorIndex = fg + 2; } else { // If default color and bold - if (bold && terminal.options.enableBold) { + if (bold) { colorIndex = 1; } } @@ -251,13 +251,6 @@ export abstract class BaseRenderLayer implements IRenderLayer { this._ctx.globalAlpha = DIM_OPACITY; } - // Draw the non-bold version of the same color if bold is not enabled - if (bold && !terminal.options.enableBold) { - // Ignore default color as it's not touched above - if (colorIndex > 1) { - colorIndex -= 8; - } - } this._ctx.drawImage(this._charAtlas, code * charAtlasCellWidth, @@ -289,10 +282,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { */ private _drawUncachedChar(terminal: ITerminal, char: string, width: number, fg: number, x: number, y: number, bold: boolean, dim: boolean): void { this._ctx.save(); - this._ctx.font = `${terminal.options.fontSize * window.devicePixelRatio}px ${terminal.options.fontFamily}`; - if (bold && terminal.options.enableBold) { - this._ctx.font = `bold ${this._ctx.font}`; - } + this._ctx.font = this._getFont(terminal, bold); this._ctx.textBaseline = 'top'; if (fg === INVERTED_DEFAULT_COLOR) { @@ -332,5 +322,16 @@ export abstract class BaseRenderLayer implements IRenderLayer { this._scaledCellHeight); this._ctx.clip(); } + + /** + * Gets the current font. + * @param terminal The terminal. + * @param isBold If we should use the bold fontWeight. + */ + protected _getFont(terminal: ITerminal, isBold: boolean): string { + const fontWeight = isBold ? terminal.options.fontWeightBold : terminal.options.fontWeight; + + return `${fontWeight} ${terminal.options.fontSize * window.devicePixelRatio}px ${terminal.options.fontFamily}`; + } } diff --git a/src/renderer/CharAtlas.ts b/src/renderer/CharAtlas.ts index 9ac1e161..1458ba76 100644 --- a/src/renderer/CharAtlas.ts +++ b/src/renderer/CharAtlas.ts @@ -12,6 +12,8 @@ export const CHAR_ATLAS_CELL_SPACING = 1; interface ICharAtlasConfig { fontSize: number; fontFamily: string; + fontWeight: string; + fontWeightBold: string; scaledCharWidth: number; scaledCharHeight: number; colors: IColorSet; @@ -64,7 +66,7 @@ export function acquireCharAtlas(terminal: ITerminal, colors: IColorSet, scaledC } const newEntry: ICharAtlasCacheEntry = { - bitmap: generator.generate(scaledCharWidth, scaledCharHeight, terminal.options.fontSize, terminal.options.fontFamily, colors.background, colors.foreground, colors.ansi), + bitmap: generator.generate(scaledCharWidth, scaledCharHeight, terminal.options.fontSize, terminal.options.fontFamily, terminal.options.fontWeight, terminal.options.fontWeightBold, colors.background, colors.foreground, colors.ansi), config: newConfig, ownedBy: [terminal] }; @@ -86,6 +88,8 @@ function generateConfig(scaledCharWidth: number, scaledCharHeight: number, termi scaledCharHeight, fontFamily: terminal.options.fontFamily, fontSize: terminal.options.fontSize, + fontWeight: terminal.options.fontWeight, + fontWeightBold: terminal.options.fontWeightBold, colors: clonedColors }; } @@ -98,6 +102,8 @@ function configEquals(a: ICharAtlasConfig, b: ICharAtlasConfig): boolean { } return a.fontFamily === b.fontFamily && a.fontSize === b.fontSize && + a.fontWeight === b.fontWeight && + a.fontWeightBold === b.fontWeightBold && a.scaledCharWidth === b.scaledCharWidth && a.scaledCharHeight === b.scaledCharHeight && a.colors.foreground === b.colors.foreground && @@ -126,7 +132,7 @@ class CharAtlasGenerator { this._ctx.scale(window.devicePixelRatio, window.devicePixelRatio); } - public generate(scaledCharWidth: number, scaledCharHeight: number, fontSize: number, fontFamily: string, background: string, foreground: string, ansiColors: string[]): HTMLCanvasElement | Promise { + public generate(scaledCharWidth: number, scaledCharHeight: number, fontSize: number, fontFamily: string, fontWeight: string, fontWeightBold: string, background: string, foreground: string, ansiColors: string[]): HTMLCanvasElement | Promise { const cellWidth = scaledCharWidth + CHAR_ATLAS_CELL_SPACING; const cellHeight = scaledCharHeight + CHAR_ATLAS_CELL_SPACING; this._canvas.width = 255 * cellWidth; @@ -137,7 +143,7 @@ class CharAtlasGenerator { this._ctx.save(); this._ctx.fillStyle = foreground; - this._ctx.font = `${fontSize * window.devicePixelRatio}px ${fontFamily}`; + this._ctx.font = this._getFont(fontWeight, fontSize, fontFamily); this._ctx.textBaseline = 'top'; // Default color @@ -151,7 +157,7 @@ class CharAtlasGenerator { } // Default color bold this._ctx.save(); - this._ctx.font = `bold ${this._ctx.font}`; + this._ctx.font = this._getFont(fontWeightBold, fontSize, fontFamily); for (let i = 0; i < 256; i++) { this._ctx.save(); this._ctx.beginPath(); @@ -163,11 +169,11 @@ class CharAtlasGenerator { this._ctx.restore(); // Colors 0-15 - this._ctx.font = `${fontSize * window.devicePixelRatio}px ${fontFamily}`; + this._ctx.font = this._getFont(fontWeight, fontSize, fontFamily); for (let colorIndex = 0; colorIndex < 16; colorIndex++) { // colors 8-15 are bold if (colorIndex === 8) { - this._ctx.font = `bold ${this._ctx.font}`; + this._ctx.font = this._getFont(fontWeightBold, fontSize, fontFamily); } const y = (colorIndex + 2) * cellHeight; // Draw ascii characters @@ -219,4 +225,8 @@ class CharAtlasGenerator { } } } + + private _getFont(fontWeight: string, fontSize: number, fontFamily: string): string { + return `${fontWeight} ${fontSize * window.devicePixelRatio}px ${fontFamily}`; + } } diff --git a/src/renderer/TextRenderLayer.ts b/src/renderer/TextRenderLayer.ts index 99a6feae..141d9823 100644 --- a/src/renderer/TextRenderLayer.ts +++ b/src/renderer/TextRenderLayer.ts @@ -33,7 +33,7 @@ export class TextRenderLayer extends BaseRenderLayer { super.resize(terminal, dim, charSizeChanged); // Clear the character width cache if the font or width has changed - const terminalFont = `${terminal.options.fontSize * window.devicePixelRatio}px ${terminal.options.fontFamily}`; + const terminalFont = this._getFont(terminal, false); if (this._characterWidth !== dim.scaledCharWidth || this._characterFont !== terminalFont) { this._characterWidth = dim.scaledCharWidth; this._characterFont = terminalFont; @@ -166,7 +166,7 @@ export class TextRenderLayer extends BaseRenderLayer { this._ctx.save(); if (flags & FLAGS.BOLD) { - this._ctx.font = `bold ${this._ctx.font}`; + this._ctx.font = this._getFont(terminal, true); // Convert the FG color to the bold variant if (fg < 8) { fg += 8; diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 43842bad..7c8612cf 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -404,7 +404,7 @@ declare module 'xterm' { * Retrieves an option's value from the terminal. * @param key The option key. */ - getOption(key: 'bellSound' | 'bellStyle' | 'cursorStyle' | 'fontFamily' | 'termName'): string; + getOption(key: 'bellSound' | 'bellStyle' | 'cursorStyle' | 'fontFamily' | 'fontWeight' | 'fontWeightBold'| 'termName'): string; /** * Retrieves an option's value from the terminal. * @param key The option key. @@ -437,6 +437,12 @@ declare module 'xterm' { * @param value The option value. */ setOption(key: 'fontFamily' | 'termName' | 'bellSound', value: string): void; + /** + * Sets an option on the terminal. + * @param key The option key. + * @param value The option value. + */ + setOption(key: 'fontWeight' | 'fontWeightBold', value: null | 'normal' | 'bold' | 'bolder' | 'lighter' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900'): void; /** * Sets an option on the terminal. * @param key The option key.