diff --git a/demo/main.js b/demo/main.js
index 7feb793966..3c8add277b 100644
--- a/demo/main.js
+++ b/demo/main.js
@@ -30,6 +30,7 @@ var terminalContainer = document.getElementById('terminal-container'),
macOptionIsMeta: document.querySelector('#option-mac-option-is-meta'),
scrollback: document.querySelector('#option-scrollback'),
tabstopwidth: document.querySelector('#option-tabstopwidth'),
+ experimentalCharAtlas: document.querySelector('#option-experimental-char-atlas'),
bellStyle: document.querySelector('#option-bell-style'),
screenReaderMode: document.querySelector('#option-screen-reader-mode')
},
@@ -87,6 +88,9 @@ optionElements.scrollback.addEventListener('change', function () {
optionElements.tabstopwidth.addEventListener('change', function () {
term.setOption('tabStopWidth', parseInt(optionElements.tabstopwidth.value, 10));
});
+optionElements.experimentalCharAtlas.addEventListener('change', function () {
+ term.setOption('experimentalCharAtlas', optionElements.experimentalCharAtlas.value);
+});
optionElements.screenReaderMode.addEventListener('change', function () {
term.setOption('screenReaderMode', optionElements.screenReaderMode.checked);
});
diff --git a/src/Terminal.ts b/src/Terminal.ts
index 4c41fb5eea..dafa6dfed7 100644
--- a/src/Terminal.ts
+++ b/src/Terminal.ts
@@ -75,6 +75,7 @@ const DEFAULT_OPTIONS: ITerminalOptions = {
bellSound: BELL_SOUND,
bellStyle: 'none',
enableBold: true,
+ experimentalCharAtlas: 'static',
fontFamily: 'courier-new, courier, monospace',
fontSize: 15,
fontWeight: 'normal',
@@ -437,6 +438,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
this.renderer.clear();
this.charMeasure.measure(this.options);
break;
+ case 'experimentalCharAtlas':
case 'enableBold':
case 'letterSpacing':
case 'lineHeight':
diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts
index 9423f65d39..9ab6938bdf 100644
--- a/src/renderer/BaseRenderLayer.ts
+++ b/src/renderer/BaseRenderLayer.ts
@@ -5,11 +5,11 @@
import { IRenderLayer, IColorSet, IRenderDimensions } from './Types';
import { CharData, ITerminal, ITerminalOptions } from '../Types';
-import { acquireCharAtlas, CHAR_ATLAS_CELL_SPACING } from './CharAtlas';
import { CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX } from '../Buffer';
+import { ICharAtlas, INVERTED_DEFAULT_COLOR, DIM_OPACITY } from './atlas/CharAtlasUtils';
+import { acquireCharAtlas } from './atlas/CharAtlasCache';
-export const INVERTED_DEFAULT_COLOR = -1;
-const DIM_OPACITY = 0.5;
+export {INVERTED_DEFAULT_COLOR, DIM_OPACITY};
export abstract class BaseRenderLayer implements IRenderLayer {
private _canvas: HTMLCanvasElement;
@@ -21,7 +21,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
private _scaledCharLeft: number = 0;
private _scaledCharTop: number = 0;
- private _charAtlas: HTMLCanvasElement | ImageBitmap;
+ private _charAtlas: ICharAtlas;
constructor(
private _container: HTMLElement,
@@ -84,16 +84,11 @@ export abstract class BaseRenderLayer implements IRenderLayer {
if (this._scaledCharWidth <= 0 && this._scaledCharHeight <= 0) {
return;
}
- this._charAtlas = null;
- const result = acquireCharAtlas(terminal, colorSet, this._scaledCharWidth, this._scaledCharHeight);
- if (result instanceof HTMLCanvasElement) {
- this._charAtlas = result;
- } else {
- result.then(bitmap => this._charAtlas = bitmap);
- }
+ this._charAtlas = acquireCharAtlas(terminal, colorSet, this._scaledCharWidth, this._scaledCharHeight);
+ this._charAtlas.warmUp();
}
- public resize(terminal: ITerminal, dim: IRenderDimensions, charSizeChanged: boolean): void {
+ public resize(terminal: ITerminal, dim: IRenderDimensions): void {
this._scaledCellWidth = dim.scaledCellWidth;
this._scaledCellHeight = dim.scaledCellHeight;
this._scaledCharWidth = dim.scaledCharWidth;
@@ -110,9 +105,8 @@ export abstract class BaseRenderLayer implements IRenderLayer {
this.clearAll();
}
- if (charSizeChanged) {
- this._refreshCharAtlas(terminal, this._colors);
- }
+ // Call this unconditionally. We cache char atlases, so it should be fairly cheap.
+ this._refreshCharAtlas(terminal, this._colors);
}
public abstract reset(terminal: ITerminal): void;
@@ -255,46 +249,16 @@ export abstract class BaseRenderLayer implements IRenderLayer {
colorIndex = 1;
}
}
- const isAscii = code < 256;
- // A color is basic if it is one of the standard normal or bold weight
- // colors of the characters held in the char atlas. Note that this excludes
- // the normal weight _light_ color characters.
- const isBasicColor = (colorIndex > 1 && fg < 16) && (fg < 8 || bold);
- const isDefaultColor = fg >= 256;
- const isDefaultBackground = bg >= 256;
- if (this._charAtlas && isAscii && (isBasicColor || isDefaultColor) && isDefaultBackground) {
- // ImageBitmap's draw about twice as fast as from a canvas
- const charAtlasCellWidth = this._scaledCharWidth + CHAR_ATLAS_CELL_SPACING;
- const charAtlasCellHeight = this._scaledCharHeight + CHAR_ATLAS_CELL_SPACING;
-
- // Apply alpha to dim the character
- if (dim) {
- 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,
- colorIndex * charAtlasCellHeight,
- charAtlasCellWidth,
- this._scaledCharHeight,
- x * this._scaledCellWidth + this._scaledCharLeft,
- y * this._scaledCellHeight + this._scaledCharTop,
- charAtlasCellWidth,
- this._scaledCharHeight);
- } else {
+ const atlasDidDraw = this._charAtlas && this._charAtlas.draw(
+ this._ctx,
+ {char, bg, fg, bold: bold && terminal.options.enableBold, dim},
+ x * this._scaledCellWidth + this._scaledCharLeft,
+ y * this._scaledCellHeight + this._scaledCharTop
+ );
+
+ if (!atlasDidDraw) {
this._drawUncachedChar(terminal, char, width, fg, x, y, bold && terminal.options.enableBold, dim);
}
- // This draws the atlas (for debugging purposes)
- // this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height);
- // this._ctx.drawImage(this._charAtlas, 0, 0);
}
/**
diff --git a/src/renderer/atlas/CharAtlasCache.ts b/src/renderer/atlas/CharAtlasCache.ts
new file mode 100644
index 0000000000..9448be2c92
--- /dev/null
+++ b/src/renderer/atlas/CharAtlasCache.ts
@@ -0,0 +1,88 @@
+/**
+ * Copyright (c) 2017 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { ITerminal } from '../../Types';
+import {
+ ICharAtlas,
+ ICharAtlasConfig,
+ IGlyphIdentifier,
+ generateConfig,
+ configEquals,
+} from './CharAtlasUtils';
+import NoneCharAtlas from './NoneCharAtlas';
+import DynamicCharAtlas from './DynamicCharAtlas';
+import { IColorSet } from '../Types';
+
+const charAtlasImplementations = {
+ 'none': NoneCharAtlas,
+ 'static': NoneCharAtlas, // TODO: implement
+ 'dynamic': DynamicCharAtlas,
+};
+
+interface ICharAtlasCacheEntry {
+ atlas: ICharAtlas;
+ config: ICharAtlasConfig;
+ // N.B. This implementation potentially holds onto copies of the terminal forever, so
+ // this may cause memory leaks.
+ ownedBy: ITerminal[];
+}
+
+const charAtlasCache: ICharAtlasCacheEntry[] = [];
+
+/**
+ * Acquires a char atlas, either generating a new one or returning an existing
+ * one that is in use by another terminal.
+ *
+ * @param terminal The terminal.
+ * @param colors The colors to use.
+ */
+export function acquireCharAtlas(
+ terminal: ITerminal,
+ colors: IColorSet,
+ scaledCharWidth: number,
+ scaledCharHeight: number,
+): ICharAtlas {
+ const newConfig = generateConfig(scaledCharWidth, scaledCharHeight, terminal, colors);
+
+ // Check to see if the terminal already owns this config
+ for (let i = 0; i < charAtlasCache.length; i++) {
+ const entry = charAtlasCache[i];
+ const ownedByIndex = entry.ownedBy.indexOf(terminal);
+ if (ownedByIndex >= 0) {
+ if (configEquals(entry.config, newConfig)) {
+ return entry.atlas;
+ } else {
+ // The configs differ, release the terminal from the entry
+ if (entry.ownedBy.length === 1) {
+ charAtlasCache.splice(i, 1);
+ } else {
+ entry.ownedBy.splice(ownedByIndex, 1);
+ }
+ break;
+ }
+ }
+ }
+
+ // Try match a char atlas from the cache
+ for (let i = 0; i < charAtlasCache.length; i++) {
+ const entry = charAtlasCache[i];
+ if (configEquals(entry.config, newConfig)) {
+ // Add the terminal to the cache entry and return
+ entry.ownedBy.push(terminal);
+ return entry.atlas;
+ }
+ }
+
+ const newEntry: ICharAtlasCacheEntry = {
+ atlas: new charAtlasImplementations[terminal.options.experimentalCharAtlas](
+ document,
+ newConfig,
+ ),
+ config: newConfig,
+ ownedBy: [terminal],
+ };
+ charAtlasCache.push(newEntry);
+ return newEntry.atlas;
+}
diff --git a/src/renderer/atlas/CharAtlasUtils.ts b/src/renderer/atlas/CharAtlasUtils.ts
new file mode 100644
index 0000000000..e1f4234206
--- /dev/null
+++ b/src/renderer/atlas/CharAtlasUtils.ts
@@ -0,0 +1,99 @@
+/**
+ * Copyright (c) 2017 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { ITerminal } from '../../Types';
+import { ITheme } from 'xterm';
+import { IColorSet } from '../Types';
+
+export interface IGlyphIdentifier {
+ char: string;
+ bg: number;
+ fg: number;
+ bold: boolean;
+ dim: boolean;
+}
+
+export const CHAR_ATLAS_CELL_SPACING = 1;
+export const INVERTED_DEFAULT_COLOR = -1;
+export const DIM_OPACITY = 0.5;
+
+export interface ICharAtlasConfig {
+ type: 'none' | 'static' | 'dynamic';
+ devicePixelRatio: number;
+ fontSize: number;
+ fontFamily: string;
+ fontWeight: string;
+ fontWeightBold: string;
+ scaledCharWidth: number;
+ scaledCharHeight: number;
+ allowTransparency: boolean;
+ colors: IColorSet;
+}
+
+export interface ICharAtlas {
+ /**
+ * Perform any work needed to warm the cache before it can be used.
+ */
+ warmUp(): Promise
;
+
+ /**
+ * May be called before warmUp finishes, however it is okay for the implementation to
+ * do nothing and return false in that case.
+ *
+ * @param ctx Where to draw the character onto.
+ * @param glyph Information about what to draw
+ * @param x The position on the context to start drawing at
+ * @param y The position on the context to start drawing at
+ * @returns The success state. True if we drew the character.
+ */
+ draw(
+ ctx: CanvasRenderingContext2D,
+ glyph: IGlyphIdentifier,
+ x: number,
+ y: number
+ ): boolean;
+}
+
+export function generateConfig(scaledCharWidth: number, scaledCharHeight: number, terminal: ITerminal, colors: IColorSet): ICharAtlasConfig {
+ const clonedColors = {
+ foreground: colors.foreground,
+ background: colors.background,
+ cursor: null,
+ cursorAccent: null,
+ selection: null,
+ ansi: colors.ansi.slice(0, 16)
+ };
+ return {
+ type: terminal.options.experimentalCharAtlas,
+ devicePixelRatio: window.devicePixelRatio,
+ scaledCharWidth,
+ scaledCharHeight,
+ fontFamily: terminal.options.fontFamily,
+ fontSize: terminal.options.fontSize,
+ fontWeight: terminal.options.fontWeight,
+ fontWeightBold: terminal.options.fontWeightBold,
+ allowTransparency: terminal.options.allowTransparency,
+ colors: clonedColors
+ };
+}
+
+export function configEquals(a: ICharAtlasConfig, b: ICharAtlasConfig): boolean {
+ for (let i = 0; i < a.colors.ansi.length; i++) {
+ if (a.colors.ansi[i] !== b.colors.ansi[i]) {
+ return false;
+ }
+ }
+ return a.type === b.type &&
+ a.devicePixelRatio === b.devicePixelRatio &&
+ a.fontFamily === b.fontFamily &&
+ a.fontSize === b.fontSize &&
+ a.fontWeight === b.fontWeight &&
+ a.fontWeightBold === b.fontWeightBold &&
+ a.allowTransparency === b.allowTransparency &&
+ a.scaledCharWidth === b.scaledCharWidth &&
+ a.scaledCharHeight === b.scaledCharHeight &&
+ a.colors.foreground === b.colors.foreground &&
+ a.colors.background === b.colors.background;
+}
diff --git a/src/renderer/atlas/DynamicCharAtlas.ts b/src/renderer/atlas/DynamicCharAtlas.ts
new file mode 100644
index 0000000000..910ec7742c
--- /dev/null
+++ b/src/renderer/atlas/DynamicCharAtlas.ts
@@ -0,0 +1,178 @@
+/**
+ * Copyright (c) 2017 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import {
+ INVERTED_DEFAULT_COLOR,
+ DIM_OPACITY,
+ ICharAtlas,
+ ICharAtlasConfig,
+ IGlyphIdentifier,
+} from './CharAtlasUtils';
+
+const ATLAS_WIDTH = 30;
+const ATLAS_HEIGHT = 30;
+
+
+type GlyphCacheKey = string;
+
+/**
+ * Removes and returns the oldest element in a map.
+ */
+function mapShift(map: Map): [K, V] {
+ // Map guarantees insertion-order iteration.
+ const entry = map.entries().next().value;
+ if (entry === undefined) {
+ return undefined;
+ }
+ map.delete(entry[0]);
+ return entry;
+}
+
+function getGlyphCacheKey(glyph: IGlyphIdentifier): GlyphCacheKey {
+ return `${glyph.bg}_${glyph.fg}_${glyph.bold ? 0 : 1}${glyph.dim ? 0 : 1}${glyph.char}`;
+}
+
+export default class DynamicCharAtlas implements ICharAtlas {
+ private _cache: Map = new Map();
+ private _cacheCanvas: HTMLCanvasElement;
+ private _cacheCtx: CanvasRenderingContext2D;
+ private _capacity: number;
+ private _cellHeight: number;
+ private _cellWidth: number;
+ private _height: number;
+ private _width: number;
+
+ constructor(document: Document, private _config: ICharAtlasConfig) {
+ this._cacheCanvas = document.createElement('canvas');
+ this._cacheCtx = this._cacheCanvas.getContext('2d', {alpha: false});
+ this._cellWidth = this._config.scaledCharWidth,
+ this._cellHeight = this._config.scaledCharHeight,
+ this._width = ATLAS_WIDTH;
+ this._height = ATLAS_HEIGHT;
+ this._capacity = this._width * this._height;
+ this._cacheCanvas.width = this._cellWidth * this._width;
+ this._cacheCanvas.height = this._cellHeight * this._height;
+ // This is useful for debugging
+ // document.body.appendChild(this._cacheCanvas);
+ }
+
+ public warmUp(): Promise {
+ return Promise.resolve();
+ }
+
+ public draw(
+ ctx: CanvasRenderingContext2D,
+ glyph: IGlyphIdentifier,
+ x: number,
+ y: number
+ ): boolean {
+ const glyphKey = getGlyphCacheKey(glyph);
+ let cachedIndex = this._cache.get(glyphKey);
+ if (cachedIndex != null) {
+ // move to end of insertion order, so this can behave like an LRU cache
+ this._cache.delete(glyphKey);
+ this._cache.set(glyphKey, cachedIndex);
+ this._drawFromCache(ctx, cachedIndex, x, y);
+ return true;
+ } else if (this._canCache(glyph)) {
+ if (this._cache.size < this._capacity) {
+ cachedIndex = this._cache.size;
+ this._cache.set(glyphKey, cachedIndex);
+ } else {
+ cachedIndex = mapShift(this._cache)[1];
+ }
+ this._drawToCache(glyph, cachedIndex);
+ this._drawFromCache(ctx, cachedIndex, x, y);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ private _canCache(glyph: IGlyphIdentifier): boolean {
+ // Only cache ascii and extended characters for now, to be safe. In the future, we could do
+ // something more complicated to determine the expected width of a character.
+ //
+ // If we switch the renderer over to webgl at some point, we may be able to use blending modes
+ // to draw overlapping glyphs from the atlas:
+ // https://github.com/servo/webrender/issues/464#issuecomment-255632875
+ // https://webglfundamentals.org/webgl/lessons/webgl-text-texture.html
+ return glyph.char.charCodeAt(0) < 256;
+ }
+
+ private _toCoordinates(index: number): [number, number] {
+ return [
+ (index % this._width) * this._cellWidth,
+ Math.floor(index / this._width) * this._cellHeight
+ ];
+ }
+
+ private _drawFromCache(
+ ctx: CanvasRenderingContext2D,
+ cacheIndex: number,
+ x: number,
+ y: number
+ ): void {
+ const [cacheX, cacheY] = this._toCoordinates(cacheIndex);
+ ctx.drawImage(
+ this._cacheCanvas,
+ cacheX,
+ cacheY,
+ this._config.scaledCharWidth,
+ this._config.scaledCharHeight,
+ x,
+ y,
+ this._config.scaledCharWidth,
+ this._config.scaledCharHeight,
+ );
+ }
+
+ // TODO: We do this (or something similar) in multiple places. We should split this off
+ // into a shared function.
+ private _drawToCache(glyph: IGlyphIdentifier, index: number): void {
+ const [x, y] = this._toCoordinates(index);
+ this._cacheCtx.save();
+
+ // Set up a clip in case the character doesn't fit in the cell
+ this._cacheCtx.beginPath();
+ this._cacheCtx.rect(x, y, this._cellWidth, this._cellHeight);
+ this._cacheCtx.clip();
+
+ // draw the background
+ if (glyph.bg === INVERTED_DEFAULT_COLOR) {
+ this._cacheCtx.fillStyle = this._config.colors.foreground;
+ } else if (glyph.bg < 256) {
+ this._cacheCtx.fillStyle = this._config.colors.ansi[glyph.bg];
+ } else {
+ this._cacheCtx.fillStyle = this._config.colors.background;
+ }
+ this._cacheCtx.fillRect(x, y, this._cellWidth, this._cellHeight);
+
+ // draw the foreground/glyph
+ this._cacheCtx.font =
+ `${this._config.fontSize * this._config.devicePixelRatio}px ${this._config.fontFamily}`;
+ if (glyph.bold) {
+ this._cacheCtx.font = `bold ${this._cacheCtx.font}`;
+ }
+ this._cacheCtx.textBaseline = 'top';
+
+ if (glyph.fg === INVERTED_DEFAULT_COLOR) {
+ this._cacheCtx.fillStyle = this._config.colors.background;
+ } else if (glyph.fg < 256) {
+ // 256 color support
+ this._cacheCtx.fillStyle = this._config.colors.ansi[glyph.fg];
+ } else {
+ this._cacheCtx.fillStyle = this._config.colors.foreground;
+ }
+
+ // Apply alpha to dim the character
+ if (glyph.dim) {
+ this._cacheCtx.globalAlpha = DIM_OPACITY;
+ }
+ // Draw the character
+ this._cacheCtx.fillText(glyph.char, x, y);
+ this._cacheCtx.restore();
+ }
+}
diff --git a/src/renderer/atlas/NoneCharAtlas.ts b/src/renderer/atlas/NoneCharAtlas.ts
new file mode 100644
index 0000000000..cc7ba45f15
--- /dev/null
+++ b/src/renderer/atlas/NoneCharAtlas.ts
@@ -0,0 +1,29 @@
+/**
+ * Copyright (c) 2017 The xterm.js authors. All rights reserved.
+ * @license MIT
+ *
+ * A dummy ICharAtlas implementation that always fails to draw characters.
+ */
+
+import {
+ ICharAtlas,
+ ICharAtlasConfig,
+ IGlyphIdentifier,
+} from './CharAtlasUtils';
+
+export default class NoneCharAtlas implements ICharAtlas {
+ constructor(document: Document, config: ICharAtlasConfig) { }
+
+ public warmUp(): Promise {
+ return Promise.resolve();
+ }
+
+ public draw(
+ ctx: CanvasRenderingContext2D,
+ glyph: IGlyphIdentifier,
+ x: number,
+ y: number
+ ): boolean {
+ return false;
+ }
+}
diff --git a/src/renderer/atlas/StaticCharAtlas.ts b/src/renderer/atlas/StaticCharAtlas.ts
new file mode 100644
index 0000000000..860d193123
--- /dev/null
+++ b/src/renderer/atlas/StaticCharAtlas.ts
@@ -0,0 +1,90 @@
+/**
+ * Copyright (c) 2017 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { ITerminal } from '../../Types';
+import { IColorSet } from '../Types';
+import { generateCharAtlas, ICharAtlasRequest } from '../../shared/CharAtlasGenerator';
+import {
+ ICharAtlasConfig,
+ CHAR_ATLAS_CELL_SPACING,
+ configEquals,
+ generateConfig,
+} from './CharAtlasUtils';
+
+interface ICharAtlasCacheEntry {
+ bitmap: HTMLCanvasElement | Promise;
+ config: ICharAtlasConfig;
+ ownedBy: ITerminal[];
+}
+
+let charAtlasCache: ICharAtlasCacheEntry[] = [];
+
+/**
+ * Acquires a char atlas, either generating a new one or returning an existing
+ * one that is in use by another terminal.
+ * @param terminal The terminal.
+ * @param colors The colors to use.
+ */
+export function acquireCharAtlas(terminal: ITerminal, colors: IColorSet, scaledCharWidth: number, scaledCharHeight: number): HTMLCanvasElement | Promise {
+ const newConfig = generateConfig(scaledCharWidth, scaledCharHeight, terminal, colors);
+
+ // Check to see if the terminal already owns this config
+ for (let i = 0; i < charAtlasCache.length; i++) {
+ const entry = charAtlasCache[i];
+ const ownedByIndex = entry.ownedBy.indexOf(terminal);
+ if (ownedByIndex >= 0) {
+ if (configEquals(entry.config, newConfig)) {
+ return entry.bitmap;
+ } else {
+ // The configs differ, release the terminal from the entry
+ if (entry.ownedBy.length === 1) {
+ charAtlasCache.splice(i, 1);
+ } else {
+ entry.ownedBy.splice(ownedByIndex, 1);
+ }
+ break;
+ }
+ }
+ }
+
+ // Try match a char atlas from the cache
+ for (let i = 0; i < charAtlasCache.length; i++) {
+ const entry = charAtlasCache[i];
+ if (configEquals(entry.config, newConfig)) {
+ // Add the terminal to the cache entry and return
+ entry.ownedBy.push(terminal);
+ return entry.bitmap;
+ }
+ }
+
+ const canvasFactory = (width: number, height: number) => {
+ const canvas = document.createElement('canvas');
+ canvas.width = width;
+ canvas.height = height;
+ return canvas;
+ };
+
+ const charAtlasConfig: ICharAtlasRequest = {
+ scaledCharWidth,
+ scaledCharHeight,
+ fontSize: terminal.options.fontSize,
+ fontFamily: terminal.options.fontFamily,
+ fontWeight: terminal.options.fontWeight,
+ fontWeightBold: terminal.options.fontWeightBold,
+ background: colors.background,
+ foreground: colors.foreground,
+ ansiColors: colors.ansi,
+ devicePixelRatio: window.devicePixelRatio,
+ allowTransparency: terminal.options.allowTransparency
+ };
+
+ const newEntry: ICharAtlasCacheEntry = {
+ bitmap: generateCharAtlas(window, canvasFactory, charAtlasConfig),
+ config: newConfig,
+ ownedBy: [terminal]
+ };
+ charAtlasCache.push(newEntry);
+ return newEntry.bitmap;
+}
diff --git a/tsconfig.json b/tsconfig.json
index 38bfd2faf6..56f140e1df 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -4,6 +4,7 @@
"target": "es5",
"rootDir": "src",
"outDir": "lib",
+ "lib": ["DOM", "ES6", "DOM.Iterable", "ScriptHost"],
"sourceMap": true,
"removeComments": true
},
diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts
index dd08b55e7b..8c3b3c2568 100644
--- a/typings/xterm.d.ts
+++ b/typings/xterm.d.ts
@@ -55,11 +55,29 @@ declare module 'xterm' {
/**
* Whether to enable the rendering of bold text.
- *
+ *
* @deprecated Use fontWeight and fontWeightBold instead.
*/
enableBold?: boolean;
+ /**
+ * What character atlas implementation to use. The character atlas caches drawn characters,
+ * speeding up rendering significantly. However, as it's currently implemented, it can create
+ * issues with some fonts where characters are non-integer pixel widths, or where characters
+ * overflow the bounds of a single cell. To mitigate these issues, we only support ASCII
+ * characters for now.
+ *
+ * - '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.
+ *
+ * Currently defaults to 'static'. This option may be removed in the future. If it is, passed
+ * parameters will be ignored.
+ */
+ experimentalCharAtlas?: 'none' | 'static' | 'dynamic';
+
/**
* The font size used to render text.
*/