diff --git a/demo/main.js b/demo/main.js index a9b1165590..a44e5271f3 100644 --- a/demo/main.js +++ b/demo/main.js @@ -182,7 +182,7 @@ function initOptions(term) { bellSound: null, bellStyle: ['none', 'sound'], cursorStyle: ['block', 'underline', 'bar'], - experimentalCharAtlas: ['none', 'static', 'dynamic'], + experimentalCharAtlas: ['none', 'dynamic'], fontFamily: null, fontWeight: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'], fontWeightBold: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'] diff --git a/src/Terminal.ts b/src/Terminal.ts index e836629041..d027f0db24 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -85,7 +85,7 @@ const DEFAULT_OPTIONS: ITerminalOptions = { bellStyle: 'none', drawBoldTextInBrightColors: true, enableBold: true, - experimentalCharAtlas: 'static', + experimentalCharAtlas: 'dynamic', fontFamily: 'courier-new, courier, monospace', fontSize: 15, fontWeight: 'normal', diff --git a/src/renderer/atlas/BaseCharAtlas.ts b/src/renderer/atlas/BaseCharAtlas.ts index 50d35faa6f..331d74d555 100644 --- a/src/renderer/atlas/BaseCharAtlas.ts +++ b/src/renderer/atlas/BaseCharAtlas.ts @@ -4,6 +4,7 @@ */ import { IGlyphIdentifier } from './Types'; +import { IColor } from '../../shared/Types'; export default abstract class BaseCharAtlas { private _didWarmUp: boolean = false; @@ -51,3 +52,24 @@ export default abstract class BaseCharAtlas { y: number ): boolean; } + +/** + * Makes a partiicular rgb color in an ImageData completely transparent. + * @returns True if the result is "empty", meaning all pixels are fully transparent. + */ +export function clearColor(imageData: ImageData, color: IColor): boolean { + let isEmpty = true; + const r = color.rgba >>> 24; + const g = color.rgba >>> 16 & 0xFF; + const b = color.rgba >>> 8 & 0xFF; + for (let offset = 0; offset < imageData.data.length; offset += 4) { + if (imageData.data[offset] === r && + imageData.data[offset + 1] === g && + imageData.data[offset + 2] === b) { + imageData.data[offset + 3] = 0; + } else { + isEmpty = false; + } + } + return isEmpty; +} diff --git a/src/renderer/atlas/CharAtlasCache.ts b/src/renderer/atlas/CharAtlasCache.ts index eee93d6cba..c835de50f1 100644 --- a/src/renderer/atlas/CharAtlasCache.ts +++ b/src/renderer/atlas/CharAtlasCache.ts @@ -10,11 +10,9 @@ import { generateConfig, configEquals } from './CharAtlasUtils'; import BaseCharAtlas from './BaseCharAtlas'; import DynamicCharAtlas from './DynamicCharAtlas'; import NoneCharAtlas from './NoneCharAtlas'; -import StaticCharAtlas from './StaticCharAtlas'; const charAtlasImplementations = { 'none': NoneCharAtlas, - 'static': StaticCharAtlas, 'dynamic': DynamicCharAtlas }; diff --git a/src/renderer/atlas/DynamicCharAtlas.ts b/src/renderer/atlas/DynamicCharAtlas.ts index e919e362b7..973ec115be 100644 --- a/src/renderer/atlas/DynamicCharAtlas.ts +++ b/src/renderer/atlas/DynamicCharAtlas.ts @@ -6,9 +6,8 @@ import { DIM_OPACITY, IGlyphIdentifier, INVERTED_DEFAULT_COLOR } from './Types'; import { ICharAtlasConfig } from '../../shared/atlas/Types'; import { IColor } from '../../shared/Types'; -import BaseCharAtlas from './BaseCharAtlas'; +import BaseCharAtlas, { clearColor } from './BaseCharAtlas'; import { DEFAULT_ANSI_COLORS } from '../ColorManager'; -import { clearColor } from '../../shared/atlas/CharAtlasGenerator'; import LRUMap from './LRUMap'; // In practice we're probably never going to exhaust a texture this large. For debugging purposes, diff --git a/src/renderer/atlas/StaticCharAtlas.ts b/src/renderer/atlas/StaticCharAtlas.ts deleted file mode 100644 index b6de82fc35..0000000000 --- a/src/renderer/atlas/StaticCharAtlas.ts +++ /dev/null @@ -1,100 +0,0 @@ -/** - * Copyright (c) 2017 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { DIM_OPACITY, IGlyphIdentifier } from './Types'; -import { CHAR_ATLAS_CELL_SPACING, ICharAtlasConfig } from '../../shared/atlas/Types'; -import { generateStaticCharAtlasTexture } from '../../shared/atlas/CharAtlasGenerator'; -import BaseCharAtlas from './BaseCharAtlas'; - -export default class StaticCharAtlas extends BaseCharAtlas { - private _texture: HTMLCanvasElement | ImageBitmap; - - constructor(private _document: Document, private _config: ICharAtlasConfig) { - super(); - } - - private _canvasFactory = (width: number, height: number) => { - const canvas = this._document.createElement('canvas'); - canvas.width = width; - canvas.height = height; - - // This is useful for debugging - // document.body.appendChild(canvas); - - return canvas; - } - - protected _doWarmUp(): void { - const result = generateStaticCharAtlasTexture(window, this._canvasFactory, this._config); - if (result instanceof HTMLCanvasElement) { - this._texture = result; - } else { - result.then(texture => { - this._texture = texture; - }); - } - } - - private _isCached(glyph: IGlyphIdentifier, colorIndex: number): boolean { - const isAscii = glyph.code < 256; - // A color is basic if it is one of the 4 bit ANSI colors. - const isBasicColor = glyph.fg < 16; - const isDefaultColor = glyph.fg >= 256; - const isDefaultBackground = glyph.bg >= 256; - return isAscii && (isBasicColor || isDefaultColor) && isDefaultBackground && !glyph.italic; - } - - public draw( - ctx: CanvasRenderingContext2D, - glyph: IGlyphIdentifier, - x: number, - y: number - ): boolean { - // we're not warmed up yet - if (this._texture == null) { - return false; - } - - let colorIndex = 0; - if (glyph.fg < 256) { - colorIndex = 2 + glyph.fg + (glyph.bold ? 16 : 0); - } else { - // If default color and bold - if (glyph.bold) { - colorIndex = 1; - } - } - if (!this._isCached(glyph, colorIndex)) { - return false; - } - - ctx.save(); - - // ImageBitmap's draw about twice as fast as from a canvas - const charAtlasCellWidth = this._config.scaledCharWidth + CHAR_ATLAS_CELL_SPACING; - const charAtlasCellHeight = this._config.scaledCharHeight + CHAR_ATLAS_CELL_SPACING; - - // Apply alpha to dim the character - if (glyph.dim) { - ctx.globalAlpha = DIM_OPACITY; - } - - ctx.drawImage( - this._texture, - glyph.code * charAtlasCellWidth, - colorIndex * charAtlasCellHeight, - charAtlasCellWidth, - this._config.scaledCharHeight, - x, - y, - charAtlasCellWidth, - this._config.scaledCharHeight - ); - - ctx.restore(); - - return true; - } -} diff --git a/src/shared/atlas/CharAtlasGenerator.ts b/src/shared/atlas/CharAtlasGenerator.ts deleted file mode 100644 index f78a6d416c..0000000000 --- a/src/shared/atlas/CharAtlasGenerator.ts +++ /dev/null @@ -1,143 +0,0 @@ -/** - * Copyright (c) 2018 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { FontWeight } from 'xterm'; -import { CHAR_ATLAS_CELL_SPACING, ICharAtlasConfig } from './Types'; -import { IColor } from '../Types'; -import { isFirefox, isSafari } from '../utils/Browser'; - -declare const Promise: any; - -export interface IOffscreenCanvas { - width: number; - height: number; - getContext(type: '2d', config?: Canvas2DContextAttributes): CanvasRenderingContext2D; - transferToImageBitmap(): ImageBitmap; -} - -/** - * Generates a char atlas. - * @param context The window or worker context. - * @param canvasFactory A function to generate a canvas with a width or height. - * @param config The config for the new char atlas. - */ -export function generateStaticCharAtlasTexture(context: Window, canvasFactory: (width: number, height: number) => HTMLCanvasElement | IOffscreenCanvas, config: ICharAtlasConfig): HTMLCanvasElement | Promise { - const cellWidth = config.scaledCharWidth + CHAR_ATLAS_CELL_SPACING; - const cellHeight = config.scaledCharHeight + CHAR_ATLAS_CELL_SPACING; - const canvas = canvasFactory( - /*255 ascii chars*/255 * cellWidth, - (/*default+default bold*/2 + /*0-15*/16 + /*0-15 bold*/16) * cellHeight - ); - const ctx = canvas.getContext('2d', {alpha: config.allowTransparency}); - - ctx.fillStyle = config.colors.background.css; - ctx.fillRect(0, 0, canvas.width, canvas.height); - - ctx.save(); - ctx.fillStyle = config.colors.foreground.css; - ctx.font = getFont(config.fontWeight, config); - ctx.textBaseline = 'top'; - - // Default color - for (let i = 0; i < 256; i++) { - ctx.save(); - ctx.beginPath(); - ctx.rect(i * cellWidth, 0, cellWidth, cellHeight); - ctx.clip(); - ctx.fillText(String.fromCharCode(i), i * cellWidth, 0); - ctx.restore(); - } - // Default color bold - ctx.save(); - ctx.font = getFont(config.fontWeightBold, config); - for (let i = 0; i < 256; i++) { - ctx.save(); - ctx.beginPath(); - ctx.rect(i * cellWidth, cellHeight, cellWidth, cellHeight); - ctx.clip(); - ctx.fillText(String.fromCharCode(i), i * cellWidth, cellHeight); - ctx.restore(); - } - ctx.restore(); - - // Colors 0-15 - ctx.font = getFont(config.fontWeight, config); - for (let colorIndex = 0; colorIndex < 16; colorIndex++) { - const y = (colorIndex + 2) * cellHeight; - // Draw ascii characters - for (let i = 0; i < 256; i++) { - ctx.save(); - ctx.beginPath(); - ctx.rect(i * cellWidth, y, cellWidth, cellHeight); - ctx.clip(); - ctx.fillStyle = config.colors.ansi[colorIndex].css; - ctx.fillText(String.fromCharCode(i), i * cellWidth, y); - ctx.restore(); - } - } - - // Colors 0-15 bold - ctx.font = getFont(config.fontWeightBold, config); - for (let colorIndex = 0; colorIndex < 16; colorIndex++) { - const y = (colorIndex + 2 + 16) * cellHeight; - // Draw ascii characters - for (let i = 0; i < 256; i++) { - ctx.save(); - ctx.beginPath(); - ctx.rect(i * cellWidth, y, cellWidth, cellHeight); - ctx.clip(); - ctx.fillStyle = config.colors.ansi[colorIndex].css; - ctx.fillText(String.fromCharCode(i), i * cellWidth, y); - ctx.restore(); - } - } - ctx.restore(); - - // Support is patchy for createImageBitmap at the moment, pass a canvas back - // if support is lacking as drawImage works there too. Firefox is also - // included here as ImageBitmap appears both buggy and has horrible - // performance (tested on v55). - if (!('createImageBitmap' in context) || isFirefox || isSafari) { - // Don't attempt to clear background colors if createImageBitmap is not supported - if (canvas instanceof HTMLCanvasElement) { - // Just return the HTMLCanvas if it's a HTMLCanvasElement - return canvas; - } - // Transfer to an ImageBitmap is this is an OffscreenCanvas - return new Promise((r: (bitmap: ImageBitmap) => void) => r(canvas.transferToImageBitmap())); - } - - const charAtlasImageData = ctx.getImageData(0, 0, canvas.width, canvas.height); - - // Remove the background color from the image so characters may overlap - clearColor(charAtlasImageData, config.colors.background); - - return context.createImageBitmap(charAtlasImageData); -} - -/** - * Makes a partiicular rgb color in an ImageData completely transparent. - * @returns True if the result is "empty", meaning all pixels are fully transparent. - */ -export function clearColor(imageData: ImageData, color: IColor): boolean { - let isEmpty = true; - const r = color.rgba >>> 24; - const g = color.rgba >>> 16 & 0xFF; - const b = color.rgba >>> 8 & 0xFF; - for (let offset = 0; offset < imageData.data.length; offset += 4) { - if (imageData.data[offset] === r && - imageData.data[offset + 1] === g && - imageData.data[offset + 2] === b) { - imageData.data[offset + 3] = 0; - } else { - isEmpty = false; - } - } - return isEmpty; -} - -function getFont(fontWeight: FontWeight, config: ICharAtlasConfig): string { - return `${fontWeight} ${config.fontSize * config.devicePixelRatio}px ${config.fontFamily}`; -} diff --git a/src/shared/atlas/Types.ts b/src/shared/atlas/Types.ts index 25eaa716e4..346e5c6e15 100644 --- a/src/shared/atlas/Types.ts +++ b/src/shared/atlas/Types.ts @@ -9,7 +9,7 @@ import { IColorSet } from '../Types'; export const CHAR_ATLAS_CELL_SPACING = 1; export interface ICharAtlasConfig { - type: 'none' | 'static' | 'dynamic'; + type: 'none' | 'dynamic'; devicePixelRatio: number; fontSize: number; fontFamily: string; diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index b4df921032..b5646961f0 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -77,17 +77,10 @@ declare module 'xterm' { * artifacts. * * - 'none': Don't use an atlas. - * - 'static': Generate an atlas when the terminal starts or is reconfigured. This atlas will - * only contain ASCII characters in 16 colors. * - 'dynamic': Generate an atlas using a LRU cache as characters are requested. Limited to - * ASCII characters (for now), but supports 256 colors. For characters covered by the static - * cache, it's slightly slower in comparison, since there's more overhead involved in - * managing the cache. - * - * Currently defaults to 'static'. This option may be removed in the future. If it is, passed - * parameters will be ignored. + * ASCII characters (for now), but supports 256 colors. (recommended) */ - experimentalCharAtlas?: 'none' | 'static' | 'dynamic'; + experimentalCharAtlas?: 'none' | 'dynamic'; /** * The font size used to render text. @@ -474,7 +467,7 @@ declare module 'xterm' { * (EXPERIMENTAL) Registers a character joiner, allowing custom sequences of * characters to be rendered as a single unit. This is useful in particular * for rendering ligatures and graphemes, among other things. - * + * * Each registered character joiner is called with a string of text * representing a portion of a line in the terminal that can be rendered as * a single unit. The joiner must return a sorted array, where each entry is @@ -483,16 +476,16 @@ declare module 'xterm' { * a single unit. When multiple joiners are provided, the results of each * are collected. If there are any overlapping substrings between them, they * are combined into one larger unit that is drawn together. - * + * * All character joiners that are registered get called every time a line is * rendered in the terminal, so it is essential for the handler function to * run as quickly as possible to avoid slowdowns when rendering. Similarly, * joiners should strive to return the smallest possible substrings to * render together, since they aren't drawn as optimally as individual * characters. - * + * * NOTE: character joiners are only used by the canvas renderer. - * + * * @param handler The function that determines character joins. It is called * with a string of text that is eligible for joining and returns an array * where each entry is an array containing the start (inclusive) and end