Merge pull request #3416 from meganrogge/merogge/pixelPerfect
pixel perfect canvas rendering of box and block characters
This commit is contained in:
@@ -230,7 +230,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
return;
|
||||
}
|
||||
|
||||
const atlas = acquireCharAtlas(this._terminal, this._colors, this.dimensions.scaledCharWidth, this.dimensions.scaledCharHeight);
|
||||
const atlas = acquireCharAtlas(this._terminal, this._colors, this.dimensions.scaledCellWidth, this.dimensions.scaledCellHeight, this.dimensions.scaledCharWidth, this.dimensions.scaledCharHeight);
|
||||
if (!('getRasterizedGlyph' in atlas)) {
|
||||
throw new Error('The webgl renderer only works with the webgl char atlas');
|
||||
}
|
||||
|
||||
@@ -28,10 +28,12 @@ const charAtlasCache: ICharAtlasCacheEntry[] = [];
|
||||
export function acquireCharAtlas(
|
||||
terminal: Terminal,
|
||||
colors: IColorSet,
|
||||
scaledCellWidth: number,
|
||||
scaledCellHeight: number,
|
||||
scaledCharWidth: number,
|
||||
scaledCharHeight: number
|
||||
): WebglCharAtlas {
|
||||
const newConfig = generateConfig(scaledCharWidth, scaledCharHeight, terminal, colors);
|
||||
const newConfig = generateConfig(scaledCellWidth, scaledCellHeight, scaledCharWidth, scaledCharHeight, terminal, colors);
|
||||
|
||||
// Check to see if the terminal already owns this config
|
||||
for (let i = 0; i < charAtlasCache.length; i++) {
|
||||
|
||||
@@ -13,7 +13,7 @@ const NULL_COLOR: IColor = {
|
||||
rgba: 0
|
||||
};
|
||||
|
||||
export function generateConfig(scaledCharWidth: number, scaledCharHeight: number, terminal: Terminal, colors: IColorSet): ICharAtlasConfig {
|
||||
export function generateConfig(scaledCellWidth: number, scaledCellHeight: number, scaledCharWidth: number, scaledCharHeight: number, terminal: Terminal, colors: IColorSet): ICharAtlasConfig {
|
||||
// null out some fields that don't matter
|
||||
const clonedColors: IColorSet = {
|
||||
foreground: colors.foreground,
|
||||
@@ -28,7 +28,12 @@ export function generateConfig(scaledCharWidth: number, scaledCharHeight: number
|
||||
contrastCache: colors.contrastCache
|
||||
};
|
||||
return {
|
||||
customGlyphs: terminal.getOption('customGlyphs'),
|
||||
devicePixelRatio: window.devicePixelRatio,
|
||||
letterSpacing: terminal.getOption('letterSpacing'),
|
||||
lineHeight: terminal.getOption('lineHeight'),
|
||||
scaledCellWidth,
|
||||
scaledCellHeight,
|
||||
scaledCharWidth,
|
||||
scaledCharHeight,
|
||||
fontFamily: terminal.getOption('fontFamily'),
|
||||
@@ -49,6 +54,9 @@ export function configEquals(a: ICharAtlasConfig, b: ICharAtlasConfig): boolean
|
||||
}
|
||||
}
|
||||
return a.devicePixelRatio === b.devicePixelRatio &&
|
||||
a.customGlyphs === b.customGlyphs &&
|
||||
a.lineHeight === b.lineHeight &&
|
||||
a.letterSpacing === b.letterSpacing &&
|
||||
a.fontFamily === b.fontFamily &&
|
||||
a.fontSize === b.fontSize &&
|
||||
a.fontWeight === b.fontWeight &&
|
||||
|
||||
@@ -17,11 +17,16 @@ export interface IGlyphIdentifier {
|
||||
}
|
||||
|
||||
export interface ICharAtlasConfig {
|
||||
customGlyphs: boolean;
|
||||
devicePixelRatio: number;
|
||||
letterSpacing: number;
|
||||
lineHeight: number;
|
||||
fontSize: number;
|
||||
fontFamily: string;
|
||||
fontWeight: FontWeight;
|
||||
fontWeightBold: FontWeight;
|
||||
scaledCellWidth: number;
|
||||
scaledCellHeight: number;
|
||||
scaledCharWidth: number;
|
||||
scaledCharHeight: number;
|
||||
allowTransparency: boolean;
|
||||
|
||||
@@ -12,6 +12,7 @@ import { IColor } from 'browser/Types';
|
||||
import { IDisposable } from 'xterm';
|
||||
import { AttributeData } from 'common/buffer/AttributeData';
|
||||
import { channels, rgba } from 'browser/Color';
|
||||
import { tryDrawCustomChar } from 'browser/renderer/CustomGlyphs';
|
||||
|
||||
// In practice we're probably never going to exhaust a texture this large. For debugging purposes,
|
||||
// however, it can be useful to set this to a really tiny value, to verify that LRU eviction works.
|
||||
@@ -83,8 +84,8 @@ export class WebglCharAtlas implements IDisposable {
|
||||
this._cacheCtx = throwIfFalsy(this.cacheCanvas.getContext('2d', { alpha: true }));
|
||||
|
||||
this._tmpCanvas = document.createElement('canvas');
|
||||
this._tmpCanvas.width = this._config.scaledCharWidth * 4 + TMP_CANVAS_GLYPH_PADDING * 2;
|
||||
this._tmpCanvas.height = this._config.scaledCharHeight + TMP_CANVAS_GLYPH_PADDING * 2;
|
||||
this._tmpCanvas.width = this._config.scaledCellWidth * 4 + TMP_CANVAS_GLYPH_PADDING * 2;
|
||||
this._tmpCanvas.height = this._config.scaledCellHeight + TMP_CANVAS_GLYPH_PADDING * 2;
|
||||
this._tmpCtx = throwIfFalsy(this._tmpCanvas.getContext('2d', { alpha: this._config.allowTransparency }));
|
||||
}
|
||||
|
||||
@@ -390,8 +391,16 @@ export class WebglCharAtlas implements IDisposable {
|
||||
// For powerline glyphs left/top padding is excluded (https://github.com/microsoft/vscode/issues/120129)
|
||||
const padding = isPowerlineGlyph ? 0 : TMP_CANVAS_GLYPH_PADDING;
|
||||
|
||||
// Draw custom characters if applicable
|
||||
let drawSuccess = false;
|
||||
if (this._config.customGlyphs !== false) {
|
||||
drawSuccess = tryDrawCustomChar(this._tmpCtx, chars, padding, padding, this._config.scaledCellWidth, this._config.scaledCellHeight);
|
||||
}
|
||||
|
||||
// Draw the character
|
||||
this._tmpCtx.fillText(chars, padding, padding + this._config.scaledCharHeight);
|
||||
if (!drawSuccess) {
|
||||
this._tmpCtx.fillText(chars, padding, padding + this._config.scaledCharHeight);
|
||||
}
|
||||
|
||||
// Draw underline and strikethrough
|
||||
if (underline || strikethrough) {
|
||||
|
||||
@@ -92,7 +92,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
if (this._scaledCharWidth <= 0 && this._scaledCharHeight <= 0) {
|
||||
return;
|
||||
}
|
||||
this._charAtlas = acquireCharAtlas(terminal, colorSet, this._scaledCharWidth, this._scaledCharHeight);
|
||||
this._charAtlas = acquireCharAtlas(terminal, colorSet, this._scaledCellWidth, this._scaledCellHeight, this._scaledCharWidth, this._scaledCharHeight);
|
||||
this._charAtlas.warmUp();
|
||||
}
|
||||
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { ITerminalOptions } from '../../../src/common/Types';
|
||||
import { ITheme } from 'xterm';
|
||||
import { assert } from 'chai';
|
||||
import { openTerminal, pollFor, writeSync, getBrowserType } from '../../../out-test/api/TestUtils';
|
||||
import { Browser, Page } from 'playwright';
|
||||
import { ITheme } from 'xterm';
|
||||
import { getBrowserType, openTerminal, pollFor, writeSync } from '../../../out-test/api/TestUtils';
|
||||
import { ITerminalOptions } from '../../../src/common/Types';
|
||||
|
||||
const APP = 'http://127.0.0.1:3001/test';
|
||||
|
||||
@@ -890,6 +890,20 @@ async function getCellColor(col: number, row: number): Promise<number[]> {
|
||||
return await page.evaluate(`Array.from(window.result)`);
|
||||
}
|
||||
|
||||
async function getCellPixels(col: number, row: number): Promise<number[]> {
|
||||
await page.evaluate(`
|
||||
window.gl = window.term._core._renderService._renderer._gl;
|
||||
window.result = new Uint8Array(window.d.scaledCellWidth * window.d.scaledCellHeight * 4);
|
||||
window.d = window.term._core._renderService.dimensions;
|
||||
window.gl.readPixels(
|
||||
Math.floor(${col - 1} * window.d.scaledCellWidth),
|
||||
Math.floor(window.gl.drawingBufferHeight - ${row} * window.d.scaledCellHeight),
|
||||
window.d.scaledCellWidth, window.d.scaledCellHeight, window.gl.RGBA, window.gl.UNSIGNED_BYTE, window.result
|
||||
);
|
||||
`);
|
||||
return await page.evaluate(`Array.from(window.result)`);
|
||||
}
|
||||
|
||||
async function setupBrowser(options: ITerminalOptions = { rendererType: 'dom' }): Promise<void> {
|
||||
const browserType = getBrowserType();
|
||||
browser = await browserType.launch({
|
||||
|
||||
@@ -147,6 +147,7 @@ if (document.location.pathname === '/test') {
|
||||
createTerminal();
|
||||
document.getElementById('dispose').addEventListener('click', disposeRecreateButtonHandler);
|
||||
document.getElementById('serialize').addEventListener('click', serializeButtonHandler);
|
||||
document.getElementById('custom-glyph').addEventListener('click', writeCustomGlyphHandler);
|
||||
}
|
||||
|
||||
function createTerminal(): void {
|
||||
@@ -431,3 +432,49 @@ function serializeButtonHandler(): void {
|
||||
term.write(output);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function writeCustomGlyphHandler() {
|
||||
term.write('\n\r');
|
||||
term.write('\n\r');
|
||||
term.write('Box styles: ┎┰┒┍┯┑╓╥╖╒╤╕ ┏┳┓┌┲┓┌┬┐┏┱┐\n\r');
|
||||
term.write('┌─┬─┐ ┏━┳━┓ ╔═╦═╗ ┠╂┨┝┿┥╟╫╢╞╪╡ ┡╇┩├╊┫┢╈┪┣╉┤\n\r');
|
||||
term.write('│ │ │ ┃ ┃ ┃ ║ ║ ║ ┖┸┚┕┷┙╙╨╜╘╧╛ └┴┘└┺┛┗┻┛┗┹┘\n\r');
|
||||
term.write('├─┼─┤ ┣━╋━┫ ╠═╬═╣ ┏┱┐┌┲┓┌┬┐┌┬┐ ┏┳┓┌┮┓┌┬┐┏┭┐\n\r');
|
||||
term.write('│ │ │ ┃ ┃ ┃ ║ ║ ║ ┡╃┤├╄┩├╆┪┢╅┤ ┞╀┦├┾┫┟╁┧┣┽┤\n\r');
|
||||
term.write('└─┴─┘ ┗━┻━┛ ╚═╩═╝ └┴┘└┴┘└┺┛┗┹┘ └┴┘└┶┛┗┻┛┗┵┘\n\r');
|
||||
term.write('\n\r');
|
||||
term.write('Other:\n\r');
|
||||
term.write('╭─╮ ╲ ╱ ╷╻╎╏┆┇┊┋ ╺╾╴ ╌╌╌ ┄┄┄ ┈┈┈\n\r');
|
||||
term.write('│ │ ╳ ╽╿╎╏┆┇┊┋ ╶╼╸ ╍╍╍ ┅┅┅ ┉┉┉\n\r');
|
||||
term.write('╰─╯ ╱ ╲ ╹╵╎╏┆┇┊┋\n\r');
|
||||
term.write('\n\r');
|
||||
term.write('All box drawing characters:\n\r');
|
||||
term.write('─ ━ │ ┃ ┄ ┅ ┆ ┇ ┈ ┉ ┊ ┋ ┌ ┍ ┎ ┏\n\r');
|
||||
term.write('┐ ┑ ┒ ┓ └ ┕ ┖ ┗ ┘ ┙ ┚ ┛ ├ ┝ ┞ ┟\n\r');
|
||||
term.write('┠ ┡ ┢ ┣ ┤ ┥ ┦ ┧ ┨ ┩ ┪ ┫ ┬ ┭ ┮ ┯\n\r');
|
||||
term.write('┰ ┱ ┲ ┳ ┴ ┵ ┶ ┷ ┸ ┹ ┺ ┻ ┼ ┽ ┾ ┿\n\r');
|
||||
term.write('╀ ╁ ╂ ╃ ╄ ╅ ╆ ╇ ╈ ╉ ╊ ╋ ╌ ╍ ╎ ╏\n\r');
|
||||
term.write('═ ║ ╒ ╓ ╔ ╕ ╖ ╗ ╘ ╙ ╚ ╛ ╜ ╝ ╞ ╟\n\r');
|
||||
term.write('╠ ╡ ╢ ╣ ╤ ╥ ╦ ╧ ╨ ╩ ╪ ╫ ╬ ╭ ╮ ╯\n\r');
|
||||
term.write('╰ ╱ ╲ ╳ ╴ ╵ ╶ ╷ ╸ ╹ ╺ ╻ ╼ ╽ ╾ ╿\n\r');
|
||||
term.write('Box drawing alignment tests:\x1b[31m █\n\r');
|
||||
term.write(' ▉\n\r');
|
||||
term.write(' ╔══╦══╗ ┌──┬──┐ ╭──┬──╮ ╭──┬──╮ ┏━━┳━━┓ ┎┒┏┑ ╷ ╻ ┏┯┓ ┌┰┐ ▊ ╱╲╱╲╳╳╳\n\r');
|
||||
term.write(' ║┌─╨─┐║ │╔═╧═╗│ │╒═╪═╕│ │╓─╁─╖│ ┃┌─╂─┐┃ ┗╃╄┙ ╶┼╴╺╋╸┠┼┨ ┝╋┥ ▋ ╲╱╲╱╳╳╳\n\r');
|
||||
term.write(' ║│╲ ╱│║ │║ ║│ ││ │ ││ │║ ┃ ║│ ┃│ ╿ │┃ ┍╅╆┓ ╵ ╹ ┗┷┛ └┸┘ ▌ ╱╲╱╲╳╳╳\n\r');
|
||||
term.write(' ╠╡ ╳ ╞╣ ├╢ ╟┤ ├┼─┼─┼┤ ├╫─╂─╫┤ ┣┿╾┼╼┿┫ ┕┛┖┚ ┌┄┄┐ ╎ ┏┅┅┓ ┋ ▍ ╲╱╲╱╳╳╳\n\r');
|
||||
term.write(' ║│╱ ╲│║ │║ ║│ ││ │ ││ │║ ┃ ║│ ┃│ ╽ │┃ ░░▒▒▓▓██ ┊ ┆ ╎ ╏ ┇ ┋ ▎\n\r');
|
||||
term.write(' ║└─╥─┘║ │╚═╤═╝│ │╘═╪═╛│ │╙─╀─╜│ ┃└─╂─┘┃ ░░▒▒▓▓██ ┊ ┆ ╎ ╏ ┇ ┋ ▏\n\r');
|
||||
term.write(' ╚══╩══╝ └──┴──┘ ╰──┴──╯ ╰──┴──╯ ┗━━┻━━┛ └╌╌┘ ╎ ┗╍╍┛ ┋ ▁▂▃▄▅▆▇█\n\r');
|
||||
term.write('Box drawing alignment tests:\x1b[32m █\n\r');
|
||||
term.write(' ▉\n\r');
|
||||
term.write(' ╔══╦══╗ ┌──┬──┐ ╭──┬──╮ ╭──┬──╮ ┏━━┳━━┓ ┎┒┏┑ ╷ ╻ ┏┯┓ ┌┰┐ ▊ ╱╲╱╲╳╳╳\n\r');
|
||||
term.write(' ║┌─╨─┐║ │╔═╧═╗│ │╒═╪═╕│ │╓─╁─╖│ ┃┌─╂─┐┃ ┗╃╄┙ ╶┼╴╺╋╸┠┼┨ ┝╋┥ ▋ ╲╱╲╱╳╳╳\n\r');
|
||||
term.write(' ║│╲ ╱│║ │║ ║│ ││ │ ││ │║ ┃ ║│ ┃│ ╿ │┃ ┍╅╆┓ ╵ ╹ ┗┷┛ └┸┘ ▌ ╱╲╱╲╳╳╳\n\r');
|
||||
term.write(' ╠╡ ╳ ╞╣ ├╢ ╟┤ ├┼─┼─┼┤ ├╫─╂─╫┤ ┣┿╾┼╼┿┫ ┕┛┖┚ ┌┄┄┐ ╎ ┏┅┅┓ ┋ ▍ ╲╱╲╱╳╳╳\n\r');
|
||||
term.write(' ║│╱ ╲│║ │║ ║│ ││ │ ││ │║ ┃ ║│ ┃│ ╽ │┃ ░░▒▒▓▓██ ┊ ┆ ╎ ╏ ┇ ┋ ▎\n\r');
|
||||
term.write(' ║└─╥─┘║ │╚═╤═╝│ │╘═╪═╛│ │╙─╀─╜│ ┃└─╂─┘┃ ░░▒▒▓▓██ ┊ ┆ ╎ ╏ ┇ ┋ ▏\n\r');
|
||||
term.write(' ╚══╩══╝ └──┴──┘ ╰──┴──╯ ╰──┴──╯ ┗━━┻━━┛ └╌╌┘ ╎ ┗╍╍┛ ┋ ▁▂▃▄▅▆▇█\n\r');
|
||||
window.scrollTo(0, 0);
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
</div>
|
||||
<hr/>
|
||||
<button id="dispose" title="This is used to testing memory leaks">Dispose terminal</button>
|
||||
<button id="custom-glyph" title="Write custom box drawing and block element characters to the terminal">Test custom glyphs</button>
|
||||
<script src="dist/client-bundle.js" defer ></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -224,6 +224,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
// The DOM renderer needs a row refresh to update the cursor styles
|
||||
this.refresh(this.buffer.y, this.buffer.y);
|
||||
break;
|
||||
case 'customGlyphs':
|
||||
case 'drawBoldTextInBrightColors':
|
||||
case 'letterSpacing':
|
||||
case 'lineHeight':
|
||||
|
||||
@@ -17,6 +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 { tryDrawCustomChar } from 'browser/renderer/CustomGlyphs';
|
||||
|
||||
export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
private _canvas: HTMLCanvasElement;
|
||||
@@ -259,10 +260,20 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
this._ctx.font = this._getFont(false, false);
|
||||
this._ctx.textBaseline = 'ideographic';
|
||||
this._clipRow(y);
|
||||
this._ctx.fillText(
|
||||
cell.getChars(),
|
||||
x * this._scaledCellWidth + this._scaledCharLeft,
|
||||
y * this._scaledCellHeight + this._scaledCharTop + this._scaledCharHeight);
|
||||
|
||||
// Draw custom characters if applicable
|
||||
let drawSuccess = false;
|
||||
if (this._optionsService.options.customGlyphs !== false) {
|
||||
drawSuccess = tryDrawCustomChar(this._ctx, cell.getChars(), x * this._scaledCellWidth, y * this._scaledCellHeight, this._scaledCellWidth, this._scaledCellHeight);
|
||||
}
|
||||
|
||||
// Draw the character
|
||||
if (!drawSuccess) {
|
||||
this._ctx.fillText(
|
||||
cell.getChars(),
|
||||
x * this._scaledCellWidth + this._scaledCharLeft,
|
||||
y * this._scaledCellHeight + this._scaledCharTop + this._scaledCharHeight);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -373,14 +384,25 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
if (cell.isDim()) {
|
||||
this._ctx.globalAlpha = DIM_OPACITY;
|
||||
}
|
||||
|
||||
// Draw custom characters if applicable
|
||||
let drawSuccess = false;
|
||||
if (this._optionsService.options.customGlyphs !== false) {
|
||||
drawSuccess = tryDrawCustomChar(this._ctx, cell.getChars(), x * this._scaledCellWidth, y * this._scaledCellHeight, this._scaledCellWidth, this._scaledCellHeight);
|
||||
}
|
||||
|
||||
// Draw the character
|
||||
this._ctx.fillText(
|
||||
cell.getChars(),
|
||||
x * this._scaledCellWidth + this._scaledCharLeft,
|
||||
y * this._scaledCellHeight + this._scaledCharTop + this._scaledCharHeight);
|
||||
if (!drawSuccess) {
|
||||
this._ctx.fillText(
|
||||
cell.getChars(),
|
||||
x * this._scaledCellWidth + this._scaledCharLeft,
|
||||
y * this._scaledCellHeight + this._scaledCharTop + this._scaledCharHeight);
|
||||
}
|
||||
|
||||
this._ctx.restore();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Clips a row to ensure no pixels will be drawn outside the cells in the row.
|
||||
* @param y The row to clip.
|
||||
|
||||
@@ -0,0 +1,563 @@
|
||||
/**
|
||||
* Copyright (c) 2021 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { throwIfFalsy } from 'browser/renderer/RendererUtils';
|
||||
|
||||
interface IBlockVector {
|
||||
x: number;
|
||||
y: number;
|
||||
w: number;
|
||||
h: number;
|
||||
}
|
||||
|
||||
export const blockElementDefinitions: { [index: string]: IBlockVector[] | undefined } = {
|
||||
// Block elements (0x2580-0x2590)
|
||||
'▀': [{ x: 0, y: 0, w: 8, h: 4 }], // UPPER HALF BLOCK
|
||||
'▁': [{ x: 0, y: 7, w: 8, h: 1 }], // LOWER ONE EIGHTH BLOCK
|
||||
'▂': [{ x: 0, y: 6, w: 8, h: 2 }], // LOWER ONE QUARTER BLOCK
|
||||
'▃': [{ x: 0, y: 5, w: 8, h: 3 }], // LOWER THREE EIGHTHS BLOCK
|
||||
'▄': [{ x: 0, y: 4, w: 8, h: 4 }], // LOWER HALF BLOCK
|
||||
'▅': [{ x: 0, y: 3, w: 8, h: 5 }], // LOWER FIVE EIGHTHS BLOCK
|
||||
'▆': [{ x: 0, y: 2, w: 8, h: 6 }], // LOWER THREE QUARTERS BLOCK
|
||||
'▇': [{ x: 0, y: 1, w: 8, h: 7 }], // LOWER SEVEN EIGHTHS BLOCK
|
||||
'█': [{ x: 0, y: 0, w: 8, h: 8 }], // FULL BLOCK
|
||||
'▉': [{ x: 0, y: 0, w: 7, h: 8 }], // LEFT SEVEN EIGHTHS BLOCK
|
||||
'▊': [{ x: 0, y: 0, w: 6, h: 8 }], // LEFT THREE QUARTERS BLOCK
|
||||
'▋': [{ x: 0, y: 0, w: 5, h: 8 }], // LEFT FIVE EIGHTHS BLOCK
|
||||
'▌': [{ x: 0, y: 0, w: 4, h: 8 }], // LEFT HALF BLOCK
|
||||
'▍': [{ x: 0, y: 0, w: 3, h: 8 }], // LEFT THREE EIGHTHS BLOCK
|
||||
'▎': [{ x: 0, y: 0, w: 2, h: 8 }], // LEFT ONE QUARTER BLOCK
|
||||
'▏': [{ x: 0, y: 0, w: 1, h: 8 }], // LEFT ONE EIGHTH BLOCK
|
||||
'▐': [{ x: 4, y: 0, w: 4, h: 8 }], // RIGHT HALF BLOCK
|
||||
|
||||
// Block elements (0x2594-0x2595)
|
||||
'▔': [{ x: 0, y: 0, w: 9, h: 1 }], // UPPER ONE EIGHTH BLOCK
|
||||
'▕': [{ x: 7, y: 0, w: 1, h: 8 }], // RIGHT ONE EIGHTH BLOCK
|
||||
|
||||
// Terminal graphic characters (0x2596-0x259F)
|
||||
'▖': [{ x: 0, y: 4, w: 4, h: 4 }], // QUADRANT LOWER LEFT
|
||||
'▗': [{ x: 4, y: 4, w: 4, h: 4 }], // QUADRANT LOWER RIGHT
|
||||
'▘': [{ x: 0, y: 0, w: 4, h: 4 }], // QUADRANT UPPER LEFT
|
||||
'▙': [{ x: 0, y: 0, w: 4, h: 8 }, { x: 0, y: 4, w: 8, h: 4 }], // QUADRANT UPPER LEFT AND LOWER LEFT AND LOWER RIGHT
|
||||
'▚': [{ x: 0, y: 0, w: 4, h: 4 }, { x: 4, y: 4, w: 4, h: 4 }], // QUADRANT UPPER LEFT AND LOWER RIGHT
|
||||
'▛': [{ x: 0, y: 0, w: 4, h: 8 }, { x: 0, y: 0, w: 4, h: 8 }], // QUADRANT UPPER LEFT AND UPPER RIGHT AND LOWER LEFT
|
||||
'▜': [{ x: 0, y: 0, w: 8, h: 4 }, { x: 4, y: 0, w: 4, h: 8 }], // QUADRANT UPPER LEFT AND UPPER RIGHT AND LOWER RIGHT
|
||||
'▝': [{ x: 4, y: 0, w: 4, h: 4 }], // QUADRANT UPPER RIGHT
|
||||
'▞': [{ x: 4, y: 0, w: 4, h: 4 }, { x: 0, y: 4, w: 4, h: 4 }], // QUADRANT UPPER RIGHT AND LOWER LEFT
|
||||
'▟': [{ x: 4, y: 0, w: 4, h: 8 }, { x: 0, y: 4, w: 8, h: 4 }], // QUADRANT UPPER RIGHT AND LOWER LEFT AND LOWER RIGHT
|
||||
|
||||
// VERTICAL ONE EIGHTH BLOCK-2 through VERTICAL ONE EIGHTH BLOCK-7
|
||||
'\u{1FB70}': [{ x: 1, y: 0, w: 1, h: 8 }],
|
||||
'\u{1FB71}': [{ x: 2, y: 0, w: 1, h: 8 }],
|
||||
'\u{1FB72}': [{ x: 3, y: 0, w: 1, h: 8 }],
|
||||
'\u{1FB73}': [{ x: 4, y: 0, w: 1, h: 8 }],
|
||||
'\u{1FB74}': [{ x: 5, y: 0, w: 1, h: 8 }],
|
||||
'\u{1FB75}': [{ x: 6, y: 0, w: 1, h: 8 }],
|
||||
|
||||
// HORIZONTAL ONE EIGHTH BLOCK-2 through HORIZONTAL ONE EIGHTH BLOCK-7
|
||||
'\u{1FB76}': [{ x: 0, y: 1, w: 8, h: 1 }],
|
||||
'\u{1FB77}': [{ x: 0, y: 2, w: 8, h: 1 }],
|
||||
'\u{1FB78}': [{ x: 0, y: 3, w: 8, h: 1 }],
|
||||
'\u{1FB79}': [{ x: 0, y: 4, w: 8, h: 1 }],
|
||||
'\u{1FB7A}': [{ x: 0, y: 5, w: 8, h: 1 }],
|
||||
'\u{1FB7B}': [{ x: 0, y: 6, w: 8, h: 1 }],
|
||||
|
||||
// LEFT AND LOWER ONE EIGHTH BLOCK
|
||||
'\u{1FB7C}': [{ x: 0, y: 0, w: 1, h: 8 }, { x: 0, y: 7, w: 8, h: 1 }],
|
||||
// LEFT AND UPPER ONE EIGHTH BLOCK
|
||||
'\u{1FB7D}': [{ x: 0, y: 0, w: 1, h: 8 }, { x: 0, y: 0, w: 8, h: 1 }],
|
||||
// RIGHT AND UPPER ONE EIGHTH BLOCK
|
||||
'\u{1FB7E}': [{ x: 7, y: 0, w: 1, h: 8 }, { x: 0, y: 0, w: 8, h: 1 }],
|
||||
// RIGHT AND LOWER ONE EIGHTH BLOCK
|
||||
'\u{1FB7F}': [{ x: 7, y: 0, w: 1, h: 8 }, { x: 0, y: 7, w: 8, h: 1 }],
|
||||
// UPPER AND LOWER ONE EIGHTH BLOCK
|
||||
'\u{1FB80}': [{ x: 0, y: 0, w: 8, h: 1 }, { x: 0, y: 7, w: 8, h: 1 }],
|
||||
// HORIZONTAL ONE EIGHTH BLOCK-1358
|
||||
'\u{1FB81}': [{ x: 0, y: 0, w: 8, h: 1 }, { x: 0, y: 2, w: 8, h: 1 }, { x: 0, y: 4, w: 8, h: 1 }, { x: 0, y: 7, w: 8, h: 1 }],
|
||||
|
||||
// UPPER ONE QUARTER BLOCK
|
||||
'\u{1FB82}': [{ x: 0, y: 0, w: 8, h: 2 }],
|
||||
// UPPER THREE EIGHTHS BLOCK
|
||||
'\u{1FB83}': [{ x: 0, y: 0, w: 8, h: 3 }],
|
||||
// UPPER FIVE EIGHTHS BLOCK
|
||||
'\u{1FB84}': [{ x: 0, y: 0, w: 8, h: 5 }],
|
||||
// UPPER THREE QUARTERS BLOCK
|
||||
'\u{1FB85}': [{ x: 0, y: 0, w: 8, h: 6 }],
|
||||
// UPPER SEVEN EIGHTHS BLOCK
|
||||
'\u{1FB86}': [{ x: 0, y: 0, w: 8, h: 7 }],
|
||||
|
||||
// RIGHT ONE QUARTER BLOCK
|
||||
'\u{1FB87}': [{ x: 6, y: 0, w: 2, h: 8 }],
|
||||
// RIGHT THREE EIGHTHS B0OCK
|
||||
'\u{1FB88}': [{ x: 5, y: 0, w: 3, h: 8 }],
|
||||
// RIGHT FIVE EIGHTHS BL0CK
|
||||
'\u{1FB89}': [{ x: 3, y: 0, w: 5, h: 8 }],
|
||||
// RIGHT THREE QUARTERS 0LOCK
|
||||
'\u{1FB8A}': [{ x: 2, y: 0, w: 6, h: 8 }],
|
||||
// RIGHT SEVEN EIGHTHS B0OCK
|
||||
'\u{1FB8B}': [{ x: 1, y: 0, w: 7, h: 8 }],
|
||||
|
||||
// CHECKER BOARD FILL
|
||||
'\u{1FB95}': [
|
||||
{ x: 0, y: 0, w: 2, h: 2 }, { x: 4, y: 0, w: 2, h: 2 },
|
||||
{ x: 2, y: 2, w: 2, h: 2 }, { x: 6, y: 2, w: 2, h: 2 },
|
||||
{ x: 0, y: 4, w: 2, h: 2 }, { x: 4, y: 4, w: 2, h: 2 },
|
||||
{ x: 2, y: 6, w: 2, h: 2 }, { x: 6, y: 6, w: 2, h: 2 }
|
||||
],
|
||||
// INVERSE CHECKER BOARD FILL
|
||||
'\u{1FB96}': [
|
||||
{ x: 2, y: 0, w: 2, h: 2 }, { x: 6, y: 0, w: 2, h: 2 },
|
||||
{ x: 0, y: 2, w: 2, h: 2 }, { x: 4, y: 2, w: 2, h: 2 },
|
||||
{ x: 2, y: 4, w: 2, h: 2 }, { x: 6, y: 4, w: 2, h: 2 },
|
||||
{ x: 0, y: 6, w: 2, h: 2 }, { x: 4, y: 6, w: 2, h: 2 }
|
||||
],
|
||||
// HEAVY HORIZONTAL FILL (upper middle and lower one quarter block)
|
||||
'\u{1FB97}': [{ x: 0, y: 2, w: 8, h: 2 }, { x: 0, y: 6, w: 8, h: 2 }]
|
||||
};
|
||||
|
||||
type PatternDefinition = number[][];
|
||||
|
||||
/**
|
||||
* Defines the repeating pattern used by special characters, the pattern is made up of a 2d array of
|
||||
* pixel values to be filled (1) or not filled (0).
|
||||
*/
|
||||
const patternCharacterDefinitions: { [key: string]: PatternDefinition | undefined } = {
|
||||
// Shade characters (0x2591-0x2593)
|
||||
'░': [ // LIGHT SHADE (25%)
|
||||
[1, 0, 0, 0],
|
||||
[0, 0, 0, 0],
|
||||
[0, 0, 1, 0],
|
||||
[0, 0, 0, 0]
|
||||
],
|
||||
'▒': [ // MEDIUM SHADE (50%)
|
||||
[1, 0],
|
||||
[0, 0],
|
||||
[0, 1],
|
||||
[0, 0]
|
||||
],
|
||||
'▓': [ // DARK SHADE (75%)
|
||||
[0, 1],
|
||||
[1, 1],
|
||||
[1, 0],
|
||||
[1, 1]
|
||||
]
|
||||
};
|
||||
|
||||
const enum Shapes {
|
||||
/** │ */ TOP_TO_BOTTOM = 'M.5,0 L.5,1',
|
||||
/** ─ */ LEFT_TO_RIGHT = 'M0,.5 L1,.5',
|
||||
|
||||
/** └ */ TOP_TO_RIGHT = 'M.5,0 L.5,.5 L1,.5',
|
||||
/** ┘ */ TOP_TO_LEFT = 'M.5,0 L.5,.5 L0,.5',
|
||||
/** ┐ */ LEFT_TO_BOTTOM = 'M0,.5 L.5,.5 L.5,1',
|
||||
/** ┌ */ RIGHT_TO_BOTTOM = 'M0.5,1 L.5,.5 L1,.5',
|
||||
|
||||
/** ╵ */ MIDDLE_TO_TOP = 'M.5,.5 L.5,0',
|
||||
/** ╴ */ MIDDLE_TO_LEFT = 'M.5,.5 L0,.5',
|
||||
/** ╶ */ MIDDLE_TO_RIGHT = 'M.5,.5 L1,.5',
|
||||
/** ╷ */ MIDDLE_TO_BOTTOM = 'M.5,.5 L.5,1',
|
||||
|
||||
/** ┴ */ T_TOP = 'M0,.5 L1,.5 M.5,.5 L.5,0',
|
||||
/** ┤ */ T_LEFT = 'M.5,0 L.5,1 M.5,.5 L0,.5',
|
||||
/** ├ */ T_RIGHT = 'M.5,0 L.5,1 M.5,.5 L1,.5',
|
||||
/** ┬ */ T_BOTTOM = 'M0,.5 L1,.5 M.5,.5 L.5,1',
|
||||
|
||||
/** ┼ */ CROSS = 'M0,.5 L1,.5 M.5,0 L.5,1',
|
||||
|
||||
/** ╌ */ TWO_DASHES_HORIZONTAL = 'M.1,.5 L.4,.5 M.6,.5 L.9,.5', // .2 empty, .3 filled
|
||||
/** ┄ */ THREE_DASHES_HORIZONTAL = 'M.0667,.5 L.2667,.5 M.4,.5 L.6,.5 M.7333,.5 L.9333,.5', // .1333 empty, .2 filled
|
||||
/** ┉ */ FOUR_DASHES_HORIZONTAL = 'M.05,.5 L.2,.5 M.3,.5 L.45,.5 M.55,.5 L.7,.5 M.8,.5 L.95,.5', // .1 empty, .15 filled
|
||||
/** ╎ */ TWO_DASHES_VERTICAL = 'M.5,.1 L.5,.4 M.5,.6 L.5,.9',
|
||||
/** ┆ */ THREE_DASHES_VERTICAL = 'M.5,.0667 L.5,.2667 M.5,.4 L.5,.6 M.5,.7333 L.5,.9333',
|
||||
/** ┊ */ FOUR_DASHES_VERTICAL = 'M.5,.05 L.5,.2 M.5,.3 L.5,.45 L.5,.55 M.5,.7 L.5,.95',
|
||||
}
|
||||
|
||||
const enum Style {
|
||||
NORMAL = 1,
|
||||
BOLD = 3
|
||||
}
|
||||
|
||||
/**
|
||||
* This contains the definitions of all box drawing characters in the format of SVG paths (ie. the
|
||||
* svg d attribute).
|
||||
*/
|
||||
export const boxDrawingDefinitions: { [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 },
|
||||
'│': { [Style.NORMAL]: Shapes.TOP_TO_BOTTOM },
|
||||
'┃': { [Style.BOLD]: Shapes.TOP_TO_BOTTOM },
|
||||
'┌': { [Style.NORMAL]: Shapes.RIGHT_TO_BOTTOM },
|
||||
'┏': { [Style.BOLD]: Shapes.RIGHT_TO_BOTTOM },
|
||||
'┐': { [Style.NORMAL]: Shapes.LEFT_TO_BOTTOM },
|
||||
'┓': { [Style.BOLD]: Shapes.LEFT_TO_BOTTOM },
|
||||
'└': { [Style.NORMAL]: Shapes.TOP_TO_RIGHT },
|
||||
'┗': { [Style.BOLD]: Shapes.TOP_TO_RIGHT },
|
||||
'┘': { [Style.NORMAL]: Shapes.TOP_TO_LEFT },
|
||||
'┛': { [Style.BOLD]: Shapes.TOP_TO_LEFT },
|
||||
'├': { [Style.NORMAL]: Shapes.T_RIGHT },
|
||||
'┣': { [Style.BOLD]: Shapes.T_RIGHT },
|
||||
'┤': { [Style.NORMAL]: Shapes.T_LEFT },
|
||||
'┫': { [Style.BOLD]: Shapes.T_LEFT },
|
||||
'┬': { [Style.NORMAL]: Shapes.T_BOTTOM },
|
||||
'┳': { [Style.BOLD]: Shapes.T_BOTTOM },
|
||||
'┴': { [Style.NORMAL]: Shapes.T_TOP },
|
||||
'┻': { [Style.BOLD]: Shapes.T_TOP },
|
||||
'┼': { [Style.NORMAL]: Shapes.CROSS },
|
||||
'╋': { [Style.BOLD]: Shapes.CROSS },
|
||||
'╴': { [Style.NORMAL]: Shapes.MIDDLE_TO_LEFT },
|
||||
'╸': { [Style.BOLD]: Shapes.MIDDLE_TO_LEFT },
|
||||
'╵': { [Style.NORMAL]: Shapes.MIDDLE_TO_TOP },
|
||||
'╹': { [Style.BOLD]: Shapes.MIDDLE_TO_TOP },
|
||||
'╶': { [Style.NORMAL]: Shapes.MIDDLE_TO_RIGHT },
|
||||
'╺': { [Style.BOLD]: Shapes.MIDDLE_TO_RIGHT },
|
||||
'╷': { [Style.NORMAL]: Shapes.MIDDLE_TO_BOTTOM },
|
||||
'╻': { [Style.BOLD]: Shapes.MIDDLE_TO_BOTTOM },
|
||||
|
||||
// Double border
|
||||
'═': { [Style.NORMAL]: (xp, yp) => `M0,${.5 - yp} L1,${.5 - yp} M0,${.5 + yp} L1,${.5 + yp}` },
|
||||
'║': { [Style.NORMAL]: (xp, yp) => `M${.5 - xp},0 L${.5 - xp},1 M${.5 + xp},0 L${.5 + xp},1` },
|
||||
'╒': { [Style.NORMAL]: (xp, yp) => `M.5,1 L.5,${.5 - yp} L1,${.5 - yp} M.5,${.5 + yp} L1,${.5 + yp}` },
|
||||
'╓': { [Style.NORMAL]: (xp, yp) => `M${.5 - xp},1 L${.5 - xp},.5 L1,.5 M${.5 + xp},.5 L${.5 + xp},1` },
|
||||
'╔': { [Style.NORMAL]: (xp, yp) => `M1,${.5 - yp} L${.5 - xp},${.5 - yp} L${.5 - xp},1 M1,${.5 + yp} L${.5 + xp},${.5 + yp} L${.5 + xp},1` },
|
||||
'╕': { [Style.NORMAL]: (xp, yp) => `M0,${.5 - yp} L.5,${.5 - yp} L.5,1 M0,${.5 + yp} L.5,${.5 + yp}` },
|
||||
'╖': { [Style.NORMAL]: (xp, yp) => `M${.5 + xp},1 L${.5 + xp},.5 L0,.5 M${.5 - xp},.5 L${.5 - xp},1` },
|
||||
'╗': { [Style.NORMAL]: (xp, yp) => `M0,${.5 + yp} L${.5 - xp},${.5 + yp} L${.5 - xp},1 M0,${.5 - yp} L${.5 + xp},${.5 - yp} L${.5 + xp},1` },
|
||||
'╘': { [Style.NORMAL]: (xp, yp) => `M.5,0 L.5,${.5 + yp} L1,${.5 + yp} M.5,${.5 - yp} L1,${.5 - yp}` },
|
||||
'╙': { [Style.NORMAL]: (xp, yp) => `M1,.5 L${.5 - xp},.5 L${.5 - xp},0 M${.5 + xp},.5 L${.5 + xp},0` },
|
||||
'╚': { [Style.NORMAL]: (xp, yp) => `M1,${.5 - yp} L${.5 + xp},${.5 - yp} L${.5 + xp},0 M1,${.5 + yp} L${.5 - xp},${.5 + yp} L${.5 - xp},0` },
|
||||
'╛': { [Style.NORMAL]: (xp, yp) => `M0,${.5 + yp} L.5,${.5 + yp} L.5,0 M0,${.5 - yp} L.5,${.5 - yp}` },
|
||||
'╜': { [Style.NORMAL]: (xp, yp) => `M0,.5 L${.5 + xp},.5 L${.5 + xp},0 M${.5 - xp},.5 L${.5 - xp},0` },
|
||||
'╝': { [Style.NORMAL]: (xp, yp) => `M0,${.5 - yp} L${.5 - xp},${.5 - yp} L${.5 - xp},0 M0,${.5 + yp} L${.5 + xp},${.5 + yp} L${.5 + xp},0` },
|
||||
'╞': { [Style.NORMAL]: (xp, yp) => `${Shapes.TOP_TO_BOTTOM} M.5,${.5 - yp} L1,${.5 - yp} M.5,${.5 + yp} L1,${.5 + yp}` },
|
||||
'╟': { [Style.NORMAL]: (xp, yp) => `M${.5 - xp},0 L${.5 - xp},1 M${.5 + xp},0 L${.5 + xp},1 M${.5 + xp},.5 L1,.5` },
|
||||
'╠': { [Style.NORMAL]: (xp, yp) => `M${.5 - xp},0 L${.5 - xp},1 M1,${.5 + yp} L${.5 + xp},${.5 + yp} L${.5 + xp},1 M1,${.5 - yp} L${.5 + xp},${.5 - yp} L${.5 + xp},0` },
|
||||
'╡': { [Style.NORMAL]: (xp, yp) => `${Shapes.TOP_TO_BOTTOM} M0,${.5 - yp} L.5,${.5 - yp} M0,${.5 + yp} L.5,${.5 + yp}` },
|
||||
'╢': { [Style.NORMAL]: (xp, yp) => `M0,.5 L${.5 - xp},.5 M${.5 - xp},0 L${.5 - xp},1 M${.5 + xp},0 L${.5 + xp},1` },
|
||||
'╣': { [Style.NORMAL]: (xp, yp) => `M${.5 + xp},0 L${.5 + xp},1 M0,${.5 + yp} L${.5 - xp},${.5 + yp} L${.5 - xp},1 M0,${.5 - yp} L${.5 - xp},${.5 - yp} L${.5 - xp},0` },
|
||||
'╤': { [Style.NORMAL]: (xp, yp) => `M0,${.5 - yp} L1,${.5 - yp} M0,${.5 + yp} L1,${.5 + yp} M.5,${.5 + yp} L.5,1` },
|
||||
'╥': { [Style.NORMAL]: (xp, yp) => `${Shapes.LEFT_TO_RIGHT} M${.5 - xp},.5 L${.5 - xp},1 M${.5 + xp},.5 L${.5 + xp},1` },
|
||||
'╦': { [Style.NORMAL]: (xp, yp) => `M0,${.5 - yp} L1,${.5 - yp} M0,${.5 + yp} L${.5 - xp},${.5 + yp} L${.5 - xp},1 M1,${.5 + yp} L${.5 + xp},${.5 + yp} L${.5 + xp},1` },
|
||||
'╧': { [Style.NORMAL]: (xp, yp) => `M.5,0 L.5,${.5 - yp} M0,${.5 - yp} L1,${.5 - yp} M0,${.5 + yp} L1,${.5 + yp}` },
|
||||
'╨': { [Style.NORMAL]: (xp, yp) => `${Shapes.LEFT_TO_RIGHT} M${.5 - xp},.5 L${.5 - xp},0 M${.5 + xp},.5 L${.5 + xp},0` },
|
||||
'╩': { [Style.NORMAL]: (xp, yp) => `M0,${.5 + yp} L1,${.5 + yp} M0,${.5 - yp} L${.5 - xp},${.5 - yp} L${.5 - xp},0 M1,${.5 - yp} L${.5 + xp},${.5 - yp} L${.5 + xp},0` },
|
||||
'╪': { [Style.NORMAL]: (xp, yp) => `${Shapes.TOP_TO_BOTTOM} M0,${.5 - yp} L1,${.5 - yp} M0,${.5 + yp} L1,${.5 + yp}` },
|
||||
'╫': { [Style.NORMAL]: (xp, yp) => `${Shapes.LEFT_TO_RIGHT} M${.5 - xp},0 L${.5 - xp},1 M${.5 + xp},0 L${.5 + xp},1` },
|
||||
'╬': { [Style.NORMAL]: (xp, yp) => `M0,${.5 + yp} L${.5 - xp},${.5 + yp} L${.5 - xp},1 M1,${.5 + yp} L${.5 + xp},${.5 + yp} L${.5 + xp},1 M0,${.5 - yp} L${.5 - xp},${.5 - yp} L${.5 - xp},0 M1,${.5 - yp} L${.5 + xp},${.5 - yp} L${.5 + xp},0` },
|
||||
|
||||
// Diagonal
|
||||
'╱': { [Style.NORMAL]: 'M1,0 L0,1' },
|
||||
'╲': { [Style.NORMAL]: 'M0,0 L1,1' },
|
||||
'╳': { [Style.NORMAL]: 'M1,0 L0,1 M0,0 L1,1' },
|
||||
|
||||
// Mixed weight
|
||||
'╼': { [Style.NORMAL]: Shapes.MIDDLE_TO_LEFT, [Style.BOLD]: Shapes.MIDDLE_TO_RIGHT },
|
||||
'╽': { [Style.NORMAL]: Shapes.MIDDLE_TO_TOP, [Style.BOLD]: Shapes.MIDDLE_TO_BOTTOM },
|
||||
'╾': { [Style.NORMAL]: Shapes.MIDDLE_TO_RIGHT, [Style.BOLD]: Shapes.MIDDLE_TO_LEFT },
|
||||
'╿': { [Style.NORMAL]: Shapes.MIDDLE_TO_BOTTOM, [Style.BOLD]: Shapes.MIDDLE_TO_TOP },
|
||||
'┍': { [Style.NORMAL]: Shapes.MIDDLE_TO_BOTTOM, [Style.BOLD]: Shapes.MIDDLE_TO_RIGHT },
|
||||
'┎': { [Style.NORMAL]: Shapes.MIDDLE_TO_RIGHT, [Style.BOLD]: Shapes.MIDDLE_TO_BOTTOM },
|
||||
'┑': { [Style.NORMAL]: Shapes.MIDDLE_TO_BOTTOM, [Style.BOLD]: Shapes.MIDDLE_TO_LEFT },
|
||||
'┒': { [Style.NORMAL]: Shapes.MIDDLE_TO_LEFT, [Style.BOLD]: Shapes.MIDDLE_TO_BOTTOM },
|
||||
'┕': { [Style.NORMAL]: Shapes.MIDDLE_TO_TOP, [Style.BOLD]: Shapes.MIDDLE_TO_RIGHT },
|
||||
'┖': { [Style.NORMAL]: Shapes.MIDDLE_TO_RIGHT, [Style.BOLD]: Shapes.MIDDLE_TO_TOP },
|
||||
'┙': { [Style.NORMAL]: Shapes.MIDDLE_TO_TOP, [Style.BOLD]: Shapes.MIDDLE_TO_LEFT },
|
||||
'┚': { [Style.NORMAL]: Shapes.MIDDLE_TO_LEFT, [Style.BOLD]: Shapes.MIDDLE_TO_TOP },
|
||||
'┝': { [Style.NORMAL]: Shapes.TOP_TO_BOTTOM, [Style.BOLD]: Shapes.MIDDLE_TO_RIGHT },
|
||||
'┞': { [Style.NORMAL]: Shapes.RIGHT_TO_BOTTOM, [Style.BOLD]: Shapes.MIDDLE_TO_TOP },
|
||||
'┟': { [Style.NORMAL]: Shapes.TOP_TO_RIGHT, [Style.BOLD]: Shapes.MIDDLE_TO_BOTTOM },
|
||||
'┠': { [Style.NORMAL]: Shapes.MIDDLE_TO_RIGHT, [Style.BOLD]: Shapes.TOP_TO_BOTTOM },
|
||||
'┡': { [Style.NORMAL]: Shapes.MIDDLE_TO_BOTTOM, [Style.BOLD]: Shapes.TOP_TO_RIGHT },
|
||||
'┢': { [Style.NORMAL]: Shapes.MIDDLE_TO_TOP, [Style.BOLD]: Shapes.RIGHT_TO_BOTTOM },
|
||||
'┥': { [Style.NORMAL]: Shapes.TOP_TO_BOTTOM, [Style.BOLD]: Shapes.MIDDLE_TO_LEFT },
|
||||
'┦': { [Style.NORMAL]: Shapes.LEFT_TO_BOTTOM, [Style.BOLD]: Shapes.MIDDLE_TO_TOP },
|
||||
'┧': { [Style.NORMAL]: Shapes.TOP_TO_LEFT, [Style.BOLD]: Shapes.MIDDLE_TO_BOTTOM },
|
||||
'┨': { [Style.NORMAL]: Shapes.MIDDLE_TO_LEFT, [Style.BOLD]: Shapes.TOP_TO_BOTTOM },
|
||||
'┩': { [Style.NORMAL]: Shapes.MIDDLE_TO_BOTTOM, [Style.BOLD]: Shapes.TOP_TO_LEFT },
|
||||
'┪': { [Style.NORMAL]: Shapes.MIDDLE_TO_TOP, [Style.BOLD]: Shapes.LEFT_TO_BOTTOM },
|
||||
'┭': { [Style.NORMAL]: Shapes.RIGHT_TO_BOTTOM, [Style.BOLD]: Shapes.MIDDLE_TO_LEFT },
|
||||
'┮': { [Style.NORMAL]: Shapes.LEFT_TO_BOTTOM, [Style.BOLD]: Shapes.MIDDLE_TO_RIGHT },
|
||||
'┯': { [Style.NORMAL]: Shapes.MIDDLE_TO_BOTTOM, [Style.BOLD]: Shapes.LEFT_TO_RIGHT },
|
||||
'┰': { [Style.NORMAL]: Shapes.LEFT_TO_RIGHT, [Style.BOLD]: Shapes.MIDDLE_TO_BOTTOM },
|
||||
'┱': { [Style.NORMAL]: Shapes.MIDDLE_TO_RIGHT, [Style.BOLD]: Shapes.LEFT_TO_BOTTOM },
|
||||
'┲': { [Style.NORMAL]: Shapes.MIDDLE_TO_LEFT, [Style.BOLD]: Shapes.RIGHT_TO_BOTTOM },
|
||||
'┵': { [Style.NORMAL]: Shapes.TOP_TO_RIGHT, [Style.BOLD]: Shapes.MIDDLE_TO_LEFT },
|
||||
'┶': { [Style.NORMAL]: Shapes.TOP_TO_LEFT, [Style.BOLD]: Shapes.MIDDLE_TO_RIGHT },
|
||||
'┷': { [Style.NORMAL]: Shapes.MIDDLE_TO_TOP, [Style.BOLD]: Shapes.LEFT_TO_RIGHT },
|
||||
'┸': { [Style.NORMAL]: Shapes.LEFT_TO_RIGHT, [Style.BOLD]: Shapes.MIDDLE_TO_TOP },
|
||||
'┹': { [Style.NORMAL]: Shapes.MIDDLE_TO_RIGHT, [Style.BOLD]: Shapes.TOP_TO_LEFT },
|
||||
'┺': { [Style.NORMAL]: Shapes.MIDDLE_TO_LEFT, [Style.BOLD]: Shapes.TOP_TO_RIGHT },
|
||||
'┽': { [Style.NORMAL]: `${Shapes.TOP_TO_BOTTOM} ${Shapes.MIDDLE_TO_RIGHT}`, [Style.BOLD]: Shapes.MIDDLE_TO_LEFT },
|
||||
'┾': { [Style.NORMAL]: `${Shapes.TOP_TO_BOTTOM} ${Shapes.MIDDLE_TO_LEFT}`, [Style.BOLD]: Shapes.MIDDLE_TO_RIGHT },
|
||||
'┿': { [Style.NORMAL]: Shapes.TOP_TO_BOTTOM, [Style.BOLD]: Shapes.LEFT_TO_RIGHT },
|
||||
'╀': { [Style.NORMAL]: `${Shapes.LEFT_TO_RIGHT} ${Shapes.MIDDLE_TO_BOTTOM}`, [Style.BOLD]: Shapes.MIDDLE_TO_TOP },
|
||||
'╁': { [Style.NORMAL]: `${Shapes.MIDDLE_TO_TOP} ${Shapes.LEFT_TO_RIGHT}`, [Style.BOLD]: Shapes.MIDDLE_TO_BOTTOM },
|
||||
'╂': { [Style.NORMAL]: Shapes.LEFT_TO_RIGHT, [Style.BOLD]: Shapes.TOP_TO_BOTTOM },
|
||||
'╃': { [Style.NORMAL]: Shapes.RIGHT_TO_BOTTOM, [Style.BOLD]: Shapes.TOP_TO_LEFT },
|
||||
'╄': { [Style.NORMAL]: Shapes.LEFT_TO_BOTTOM, [Style.BOLD]: Shapes.TOP_TO_RIGHT },
|
||||
'╅': { [Style.NORMAL]: Shapes.TOP_TO_RIGHT, [Style.BOLD]: Shapes.LEFT_TO_BOTTOM },
|
||||
'╆': { [Style.NORMAL]: Shapes.TOP_TO_LEFT, [Style.BOLD]: Shapes.RIGHT_TO_BOTTOM },
|
||||
'╇': { [Style.NORMAL]: Shapes.MIDDLE_TO_BOTTOM, [Style.BOLD]: `${Shapes.MIDDLE_TO_TOP} ${Shapes.LEFT_TO_RIGHT}` },
|
||||
'╈': { [Style.NORMAL]: Shapes.MIDDLE_TO_TOP, [Style.BOLD]: `${Shapes.LEFT_TO_RIGHT} ${Shapes.MIDDLE_TO_BOTTOM}` },
|
||||
'╉': { [Style.NORMAL]: Shapes.MIDDLE_TO_RIGHT, [Style.BOLD]: `${Shapes.TOP_TO_BOTTOM} ${Shapes.MIDDLE_TO_LEFT}` },
|
||||
'╊': { [Style.NORMAL]: Shapes.MIDDLE_TO_LEFT, [Style.BOLD]: `${Shapes.TOP_TO_BOTTOM} ${Shapes.MIDDLE_TO_RIGHT}` },
|
||||
|
||||
// Dashed
|
||||
'╌': { [Style.NORMAL]: Shapes.TWO_DASHES_HORIZONTAL },
|
||||
'╍': { [Style.BOLD]: Shapes.TWO_DASHES_HORIZONTAL },
|
||||
'┄': { [Style.NORMAL]: Shapes.THREE_DASHES_HORIZONTAL },
|
||||
'┅': { [Style.BOLD]: Shapes.THREE_DASHES_HORIZONTAL },
|
||||
'┈': { [Style.NORMAL]: Shapes.FOUR_DASHES_HORIZONTAL },
|
||||
'┉': { [Style.BOLD]: Shapes.FOUR_DASHES_HORIZONTAL },
|
||||
'╎': { [Style.NORMAL]: Shapes.TWO_DASHES_VERTICAL },
|
||||
'╏': { [Style.BOLD]: Shapes.TWO_DASHES_VERTICAL },
|
||||
'┆': { [Style.NORMAL]: Shapes.THREE_DASHES_VERTICAL },
|
||||
'┇': { [Style.BOLD]: Shapes.THREE_DASHES_VERTICAL },
|
||||
'┊': { [Style.NORMAL]: Shapes.FOUR_DASHES_VERTICAL },
|
||||
'┋': { [Style.BOLD]: Shapes.FOUR_DASHES_VERTICAL },
|
||||
|
||||
// Curved
|
||||
'╭': { [Style.NORMAL]: 'C.5,1,.5,.5,1,.5' },
|
||||
'╮': { [Style.NORMAL]: 'C.5,1,.5,.5,0,.5' },
|
||||
'╯': { [Style.NORMAL]: 'C.5,0,.5,.5,0,.5' },
|
||||
'╰': { [Style.NORMAL]: 'C.5,0,.5,.5,1,.5' }
|
||||
};
|
||||
|
||||
/**
|
||||
* Try drawing a custom block element or box drawing character, returning whether it was
|
||||
* successfully drawn.
|
||||
*/
|
||||
export function tryDrawCustomChar(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
c: string,
|
||||
xOffset: number,
|
||||
yOffset: number,
|
||||
scaledCellWidth: number,
|
||||
scaledCellHeight: number
|
||||
): boolean {
|
||||
const blockElementDefinition = blockElementDefinitions[c];
|
||||
if (blockElementDefinition) {
|
||||
drawBlockElementChar(ctx, blockElementDefinition, xOffset, yOffset, scaledCellWidth, scaledCellHeight);
|
||||
return true;
|
||||
}
|
||||
|
||||
const patternDefinition = patternCharacterDefinitions[c];
|
||||
if (patternDefinition) {
|
||||
drawPatternChar(ctx, patternDefinition, xOffset, yOffset, scaledCellWidth, scaledCellHeight);
|
||||
return true;
|
||||
}
|
||||
|
||||
const boxDrawingDefinition = boxDrawingDefinitions[c];
|
||||
if (boxDrawingDefinition) {
|
||||
drawBoxDrawingChar(ctx, boxDrawingDefinition, xOffset, yOffset, scaledCellWidth, scaledCellHeight);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function drawBlockElementChar(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
charDefinition: IBlockVector[],
|
||||
xOffset: number,
|
||||
yOffset: number,
|
||||
scaledCellWidth: number,
|
||||
scaledCellHeight: number
|
||||
): void {
|
||||
for (let i = 0; i < charDefinition.length; i++) {
|
||||
const box = charDefinition[i];
|
||||
const xEighth = scaledCellWidth / 8;
|
||||
const yEighth = scaledCellHeight / 8;
|
||||
ctx.fillRect(
|
||||
xOffset + box.x * xEighth,
|
||||
yOffset + box.y * yEighth,
|
||||
box.w * xEighth,
|
||||
box.h * yEighth
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const cachedPatterns: Map<PatternDefinition, Map</* fillStyle */string, CanvasPattern>> = new Map();
|
||||
|
||||
function drawPatternChar(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
charDefinition: number[][],
|
||||
xOffset: number,
|
||||
yOffset: number,
|
||||
scaledCellWidth: number,
|
||||
scaledCellHeight: number
|
||||
): void {
|
||||
let patternSet = cachedPatterns.get(charDefinition);
|
||||
if (!patternSet) {
|
||||
patternSet = new Map();
|
||||
cachedPatterns.set(charDefinition, patternSet);
|
||||
}
|
||||
const fillStyle = ctx.fillStyle;
|
||||
if (typeof fillStyle !== 'string') {
|
||||
throw new Error(`Unexpected fillStyle type "${fillStyle}"`);
|
||||
}
|
||||
let pattern = patternSet.get(fillStyle);
|
||||
if (!pattern) {
|
||||
const width = charDefinition[0].length;
|
||||
const height = charDefinition.length;
|
||||
const tmpCanvas = document.createElement('canvas');
|
||||
tmpCanvas.width = width;
|
||||
tmpCanvas.height = height;
|
||||
const tmpCtx = throwIfFalsy(tmpCanvas.getContext('2d'));
|
||||
const imageData = new ImageData(width, height);
|
||||
|
||||
// Extract rgba from fillStyle
|
||||
let r: number;
|
||||
let g: number;
|
||||
let b: number;
|
||||
let a: number;
|
||||
if (fillStyle.startsWith('#')) {
|
||||
r = parseInt(fillStyle.substr(1, 2), 16);
|
||||
g = parseInt(fillStyle.substr(3, 2), 16);
|
||||
b = parseInt(fillStyle.substr(5, 2), 16);
|
||||
a = fillStyle.length > 7 && parseInt(fillStyle.substr(7, 2), 16) || 1;
|
||||
} else if (fillStyle.startsWith('rgba')) {
|
||||
([r, g, b, a] = fillStyle.substring(5, fillStyle.length - 1).split(',').map(e => parseFloat(e)));
|
||||
} else {
|
||||
throw new Error(`Unexpected fillStyle color format "${fillStyle}" when drawing pattern glyph`);
|
||||
}
|
||||
|
||||
for (let y = 0; y < height; y++) {
|
||||
for (let x = 0; x < width; x++) {
|
||||
imageData.data[(y * width + x) * 4 ] = r;
|
||||
imageData.data[(y * width + x) * 4 + 1] = g;
|
||||
imageData.data[(y * width + x) * 4 + 2] = b;
|
||||
imageData.data[(y * width + x) * 4 + 3] = charDefinition[y][x] * (a * 255);
|
||||
}
|
||||
}
|
||||
tmpCtx.putImageData(imageData, 0, 0);
|
||||
pattern = throwIfFalsy(ctx.createPattern(tmpCanvas, null));
|
||||
patternSet.set(fillStyle, pattern);
|
||||
}
|
||||
ctx.fillStyle = pattern;
|
||||
ctx.fillRect(xOffset, yOffset, scaledCellWidth, scaledCellHeight);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the following box drawing characters by mapping a subset of SVG d attribute instructions to
|
||||
* canvas draw calls.
|
||||
*
|
||||
* Box styles: ┎┰┒┍┯┑╓╥╖╒╤╕ ┏┳┓┌┲┓┌┬┐┏┱┐
|
||||
* ┌─┬─┐ ┏━┳━┓ ╔═╦═╗ ┠╂┨┝┿┥╟╫╢╞╪╡ ┡╇┩├╊┫┢╈┪┣╉┤
|
||||
* │ │ │ ┃ ┃ ┃ ║ ║ ║ ┖┸┚┕┷┙╙╨╜╘╧╛ └┴┘└┺┛┗┻┛┗┹┘
|
||||
* ├─┼─┤ ┣━╋━┫ ╠═╬═╣ ┏┱┐┌┲┓┌┬┐┌┬┐ ┏┳┓┌┮┓┌┬┐┏┭┐
|
||||
* │ │ │ ┃ ┃ ┃ ║ ║ ║ ┡╃┤├╄┩├╆┪┢╅┤ ┞╀┦├┾┫┟╁┧┣┽┤
|
||||
* └─┴─┘ ┗━┻━┛ ╚═╩═╝ └┴┘└┴┘└┺┛┗┹┘ └┴┘└┶┛┗┻┛┗┵┘
|
||||
*
|
||||
* Other:
|
||||
* ╭─╮ ╲ ╱ ╷╻╎╏┆┇┊┋ ╺╾╴ ╌╌╌ ┄┄┄ ┈┈┈
|
||||
* │ │ ╳ ╽╿╎╏┆┇┊┋ ╶╼╸ ╍╍╍ ┅┅┅ ┉┉┉
|
||||
* ╰─╯ ╱ ╲ ╹╵╎╏┆┇┊┋
|
||||
*
|
||||
* All box drawing characters:
|
||||
* ─ ━ │ ┃ ┄ ┅ ┆ ┇ ┈ ┉ ┊ ┋ ┌ ┍ ┎ ┏
|
||||
* ┐ ┑ ┒ ┓ └ ┕ ┖ ┗ ┘ ┙ ┚ ┛ ├ ┝ ┞ ┟
|
||||
* ┠ ┡ ┢ ┣ ┤ ┥ ┦ ┧ ┨ ┩ ┪ ┫ ┬ ┭ ┮ ┯
|
||||
* ┰ ┱ ┲ ┳ ┴ ┵ ┶ ┷ ┸ ┹ ┺ ┻ ┼ ┽ ┾ ┿
|
||||
* ╀ ╁ ╂ ╃ ╄ ╅ ╆ ╇ ╈ ╉ ╊ ╋ ╌ ╍ ╎ ╏
|
||||
* ═ ║ ╒ ╓ ╔ ╕ ╖ ╗ ╘ ╙ ╚ ╛ ╜ ╝ ╞ ╟
|
||||
* ╠ ╡ ╢ ╣ ╤ ╥ ╦ ╧ ╨ ╩ ╪ ╫ ╬ ╭ ╮ ╯
|
||||
* ╰ ╱ ╲ ╳ ╴ ╵ ╶ ╷ ╸ ╹ ╺ ╻ ╼ ╽ ╾ ╿
|
||||
*
|
||||
* ---
|
||||
*
|
||||
* Box drawing alignment tests: █
|
||||
* ▉
|
||||
* ╔══╦══╗ ┌──┬──┐ ╭──┬──╮ ╭──┬──╮ ┏━━┳━━┓ ┎┒┏┑ ╷ ╻ ┏┯┓ ┌┰┐ ▊ ╱╲╱╲╳╳╳
|
||||
* ║┌─╨─┐║ │╔═╧═╗│ │╒═╪═╕│ │╓─╁─╖│ ┃┌─╂─┐┃ ┗╃╄┙ ╶┼╴╺╋╸┠┼┨ ┝╋┥ ▋ ╲╱╲╱╳╳╳
|
||||
* ║│╲ ╱│║ │║ ║│ ││ │ ││ │║ ┃ ║│ ┃│ ╿ │┃ ┍╅╆┓ ╵ ╹ ┗┷┛ └┸┘ ▌ ╱╲╱╲╳╳╳
|
||||
* ╠╡ ╳ ╞╣ ├╢ ╟┤ ├┼─┼─┼┤ ├╫─╂─╫┤ ┣┿╾┼╼┿┫ ┕┛┖┚ ┌┄┄┐ ╎ ┏┅┅┓ ┋ ▍ ╲╱╲╱╳╳╳
|
||||
* ║│╱ ╲│║ │║ ║│ ││ │ ││ │║ ┃ ║│ ┃│ ╽ │┃ ░░▒▒▓▓██ ┊ ┆ ╎ ╏ ┇ ┋ ▎
|
||||
* ║└─╥─┘║ │╚═╤═╝│ │╘═╪═╛│ │╙─╀─╜│ ┃└─╂─┘┃ ░░▒▒▓▓██ ┊ ┆ ╎ ╏ ┇ ┋ ▏
|
||||
* ╚══╩══╝ └──┴──┘ ╰──┴──╯ ╰──┴──╯ ┗━━┻━━┛ └╌╌┘ ╎ ┗╍╍┛ ┋ ▁▂▃▄▅▆▇█
|
||||
*
|
||||
* Source: https://www.w3.org/2001/06/utf-8-test/UTF-8-demo.html
|
||||
*/
|
||||
function drawBoxDrawingChar(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
charDefinition: { [fontWeight: number]: string | ((xp: number, yp: number) => string) },
|
||||
xOffset: number,
|
||||
yOffset: number,
|
||||
scaledCellWidth: number,
|
||||
scaledCellHeight: number
|
||||
): void {
|
||||
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 / scaledCellHeight * scaledCellWidth;
|
||||
actualInstructions = instructions(xp, yp);
|
||||
} else {
|
||||
actualInstructions = instructions;
|
||||
}
|
||||
for (const instruction of actualInstructions.split(' ')) {
|
||||
const type = instruction[0];
|
||||
const f = svgToCanvasInstructionMap[type];
|
||||
if (!f) {
|
||||
console.error(`Could not find drawing instructions for "${type}"`);
|
||||
continue;
|
||||
}
|
||||
const args: string[] = instruction.substring(1).split(',');
|
||||
if (!args[0] || !args[1]) {
|
||||
continue;
|
||||
}
|
||||
f(ctx, translateArgs(args, scaledCellWidth, scaledCellHeight, xOffset, yOffset));
|
||||
}
|
||||
ctx.stroke();
|
||||
ctx.closePath();
|
||||
}
|
||||
}
|
||||
|
||||
function clamp(value: number, max: number, min: number = 0): number {
|
||||
return Math.max(Math.min(value, max), min);
|
||||
}
|
||||
|
||||
const svgToCanvasInstructionMap: { [index: string]: any } = {
|
||||
'C': (ctx: CanvasRenderingContext2D, args: number[]) => ctx.bezierCurveTo(args[0], args[1], args[2], args[3], args[4], args[5]),
|
||||
'L': (ctx: CanvasRenderingContext2D, args: number[]) => ctx.lineTo(args[0], args[1]),
|
||||
'M': (ctx: CanvasRenderingContext2D, args: number[]) => ctx.moveTo(args[0], args[1])
|
||||
};
|
||||
|
||||
function translateArgs(args: string[], cellWidth: number, cellHeight: number, xOffset: number, yOffset: number): number[] {
|
||||
const result = args.map(e => parseFloat(e) || parseInt(e));
|
||||
|
||||
if (result.length < 2) {
|
||||
throw new Error('Too few arguments for instruction');
|
||||
}
|
||||
|
||||
for (let x = 0; x < result.length; x += 2) {
|
||||
// Translate from 0-1 to 0-cellWidth
|
||||
result[x] *= cellWidth;
|
||||
// Ensure coordinate doesn't escape cell bounds and round to the nearest 0.5 to ensure a crisp
|
||||
// line at 100% devicePixelRatio
|
||||
if (result[x] !== 0) {
|
||||
result[x] = clamp(Math.round(result[x] + 0.5) - 0.5, cellWidth, 0);
|
||||
}
|
||||
// Apply the cell's offset (ie. x*cellWidth)
|
||||
result[x] += xOffset;
|
||||
}
|
||||
|
||||
for (let y = 1; y < result.length; y += 2) {
|
||||
// Translate from 0-1 to 0-cellHeight
|
||||
result[y] *= cellHeight;
|
||||
// Ensure coordinate doesn't escape cell bounds and round to the nearest 0.5 to ensure a crisp
|
||||
// line at 100% devicePixelRatio
|
||||
if (result[y] !== 0) {
|
||||
result[y] = clamp(Math.round(result[y] + 0.5) - 0.5, cellHeight, 0);
|
||||
}
|
||||
// Apply the cell's offset (ie. x*cellHeight)
|
||||
result[y] += yOffset;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -21,6 +21,7 @@ export const DEFAULT_OPTIONS: ITerminalOptions = Object.freeze({
|
||||
cursorBlink: false,
|
||||
cursorStyle: 'block',
|
||||
cursorWidth: 1,
|
||||
customGlyphs: true,
|
||||
bellSound: DEFAULT_BELL_SOUND,
|
||||
bellStyle: 'none',
|
||||
drawBoldTextInBrightColors: true,
|
||||
|
||||
@@ -244,6 +244,7 @@ export interface ITerminalOptions {
|
||||
cursorBlink: boolean;
|
||||
cursorStyle: 'block' | 'underline' | 'bar';
|
||||
cursorWidth: number;
|
||||
customGlyphs: boolean;
|
||||
disableStdin: boolean;
|
||||
drawBoldTextInBrightColors: boolean;
|
||||
fastScrollModifier: 'alt' | 'ctrl' | 'shift' | undefined;
|
||||
|
||||
Vendored
+8
@@ -78,6 +78,14 @@ declare module 'xterm-headless' {
|
||||
*/
|
||||
cursorWidth?: number;
|
||||
|
||||
/**
|
||||
* Whether to draw custom glyphs for block element and box drawing characters instead of using
|
||||
* the font. This should typically result in better rendering with continuous lines, even when
|
||||
* line height and letter spacing is used. Note that this doesn't work with the DOM renderer
|
||||
* which renders all characters using the font. The default is true.
|
||||
*/
|
||||
customGlyphs?: boolean;
|
||||
|
||||
/**
|
||||
* Whether input should be disabled.
|
||||
*/
|
||||
|
||||
Vendored
+8
@@ -90,6 +90,14 @@ declare module 'xterm' {
|
||||
*/
|
||||
cursorWidth?: number;
|
||||
|
||||
/**
|
||||
* Whether to draw custom glyphs for block element and box drawing characters instead of using
|
||||
* the font. This should typically result in better rendering with continuous lines, even when
|
||||
* line height and letter spacing is used. Note that this doesn't work with the DOM renderer
|
||||
* which renders all characters using the font. The default is true.
|
||||
*/
|
||||
customGlyphs?: boolean;
|
||||
|
||||
/**
|
||||
* Whether input should be disabled.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user