2 * Copyright (c) 2017 The xterm.js authors. All rights reserved.
6 import { IRenderDimensions, IRenderLayer } from 'browser/renderer/Types';
7 import { ICellData } from 'common/Types';
8 import { DEFAULT_COLOR, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE } from 'common/buffer/Constants';
9 import { IGlyphIdentifier } from 'browser/renderer/atlas/Types';
10 import { DIM_OPACITY, INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants';
11 import { BaseCharAtlas } from 'browser/renderer/atlas/BaseCharAtlas';
12 import { acquireCharAtlas } from 'browser/renderer/atlas/CharAtlasCache';
13 import { AttributeData } from 'common/buffer/AttributeData';
14 import { IColorSet } from 'browser/Types';
15 import { CellData } from 'common/buffer/CellData';
16 import { IBufferService, IOptionsService } from 'common/services/Services';
17 import { throwIfFalsy } from 'browser/renderer/RendererUtils';
19 export abstract class BaseRenderLayer implements IRenderLayer {
20 private _canvas: HTMLCanvasElement;
21 protected _ctx!: CanvasRenderingContext2D;
22 private _scaledCharWidth: number = 0;
23 private _scaledCharHeight: number = 0;
24 private _scaledCellWidth: number = 0;
25 private _scaledCellHeight: number = 0;
26 private _scaledCharLeft: number = 0;
27 private _scaledCharTop: number = 0;
29 protected _charAtlas: BaseCharAtlas | undefined;
32 * An object that's reused when drawing glyphs in order to reduce GC.
34 private _currentGlyphIdentifier: IGlyphIdentifier = {
45 private _container: HTMLElement,
48 private _alpha: boolean,
49 protected _colors: IColorSet,
50 private _rendererId: number,
51 protected readonly _bufferService: IBufferService,
52 protected readonly _optionsService: IOptionsService
54 this._canvas = document.createElement('canvas');
55 this._canvas.classList.add(`xterm-${id}-layer`);
56 this._canvas.style.zIndex = zIndex.toString();
58 this._container.appendChild(this._canvas);
61 public dispose(): void {
62 this._container.removeChild(this._canvas);
63 if (this._charAtlas) {
64 this._charAtlas.dispose();
68 private _initCanvas(): void {
69 this._ctx = throwIfFalsy(this._canvas.getContext('2d', {alpha: this._alpha}));
70 // Draw the background if this is an opaque layer
76 public onOptionsChanged(): void {}
77 public onBlur(): void {}
78 public onFocus(): void {}
79 public onCursorMove(): void {}
80 public onGridChanged(startRow: number, endRow: number): void {}
81 public onSelectionChanged(start: [number, number], end: [number, number], columnSelectMode: boolean = false): void {}
83 public setColors(colorSet: IColorSet): void {
84 this._refreshCharAtlas(colorSet);
87 protected _setTransparency(alpha: boolean): void {
88 // Do nothing when alpha doesn't change
89 if (alpha === this._alpha) {
93 // Create new canvas and replace old one
94 const oldCanvas = this._canvas;
96 // Cloning preserves properties
97 this._canvas = <HTMLCanvasElement>this._canvas.cloneNode();
99 this._container.replaceChild(this._canvas, oldCanvas);
101 // Regenerate char atlas and force a full redraw
102 this._refreshCharAtlas(this._colors);
103 this.onGridChanged(0, this._bufferService.rows - 1);
107 * Refreshes the char atlas, aquiring a new one if necessary.
108 * @param colorSet The color set to use for the char atlas.
110 private _refreshCharAtlas(colorSet: IColorSet): void {
111 if (this._scaledCharWidth <= 0 && this._scaledCharHeight <= 0) {
114 this._charAtlas = acquireCharAtlas(this._optionsService.options, this._rendererId, colorSet, this._scaledCharWidth, this._scaledCharHeight);
115 this._charAtlas.warmUp();
118 public resize(dim: IRenderDimensions): void {
119 this._scaledCellWidth = dim.scaledCellWidth;
120 this._scaledCellHeight = dim.scaledCellHeight;
121 this._scaledCharWidth = dim.scaledCharWidth;
122 this._scaledCharHeight = dim.scaledCharHeight;
123 this._scaledCharLeft = dim.scaledCharLeft;
124 this._scaledCharTop = dim.scaledCharTop;
125 this._canvas.width = dim.scaledCanvasWidth;
126 this._canvas.height = dim.scaledCanvasHeight;
127 this._canvas.style.width = `${dim.canvasWidth}px`;
128 this._canvas.style.height = `${dim.canvasHeight}px`;
130 // Draw the background if this is an opaque layer
135 this._refreshCharAtlas(this._colors);
138 public abstract reset(): void;
141 * Fills 1+ cells completely. This uses the existing fillStyle on the context.
142 * @param x The column to start at.
143 * @param y The row to start at
144 * @param width The number of columns to fill.
145 * @param height The number of rows to fill.
147 protected _fillCells(x: number, y: number, width: number, height: number): void {
149 x * this._scaledCellWidth,
150 y * this._scaledCellHeight,
151 width * this._scaledCellWidth,
152 height * this._scaledCellHeight);
156 * Fills a 1px line (2px on HDPI) at the bottom of the cell. This uses the
157 * existing fillStyle on the context.
158 * @param x The column to fill.
159 * @param y The row to fill.
161 protected _fillBottomLineAtCells(x: number, y: number, width: number = 1): void {
163 x * this._scaledCellWidth,
164 (y + 1) * this._scaledCellHeight - window.devicePixelRatio - 1 /* Ensure it's drawn within the cell */,
165 width * this._scaledCellWidth,
166 window.devicePixelRatio);
170 * Fills a 1px line (2px on HDPI) at the left of the cell. This uses the
171 * existing fillStyle on the context.
172 * @param x The column to fill.
173 * @param y The row to fill.
175 protected _fillLeftLineAtCell(x: number, y: number): void {
177 x * this._scaledCellWidth,
178 y * this._scaledCellHeight,
179 window.devicePixelRatio,
180 this._scaledCellHeight);
184 * Strokes a 1px rectangle (2px on HDPI) around a cell. This uses the existing
185 * strokeStyle on the context.
186 * @param x The column to fill.
187 * @param y The row to fill.
189 protected _strokeRectAtCell(x: number, y: number, width: number, height: number): void {
190 this._ctx.lineWidth = window.devicePixelRatio;
191 this._ctx.strokeRect(
192 x * this._scaledCellWidth + window.devicePixelRatio / 2,
193 y * this._scaledCellHeight + (window.devicePixelRatio / 2),
194 width * this._scaledCellWidth - window.devicePixelRatio,
195 (height * this._scaledCellHeight) - window.devicePixelRatio);
199 * Clears the entire canvas.
201 protected _clearAll(): void {
203 this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height);
205 this._ctx.fillStyle = this._colors.background.css;
206 this._ctx.fillRect(0, 0, this._canvas.width, this._canvas.height);
211 * Clears 1+ cells completely.
212 * @param x The column to start at.
213 * @param y The row to start at.
214 * @param width The number of columns to clear.
215 * @param height The number of rows to clear.
217 protected _clearCells(x: number, y: number, width: number, height: number): void {
220 x * this._scaledCellWidth,
221 y * this._scaledCellHeight,
222 width * this._scaledCellWidth,
223 height * this._scaledCellHeight);
225 this._ctx.fillStyle = this._colors.background.css;
227 x * this._scaledCellWidth,
228 y * this._scaledCellHeight,
229 width * this._scaledCellWidth,
230 height * this._scaledCellHeight);
235 * Draws a truecolor character at the cell. The character will be clipped to
236 * ensure that it fits with the cell, including the cell to the right if it's
237 * a wide character. This uses the existing fillStyle on the context.
238 * @param cell The cell data for the character to draw.
239 * @param x The column to draw at.
240 * @param y The row to draw at.
241 * @param color The color of the character.
243 protected _fillCharTrueColor(cell: CellData, x: number, y: number): void {
244 this._ctx.font = this._getFont(false, false);
245 this._ctx.textBaseline = 'middle';
249 x * this._scaledCellWidth + this._scaledCharLeft,
250 y * this._scaledCellHeight + this._scaledCharTop + this._scaledCharHeight / 2);
254 * Draws one or more characters at a cell. If possible this will draw using
255 * the character atlas to reduce draw time.
256 * @param chars The character or characters.
257 * @param code The character code.
258 * @param width The width of the characters.
259 * @param x The column to draw at.
260 * @param y The row to draw at.
261 * @param fg The foreground color, in the format stored within the attributes.
262 * @param bg The background color, in the format stored within the attributes.
263 * This is used to validate whether a cached image can be used.
264 * @param bold Whether the text is bold.
266 protected _drawChars(cell: ICellData, x: number, y: number): void {
268 // skip cache right away if we draw in RGB
269 // Note: to avoid bad runtime JoinedCellData will be skipped
270 // in the cache handler itself (atlasDidDraw == false) and
271 // fall through to uncached later down below
272 if (cell.isFgRGB() || cell.isBgRGB()) {
273 this._drawUncachedChars(cell, x, y);
279 if (cell.isInverse()) {
280 fg = (cell.isBgDefault()) ? INVERTED_DEFAULT_COLOR : cell.getBgColor();
281 bg = (cell.isFgDefault()) ? INVERTED_DEFAULT_COLOR : cell.getFgColor();
283 bg = (cell.isBgDefault()) ? DEFAULT_COLOR : cell.getBgColor();
284 fg = (cell.isFgDefault()) ? DEFAULT_COLOR : cell.getFgColor();
287 const drawInBrightColor = this._optionsService.options.drawBoldTextInBrightColors && cell.isBold() && fg < 8 && fg !== INVERTED_DEFAULT_COLOR;
289 fg += drawInBrightColor ? 8 : 0;
290 this._currentGlyphIdentifier.chars = cell.getChars() || WHITESPACE_CELL_CHAR;
291 this._currentGlyphIdentifier.code = cell.getCode() || WHITESPACE_CELL_CODE;
292 this._currentGlyphIdentifier.bg = bg;
293 this._currentGlyphIdentifier.fg = fg;
294 this._currentGlyphIdentifier.bold = !!cell.isBold();
295 this._currentGlyphIdentifier.dim = !!cell.isDim();
296 this._currentGlyphIdentifier.italic = !!cell.isItalic();
297 const atlasDidDraw = this._charAtlas && this._charAtlas.draw(
299 this._currentGlyphIdentifier,
300 x * this._scaledCellWidth + this._scaledCharLeft,
301 y * this._scaledCellHeight + this._scaledCharTop
305 this._drawUncachedChars(cell, x, y);
310 * Draws one or more characters at one or more cells. The character(s) will be
311 * clipped to ensure that they fit with the cell(s), including the cell to the
312 * right if the last character is a wide character.
313 * @param chars The character.
314 * @param width The width of the character.
315 * @param fg The foreground color, in the format stored within the attributes.
316 * @param x The column to draw at.
317 * @param y The row to draw at.
319 private _drawUncachedChars(cell: ICellData, x: number, y: number): void {
321 this._ctx.font = this._getFont(!!cell.isBold(), !!cell.isItalic());
322 this._ctx.textBaseline = 'middle';
324 if (cell.isInverse()) {
325 if (cell.isBgDefault()) {
326 this._ctx.fillStyle = this._colors.background.css;
327 } else if (cell.isBgRGB()) {
328 this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getBgColor()).join(',')})`;
330 this._ctx.fillStyle = this._colors.ansi[cell.getBgColor()].css;
333 if (cell.isFgDefault()) {
334 this._ctx.fillStyle = this._colors.foreground.css;
335 } else if (cell.isFgRGB()) {
336 this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getFgColor()).join(',')})`;
338 let fg = cell.getFgColor();
339 if (this._optionsService.options.drawBoldTextInBrightColors && cell.isBold() && fg < 8) {
342 this._ctx.fillStyle = this._colors.ansi[fg].css;
348 // Apply alpha to dim the character
350 this._ctx.globalAlpha = DIM_OPACITY;
352 // Draw the character
355 x * this._scaledCellWidth + this._scaledCharLeft,
356 y * this._scaledCellHeight + this._scaledCharTop + this._scaledCharHeight / 2);
361 * Clips a row to ensure no pixels will be drawn outside the cells in the row.
362 * @param y The row to clip.
364 private _clipRow(y: number): void {
365 this._ctx.beginPath();
368 y * this._scaledCellHeight,
369 this._bufferService.cols * this._scaledCellWidth,
370 this._scaledCellHeight);
375 * Gets the current font.
376 * @param isBold If we should use the bold fontWeight.
378 protected _getFont(isBold: boolean, isItalic: boolean): string {
379 const fontWeight = isBold ? this._optionsService.options.fontWeightBold : this._optionsService.options.fontWeight;
380 const fontStyle = isItalic ? 'italic' : '';
382 return `${fontStyle} ${fontWeight} ${this._optionsService.options.fontSize * window.devicePixelRatio}px ${this._optionsService.options.fontFamily}`;