xterm
[VSoRC/.git] / node_modules / xterm / src / renderer / Renderer.ts
1 /**
2  * Copyright (c) 2017 The xterm.js authors. All rights reserved.
3  * @license MIT
4  */
5
6 import { TextRenderLayer } from '../browser/renderer/TextRenderLayer';
7 import { SelectionRenderLayer } from '../browser/renderer/SelectionRenderLayer';
8 import { CursorRenderLayer } from './CursorRenderLayer';
9 import { IRenderLayer, IRenderer, IRenderDimensions, CharacterJoinerHandler, ICharacterJoinerRegistry } from 'browser/renderer/Types';
10 import { ITerminal } from '../Types';
11 import { LinkRenderLayer } from '../browser/renderer/LinkRenderLayer';
12 import { CharacterJoinerRegistry } from 'browser/renderer/CharacterJoinerRegistry';
13 import { Disposable } from 'common/Lifecycle';
14 import { IColorSet } from 'browser/Types';
15 import { ICharSizeService } from 'browser/services/Services';
16 import { IBufferService, IOptionsService } from 'common/services/Services';
17 import { removeTerminalFromCache } from 'browser/renderer/atlas/CharAtlasCache';
18
19 let nextRendererId = 1;
20
21 export class Renderer extends Disposable implements IRenderer {
22   private _id = nextRendererId++;
23
24   private _renderLayers: IRenderLayer[];
25   private _devicePixelRatio: number;
26   private _characterJoinerRegistry: ICharacterJoinerRegistry;
27
28   public dimensions: IRenderDimensions;
29
30   constructor(
31     private _colors: IColorSet,
32     private readonly _terminal: ITerminal,
33     readonly bufferService: IBufferService,
34     private readonly _charSizeService: ICharSizeService,
35     readonly optionsService: IOptionsService
36   ) {
37     super();
38     const allowTransparency = this._terminal.options.allowTransparency;
39     this._characterJoinerRegistry = new CharacterJoinerRegistry(bufferService);
40
41     this._renderLayers = [
42       new TextRenderLayer(this._terminal.screenElement, 0, this._colors, this._characterJoinerRegistry, allowTransparency, this._id, bufferService, optionsService),
43       new SelectionRenderLayer(this._terminal.screenElement, 1, this._colors, this._id, bufferService, optionsService),
44       new LinkRenderLayer(this._terminal.screenElement, 2, this._colors, this._id, this._terminal.linkifier, bufferService, optionsService),
45       new CursorRenderLayer(this._terminal.screenElement, 3, this._colors, this._terminal, this._id, bufferService, optionsService)
46     ];
47     this.dimensions = {
48       scaledCharWidth: null,
49       scaledCharHeight: null,
50       scaledCellWidth: null,
51       scaledCellHeight: null,
52       scaledCharLeft: null,
53       scaledCharTop: null,
54       scaledCanvasWidth: null,
55       scaledCanvasHeight: null,
56       canvasWidth: null,
57       canvasHeight: null,
58       actualCellWidth: null,
59       actualCellHeight: null
60     };
61     this._devicePixelRatio = window.devicePixelRatio;
62     this._updateDimensions();
63     this.onOptionsChanged();
64   }
65
66   public dispose(): void {
67     super.dispose();
68     this._renderLayers.forEach(l => l.dispose());
69     removeTerminalFromCache(this._id);
70   }
71
72   public onDevicePixelRatioChange(): void {
73     // If the device pixel ratio changed, the char atlas needs to be regenerated
74     // and the terminal needs to refreshed
75     if (this._devicePixelRatio !== window.devicePixelRatio) {
76       this._devicePixelRatio = window.devicePixelRatio;
77       this.onResize(this._terminal.cols, this._terminal.rows);
78     }
79   }
80
81   public setColors(colors: IColorSet): void {
82     this._colors = colors;
83
84     // Clear layers and force a full render
85     this._renderLayers.forEach(l => {
86       l.setColors(this._colors);
87       l.reset();
88     });
89   }
90
91   public onResize(cols: number, rows: number): void {
92     // Update character and canvas dimensions
93     this._updateDimensions();
94
95     // Resize all render layers
96     this._renderLayers.forEach(l => l.resize(this.dimensions));
97
98     // Resize the screen
99     this._terminal.screenElement.style.width = `${this.dimensions.canvasWidth}px`;
100     this._terminal.screenElement.style.height = `${this.dimensions.canvasHeight}px`;
101   }
102
103   public onCharSizeChanged(): void {
104     this.onResize(this._terminal.cols, this._terminal.rows);
105   }
106
107   public onBlur(): void {
108     this._runOperation(l => l.onBlur());
109   }
110
111   public onFocus(): void {
112     this._runOperation(l => l.onFocus());
113   }
114
115   public onSelectionChanged(start: [number, number], end: [number, number], columnSelectMode: boolean = false): void {
116     this._runOperation(l => l.onSelectionChanged(start, end, columnSelectMode));
117   }
118
119   public onCursorMove(): void {
120     this._runOperation(l => l.onCursorMove());
121   }
122
123   public onOptionsChanged(): void {
124     this._runOperation(l => l.onOptionsChanged());
125   }
126
127   public clear(): void {
128     this._runOperation(l => l.reset());
129   }
130
131   private _runOperation(operation: (layer: IRenderLayer) => void): void {
132     this._renderLayers.forEach(l => operation(l));
133   }
134
135   /**
136    * Performs the refresh loop callback, calling refresh only if a refresh is
137    * necessary before queueing up the next one.
138    */
139   public renderRows(start: number, end: number): void {
140     this._renderLayers.forEach(l => l.onGridChanged(start, end));
141   }
142
143   /**
144    * Recalculates the character and canvas dimensions.
145    */
146   private _updateDimensions(): void {
147     if (!this._charSizeService.hasValidSize) {
148       return;
149     }
150
151     // Calculate the scaled character width. Width is floored as it must be
152     // drawn to an integer grid in order for the CharAtlas "stamps" to not be
153     // blurry. When text is drawn to the grid not using the CharAtlas, it is
154     // clipped to ensure there is no overlap with the next cell.
155     this.dimensions.scaledCharWidth = Math.floor(this._charSizeService.width * window.devicePixelRatio);
156
157     // Calculate the scaled character height. Height is ceiled in case
158     // devicePixelRatio is a floating point number in order to ensure there is
159     // enough space to draw the character to the cell.
160     this.dimensions.scaledCharHeight = Math.ceil(this._charSizeService.height * window.devicePixelRatio);
161
162     // Calculate the scaled cell height, if lineHeight is not 1 then the value
163     // will be floored because since lineHeight can never be lower then 1, there
164     // is a guarentee that the scaled line height will always be larger than
165     // scaled char height.
166     this.dimensions.scaledCellHeight = Math.floor(this.dimensions.scaledCharHeight * this._terminal.options.lineHeight);
167
168     // Calculate the y coordinate within a cell that text should draw from in
169     // order to draw in the center of a cell.
170     this.dimensions.scaledCharTop = this._terminal.options.lineHeight === 1 ? 0 : Math.round((this.dimensions.scaledCellHeight - this.dimensions.scaledCharHeight) / 2);
171
172     // Calculate the scaled cell width, taking the letterSpacing into account.
173     this.dimensions.scaledCellWidth = this.dimensions.scaledCharWidth + Math.round(this._terminal.options.letterSpacing);
174
175     // Calculate the x coordinate with a cell that text should draw from in
176     // order to draw in the center of a cell.
177     this.dimensions.scaledCharLeft = Math.floor(this._terminal.options.letterSpacing / 2);
178
179     // Recalculate the canvas dimensions; scaled* define the actual number of
180     // pixel in the canvas
181     this.dimensions.scaledCanvasHeight = this._terminal.rows * this.dimensions.scaledCellHeight;
182     this.dimensions.scaledCanvasWidth = this._terminal.cols * this.dimensions.scaledCellWidth;
183
184     // The the size of the canvas on the page. It's very important that this
185     // rounds to nearest integer and not ceils as browsers often set
186     // window.devicePixelRatio as something like 1.100000023841858, when it's
187     // actually 1.1. Ceiling causes blurriness as the backing canvas image is 1
188     // pixel too large for the canvas element size.
189     this.dimensions.canvasHeight = Math.round(this.dimensions.scaledCanvasHeight / window.devicePixelRatio);
190     this.dimensions.canvasWidth = Math.round(this.dimensions.scaledCanvasWidth / window.devicePixelRatio);
191
192     // Get the _actual_ dimensions of an individual cell. This needs to be
193     // derived from the canvasWidth/Height calculated above which takes into
194     // account window.devicePixelRatio. ICharSizeService.width/height by itself
195     // is insufficient when the page is not at 100% zoom level as it's measured
196     // in CSS pixels, but the actual char size on the canvas can differ.
197     this.dimensions.actualCellHeight = this.dimensions.canvasHeight / this._terminal.rows;
198     this.dimensions.actualCellWidth = this.dimensions.canvasWidth / this._terminal.cols;
199   }
200
201   public registerCharacterJoiner(handler: CharacterJoinerHandler): number {
202     return this._characterJoinerRegistry.registerCharacterJoiner(handler);
203   }
204
205   public deregisterCharacterJoiner(joinerId: number): boolean {
206     return this._characterJoinerRegistry.deregisterCharacterJoiner(joinerId);
207   }
208 }