From da0928102b72e7c28d3513033b08524b76b48bb8 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 18 Aug 2021 08:19:23 -0700 Subject: [PATCH] Support shade char with patterns --- .../src/atlas/WebglCharAtlas.ts | 2 +- src/browser/renderer/BaseRenderLayer.ts | 2 +- ...xAndBlockCharacters.ts => CustomGlyphs.ts} | 88 +++++++++++++++++++ 3 files changed, 90 insertions(+), 2 deletions(-) rename src/browser/renderer/{BoxAndBlockCharacters.ts => CustomGlyphs.ts} (90%) diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index 3b40659e..e1f69555 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -12,7 +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/BoxAndBlockCharacters'; +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. diff --git a/src/browser/renderer/BaseRenderLayer.ts b/src/browser/renderer/BaseRenderLayer.ts index b994a8df..117213eb 100644 --- a/src/browser/renderer/BaseRenderLayer.ts +++ b/src/browser/renderer/BaseRenderLayer.ts @@ -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 { tryDrawCustomChar } from 'browser/renderer/BoxAndBlockCharacters'; +import { tryDrawCustomChar } from 'browser/renderer/CustomGlyphs'; export abstract class BaseRenderLayer implements IRenderLayer { private _canvas: HTMLCanvasElement; diff --git a/src/browser/renderer/BoxAndBlockCharacters.ts b/src/browser/renderer/CustomGlyphs.ts similarity index 90% rename from src/browser/renderer/BoxAndBlockCharacters.ts rename to src/browser/renderer/CustomGlyphs.ts index 7d9a32bf..55dafc6b 100644 --- a/src/browser/renderer/BoxAndBlockCharacters.ts +++ b/src/browser/renderer/CustomGlyphs.ts @@ -3,6 +3,8 @@ * @license MIT */ +import { throwIfFalsy } from 'browser/renderer/RendererUtils'; + interface IBlockVector { x: number; y: number; @@ -101,6 +103,33 @@ export const blockElementDefinitions: { [index: string]: IBlockVector[] | undefi '\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 } = { + '░': [ + [1, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 0] + ], + '▒': [ + [1, 0], + [0, 0], + [0, 1], + [0, 0] + ], + '▓': [ + [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', @@ -299,6 +328,13 @@ export function tryDrawCustomChar( return true; } + const patternDefinition = patternCharacterDefinitions[c]; + if (patternDefinition) { + console.log('draw pattern', c); + drawPatternChar(ctx, patternDefinition, xOffset, yOffset, scaledCellWidth, scaledCellHeight); + return true; + } + const boxDrawingDefinition = boxDrawingDefinitions[c]; if (boxDrawingDefinition) { drawBoxDrawingChar(ctx, boxDrawingDefinition, xOffset, yOffset, scaledCellWidth, scaledCellHeight); @@ -329,6 +365,58 @@ function drawBlockElementChar( } } +const cachedPatterns: Map> = 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); + } + // TODO: Unsafe? + const fillStyle = ctx.fillStyle as string; + 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); + // TODO: This is a little unsafe, fillStyle could be rgb/rgba format + const r = parseInt(fillStyle.substr(1, 2), 16); + const g = parseInt(fillStyle.substr(3, 2), 16); + const b = parseInt(fillStyle.substr(5, 2), 16); + const a = fillStyle.length > 7 && parseInt(fillStyle.substr(7, 2), 16) || undefined; + 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); + } + } + console.log('put', imageData); + tmpCtx.putImageData(imageData, 0, 0); + // TODO: This will break for different colored patterns + // TODO: This could happen multiple times + pattern = throwIfFalsy(ctx.createPattern(tmpCanvas, null)); + patternSet.set(fillStyle, pattern); + } + + console.log('fill style', 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.