@xterm/addon-webgl 0.19.0 → 0.20.0-beta.10

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.
@@ -0,0 +1,592 @@
1
+ /**
2
+ * Copyright (c) 2021 The xterm.js authors. All rights reserved.
3
+ * @license MIT
4
+ */
5
+
6
+ import { throwIfFalsy } from 'browser/renderer/shared/RendererUtils';
7
+ import { customGlyphDefinitions } from './CustomGlyphDefinitions';
8
+ import { CustomGlyphDefinitionType, CustomGlyphVectorType, type CustomGlyphDefinitionPart, type CustomGlyphPathDrawFunctionDefinition, type CustomGlyphPatternDefinition, type ICustomGlyphSolidOctantBlockVector, type ICustomGlyphVectorShape } from './Types';
9
+
10
+ /**
11
+ * Try drawing a custom block element or box drawing character, returning whether it was
12
+ * successfully drawn.
13
+ */
14
+ export function tryDrawCustomGlyph(
15
+ ctx: CanvasRenderingContext2D,
16
+ c: string,
17
+ xOffset: number,
18
+ yOffset: number,
19
+ deviceCellWidth: number,
20
+ deviceCellHeight: number,
21
+ fontSize: number,
22
+ devicePixelRatio: number,
23
+ backgroundColor?: string
24
+ ): boolean {
25
+ const unifiedCharDefinition = customGlyphDefinitions[c];
26
+ if (unifiedCharDefinition) {
27
+ // Normalize to array for uniform handling
28
+ const parts = Array.isArray(unifiedCharDefinition) ? unifiedCharDefinition : [unifiedCharDefinition];
29
+ for (const part of parts) {
30
+ drawDefinitionPart(ctx, part, xOffset, yOffset, deviceCellWidth, deviceCellHeight, fontSize, devicePixelRatio, backgroundColor);
31
+ }
32
+ return true;
33
+ }
34
+
35
+ return false;
36
+ }
37
+
38
+ function drawDefinitionPart(
39
+ ctx: CanvasRenderingContext2D,
40
+ part: CustomGlyphDefinitionPart,
41
+ xOffset: number,
42
+ yOffset: number,
43
+ deviceCellWidth: number,
44
+ deviceCellHeight: number,
45
+ fontSize: number,
46
+ devicePixelRatio: number,
47
+ backgroundColor?: string
48
+ ): void {
49
+ // Handle clipPath generically for any definition type
50
+ if (part.clipPath) {
51
+ ctx.save();
52
+ applyClipPath(ctx, part.clipPath, xOffset, yOffset, deviceCellWidth, deviceCellHeight);
53
+ }
54
+
55
+ switch (part.type) {
56
+ case CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR:
57
+ drawBlockVectorChar(ctx, part.data, xOffset, yOffset, deviceCellWidth, deviceCellHeight);
58
+ break;
59
+ case CustomGlyphDefinitionType.BLOCK_PATTERN:
60
+ drawPatternChar(ctx, part.data, xOffset, yOffset, deviceCellWidth, deviceCellHeight);
61
+ break;
62
+ case CustomGlyphDefinitionType.PATH_FUNCTION:
63
+ drawPathFunctionCharacter(ctx, part.data, xOffset, yOffset, deviceCellWidth, deviceCellHeight, devicePixelRatio, part.strokeWidth);
64
+ break;
65
+ case CustomGlyphDefinitionType.PATH:
66
+ drawPathDefinitionCharacter(ctx, part.data, xOffset, yOffset, deviceCellWidth, deviceCellHeight);
67
+ break;
68
+ case CustomGlyphDefinitionType.PATH_NEGATIVE:
69
+ drawPathNegativeDefinitionCharacter(ctx, part.data, xOffset, yOffset, deviceCellWidth, deviceCellHeight, devicePixelRatio, backgroundColor);
70
+ break;
71
+ case CustomGlyphDefinitionType.VECTOR_SHAPE:
72
+ drawVectorShape(ctx, part.data, xOffset, yOffset, deviceCellWidth, deviceCellHeight, fontSize, devicePixelRatio);
73
+ break;
74
+ }
75
+
76
+ if (part.clipPath) {
77
+ ctx.restore();
78
+ }
79
+ }
80
+
81
+ function drawBlockVectorChar(
82
+ ctx: CanvasRenderingContext2D,
83
+ charDefinition: ICustomGlyphSolidOctantBlockVector[],
84
+ xOffset: number,
85
+ yOffset: number,
86
+ deviceCellWidth: number,
87
+ deviceCellHeight: number
88
+ ): void {
89
+ for (let i = 0; i < charDefinition.length; i++) {
90
+ const box = charDefinition[i];
91
+ const xEighth = deviceCellWidth / 8;
92
+ const yEighth = deviceCellHeight / 8;
93
+ ctx.fillRect(
94
+ xOffset + box.x * xEighth,
95
+ yOffset + box.y * yEighth,
96
+ box.w * xEighth,
97
+ box.h * yEighth
98
+ );
99
+ }
100
+ }
101
+
102
+ function drawPathDefinitionCharacter(
103
+ ctx: CanvasRenderingContext2D,
104
+ charDefinition: CustomGlyphPathDrawFunctionDefinition | string,
105
+ xOffset: number,
106
+ yOffset: number,
107
+ deviceCellWidth: number,
108
+ deviceCellHeight: number
109
+ ): void {
110
+ const instructions = typeof charDefinition === 'string' ? charDefinition : charDefinition(0, 0);
111
+ ctx.beginPath();
112
+ let currentX = 0;
113
+ let currentY = 0;
114
+ let lastControlX = 0;
115
+ let lastControlY = 0;
116
+ let lastCommand = '';
117
+ for (const instruction of instructions.split(' ')) {
118
+ const type = instruction[0];
119
+ const args: string[] = instruction.substring(1).split(',');
120
+ if (type === 'Z') {
121
+ ctx.closePath();
122
+ lastCommand = type;
123
+ continue;
124
+ }
125
+ if (type === 'V') {
126
+ const y = yOffset + parseFloat(args[0]) * deviceCellHeight;
127
+ ctx.lineTo(currentX, y);
128
+ currentY = y;
129
+ lastControlX = currentX;
130
+ lastControlY = currentY;
131
+ lastCommand = type;
132
+ continue;
133
+ }
134
+ if (type === 'H') {
135
+ const x = xOffset + parseFloat(args[0]) * deviceCellWidth;
136
+ ctx.lineTo(x, currentY);
137
+ currentX = x;
138
+ lastControlX = currentX;
139
+ lastControlY = currentY;
140
+ lastCommand = type;
141
+ continue;
142
+ }
143
+ if (!args[0] || !args[1]) {
144
+ continue;
145
+ }
146
+ if (type === 'A') {
147
+ // SVG arc: A rx,ry,xAxisRotation,largeArcFlag,sweepFlag,x,y
148
+ const rx = parseFloat(args[0]) * deviceCellWidth;
149
+ const ry = parseFloat(args[1]) * deviceCellHeight;
150
+ const xAxisRotation = parseFloat(args[2]) * Math.PI / 180;
151
+ const largeArcFlag = parseInt(args[3]);
152
+ const sweepFlag = parseInt(args[4]);
153
+ const x = xOffset + parseFloat(args[5]) * deviceCellWidth;
154
+ const y = yOffset + parseFloat(args[6]) * deviceCellHeight;
155
+ drawSvgArc(ctx, currentX, currentY, rx, ry, xAxisRotation, largeArcFlag, sweepFlag, x, y);
156
+ currentX = x;
157
+ currentY = y;
158
+ continue;
159
+ }
160
+ const translatedArgs = args.map((e, i) => {
161
+ const val = parseFloat(e);
162
+ return i % 2 === 0
163
+ ? xOffset + val * deviceCellWidth
164
+ : yOffset + val * deviceCellHeight;
165
+ });
166
+ if (type === 'M') {
167
+ ctx.moveTo(translatedArgs[0], translatedArgs[1]);
168
+ currentX = translatedArgs[0];
169
+ currentY = translatedArgs[1];
170
+ lastControlX = currentX;
171
+ lastControlY = currentY;
172
+ } else if (type === 'L') {
173
+ ctx.lineTo(translatedArgs[0], translatedArgs[1]);
174
+ currentX = translatedArgs[0];
175
+ currentY = translatedArgs[1];
176
+ lastControlX = currentX;
177
+ lastControlY = currentY;
178
+ } else if (type === 'Q') {
179
+ ctx.quadraticCurveTo(translatedArgs[0], translatedArgs[1], translatedArgs[2], translatedArgs[3]);
180
+ lastControlX = translatedArgs[0];
181
+ lastControlY = translatedArgs[1];
182
+ currentX = translatedArgs[2];
183
+ currentY = translatedArgs[3];
184
+ } else if (type === 'T') {
185
+ // T uses reflection of last control point if previous command was Q or T
186
+ let cpX: number;
187
+ let cpY: number;
188
+ if (lastCommand === 'Q' || lastCommand === 'T') {
189
+ cpX = 2 * currentX - lastControlX;
190
+ cpY = 2 * currentY - lastControlY;
191
+ } else {
192
+ cpX = currentX;
193
+ cpY = currentY;
194
+ }
195
+ ctx.quadraticCurveTo(cpX, cpY, translatedArgs[0], translatedArgs[1]);
196
+ lastControlX = cpX;
197
+ lastControlY = cpY;
198
+ currentX = translatedArgs[0];
199
+ currentY = translatedArgs[1];
200
+ } else if (type === 'C') {
201
+ ctx.bezierCurveTo(translatedArgs[0], translatedArgs[1], translatedArgs[2], translatedArgs[3], translatedArgs[4], translatedArgs[5]);
202
+ lastControlX = translatedArgs[2];
203
+ lastControlY = translatedArgs[3];
204
+ currentX = translatedArgs[4];
205
+ currentY = translatedArgs[5];
206
+ }
207
+ lastCommand = type;
208
+ }
209
+ ctx.fill();
210
+ }
211
+
212
+ /**
213
+ * Converts SVG arc parameters to canvas arc/ellipse calls.
214
+ * Based on the SVG spec's endpoint to center parameterization conversion.
215
+ */
216
+ function drawSvgArc(
217
+ ctx: CanvasRenderingContext2D,
218
+ x1: number, y1: number,
219
+ rx: number, ry: number,
220
+ phi: number,
221
+ largeArcFlag: number,
222
+ sweepFlag: number,
223
+ x2: number, y2: number
224
+ ): void {
225
+ // Handle degenerate cases
226
+ if (rx === 0 || ry === 0) {
227
+ ctx.lineTo(x2, y2);
228
+ return;
229
+ }
230
+
231
+ rx = Math.abs(rx);
232
+ ry = Math.abs(ry);
233
+
234
+ const cosPhi = Math.cos(phi);
235
+ const sinPhi = Math.sin(phi);
236
+
237
+ // Step 1: Compute (x1', y1')
238
+ const dx = (x1 - x2) / 2;
239
+ const dy = (y1 - y2) / 2;
240
+ const x1p = cosPhi * dx + sinPhi * dy;
241
+ const y1p = -sinPhi * dx + cosPhi * dy;
242
+
243
+ // Step 2: Compute (cx', cy')
244
+ let rxSq = rx * rx;
245
+ let rySq = ry * ry;
246
+ const x1pSq = x1p * x1p;
247
+ const y1pSq = y1p * y1p;
248
+
249
+ // Correct radii if necessary
250
+ const lambda = x1pSq / rxSq + y1pSq / rySq;
251
+ if (lambda > 1) {
252
+ const lambdaSqrt = Math.sqrt(lambda);
253
+ rx *= lambdaSqrt;
254
+ ry *= lambdaSqrt;
255
+ rxSq = rx * rx;
256
+ rySq = ry * ry;
257
+ }
258
+
259
+ let sq = (rxSq * rySq - rxSq * y1pSq - rySq * x1pSq) / (rxSq * y1pSq + rySq * x1pSq);
260
+ if (sq < 0) sq = 0;
261
+ const coef = (largeArcFlag === sweepFlag ? -1 : 1) * Math.sqrt(sq);
262
+ const cxp = coef * (rx * y1p / ry);
263
+ const cyp = coef * -(ry * x1p / rx);
264
+
265
+ // Step 3: Compute (cx, cy) from (cx', cy')
266
+ const cx = cosPhi * cxp - sinPhi * cyp + (x1 + x2) / 2;
267
+ const cy = sinPhi * cxp + cosPhi * cyp + (y1 + y2) / 2;
268
+
269
+ // Step 4: Compute angles
270
+ const ux = (x1p - cxp) / rx;
271
+ const uy = (y1p - cyp) / ry;
272
+ const vx = (-x1p - cxp) / rx;
273
+ const vy = (-y1p - cyp) / ry;
274
+
275
+ const startAngle = Math.atan2(uy, ux);
276
+ let dTheta = Math.atan2(vy, vx) - startAngle;
277
+
278
+ if (sweepFlag === 0 && dTheta > 0) {
279
+ dTheta -= 2 * Math.PI;
280
+ } else if (sweepFlag === 1 && dTheta < 0) {
281
+ dTheta += 2 * Math.PI;
282
+ }
283
+
284
+ const endAngle = startAngle + dTheta;
285
+
286
+ ctx.ellipse(cx, cy, rx, ry, phi, startAngle, endAngle, sweepFlag === 0);
287
+ }
288
+
289
+ /**
290
+ * Draws a "negative" path where the background color is used to draw the shape on top of a
291
+ * foreground-filled cell. This creates the appearance of a cutout without using actual
292
+ * transparency, which allows SPAA (subpixel anti-aliasing) to work correctly.
293
+ *
294
+ * @param ctx The canvas rendering context (fillStyle should be set to foreground color)
295
+ * @param charDefinition The vector shape definition for the negative shape
296
+ * @param xOffset The x offset to draw at
297
+ * @param yOffset The y offset to draw at
298
+ * @param deviceCellWidth The width of the cell in device pixels
299
+ * @param deviceCellHeight The height of the cell in device pixels
300
+ * @param devicePixelRatio The device pixel ratio
301
+ * @param backgroundColor The background color to use for the "cutout" portion
302
+ */
303
+ function drawPathNegativeDefinitionCharacter(
304
+ ctx: CanvasRenderingContext2D,
305
+ charDefinition: ICustomGlyphVectorShape,
306
+ xOffset: number,
307
+ yOffset: number,
308
+ deviceCellWidth: number,
309
+ deviceCellHeight: number,
310
+ devicePixelRatio: number,
311
+ backgroundColor?: string
312
+ ): void {
313
+ ctx.save();
314
+
315
+ // First, fill the entire cell with foreground color
316
+ ctx.fillRect(xOffset, yOffset, deviceCellWidth, deviceCellHeight);
317
+
318
+ // Then draw the "negative" shape with the background color
319
+ if (backgroundColor) {
320
+ ctx.fillStyle = backgroundColor;
321
+ ctx.strokeStyle = backgroundColor;
322
+ }
323
+
324
+ ctx.lineWidth = devicePixelRatio;
325
+ ctx.lineCap = 'square';
326
+ ctx.beginPath();
327
+ for (const instruction of charDefinition.d.split(' ')) {
328
+ const type = instruction[0];
329
+ const args: string[] = instruction.substring(1).split(',');
330
+ if (!args[0] || !args[1]) {
331
+ if (type === 'Z') {
332
+ ctx.closePath();
333
+ }
334
+ continue;
335
+ }
336
+ const translatedArgs = args.map((e, i) => {
337
+ const val = parseFloat(e);
338
+ return i % 2 === 0
339
+ ? xOffset + val * deviceCellWidth
340
+ : yOffset + val * deviceCellHeight;
341
+ });
342
+ if (type === 'M') {
343
+ ctx.moveTo(translatedArgs[0], translatedArgs[1]);
344
+ } else if (type === 'L') {
345
+ ctx.lineTo(translatedArgs[0], translatedArgs[1]);
346
+ }
347
+ }
348
+
349
+ if (charDefinition.type === CustomGlyphVectorType.STROKE) {
350
+ ctx.stroke();
351
+ } else {
352
+ ctx.fill();
353
+ }
354
+
355
+ ctx.restore();
356
+ }
357
+
358
+ const cachedPatterns: Map<CustomGlyphPatternDefinition, Map</* fillStyle */string, CanvasPattern>> = new Map();
359
+
360
+ function drawPatternChar(
361
+ ctx: CanvasRenderingContext2D,
362
+ charDefinition: number[][],
363
+ xOffset: number,
364
+ yOffset: number,
365
+ deviceCellWidth: number,
366
+ deviceCellHeight: number
367
+ ): void {
368
+ let patternSet = cachedPatterns.get(charDefinition);
369
+ if (!patternSet) {
370
+ patternSet = new Map();
371
+ cachedPatterns.set(charDefinition, patternSet);
372
+ }
373
+ const fillStyle = ctx.fillStyle;
374
+ if (typeof fillStyle !== 'string') {
375
+ throw new Error(`Unexpected fillStyle type "${fillStyle}"`);
376
+ }
377
+ let pattern = patternSet.get(fillStyle);
378
+ if (!pattern) {
379
+ const width = charDefinition[0].length;
380
+ const height = charDefinition.length;
381
+ const tmpCanvas = ctx.canvas.ownerDocument.createElement('canvas');
382
+ tmpCanvas.width = width;
383
+ tmpCanvas.height = height;
384
+ const tmpCtx = throwIfFalsy(tmpCanvas.getContext('2d'));
385
+ const imageData = new ImageData(width, height);
386
+
387
+ // Extract rgba from fillStyle
388
+ let r: number;
389
+ let g: number;
390
+ let b: number;
391
+ let a: number;
392
+ if (fillStyle.startsWith('#')) {
393
+ r = parseInt(fillStyle.slice(1, 3), 16);
394
+ g = parseInt(fillStyle.slice(3, 5), 16);
395
+ b = parseInt(fillStyle.slice(5, 7), 16);
396
+ a = fillStyle.length > 7 && parseInt(fillStyle.slice(7, 9), 16) || 1;
397
+ } else if (fillStyle.startsWith('rgba')) {
398
+ ([r, g, b, a] = fillStyle.substring(5, fillStyle.length - 1).split(',').map(e => parseFloat(e)));
399
+ } else {
400
+ throw new Error(`Unexpected fillStyle color format "${fillStyle}" when drawing pattern glyph`);
401
+ }
402
+
403
+ for (let y = 0; y < height; y++) {
404
+ for (let x = 0; x < width; x++) {
405
+ imageData.data[(y * width + x) * 4 ] = r;
406
+ imageData.data[(y * width + x) * 4 + 1] = g;
407
+ imageData.data[(y * width + x) * 4 + 2] = b;
408
+ imageData.data[(y * width + x) * 4 + 3] = charDefinition[y][x] * (a * 255);
409
+ }
410
+ }
411
+ tmpCtx.putImageData(imageData, 0, 0);
412
+ pattern = throwIfFalsy(ctx.createPattern(tmpCanvas, null));
413
+ patternSet.set(fillStyle, pattern);
414
+ }
415
+ ctx.fillStyle = pattern;
416
+ ctx.fillRect(xOffset, yOffset, deviceCellWidth, deviceCellHeight);
417
+ }
418
+
419
+ function drawPathFunctionCharacter(
420
+ ctx: CanvasRenderingContext2D,
421
+ charDefinition: string | ((xp: number, yp: number) => string),
422
+ xOffset: number,
423
+ yOffset: number,
424
+ deviceCellWidth: number,
425
+ deviceCellHeight: number,
426
+ devicePixelRatio: number,
427
+ strokeWidth?: number
428
+ ): void {
429
+ ctx.strokeStyle = ctx.fillStyle;
430
+ ctx.beginPath();
431
+ ctx.lineWidth = devicePixelRatio * (strokeWidth ?? 1);
432
+ let actualInstructions: string;
433
+ if (typeof charDefinition === 'function') {
434
+ const xp = .15;
435
+ const yp = .15 / deviceCellHeight * deviceCellWidth;
436
+ actualInstructions = charDefinition(xp, yp);
437
+ } else {
438
+ actualInstructions = charDefinition;
439
+ }
440
+ for (const instruction of actualInstructions.split(' ')) {
441
+ const type = instruction[0];
442
+ if (type === 'Z') {
443
+ ctx.closePath();
444
+ continue;
445
+ }
446
+ const f = svgToCanvasInstructionMap[type];
447
+ if (!f) {
448
+ console.error(`Could not find drawing instructions for "${type}"`);
449
+ continue;
450
+ }
451
+ const args: string[] = instruction.substring(1).split(',');
452
+ if (!args[0] || !args[1]) {
453
+ continue;
454
+ }
455
+ f(ctx, translateArgs(args, deviceCellWidth, deviceCellHeight, xOffset, yOffset, true, devicePixelRatio));
456
+ }
457
+ ctx.stroke();
458
+ ctx.closePath();
459
+ }
460
+
461
+ /**
462
+ * Applies a clip path to the canvas context from SVG-like path instructions.
463
+ */
464
+ function applyClipPath(
465
+ ctx: CanvasRenderingContext2D,
466
+ clipPath: string,
467
+ xOffset: number,
468
+ yOffset: number,
469
+ deviceCellWidth: number,
470
+ deviceCellHeight: number
471
+ ): void {
472
+ ctx.beginPath();
473
+ for (const instruction of clipPath.split(' ')) {
474
+ const type = instruction[0];
475
+ if (type === 'Z') {
476
+ ctx.closePath();
477
+ continue;
478
+ }
479
+ const args: string[] = instruction.substring(1).split(',');
480
+ if (!args[0] || !args[1]) {
481
+ continue;
482
+ }
483
+ const x = xOffset + parseFloat(args[0]) * deviceCellWidth;
484
+ const y = yOffset + parseFloat(args[1]) * deviceCellHeight;
485
+ if (type === 'M') {
486
+ ctx.moveTo(x, y);
487
+ } else if (type === 'L') {
488
+ ctx.lineTo(x, y);
489
+ }
490
+ }
491
+ ctx.clip();
492
+ }
493
+
494
+ function drawVectorShape(
495
+ ctx: CanvasRenderingContext2D,
496
+ charDefinition: ICustomGlyphVectorShape,
497
+ xOffset: number,
498
+ yOffset: number,
499
+ deviceCellWidth: number,
500
+ deviceCellHeight: number,
501
+ fontSize: number,
502
+ devicePixelRatio: number
503
+ ): void {
504
+ // Clip the cell to make sure drawing doesn't occur beyond bounds
505
+ const clipRegion = new Path2D();
506
+ clipRegion.rect(xOffset, yOffset, deviceCellWidth, deviceCellHeight);
507
+ ctx.clip(clipRegion);
508
+
509
+ ctx.beginPath();
510
+ // Scale the stroke with DPR and font size
511
+ const cssLineWidth = fontSize / 12;
512
+ ctx.lineWidth = devicePixelRatio * cssLineWidth;
513
+ for (const instruction of charDefinition.d.split(' ')) {
514
+ const type = instruction[0];
515
+ if (type === 'Z') {
516
+ ctx.closePath();
517
+ continue;
518
+ }
519
+ const f = svgToCanvasInstructionMap[type];
520
+ if (!f) {
521
+ console.error(`Could not find drawing instructions for "${type}"`);
522
+ continue;
523
+ }
524
+ const args: string[] = instruction.substring(1).split(',');
525
+ if (!args[0] || !args[1]) {
526
+ continue;
527
+ }
528
+ f(ctx, translateArgs(
529
+ args,
530
+ deviceCellWidth,
531
+ deviceCellHeight,
532
+ xOffset,
533
+ yOffset,
534
+ false,
535
+ devicePixelRatio,
536
+ (charDefinition.leftPadding ?? 0) * (cssLineWidth / 2),
537
+ (charDefinition.rightPadding ?? 0) * (cssLineWidth / 2)
538
+ ));
539
+ }
540
+ if (charDefinition.type === CustomGlyphVectorType.STROKE) {
541
+ ctx.strokeStyle = ctx.fillStyle;
542
+ ctx.stroke();
543
+ } else {
544
+ ctx.fill();
545
+ }
546
+ ctx.closePath();
547
+ }
548
+
549
+ function clamp(value: number, max: number, min: number = 0): number {
550
+ return Math.max(Math.min(value, max), min);
551
+ }
552
+
553
+ const svgToCanvasInstructionMap: { [index: string]: any } = {
554
+ 'C': (ctx: CanvasRenderingContext2D, args: number[]) => ctx.bezierCurveTo(args[0], args[1], args[2], args[3], args[4], args[5]),
555
+ 'L': (ctx: CanvasRenderingContext2D, args: number[]) => ctx.lineTo(args[0], args[1]),
556
+ 'M': (ctx: CanvasRenderingContext2D, args: number[]) => ctx.moveTo(args[0], args[1]),
557
+ 'Q': (ctx: CanvasRenderingContext2D, args: number[]) => ctx.quadraticCurveTo(args[0], args[1], args[2], args[3])
558
+ };
559
+
560
+ function translateArgs(args: string[], cellWidth: number, cellHeight: number, xOffset: number, yOffset: number, doClamp: boolean, devicePixelRatio: number, leftPadding: number = 0, rightPadding: number = 0): number[] {
561
+ const result = args.map(e => parseFloat(e) || parseInt(e));
562
+
563
+ if (result.length < 2) {
564
+ throw new Error('Too few arguments for instruction');
565
+ }
566
+
567
+ for (let x = 0; x < result.length; x += 2) {
568
+ // Translate from 0-1 to 0-cellWidth
569
+ result[x] *= cellWidth - (leftPadding * devicePixelRatio) - (rightPadding * devicePixelRatio);
570
+ // Ensure coordinate doesn't escape cell bounds and round to the nearest 0.5 to ensure a crisp
571
+ // line at 100% devicePixelRatio
572
+ if (doClamp && result[x] !== 0) {
573
+ result[x] = clamp(Math.round(result[x] + 0.5) - 0.5, cellWidth, 0);
574
+ }
575
+ // Apply the cell's offset (ie. x*cellWidth)
576
+ result[x] += xOffset + (leftPadding * devicePixelRatio);
577
+ }
578
+
579
+ for (let y = 1; y < result.length; y += 2) {
580
+ // Translate from 0-1 to 0-cellHeight
581
+ result[y] *= cellHeight;
582
+ // Ensure coordinate doesn't escape cell bounds and round to the nearest 0.5 to ensure a crisp
583
+ // line at 100% devicePixelRatio
584
+ if (doClamp && result[y] !== 0) {
585
+ result[y] = clamp(Math.round(result[y] + 0.5) - 0.5, cellHeight, 0);
586
+ }
587
+ // Apply the cell's offset (ie. x*cellHeight)
588
+ result[y] += yOffset;
589
+ }
590
+
591
+ return result;
592
+ }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Copyright (c) 2021 The xterm.js authors. All rights reserved.
3
+ * @license MIT
4
+ */
5
+
6
+ export interface ICustomGlyphSolidOctantBlockVector {
7
+ x: number;
8
+ y: number;
9
+ w: number;
10
+ h: number;
11
+ }
12
+
13
+ /**
14
+ * @param xp The percentage of 15% of the x axis.
15
+ * @param yp The percentage of 15% of the x axis on the y axis.
16
+ */
17
+ export type CustomGlyphPathDrawFunctionDefinition = (xp: number, yp: number) => string;
18
+
19
+ export interface ICustomGlyphVectorShape {
20
+ d: string;
21
+ type: CustomGlyphVectorType;
22
+ leftPadding?: number;
23
+ rightPadding?: number;
24
+ }
25
+
26
+ export const enum CustomGlyphVectorType {
27
+ FILL,
28
+ STROKE
29
+ }
30
+
31
+ export type CustomGlyphPatternDefinition = number[][];
32
+
33
+ export const enum CustomGlyphDefinitionType {
34
+ SOLID_OCTANT_BLOCK_VECTOR,
35
+ BLOCK_PATTERN,
36
+ PATH_FUNCTION,
37
+ PATH,
38
+ PATH_NEGATIVE,
39
+ VECTOR_SHAPE,
40
+ }
41
+
42
+ export type CustomGlyphDefinitionPartRaw = (
43
+ { type: CustomGlyphDefinitionType.SOLID_OCTANT_BLOCK_VECTOR, data: ICustomGlyphSolidOctantBlockVector[] } |
44
+ { type: CustomGlyphDefinitionType.BLOCK_PATTERN, data: CustomGlyphPatternDefinition } |
45
+ { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: CustomGlyphPathDrawFunctionDefinition | string } |
46
+ { type: CustomGlyphDefinitionType.PATH, data: string } |
47
+ { type: CustomGlyphDefinitionType.PATH_NEGATIVE, data: ICustomGlyphVectorShape } |
48
+ { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: ICustomGlyphVectorShape}
49
+ );
50
+
51
+ export interface ICustomGlyphDefinitionCommon {
52
+ /**
53
+ * A custom clip path for the draw definition, restricting the area it can draw to.
54
+ */
55
+ clipPath?: string;
56
+ /**
57
+ * The stroke width to use when drawing the path. Defaults to 1.
58
+ */
59
+ strokeWidth?: number;
60
+ }
61
+
62
+ export type CustomGlyphDefinitionPart = CustomGlyphDefinitionPartRaw & ICustomGlyphDefinitionCommon;
63
+
64
+ /**
65
+ * A character definition that can be a single part or an array of parts drawn in sequence.
66
+ */
67
+ export type CustomGlyphCharacterDefinition = CustomGlyphDefinitionPart | CustomGlyphDefinitionPart[];