Add setting, clean up drawing, fix true color

This commit is contained in:
Daniel Imms
2021-08-18 06:44:13 -07:00
parent ca88476934
commit 9fb1ce11fb
6 changed files with 118 additions and 45 deletions
+20 -35
View File
@@ -17,7 +17,7 @@ import { IBufferService, IOptionsService } from 'common/services/Services';
import { throwIfFalsy } from 'browser/renderer/RendererUtils';
import { channels, color, rgba } from 'browser/Color';
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 {
private _canvas: HTMLCanvasElement;
@@ -260,8 +260,15 @@ export abstract class BaseRenderLayer implements IRenderLayer {
this._ctx.font = this._getFont(false, false);
this._ctx.textBaseline = 'ideographic';
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(
cell.getChars(),
x * this._scaledCellWidth + this._scaledCharLeft,
@@ -377,46 +384,24 @@ export abstract class BaseRenderLayer implements IRenderLayer {
if (cell.isDim()) {
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(
cell.getChars(),
x * this._scaledCellWidth + this._scaledCharLeft,
y * this._scaledCellHeight + this._scaledCharTop + this._scaledCharHeight);
}
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.
+80 -10
View File
@@ -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: 8 }],
'▇': [{ 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)
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
'─': { [Style.NORMAL]: 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}` },
// Dashed
// TODO: Spacing dashes evenly, use 1/2 padding on each edge so the line is continuous
'╌': { [Style.NORMAL]: Shapes.TWO_DASHES_HORIZONTAL },
'╍': { [Style.BOLD]: Shapes.TWO_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' }
};
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];
if (!match) {
return;
/**
* Try drawing a custom block element or box drawing character, returning whether it was
* successfully drawn.
*/
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.lineWidth = window.devicePixelRatio * Number.parseInt(fontWeight);
let actualInstructions: string;
if (typeof instructions === 'function') {
const xp = .15;
const yp = .15 / cellHeight * cellWidth;
const yp = .15 / scaledCellHeight * scaledCellWidth;
actualInstructions = instructions(xp, yp);
} else {
actualInstructions = instructions;
@@ -295,7 +365,7 @@ export function drawBoxChar(ctx: CanvasRenderingContext2D, c: string, xOffset: n
if (!args[0] || !args[1]) {
continue;
}
f(ctx, translateArgs(args, cellWidth, cellHeight, xOffset, yOffset));
f(ctx, translateArgs(args, scaledCellWidth, scaledCellHeight, xOffset, yOffset));
}
ctx.stroke();
ctx.closePath();
+1
View File
@@ -21,6 +21,7 @@ export const DEFAULT_OPTIONS: ITerminalOptions = Object.freeze({
cursorBlink: false,
cursorStyle: 'block',
cursorWidth: 1,
customBlockAndBoxCharacters: true,
bellSound: DEFAULT_BELL_SOUND,
bellStyle: 'none',
drawBoldTextInBrightColors: true,
+1
View File
@@ -244,6 +244,7 @@ export interface ITerminalOptions {
cursorBlink: boolean;
cursorStyle: 'block' | 'underline' | 'bar';
cursorWidth: number;
customBlockAndBoxCharacters: boolean;
disableStdin: boolean;
drawBoldTextInBrightColors: boolean;
fastScrollModifier: 'alt' | 'ctrl' | 'shift' | undefined;
+8
View File
@@ -78,6 +78,14 @@ declare module 'xterm-headless' {
*/
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.
*/
+8
View File
@@ -90,6 +90,14 @@ declare module 'xterm' {
*/
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.
*/