Add setting, clean up drawing, fix true color
This commit is contained in:
@@ -17,7 +17,7 @@ import { IBufferService, IOptionsService } from 'common/services/Services';
|
|||||||
import { throwIfFalsy } from 'browser/renderer/RendererUtils';
|
import { throwIfFalsy } from 'browser/renderer/RendererUtils';
|
||||||
import { channels, color, rgba } from 'browser/Color';
|
import { channels, color, rgba } from 'browser/Color';
|
||||||
import { removeElementFromParent } from 'browser/Dom';
|
import { removeElementFromParent } from 'browser/Dom';
|
||||||
import { boxCharacters, boxDrawingBoxes, drawBoxChar } from 'browser/renderer/BoxAndBlockCharacters';
|
import { tryDrawCustomChar } from 'browser/renderer/BoxAndBlockCharacters';
|
||||||
|
|
||||||
export abstract class BaseRenderLayer implements IRenderLayer {
|
export abstract class BaseRenderLayer implements IRenderLayer {
|
||||||
private _canvas: HTMLCanvasElement;
|
private _canvas: HTMLCanvasElement;
|
||||||
@@ -260,8 +260,15 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
|||||||
this._ctx.font = this._getFont(false, false);
|
this._ctx.font = this._getFont(false, false);
|
||||||
this._ctx.textBaseline = 'ideographic';
|
this._ctx.textBaseline = 'ideographic';
|
||||||
this._clipRow(y);
|
this._clipRow(y);
|
||||||
// TODO: fix
|
|
||||||
if (!this._drawBoxChar(cell, x, y)) {
|
// Draw custom characters if applicable
|
||||||
|
let drawSuccess = false;
|
||||||
|
if (this._optionsService.options.customBlockAndBoxCharacters !== false) {
|
||||||
|
drawSuccess = tryDrawCustomChar(this._ctx, cell.getChars(), x, y, this._scaledCellWidth, this._scaledCellHeight, this._scaledCharLeft, this._scaledCharTop);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw the character
|
||||||
|
if (!drawSuccess) {
|
||||||
this._ctx.fillText(
|
this._ctx.fillText(
|
||||||
cell.getChars(),
|
cell.getChars(),
|
||||||
x * this._scaledCellWidth + this._scaledCharLeft,
|
x * this._scaledCellWidth + this._scaledCharLeft,
|
||||||
@@ -377,46 +384,24 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
|||||||
if (cell.isDim()) {
|
if (cell.isDim()) {
|
||||||
this._ctx.globalAlpha = DIM_OPACITY;
|
this._ctx.globalAlpha = DIM_OPACITY;
|
||||||
}
|
}
|
||||||
if (!this._drawBoxChar(cell, x, y)) {
|
|
||||||
// Draw the character
|
// Draw custom characters if applicable
|
||||||
|
let drawSuccess = false;
|
||||||
|
if (this._optionsService.options.customBlockAndBoxCharacters !== false) {
|
||||||
|
drawSuccess = tryDrawCustomChar(this._ctx, cell.getChars(), x, y, this._scaledCellWidth, this._scaledCellHeight, this._scaledCharLeft, this._scaledCharTop);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw the character
|
||||||
|
if (!drawSuccess) {
|
||||||
this._ctx.fillText(
|
this._ctx.fillText(
|
||||||
cell.getChars(),
|
cell.getChars(),
|
||||||
x * this._scaledCellWidth + this._scaledCharLeft,
|
x * this._scaledCellWidth + this._scaledCharLeft,
|
||||||
y * this._scaledCellHeight + this._scaledCharTop + this._scaledCharHeight);
|
y * this._scaledCellHeight + this._scaledCharTop + this._scaledCharHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._ctx.restore();
|
this._ctx.restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
private _drawBoxChar(cell: ICellData, x: number, y: number): boolean {
|
|
||||||
const char = cell.getChars();
|
|
||||||
|
|
||||||
const boxes = boxDrawingBoxes[char];
|
|
||||||
if (boxes) {
|
|
||||||
this._ctx.strokeStyle = this._ctx.fillStyle;
|
|
||||||
const xOffset = x * this._scaledCellWidth + this._scaledCharLeft;
|
|
||||||
const yOffset = y * this._scaledCellHeight + this._scaledCharTop;
|
|
||||||
for (let i = 0; i < boxes.length; i++) {
|
|
||||||
const box = boxes[i];
|
|
||||||
const xEighth = this._scaledCellWidth / 8;
|
|
||||||
const yEighth = this._scaledCellHeight / 8;
|
|
||||||
this._ctx.fillRect(
|
|
||||||
xOffset,
|
|
||||||
yOffset,
|
|
||||||
box.w * xEighth,
|
|
||||||
box.h * yEighth);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const lineSegments = boxCharacters[char];
|
|
||||||
if (!lineSegments) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
this._ctx.strokeStyle = this._ctx.fillStyle;
|
|
||||||
drawBoxChar(this._ctx, char, x * this._scaledCellWidth, y * this._scaledCellHeight, this._scaledCellWidth, this._scaledCellHeight);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clips a row to ensure no pixels will be drawn outside the cells in the row.
|
* Clips a row to ensure no pixels will be drawn outside the cells in the row.
|
||||||
|
|||||||
@@ -1,5 +1,16 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 The xterm.js authors. All rights reserved.
|
||||||
|
* @license MIT
|
||||||
|
*/
|
||||||
|
|
||||||
export const boxDrawingBoxes: { [index: string]: any } = {
|
interface IBlockVector {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
w: number;
|
||||||
|
h: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const blockElementChars: { [index: string]: IBlockVector[] | undefined } = {
|
||||||
'▀': [{ x: 0, y: 0, w: 8, h: 4 }],
|
'▀': [{ x: 0, y: 0, w: 8, h: 4 }],
|
||||||
'█': [{ x: 0, y: 0, w: 8, h: 8 }],
|
'█': [{ x: 0, y: 0, w: 8, h: 8 }],
|
||||||
'▇': [{ x: 0, y: 1, w: 8, h: 7 }],
|
'▇': [{ x: 0, y: 1, w: 8, h: 7 }],
|
||||||
@@ -125,7 +136,7 @@ const enum Style {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// This contains the definitions of all box drawing characters as SVG paths (ie. the svg d attribute)
|
// This contains the definitions of all box drawing characters as SVG paths (ie. the svg d attribute)
|
||||||
export const boxCharacters: { [character: string]: { [fontWeight: number]: string | ((xp: number, yp: number) => string) } } = {
|
export const boxDrawingChars: { [character: string]: { [fontWeight: number]: string | ((xp: number, yp: number) => string) } | undefined } = {
|
||||||
// Uniform normal and bold
|
// Uniform normal and bold
|
||||||
'─': { [Style.NORMAL]: Shapes.LEFT_TO_RIGHT },
|
'─': { [Style.NORMAL]: Shapes.LEFT_TO_RIGHT },
|
||||||
'━': { [Style.BOLD]: Shapes.LEFT_TO_RIGHT },
|
'━': { [Style.BOLD]: Shapes.LEFT_TO_RIGHT },
|
||||||
@@ -247,7 +258,6 @@ export const boxCharacters: { [character: string]: { [fontWeight: number]: strin
|
|||||||
'╊': { [Style.NORMAL]: Shapes.MIDDLE_TO_LEFT, [Style.BOLD]: `${Shapes.TOP_TO_BOTTOM} ${Shapes.MIDDLE_TO_RIGHT}` },
|
'╊': { [Style.NORMAL]: Shapes.MIDDLE_TO_LEFT, [Style.BOLD]: `${Shapes.TOP_TO_BOTTOM} ${Shapes.MIDDLE_TO_RIGHT}` },
|
||||||
|
|
||||||
// Dashed
|
// Dashed
|
||||||
// TODO: Spacing dashes evenly, use 1/2 padding on each edge so the line is continuous
|
|
||||||
'╌': { [Style.NORMAL]: Shapes.TWO_DASHES_HORIZONTAL },
|
'╌': { [Style.NORMAL]: Shapes.TWO_DASHES_HORIZONTAL },
|
||||||
'╍': { [Style.BOLD]: Shapes.TWO_DASHES_HORIZONTAL },
|
'╍': { [Style.BOLD]: Shapes.TWO_DASHES_HORIZONTAL },
|
||||||
'┄': { [Style.NORMAL]: Shapes.THREE_DASHES_HORIZONTAL },
|
'┄': { [Style.NORMAL]: Shapes.THREE_DASHES_HORIZONTAL },
|
||||||
@@ -268,18 +278,78 @@ export const boxCharacters: { [character: string]: { [fontWeight: number]: strin
|
|||||||
'╰': { [Style.NORMAL]: 'C.5,0,.5,.5,1,.5' }
|
'╰': { [Style.NORMAL]: 'C.5,0,.5,.5,1,.5' }
|
||||||
};
|
};
|
||||||
|
|
||||||
export function drawBoxChar(ctx: CanvasRenderingContext2D, c: string, xOffset: number, yOffset: number, cellWidth: number, cellHeight: number): void {
|
/**
|
||||||
const match: { [fontWeight: number]: string | ((xp: number, yp: number) => string) } = boxCharacters[c];
|
* Try drawing a custom block element or box drawing character, returning whether it was
|
||||||
if (!match) {
|
* successfully drawn.
|
||||||
return;
|
*/
|
||||||
|
export function tryDrawCustomChar(
|
||||||
|
ctx: CanvasRenderingContext2D,
|
||||||
|
c: string,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
scaledCellWidth: number,
|
||||||
|
scaledCellHeight: number,
|
||||||
|
scaledCharLeft: number,
|
||||||
|
scaledCharTop: number
|
||||||
|
): boolean {
|
||||||
|
const blockElementInstruction = blockElementChars[c];
|
||||||
|
if (blockElementInstruction) {
|
||||||
|
drawBlockElementChar(ctx, blockElementInstruction, x, y, scaledCellWidth, scaledCellHeight, scaledCharLeft, scaledCharTop);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
for (const [fontWeight, instructions] of Object.entries(match)) {
|
|
||||||
|
const boxDrawingInstruction = boxDrawingChars[c];
|
||||||
|
if (boxDrawingInstruction) {
|
||||||
|
drawBoxDrawingChar(ctx, boxDrawingInstruction, x, y, scaledCellWidth, scaledCellHeight);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawBlockElementChar(
|
||||||
|
ctx: CanvasRenderingContext2D,
|
||||||
|
instruction: IBlockVector[],
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
scaledCellWidth: number,
|
||||||
|
scaledCellHeight: number,
|
||||||
|
scaledCharLeft: number,
|
||||||
|
scaledCharTop: number
|
||||||
|
): void {
|
||||||
|
const xOffset = x * scaledCellWidth + scaledCharLeft;
|
||||||
|
const yOffset = y * scaledCellHeight + scaledCharTop;
|
||||||
|
for (let i = 0; i < instruction.length; i++) {
|
||||||
|
const box = instruction[i];
|
||||||
|
const xEighth = scaledCellWidth / 8;
|
||||||
|
const yEighth = scaledCellHeight / 8;
|
||||||
|
ctx.fillRect(
|
||||||
|
xOffset,
|
||||||
|
yOffset,
|
||||||
|
box.w * xEighth,
|
||||||
|
box.h * yEighth
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawBoxDrawingChar(
|
||||||
|
ctx: CanvasRenderingContext2D,
|
||||||
|
charDefinition: { [fontWeight: number]: string | ((xp: number, yp: number) => string) },
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
scaledCellWidth: number,
|
||||||
|
scaledCellHeight: number
|
||||||
|
): void {
|
||||||
|
const xOffset = x * scaledCellWidth;
|
||||||
|
const yOffset = y * scaledCellHeight;
|
||||||
|
ctx.strokeStyle = ctx.fillStyle;
|
||||||
|
for (const [fontWeight, instructions] of Object.entries(charDefinition)) {
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.lineWidth = window.devicePixelRatio * Number.parseInt(fontWeight);
|
ctx.lineWidth = window.devicePixelRatio * Number.parseInt(fontWeight);
|
||||||
let actualInstructions: string;
|
let actualInstructions: string;
|
||||||
if (typeof instructions === 'function') {
|
if (typeof instructions === 'function') {
|
||||||
const xp = .15;
|
const xp = .15;
|
||||||
const yp = .15 / cellHeight * cellWidth;
|
const yp = .15 / scaledCellHeight * scaledCellWidth;
|
||||||
actualInstructions = instructions(xp, yp);
|
actualInstructions = instructions(xp, yp);
|
||||||
} else {
|
} else {
|
||||||
actualInstructions = instructions;
|
actualInstructions = instructions;
|
||||||
@@ -295,7 +365,7 @@ export function drawBoxChar(ctx: CanvasRenderingContext2D, c: string, xOffset: n
|
|||||||
if (!args[0] || !args[1]) {
|
if (!args[0] || !args[1]) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
f(ctx, translateArgs(args, cellWidth, cellHeight, xOffset, yOffset));
|
f(ctx, translateArgs(args, scaledCellWidth, scaledCellHeight, xOffset, yOffset));
|
||||||
}
|
}
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
ctx.closePath();
|
ctx.closePath();
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ export const DEFAULT_OPTIONS: ITerminalOptions = Object.freeze({
|
|||||||
cursorBlink: false,
|
cursorBlink: false,
|
||||||
cursorStyle: 'block',
|
cursorStyle: 'block',
|
||||||
cursorWidth: 1,
|
cursorWidth: 1,
|
||||||
|
customBlockAndBoxCharacters: true,
|
||||||
bellSound: DEFAULT_BELL_SOUND,
|
bellSound: DEFAULT_BELL_SOUND,
|
||||||
bellStyle: 'none',
|
bellStyle: 'none',
|
||||||
drawBoldTextInBrightColors: true,
|
drawBoldTextInBrightColors: true,
|
||||||
|
|||||||
@@ -244,6 +244,7 @@ export interface ITerminalOptions {
|
|||||||
cursorBlink: boolean;
|
cursorBlink: boolean;
|
||||||
cursorStyle: 'block' | 'underline' | 'bar';
|
cursorStyle: 'block' | 'underline' | 'bar';
|
||||||
cursorWidth: number;
|
cursorWidth: number;
|
||||||
|
customBlockAndBoxCharacters: boolean;
|
||||||
disableStdin: boolean;
|
disableStdin: boolean;
|
||||||
drawBoldTextInBrightColors: boolean;
|
drawBoldTextInBrightColors: boolean;
|
||||||
fastScrollModifier: 'alt' | 'ctrl' | 'shift' | undefined;
|
fastScrollModifier: 'alt' | 'ctrl' | 'shift' | undefined;
|
||||||
|
|||||||
Vendored
+8
@@ -78,6 +78,14 @@ declare module 'xterm-headless' {
|
|||||||
*/
|
*/
|
||||||
cursorWidth?: number;
|
cursorWidth?: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to draw custom block element and box drawing characters instead of using the font.
|
||||||
|
* This should typically result in better rendering with continuous lines. Note that this
|
||||||
|
* doesn't work with the DOM renderer which renders all characters using the font. The default
|
||||||
|
* is true.
|
||||||
|
*/
|
||||||
|
customBlockAndBoxCharacters?: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether input should be disabled.
|
* Whether input should be disabled.
|
||||||
*/
|
*/
|
||||||
|
|||||||
Vendored
+8
@@ -90,6 +90,14 @@ declare module 'xterm' {
|
|||||||
*/
|
*/
|
||||||
cursorWidth?: number;
|
cursorWidth?: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to draw custom block element and box drawing characters instead of using the font.
|
||||||
|
* This should typically result in better rendering with continuous lines. Note that this
|
||||||
|
* doesn't work with the DOM renderer which renders all characters using the font. The default
|
||||||
|
* is true.
|
||||||
|
*/
|
||||||
|
customBlockAndBoxCharacters?: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether input should be disabled.
|
* Whether input should be disabled.
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user