@xterm/addon-webgl 0.20.0-beta.20 → 0.20.0-beta.201

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -13,7 +13,8 @@ import { ICharSizeService, ICharacterJoinerService, ICoreBrowserService, IThemeS
13
13
  import { CharData, IBufferLine, ICellData } from 'common/Types';
14
14
  import { AttributeData } from 'common/buffer/AttributeData';
15
15
  import { CellData } from 'common/buffer/CellData';
16
- import { Attributes, Content, NULL_CELL_CHAR, NULL_CELL_CODE } from 'common/buffer/Constants';
16
+ import { Attributes, Content, FgFlags, NULL_CELL_CHAR, NULL_CELL_CODE } from 'common/buffer/Constants';
17
+ import { TextBlinkStateManager } from 'browser/renderer/shared/TextBlinkStateManager';
17
18
  import { ICoreService, IDecorationService, IOptionsService } from 'common/services/Services';
18
19
  import { Terminal } from '@xterm/xterm';
19
20
  import { GlyphRenderer } from './GlyphRenderer';
@@ -22,14 +23,15 @@ import { COMBINED_CHAR_BIT_MASK, RENDER_MODEL_BG_OFFSET, RENDER_MODEL_EXT_OFFSET
22
23
  import { IWebGL2RenderingContext, type ITextureAtlas } from './Types';
23
24
  import { LinkRenderLayer } from './renderLayer/LinkRenderLayer';
24
25
  import { IRenderLayer } from './renderLayer/Types';
25
- import { Emitter, Event } from 'vs/base/common/event';
26
- import { addDisposableListener } from 'vs/base/browser/dom';
27
- import { combinedDisposable, Disposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';
26
+ import { Emitter, EventUtils } from 'common/Event';
27
+ import { addDisposableListener } from 'browser/Dom';
28
+ import { combinedDisposable, Disposable, MutableDisposable, toDisposable } from 'common/Lifecycle';
28
29
  import { createRenderDimensions } from 'browser/renderer/shared/RendererUtils';
29
30
 
30
31
  export class WebglRenderer extends Disposable implements IRenderer {
31
32
  private _renderLayers: IRenderLayer[];
32
- private _cursorBlinkStateManager: MutableDisposable<CursorBlinkStateManager> = new MutableDisposable();
33
+ private _cursorBlinkStateManager: MutableDisposable<CursorBlinkStateManager> = this._register(new MutableDisposable());
34
+ private _textBlinkStateManager: TextBlinkStateManager;
33
35
  private _charAtlasDisposable = this._register(new MutableDisposable());
34
36
  private _charAtlas: ITextureAtlas | undefined;
35
37
  private _devicePixelRatio: number;
@@ -37,8 +39,9 @@ export class WebglRenderer extends Disposable implements IRenderer {
37
39
  private _observerDisposable = this._register(new MutableDisposable());
38
40
 
39
41
  private _model: RenderModel = new RenderModel();
42
+ private _rowHasBlinkingCells: boolean[] = [];
43
+ private _rowHasBlinkingCellsCount: number = 0;
40
44
  private _workCell: ICellData = new CellData();
41
- private _workCell2: ICellData = new CellData();
42
45
  private _cellColorResolver: CellColorResolver;
43
46
 
44
47
  private _canvas: HTMLCanvasElement;
@@ -105,6 +108,12 @@ export class WebglRenderer extends Disposable implements IRenderer {
105
108
  this._updateDimensions();
106
109
  this._updateCursorBlink();
107
110
  this._register(_optionsService.onOptionChange(() => this._handleOptionsChanged()));
111
+ this._textBlinkStateManager = this._register(new TextBlinkStateManager(
112
+ () => this._requestRedrawViewport(),
113
+ this._coreBrowserService,
114
+ this._optionsService
115
+ ));
116
+ this._resetBlinkingRowState();
108
117
 
109
118
  this._deviceMaxTextureSize = this._gl.getParameter(this._gl.MAX_TEXTURE_SIZE);
110
119
 
@@ -136,6 +145,8 @@ export class WebglRenderer extends Disposable implements IRenderer {
136
145
  this._observerDisposable.value = observeDevicePixelDimensions(this._canvas, w, (w, h) => this._setCanvasDevicePixelDimensions(w, h));
137
146
  }));
138
147
 
148
+ this._register(addDisposableListener(this._coreBrowserService.mainDocument, 'mousedown', () => this._cursorBlinkStateManager.value?.restartBlinkAnimation()));
149
+
139
150
  this._core.screenElement!.appendChild(this._canvas);
140
151
 
141
152
  [this._rectangleRenderer.value, this._glyphRenderer.value] = this._initializeWebGLState();
@@ -176,6 +187,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
176
187
  this._updateDimensions();
177
188
 
178
189
  this._model.resize(this._terminal.cols, this._terminal.rows);
190
+ this._resetBlinkingRowState();
179
191
 
180
192
  // Resize all render layers
181
193
  for (const l of this._renderLayers) {
@@ -202,6 +214,9 @@ export class WebglRenderer extends Disposable implements IRenderer {
202
214
  // Force a full refresh. Resizing `_glyphRenderer` should clear it already,
203
215
  // so there is no need to clear it again here.
204
216
  this._clearModel(false);
217
+
218
+ // Render synchronously to avoid flicker when the canvas is cleared
219
+ this._onRequestRedraw.fire({ start: 0, end: this._terminal.rows - 1, sync: true });
205
220
  }
206
221
 
207
222
  public handleCharSizeChanged(): void {
@@ -226,6 +241,10 @@ export class WebglRenderer extends Disposable implements IRenderer {
226
241
  this._requestRedrawViewport();
227
242
  }
228
243
 
244
+ public handleViewportVisibilityChange(isVisible: boolean): void {
245
+ this._textBlinkStateManager.setViewportVisible(isVisible);
246
+ }
247
+
229
248
  public handleSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void {
230
249
  for (const l of this._renderLayers) {
231
250
  l.handleSelectionChanged(this._terminal, start, end, columnSelectMode);
@@ -285,8 +304,8 @@ export class WebglRenderer extends Disposable implements IRenderer {
285
304
  if (this._charAtlas !== atlas) {
286
305
  this._onChangeTextureAtlas.fire(atlas.pages[0].canvas);
287
306
  this._charAtlasDisposable.value = combinedDisposable(
288
- Event.forward(atlas.onAddTextureAtlasCanvas, this._onAddTextureAtlasCanvas),
289
- Event.forward(atlas.onRemoveTextureAtlasCanvas, this._onRemoveTextureAtlasCanvas)
307
+ EventUtils.forward(atlas.onAddTextureAtlasCanvas, this._onAddTextureAtlasCanvas),
308
+ EventUtils.forward(atlas.onRemoveTextureAtlasCanvas, this._onRemoveTextureAtlasCanvas)
290
309
  );
291
310
  }
292
311
  this._charAtlas = atlas;
@@ -318,6 +337,9 @@ export class WebglRenderer extends Disposable implements IRenderer {
318
337
  l.reset(this._terminal);
319
338
  }
320
339
 
340
+ this._resetBlinkingRowState();
341
+ this._textBlinkStateManager.setNeedsBlinkInViewport(false);
342
+
321
343
  this._cursorBlinkStateManager.value?.restartBlinkAnimation();
322
344
  this._updateCursorBlink();
323
345
  }
@@ -415,6 +437,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
415
437
  for (y = start; y <= end; y++) {
416
438
  row = y + terminal.buffer.ydisp;
417
439
  line = terminal.buffer.lines.get(row)!;
440
+ let rowHasBlinkingCells = false;
418
441
  this._model.lineLengths[y] = 0;
419
442
  isCursorRow = cursorY === row;
420
443
  skipJoinedCheckUntilX = 0;
@@ -472,8 +495,12 @@ export class WebglRenderer extends Disposable implements IRenderer {
472
495
  code = cell.getCode();
473
496
  i = ((y * terminal.cols) + x) * RENDER_MODEL_INDICIES_PER_CELL;
474
497
 
498
+ if (!rowHasBlinkingCells && cell.isBlink()) {
499
+ rowHasBlinkingCells = true;
500
+ }
501
+
475
502
  // Load colors/resolve overrides into work colors
476
- 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);
477
504
 
478
505
  // Override colors for cursor cell
479
506
  if (isCursorVisible && row === cursorY) {
@@ -501,6 +528,10 @@ export class WebglRenderer extends Disposable implements IRenderer {
501
528
  }
502
529
  }
503
530
 
531
+ if (this._textBlinkStateManager.isEnabled && !this._textBlinkStateManager.isBlinkOn && cell.isBlink()) {
532
+ this._cellColorResolver.result.fg |= FgFlags.INVISIBLE;
533
+ }
534
+
504
535
  if (code !== NULL_CELL_CODE) {
505
536
  this._model.lineLengths[y] = x + 1;
506
537
  }
@@ -547,11 +578,31 @@ export class WebglRenderer extends Disposable implements IRenderer {
547
578
  x--; // Go back to the previous update cell for next iteration
548
579
  }
549
580
  }
581
+ this._setRowBlinkState(y, rowHasBlinkingCells);
550
582
  }
551
583
  if (modelUpdated) {
552
584
  this._rectangleRenderer.value!.updateBackgrounds(this._model);
553
585
  }
554
586
  this._rectangleRenderer.value!.updateCursor(this._model);
587
+ this._updateTextBlinkState();
588
+ }
589
+
590
+ private _resetBlinkingRowState(): void {
591
+ this._rowHasBlinkingCells = new Array(this._terminal.rows).fill(false);
592
+ this._rowHasBlinkingCellsCount = 0;
593
+ }
594
+
595
+ private _setRowBlinkState(row: number, hasBlinkingCells: boolean): void {
596
+ const previous = this._rowHasBlinkingCells[row];
597
+ if (previous === hasBlinkingCells) {
598
+ return;
599
+ }
600
+ this._rowHasBlinkingCells[row] = hasBlinkingCells;
601
+ this._rowHasBlinkingCellsCount += hasBlinkingCells ? 1 : -1;
602
+ }
603
+
604
+ private _updateTextBlinkState(): void {
605
+ this._textBlinkStateManager.setNeedsBlinkInViewport(this._rowHasBlinkingCellsCount > 0);
555
606
  }
556
607
 
557
608
  /**
@@ -617,7 +668,8 @@ export class WebglRenderer extends Disposable implements IRenderer {
617
668
  // the change as it's an exact multiple of the cell sizes.
618
669
  this._canvas.width = width;
619
670
  this._canvas.height = height;
620
- this._requestRedrawViewport();
671
+ // Render synchronously to avoid flicker when the canvas is cleared
672
+ this._onRequestRedraw.fire({ start: 0, end: this._terminal.rows - 1, sync: true });
621
673
  }
622
674
 
623
675
  private _requestRedrawViewport(): void {
@@ -3,10 +3,64 @@
3
3
  * @license MIT
4
4
  */
5
5
 
6
- import { CustomGlyphDefinitionType, CustomGlyphScaleType, CustomGlyphVectorType, type CustomGlyphCharacterDefinition, type CustomGlyphPathDrawFunctionDefinition } from './Types';
6
+ import { CustomGlyphDefinitionType, CustomGlyphScaleType, CustomGlyphVectorType, type CustomGlyphCharacterDefinition, type CustomGlyphDefinitionPart, type CustomGlyphPathDrawFunctionDefinition } from './Types';
7
7
 
8
8
  /* eslint-disable max-len */
9
9
 
10
+ const enum Shapes {
11
+ /** │ */ TOP_TO_BOTTOM = 'M.5,0 L.5,1',
12
+ /** ─ */ LEFT_TO_RIGHT = 'M0,.5 L1,.5',
13
+
14
+ /** └ */ TOP_TO_RIGHT = 'M.5,0 L.5,.5 L1,.5',
15
+ /** ┘ */ TOP_TO_LEFT = 'M.5,0 L.5,.5 L0,.5',
16
+ /** ┐ */ LEFT_TO_BOTTOM = 'M0,.5 L.5,.5 L.5,1',
17
+ /** ┌ */ RIGHT_TO_BOTTOM = 'M0.5,1 L.5,.5 L1,.5',
18
+
19
+ /** ╵ */ MIDDLE_TO_TOP = 'M.5,.5 L.5,0',
20
+ /** ╴ */ MIDDLE_TO_LEFT = 'M.5,.5 L0,.5',
21
+ /** ╶ */ MIDDLE_TO_RIGHT = 'M.5,.5 L1,.5',
22
+ /** ╷ */ MIDDLE_TO_BOTTOM = 'M.5,.5 L.5,1',
23
+
24
+ /** ┴ */ T_TOP = 'M0,.5 L1,.5 M.5,.5 L.5,0',
25
+ /** ┤ */ T_LEFT = 'M.5,0 L.5,1 M.5,.5 L0,.5',
26
+ /** ├ */ T_RIGHT = 'M.5,0 L.5,1 M.5,.5 L1,.5',
27
+ /** ┬ */ T_BOTTOM = 'M0,.5 L1,.5 M.5,.5 L.5,1',
28
+
29
+ /** ┼ */ CROSS = 'M0,.5 L1,.5 M.5,0 L.5,1',
30
+
31
+ /** ╌ */ TWO_DASHES_HORIZONTAL = 'M.1,.5 L.4,.5 M.6,.5 L.9,.5', // .2 empty, .3 filled
32
+ /** ┄ */ THREE_DASHES_HORIZONTAL = 'M.0667,.5 L.2667,.5 M.4,.5 L.6,.5 M.7333,.5 L.9333,.5', // .1333 empty, .2 filled
33
+ /** ┉ */ FOUR_DASHES_HORIZONTAL = 'M.05,.5 L.2,.5 M.3,.5 L.45,.5 M.55,.5 L.7,.5 M.8,.5 L.95,.5', // .1 empty, .15 filled
34
+ /** ╎ */ TWO_DASHES_VERTICAL = 'M.5,.1 L.5,.4 M.5,.6 L.5,.9',
35
+ /** ┆ */ THREE_DASHES_VERTICAL = 'M.5,.0667 L.5,.2667 M.5,.4 L.5,.6 M.5,.7333 L.5,.9333',
36
+ /** ┊ */ FOUR_DASHES_VERTICAL = 'M.5,.05 L.5,.2 M.5,.3 L.5,.45 L.5,.55 M.5,.7 L.5,.95',
37
+ }
38
+
39
+ namespace GitBranchSymbolsParts {
40
+ // Lines
41
+ export const LINE_H: CustomGlyphDefinitionPart = Object.freeze({ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.LEFT_TO_RIGHT, strokeWidth: 1 });
42
+ export const LINE_V: CustomGlyphDefinitionPart = Object.freeze({ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: Shapes.TOP_TO_BOTTOM, strokeWidth: 1 });
43
+
44
+ // Fading lines
45
+ export const FADE_RIGHT: CustomGlyphDefinitionPart = Object.freeze({ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: 'M0,.5 L.28,.5 M.32,.5 L.52,.5 M.60,.5 L.72,.5 M.84,.5 L.90,.5', strokeWidth: 1 });
46
+ export const FADE_LEFT: CustomGlyphDefinitionPart = Object.freeze({ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: 'M.10,.5 L.16,.5 M.28,.5 L.40,.5 M.48,.5 L.68,.5 M.72,.5 L1,.5', strokeWidth: 1 });
47
+ export const FADE_DOWN: CustomGlyphDefinitionPart = Object.freeze({ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: 'M.5,0 L.5,.28 M.5,.32 L.5,.52 M.5,.60 L.5,.72 M.5,.84 L.5,.90', strokeWidth: 1 });
48
+ export const FADE_UP: CustomGlyphDefinitionPart = Object.freeze({ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: 'M.5,.10 L.5,.16 M.5,.28 L.5,.40 M.5,.48 L.5,.68 M.5,.72 L.5,1', strokeWidth: 1 });
49
+
50
+ // Curved corners
51
+ export const CURVE_DOWN_RIGHT: CustomGlyphDefinitionPart = Object.freeze({ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: (xp: number, yp: number) => `M.5,1 L.5,${.5 + (yp / .15 * .5)} C.5,${.5 + (yp / .15 * .5)},.5,.5,1,.5`, strokeWidth: 1 });
52
+ export const CURVE_DOWN_LEFT: CustomGlyphDefinitionPart = Object.freeze({ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: (xp: number, yp: number) => `M.5,1 L.5,${.5 + (yp / .15 * .5)} C.5,${.5 + (yp / .15 * .5)},.5,.5,0,.5`, strokeWidth: 1 });
53
+ export const CURVE_UP_RIGHT: CustomGlyphDefinitionPart = Object.freeze({ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: (xp: number, yp: number) => `M.5,0 L.5,${.5 - (yp / .15 * .5)} C.5,${.5 - (yp / .15 * .5)},.5,.5,1,.5`, strokeWidth: 1 });
54
+ export const CURVE_UP_LEFT: CustomGlyphDefinitionPart = Object.freeze({ type: CustomGlyphDefinitionType.PATH_FUNCTION, data: (xp: number, yp: number) => `M.5,0 L.5,${.5 - (yp / .15 * .5)} C.5,${.5 - (yp / .15 * .5)},.5,.5,0,.5`, strokeWidth: 1 });
55
+
56
+ // Node parts
57
+ export const NODE_FILL: CustomGlyphDefinitionPart = Object.freeze({ type: CustomGlyphDefinitionType.PATH, data: 'M.85,.5 A.35,.175,0,1,1,.15,.5 A.35,.175,0,1,1,.85,.5' });
58
+ export const NODE_STROKE: CustomGlyphDefinitionPart = Object.freeze({ type: CustomGlyphDefinitionType.PATH, data: 'M.85,.5 A.35,.175,0,1,1,.15,.5 A.35,.175,0,1,1,.85,.5', strokeWidth: 1 });
59
+ export const NODE_LINE_RIGHT: CustomGlyphDefinitionPart = Object.freeze({ type: CustomGlyphDefinitionType.PATH, data: 'M1,.5 L.85,.5', strokeWidth: 1 });
60
+ export const NODE_LINE_LEFT: CustomGlyphDefinitionPart = Object.freeze({ type: CustomGlyphDefinitionType.PATH, data: 'M0,.5 L.15,.5', strokeWidth: 1 });
61
+ export const NODE_LINE_DOWN: CustomGlyphDefinitionPart = Object.freeze({ type: CustomGlyphDefinitionType.PATH, data: 'M.5,1 L.5,.7', strokeWidth: 1 });
62
+ export const NODE_LINE_UP: CustomGlyphDefinitionPart = Object.freeze({ type: CustomGlyphDefinitionType.PATH, data: 'M.5,0 L.5,.3', strokeWidth: 1 });
63
+ }
10
64
 
11
65
  export const customGlyphDefinitions: { [index: string]: CustomGlyphCharacterDefinition | undefined } = {
12
66
  // #region Box Drawing (2500-257F)
@@ -299,6 +353,109 @@ export const customGlyphDefinitions: { [index: string]: CustomGlyphCharacterDefi
299
353
 
300
354
  // #endregion
301
355
 
356
+ // #region Progress Indicators (EE00-EE0B)
357
+
358
+ // initially added in Fira Code and later Nerd Fonts
359
+ // https://github.com/ryanoasis/nerd-fonts/pull/1733
360
+
361
+ // Progress bars (EE00-EE05)
362
+ '\u{EE00}': { type: CustomGlyphDefinitionType.PATH, data: 'M0,0 L1,0 L1,.05 L.1,.05 L.1,.95 L1,.95 L1,1 L0,1 Z' }, // PROGRESS BAR EMPTY START
363
+ '\u{EE01}': { type: CustomGlyphDefinitionType.PATH, data: 'M0,0 L1,0 L1,.05 L0,.05 Z M0,.95 L1,.95 L1,1 L0,1 Z' }, // PROGRESS BAR EMPTY MIDDLE
364
+ '\u{EE02}': { type: CustomGlyphDefinitionType.PATH, data: 'M1,0 L0,0 L0,.05 L.9,.05 L.9,.95 L0,.95 L0,1 L1,1 Z' }, // PROGRESS BAR EMPTY END
365
+ '\u{EE03}': { type: CustomGlyphDefinitionType.PATH, data: 'M0,0 L1,0 L1,.05 L.1,.05 L.1,.95 L1,.95 L1,1 L0,1 Z M.25,.15 L1,.15 L1,.85 L.25,.85 Z' }, // PROGRESS BAR FILLED START
366
+ '\u{EE04}': { type: CustomGlyphDefinitionType.PATH, data: 'M0,0 L1,0 L1,.05 L0,.05 Z M0,.95 L1,.95 L1,1 L0,1 Z M0,.15 L1,.15 L1,.85 L0,.85 Z' }, // PROGRESS BAR FILLED MIDDLE
367
+ '\u{EE05}': { type: CustomGlyphDefinitionType.PATH, data: 'M1,0 L0,0 L0,.05 L.9,.05 L.9,.95 L0,.95 L0,1 L1,1 Z M0,.15 L.75,.15 L.75,.85 L0,.85 Z' }, // PROGRESS BAR FILLED END
368
+
369
+ // Progress spinners (EE06-EE0B) - 6-frame spinner animation using stroked arcs
370
+ '\u{EE06}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M0.6574,0.303 Q0.623,0.291,0.5852,0.285 T0.5082,0.279 T0.4311,0.285 T0.359,0.303 L0.3082,0.248 Q0.3525,0.232,0.4041,0.2235 T0.5082,0.215 T0.6123,0.2235 T0.7082,0.248 Z', type: CustomGlyphVectorType.FILL }, scaleType: CustomGlyphScaleType.CHAR }, // SPINNER FRAME 1 (12 o'clock)
371
+ '\u{EE07}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M0.8557,0.582 L0.7656,0.551 Q0.7852,0.53,0.7951,0.507 T0.8049,0.46 Q0.8049,0.424,0.782,0.3905 T0.718,0.332 T0.6221,0.293 T0.5082,0.279 L0.5082,0.215 Q0.5607,0.215,0.6123,0.2235 T0.709,0.248 T0.7918,0.287 T0.8557,0.3375 T0.8959,0.3965 T0.9098,0.46 T0.8959,0.5235 T0.8557,0.582 Z', type: CustomGlyphVectorType.FILL }, scaleType: CustomGlyphScaleType.CHAR }, // SPINNER FRAME 2 (2 o'clock)
372
+ '\u{EE08}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M0.5082,0.705 Q0.482,0.705,0.4557,0.703 T0.4049,0.697 L0.4311,0.635 Q0.4508,0.638,0.4697,0.6395 T0.5082,0.641 Q0.5672,0.641,0.6221,0.627 T0.718,0.588 T0.782,0.5295 T0.8049,0.46 Q0.8049,0.436,0.7951,0.413 T0.7656,0.369 L0.8557,0.338 Q0.882,0.365,0.8959,0.3965 T0.9098,0.46 T0.8959,0.5235 T0.8557,0.5825 T0.7918,0.633 T0.709,0.672 T0.6123,0.6965 T0.5082,0.705 Z', type: CustomGlyphVectorType.FILL }, scaleType: CustomGlyphScaleType.CHAR }, // SPINNER FRAME 3 (4 o'clock)
373
+ '\u{EE09}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M0.5082,0.705 Q0.4557,0.705,0.4041,0.6965 T0.3074,0.672 T0.2246,0.633 T0.1607,0.5825 T0.1205,0.5235 T0.1066,0.46 L0.2115,0.46 Q0.2115,0.496,0.2344,0.5295 T0.2984,0.588 T0.3943,0.627 T0.5082,0.641 T0.6221,0.627 T0.718,0.588 T0.782,0.5295 T0.8049,0.46 L0.9098,0.46 Q0.9098,0.492,0.8959,0.5235 T0.8557,0.5825 T0.7918,0.633 T0.709,0.672 T0.6123,0.6965 T0.5082,0.705 Z', type: CustomGlyphVectorType.FILL }, scaleType: CustomGlyphScaleType.CHAR }, // SPINNER FRAME 4 (6 o'clock)
374
+ '\u{EE0A}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M0.5082,0.705 Q0.4557,0.705,0.4041,0.6965 T0.3074,0.672 T0.2246,0.633 T0.1607,0.5825 T0.1205,0.5235 T0.1066,0.46 T0.1205,0.3965 T0.1607,0.338 L0.2508,0.369 Q0.2311,0.39,0.2213,0.413 T0.2115,0.46 Q0.2115,0.496,0.2344,0.5295 T0.2984,0.588 T0.3943,0.627 T0.5082,0.641 Q0.5279,0.641,0.5467,0.6395 T0.5852,0.635 L0.6115,0.697 Q0.5869,0.701,0.5607,0.703 T0.5082,0.705 Z', type: CustomGlyphVectorType.FILL }, scaleType: CustomGlyphScaleType.CHAR }, // SPINNER FRAME 5 (8 o'clock)
375
+ '\u{EE0B}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M0.1607,0.582 Q0.1344,0.555,0.1205,0.5235 T0.1066,0.46 T0.1205,0.3965 T0.1607,0.3375 T0.2246,0.287 T0.3074,0.248 T0.4041,0.2235 T0.5082,0.215 L0.5082,0.279 Q0.4492,0.279,0.3943,0.293 T0.2984,0.332 T0.2344,0.3905 T0.2115,0.46 Q0.2115,0.484,0.2213,0.507 T0.2508,0.551 Z', type: CustomGlyphVectorType.FILL }, scaleType: CustomGlyphScaleType.CHAR }, // SPINNER FRAME 6 (10 o'clock)
376
+
377
+ // #endregion
378
+
379
+ // #region Git Branch Symbols (F5D0-F60D)
380
+
381
+ // Initially added in Kitty (https://github.com/kovidgoyal/kitty/pull/7681)
382
+ // then in Wezterm (https://github.com/wezterm/wezterm/issues/6328).
383
+
384
+ // Straight lines (F5D0-F5D9)
385
+ '\u{F5D0}': GitBranchSymbolsParts.LINE_H, // Same as 2500
386
+ '\u{F5D1}': GitBranchSymbolsParts.LINE_V, // Same as 2502
387
+ '\u{F5D2}': GitBranchSymbolsParts.FADE_RIGHT,
388
+ '\u{F5D3}': GitBranchSymbolsParts.FADE_LEFT,
389
+ '\u{F5D4}': GitBranchSymbolsParts.FADE_DOWN,
390
+ '\u{F5D5}': GitBranchSymbolsParts.FADE_UP,
391
+
392
+ // Curved lines (F5D6-F5D9)
393
+ '\u{F5D6}': GitBranchSymbolsParts.CURVE_DOWN_RIGHT, // Same as 256D)
394
+ '\u{F5D7}': GitBranchSymbolsParts.CURVE_DOWN_LEFT, // Same as 256E)
395
+ '\u{F5D8}': GitBranchSymbolsParts.CURVE_UP_RIGHT, // Same as 2570)
396
+ '\u{F5D9}': GitBranchSymbolsParts.CURVE_UP_LEFT, // Same as 256F)
397
+
398
+ // Branching lines (F5DA-F5ED)
399
+ '\u{F5DA}': [GitBranchSymbolsParts.LINE_V, GitBranchSymbolsParts.CURVE_UP_RIGHT],
400
+ '\u{F5DB}': [GitBranchSymbolsParts.LINE_V, GitBranchSymbolsParts.CURVE_DOWN_RIGHT],
401
+ '\u{F5DC}': [GitBranchSymbolsParts.CURVE_DOWN_RIGHT, GitBranchSymbolsParts.CURVE_UP_RIGHT],
402
+ '\u{F5DD}': [GitBranchSymbolsParts.LINE_V, GitBranchSymbolsParts.CURVE_UP_LEFT],
403
+ '\u{F5DE}': [GitBranchSymbolsParts.LINE_V, GitBranchSymbolsParts.CURVE_DOWN_LEFT],
404
+ '\u{F5DF}': [GitBranchSymbolsParts.CURVE_DOWN_LEFT, GitBranchSymbolsParts.CURVE_UP_LEFT],
405
+ '\u{F5E0}': [GitBranchSymbolsParts.LINE_H, GitBranchSymbolsParts.CURVE_DOWN_LEFT],
406
+ '\u{F5E1}': [GitBranchSymbolsParts.LINE_H, GitBranchSymbolsParts.CURVE_DOWN_RIGHT],
407
+ '\u{F5E2}': [GitBranchSymbolsParts.CURVE_DOWN_LEFT, GitBranchSymbolsParts.CURVE_DOWN_RIGHT],
408
+ '\u{F5E3}': [GitBranchSymbolsParts.LINE_H, GitBranchSymbolsParts.CURVE_UP_LEFT],
409
+ '\u{F5E4}': [GitBranchSymbolsParts.LINE_H, GitBranchSymbolsParts.CURVE_UP_RIGHT],
410
+ '\u{F5E5}': [GitBranchSymbolsParts.CURVE_UP_LEFT, GitBranchSymbolsParts.CURVE_UP_RIGHT],
411
+ '\u{F5E6}': [GitBranchSymbolsParts.LINE_V, GitBranchSymbolsParts.CURVE_UP_LEFT, GitBranchSymbolsParts.CURVE_UP_RIGHT],
412
+ '\u{F5E7}': [GitBranchSymbolsParts.LINE_V, GitBranchSymbolsParts.CURVE_DOWN_LEFT, GitBranchSymbolsParts.CURVE_DOWN_RIGHT],
413
+ '\u{F5E8}': [GitBranchSymbolsParts.LINE_H, GitBranchSymbolsParts.CURVE_DOWN_LEFT, GitBranchSymbolsParts.CURVE_UP_LEFT],
414
+ '\u{F5E9}': [GitBranchSymbolsParts.LINE_H, GitBranchSymbolsParts.CURVE_DOWN_RIGHT, GitBranchSymbolsParts.CURVE_UP_RIGHT],
415
+ '\u{F5EA}': [GitBranchSymbolsParts.LINE_V, GitBranchSymbolsParts.CURVE_DOWN_RIGHT, GitBranchSymbolsParts.CURVE_UP_LEFT],
416
+ '\u{F5EB}': [GitBranchSymbolsParts.LINE_V, GitBranchSymbolsParts.CURVE_DOWN_LEFT, GitBranchSymbolsParts.CURVE_UP_RIGHT],
417
+ '\u{F5EC}': [GitBranchSymbolsParts.LINE_H, GitBranchSymbolsParts.CURVE_DOWN_RIGHT, GitBranchSymbolsParts.CURVE_UP_LEFT],
418
+ '\u{F5ED}': [GitBranchSymbolsParts.LINE_H, GitBranchSymbolsParts.CURVE_DOWN_LEFT, GitBranchSymbolsParts.CURVE_UP_RIGHT],
419
+
420
+ // Nodes (F5EE-F5FB)
421
+ '\u{F5EE}': [GitBranchSymbolsParts.NODE_FILL, GitBranchSymbolsParts.NODE_STROKE],
422
+ '\u{F5EF}': GitBranchSymbolsParts.NODE_STROKE,
423
+ '\u{F5F0}': [GitBranchSymbolsParts.NODE_FILL, GitBranchSymbolsParts.NODE_STROKE, GitBranchSymbolsParts.NODE_LINE_RIGHT],
424
+ '\u{F5F1}': [GitBranchSymbolsParts.NODE_STROKE, GitBranchSymbolsParts.NODE_LINE_RIGHT],
425
+ '\u{F5F2}': [GitBranchSymbolsParts.NODE_FILL, GitBranchSymbolsParts.NODE_STROKE, GitBranchSymbolsParts.NODE_LINE_LEFT],
426
+ '\u{F5F3}': [GitBranchSymbolsParts.NODE_STROKE, GitBranchSymbolsParts.NODE_LINE_LEFT],
427
+ '\u{F5F4}': [GitBranchSymbolsParts.NODE_FILL, GitBranchSymbolsParts.NODE_STROKE, GitBranchSymbolsParts.NODE_LINE_LEFT, GitBranchSymbolsParts.NODE_LINE_RIGHT],
428
+ '\u{F5F5}': [GitBranchSymbolsParts.NODE_STROKE, GitBranchSymbolsParts.NODE_LINE_LEFT, GitBranchSymbolsParts.NODE_LINE_RIGHT],
429
+ '\u{F5F6}': [GitBranchSymbolsParts.NODE_FILL, GitBranchSymbolsParts.NODE_STROKE, GitBranchSymbolsParts.NODE_LINE_DOWN],
430
+ '\u{F5F7}': [GitBranchSymbolsParts.NODE_STROKE, GitBranchSymbolsParts.NODE_LINE_DOWN],
431
+ '\u{F5F8}': [GitBranchSymbolsParts.NODE_FILL, GitBranchSymbolsParts.NODE_STROKE, GitBranchSymbolsParts.NODE_LINE_UP],
432
+ '\u{F5F9}': [GitBranchSymbolsParts.NODE_STROKE, GitBranchSymbolsParts.NODE_LINE_UP],
433
+ '\u{F5FA}': [GitBranchSymbolsParts.NODE_FILL, GitBranchSymbolsParts.NODE_STROKE, GitBranchSymbolsParts.NODE_LINE_DOWN, GitBranchSymbolsParts.NODE_LINE_UP],
434
+ '\u{F5FB}': [GitBranchSymbolsParts.NODE_STROKE, GitBranchSymbolsParts.NODE_LINE_DOWN, GitBranchSymbolsParts.NODE_LINE_UP],
435
+
436
+ // Extended Nodes (F5FC-F60D)
437
+ // These were added a little later https://github.com/kovidgoyal/kitty/pull/7805
438
+ '\u{F5FC}': [GitBranchSymbolsParts.NODE_FILL, GitBranchSymbolsParts.NODE_STROKE, GitBranchSymbolsParts.NODE_LINE_DOWN, GitBranchSymbolsParts.NODE_LINE_RIGHT],
439
+ '\u{F5FD}': [GitBranchSymbolsParts.NODE_STROKE, GitBranchSymbolsParts.NODE_LINE_DOWN, GitBranchSymbolsParts.NODE_LINE_RIGHT],
440
+ '\u{F5FE}': [GitBranchSymbolsParts.NODE_FILL, GitBranchSymbolsParts.NODE_STROKE, GitBranchSymbolsParts.NODE_LINE_DOWN, GitBranchSymbolsParts.NODE_LINE_LEFT],
441
+ '\u{F5FF}': [GitBranchSymbolsParts.NODE_STROKE, GitBranchSymbolsParts.NODE_LINE_DOWN, GitBranchSymbolsParts.NODE_LINE_LEFT],
442
+ '\u{F600}': [GitBranchSymbolsParts.NODE_FILL, GitBranchSymbolsParts.NODE_STROKE, GitBranchSymbolsParts.NODE_LINE_UP, GitBranchSymbolsParts.NODE_LINE_RIGHT],
443
+ '\u{F601}': [GitBranchSymbolsParts.NODE_STROKE, GitBranchSymbolsParts.NODE_LINE_UP, GitBranchSymbolsParts.NODE_LINE_RIGHT],
444
+ '\u{F602}': [GitBranchSymbolsParts.NODE_FILL, GitBranchSymbolsParts.NODE_STROKE, GitBranchSymbolsParts.NODE_LINE_UP, GitBranchSymbolsParts.NODE_LINE_LEFT],
445
+ '\u{F603}': [GitBranchSymbolsParts.NODE_STROKE, GitBranchSymbolsParts.NODE_LINE_UP, GitBranchSymbolsParts.NODE_LINE_LEFT],
446
+ '\u{F604}': [GitBranchSymbolsParts.NODE_FILL, GitBranchSymbolsParts.NODE_STROKE, GitBranchSymbolsParts.NODE_LINE_UP, GitBranchSymbolsParts.NODE_LINE_DOWN, GitBranchSymbolsParts.NODE_LINE_RIGHT],
447
+ '\u{F605}': [GitBranchSymbolsParts.NODE_STROKE, GitBranchSymbolsParts.NODE_LINE_UP, GitBranchSymbolsParts.NODE_LINE_DOWN, GitBranchSymbolsParts.NODE_LINE_RIGHT],
448
+ '\u{F606}': [GitBranchSymbolsParts.NODE_FILL, GitBranchSymbolsParts.NODE_STROKE, GitBranchSymbolsParts.NODE_LINE_UP, GitBranchSymbolsParts.NODE_LINE_DOWN, GitBranchSymbolsParts.NODE_LINE_LEFT],
449
+ '\u{F607}': [GitBranchSymbolsParts.NODE_STROKE, GitBranchSymbolsParts.NODE_LINE_UP, GitBranchSymbolsParts.NODE_LINE_DOWN, GitBranchSymbolsParts.NODE_LINE_LEFT],
450
+ '\u{F608}': [GitBranchSymbolsParts.NODE_FILL, GitBranchSymbolsParts.NODE_STROKE, GitBranchSymbolsParts.NODE_LINE_DOWN, GitBranchSymbolsParts.NODE_LINE_LEFT, GitBranchSymbolsParts.NODE_LINE_RIGHT],
451
+ '\u{F609}': [GitBranchSymbolsParts.NODE_STROKE, GitBranchSymbolsParts.NODE_LINE_DOWN, GitBranchSymbolsParts.NODE_LINE_LEFT, GitBranchSymbolsParts.NODE_LINE_RIGHT],
452
+ '\u{F60A}': [GitBranchSymbolsParts.NODE_FILL, GitBranchSymbolsParts.NODE_STROKE, GitBranchSymbolsParts.NODE_LINE_UP, GitBranchSymbolsParts.NODE_LINE_LEFT, GitBranchSymbolsParts.NODE_LINE_RIGHT],
453
+ '\u{F60B}': [GitBranchSymbolsParts.NODE_STROKE, GitBranchSymbolsParts.NODE_LINE_UP, GitBranchSymbolsParts.NODE_LINE_LEFT, GitBranchSymbolsParts.NODE_LINE_RIGHT],
454
+ '\u{F60C}': [GitBranchSymbolsParts.NODE_FILL, GitBranchSymbolsParts.NODE_STROKE, GitBranchSymbolsParts.NODE_LINE_UP, GitBranchSymbolsParts.NODE_LINE_DOWN, GitBranchSymbolsParts.NODE_LINE_LEFT, GitBranchSymbolsParts.NODE_LINE_RIGHT],
455
+ '\u{F60D}': [GitBranchSymbolsParts.NODE_STROKE, GitBranchSymbolsParts.NODE_LINE_UP, GitBranchSymbolsParts.NODE_LINE_DOWN, GitBranchSymbolsParts.NODE_LINE_LEFT, GitBranchSymbolsParts.NODE_LINE_RIGHT],
456
+
457
+ // #endregion
458
+
302
459
  // #region Symbols for Legacy Computing (1FB00-1FB3B)
303
460
 
304
461
  // https://www.unicode.org/charts/PDF/U1FB00.pdf
@@ -516,8 +673,8 @@ export const customGlyphDefinitions: { [index: string]: CustomGlyphCharacterDefi
516
673
  ] },
517
674
 
518
675
  // Diagonal fill characters (1FB98-1FB99)
519
- '\u{1FB98}': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: 'M0,0 L1,1 M0,.25 L.75,1 M0,.5 L.5,1 M0,.75 L.25,1 M.25,0 L1,.75 M.5,0 L1,.5 M.75,0 L1,.25', strokeWidth: 1 }, // UPPER LEFT TO LOWER RIGHT FILL
520
- '\u{1FB99}': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: 'M0,.25 L.25,0 M0,.5 L.5,0 M0,.75 L.75,0 M0,1 L1,0 M.25,1 L1,.25 M.5,1 L1,.5 M.75,1 L1,.75', strokeWidth: 1 }, // UPPER RIGHT TO LOWER LEFT FILL
676
+ '\u{1FB98}': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: 'M-0.25,-0.25 L1.25,1.25 M-0.25,0 L1,1.25 M-0.25,0.25 L0.75,1.25 M-0.25,0.5 L0.5,1.25 M0,-0.25 L1.25,1 M0.25,-0.25 L1.25,0.75 M0.5,-0.25 L1.25,0.5 M-0.25,0.75 L0.25,1.25 M0.75,-0.25 L1.25,0.25', strokeWidth: 1 }, // UPPER LEFT TO LOWER RIGHT FILL
677
+ '\u{1FB99}': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: 'M-0.25,0.5 L0.5,-0.25 M-0.25,0.75 L0.75,-0.25 M-0.25,1 L1,-0.25 M-0.25,1.25 L1.25,-0.25 M0,1.25 L1.25,0 M0.25,1.25 L1.25,0.25 M0.5,1.25 L1.25,0.5 M-0.25,0.25 L0.25,-0.25 M0.75,1.25 L1.25,0.75', strokeWidth: 1 }, // UPPER RIGHT TO LOWER LEFT FILL
521
678
 
522
679
  // Smooth mosaic terminal graphic characters (1FB9A-1FB9B)
523
680
  '\u{1FB9A}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M0,0 L.5,.5 L0,1 L1,1 L.5,.5 L1,0', type: CustomGlyphVectorType.FILL } }, // UPPER AND LOWER TRIANGULAR HALF BLOCK
@@ -692,6 +849,27 @@ export const customGlyphDefinitions: { [index: string]: CustomGlyphCharacterDefi
692
849
  // #endregion
693
850
  };
694
851
 
852
+ export const blockPatternCodepoints = new Set<number>([
853
+ // Shade characters (2591-2593)
854
+ 0x2591,
855
+ 0x2592,
856
+ 0x2593,
857
+ // Rectangular shade characters (1FB8C-1FB94)
858
+ 0x1FB8C,
859
+ 0x1FB8D,
860
+ 0x1FB8E,
861
+ 0x1FB8F,
862
+ 0x1FB90,
863
+ 0x1FB91,
864
+ 0x1FB92,
865
+ 0x1FB94,
866
+ // Triangular shade characters (1FB9C-1FB9F)
867
+ 0x1FB9C,
868
+ 0x1FB9D,
869
+ 0x1FB9E,
870
+ 0x1FB9F
871
+ ]);
872
+
695
873
  /**
696
874
  * Generates a drawing function for sextant characters. Sextants are a 2x3 grid where each cell
697
875
  * can be on or off.
@@ -839,31 +1017,3 @@ function segmentedDigit(pattern: number): string {
839
1017
  return paths.join(' ');
840
1018
  }
841
1019
 
842
- const enum Shapes {
843
- /** │ */ TOP_TO_BOTTOM = 'M.5,0 L.5,1',
844
- /** ─ */ LEFT_TO_RIGHT = 'M0,.5 L1,.5',
845
-
846
- /** └ */ TOP_TO_RIGHT = 'M.5,0 L.5,.5 L1,.5',
847
- /** ┘ */ TOP_TO_LEFT = 'M.5,0 L.5,.5 L0,.5',
848
- /** ┐ */ LEFT_TO_BOTTOM = 'M0,.5 L.5,.5 L.5,1',
849
- /** ┌ */ RIGHT_TO_BOTTOM = 'M0.5,1 L.5,.5 L1,.5',
850
-
851
- /** ╵ */ MIDDLE_TO_TOP = 'M.5,.5 L.5,0',
852
- /** ╴ */ MIDDLE_TO_LEFT = 'M.5,.5 L0,.5',
853
- /** ╶ */ MIDDLE_TO_RIGHT = 'M.5,.5 L1,.5',
854
- /** ╷ */ MIDDLE_TO_BOTTOM = 'M.5,.5 L.5,1',
855
-
856
- /** ┴ */ T_TOP = 'M0,.5 L1,.5 M.5,.5 L.5,0',
857
- /** ┤ */ T_LEFT = 'M.5,0 L.5,1 M.5,.5 L0,.5',
858
- /** ├ */ T_RIGHT = 'M.5,0 L.5,1 M.5,.5 L1,.5',
859
- /** ┬ */ T_BOTTOM = 'M0,.5 L1,.5 M.5,.5 L.5,1',
860
-
861
- /** ┼ */ CROSS = 'M0,.5 L1,.5 M.5,0 L.5,1',
862
-
863
- /** ╌ */ TWO_DASHES_HORIZONTAL = 'M.1,.5 L.4,.5 M.6,.5 L.9,.5', // .2 empty, .3 filled
864
- /** ┄ */ THREE_DASHES_HORIZONTAL = 'M.0667,.5 L.2667,.5 M.4,.5 L.6,.5 M.7333,.5 L.9333,.5', // .1333 empty, .2 filled
865
- /** ┉ */ FOUR_DASHES_HORIZONTAL = 'M.05,.5 L.2,.5 M.3,.5 L.45,.5 M.55,.5 L.7,.5 M.8,.5 L.95,.5', // .1 empty, .15 filled
866
- /** ╎ */ TWO_DASHES_VERTICAL = 'M.5,.1 L.5,.4 M.5,.6 L.5,.9',
867
- /** ┆ */ THREE_DASHES_VERTICAL = 'M.5,.0667 L.5,.2667 M.5,.4 L.5,.6 M.5,.7333 L.5,.9333',
868
- /** ┊ */ FOUR_DASHES_VERTICAL = 'M.5,.05 L.5,.2 M.5,.3 L.5,.45 L.5,.55 M.5,.7 L.5,.95',
869
- }
@@ -22,14 +22,15 @@ export function tryDrawCustomGlyph(
22
22
  deviceCharHeight: number,
23
23
  fontSize: number,
24
24
  devicePixelRatio: number,
25
- backgroundColor?: string
25
+ backgroundColor?: string,
26
+ variantOffset: number = 0
26
27
  ): boolean {
27
28
  const unifiedCharDefinition = customGlyphDefinitions[c];
28
29
  if (unifiedCharDefinition) {
29
30
  // Normalize to array for uniform handling
30
31
  const parts = Array.isArray(unifiedCharDefinition) ? unifiedCharDefinition : [unifiedCharDefinition];
31
32
  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);
33
34
  }
34
35
  return true;
35
36
  }
@@ -48,7 +49,8 @@ function drawDefinitionPart(
48
49
  deviceCharHeight: number,
49
50
  fontSize: number,
50
51
  devicePixelRatio: number,
51
- backgroundColor?: string
52
+ backgroundColor?: string,
53
+ variantOffset: number = 0
52
54
  ): void {
53
55
  // Handle scaleType - adjust dimensions and offset when scaling to character area
54
56
  let drawWidth = deviceCellWidth;
@@ -74,13 +76,13 @@ function drawDefinitionPart(
74
76
  drawBlockVectorChar(ctx, part.data, drawXOffset, drawYOffset, drawWidth, drawHeight);
75
77
  break;
76
78
  case CustomGlyphDefinitionType.BLOCK_PATTERN:
77
- drawPatternChar(ctx, part.data, drawXOffset, drawYOffset, drawWidth, drawHeight);
79
+ drawPatternChar(ctx, part.data, drawXOffset, drawYOffset, drawWidth, drawHeight, variantOffset);
78
80
  break;
79
81
  case CustomGlyphDefinitionType.PATH_FUNCTION:
80
82
  drawPathFunctionCharacter(ctx, part.data, drawXOffset, drawYOffset, drawWidth, drawHeight, devicePixelRatio, part.strokeWidth);
81
83
  break;
82
84
  case CustomGlyphDefinitionType.PATH:
83
- drawPathDefinitionCharacter(ctx, part.data, drawXOffset, drawYOffset, drawWidth, drawHeight);
85
+ drawPathDefinitionCharacter(ctx, part.data, drawXOffset, drawYOffset, drawWidth, drawHeight, devicePixelRatio, part.strokeWidth);
84
86
  break;
85
87
  case CustomGlyphDefinitionType.PATH_NEGATIVE:
86
88
  drawPathNegativeDefinitionCharacter(ctx, part.data, drawXOffset, drawYOffset, drawWidth, drawHeight, devicePixelRatio, backgroundColor);
@@ -171,7 +173,9 @@ function drawPathDefinitionCharacter(
171
173
  xOffset: number,
172
174
  yOffset: number,
173
175
  deviceCellWidth: number,
174
- deviceCellHeight: number
176
+ deviceCellHeight: number,
177
+ devicePixelRatio: number,
178
+ strokeWidth?: number
175
179
  ): void {
176
180
  const instructions = typeof charDefinition === 'string' ? charDefinition : charDefinition(0, 0);
177
181
  ctx.beginPath();
@@ -272,7 +276,13 @@ function drawPathDefinitionCharacter(
272
276
  }
273
277
  lastCommand = type;
274
278
  }
275
- ctx.fill();
279
+ if (strokeWidth !== undefined) {
280
+ ctx.strokeStyle = ctx.fillStyle;
281
+ ctx.lineWidth = devicePixelRatio * strokeWidth;
282
+ ctx.stroke();
283
+ } else {
284
+ ctx.fill();
285
+ }
276
286
  }
277
287
 
278
288
  /**
@@ -429,7 +439,8 @@ function drawPatternChar(
429
439
  xOffset: number,
430
440
  yOffset: number,
431
441
  deviceCellWidth: number,
432
- deviceCellHeight: number
442
+ deviceCellHeight: number,
443
+ variantOffset: number = 0
433
444
  ): void {
434
445
  let patternSet = cachedPatterns.get(charDefinition);
435
446
  if (!patternSet) {
@@ -478,6 +489,15 @@ function drawPatternChar(
478
489
  pattern = throwIfFalsy(ctx.createPattern(tmpCanvas, null));
479
490
  patternSet.set(fillStyle, pattern);
480
491
  }
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
+ }
481
501
  ctx.fillStyle = pattern;
482
502
  ctx.fillRect(xOffset, yOffset, deviceCellWidth, deviceCellHeight);
483
503
  }
@@ -492,6 +512,11 @@ function drawPathFunctionCharacter(
492
512
  devicePixelRatio: number,
493
513
  strokeWidth?: number
494
514
  ): void {
515
+ ctx.save();
516
+ ctx.beginPath();
517
+ ctx.rect(xOffset, yOffset, deviceCellWidth, deviceCellHeight);
518
+ ctx.clip();
519
+
495
520
  ctx.beginPath();
496
521
  let actualInstructions: string;
497
522
  if (typeof charDefinition === 'function') {
@@ -518,7 +543,7 @@ function drawPathFunctionCharacter(
518
543
  if (!args[0] || !args[1]) {
519
544
  continue;
520
545
  }
521
- f(ctx, translateArgs(args, deviceCellWidth, deviceCellHeight, xOffset, yOffset, true, devicePixelRatio), state);
546
+ f(ctx, translateArgs(args, deviceCellWidth, deviceCellHeight, xOffset, yOffset, true, devicePixelRatio, 0, 0, false), state);
522
547
  state.lastCommand = type;
523
548
  }
524
549
  if (strokeWidth !== undefined) {
@@ -529,6 +554,7 @@ function drawPathFunctionCharacter(
529
554
  ctx.fill();
530
555
  }
531
556
  ctx.closePath();
557
+ ctx.restore();
532
558
  }
533
559
 
534
560
  /**
@@ -677,7 +703,7 @@ const svgToCanvasInstructionMap: { [index: string]: (ctx: CanvasRenderingContext
677
703
  }
678
704
  };
679
705
 
680
- function translateArgs(args: string[], cellWidth: number, cellHeight: number, xOffset: number, yOffset: number, doClamp: boolean, devicePixelRatio: number, leftPadding: number = 0, rightPadding: number = 0): number[] {
706
+ function translateArgs(args: string[], cellWidth: number, cellHeight: number, xOffset: number, yOffset: number, doClamp: boolean, devicePixelRatio: number, leftPadding: number = 0, rightPadding: number = 0, clampToCell: boolean = true): number[] {
681
707
  const result = args.map(e => parseFloat(e) || parseInt(e));
682
708
 
683
709
  if (result.length < 2) {
@@ -687,10 +713,11 @@ function translateArgs(args: string[], cellWidth: number, cellHeight: number, xO
687
713
  for (let x = 0; x < result.length; x += 2) {
688
714
  // Translate from 0-1 to 0-cellWidth
689
715
  result[x] *= cellWidth - (leftPadding * devicePixelRatio) - (rightPadding * devicePixelRatio);
690
- // Ensure coordinate doesn't escape cell bounds and round to the nearest 0.5 to ensure a crisp
691
- // line at 100% devicePixelRatio
716
+ // Round to the nearest 0.5 to ensure a crisp line at 100% devicePixelRatio, and optionally
717
+ // clamp to the cell bounds.
692
718
  if (doClamp && result[x] !== 0) {
693
- result[x] = clamp(Math.round(result[x] + 0.5) - 0.5, cellWidth, 0);
719
+ const rounded = Math.round(result[x] + 0.5) - 0.5;
720
+ result[x] = clampToCell ? clamp(rounded, cellWidth, 0) : rounded;
694
721
  }
695
722
  // Apply the cell's offset (ie. x*cellWidth)
696
723
  result[x] += xOffset + (leftPadding * devicePixelRatio);
@@ -699,10 +726,11 @@ function translateArgs(args: string[], cellWidth: number, cellHeight: number, xO
699
726
  for (let y = 1; y < result.length; y += 2) {
700
727
  // Translate from 0-1 to 0-cellHeight
701
728
  result[y] *= cellHeight;
702
- // Ensure coordinate doesn't escape cell bounds and round to the nearest 0.5 to ensure a crisp
703
- // line at 100% devicePixelRatio
729
+ // Round to the nearest 0.5 to ensure a crisp line at 100% devicePixelRatio, and optionally
730
+ // clamp to the cell bounds.
704
731
  if (doClamp && result[y] !== 0) {
705
- result[y] = clamp(Math.round(result[y] + 0.5) - 0.5, cellHeight, 0);
732
+ const rounded = Math.round(result[y] + 0.5) - 0.5;
733
+ result[y] = clampToCell ? clamp(rounded, cellHeight, 0) : rounded;
706
734
  }
707
735
  // Apply the cell's offset (ie. x*cellHeight)
708
736
  result[y] += yOffset;
@@ -7,7 +7,7 @@ import { ReadonlyColorSet } from 'browser/Types';
7
7
  import { acquireTextureAtlas } from '../CharAtlasCache';
8
8
  import { IRenderDimensions } from 'browser/renderer/shared/Types';
9
9
  import { ICoreBrowserService, IThemeService } from 'browser/services/Services';
10
- import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
10
+ import { Disposable, toDisposable } from 'common/Lifecycle';
11
11
  import { CellData } from 'common/buffer/CellData';
12
12
  import { IOptionsService } from 'common/services/Services';
13
13
  import { Terminal } from '@xterm/xterm';
@@ -57,21 +57,26 @@ declare module '@xterm/addon-webgl' {
57
57
  * unicode ranges:
58
58
  *
59
59
  * - Box Drawing (U+2500-U+257F)
60
- * - Box Elements (U+2580-U+259F)
60
+ * - Block Elements (U+2580-U+259F)
61
61
  * - Braille Patterns (U+2800-U+28FF)
62
- * - Powerline Symbols (U+E0A0U+E0D4)
63
- * - Symbols for Legacy Computing (U+1FB00–U+1FBFF)
62
+ * - Powerline Symbols (U+E0A0-U+E0D4, Private Use Area with widespread
63
+ * adoption)
64
+ * - Progress Indicators (U+EE00-U+EE0B, Private Use Area initially added in
65
+ * [Fira Code](https://github.com/tonsky/FiraCode) and later
66
+ * [Nerd Fonts](https://github.com/ryanoasis/nerd-fonts/pull/1733)).
67
+ * - Git Branch Symbols (U+F5D0-U+F60D, Private Use Area initially adopted
68
+ * in [Kitty in 2024](https://github.com/kovidgoyal/kitty/pull/7681) by
69
+ * author of [vim-flog](https://github.com/rbong/vim-flog))
70
+ * - Symbols for Legacy Computing (U+1FB00-U+1FBFF)
64
71
  *
65
72
  * This will typically result in better rendering with continuous lines,
66
- * even when line height and letter spacing is used. Note that this doesn't
67
- * work with the DOM renderer which renders all characters using the font.
68
- * The default is true.
73
+ * even when line height and letter spacing is used. The default is true.
69
74
  */
70
75
  customGlyphs?: boolean;
71
76
 
72
77
  /**
73
78
  * Whether to enable the preserveDrawingBuffer flag when creating the WebGL
74
- * context. This may be useful in tests. This defaults to false.
79
+ * context. This may be useful in tests. This default is false.
75
80
  */
76
81
  preserveDrawingBuffer?: boolean
77
82
  }