From b9da995ff6fedc0e03fa99ad160c663bd65e73b3 Mon Sep 17 00:00:00 2001 From: tisilent Date: Sat, 11 Nov 2023 21:30:04 +0800 Subject: [PATCH 01/12] add vivid curly --- .../renderer/shared/CellColorResolver.ts | 11 +++ src/browser/renderer/shared/TextureAtlas.ts | 81 +++++++++++-------- 2 files changed, 58 insertions(+), 34 deletions(-) diff --git a/src/browser/renderer/shared/CellColorResolver.ts b/src/browser/renderer/shared/CellColorResolver.ts index 5837a675b2..d581af19fa 100644 --- a/src/browser/renderer/shared/CellColorResolver.ts +++ b/src/browser/renderer/shared/CellColorResolver.ts @@ -60,6 +60,17 @@ export class CellColorResolver { if (code !== NULL_CELL_CODE && cell.extended.underlineStyle === UnderlineStyle.DOTTED) { const lineWidth = Math.max(1, Math.floor(this._optionService.rawOptions.fontSize * this._coreBrowserService.dpr / 15)); $variantOffset = x * deviceCellWidth % (Math.round(lineWidth) * 2); + } else if (code !== NULL_CELL_CODE && cell.extended.underlineStyle === UnderlineStyle.CURLY) { + // 3px per segment + // 0-2 forward, 3-5 reverse + const offset = x * deviceCellWidth % 3; + const fullSegmentCount = (x * deviceCellWidth) / (3 * 2); + const forwardReverse = fullSegmentCount - Math.floor(fullSegmentCount) >= 0.5 ? 1 : 0; + if (forwardReverse === 0) { + $variantOffset = offset; + } else { + $variantOffset = offset + 3; + } } // Apply decorations on the bottom layer diff --git a/src/browser/renderer/shared/TextureAtlas.ts b/src/browser/renderer/shared/TextureAtlas.ts index f3f67b8b21..2d498b90dd 100644 --- a/src/browser/renderer/shared/TextureAtlas.ts +++ b/src/browser/renderer/shared/TextureAtlas.ts @@ -551,7 +551,7 @@ export class TextureAtlas implements ITextureAtlas { this._tmpCtx.save(); const xChLeft = xLeft + i * this._config.deviceCellWidth; const xChRight = xLeft + (i + 1) * this._config.deviceCellWidth; - const xChMid = xChLeft + this._config.deviceCellWidth / 2; + // const xChMid = xChLeft + this._config.deviceCellWidth / 2; switch (this._workAttributeData.extended.underlineStyle) { case UnderlineStyle.DOUBLE: this._tmpCtx.moveTo(xChLeft, yTop); @@ -560,39 +560,52 @@ export class TextureAtlas implements ITextureAtlas { this._tmpCtx.lineTo(xChRight, yBot); break; case UnderlineStyle.CURLY: - // Choose the bezier top and bottom based on the device pixel ratio, the curly line is - // made taller when the line width is as otherwise it's not very clear otherwise. - const yCurlyBot = lineWidth <= 1 ? yBot : Math.ceil(padding + this._config.deviceCharHeight - lineWidth / 2) - yOffset; - const yCurlyTop = lineWidth <= 1 ? yTop : Math.ceil(padding + this._config.deviceCharHeight + lineWidth / 2) - yOffset; - // Clip the left and right edges of the underline such that it can be drawn just outside - // the edge of the cell to ensure a continuous stroke when there are multiple underlined - // glyphs adjacent to one another. - const clipRegion = new Path2D(); - clipRegion.rect(xChLeft, yTop, this._config.deviceCellWidth, yBot - yTop); - this._tmpCtx.clip(clipRegion); - // Start 1/2 cell before and end 1/2 cells after to ensure a smooth curve with other - // cells - this._tmpCtx.moveTo(xChLeft - this._config.deviceCellWidth / 2, yMid); - this._tmpCtx.bezierCurveTo( - xChLeft - this._config.deviceCellWidth / 2, yCurlyTop, - xChLeft, yCurlyTop, - xChLeft, yMid - ); - this._tmpCtx.bezierCurveTo( - xChLeft, yCurlyBot, - xChMid, yCurlyBot, - xChMid, yMid - ); - this._tmpCtx.bezierCurveTo( - xChMid, yCurlyTop, - xChRight, yCurlyTop, - xChRight, yMid - ); - this._tmpCtx.bezierCurveTo( - xChRight, yCurlyBot, - xChRight + this._config.deviceCellWidth / 2, yCurlyBot, - xChRight + this._config.deviceCellWidth / 2, yMid - ); + let upDown: 'Up' | 'Down' = 'Up'; + let segmentOffset = 0; + if (nextOffset >= 0 && nextOffset <= 2) { + // Up + upDown = 'Up'; + segmentOffset = nextOffset; + } else { + // Dowm + upDown = 'Down'; + segmentOffset = nextOffset - 3; + } + for (let index = 0; index < this._config.deviceCellWidth; index++) { + if (segmentOffset >= 3) { + upDown = upDown === 'Down' ? 'Up' : 'Down'; + segmentOffset = 0; + } + if (upDown === 'Up') { + if (segmentOffset === 0) { + this._tmpCtx.fillRect(xChLeft + index, yMid - 0.5, 1, 1); + } else if (segmentOffset === 1 || segmentOffset === 2) { + this._tmpCtx.fillRect(xChLeft + index, yMid -0.5 - 1, 1, 1); + } + } else if (upDown === 'Down') { + if (segmentOffset === 0) { + this._tmpCtx.fillRect(xChLeft + index, yMid - 0.5, 1, 1); + } else if (segmentOffset === 1 || segmentOffset === 2) { + this._tmpCtx.fillRect(xChLeft + index, yMid -0.5 + 1, 1, 1); + } + } + segmentOffset++; + } + // handle next + if (segmentOffset >= 3) { + const nextUpDown: 'Up' | 'Down' = upDown === 'Up' ? 'Down' : 'Up'; + if (nextUpDown === 'Up') { + nextOffset = 0; + } else { + nextOffset = 3; + } + } else { + if (upDown === 'Up') { + nextOffset = segmentOffset; + } else { + nextOffset = segmentOffset + 3; + } + } break; case UnderlineStyle.DOTTED: const offsetWidth = nextOffset === 0 ? 0 : From 0bf6b9fe098b457924c023dc051aef8d66e3fc34 Mon Sep 17 00:00:00 2001 From: tisilent Date: Sat, 11 Nov 2023 21:37:21 +0800 Subject: [PATCH 02/12] set the offset of yMid --- src/browser/renderer/shared/TextureAtlas.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/browser/renderer/shared/TextureAtlas.ts b/src/browser/renderer/shared/TextureAtlas.ts index 2d498b90dd..ff1f3da007 100644 --- a/src/browser/renderer/shared/TextureAtlas.ts +++ b/src/browser/renderer/shared/TextureAtlas.ts @@ -560,6 +560,8 @@ export class TextureAtlas implements ITextureAtlas { this._tmpCtx.lineTo(xChRight, yBot); break; case UnderlineStyle.CURLY: + // [TODO] Up or down offset, To be verified. + const yMidOffset = Math.floor(yMid); let upDown: 'Up' | 'Down' = 'Up'; let segmentOffset = 0; if (nextOffset >= 0 && nextOffset <= 2) { @@ -578,15 +580,15 @@ export class TextureAtlas implements ITextureAtlas { } if (upDown === 'Up') { if (segmentOffset === 0) { - this._tmpCtx.fillRect(xChLeft + index, yMid - 0.5, 1, 1); + this._tmpCtx.fillRect(xChLeft + index, yMidOffset, 1, 1); } else if (segmentOffset === 1 || segmentOffset === 2) { - this._tmpCtx.fillRect(xChLeft + index, yMid -0.5 - 1, 1, 1); + this._tmpCtx.fillRect(xChLeft + index, yMidOffset - 1, 1, 1); } } else if (upDown === 'Down') { if (segmentOffset === 0) { - this._tmpCtx.fillRect(xChLeft + index, yMid - 0.5, 1, 1); + this._tmpCtx.fillRect(xChLeft + index, yMidOffset, 1, 1); } else if (segmentOffset === 1 || segmentOffset === 2) { - this._tmpCtx.fillRect(xChLeft + index, yMid -0.5 + 1, 1, 1); + this._tmpCtx.fillRect(xChLeft + index, yMidOffset + 1, 1, 1); } } segmentOffset++; From 400a263ba453fe371152456dac0f2d7ef076fe10 Mon Sep 17 00:00:00 2001 From: tisilent Date: Sat, 11 Nov 2023 22:07:07 +0800 Subject: [PATCH 03/12] add types,modify segmentsize to 4 --- .../renderer/shared/CellColorResolver.ts | 17 ++++--- src/browser/renderer/shared/Constants.ts | 2 + src/browser/renderer/shared/TextureAtlas.ts | 49 +++++++++++-------- src/browser/renderer/shared/Types.d.ts | 2 + 4 files changed, 41 insertions(+), 29 deletions(-) diff --git a/src/browser/renderer/shared/CellColorResolver.ts b/src/browser/renderer/shared/CellColorResolver.ts index d581af19fa..b374711735 100644 --- a/src/browser/renderer/shared/CellColorResolver.ts +++ b/src/browser/renderer/shared/CellColorResolver.ts @@ -1,10 +1,11 @@ -import { ISelectionRenderModel } from 'browser/renderer/shared/Types'; +import { ISelectionRenderModel, UnderlineCurlySegmentType } from 'browser/renderer/shared/Types'; import { ICoreBrowserService, IThemeService } from 'browser/services/Services'; import { ReadonlyColorSet } from 'browser/Types'; import { Attributes, BgFlags, ExtFlags, FgFlags, NULL_CELL_CODE, UnderlineStyle } from 'common/buffer/Constants'; import { IDecorationService, IOptionsService } from 'common/services/Services'; import { ICellData } from 'common/Types'; import { Terminal } from '@xterm/xterm'; +import { UNDERLINE_CURLY_SEGMENT_SIZE } from 'browser/renderer/shared/Constants'; // Work variables to avoid garbage collection let $fg = 0; @@ -61,15 +62,15 @@ export class CellColorResolver { const lineWidth = Math.max(1, Math.floor(this._optionService.rawOptions.fontSize * this._coreBrowserService.dpr / 15)); $variantOffset = x * deviceCellWidth % (Math.round(lineWidth) * 2); } else if (code !== NULL_CELL_CODE && cell.extended.underlineStyle === UnderlineStyle.CURLY) { - // 3px per segment - // 0-2 forward, 3-5 reverse - const offset = x * deviceCellWidth % 3; - const fullSegmentCount = (x * deviceCellWidth) / (3 * 2); - const forwardReverse = fullSegmentCount - Math.floor(fullSegmentCount) >= 0.5 ? 1 : 0; - if (forwardReverse === 0) { + // 4px per segment + // 0-3 forward (Up), 4-7 reverse (Down) + const offset = x * deviceCellWidth % UNDERLINE_CURLY_SEGMENT_SIZE; + const fullSegmentCount = (x * deviceCellWidth) / (UNDERLINE_CURLY_SEGMENT_SIZE * 2); + const forwardReverse: UnderlineCurlySegmentType = fullSegmentCount - Math.floor(fullSegmentCount) >= 0.5 ? 'down' : 'up'; + if (forwardReverse === 'up') { $variantOffset = offset; } else { - $variantOffset = offset + 3; + $variantOffset = offset + UNDERLINE_CURLY_SEGMENT_SIZE; } } diff --git a/src/browser/renderer/shared/Constants.ts b/src/browser/renderer/shared/Constants.ts index b5105ec787..18fcb61d5b 100644 --- a/src/browser/renderer/shared/Constants.ts +++ b/src/browser/renderer/shared/Constants.ts @@ -12,3 +12,5 @@ export const DIM_OPACITY = 0.5; // would result in truncated text (Issue 3353). Using 'bottom' for Chrome would result in slightly // unaligned Powerline fonts (PR 3356#issuecomment-850928179). export const TEXT_BASELINE: CanvasTextBaseline = isFirefox || isLegacyEdge ? 'bottom' : 'ideographic'; + +export const UNDERLINE_CURLY_SEGMENT_SIZE = 4; diff --git a/src/browser/renderer/shared/TextureAtlas.ts b/src/browser/renderer/shared/TextureAtlas.ts index ff1f3da007..949eb4930c 100644 --- a/src/browser/renderer/shared/TextureAtlas.ts +++ b/src/browser/renderer/shared/TextureAtlas.ts @@ -4,10 +4,10 @@ */ import { IColorContrastCache } from 'browser/Types'; -import { DIM_OPACITY, TEXT_BASELINE } from 'browser/renderer/shared/Constants'; +import { DIM_OPACITY, TEXT_BASELINE, UNDERLINE_CURLY_SEGMENT_SIZE } from 'browser/renderer/shared/Constants'; import { tryDrawCustomChar } from 'browser/renderer/shared/CustomGlyphs'; import { computeNextVariantOffset, excludeFromContrastRatioDemands, isPowerlineGlyph, isRestrictedPowerlineGlyph, throwIfFalsy } from 'browser/renderer/shared/RendererUtils'; -import { IBoundingBox, ICharAtlasConfig, IRasterizedGlyph, ITextureAtlas } from 'browser/renderer/shared/Types'; +import { IBoundingBox, ICharAtlasConfig, IRasterizedGlyph, ITextureAtlas, UnderlineCurlySegmentType } from 'browser/renderer/shared/Types'; import { NULL_COLOR, color, rgba } from 'common/Color'; import { EventEmitter } from 'common/EventEmitter'; import { FourKeyMap } from 'common/MultiKeyMap'; @@ -560,52 +560,59 @@ export class TextureAtlas implements ITextureAtlas { this._tmpCtx.lineTo(xChRight, yBot); break; case UnderlineStyle.CURLY: + // Draw Curly through segments. + // Segments are distinguished from up to down + // up: + // *** + // * + // down: + // * + // *** + // [TODO] Up or down offset, To be verified. const yMidOffset = Math.floor(yMid); - let upDown: 'Up' | 'Down' = 'Up'; + let segmentType: UnderlineCurlySegmentType = 'up'; let segmentOffset = 0; - if (nextOffset >= 0 && nextOffset <= 2) { - // Up - upDown = 'Up'; + if (nextOffset >= 0 && nextOffset <= UNDERLINE_CURLY_SEGMENT_SIZE - 1) { + segmentType = 'up'; segmentOffset = nextOffset; } else { - // Dowm - upDown = 'Down'; - segmentOffset = nextOffset - 3; + segmentType = 'down'; + segmentOffset = nextOffset - UNDERLINE_CURLY_SEGMENT_SIZE; } for (let index = 0; index < this._config.deviceCellWidth; index++) { - if (segmentOffset >= 3) { - upDown = upDown === 'Down' ? 'Up' : 'Down'; + if (segmentOffset >= UNDERLINE_CURLY_SEGMENT_SIZE) { + segmentType = segmentType === 'down' ? 'up' : 'down'; segmentOffset = 0; } - if (upDown === 'Up') { + if (segmentType === 'up') { if (segmentOffset === 0) { this._tmpCtx.fillRect(xChLeft + index, yMidOffset, 1, 1); - } else if (segmentOffset === 1 || segmentOffset === 2) { + } else { this._tmpCtx.fillRect(xChLeft + index, yMidOffset - 1, 1, 1); } - } else if (upDown === 'Down') { + } else if (segmentType === 'down') { if (segmentOffset === 0) { this._tmpCtx.fillRect(xChLeft + index, yMidOffset, 1, 1); - } else if (segmentOffset === 1 || segmentOffset === 2) { + } else { this._tmpCtx.fillRect(xChLeft + index, yMidOffset + 1, 1, 1); } } segmentOffset++; } // handle next - if (segmentOffset >= 3) { - const nextUpDown: 'Up' | 'Down' = upDown === 'Up' ? 'Down' : 'Up'; - if (nextUpDown === 'Up') { + if (segmentOffset >= UNDERLINE_CURLY_SEGMENT_SIZE) { + const nextUpDown: UnderlineCurlySegmentType = segmentType === 'up' ? 'down' : 'up'; + if (nextUpDown === 'up') { nextOffset = 0; } else { - nextOffset = 3; + nextOffset = UNDERLINE_CURLY_SEGMENT_SIZE; } } else { - if (upDown === 'Up') { + if (segmentType === 'up') { nextOffset = segmentOffset; } else { - nextOffset = segmentOffset + 3; + nextOffset = segmentOffset + UNDERLINE_CURLY_SEGMENT_SIZE; } } break; diff --git a/src/browser/renderer/shared/Types.d.ts b/src/browser/renderer/shared/Types.d.ts index c0d5e9d656..e97e8e6ce4 100644 --- a/src/browser/renderer/shared/Types.d.ts +++ b/src/browser/renderer/shared/Types.d.ts @@ -171,3 +171,5 @@ export interface ISelectionRenderModel { update(terminal: ITerminal, start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode?: boolean): void; isCellSelected(terminal: Terminal, x: number, y: number): boolean; } + +export type UnderlineCurlySegmentType = 'up' | 'down'; From 69c8a05e0bca0c9cb7073e8bc6f1e98b4cf3bc28 Mon Sep 17 00:00:00 2001 From: tisilent Date: Mon, 13 Nov 2023 17:55:25 +0800 Subject: [PATCH 04/12] Add draw plan --- .../renderer/shared/CellColorResolver.ts | 16 +-- .../renderer/shared/RendererUtils.test.ts | 11 ++- src/browser/renderer/shared/RendererUtils.ts | 98 ++++++++++++++++++- src/browser/renderer/shared/TextureAtlas.ts | 85 +++++++--------- 4 files changed, 146 insertions(+), 64 deletions(-) diff --git a/src/browser/renderer/shared/CellColorResolver.ts b/src/browser/renderer/shared/CellColorResolver.ts index b374711735..08d7181b0f 100644 --- a/src/browser/renderer/shared/CellColorResolver.ts +++ b/src/browser/renderer/shared/CellColorResolver.ts @@ -1,11 +1,11 @@ -import { ISelectionRenderModel, UnderlineCurlySegmentType } from 'browser/renderer/shared/Types'; +import { ISelectionRenderModel } from 'browser/renderer/shared/Types'; import { ICoreBrowserService, IThemeService } from 'browser/services/Services'; import { ReadonlyColorSet } from 'browser/Types'; import { Attributes, BgFlags, ExtFlags, FgFlags, NULL_CELL_CODE, UnderlineStyle } from 'common/buffer/Constants'; import { IDecorationService, IOptionsService } from 'common/services/Services'; import { ICellData } from 'common/Types'; import { Terminal } from '@xterm/xterm'; -import { UNDERLINE_CURLY_SEGMENT_SIZE } from 'browser/renderer/shared/Constants'; +import { getCurlyVariantOffset } from 'browser/renderer/shared/RendererUtils'; // Work variables to avoid garbage collection let $fg = 0; @@ -62,16 +62,8 @@ export class CellColorResolver { const lineWidth = Math.max(1, Math.floor(this._optionService.rawOptions.fontSize * this._coreBrowserService.dpr / 15)); $variantOffset = x * deviceCellWidth % (Math.round(lineWidth) * 2); } else if (code !== NULL_CELL_CODE && cell.extended.underlineStyle === UnderlineStyle.CURLY) { - // 4px per segment - // 0-3 forward (Up), 4-7 reverse (Down) - const offset = x * deviceCellWidth % UNDERLINE_CURLY_SEGMENT_SIZE; - const fullSegmentCount = (x * deviceCellWidth) / (UNDERLINE_CURLY_SEGMENT_SIZE * 2); - const forwardReverse: UnderlineCurlySegmentType = fullSegmentCount - Math.floor(fullSegmentCount) >= 0.5 ? 'down' : 'up'; - if (forwardReverse === 'up') { - $variantOffset = offset; - } else { - $variantOffset = offset + UNDERLINE_CURLY_SEGMENT_SIZE; - } + const lineWidth = Math.max(1, Math.floor(this._optionService.rawOptions.fontSize * this._coreBrowserService.dpr / 15)); + $variantOffset = getCurlyVariantOffset(x, deviceCellWidth, lineWidth); } // Apply decorations on the bottom layer diff --git a/src/browser/renderer/shared/RendererUtils.test.ts b/src/browser/renderer/shared/RendererUtils.test.ts index a050e8a94f..73bf9835bc 100644 --- a/src/browser/renderer/shared/RendererUtils.test.ts +++ b/src/browser/renderer/shared/RendererUtils.test.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { computeNextVariantOffset } from 'browser/renderer/shared/RendererUtils'; +import { computeNextVariantOffset, createDrawCurlyPlan } from 'browser/renderer/shared/RendererUtils'; import { assert } from 'chai'; describe('RendererUtils', () => { @@ -28,7 +28,7 @@ describe('RendererUtils', () => { line = 2; variantOffset = 0; cells = [cellWidth, cellWidth, doubleCellWidth, doubleCellWidth]; - result = [3, 2, 0 ,2]; + result = [3, 2, 0, 2]; for (let index = 0; index < cells.length; index++) { const cell = cells[index]; variantOffset = computeNextVariantOffset(cell, line, variantOffset); @@ -47,4 +47,11 @@ describe('RendererUtils', () => { assert.equal(variantOffset, result[index]); } }); + + // it('createDrawCurlyPlan', () => { + // const cellWidth = 11; + // const lineWidth = 2; + // const result = createDrawCurlyPlan(cellWidth,lineWidth); + // console.log(result); + // }); }); diff --git a/src/browser/renderer/shared/RendererUtils.ts b/src/browser/renderer/shared/RendererUtils.ts index 59b87b0e30..5d41b3b00f 100644 --- a/src/browser/renderer/shared/RendererUtils.ts +++ b/src/browser/renderer/shared/RendererUtils.ts @@ -3,7 +3,9 @@ * @license MIT */ -import { IDimensions, IRenderDimensions } from 'browser/renderer/shared/Types'; +import { UNDERLINE_CURLY_SEGMENT_SIZE } from 'browser/renderer/shared/Constants'; +import { IDimensions, IRenderDimensions, UnderlineCurlySegmentType } from 'browser/renderer/shared/Types'; +import { TwoKeyMap } from 'common/MultiKeyMap'; export function throwIfFalsy(value: T | undefined | null): T { if (!value) { @@ -60,3 +62,97 @@ function createDimension(): IDimensions { export function computeNextVariantOffset(cellWidth: number, lineWidth: number, currentOffset: number = 0): number { return (cellWidth - (Math.round(lineWidth) * 2 - currentOffset)) % (Math.round(lineWidth) * 2); } + +// TwoKeyMap +// eslint-disable-next-line @typescript-eslint/naming-convention +const _curlyVariantCache = new TwoKeyMap(); + +export function getCurlyVariant(cellWidth: number, lineWidth: number, offset: number): string { + if (_curlyVariantCache.get(cellWidth, lineWidth)) { + const curlyVariants = _curlyVariantCache.get(cellWidth, lineWidth) as any[]; + if (curlyVariants.length > 0) { + if (!curlyVariants[offset]) { + return curlyVariants[0]; + } + return curlyVariants[offset]; + } + } + return ''; +} + +export function getCurlyVariantOffset(x: number, cellWidth: number, lineWidth: number): number { + if (!_curlyVariantCache.get(cellWidth, lineWidth)) { + const curlyVariants = createDrawCurlyPlan(cellWidth, lineWidth); + _curlyVariantCache.set(cellWidth, lineWidth, curlyVariants); + return x % curlyVariants.length; + } + if (_curlyVariantCache.get(cellWidth, lineWidth)) { + const curlyVariants = _curlyVariantCache.get(cellWidth, lineWidth) as any[]; + return x % curlyVariants.length; + } + return 0; +} + +export function createDrawCurlyPlan(cellWidth: number, lineWidth: number): any[] { + const defaultFullSegmentWidth = UNDERLINE_CURLY_SEGMENT_SIZE * lineWidth * 2; + // 8 for variant size + if (defaultFullSegmentWidth <= 8) { + return decrement(cellWidth, 8, 1, 3, 8); + } + + if (cellWidth === 18 && lineWidth === 2) { + return decrement(cellWidth, 12, 2, 4, 2); + } + + if (cellWidth === 20 && lineWidth === 2) { + return decrement(cellWidth, 16, 2, 6, 4); + } + + return []; +} + +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type +function decrement(cellWidth: number, fullSegmentWidth: number, point: number, line: number, cellNum: number = 0) { + let countPx = cellWidth * cellNum ?? fullSegmentWidth; + const result: any[] = []; + let midOrLine: 0 | 1 = 0; + let waitHandleLinePx = 0; + let upOrDown: 0 | 1 = 0; + while (countPx > 0) { + const cellResult: any[] = []; + let cellCurrentWidth = cellWidth; + while (cellCurrentWidth > 0) { + if (midOrLine === 0) { + cellResult.push(`M${point}`); + cellCurrentWidth -= point; + midOrLine = 1; + } else if (midOrLine === 1) { + if (waitHandleLinePx > 0) { + const tag = upOrDown === 0 ? 'U' : 'D'; + cellResult.push(`${tag}${waitHandleLinePx}`); + cellCurrentWidth -= waitHandleLinePx; + waitHandleLinePx = 0; + midOrLine = 0; + upOrDown = upOrDown === 0 ? 1 : 0; + } else { + const usingWidth = line; + if (usingWidth > cellCurrentWidth) { + const tag = upOrDown === 0 ? 'U' : 'D'; + cellResult.push(`${tag}${cellCurrentWidth}`); + waitHandleLinePx = usingWidth - cellCurrentWidth; + cellCurrentWidth = 0; + } else { + const tag = upOrDown === 0 ? 'U' : 'D'; + cellResult.push(`${tag}${line}`); + cellCurrentWidth -= line; + midOrLine = 0; + upOrDown = upOrDown === 0 ? 1 : 0; + } + } + } + } + countPx -= cellWidth; + result.push(cellResult.join(' ')); + } + return result; +} diff --git a/src/browser/renderer/shared/TextureAtlas.ts b/src/browser/renderer/shared/TextureAtlas.ts index 949eb4930c..f1b5e45ac4 100644 --- a/src/browser/renderer/shared/TextureAtlas.ts +++ b/src/browser/renderer/shared/TextureAtlas.ts @@ -4,10 +4,10 @@ */ import { IColorContrastCache } from 'browser/Types'; -import { DIM_OPACITY, TEXT_BASELINE, UNDERLINE_CURLY_SEGMENT_SIZE } from 'browser/renderer/shared/Constants'; +import { DIM_OPACITY, TEXT_BASELINE } from 'browser/renderer/shared/Constants'; import { tryDrawCustomChar } from 'browser/renderer/shared/CustomGlyphs'; -import { computeNextVariantOffset, excludeFromContrastRatioDemands, isPowerlineGlyph, isRestrictedPowerlineGlyph, throwIfFalsy } from 'browser/renderer/shared/RendererUtils'; -import { IBoundingBox, ICharAtlasConfig, IRasterizedGlyph, ITextureAtlas, UnderlineCurlySegmentType } from 'browser/renderer/shared/Types'; +import { computeNextVariantOffset, excludeFromContrastRatioDemands, getCurlyVariant, isPowerlineGlyph, isRestrictedPowerlineGlyph, throwIfFalsy } from 'browser/renderer/shared/RendererUtils'; +import { IBoundingBox, ICharAtlasConfig, IRasterizedGlyph, ITextureAtlas } from 'browser/renderer/shared/Types'; import { NULL_COLOR, color, rgba } from 'common/Color'; import { EventEmitter } from 'common/EventEmitter'; import { FourKeyMap } from 'common/MultiKeyMap'; @@ -568,58 +568,45 @@ export class TextureAtlas implements ITextureAtlas { // down: // * // *** - // [TODO] Up or down offset, To be verified. - const yMidOffset = Math.floor(yMid); - let segmentType: UnderlineCurlySegmentType = 'up'; - let segmentOffset = 0; - if (nextOffset >= 0 && nextOffset <= UNDERLINE_CURLY_SEGMENT_SIZE - 1) { - segmentType = 'up'; - segmentOffset = nextOffset; - } else { - segmentType = 'down'; - segmentOffset = nextOffset - UNDERLINE_CURLY_SEGMENT_SIZE; - } - for (let index = 0; index < this._config.deviceCellWidth; index++) { - if (segmentOffset >= UNDERLINE_CURLY_SEGMENT_SIZE) { - segmentType = segmentType === 'down' ? 'up' : 'down'; - segmentOffset = 0; - } - if (segmentType === 'up') { - if (segmentOffset === 0) { - this._tmpCtx.fillRect(xChLeft + index, yMidOffset, 1, 1); - } else { - this._tmpCtx.fillRect(xChLeft + index, yMidOffset - 1, 1, 1); - } - } else if (segmentType === 'down') { - if (segmentOffset === 0) { - this._tmpCtx.fillRect(xChLeft + index, yMidOffset, 1, 1); - } else { - this._tmpCtx.fillRect(xChLeft + index, yMidOffset + 1, 1, 1); + const yMidRectOffset = Math.floor(yMid) - lineWidth + 1; + + const plan = getCurlyVariant(this._config.deviceCellWidth, lineWidth, nextOffset) as string; + const steps = plan.split(' '); + let xOffset = 0; + steps.forEach(step => { + const d = step.substring(0, 1); + const px = Number.parseInt(step.substring(1)); + if (d === 'M') { + if (lineWidth > 1) { + this._tmpCtx.fillRect(xChLeft + xOffset, yMidRectOffset - 1, px, lineWidth + 1); + xOffset += px; + return; } + this._tmpCtx.fillRect(xChLeft + xOffset, yMidRectOffset, px, 1); + xOffset += px; + return; } - segmentOffset++; - } - // handle next - if (segmentOffset >= UNDERLINE_CURLY_SEGMENT_SIZE) { - const nextUpDown: UnderlineCurlySegmentType = segmentType === 'up' ? 'down' : 'up'; - if (nextUpDown === 'up') { - nextOffset = 0; - } else { - nextOffset = UNDERLINE_CURLY_SEGMENT_SIZE; + + if (d === 'U') { + this._tmpCtx.fillRect(xChLeft + xOffset, yMidRectOffset - lineWidth, px, lineWidth); + xOffset += px; + return; } - } else { - if (segmentType === 'up') { - nextOffset = segmentOffset; - } else { - nextOffset = segmentOffset + UNDERLINE_CURLY_SEGMENT_SIZE; + + if (d === 'D') { + this._tmpCtx.fillRect(xChLeft + xOffset, yMidRectOffset + 1, px, lineWidth); + xOffset += px; + return; } - } + }); + // handle next + nextOffset++; break; case UnderlineStyle.DOTTED: const offsetWidth = nextOffset === 0 ? 0 : (nextOffset >= lineWidth ? lineWidth * 2 - nextOffset : lineWidth - nextOffset); - // a line and a gap. + // a line and a gap. const isLineStart = nextOffset >= lineWidth ? false : true; if (isLineStart === false || offsetWidth === 0) { this._tmpCtx.setLineDash([Math.round(lineWidth), Math.round(lineWidth)]); @@ -1089,13 +1076,13 @@ function clearColor(imageData: ImageData, bg: IColor, fg: IColor, enableThreshol 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 + 1] === g && + imageData.data[offset + 2] === b) { imageData.data[offset + 3] = 0; } else { // Check the threshold based difference if (enableThresholdCheck && - (Math.abs(imageData.data[offset] - r) + + (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; From 3bc81b4ec618d9bade5d9e5859a86a917bc57b5f Mon Sep 17 00:00:00 2001 From: tisilent Date: Mon, 13 Nov 2023 20:29:06 +0800 Subject: [PATCH 05/12] Formula description --- src/browser/renderer/shared/RendererUtils.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/browser/renderer/shared/RendererUtils.ts b/src/browser/renderer/shared/RendererUtils.ts index 5d41b3b00f..9a01c69617 100644 --- a/src/browser/renderer/shared/RendererUtils.ts +++ b/src/browser/renderer/shared/RendererUtils.ts @@ -97,22 +97,26 @@ export function createDrawCurlyPlan(cellWidth: number, lineWidth: number): any[] const defaultFullSegmentWidth = UNDERLINE_CURLY_SEGMENT_SIZE * lineWidth * 2; // 8 for variant size if (defaultFullSegmentWidth <= 8) { - return decrement(cellWidth, 8, 1, 3, 8); + return createVariantSequences(cellWidth, 8, 1, 3, 8); } + // x * 18 = y * 16 (full segment width) + // x <= 8 (variants) + // When the balance cannot be reached, decrease the full segment width + // As follows, use 12 if (cellWidth === 18 && lineWidth === 2) { - return decrement(cellWidth, 12, 2, 4, 2); + return createVariantSequences(cellWidth, 12, 2, 4, 2); } if (cellWidth === 20 && lineWidth === 2) { - return decrement(cellWidth, 16, 2, 6, 4); + return createVariantSequences(cellWidth, 16, 2, 6, 4); } return []; } // eslint-disable-next-line @typescript-eslint/explicit-function-return-type -function decrement(cellWidth: number, fullSegmentWidth: number, point: number, line: number, cellNum: number = 0) { +function createVariantSequences(cellWidth: number, fullSegmentWidth: number, point: number, line: number, cellNum: number = 0) { let countPx = cellWidth * cellNum ?? fullSegmentWidth; const result: any[] = []; let midOrLine: 0 | 1 = 0; From e883c13501d940aa084dcf84a70005e58baf7883 Mon Sep 17 00:00:00 2001 From: tisilent Date: Wed, 15 Nov 2023 00:33:41 +0800 Subject: [PATCH 06/12] vivid curly first edition done. --- addons/addon-canvas/src/BaseRenderLayer.ts | 4 +- addons/addon-webgl/src/GlyphRenderer.ts | 10 +- addons/addon-webgl/src/WebglRenderer.ts | 4 +- .../renderer/shared/CellColorResolver.ts | 10 +- src/browser/renderer/shared/RendererUtils.ts | 60 ++++++++---- src/browser/renderer/shared/TextureAtlas.ts | 92 +++++++++++++++---- src/browser/renderer/shared/Types.d.ts | 4 +- src/common/MultiKeyMap.ts | 19 ++++ 8 files changed, 150 insertions(+), 53 deletions(-) diff --git a/addons/addon-canvas/src/BaseRenderLayer.ts b/addons/addon-canvas/src/BaseRenderLayer.ts index e7e234003e..d3f0ae6b18 100644 --- a/addons/addon-canvas/src/BaseRenderLayer.ts +++ b/addons/addon-canvas/src/BaseRenderLayer.ts @@ -373,9 +373,9 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer let glyph: IRasterizedGlyph; if (chars && chars.length > 1) { - glyph = this._charAtlas.getRasterizedGlyphCombinedChar(chars, this._cellColorResolver.result.bg, this._cellColorResolver.result.fg, this._cellColorResolver.result.ext, true); + glyph = this._charAtlas.getRasterizedGlyphCombinedChar(chars, this._cellColorResolver.result.bg, this._cellColorResolver.result.fg, this._cellColorResolver.result.ext, this._cellColorResolver.result.underlineVariantOffset, true); } else { - glyph = this._charAtlas.getRasterizedGlyph(cell.getCode() || WHITESPACE_CELL_CODE, this._cellColorResolver.result.bg, this._cellColorResolver.result.fg, this._cellColorResolver.result.ext, true); + glyph = this._charAtlas.getRasterizedGlyph(cell.getCode() || WHITESPACE_CELL_CODE, this._cellColorResolver.result.bg, this._cellColorResolver.result.fg, this._cellColorResolver.result.ext, this._cellColorResolver.result.underlineVariantOffset, true); } if (!glyph.size.x || !glyph.size.y) { return; diff --git a/addons/addon-webgl/src/GlyphRenderer.ts b/addons/addon-webgl/src/GlyphRenderer.ts index 1fb0e18c54..563272feba 100644 --- a/addons/addon-webgl/src/GlyphRenderer.ts +++ b/addons/addon-webgl/src/GlyphRenderer.ts @@ -212,15 +212,15 @@ export class GlyphRenderer extends Disposable { return this._atlas ? this._atlas.beginFrame() : true; } - public updateCell(x: number, y: number, code: number, bg: number, fg: number, ext: number, chars: string, lastBg: number): void { + public updateCell(x: number, y: number, code: number, bg: number, fg: number, ext: number, chars: string, lastBg: number, underlineVariantOffset: number): void { // Since this function is called for every cell (`rows*cols`), it must be very optimized. It // should not instantiate any variables unless a new glyph is drawn to the cache where the // slight slowdown is acceptable for the developer ergonomics provided as it's a once of for // each glyph. - this._updateCell(this._vertices.attributes, x, y, code, bg, fg, ext, chars, lastBg); + this._updateCell(this._vertices.attributes, x, y, code, bg, fg, ext, chars, lastBg, underlineVariantOffset); } - private _updateCell(array: Float32Array, x: number, y: number, code: number | undefined, bg: number, fg: number, ext: number, chars: string, lastBg: number): void { + private _updateCell(array: Float32Array, x: number, y: number, code: number | undefined, bg: number, fg: number, ext: number, chars: string, lastBg: number, underlineVariantOffset: number): void { $i = (y * this._terminal.cols + x) * INDICES_PER_CELL; // Exit early if this is a null character, allow space character to continue as it may have @@ -236,9 +236,9 @@ export class GlyphRenderer extends Disposable { // Get the glyph if (chars && chars.length > 1) { - $glyph = this._atlas.getRasterizedGlyphCombinedChar(chars, bg, fg, ext, false); + $glyph = this._atlas.getRasterizedGlyphCombinedChar(chars, bg, fg, ext, underlineVariantOffset, false); } else { - $glyph = this._atlas.getRasterizedGlyph(code, bg, fg, ext, false); + $glyph = this._atlas.getRasterizedGlyph(code, bg, fg, ext, underlineVariantOffset, false); } $leftCellPadding = Math.floor((this._dimensions.device.cell.width - this._dimensions.device.char.width) / 2); diff --git a/addons/addon-webgl/src/WebglRenderer.ts b/addons/addon-webgl/src/WebglRenderer.ts index 2bccc9b7c9..33fa30c861 100644 --- a/addons/addon-webgl/src/WebglRenderer.ts +++ b/addons/addon-webgl/src/WebglRenderer.ts @@ -496,7 +496,7 @@ export class WebglRenderer extends Disposable implements IRenderer { this._model.cells[i + RENDER_MODEL_FG_OFFSET] = this._cellColorResolver.result.fg; this._model.cells[i + RENDER_MODEL_EXT_OFFSET] = this._cellColorResolver.result.ext; - this._glyphRenderer.value!.updateCell(x, y, code, this._cellColorResolver.result.bg, this._cellColorResolver.result.fg, this._cellColorResolver.result.ext, chars, lastBg); + this._glyphRenderer.value!.updateCell(x, y, code, this._cellColorResolver.result.bg, this._cellColorResolver.result.fg, this._cellColorResolver.result.ext, chars, lastBg, this._cellColorResolver.result.underlineVariantOffset); if (isJoined) { // Restore work cell @@ -505,7 +505,7 @@ export class WebglRenderer extends Disposable implements IRenderer { // Null out non-first cells for (x++; x < lastCharX; x++) { j = ((y * terminal.cols) + x) * RENDER_MODEL_INDICIES_PER_CELL; - this._glyphRenderer.value!.updateCell(x, y, NULL_CELL_CODE, 0, 0, 0, NULL_CELL_CHAR, 0); + this._glyphRenderer.value!.updateCell(x, y, NULL_CELL_CODE, 0, 0, 0, NULL_CELL_CHAR, 0, 0); this._model.cells[j] = NULL_CELL_CODE; this._model.cells[j + RENDER_MODEL_BG_OFFSET] = this._cellColorResolver.result.bg; this._model.cells[j + RENDER_MODEL_FG_OFFSET] = this._cellColorResolver.result.fg; diff --git a/src/browser/renderer/shared/CellColorResolver.ts b/src/browser/renderer/shared/CellColorResolver.ts index 08d7181b0f..8fec748846 100644 --- a/src/browser/renderer/shared/CellColorResolver.ts +++ b/src/browser/renderer/shared/CellColorResolver.ts @@ -15,16 +15,18 @@ let $hasBg = false; let $isSelected = false; let $colors: ReadonlyColorSet | undefined; let $variantOffset = 0; +let $underlineVariantOffset = 0; export class CellColorResolver { /** * The shared result of the {@link resolve} call. This is only safe to use immediately after as * any other calls will share object. */ - public readonly result: { fg: number, bg: number, ext: number } = { + public readonly result: { fg: number, bg: number, ext: number, underlineVariantOffset: number } = { fg: 0, bg: 0, - ext: 0 + ext: 0, + underlineVariantOffset: 0 }; constructor( @@ -56,6 +58,7 @@ export class CellColorResolver { $isSelected = false; $colors = this._themeService.colors; $variantOffset = 0; + $underlineVariantOffset = 0; const code = cell.getCode(); if (code !== NULL_CELL_CODE && cell.extended.underlineStyle === UnderlineStyle.DOTTED) { @@ -63,7 +66,7 @@ export class CellColorResolver { $variantOffset = x * deviceCellWidth % (Math.round(lineWidth) * 2); } else if (code !== NULL_CELL_CODE && cell.extended.underlineStyle === UnderlineStyle.CURLY) { const lineWidth = Math.max(1, Math.floor(this._optionService.rawOptions.fontSize * this._coreBrowserService.dpr / 15)); - $variantOffset = getCurlyVariantOffset(x, deviceCellWidth, lineWidth); + $underlineVariantOffset = getCurlyVariantOffset(x, deviceCellWidth, lineWidth); } // Apply decorations on the bottom layer @@ -150,5 +153,6 @@ export class CellColorResolver { // Reset overrides variantOffset this.result.ext &= ~ExtFlags.VARIANT_OFFSET; this.result.ext |= ($variantOffset << 29) & ExtFlags.VARIANT_OFFSET; + this.result.underlineVariantOffset = $underlineVariantOffset; } } diff --git a/src/browser/renderer/shared/RendererUtils.ts b/src/browser/renderer/shared/RendererUtils.ts index 9a01c69617..67bdc31f6e 100644 --- a/src/browser/renderer/shared/RendererUtils.ts +++ b/src/browser/renderer/shared/RendererUtils.ts @@ -4,7 +4,7 @@ */ import { UNDERLINE_CURLY_SEGMENT_SIZE } from 'browser/renderer/shared/Constants'; -import { IDimensions, IRenderDimensions, UnderlineCurlySegmentType } from 'browser/renderer/shared/Types'; +import { IDimensions, IRenderDimensions } from 'browser/renderer/shared/Types'; import { TwoKeyMap } from 'common/MultiKeyMap'; export function throwIfFalsy(value: T | undefined | null): T { @@ -95,41 +95,59 @@ export function getCurlyVariantOffset(x: number, cellWidth: number, lineWidth: n export function createDrawCurlyPlan(cellWidth: number, lineWidth: number): any[] { const defaultFullSegmentWidth = UNDERLINE_CURLY_SEGMENT_SIZE * lineWidth * 2; - // 8 for variant size - if (defaultFullSegmentWidth <= 8) { - return createVariantSequences(cellWidth, 8, 1, 3, 8); - } - // x * 18 = y * 16 (full segment width) - // x <= 8 (variants) - // When the balance cannot be reached, decrease the full segment width - // As follows, use 12 - if (cellWidth === 18 && lineWidth === 2) { - return createVariantSequences(cellWidth, 12, 2, 4, 2); + if (lineWidth === 2) { + // 12 look better + return createVariantSequences(cellWidth, 12, lineWidth, 4); } - if (cellWidth === 20 && lineWidth === 2) { - return createVariantSequences(cellWidth, 16, 2, 6, 4); + if (lineWidth === 3) { + return createVariantSequences(cellWidth, 16, lineWidth, 5); } - return []; + return createVariantSequences(cellWidth, defaultFullSegmentWidth , lineWidth, 3 * lineWidth); } -// eslint-disable-next-line @typescript-eslint/explicit-function-return-type -function createVariantSequences(cellWidth: number, fullSegmentWidth: number, point: number, line: number, cellNum: number = 0) { - let countPx = cellWidth * cellNum ?? fullSegmentWidth; +function createVariantSequences(cellWidth: number, fullSegmentWidth: number, point: number, line: number, cellNum: number = 0, vague: boolean = false): string[] { + let countPx = cellWidth * ((cellNum !== 0 ? cellNum : fullSegmentWidth)); const result: any[] = []; let midOrLine: 0 | 1 = 0; let waitHandleLinePx = 0; let upOrDown: 0 | 1 = 0; + let lastUpOrDown: 0 | 1 = 0; while (countPx > 0) { const cellResult: any[] = []; let cellCurrentWidth = cellWidth; while (cellCurrentWidth > 0) { if (midOrLine === 0) { - cellResult.push(`M${point}`); - cellCurrentWidth -= point; - midOrLine = 1; + let tag = vague ? 'M' : upOrDown === 0 ? 'A' : 'B'; + if (waitHandleLinePx > 0) { + // right + tag = lastUpOrDown === 0 ? 'M' : 'P'; + cellResult.push(`${tag}${waitHandleLinePx}`); + cellCurrentWidth -= waitHandleLinePx; + waitHandleLinePx = 0; + midOrLine = 1; + } else { + // left + tag = lastUpOrDown === 0 ? 'Z' : 'Q'; + const usingWidth = point; + if (usingWidth > cellCurrentWidth) { + cellResult.push(`${tag}${cellCurrentWidth}`); + waitHandleLinePx = usingWidth - cellCurrentWidth; + cellCurrentWidth = 0; + } else { + if (upOrDown === 0) { + cellResult.push(`Y${point}`); + cellCurrentWidth -= point; + midOrLine = 1; + } else if (upOrDown === 1) { + cellResult.push(`B${point}`); + cellCurrentWidth -= point; + midOrLine = 1; + } + } + } } else if (midOrLine === 1) { if (waitHandleLinePx > 0) { const tag = upOrDown === 0 ? 'U' : 'D'; @@ -137,6 +155,7 @@ function createVariantSequences(cellWidth: number, fullSegmentWidth: number, poi cellCurrentWidth -= waitHandleLinePx; waitHandleLinePx = 0; midOrLine = 0; + lastUpOrDown = upOrDown; upOrDown = upOrDown === 0 ? 1 : 0; } else { const usingWidth = line; @@ -150,6 +169,7 @@ function createVariantSequences(cellWidth: number, fullSegmentWidth: number, poi cellResult.push(`${tag}${line}`); cellCurrentWidth -= line; midOrLine = 0; + lastUpOrDown = upOrDown; upOrDown = upOrDown === 0 ? 1 : 0; } } diff --git a/src/browser/renderer/shared/TextureAtlas.ts b/src/browser/renderer/shared/TextureAtlas.ts index f1b5e45ac4..4a9b825446 100644 --- a/src/browser/renderer/shared/TextureAtlas.ts +++ b/src/browser/renderer/shared/TextureAtlas.ts @@ -10,7 +10,7 @@ import { computeNextVariantOffset, excludeFromContrastRatioDemands, getCurlyVari import { IBoundingBox, ICharAtlasConfig, IRasterizedGlyph, ITextureAtlas } from 'browser/renderer/shared/Types'; import { NULL_COLOR, color, rgba } from 'common/Color'; import { EventEmitter } from 'common/EventEmitter'; -import { FourKeyMap } from 'common/MultiKeyMap'; +import { FiveKeyMap } from 'common/MultiKeyMap'; import { IdleTaskQueue } from 'common/TaskQueue'; import { IColor } from 'common/Types'; import { AttributeData } from 'common/buffer/AttributeData'; @@ -58,8 +58,8 @@ let $glyph = undefined; export class TextureAtlas implements ITextureAtlas { private _didWarmUp: boolean = false; - private _cacheMap: FourKeyMap = new FourKeyMap(); - private _cacheMapCombined: FourKeyMap = new FourKeyMap(); + private _cacheMap: FiveKeyMap = new FiveKeyMap(); + private _cacheMapCombined: FiveKeyMap = new FiveKeyMap(); // The texture that the atlas is drawn to private _pages: AtlasPage[] = []; @@ -121,9 +121,9 @@ export class TextureAtlas implements ITextureAtlas { const queue = new IdleTaskQueue(); for (let i = 33; i < 126; i++) { queue.enqueue(() => { - if (!this._cacheMap.get(i, DEFAULT_COLOR, DEFAULT_COLOR, DEFAULT_EXT)) { + if (!this._cacheMap.get(i, DEFAULT_COLOR, DEFAULT_COLOR, DEFAULT_EXT, 0)) { const rasterizedGlyph = this._drawToCache(i, DEFAULT_COLOR, DEFAULT_COLOR, DEFAULT_EXT); - this._cacheMap.set(i, DEFAULT_COLOR, DEFAULT_COLOR, DEFAULT_EXT, rasterizedGlyph); + this._cacheMap.set(i, DEFAULT_COLOR, DEFAULT_COLOR, DEFAULT_EXT, 0, rasterizedGlyph); } }); } @@ -242,29 +242,30 @@ export class TextureAtlas implements ITextureAtlas { } } - public getRasterizedGlyphCombinedChar(chars: string, bg: number, fg: number, ext: number, restrictToCellHeight: boolean): IRasterizedGlyph { - return this._getFromCacheMap(this._cacheMapCombined, chars, bg, fg, ext, restrictToCellHeight); + public getRasterizedGlyphCombinedChar(chars: string, bg: number, fg: number, ext: number, underlineVariantOffset: number, restrictToCellHeight: boolean): IRasterizedGlyph { + return this._getFromCacheMap(this._cacheMapCombined, chars, bg, fg, ext, underlineVariantOffset, restrictToCellHeight); } - public getRasterizedGlyph(code: number, bg: number, fg: number, ext: number, restrictToCellHeight: boolean): IRasterizedGlyph { - return this._getFromCacheMap(this._cacheMap, code, bg, fg, ext, restrictToCellHeight); + public getRasterizedGlyph(code: number, bg: number, fg: number, ext: number, underlineVariantOffset: number, restrictToCellHeight: boolean): IRasterizedGlyph { + return this._getFromCacheMap(this._cacheMap, code, bg, fg, ext, underlineVariantOffset, restrictToCellHeight); } /** * Gets the glyphs texture coords, drawing the texture if it's not already */ private _getFromCacheMap( - cacheMap: FourKeyMap, + cacheMap: FiveKeyMap, key: string | number, bg: number, fg: number, ext: number, + underlineVariantOffset: number, restrictToCellHeight: boolean = false ): IRasterizedGlyph { - $glyph = cacheMap.get(key, bg, fg, ext); + $glyph = cacheMap.get(key, bg, fg, ext, underlineVariantOffset); if (!$glyph) { - $glyph = this._drawToCache(key, bg, fg, ext, restrictToCellHeight); - cacheMap.set(key, bg, fg, ext, $glyph); + $glyph = this._drawToCache(key, bg, fg, ext, underlineVariantOffset, restrictToCellHeight); + cacheMap.set(key, bg, fg, ext, underlineVariantOffset, $glyph); } return $glyph; } @@ -425,7 +426,7 @@ export class TextureAtlas implements ITextureAtlas { } @traceCall - private _drawToCache(codeOrChars: number | string, bg: number, fg: number, ext: number, restrictToCellHeight: boolean = false): IRasterizedGlyph { + private _drawToCache(codeOrChars: number | string, bg: number, fg: number, ext: number, underlineVariantoffset: number = 0, restrictToCellHeight: boolean = false): IRasterizedGlyph { const chars = typeof codeOrChars === 'number' ? String.fromCharCode(codeOrChars) : codeOrChars; // Uncomment for debugging @@ -545,7 +546,7 @@ export class TextureAtlas implements ITextureAtlas { const yTop = Math.ceil(padding + this._config.deviceCharHeight) - yOffset - (restrictToCellHeight ? lineWidth * 2 : 0); const yMid = yTop + lineWidth; const yBot = yTop + lineWidth * 2; - let nextOffset = this._workAttributeData.getUnderlineVariantOffset(); + let nextOffset = underlineVariantoffset !== 0 ? underlineVariantoffset : this._workAttributeData.getUnderlineVariantOffset(); for (let i = 0; i < chWidth; i++) { this._tmpCtx.save(); @@ -574,14 +575,61 @@ export class TextureAtlas implements ITextureAtlas { const plan = getCurlyVariant(this._config.deviceCellWidth, lineWidth, nextOffset) as string; const steps = plan.split(' '); let xOffset = 0; + // const midHeight = yBot - yTop - 2; steps.forEach(step => { const d = step.substring(0, 1); const px = Number.parseInt(step.substring(1)); - if (d === 'M') { + if (d === 'A' || d === 'Y' || d === 'B' || d === 'M' || d === 'Q' || d === 'P' || d === 'Z') { if (lineWidth > 1) { - this._tmpCtx.fillRect(xChLeft + xOffset, yMidRectOffset - 1, px, lineWidth + 1); - xOffset += px; - return; + if (px < lineWidth) { + if (d === 'Q') { + for (let index = 0; index < px; index++) { + this._tmpCtx.fillRect(xChLeft + xOffset + index, yMidRectOffset - index, 1, lineWidth); + } + xOffset += px; + return; + } + + if (d === 'P') { + for (let index = 0; index < px; index++) { + this._tmpCtx.fillRect(xChLeft + xOffset + index, yMidRectOffset - (lineWidth - px) - index, 1, lineWidth); + } + xOffset += px; + return; + } + + if (d === 'Z') { + for (let index = 0; index < px; index++) { + this._tmpCtx.fillRect(xChLeft + xOffset + index, yMidRectOffset - (lineWidth - 1) + index, 1, lineWidth); + } + xOffset += px; + return; + } + + if (d === 'M') { + for (let index = 0; index < px; index++) { + this._tmpCtx.fillRect(xChLeft + xOffset + index, yMidRectOffset - (lineWidth - 1) + (lineWidth - px) - index, 1, lineWidth); + } + xOffset += px; + return; + } + } else { + if (d === 'Y') { + for (let index = 0; index < px; index++) { + this._tmpCtx.fillRect(xChLeft + xOffset + index, yMidRectOffset - index, 1, lineWidth); + } + xOffset += px; + return; + } + + if (d === 'B') { + for (let index = 0; index < px; index++) { + this._tmpCtx.fillRect(xChLeft + xOffset + index, yMidRectOffset - (lineWidth - 1) + index, 1, lineWidth); + } + xOffset += px; + return; + } + } } this._tmpCtx.fillRect(xChLeft + xOffset, yMidRectOffset, px, 1); xOffset += px; @@ -589,8 +637,14 @@ export class TextureAtlas implements ITextureAtlas { } if (d === 'U') { + // if (lineWidth > 1) { + // this._tmpCtx.fillRect(xChLeft + xOffset, yMidRectOffset - lineWidth, px, 1); + // this._tmpCtx.fillRect(xChLeft + xOffset + 1, yMidRectOffset - lineWidth + 1, px - 2, 1); + // xOffset += px; + // } else { this._tmpCtx.fillRect(xChLeft + xOffset, yMidRectOffset - lineWidth, px, lineWidth); xOffset += px; + // } return; } diff --git a/src/browser/renderer/shared/Types.d.ts b/src/browser/renderer/shared/Types.d.ts index e97e8e6ce4..00a5a52819 100644 --- a/src/browser/renderer/shared/Types.d.ts +++ b/src/browser/renderer/shared/Types.d.ts @@ -107,8 +107,8 @@ export interface ITextureAtlas extends IDisposable { * Clear all glyphs from the texture atlas. */ clearTexture(): void; - getRasterizedGlyph(code: number, bg: number, fg: number, ext: number, restrictToCellHeight: boolean): IRasterizedGlyph; - getRasterizedGlyphCombinedChar(chars: string, bg: number, fg: number, ext: number, restrictToCellHeight: boolean): IRasterizedGlyph; + getRasterizedGlyph(code: number, bg: number, fg: number, ext: number, underlineVariantOffset: number, restrictToCellHeight: boolean): IRasterizedGlyph; + getRasterizedGlyphCombinedChar(chars: string, bg: number, fg: number, ext: number, underlineVariantOffset: number, restrictToCellHeight: boolean): IRasterizedGlyph; } /** diff --git a/src/common/MultiKeyMap.ts b/src/common/MultiKeyMap.ts index 6287a8f243..edd5abb5f9 100644 --- a/src/common/MultiKeyMap.ts +++ b/src/common/MultiKeyMap.ts @@ -40,3 +40,22 @@ export class FourKeyMap { + private _data: { [bg: string | number]: FourKeyMap | undefined } = {}; + + public set(first: TFirst, second: TSecond, third: TThird, fourth: TFourth, five: TFive, value: TValue): void { + if (!this._data[first]) { + this._data[first] = new FourKeyMap(); + } + this._data[first as string | number]!.set(second, third, fourth, five, value); + } + + public get(first: TFirst, second: TSecond, third: TThird, fourth: TFourth, five: TFive): TValue | undefined { + return this._data[first as string | number] ? this._data[first as string | number]?.get(second, third, fourth, five) : undefined; + } + + public clear(): void { + this._data = {}; + } +} From d043a1a55075d441743dc240bba3257e8f52cd6e Mon Sep 17 00:00:00 2001 From: tisilent Date: Sat, 18 Nov 2023 18:08:36 +0800 Subject: [PATCH 07/12] Add types --- .../renderer/shared/CellColorResolver.ts | 13 +- .../renderer/shared/RendererUtils.test.ts | 7 -- src/browser/renderer/shared/RendererUtils.ts | 95 ++++++++------- src/browser/renderer/shared/TextureAtlas.ts | 111 +++++++++--------- src/browser/renderer/shared/Types.d.ts | 15 ++- 5 files changed, 121 insertions(+), 120 deletions(-) diff --git a/src/browser/renderer/shared/CellColorResolver.ts b/src/browser/renderer/shared/CellColorResolver.ts index 8fec748846..4e58c5baf4 100644 --- a/src/browser/renderer/shared/CellColorResolver.ts +++ b/src/browser/renderer/shared/CellColorResolver.ts @@ -61,12 +61,15 @@ export class CellColorResolver { $underlineVariantOffset = 0; const code = cell.getCode(); - if (code !== NULL_CELL_CODE && cell.extended.underlineStyle === UnderlineStyle.DOTTED) { - const lineWidth = Math.max(1, Math.floor(this._optionService.rawOptions.fontSize * this._coreBrowserService.dpr / 15)); - $variantOffset = x * deviceCellWidth % (Math.round(lineWidth) * 2); - } else if (code !== NULL_CELL_CODE && cell.extended.underlineStyle === UnderlineStyle.CURLY) { + + // Underline handle + if (code !== NULL_CELL_CODE && cell.extended.underlineStyle !== UnderlineStyle.NONE) { const lineWidth = Math.max(1, Math.floor(this._optionService.rawOptions.fontSize * this._coreBrowserService.dpr / 15)); - $underlineVariantOffset = getCurlyVariantOffset(x, deviceCellWidth, lineWidth); + if (cell.extended.underlineStyle === UnderlineStyle.DOTTED) { + $variantOffset = x * deviceCellWidth % (Math.round(lineWidth) * 2); + } else if (cell.extended.underlineStyle === UnderlineStyle.CURLY) { + $underlineVariantOffset = getCurlyVariantOffset(x, deviceCellWidth, lineWidth); + } } // Apply decorations on the bottom layer diff --git a/src/browser/renderer/shared/RendererUtils.test.ts b/src/browser/renderer/shared/RendererUtils.test.ts index 73bf9835bc..41e5846ba8 100644 --- a/src/browser/renderer/shared/RendererUtils.test.ts +++ b/src/browser/renderer/shared/RendererUtils.test.ts @@ -47,11 +47,4 @@ describe('RendererUtils', () => { assert.equal(variantOffset, result[index]); } }); - - // it('createDrawCurlyPlan', () => { - // const cellWidth = 11; - // const lineWidth = 2; - // const result = createDrawCurlyPlan(cellWidth,lineWidth); - // console.log(result); - // }); }); diff --git a/src/browser/renderer/shared/RendererUtils.ts b/src/browser/renderer/shared/RendererUtils.ts index 67bdc31f6e..91572ccc9a 100644 --- a/src/browser/renderer/shared/RendererUtils.ts +++ b/src/browser/renderer/shared/RendererUtils.ts @@ -4,7 +4,7 @@ */ import { UNDERLINE_CURLY_SEGMENT_SIZE } from 'browser/renderer/shared/Constants'; -import { IDimensions, IRenderDimensions } from 'browser/renderer/shared/Types'; +import { IDimensions, IRenderDimensions, UnderlineCurlyJoinOrLine, UnderlineCurlyLineType, UnderlineDrawCurlyOp } from 'browser/renderer/shared/Types'; import { TwoKeyMap } from 'common/MultiKeyMap'; export function throwIfFalsy(value: T | undefined | null): T { @@ -81,15 +81,15 @@ export function getCurlyVariant(cellWidth: number, lineWidth: number, offset: nu } export function getCurlyVariantOffset(x: number, cellWidth: number, lineWidth: number): number { + if (_curlyVariantCache.get(cellWidth, lineWidth)) { + const curlyVariants = _curlyVariantCache.get(cellWidth, lineWidth) as any[]; + return x % curlyVariants.length; + } if (!_curlyVariantCache.get(cellWidth, lineWidth)) { const curlyVariants = createDrawCurlyPlan(cellWidth, lineWidth); _curlyVariantCache.set(cellWidth, lineWidth, curlyVariants); return x % curlyVariants.length; } - if (_curlyVariantCache.get(cellWidth, lineWidth)) { - const curlyVariants = _curlyVariantCache.get(cellWidth, lineWidth) as any[]; - return x % curlyVariants.length; - } return 0; } @@ -105,77 +105,74 @@ export function createDrawCurlyPlan(cellWidth: number, lineWidth: number): any[] return createVariantSequences(cellWidth, 16, lineWidth, 5); } + // if (lineWidth === 4) { + // return createVariantSequences(cellWidth, 20 , lineWidth, 6); + // } + return createVariantSequences(cellWidth, defaultFullSegmentWidth , lineWidth, 3 * lineWidth); } -function createVariantSequences(cellWidth: number, fullSegmentWidth: number, point: number, line: number, cellNum: number = 0, vague: boolean = false): string[] { - let countPx = cellWidth * ((cellNum !== 0 ? cellNum : fullSegmentWidth)); - const result: any[] = []; - let midOrLine: 0 | 1 = 0; - let waitHandleLinePx = 0; - let upOrDown: 0 | 1 = 0; - let lastUpOrDown: 0 | 1 = 0; - while (countPx > 0) { +function createVariantSequences(cellWidth: number, fullSegmentWidth: number, point: number, line: number): string[] { + const result: string[] = []; + let totalPixels = cellWidth * fullSegmentWidth; + let joinOrLine: UnderlineCurlyJoinOrLine = 'join'; + let upOrDown: UnderlineCurlyLineType = 'up'; + let lastUpOrDown: UnderlineCurlyLineType = 'up'; + // Split between cells to be processed + let waitHandleLinePixels = 0; + while (totalPixels > 0) { const cellResult: any[] = []; let cellCurrentWidth = cellWidth; while (cellCurrentWidth > 0) { - if (midOrLine === 0) { - let tag = vague ? 'M' : upOrDown === 0 ? 'A' : 'B'; - if (waitHandleLinePx > 0) { + if (joinOrLine === 'join') { + let token: UnderlineDrawCurlyOp = upOrDown === 'up' ? 'Y' : 'B'; + if (waitHandleLinePixels > 0) { // right - tag = lastUpOrDown === 0 ? 'M' : 'P'; - cellResult.push(`${tag}${waitHandleLinePx}`); - cellCurrentWidth -= waitHandleLinePx; - waitHandleLinePx = 0; - midOrLine = 1; + token = lastUpOrDown === 'up' ? 'M' : 'P'; + cellResult.push(`${token}${waitHandleLinePixels}`); + cellCurrentWidth -= waitHandleLinePixels; + waitHandleLinePixels = 0; + joinOrLine = 'line'; } else { // left - tag = lastUpOrDown === 0 ? 'Z' : 'Q'; const usingWidth = point; if (usingWidth > cellCurrentWidth) { - cellResult.push(`${tag}${cellCurrentWidth}`); - waitHandleLinePx = usingWidth - cellCurrentWidth; + token = lastUpOrDown === 'up' ? 'Z' : 'Q'; + cellResult.push(`${token}${cellCurrentWidth}`); + waitHandleLinePixels = usingWidth - cellCurrentWidth; cellCurrentWidth = 0; } else { - if (upOrDown === 0) { - cellResult.push(`Y${point}`); - cellCurrentWidth -= point; - midOrLine = 1; - } else if (upOrDown === 1) { - cellResult.push(`B${point}`); - cellCurrentWidth -= point; - midOrLine = 1; - } + cellResult.push(`${token}${point}`); + cellCurrentWidth -= point; + joinOrLine = 'line'; } } - } else if (midOrLine === 1) { - if (waitHandleLinePx > 0) { - const tag = upOrDown === 0 ? 'U' : 'D'; - cellResult.push(`${tag}${waitHandleLinePx}`); - cellCurrentWidth -= waitHandleLinePx; - waitHandleLinePx = 0; - midOrLine = 0; + } else if (joinOrLine === 'line') { + const token: UnderlineDrawCurlyOp = upOrDown === 'up' ? 'U' : 'D'; + if (waitHandleLinePixels > 0) { + cellResult.push(`${token}${waitHandleLinePixels}`); + cellCurrentWidth -= waitHandleLinePixels; + waitHandleLinePixels = 0; + joinOrLine = 'join'; lastUpOrDown = upOrDown; - upOrDown = upOrDown === 0 ? 1 : 0; + upOrDown = upOrDown === 'up' ? 'down' : 'up'; } else { const usingWidth = line; if (usingWidth > cellCurrentWidth) { - const tag = upOrDown === 0 ? 'U' : 'D'; - cellResult.push(`${tag}${cellCurrentWidth}`); - waitHandleLinePx = usingWidth - cellCurrentWidth; + cellResult.push(`${token}${cellCurrentWidth}`); + waitHandleLinePixels = usingWidth - cellCurrentWidth; cellCurrentWidth = 0; } else { - const tag = upOrDown === 0 ? 'U' : 'D'; - cellResult.push(`${tag}${line}`); + cellResult.push(`${token}${line}`); cellCurrentWidth -= line; - midOrLine = 0; + joinOrLine = 'join'; lastUpOrDown = upOrDown; - upOrDown = upOrDown === 0 ? 1 : 0; + upOrDown = upOrDown === 'up' ? 'down' : 'up'; } } } } - countPx -= cellWidth; + totalPixels -= cellWidth; result.push(cellResult.join(' ')); } return result; diff --git a/src/browser/renderer/shared/TextureAtlas.ts b/src/browser/renderer/shared/TextureAtlas.ts index 4a9b825446..61abe19e25 100644 --- a/src/browser/renderer/shared/TextureAtlas.ts +++ b/src/browser/renderer/shared/TextureAtlas.ts @@ -569,91 +569,86 @@ export class TextureAtlas implements ITextureAtlas { // down: // * // *** + // [TODO] Up or down offset, To be verified. - const yMidRectOffset = Math.floor(yMid) - lineWidth + 1; + const yMidRectOffset = Math.floor(yMid) - lineWidth; const plan = getCurlyVariant(this._config.deviceCellWidth, lineWidth, nextOffset) as string; const steps = plan.split(' '); let xOffset = 0; - // const midHeight = yBot - yTop - 2; + steps.forEach(step => { - const d = step.substring(0, 1); - const px = Number.parseInt(step.substring(1)); - if (d === 'A' || d === 'Y' || d === 'B' || d === 'M' || d === 'Q' || d === 'P' || d === 'Z') { - if (lineWidth > 1) { - if (px < lineWidth) { - if (d === 'Q') { - for (let index = 0; index < px; index++) { - this._tmpCtx.fillRect(xChLeft + xOffset + index, yMidRectOffset - index, 1, lineWidth); - } - xOffset += px; - return; + const token = step.substring(0, 1); + const pixels = Number.parseInt(step.substring(1)); + + // draw join + if (token === 'Y' || token === 'B' || token === 'M' || token === 'Q' || token === 'P' || token === 'Z') { + if (pixels < lineWidth) { + if (token === 'Q') { + for (let index = 0; index < pixels; index++) { + this._tmpCtx.fillRect(xChLeft + xOffset + index, yMidRectOffset - index, 1, lineWidth); } + xOffset += pixels; + return; + } - if (d === 'P') { - for (let index = 0; index < px; index++) { - this._tmpCtx.fillRect(xChLeft + xOffset + index, yMidRectOffset - (lineWidth - px) - index, 1, lineWidth); - } - xOffset += px; - return; + if (token === 'P') { + for (let index = 0; index < pixels; index++) { + this._tmpCtx.fillRect(xChLeft + xOffset + index, yMidRectOffset - (lineWidth - pixels) - index, 1, lineWidth); } + xOffset += pixels; + return; + } - if (d === 'Z') { - for (let index = 0; index < px; index++) { - this._tmpCtx.fillRect(xChLeft + xOffset + index, yMidRectOffset - (lineWidth - 1) + index, 1, lineWidth); - } - xOffset += px; - return; + if (token === 'Z') { + for (let index = 0; index < pixels; index++) { + this._tmpCtx.fillRect(xChLeft + xOffset + index, yMidRectOffset - (lineWidth - 1) + index, 1, lineWidth); } + xOffset += pixels; + return; + } - if (d === 'M') { - for (let index = 0; index < px; index++) { - this._tmpCtx.fillRect(xChLeft + xOffset + index, yMidRectOffset - (lineWidth - 1) + (lineWidth - px) - index, 1, lineWidth); - } - xOffset += px; - return; + if (token === 'M') { + for (let index = 0; index < pixels; index++) { + this._tmpCtx.fillRect(xChLeft + xOffset + index, yMidRectOffset - (lineWidth - 1) + (lineWidth - pixels) + index, 1, lineWidth); } - } else { - if (d === 'Y') { - for (let index = 0; index < px; index++) { - this._tmpCtx.fillRect(xChLeft + xOffset + index, yMidRectOffset - index, 1, lineWidth); - } - xOffset += px; - return; + xOffset += pixels; + return; + } + } else { + if (token === 'Y') { + for (let index = 0; index < pixels; index++) { + this._tmpCtx.fillRect(xChLeft + xOffset + index, yMidRectOffset - index, 1, lineWidth); } + xOffset += pixels; + return; + } - if (d === 'B') { - for (let index = 0; index < px; index++) { - this._tmpCtx.fillRect(xChLeft + xOffset + index, yMidRectOffset - (lineWidth - 1) + index, 1, lineWidth); - } - xOffset += px; - return; + if (token === 'B') { + for (let index = 0; index < pixels; index++) { + this._tmpCtx.fillRect(xChLeft + xOffset + index, yMidRectOffset - (lineWidth - 1) + index, 1, lineWidth); } + xOffset += pixels; + return; } } - this._tmpCtx.fillRect(xChLeft + xOffset, yMidRectOffset, px, 1); - xOffset += px; return; } - if (d === 'U') { - // if (lineWidth > 1) { - // this._tmpCtx.fillRect(xChLeft + xOffset, yMidRectOffset - lineWidth, px, 1); - // this._tmpCtx.fillRect(xChLeft + xOffset + 1, yMidRectOffset - lineWidth + 1, px - 2, 1); - // xOffset += px; - // } else { - this._tmpCtx.fillRect(xChLeft + xOffset, yMidRectOffset - lineWidth, px, lineWidth); - xOffset += px; - // } + // draw line + if (token === 'U') { + this._tmpCtx.fillRect(xChLeft + xOffset, yMidRectOffset - lineWidth, pixels, lineWidth); + xOffset += pixels; return; } - if (d === 'D') { - this._tmpCtx.fillRect(xChLeft + xOffset, yMidRectOffset + 1, px, lineWidth); - xOffset += px; + if (token === 'D') { + this._tmpCtx.fillRect(xChLeft + xOffset, yMidRectOffset + 1, pixels, lineWidth); + xOffset += pixels; return; } }); + // handle next nextOffset++; break; diff --git a/src/browser/renderer/shared/Types.d.ts b/src/browser/renderer/shared/Types.d.ts index 00a5a52819..a31754a4b1 100644 --- a/src/browser/renderer/shared/Types.d.ts +++ b/src/browser/renderer/shared/Types.d.ts @@ -172,4 +172,17 @@ export interface ISelectionRenderModel { isCellSelected(terminal: Terminal, x: number, y: number): boolean; } -export type UnderlineCurlySegmentType = 'up' | 'down'; +export type UnderlineCurlyLineType = 'up' | 'down'; +export type UnderlineCurlyJoinOrLine = 'join' | 'line'; + +/** + * Y Complete upward join + * B Complete downward join + * M Split Right downward join + * P Split right upward join + * Q Split left upward join + * Z Split left downward join + * U Up line + * D Down line + */ +export type UnderlineDrawCurlyOp = 'Y' | 'B' | 'M' | 'Q' | 'P' | 'Z' | 'U' | 'D'; From ac7318b82256de861ac4e0f7a41f0e9f663f354f Mon Sep 17 00:00:00 2001 From: tisilent Date: Sat, 18 Nov 2023 18:11:04 +0800 Subject: [PATCH 08/12] Rename --- src/browser/renderer/shared/RendererUtils.ts | 22 ++++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/browser/renderer/shared/RendererUtils.ts b/src/browser/renderer/shared/RendererUtils.ts index 91572ccc9a..772a1e8fe6 100644 --- a/src/browser/renderer/shared/RendererUtils.ts +++ b/src/browser/renderer/shared/RendererUtils.ts @@ -119,19 +119,19 @@ function createVariantSequences(cellWidth: number, fullSegmentWidth: number, poi let upOrDown: UnderlineCurlyLineType = 'up'; let lastUpOrDown: UnderlineCurlyLineType = 'up'; // Split between cells to be processed - let waitHandleLinePixels = 0; + let waitHandlePixels = 0; while (totalPixels > 0) { const cellResult: any[] = []; let cellCurrentWidth = cellWidth; while (cellCurrentWidth > 0) { if (joinOrLine === 'join') { let token: UnderlineDrawCurlyOp = upOrDown === 'up' ? 'Y' : 'B'; - if (waitHandleLinePixels > 0) { + if (waitHandlePixels > 0) { // right token = lastUpOrDown === 'up' ? 'M' : 'P'; - cellResult.push(`${token}${waitHandleLinePixels}`); - cellCurrentWidth -= waitHandleLinePixels; - waitHandleLinePixels = 0; + cellResult.push(`${token}${waitHandlePixels}`); + cellCurrentWidth -= waitHandlePixels; + waitHandlePixels = 0; joinOrLine = 'line'; } else { // left @@ -139,7 +139,7 @@ function createVariantSequences(cellWidth: number, fullSegmentWidth: number, poi if (usingWidth > cellCurrentWidth) { token = lastUpOrDown === 'up' ? 'Z' : 'Q'; cellResult.push(`${token}${cellCurrentWidth}`); - waitHandleLinePixels = usingWidth - cellCurrentWidth; + waitHandlePixels = usingWidth - cellCurrentWidth; cellCurrentWidth = 0; } else { cellResult.push(`${token}${point}`); @@ -149,10 +149,10 @@ function createVariantSequences(cellWidth: number, fullSegmentWidth: number, poi } } else if (joinOrLine === 'line') { const token: UnderlineDrawCurlyOp = upOrDown === 'up' ? 'U' : 'D'; - if (waitHandleLinePixels > 0) { - cellResult.push(`${token}${waitHandleLinePixels}`); - cellCurrentWidth -= waitHandleLinePixels; - waitHandleLinePixels = 0; + if (waitHandlePixels > 0) { + cellResult.push(`${token}${waitHandlePixels}`); + cellCurrentWidth -= waitHandlePixels; + waitHandlePixels = 0; joinOrLine = 'join'; lastUpOrDown = upOrDown; upOrDown = upOrDown === 'up' ? 'down' : 'up'; @@ -160,7 +160,7 @@ function createVariantSequences(cellWidth: number, fullSegmentWidth: number, poi const usingWidth = line; if (usingWidth > cellCurrentWidth) { cellResult.push(`${token}${cellCurrentWidth}`); - waitHandleLinePixels = usingWidth - cellCurrentWidth; + waitHandlePixels = usingWidth - cellCurrentWidth; cellCurrentWidth = 0; } else { cellResult.push(`${token}${line}`); From a619228a3de3a3b7bbd46684b14b6380f749fab3 Mon Sep 17 00:00:00 2001 From: tisilent Date: Sat, 18 Nov 2023 18:45:02 +0800 Subject: [PATCH 09/12] use underlineVariantOffset --- .../renderer/shared/CellColorResolver.ts | 10 ++++---- src/browser/renderer/shared/TextureAtlas.ts | 25 ++++++++++--------- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/browser/renderer/shared/CellColorResolver.ts b/src/browser/renderer/shared/CellColorResolver.ts index 4e58c5baf4..2922278559 100644 --- a/src/browser/renderer/shared/CellColorResolver.ts +++ b/src/browser/renderer/shared/CellColorResolver.ts @@ -14,7 +14,7 @@ let $hasFg = false; let $hasBg = false; let $isSelected = false; let $colors: ReadonlyColorSet | undefined; -let $variantOffset = 0; +// let $variantOffset = 0; let $underlineVariantOffset = 0; export class CellColorResolver { @@ -57,7 +57,7 @@ export class CellColorResolver { $hasFg = false; $isSelected = false; $colors = this._themeService.colors; - $variantOffset = 0; + // $variantOffset = 0; $underlineVariantOffset = 0; const code = cell.getCode(); @@ -66,7 +66,7 @@ export class CellColorResolver { if (code !== NULL_CELL_CODE && cell.extended.underlineStyle !== UnderlineStyle.NONE) { const lineWidth = Math.max(1, Math.floor(this._optionService.rawOptions.fontSize * this._coreBrowserService.dpr / 15)); if (cell.extended.underlineStyle === UnderlineStyle.DOTTED) { - $variantOffset = x * deviceCellWidth % (Math.round(lineWidth) * 2); + $underlineVariantOffset = x * deviceCellWidth % (Math.round(lineWidth) * 2); } else if (cell.extended.underlineStyle === UnderlineStyle.CURLY) { $underlineVariantOffset = getCurlyVariantOffset(x, deviceCellWidth, lineWidth); } @@ -154,8 +154,8 @@ export class CellColorResolver { this.result.fg = $hasFg ? $fg : this.result.fg; // Reset overrides variantOffset - this.result.ext &= ~ExtFlags.VARIANT_OFFSET; - this.result.ext |= ($variantOffset << 29) & ExtFlags.VARIANT_OFFSET; + // this.result.ext &= ~ExtFlags.VARIANT_OFFSET; + // this.result.ext |= ($variantOffset << 29) & ExtFlags.VARIANT_OFFSET; this.result.underlineVariantOffset = $underlineVariantOffset; } } diff --git a/src/browser/renderer/shared/TextureAtlas.ts b/src/browser/renderer/shared/TextureAtlas.ts index 61abe19e25..ff4636d5fa 100644 --- a/src/browser/renderer/shared/TextureAtlas.ts +++ b/src/browser/renderer/shared/TextureAtlas.ts @@ -7,7 +7,7 @@ import { IColorContrastCache } from 'browser/Types'; import { DIM_OPACITY, TEXT_BASELINE } from 'browser/renderer/shared/Constants'; import { tryDrawCustomChar } from 'browser/renderer/shared/CustomGlyphs'; import { computeNextVariantOffset, excludeFromContrastRatioDemands, getCurlyVariant, isPowerlineGlyph, isRestrictedPowerlineGlyph, throwIfFalsy } from 'browser/renderer/shared/RendererUtils'; -import { IBoundingBox, ICharAtlasConfig, IRasterizedGlyph, ITextureAtlas } from 'browser/renderer/shared/Types'; +import { IBoundingBox, ICharAtlasConfig, IRasterizedGlyph, ITextureAtlas, UnderlineDrawCurlyOp } from 'browser/renderer/shared/Types'; import { NULL_COLOR, color, rgba } from 'common/Color'; import { EventEmitter } from 'common/EventEmitter'; import { FiveKeyMap } from 'common/MultiKeyMap'; @@ -546,7 +546,7 @@ export class TextureAtlas implements ITextureAtlas { const yTop = Math.ceil(padding + this._config.deviceCharHeight) - yOffset - (restrictToCellHeight ? lineWidth * 2 : 0); const yMid = yTop + lineWidth; const yBot = yTop + lineWidth * 2; - let nextOffset = underlineVariantoffset !== 0 ? underlineVariantoffset : this._workAttributeData.getUnderlineVariantOffset(); + let nextOffset = underlineVariantoffset; for (let i = 0; i < chWidth; i++) { this._tmpCtx.save(); @@ -578,13 +578,14 @@ export class TextureAtlas implements ITextureAtlas { let xOffset = 0; steps.forEach(step => { - const token = step.substring(0, 1); + const op: UnderlineDrawCurlyOp = step.substring(0, 1) as UnderlineDrawCurlyOp; const pixels = Number.parseInt(step.substring(1)); // draw join - if (token === 'Y' || token === 'B' || token === 'M' || token === 'Q' || token === 'P' || token === 'Z') { + if (op === 'Y' || op === 'B' || op === 'M' || op === 'Q' || op === 'P' || op === 'Z') { + // TODO recode if (pixels < lineWidth) { - if (token === 'Q') { + if (op === 'Q') { for (let index = 0; index < pixels; index++) { this._tmpCtx.fillRect(xChLeft + xOffset + index, yMidRectOffset - index, 1, lineWidth); } @@ -592,7 +593,7 @@ export class TextureAtlas implements ITextureAtlas { return; } - if (token === 'P') { + if (op === 'P') { for (let index = 0; index < pixels; index++) { this._tmpCtx.fillRect(xChLeft + xOffset + index, yMidRectOffset - (lineWidth - pixels) - index, 1, lineWidth); } @@ -600,7 +601,7 @@ export class TextureAtlas implements ITextureAtlas { return; } - if (token === 'Z') { + if (op === 'Z') { for (let index = 0; index < pixels; index++) { this._tmpCtx.fillRect(xChLeft + xOffset + index, yMidRectOffset - (lineWidth - 1) + index, 1, lineWidth); } @@ -608,7 +609,7 @@ export class TextureAtlas implements ITextureAtlas { return; } - if (token === 'M') { + if (op === 'M') { for (let index = 0; index < pixels; index++) { this._tmpCtx.fillRect(xChLeft + xOffset + index, yMidRectOffset - (lineWidth - 1) + (lineWidth - pixels) + index, 1, lineWidth); } @@ -616,7 +617,7 @@ export class TextureAtlas implements ITextureAtlas { return; } } else { - if (token === 'Y') { + if (op === 'Y') { for (let index = 0; index < pixels; index++) { this._tmpCtx.fillRect(xChLeft + xOffset + index, yMidRectOffset - index, 1, lineWidth); } @@ -624,7 +625,7 @@ export class TextureAtlas implements ITextureAtlas { return; } - if (token === 'B') { + if (op === 'B') { for (let index = 0; index < pixels; index++) { this._tmpCtx.fillRect(xChLeft + xOffset + index, yMidRectOffset - (lineWidth - 1) + index, 1, lineWidth); } @@ -636,13 +637,13 @@ export class TextureAtlas implements ITextureAtlas { } // draw line - if (token === 'U') { + if (op === 'U') { this._tmpCtx.fillRect(xChLeft + xOffset, yMidRectOffset - lineWidth, pixels, lineWidth); xOffset += pixels; return; } - if (token === 'D') { + if (op === 'D') { this._tmpCtx.fillRect(xChLeft + xOffset, yMidRectOffset + 1, pixels, lineWidth); xOffset += pixels; return; From bf11d298d321d299ee9d1bbc361c9c7aabe9e067 Mon Sep 17 00:00:00 2001 From: tisilent Date: Mon, 20 Nov 2023 19:37:33 +0800 Subject: [PATCH 10/12] Modify curve generation plan. --- .../renderer/shared/CellColorResolver.ts | 2 +- src/browser/renderer/shared/Constants.ts | 2 -- src/browser/renderer/shared/RendererUtils.ts | 36 ++++++------------- src/browser/renderer/shared/TextureAtlas.ts | 9 +++-- 4 files changed, 16 insertions(+), 33 deletions(-) diff --git a/src/browser/renderer/shared/CellColorResolver.ts b/src/browser/renderer/shared/CellColorResolver.ts index 2922278559..f3099acd98 100644 --- a/src/browser/renderer/shared/CellColorResolver.ts +++ b/src/browser/renderer/shared/CellColorResolver.ts @@ -1,7 +1,7 @@ import { ISelectionRenderModel } from 'browser/renderer/shared/Types'; import { ICoreBrowserService, IThemeService } from 'browser/services/Services'; import { ReadonlyColorSet } from 'browser/Types'; -import { Attributes, BgFlags, ExtFlags, FgFlags, NULL_CELL_CODE, UnderlineStyle } from 'common/buffer/Constants'; +import { Attributes, BgFlags, FgFlags, NULL_CELL_CODE, UnderlineStyle } from 'common/buffer/Constants'; import { IDecorationService, IOptionsService } from 'common/services/Services'; import { ICellData } from 'common/Types'; import { Terminal } from '@xterm/xterm'; diff --git a/src/browser/renderer/shared/Constants.ts b/src/browser/renderer/shared/Constants.ts index 18fcb61d5b..b5105ec787 100644 --- a/src/browser/renderer/shared/Constants.ts +++ b/src/browser/renderer/shared/Constants.ts @@ -12,5 +12,3 @@ export const DIM_OPACITY = 0.5; // would result in truncated text (Issue 3353). Using 'bottom' for Chrome would result in slightly // unaligned Powerline fonts (PR 3356#issuecomment-850928179). export const TEXT_BASELINE: CanvasTextBaseline = isFirefox || isLegacyEdge ? 'bottom' : 'ideographic'; - -export const UNDERLINE_CURLY_SEGMENT_SIZE = 4; diff --git a/src/browser/renderer/shared/RendererUtils.ts b/src/browser/renderer/shared/RendererUtils.ts index 772a1e8fe6..001a3cd5e4 100644 --- a/src/browser/renderer/shared/RendererUtils.ts +++ b/src/browser/renderer/shared/RendererUtils.ts @@ -3,7 +3,6 @@ * @license MIT */ -import { UNDERLINE_CURLY_SEGMENT_SIZE } from 'browser/renderer/shared/Constants'; import { IDimensions, IRenderDimensions, UnderlineCurlyJoinOrLine, UnderlineCurlyLineType, UnderlineDrawCurlyOp } from 'browser/renderer/shared/Types'; import { TwoKeyMap } from 'common/MultiKeyMap'; @@ -93,28 +92,15 @@ export function getCurlyVariantOffset(x: number, cellWidth: number, lineWidth: n return 0; } -export function createDrawCurlyPlan(cellWidth: number, lineWidth: number): any[] { - const defaultFullSegmentWidth = UNDERLINE_CURLY_SEGMENT_SIZE * lineWidth * 2; +const defaultCurlyLinePixels = 3; - if (lineWidth === 2) { - // 12 look better - return createVariantSequences(cellWidth, 12, lineWidth, 4); - } - - if (lineWidth === 3) { - return createVariantSequences(cellWidth, 16, lineWidth, 5); - } - - // if (lineWidth === 4) { - // return createVariantSequences(cellWidth, 20 , lineWidth, 6); - // } - - return createVariantSequences(cellWidth, defaultFullSegmentWidth , lineWidth, 3 * lineWidth); +export function createDrawCurlyPlan(cellWidth: number, lineWidth: number): string[] { + return createVariantSequences(cellWidth, lineWidth, defaultCurlyLinePixels + lineWidth - 1); } -function createVariantSequences(cellWidth: number, fullSegmentWidth: number, point: number, line: number): string[] { +function createVariantSequences(cellWidth: number, joinPixels: number, linePixels: number): string[] { const result: string[] = []; - let totalPixels = cellWidth * fullSegmentWidth; + let totalPixels = cellWidth * ((joinPixels + linePixels) * 2); let joinOrLine: UnderlineCurlyJoinOrLine = 'join'; let upOrDown: UnderlineCurlyLineType = 'up'; let lastUpOrDown: UnderlineCurlyLineType = 'up'; @@ -135,15 +121,15 @@ function createVariantSequences(cellWidth: number, fullSegmentWidth: number, poi joinOrLine = 'line'; } else { // left - const usingWidth = point; + const usingWidth = joinPixels; if (usingWidth > cellCurrentWidth) { token = lastUpOrDown === 'up' ? 'Z' : 'Q'; cellResult.push(`${token}${cellCurrentWidth}`); waitHandlePixels = usingWidth - cellCurrentWidth; cellCurrentWidth = 0; } else { - cellResult.push(`${token}${point}`); - cellCurrentWidth -= point; + cellResult.push(`${token}${joinPixels}`); + cellCurrentWidth -= joinPixels; joinOrLine = 'line'; } } @@ -157,14 +143,14 @@ function createVariantSequences(cellWidth: number, fullSegmentWidth: number, poi lastUpOrDown = upOrDown; upOrDown = upOrDown === 'up' ? 'down' : 'up'; } else { - const usingWidth = line; + const usingWidth = linePixels; if (usingWidth > cellCurrentWidth) { cellResult.push(`${token}${cellCurrentWidth}`); waitHandlePixels = usingWidth - cellCurrentWidth; cellCurrentWidth = 0; } else { - cellResult.push(`${token}${line}`); - cellCurrentWidth -= line; + cellResult.push(`${token}${linePixels}`); + cellCurrentWidth -= linePixels; joinOrLine = 'join'; lastUpOrDown = upOrDown; upOrDown = upOrDown === 'up' ? 'down' : 'up'; diff --git a/src/browser/renderer/shared/TextureAtlas.ts b/src/browser/renderer/shared/TextureAtlas.ts index ff4636d5fa..f1544b094b 100644 --- a/src/browser/renderer/shared/TextureAtlas.ts +++ b/src/browser/renderer/shared/TextureAtlas.ts @@ -552,7 +552,6 @@ export class TextureAtlas implements ITextureAtlas { this._tmpCtx.save(); const xChLeft = xLeft + i * this._config.deviceCellWidth; const xChRight = xLeft + (i + 1) * this._config.deviceCellWidth; - // const xChMid = xChLeft + this._config.deviceCellWidth / 2; switch (this._workAttributeData.extended.underlineStyle) { case UnderlineStyle.DOUBLE: this._tmpCtx.moveTo(xChLeft, yTop); @@ -656,7 +655,7 @@ export class TextureAtlas implements ITextureAtlas { case UnderlineStyle.DOTTED: const offsetWidth = nextOffset === 0 ? 0 : (nextOffset >= lineWidth ? lineWidth * 2 - nextOffset : lineWidth - nextOffset); - // a line and a gap. + // a line and a gap. const isLineStart = nextOffset >= lineWidth ? false : true; if (isLineStart === false || offsetWidth === 0) { this._tmpCtx.setLineDash([Math.round(lineWidth), Math.round(lineWidth)]); @@ -1126,13 +1125,13 @@ function clearColor(imageData: ImageData, bg: IColor, fg: IColor, enableThreshol 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 + 1] === g && + imageData.data[offset + 2] === b) { imageData.data[offset + 3] = 0; } else { // Check the threshold based difference if (enableThresholdCheck && - (Math.abs(imageData.data[offset] - r) + + (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; From 31816f9912cd9fdc3931841da2bd88af324e3ce2 Mon Sep 17 00:00:00 2001 From: tisilent Date: Sat, 25 Nov 2023 21:34:45 +0800 Subject: [PATCH 11/12] add types --- src/browser/renderer/shared/RendererUtils.ts | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/browser/renderer/shared/RendererUtils.ts b/src/browser/renderer/shared/RendererUtils.ts index 001a3cd5e4..2093b74d7e 100644 --- a/src/browser/renderer/shared/RendererUtils.ts +++ b/src/browser/renderer/shared/RendererUtils.ts @@ -63,13 +63,12 @@ export function computeNextVariantOffset(cellWidth: number, lineWidth: number, c } // TwoKeyMap -// eslint-disable-next-line @typescript-eslint/naming-convention -const _curlyVariantCache = new TwoKeyMap(); +const curlyVariantCache = new TwoKeyMap(); export function getCurlyVariant(cellWidth: number, lineWidth: number, offset: number): string { - if (_curlyVariantCache.get(cellWidth, lineWidth)) { - const curlyVariants = _curlyVariantCache.get(cellWidth, lineWidth) as any[]; - if (curlyVariants.length > 0) { + if (curlyVariantCache.get(cellWidth, lineWidth)) { + const curlyVariants = curlyVariantCache.get(cellWidth, lineWidth); + if (curlyVariants && curlyVariants.length > 0) { if (!curlyVariants[offset]) { return curlyVariants[0]; } @@ -80,13 +79,13 @@ export function getCurlyVariant(cellWidth: number, lineWidth: number, offset: nu } export function getCurlyVariantOffset(x: number, cellWidth: number, lineWidth: number): number { - if (_curlyVariantCache.get(cellWidth, lineWidth)) { - const curlyVariants = _curlyVariantCache.get(cellWidth, lineWidth) as any[]; + if (curlyVariantCache.get(cellWidth, lineWidth)) { + const curlyVariants = curlyVariantCache.get(cellWidth, lineWidth) as any[]; return x % curlyVariants.length; } - if (!_curlyVariantCache.get(cellWidth, lineWidth)) { + if (!curlyVariantCache.get(cellWidth, lineWidth)) { const curlyVariants = createDrawCurlyPlan(cellWidth, lineWidth); - _curlyVariantCache.set(cellWidth, lineWidth, curlyVariants); + curlyVariantCache.set(cellWidth, lineWidth, curlyVariants); return x % curlyVariants.length; } return 0; From 07d8b518dbcd428b2d1edfed6b3899e5f68bb451 Mon Sep 17 00:00:00 2001 From: tisilent Date: Sat, 25 Nov 2023 22:34:54 +0800 Subject: [PATCH 12/12] better --- src/browser/renderer/shared/RendererUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/renderer/shared/RendererUtils.ts b/src/browser/renderer/shared/RendererUtils.ts index 2093b74d7e..105031fc8f 100644 --- a/src/browser/renderer/shared/RendererUtils.ts +++ b/src/browser/renderer/shared/RendererUtils.ts @@ -94,7 +94,7 @@ export function getCurlyVariantOffset(x: number, cellWidth: number, lineWidth: n const defaultCurlyLinePixels = 3; export function createDrawCurlyPlan(cellWidth: number, lineWidth: number): string[] { - return createVariantSequences(cellWidth, lineWidth, defaultCurlyLinePixels + lineWidth - 1); + return createVariantSequences(cellWidth, lineWidth, defaultCurlyLinePixels + (lineWidth > 1 ? 1 : 0)); } function createVariantSequences(cellWidth: number, joinPixels: number, linePixels: number): string[] {