Merge branch 'master' into tyriar/dim_bg

This commit is contained in:
Daniel Imms
2022-07-29 10:20:22 -07:00
committed by GitHub
31 changed files with 328 additions and 172 deletions
+37 -39
View File
@@ -11,11 +11,12 @@ import { WebglCharAtlas } from './atlas/WebglCharAtlas';
import { RectangleRenderer } from './RectangleRenderer';
import { IWebGL2RenderingContext } from './Types';
import { RenderModel, COMBINED_CHAR_BIT_MASK, RENDER_MODEL_BG_OFFSET, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_INDICIES_PER_CELL } from './RenderModel';
import { Disposable } from 'common/Lifecycle';
import { Disposable, toDisposable } from 'common/Lifecycle';
import { Attributes, BgFlags, Content, FgFlags, NULL_CELL_CHAR, NULL_CELL_CODE } from 'common/buffer/Constants';
import { Terminal, IEvent } from 'xterm';
import { IRenderLayer } from './renderLayer/Types';
import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/Types';
import { observeDevicePixelDimensions } from 'browser/renderer/DevicePixelObserver';
import { ITerminal, IColorSet } from 'browser/Types';
import { EventEmitter } from 'common/EventEmitter';
import { CellData } from 'common/buffer/CellData';
@@ -96,6 +97,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
}
this.register(addDisposableDomListener(this._canvas, 'webglcontextlost', (e) => { this._onContextLoss.fire(e); }));
this.register(observeDevicePixelDimensions(this._canvas, (w, h) => this._setCanvasDevicePixelDimensions(w, h)));
this._core.screenElement!.appendChild(this._canvas);
@@ -518,67 +520,63 @@ export class WebglRenderer extends Disposable implements IRenderer {
return;
}
// Calculate the scaled character width. Width is floored as it must be
// drawn to an integer grid in order for the CharAtlas "stamps" to not be
// blurry. When text is drawn to the grid not using the CharAtlas, it is
// clipped to ensure there is no overlap with the next cell.
// NOTE: ceil fixes sometime, floor does others :s
// Calculate the scaled character width. Width is floored as it must be drawn to an integer grid
// in order for the char atlas glyphs to not be blurry.
this.dimensions.scaledCharWidth = Math.floor((this._core as any)._charSizeService.width * this._devicePixelRatio);
// Calculate the scaled character height. Height is ceiled in case
// devicePixelRatio is a floating point number in order to ensure there is
// enough space to draw the character to the cell.
// Calculate the scaled character height. Height is ceiled in case devicePixelRatio is a
// floating point number in order to ensure there is enough space to draw the character to the
// cell.
this.dimensions.scaledCharHeight = Math.ceil((this._core as any)._charSizeService.height * this._devicePixelRatio);
// Calculate the scaled cell height, if lineHeight is not 1 then the value
// will be floored because since lineHeight can never be lower then 1, there
// is a guarentee that the scaled line height will always be larger than
// scaled char height.
// Calculate the scaled cell height, if lineHeight is _not_ 1, the resulting value will be
// floored since lineHeight can never be lower then 1, this guarentees the scaled cell height
// will always be larger than scaled char height.
this.dimensions.scaledCellHeight = Math.floor(this.dimensions.scaledCharHeight * this._terminal.options.lineHeight!);
// Calculate the y coordinate within a cell that text should draw from in
// order to draw in the center of a cell.
// Calculate the y offset within a cell that glyph should draw at in order for it to be centered
// correctly within the cell.
this.dimensions.scaledCharTop = this._terminal.options.lineHeight === 1 ? 0 : Math.round((this.dimensions.scaledCellHeight - this.dimensions.scaledCharHeight) / 2);
// Calculate the scaled cell width, taking the letterSpacing into account.
this.dimensions.scaledCellWidth = this.dimensions.scaledCharWidth + Math.round(this._terminal.options.letterSpacing!);
// Calculate the x coordinate with a cell that text should draw from in
// order to draw in the center of a cell.
// Calculate the x offset with a cell that text should draw from in order for it to be centered
// correctly within the cell.
this.dimensions.scaledCharLeft = Math.floor(this._terminal.options.letterSpacing! / 2);
// Recalculate the canvas dimensions; scaled* define the actual number of
// pixel in the canvas
// Recalculate the canvas dimensions, the scaled dimensions define the actual number of pixel in
// the canvas
this.dimensions.scaledCanvasHeight = this._terminal.rows * this.dimensions.scaledCellHeight;
this.dimensions.scaledCanvasWidth = this._terminal.cols * this.dimensions.scaledCellWidth;
// The the size of the canvas on the page. It's very important that this
// rounds to nearest integer and not ceils as browsers often set
// window.devicePixelRatio as something like 1.100000023841858, when it's
// actually 1.1. Ceiling causes blurriness as the backing canvas image is 1
// pixel too large for the canvas element size.
// The the size of the canvas on the page. It's important that this rounds to nearest integer
// and not ceils as browsers often have floating point precision issues where
// `window.devicePixelRatio` ends up being something like `1.100000023841858` for example, when
// it's actually 1.1. Ceiling may causes blurriness as the backing canvas image is 1 pixel too
// large for the canvas element size.
this.dimensions.canvasHeight = Math.round(this.dimensions.scaledCanvasHeight / this._devicePixelRatio);
this.dimensions.canvasWidth = Math.round(this.dimensions.scaledCanvasWidth / this._devicePixelRatio);
// this.dimensions.scaledCanvasHeight = this.dimensions.canvasHeight * devicePixelRatio;
// this.dimensions.scaledCanvasWidth = this.dimensions.canvasWidth * devicePixelRatio;
// Get the _actual_ dimensions of an individual cell. This needs to be
// derived from the canvasWidth/Height calculated above which takes into
// account window.devicePixelRatio. CharMeasure.width/height by itself is
// insufficient when the page is not at 100% zoom level as CharMeasure is
// measured in CSS pixels, but the actual char size on the canvas can
// differ.
// this.dimensions.actualCellHeight = this.dimensions.canvasHeight / this._terminal.rows;
// this.dimensions.actualCellWidth = this.dimensions.canvasWidth / this._terminal.cols;
// This fixes 110% and 125%, not 150% or 175% though
// Get the CSS dimensions of an individual cell. This needs to be derived from the calculated
// device pixel canvas value above. CharMeasure.width/height by itself is insufficient when the
// page is not at 100% zoom level as CharMeasure is measured in CSS pixels, but the actual char
// size on the canvas can differ.
this.dimensions.actualCellHeight = this.dimensions.scaledCellHeight / this._devicePixelRatio;
this.dimensions.actualCellWidth = this.dimensions.scaledCellWidth / this._devicePixelRatio;
}
private _setCanvasDevicePixelDimensions(width: number, height: number): void {
if (this.dimensions.scaledCanvasWidth === width && this.dimensions.scaledCanvasHeight === height) {
return;
}
this.dimensions.scaledCanvasWidth = width;
this.dimensions.scaledCanvasHeight = height;
this._canvas.width = width;
this._canvas.height = height;
this._requestRedrawViewport();
}
private _requestRedrawViewport(): void {
this._onRequestRedraw.fire({ start: 0, end: this._terminal.rows - 1 });
}
@@ -416,15 +416,15 @@ export class WebglCharAtlas implements IDisposable {
this._tmpCtx.fillText(chars, padding, padding + this._config.scaledCharHeight);
}
// If this charcater is underscore and beyond the cell bounds, shift it up until it is visible,
// try for a maximum of 5 pixels.
// If this charcater is underscore and beyond the cell bounds, shift it up until it is visible
// even on the bottom row, try for a maximum of 5 pixels.
if (chars === '_' && !this._config.allowTransparency) {
let isBeyondCellBounds = clearColor(this._tmpCtx.getImageData(padding, padding, this._config.scaledCellWidth, this._config.scaledCellHeight), backgroundColor, foregroundColor, this._config.allowTransparency);
let isBeyondCellBounds = clearColor(this._tmpCtx.getImageData(padding, padding, this._config.scaledCellWidth, this._config.scaledCellHeight), backgroundColor, foregroundColor);
if (isBeyondCellBounds) {
for (let offset = 1; offset <= 5; offset++) {
this._tmpCtx.clearRect(0, 0, this._tmpCanvas.width, this._tmpCanvas.height);
this._tmpCtx.fillText(chars, padding, padding + this._config.scaledCharHeight - offset);
isBeyondCellBounds = clearColor(this._tmpCtx.getImageData(padding, padding, this._config.scaledCellWidth, this._config.scaledCellHeight), backgroundColor, foregroundColor, this._config.allowTransparency);
isBeyondCellBounds = clearColor(this._tmpCtx.getImageData(padding, padding, this._config.scaledCellWidth, this._config.scaledCellHeight), backgroundColor, foregroundColor);
if (!isBeyondCellBounds) {
break;
}
@@ -460,7 +460,12 @@ export class WebglCharAtlas implements IDisposable {
);
// Clear out the background color and determine if the glyph is empty.
const isEmpty = clearColor(imageData, backgroundColor, foregroundColor, this._config.allowTransparency);
let isEmpty: boolean;
if (!this._config.allowTransparency) {
isEmpty = clearColor(imageData, backgroundColor, foregroundColor);
} else {
isEmpty = checkCompletelyTransparent(imageData);
}
// Handle empty glyphs
if (isEmpty) {
@@ -604,7 +609,7 @@ export class WebglCharAtlas implements IDisposable {
* transparent.
* @returns True if the result is "empty", meaning all pixels are fully transparent.
*/
function clearColor(imageData: ImageData, bg: IColor, fg: IColor, allowTransparency: boolean): boolean {
function clearColor(imageData: ImageData, bg: IColor, fg: IColor): boolean {
// Get color channels
const r = bg.rgba >>> 24;
const g = bg.rgba >>> 16 & 0xFF;
@@ -624,15 +629,14 @@ function clearColor(imageData: ImageData, bg: IColor, fg: IColor, allowTranspare
// Set alpha channel of relevent pixels to 0
let isEmpty = true;
for (let offset = 0; offset < imageData.data.length; offset += 4) {
// Check exact match
if (imageData.data[offset] === r &&
imageData.data[offset + 1] === g &&
imageData.data[offset + 2] === b) {
imageData.data[offset + 3] = 0;
} else {
// Check the threshold only when transparency is not allowed only as overlapping isn't an
// issue for transparency glyphs.
if (!allowTransparency &&
(Math.abs(imageData.data[offset] - r) +
// Check the threshold based difference
if ((Math.abs(imageData.data[offset] - r) +
Math.abs(imageData.data[offset + 1] - g) +
Math.abs(imageData.data[offset + 2] - b)) < threshold) {
imageData.data[offset + 3] = 0;
@@ -645,6 +649,15 @@ function clearColor(imageData: ImageData, bg: IColor, fg: IColor, allowTranspare
return isEmpty;
}
function checkCompletelyTransparent(imageData: ImageData): boolean {
for (let offset = 0; offset < imageData.data.length; offset += 4) {
if (imageData.data[offset + 3] > 0) {
return false;
}
}
return true;
}
function toPaddedHex(c: number): string {
const s = c.toString(16);
return s.length < 2 ? '0' + s : s;
@@ -54,6 +54,12 @@ export class CursorRenderLayer extends BaseRenderLayer {
this.onOptionsChanged(terminal);
}
public override dispose(): void {
this._cursorBlinkStateManager?.dispose();
this._cursorBlinkStateManager = undefined;
super.dispose();
}
public resize(terminal: Terminal, dim: IRenderDimensions): void {
super.resize(terminal, dim);
// Resizing the canvas discards the contents of the canvas so clear state