@xterm/addon-webgl 0.20.0-beta.9 → 0.20.0-beta.90

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.
@@ -5,7 +5,7 @@
5
5
 
6
6
  import { throwIfFalsy } from 'browser/renderer/shared/RendererUtils';
7
7
  import { customGlyphDefinitions } from './CustomGlyphDefinitions';
8
- import { CustomGlyphDefinitionType, CustomGlyphVectorType, type CustomGlyphPathDrawFunctionDefinition, type CustomGlyphPatternDefinition, type CustomGlyphRegionDefinition, type ICustomGlyphSolidOctantBlockVector, type ICustomGlyphVectorShape } from './Types';
8
+ import { CustomGlyphDefinitionType, CustomGlyphScaleType, CustomGlyphVectorType, type CustomGlyphDefinitionPart, type CustomGlyphPathDrawFunctionDefinition, type CustomGlyphPatternDefinition, type ICustomGlyphSolidOctantBlockVector, type ICustomGlyphVectorShape } from './Types';
9
9
 
10
10
  /**
11
11
  * Try drawing a custom block element or box drawing character, returning whether it was
@@ -18,48 +18,86 @@ export function tryDrawCustomGlyph(
18
18
  yOffset: number,
19
19
  deviceCellWidth: number,
20
20
  deviceCellHeight: number,
21
+ deviceCharWidth: number,
22
+ deviceCharHeight: number,
21
23
  fontSize: number,
22
24
  devicePixelRatio: number,
23
25
  backgroundColor?: string
24
26
  ): boolean {
25
27
  const unifiedCharDefinition = customGlyphDefinitions[c];
26
28
  if (unifiedCharDefinition) {
27
- switch (unifiedCharDefinition.type) {
28
- case CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR:
29
- drawBlockVectorChar(ctx, unifiedCharDefinition.data, xOffset, yOffset, deviceCellWidth, deviceCellHeight);
30
- return true;
31
- case CustomGlyphDefinitionType.BLOCK_PATTERN:
32
- drawPatternChar(ctx, unifiedCharDefinition.data, xOffset, yOffset, deviceCellWidth, deviceCellHeight);
33
- return true;
34
- case CustomGlyphDefinitionType.BLOCK_PATTERN_WITH_REGION:
35
- drawBlockPatternWithRegion(ctx, unifiedCharDefinition.data, xOffset, yOffset, deviceCellWidth, deviceCellHeight);
36
- return true;
37
- case CustomGlyphDefinitionType.BLOCK_PATTERN_WITH_REGION_AND_SOLID_OCTANT_BLOCK_VECTOR:
38
- drawBlockPatternWithRegion(ctx, unifiedCharDefinition.data.pattern, xOffset, yOffset, deviceCellWidth, deviceCellHeight);
39
- drawBlockVectorChar(ctx, unifiedCharDefinition.data.vectors, xOffset, yOffset, deviceCellWidth, deviceCellHeight);
40
- return true;
41
- case CustomGlyphDefinitionType.BLOCK_PATTERN_WITH_CLIP_PATH:
42
- drawBlockPatternWithClipPath(ctx, unifiedCharDefinition.data, xOffset, yOffset, deviceCellWidth, deviceCellHeight);
43
- return true;
44
- case CustomGlyphDefinitionType.PATH_FUNCTION:
45
- case CustomGlyphDefinitionType.PATH:
46
- drawPathDefinitionCharacter(ctx, unifiedCharDefinition.data, xOffset, yOffset, deviceCellWidth, deviceCellHeight);
47
- return true;
48
- case CustomGlyphDefinitionType.PATH_NEGATIVE:
49
- drawPathNegativeDefinitionCharacter(ctx, unifiedCharDefinition.data, xOffset, yOffset, deviceCellWidth, deviceCellHeight, devicePixelRatio, backgroundColor);
50
- return true;
51
- case CustomGlyphDefinitionType.VECTOR_SHAPE:
52
- drawVectorShape(ctx, unifiedCharDefinition.data, xOffset, yOffset, deviceCellWidth, deviceCellHeight, fontSize, devicePixelRatio);
53
- return true;
54
- case CustomGlyphDefinitionType.PATH_FUNCTION_WITH_WEIGHT:
55
- drawPathDefinitionCharacterWithWeight(ctx, unifiedCharDefinition.data, xOffset, yOffset, deviceCellWidth, deviceCellHeight, devicePixelRatio);
56
- return true;
29
+ // Normalize to array for uniform handling
30
+ const parts = Array.isArray(unifiedCharDefinition) ? unifiedCharDefinition : [unifiedCharDefinition];
31
+ for (const part of parts) {
32
+ drawDefinitionPart(ctx, part, xOffset, yOffset, deviceCellWidth, deviceCellHeight, deviceCharWidth, deviceCharHeight, fontSize, devicePixelRatio, backgroundColor);
57
33
  }
34
+ return true;
58
35
  }
59
36
 
60
37
  return false;
61
38
  }
62
39
 
40
+ function drawDefinitionPart(
41
+ ctx: CanvasRenderingContext2D,
42
+ part: CustomGlyphDefinitionPart,
43
+ xOffset: number,
44
+ yOffset: number,
45
+ deviceCellWidth: number,
46
+ deviceCellHeight: number,
47
+ deviceCharWidth: number,
48
+ deviceCharHeight: number,
49
+ fontSize: number,
50
+ devicePixelRatio: number,
51
+ backgroundColor?: string
52
+ ): void {
53
+ // Handle scaleType - adjust dimensions and offset when scaling to character area
54
+ let drawWidth = deviceCellWidth;
55
+ let drawHeight = deviceCellHeight;
56
+ let drawXOffset = xOffset;
57
+ let drawYOffset = yOffset;
58
+ if (part.scaleType === CustomGlyphScaleType.CHAR) {
59
+ drawWidth = deviceCharWidth;
60
+ drawHeight = deviceCharHeight;
61
+ // Center the character within the cell
62
+ drawXOffset = xOffset + (deviceCellWidth - deviceCharWidth) / 2;
63
+ drawYOffset = yOffset + (deviceCellHeight - deviceCharHeight) / 2;
64
+ }
65
+
66
+ // Handle clipPath generically for any definition type
67
+ if (part.clipPath) {
68
+ ctx.save();
69
+ applyClipPath(ctx, part.clipPath, drawXOffset, drawYOffset, drawWidth, drawHeight);
70
+ }
71
+
72
+ switch (part.type) {
73
+ case CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR:
74
+ drawBlockVectorChar(ctx, part.data, drawXOffset, drawYOffset, drawWidth, drawHeight);
75
+ break;
76
+ case CustomGlyphDefinitionType.BLOCK_PATTERN:
77
+ drawPatternChar(ctx, part.data, drawXOffset, drawYOffset, drawWidth, drawHeight);
78
+ break;
79
+ case CustomGlyphDefinitionType.PATH_FUNCTION:
80
+ drawPathFunctionCharacter(ctx, part.data, drawXOffset, drawYOffset, drawWidth, drawHeight, devicePixelRatio, part.strokeWidth);
81
+ break;
82
+ case CustomGlyphDefinitionType.PATH:
83
+ drawPathDefinitionCharacter(ctx, part.data, drawXOffset, drawYOffset, drawWidth, drawHeight, devicePixelRatio, part.strokeWidth);
84
+ break;
85
+ case CustomGlyphDefinitionType.PATH_NEGATIVE:
86
+ drawPathNegativeDefinitionCharacter(ctx, part.data, drawXOffset, drawYOffset, drawWidth, drawHeight, devicePixelRatio, backgroundColor);
87
+ break;
88
+ case CustomGlyphDefinitionType.VECTOR_SHAPE:
89
+ drawVectorShape(ctx, part.data, drawXOffset, drawYOffset, drawWidth, drawHeight, fontSize, devicePixelRatio);
90
+ break;
91
+ case CustomGlyphDefinitionType.BRAILLE:
92
+ drawBrailleCharacter(ctx, part.data, drawXOffset, drawYOffset, drawWidth, drawHeight);
93
+ break;
94
+ }
95
+
96
+ if (part.clipPath) {
97
+ ctx.restore();
98
+ }
99
+ }
100
+
63
101
  function drawBlockVectorChar(
64
102
  ctx: CanvasRenderingContext2D,
65
103
  charDefinition: ICustomGlyphSolidOctantBlockVector[],
@@ -81,13 +119,61 @@ function drawBlockVectorChar(
81
119
  }
82
120
  }
83
121
 
122
+ /**
123
+ * Braille dot positions in octant coordinates (x, y for center of each dot area)
124
+ * Columns: left=1-2, right=5-6 (leaving 0 and 7 as margins, 3-4 as gap)
125
+ * Rows: 0-1, 2-3, 4-5, 6-7 for the 4 rows
126
+ */
127
+ const brailleDotPositions = new Uint8Array([
128
+ 1, 0, // dot 1 - bit 0
129
+ 1, 2, // dot 2 - bit 1
130
+ 1, 4, // dot 3 - bit 2
131
+ 5, 0, // dot 4 - bit 3
132
+ 5, 2, // dot 5 - bit 4
133
+ 5, 4, // dot 6 - bit 5
134
+ 1, 6, // dot 7 - bit 6
135
+ 5, 6, // dot 8 - bit 7
136
+ ]);
137
+
138
+ /**
139
+ * Draws a braille pattern
140
+ */
141
+ function drawBrailleCharacter(
142
+ ctx: CanvasRenderingContext2D,
143
+ pattern: number,
144
+ xOffset: number,
145
+ yOffset: number,
146
+ deviceCellWidth: number,
147
+ deviceCellHeight: number
148
+ ): void {
149
+ const xEighth = deviceCellWidth / 8;
150
+ const paddingY = deviceCellHeight * 0.1;
151
+ const usableHeight = deviceCellHeight * 0.8;
152
+ const yEighth = usableHeight / 8;
153
+ const radius = Math.min(xEighth, yEighth);
154
+
155
+ for (let bit = 0; bit < 8; bit++) {
156
+ if (pattern & (1 << bit)) {
157
+ const x = brailleDotPositions[bit * 2];
158
+ const y = brailleDotPositions[bit * 2 + 1];
159
+ const cx = xOffset + (x + 1) * xEighth;
160
+ const cy = yOffset + paddingY + (y + 1) * yEighth;
161
+ ctx.beginPath();
162
+ ctx.arc(cx, cy, radius, 0, Math.PI * 2);
163
+ ctx.fill();
164
+ }
165
+ }
166
+ }
167
+
84
168
  function drawPathDefinitionCharacter(
85
169
  ctx: CanvasRenderingContext2D,
86
170
  charDefinition: CustomGlyphPathDrawFunctionDefinition | string,
87
171
  xOffset: number,
88
172
  yOffset: number,
89
173
  deviceCellWidth: number,
90
- deviceCellHeight: number
174
+ deviceCellHeight: number,
175
+ devicePixelRatio: number,
176
+ strokeWidth?: number
91
177
  ): void {
92
178
  const instructions = typeof charDefinition === 'string' ? charDefinition : charDefinition(0, 0);
93
179
  ctx.beginPath();
@@ -188,7 +274,13 @@ function drawPathDefinitionCharacter(
188
274
  }
189
275
  lastCommand = type;
190
276
  }
191
- ctx.fill();
277
+ if (strokeWidth !== undefined) {
278
+ ctx.strokeStyle = ctx.fillStyle;
279
+ ctx.lineWidth = devicePixelRatio * strokeWidth;
280
+ ctx.stroke();
281
+ } else {
282
+ ctx.fill();
283
+ }
192
284
  }
193
285
 
194
286
  /**
@@ -398,139 +490,66 @@ function drawPatternChar(
398
490
  ctx.fillRect(xOffset, yOffset, deviceCellWidth, deviceCellHeight);
399
491
  }
400
492
 
401
- /**
402
- * Draws rectangular shade characters - medium shade pattern clipped to a region.
403
- * Uses a checkerboard pattern that shifts 1px each row (same as medium shade U+2592).
404
- */
405
- function drawBlockPatternWithRegion(
493
+ function drawPathFunctionCharacter(
406
494
  ctx: CanvasRenderingContext2D,
407
- definition: [pattern: CustomGlyphPatternDefinition, region: CustomGlyphRegionDefinition],
408
- xOffset: number,
409
- yOffset: number,
410
- deviceCellWidth: number,
411
- deviceCellHeight: number
412
- ): void {
413
- const [pattern, region] = definition;
414
- const [rx, ry, rw, rh] = region;
415
- const regionX = Math.round(xOffset + rx * deviceCellWidth);
416
- const regionY = Math.round(yOffset + ry * deviceCellHeight);
417
- const regionW = Math.round(rw * deviceCellWidth);
418
- const regionH = Math.round(rh * deviceCellHeight);
419
-
420
- // Save context state
421
- ctx.save();
422
-
423
- // Clip to the region
424
- ctx.beginPath();
425
- ctx.rect(regionX, regionY, regionW, regionH);
426
- ctx.clip();
427
-
428
- // Draw the pattern
429
- drawPatternChar(ctx, pattern, xOffset, yOffset, deviceCellWidth, deviceCellHeight);
430
-
431
- // Restore context state
432
- ctx.restore();
433
- }
434
-
435
- /**
436
- * Draws the following box drawing characters by mapping a subset of SVG d attribute instructions to
437
- * canvas draw calls.
438
- *
439
- * Box styles: ┎┰┒┍┯┑╓╥╖╒╤╕ ┏┳┓┌┲┓┌┬┐┏┱┐
440
- * ┌─┬─┐ ┏━┳━┓ ╔═╦═╗ ┠╂┨┝┿┥╟╫╢╞╪╡ ┡╇┩├╊┫┢╈┪┣╉┤
441
- * │ │ │ ┃ ┃ ┃ ║ ║ ║ ┖┸┚┕┷┙╙╨╜╘╧╛ └┴┘└┺┛┗┻┛┗┹┘
442
- * ├─┼─┤ ┣━╋━┫ ╠═╬═╣ ┏┱┐┌┲┓┌┬┐┌┬┐ ┏┳┓┌┮┓┌┬┐┏┭┐
443
- * │ │ │ ┃ ┃ ┃ ║ ║ ║ ┡╃┤├╄┩├╆┪┢╅┤ ┞╀┦├┾┫┟╁┧┣┽┤
444
- * └─┴─┘ ┗━┻━┛ ╚═╩═╝ └┴┘└┴┘└┺┛┗┹┘ └┴┘└┶┛┗┻┛┗┵┘
445
- *
446
- * Other:
447
- * ╭─╮ ╲ ╱ ╷╻╎╏┆┇┊┋ ╺╾╴ ╌╌╌ ┄┄┄ ┈┈┈
448
- * │ │ ╳ ╽╿╎╏┆┇┊┋ ╶╼╸ ╍╍╍ ┅┅┅ ┉┉┉
449
- * ╰─╯ ╱ ╲ ╹╵╎╏┆┇┊┋
450
- *
451
- * All box drawing characters:
452
- * ─ ━ │ ┃ ┄ ┅ ┆ ┇ ┈ ┉ ┊ ┋ ┌ ┍ ┎ ┏
453
- * ┐ ┑ ┒ ┓ └ ┕ ┖ ┗ ┘ ┙ ┚ ┛ ├ ┝ ┞ ┟
454
- * ┠ ┡ ┢ ┣ ┤ ┥ ┦ ┧ ┨ ┩ ┪ ┫ ┬ ┭ ┮ ┯
455
- * ┰ ┱ ┲ ┳ ┴ ┵ ┶ ┷ ┸ ┹ ┺ ┻ ┼ ┽ ┾ ┿
456
- * ╀ ╁ ╂ ╃ ╄ ╅ ╆ ╇ ╈ ╉ ╊ ╋ ╌ ╍ ╎ ╏
457
- * ═ ║ ╒ ╓ ╔ ╕ ╖ ╗ ╘ ╙ ╚ ╛ ╜ ╝ ╞ ╟
458
- * ╠ ╡ ╢ ╣ ╤ ╥ ╦ ╧ ╨ ╩ ╪ ╫ ╬ ╭ ╮ ╯
459
- * ╰ ╱ ╲ ╳ ╴ ╵ ╶ ╷ ╸ ╹ ╺ ╻ ╼ ╽ ╾ ╿
460
- *
461
- * ---
462
- *
463
- * Box drawing alignment tests: █
464
- * ▉
465
- * ╔══╦══╗ ┌──┬──┐ ╭──┬──╮ ╭──┬──╮ ┏━━┳━━┓ ┎┒┏┑ ╷ ╻ ┏┯┓ ┌┰┐ ▊ ╱╲╱╲╳╳╳
466
- * ║┌─╨─┐║ │╔═╧═╗│ │╒═╪═╕│ │╓─╁─╖│ ┃┌─╂─┐┃ ┗╃╄┙ ╶┼╴╺╋╸┠┼┨ ┝╋┥ ▋ ╲╱╲╱╳╳╳
467
- * ║│╲ ╱│║ │║ ║│ ││ │ ││ │║ ┃ ║│ ┃│ ╿ │┃ ┍╅╆┓ ╵ ╹ ┗┷┛ └┸┘ ▌ ╱╲╱╲╳╳╳
468
- * ╠╡ ╳ ╞╣ ├╢ ╟┤ ├┼─┼─┼┤ ├╫─╂─╫┤ ┣┿╾┼╼┿┫ ┕┛┖┚ ┌┄┄┐ ╎ ┏┅┅┓ ┋ ▍ ╲╱╲╱╳╳╳
469
- * ║│╱ ╲│║ │║ ║│ ││ │ ││ │║ ┃ ║│ ┃│ ╽ │┃ ░░▒▒▓▓██ ┊ ┆ ╎ ╏ ┇ ┋ ▎
470
- * ║└─╥─┘║ │╚═╤═╝│ │╘═╪═╛│ │╙─╀─╜│ ┃└─╂─┘┃ ░░▒▒▓▓██ ┊ ┆ ╎ ╏ ┇ ┋ ▏
471
- * ╚══╩══╝ └──┴──┘ ╰──┴──╯ ╰──┴──╯ ┗━━┻━━┛ └╌╌┘ ╎ ┗╍╍┛ ┋ ▁▂▃▄▅▆▇█
472
- *
473
- * Source: https://www.w3.org/2001/06/utf-8-test/UTF-8-demo.html
474
- */
475
- function drawPathDefinitionCharacterWithWeight(
476
- ctx: CanvasRenderingContext2D,
477
- charDefinition: { [fontWeight: number]: string | ((xp: number, yp: number) => string) },
495
+ charDefinition: string | ((xp: number, yp: number) => string),
478
496
  xOffset: number,
479
497
  yOffset: number,
480
498
  deviceCellWidth: number,
481
499
  deviceCellHeight: number,
482
- devicePixelRatio: number
500
+ devicePixelRatio: number,
501
+ strokeWidth?: number
483
502
  ): void {
484
- ctx.strokeStyle = ctx.fillStyle;
485
- for (const [fontWeight, instructions] of Object.entries(charDefinition)) {
486
- ctx.beginPath();
487
- ctx.lineWidth = devicePixelRatio * Number.parseInt(fontWeight);
488
- let actualInstructions: string;
489
- if (typeof instructions === 'function') {
490
- const xp = .15;
491
- const yp = .15 / deviceCellHeight * deviceCellWidth;
492
- actualInstructions = instructions(xp, yp);
493
- } else {
494
- actualInstructions = instructions;
503
+ ctx.beginPath();
504
+ let actualInstructions: string;
505
+ if (typeof charDefinition === 'function') {
506
+ const xp = .15;
507
+ const yp = .15 / deviceCellHeight * deviceCellWidth;
508
+ actualInstructions = charDefinition(xp, yp);
509
+ } else {
510
+ actualInstructions = charDefinition;
511
+ }
512
+ const state: ISvgPathState = { currentX: 0, currentY: 0, lastControlX: 0, lastControlY: 0, lastCommand: '' };
513
+ for (const instruction of actualInstructions.split(' ')) {
514
+ const type = instruction[0];
515
+ if (type === 'Z') {
516
+ ctx.closePath();
517
+ state.lastCommand = type;
518
+ continue;
495
519
  }
496
- for (const instruction of actualInstructions.split(' ')) {
497
- const type = instruction[0];
498
- if (type === 'Z') {
499
- ctx.closePath();
500
- continue;
501
- }
502
- const f = svgToCanvasInstructionMap[type];
503
- if (!f) {
504
- console.error(`Could not find drawing instructions for "${type}"`);
505
- continue;
506
- }
507
- const args: string[] = instruction.substring(1).split(',');
508
- if (!args[0] || !args[1]) {
509
- continue;
510
- }
511
- f(ctx, translateArgs(args, deviceCellWidth, deviceCellHeight, xOffset, yOffset, true, devicePixelRatio));
520
+ const f = svgToCanvasInstructionMap[type];
521
+ if (!f) {
522
+ console.error(`Could not find drawing instructions for "${type}"`);
523
+ continue;
512
524
  }
525
+ const args: string[] = instruction.substring(1).split(',');
526
+ if (!args[0] || !args[1]) {
527
+ continue;
528
+ }
529
+ f(ctx, translateArgs(args, deviceCellWidth, deviceCellHeight, xOffset, yOffset, true, devicePixelRatio), state);
530
+ state.lastCommand = type;
531
+ }
532
+ if (strokeWidth !== undefined) {
533
+ ctx.strokeStyle = ctx.fillStyle;
534
+ ctx.lineWidth = devicePixelRatio * strokeWidth;
513
535
  ctx.stroke();
514
- ctx.closePath();
536
+ } else {
537
+ ctx.fill();
515
538
  }
539
+ ctx.closePath();
516
540
  }
517
541
 
518
542
  /**
519
- * Draws a pattern clipped to an arbitrary path (for triangular shades, etc.)
543
+ * Applies a clip path to the canvas context from SVG-like path instructions.
520
544
  */
521
- function drawBlockPatternWithClipPath(
545
+ function applyClipPath(
522
546
  ctx: CanvasRenderingContext2D,
523
- definition: [pattern: CustomGlyphPatternDefinition, clipPath: string],
547
+ clipPath: string,
524
548
  xOffset: number,
525
549
  yOffset: number,
526
550
  deviceCellWidth: number,
527
551
  deviceCellHeight: number
528
552
  ): void {
529
- const [pattern, clipPath] = definition;
530
-
531
- ctx.save();
532
-
533
- // Build clip path from SVG-like instructions
534
553
  ctx.beginPath();
535
554
  for (const instruction of clipPath.split(' ')) {
536
555
  const type = instruction[0];
@@ -551,11 +570,6 @@ function drawBlockPatternWithClipPath(
551
570
  }
552
571
  }
553
572
  ctx.clip();
554
-
555
- // Draw the pattern
556
- drawPatternChar(ctx, pattern, xOffset, yOffset, deviceCellWidth, deviceCellHeight);
557
-
558
- ctx.restore();
559
573
  }
560
574
 
561
575
  function drawVectorShape(
@@ -577,10 +591,12 @@ function drawVectorShape(
577
591
  // Scale the stroke with DPR and font size
578
592
  const cssLineWidth = fontSize / 12;
579
593
  ctx.lineWidth = devicePixelRatio * cssLineWidth;
594
+ const state: ISvgPathState = { currentX: 0, currentY: 0, lastControlX: 0, lastControlY: 0, lastCommand: '' };
580
595
  for (const instruction of charDefinition.d.split(' ')) {
581
596
  const type = instruction[0];
582
597
  if (type === 'Z') {
583
598
  ctx.closePath();
599
+ state.lastCommand = type;
584
600
  continue;
585
601
  }
586
602
  const f = svgToCanvasInstructionMap[type];
@@ -602,7 +618,8 @@ function drawVectorShape(
602
618
  devicePixelRatio,
603
619
  (charDefinition.leftPadding ?? 0) * (cssLineWidth / 2),
604
620
  (charDefinition.rightPadding ?? 0) * (cssLineWidth / 2)
605
- ));
621
+ ), state);
622
+ state.lastCommand = type;
606
623
  }
607
624
  if (charDefinition.type === CustomGlyphVectorType.STROKE) {
608
625
  ctx.strokeStyle = ctx.fillStyle;
@@ -617,11 +634,55 @@ function clamp(value: number, max: number, min: number = 0): number {
617
634
  return Math.max(Math.min(value, max), min);
618
635
  }
619
636
 
620
- const svgToCanvasInstructionMap: { [index: string]: any } = {
621
- 'C': (ctx: CanvasRenderingContext2D, args: number[]) => ctx.bezierCurveTo(args[0], args[1], args[2], args[3], args[4], args[5]),
622
- 'L': (ctx: CanvasRenderingContext2D, args: number[]) => ctx.lineTo(args[0], args[1]),
623
- 'M': (ctx: CanvasRenderingContext2D, args: number[]) => ctx.moveTo(args[0], args[1]),
624
- 'Q': (ctx: CanvasRenderingContext2D, args: number[]) => ctx.quadraticCurveTo(args[0], args[1], args[2], args[3])
637
+ interface ISvgPathState {
638
+ currentX: number;
639
+ currentY: number;
640
+ lastControlX: number;
641
+ lastControlY: number;
642
+ lastCommand: string;
643
+ }
644
+
645
+ const svgToCanvasInstructionMap: { [index: string]: (ctx: CanvasRenderingContext2D, args: number[], state: ISvgPathState) => void } = {
646
+ 'C': (ctx, args, state) => {
647
+ ctx.bezierCurveTo(args[0], args[1], args[2], args[3], args[4], args[5]);
648
+ state.lastControlX = args[2];
649
+ state.lastControlY = args[3];
650
+ state.currentX = args[4];
651
+ state.currentY = args[5];
652
+ },
653
+ 'L': (ctx, args, state) => {
654
+ ctx.lineTo(args[0], args[1]);
655
+ state.lastControlX = state.currentX = args[0];
656
+ state.lastControlY = state.currentY = args[1];
657
+ },
658
+ 'M': (ctx, args, state) => {
659
+ ctx.moveTo(args[0], args[1]);
660
+ state.lastControlX = state.currentX = args[0];
661
+ state.lastControlY = state.currentY = args[1];
662
+ },
663
+ 'Q': (ctx, args, state) => {
664
+ ctx.quadraticCurveTo(args[0], args[1], args[2], args[3]);
665
+ state.lastControlX = args[0];
666
+ state.lastControlY = args[1];
667
+ state.currentX = args[2];
668
+ state.currentY = args[3];
669
+ },
670
+ 'T': (ctx, args, state) => {
671
+ let cpX: number;
672
+ let cpY: number;
673
+ if (state.lastCommand === 'Q' || state.lastCommand === 'T') {
674
+ cpX = 2 * state.currentX - state.lastControlX;
675
+ cpY = 2 * state.currentY - state.lastControlY;
676
+ } else {
677
+ cpX = state.currentX;
678
+ cpY = state.currentY;
679
+ }
680
+ ctx.quadraticCurveTo(cpX, cpY, args[0], args[1]);
681
+ state.lastControlX = cpX;
682
+ state.lastControlY = cpY;
683
+ state.currentX = args[0];
684
+ state.currentY = args[1];
685
+ }
625
686
  };
626
687
 
627
688
  function translateArgs(args: string[], cellWidth: number, cellHeight: number, xOffset: number, yOffset: number, doClamp: boolean, devicePixelRatio: number, leftPadding: number = 0, rightPadding: number = 0): number[] {
@@ -33,29 +33,53 @@ export type CustomGlyphPatternDefinition = number[][];
33
33
  export const enum CustomGlyphDefinitionType {
34
34
  SOLID_OCTANT_BLOCK_VECTOR,
35
35
  BLOCK_PATTERN,
36
- BLOCK_PATTERN_WITH_REGION,
37
- BLOCK_PATTERN_WITH_REGION_AND_SOLID_OCTANT_BLOCK_VECTOR,
38
- BLOCK_PATTERN_WITH_CLIP_PATH,
39
36
  PATH_FUNCTION,
40
- PATH_FUNCTION_WITH_WEIGHT,
41
37
  PATH,
42
38
  PATH_NEGATIVE,
43
39
  VECTOR_SHAPE,
40
+ BRAILLE,
44
41
  }
45
42
 
46
- export type CustomGlyphRegionDefinition = [x: number, y: number, w: number, h: number];
47
-
48
- export type CustomGlyphCharacterDefinition = (
43
+ export type CustomGlyphDefinitionPartRaw = (
49
44
  { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: ICustomGlyphSolidOctantBlockVector[] } |
50
45
  { type: CustomGlyphDefinitionType.BLOCK_PATTERN, data: CustomGlyphPatternDefinition } |
51
- { type: CustomGlyphDefinitionType.BLOCK_PATTERN_WITH_REGION, data: [pattern: CustomGlyphPatternDefinition, region: CustomGlyphRegionDefinition] } |
52
- // TODO: Consolidate these, draws should be possible via regions/clipping instead of special
53
- // casing
54
- { type: CustomGlyphDefinitionType.BLOCK_PATTERN_WITH_REGION_AND_SOLID_OCTANT_BLOCK_VECTOR, data: { pattern: [pattern: CustomGlyphPatternDefinition, region: CustomGlyphRegionDefinition], vectors: ICustomGlyphSolidOctantBlockVector[] } } |
55
- { type: CustomGlyphDefinitionType.BLOCK_PATTERN_WITH_CLIP_PATH, data: [pattern: CustomGlyphPatternDefinition, clipPath: string] } |
56
- { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: CustomGlyphPathDrawFunctionDefinition } |
57
- { type: CustomGlyphDefinitionType.PATH_FUNCTION_WITH_WEIGHT, data: { [fontWeight: number]: string | CustomGlyphPathDrawFunctionDefinition } } |
46
+ { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: CustomGlyphPathDrawFunctionDefinition | string } |
58
47
  { type: CustomGlyphDefinitionType.PATH, data: string } |
59
48
  { type: CustomGlyphDefinitionType.PATH_NEGATIVE, data: ICustomGlyphVectorShape } |
60
- { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: ICustomGlyphVectorShape }
49
+ { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: ICustomGlyphVectorShape} |
50
+ { type: CustomGlyphDefinitionType.BRAILLE, data: number }
61
51
  );
52
+
53
+ export const enum CustomGlyphScaleType {
54
+ /**
55
+ * Scale to the entire cell, including letter spacing and line height.
56
+ */
57
+ CELL,
58
+ /**
59
+ * Scale to only the character area, excluding letter spacing and line height.
60
+ */
61
+ CHAR,
62
+ }
63
+
64
+ export interface ICustomGlyphDefinitionCommon {
65
+ /**
66
+ * A custom clip path for the draw definition, restricting the area it can draw to.
67
+ */
68
+ clipPath?: string;
69
+ /**
70
+ * The stroke width to use when drawing the path. Defaults to 1.
71
+ */
72
+ strokeWidth?: number;
73
+ /**
74
+ * Defines how to scale the draw. Defaults to scaling to the full cell including letter spacing
75
+ * and line height.
76
+ */
77
+ scaleType?: CustomGlyphScaleType;
78
+ }
79
+
80
+ export type CustomGlyphDefinitionPart = CustomGlyphDefinitionPartRaw & ICustomGlyphDefinitionCommon;
81
+
82
+ /**
83
+ * A character definition that can be a single part or an array of parts drawn in sequence.
84
+ */
85
+ export type CustomGlyphCharacterDefinition = CustomGlyphDefinitionPart | CustomGlyphDefinitionPart[];
@@ -32,7 +32,7 @@ declare module '@xterm/addon-webgl' {
32
32
  */
33
33
  public readonly onRemoveTextureAtlasCanvas: IEvent<HTMLCanvasElement>;
34
34
 
35
- constructor(preserveDrawingBuffer?: boolean);
35
+ constructor(options?: IWebglAddonOptions);
36
36
 
37
37
  /**
38
38
  * Activates the addon.
@@ -50,4 +50,34 @@ declare module '@xterm/addon-webgl' {
50
50
  */
51
51
  public clearTextureAtlas(): void;
52
52
  }
53
+
54
+ export interface IWebglAddonOptions {
55
+ /**
56
+ * Whether to draw custom glyphs instead of using the font for the following
57
+ * unicode ranges:
58
+ *
59
+ * - Box Drawing (U+2500-U+257F)
60
+ * - Block Elements (U+2580-U+259F)
61
+ * - Braille Patterns (U+2800-U+28FF)
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)
71
+ *
72
+ * This will typically result in better rendering with continuous lines,
73
+ * even when line height and letter spacing is used. The default is true.
74
+ */
75
+ customGlyphs?: boolean;
76
+
77
+ /**
78
+ * Whether to enable the preserveDrawingBuffer flag when creating the WebGL
79
+ * context. This may be useful in tests. This default is false.
80
+ */
81
+ preserveDrawingBuffer?: boolean
82
+ }
53
83
  }