Skip to content

Commit 452ee50

Browse files
authored
Merge pull request #5669 from Tyriar/custom_glyph_alignment
Use offset variants for some custom glyphs
2 parents b287397 + f41e53a commit 452ee50

File tree

6 files changed

+53
-9
lines changed

6 files changed

+53
-9
lines changed

addons/addon-webgl/src/CellColorResolver.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { ICellData } from 'common/Types';
77
import { Terminal } from '@xterm/xterm';
88
import { rgba } from 'common/Color';
99
import { treatGlyphAsBackgroundColor } from 'browser/renderer/shared/RendererUtils';
10+
import { blockPatternCodepoints } from './customGlyphs/CustomGlyphDefinitions';
1011

1112
// Work variables to avoid garbage collection
1213
let $fg = 0;
@@ -42,7 +43,7 @@ export class CellColorResolver {
4243
* Resolves colors for the cell, putting the result into the shared {@link result}. This resolves
4344
* overrides, inverse and selection for the cell which can then be used to feed into the renderer.
4445
*/
45-
public resolve(cell: ICellData, x: number, y: number, deviceCellWidth: number): void {
46+
public resolve(cell: ICellData, x: number, y: number, deviceCellWidth: number, deviceCellHeight: number): void {
4647
this.result.bg = cell.bg;
4748
this.result.fg = cell.fg;
4849
this.result.ext = cell.bg & BgFlags.HAS_EXTENDED ? cell.extended.ext : 0;
@@ -63,7 +64,9 @@ export class CellColorResolver {
6364
const lineWidth = Math.max(1, Math.floor(this._optionService.rawOptions.fontSize * this._coreBrowserService.dpr / 15));
6465
$variantOffset = x * deviceCellWidth % (Math.round(lineWidth) * 2);
6566
}
66-
67+
if ($variantOffset === 0 && blockPatternCodepoints.has(code)) {
68+
$variantOffset = ((x * deviceCellWidth) % 2) * 2 + ((y * deviceCellHeight) % 2);
69+
}
6770
// Apply decorations on the bottom layer
6871
this._decorationService.forEachDecorationAtCell(x, y, 'bottom', d => {
6972
if (d.backgroundColorRGB) {

addons/addon-webgl/src/TextureAtlas.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -526,7 +526,8 @@ export class TextureAtlas implements ITextureAtlas {
526526
// Draw custom characters if applicable
527527
let customGlyph = false;
528528
if (this._config.customGlyphs !== false) {
529-
customGlyph = tryDrawCustomGlyph(this._tmpCtx, chars, padding, padding, this._config.deviceCellWidth, this._config.deviceCellHeight, this._config.deviceCharWidth, this._config.deviceCharHeight, this._config.fontSize, this._config.devicePixelRatio, backgroundColor.css);
529+
const variantOffset = this._workAttributeData.getUnderlineVariantOffset();
530+
customGlyph = tryDrawCustomGlyph(this._tmpCtx, chars, padding, padding, this._config.deviceCellWidth, this._config.deviceCellHeight, this._config.deviceCharWidth, this._config.deviceCharHeight, this._config.fontSize, this._config.devicePixelRatio, backgroundColor.css, variantOffset);
530531
}
531532

532533
// Whether to clear pixels based on a threshold difference between the glyph color and the

addons/addon-webgl/src/WebglRenderer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -500,7 +500,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
500500
}
501501

502502
// Load colors/resolve overrides into work colors
503-
this._cellColorResolver.resolve(cell, x, row, this.dimensions.device.cell.width);
503+
this._cellColorResolver.resolve(cell, x, row, this.dimensions.device.cell.width, this.dimensions.device.cell.height);
504504

505505
// Override colors for cursor cell
506506
if (isCursorVisible && row === cursorY) {

addons/addon-webgl/src/customGlyphs/CustomGlyphDefinitions.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -820,6 +820,27 @@ export const customGlyphDefinitions: { [index: string]: CustomGlyphCharacterDefi
820820
// #endregion
821821
};
822822

823+
export const blockPatternCodepoints = new Set<number>([
824+
// Shade characters (2591-2593)
825+
0x2591,
826+
0x2592,
827+
0x2593,
828+
// Rectangular shade characters (1FB8C-1FB94)
829+
0x1FB8C,
830+
0x1FB8D,
831+
0x1FB8E,
832+
0x1FB8F,
833+
0x1FB90,
834+
0x1FB91,
835+
0x1FB92,
836+
0x1FB94,
837+
// Triangular shade characters (1FB9C-1FB9F)
838+
0x1FB9C,
839+
0x1FB9D,
840+
0x1FB9E,
841+
0x1FB9F
842+
]);
843+
823844
/**
824845
* Generates a drawing function for sextant characters. Sextants are a 2x3 grid where each cell
825846
* can be on or off.

addons/addon-webgl/src/customGlyphs/CustomGlyphRasterizer.ts

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,15 @@ export function tryDrawCustomGlyph(
2222
deviceCharHeight: number,
2323
fontSize: number,
2424
devicePixelRatio: number,
25-
backgroundColor?: string
25+
backgroundColor?: string,
26+
variantOffset: number = 0
2627
): boolean {
2728
const unifiedCharDefinition = customGlyphDefinitions[c];
2829
if (unifiedCharDefinition) {
2930
// Normalize to array for uniform handling
3031
const parts = Array.isArray(unifiedCharDefinition) ? unifiedCharDefinition : [unifiedCharDefinition];
3132
for (const part of parts) {
32-
drawDefinitionPart(ctx, part, xOffset, yOffset, deviceCellWidth, deviceCellHeight, deviceCharWidth, deviceCharHeight, fontSize, devicePixelRatio, backgroundColor);
33+
drawDefinitionPart(ctx, part, xOffset, yOffset, deviceCellWidth, deviceCellHeight, deviceCharWidth, deviceCharHeight, fontSize, devicePixelRatio, backgroundColor, variantOffset);
3334
}
3435
return true;
3536
}
@@ -48,7 +49,8 @@ function drawDefinitionPart(
4849
deviceCharHeight: number,
4950
fontSize: number,
5051
devicePixelRatio: number,
51-
backgroundColor?: string
52+
backgroundColor?: string,
53+
variantOffset: number = 0
5254
): void {
5355
// Handle scaleType - adjust dimensions and offset when scaling to character area
5456
let drawWidth = deviceCellWidth;
@@ -74,7 +76,7 @@ function drawDefinitionPart(
7476
drawBlockVectorChar(ctx, part.data, drawXOffset, drawYOffset, drawWidth, drawHeight);
7577
break;
7678
case CustomGlyphDefinitionType.BLOCK_PATTERN:
77-
drawPatternChar(ctx, part.data, drawXOffset, drawYOffset, drawWidth, drawHeight);
79+
drawPatternChar(ctx, part.data, drawXOffset, drawYOffset, drawWidth, drawHeight, variantOffset);
7880
break;
7981
case CustomGlyphDefinitionType.PATH_FUNCTION:
8082
drawPathFunctionCharacter(ctx, part.data, drawXOffset, drawYOffset, drawWidth, drawHeight, devicePixelRatio, part.strokeWidth);
@@ -437,7 +439,8 @@ function drawPatternChar(
437439
xOffset: number,
438440
yOffset: number,
439441
deviceCellWidth: number,
440-
deviceCellHeight: number
442+
deviceCellHeight: number,
443+
variantOffset: number = 0
441444
): void {
442445
let patternSet = cachedPatterns.get(charDefinition);
443446
if (!patternSet) {
@@ -486,6 +489,15 @@ function drawPatternChar(
486489
pattern = throwIfFalsy(ctx.createPattern(tmpCanvas, null));
487490
patternSet.set(fillStyle, pattern);
488491
}
492+
// Apply pattern offset to ensure seamless tiling across cells when cell dimensions are odd.
493+
// variantOffset encodes: bit 1 = x pixel shift, bit 0 = y pixel shift.
494+
const dx = (variantOffset >> 1) & 1;
495+
const dy = variantOffset & 1;
496+
if (dx !== 0 || dy !== 0) {
497+
pattern.setTransform(new DOMMatrix().translateSelf(-dx, -dy));
498+
} else {
499+
pattern.setTransform(new DOMMatrix());
500+
}
489501
ctx.fillStyle = pattern;
490502
ctx.fillRect(xOffset, yOffset, deviceCellWidth, deviceCellHeight);
491503
}

demo/client/components/window/testWindow.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,13 @@ function customGlyphAlignmentHandler(term: Terminal): void {
397397
}
398398
}
399399

400+
term.write('\x1b[0mTriangular fill tests:\x1b[36m\n\r');
401+
term.write('1FB9C 1FB9D 1FB9E 1FB9F all\n\r');
402+
term.write('\u{02592}\u{02592}\u{02592}\u{1FB9C} \u{1FB9D}\u{02592}\u{02592}\u{02592} \u{00020}\u{00020}\u{00020}\u{1FB9E} \u{1FB9F}\u{00020}\u{00020}\u{00020} \u{00020}\u{1FB9E}\u{1FB9F}\u{00020}\n\r');
403+
term.write('\u{02592}\u{02592}\u{1FB9C}\u{00020} \u{00020}\u{1FB9D}\u{02592}\u{02592} \u{00020}\u{00020}\u{1FB9E}\u{02592} \u{02592}\u{1FB9F}\u{00020}\u{00020} \u{1FB9E}\u{02592}\u{02592}\u{1FB9F}\n\r');
404+
term.write('\u{02592}\u{1FB9C}\u{00020}\u{00020} \u{00020}\u{00020}\u{1FB9D}\u{02592} \u{00020}\u{1FB9E}\u{02592}\u{02592} \u{02592}\u{02592}\u{1FB9F}\u{00020} \u{1FB9D}\u{02592}\u{02592}\u{1FB9C}\n\r');
405+
term.write('\u{1FB9C}\u{00020}\u{00020}\u{00020} \u{00020}\u{00020}\u{00020}\u{1FB9D} \u{1FB9E}\u{02592}\u{02592}\u{02592} \u{02592}\u{02592}\u{02592}\u{1FB9F} \u{00020}\u{1FB9D}\u{1FB9C}\u{00020}\n\r');
406+
400407
term.write('\x1b[0mPowerline alignment tests:\n\r');
401408
const powerlineLeftChars = ['\u{E0B2}', '\u{E0B3}', '\u{E0B6}', '\u{E0B7}', '\u{E0BA}', '\u{E0BB}', '\u{E0BE}', '\u{E0BF}', '\u{E0C2}', '\u{E0C3}', '\u{E0C5}', '\u{E0C7}', '\u{E0CA}', '\u{E0D4}'];
402409
const powerlineRightChars = ['\u{E0B0}', '\u{E0B1}', '\u{E0B4}', '\u{E0B5}', '\u{E0B8}', '\u{E0B9}', '\u{E0BC}', '\u{E0BD}', '\u{E0C0}', '\u{E0C1}', '\u{E0C4}', '\u{E0C6}', '\u{E0C8}', '\u{E0D2}', '\u{E0CC}', '\u{E0CD}', '\u{E0CE}', '\u{E0CF}', '\u{E0D0}', '\u{E0D1}'];

0 commit comments

Comments
 (0)