@xterm/addon-webgl 0.20.0-beta.5 → 0.20.0-beta.6

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.
@@ -19,7 +19,8 @@ export function tryDrawCustomGlyph(
19
19
  deviceCellWidth: number,
20
20
  deviceCellHeight: number,
21
21
  fontSize: number,
22
- devicePixelRatio: number
22
+ devicePixelRatio: number,
23
+ backgroundColor?: string
23
24
  ): boolean {
24
25
  const unifiedCharDefinition = customGlyphDefinitions[c];
25
26
  if (unifiedCharDefinition) {
@@ -37,10 +38,16 @@ export function tryDrawCustomGlyph(
37
38
  drawBlockPatternWithRegion(ctx, unifiedCharDefinition.data.pattern, xOffset, yOffset, deviceCellWidth, deviceCellHeight);
38
39
  drawBlockVectorChar(ctx, unifiedCharDefinition.data.vectors, xOffset, yOffset, deviceCellWidth, deviceCellHeight);
39
40
  return true;
41
+ case CustomGlyphDefinitionType.BLOCK_PATTERN_WITH_CLIP_PATH:
42
+ drawBlockPatternWithClipPath(ctx, unifiedCharDefinition.data, xOffset, yOffset, deviceCellWidth, deviceCellHeight);
43
+ return true;
40
44
  case CustomGlyphDefinitionType.PATH_FUNCTION:
41
45
  case CustomGlyphDefinitionType.PATH:
42
46
  drawPathDefinitionCharacter(ctx, unifiedCharDefinition.data, xOffset, yOffset, deviceCellWidth, deviceCellHeight);
43
47
  return true;
48
+ case CustomGlyphDefinitionType.PATH_NEGATIVE:
49
+ drawPathNegativeDefinitionCharacter(ctx, unifiedCharDefinition.data, xOffset, yOffset, deviceCellWidth, deviceCellHeight, devicePixelRatio, backgroundColor);
50
+ return true;
44
51
  case CustomGlyphDefinitionType.VECTOR_SHAPE:
45
52
  drawVectorShape(ctx, unifiedCharDefinition.data, xOffset, yOffset, deviceCellWidth, deviceCellHeight, fontSize, devicePixelRatio);
46
53
  return true;
@@ -84,7 +91,222 @@ function drawPathDefinitionCharacter(
84
91
  ): void {
85
92
  const instructions = typeof charDefinition === 'string' ? charDefinition : charDefinition(0, 0);
86
93
  ctx.beginPath();
94
+ let currentX = 0;
95
+ let currentY = 0;
96
+ let lastControlX = 0;
97
+ let lastControlY = 0;
98
+ let lastCommand = '';
87
99
  for (const instruction of instructions.split(' ')) {
100
+ const type = instruction[0];
101
+ const args: string[] = instruction.substring(1).split(',');
102
+ if (type === 'Z') {
103
+ ctx.closePath();
104
+ lastCommand = type;
105
+ continue;
106
+ }
107
+ if (type === 'V') {
108
+ const y = yOffset + parseFloat(args[0]) * deviceCellHeight;
109
+ ctx.lineTo(currentX, y);
110
+ currentY = y;
111
+ lastControlX = currentX;
112
+ lastControlY = currentY;
113
+ lastCommand = type;
114
+ continue;
115
+ }
116
+ if (type === 'H') {
117
+ const x = xOffset + parseFloat(args[0]) * deviceCellWidth;
118
+ ctx.lineTo(x, currentY);
119
+ currentX = x;
120
+ lastControlX = currentX;
121
+ lastControlY = currentY;
122
+ lastCommand = type;
123
+ continue;
124
+ }
125
+ if (!args[0] || !args[1]) {
126
+ continue;
127
+ }
128
+ if (type === 'A') {
129
+ // SVG arc: A rx,ry,xAxisRotation,largeArcFlag,sweepFlag,x,y
130
+ const rx = parseFloat(args[0]) * deviceCellWidth;
131
+ const ry = parseFloat(args[1]) * deviceCellHeight;
132
+ const xAxisRotation = parseFloat(args[2]) * Math.PI / 180;
133
+ const largeArcFlag = parseInt(args[3]);
134
+ const sweepFlag = parseInt(args[4]);
135
+ const x = xOffset + parseFloat(args[5]) * deviceCellWidth;
136
+ const y = yOffset + parseFloat(args[6]) * deviceCellHeight;
137
+ drawSvgArc(ctx, currentX, currentY, rx, ry, xAxisRotation, largeArcFlag, sweepFlag, x, y);
138
+ currentX = x;
139
+ currentY = y;
140
+ continue;
141
+ }
142
+ const translatedArgs = args.map((e, i) => {
143
+ const val = parseFloat(e);
144
+ return i % 2 === 0
145
+ ? xOffset + val * deviceCellWidth
146
+ : yOffset + val * deviceCellHeight;
147
+ });
148
+ if (type === 'M') {
149
+ ctx.moveTo(translatedArgs[0], translatedArgs[1]);
150
+ currentX = translatedArgs[0];
151
+ currentY = translatedArgs[1];
152
+ lastControlX = currentX;
153
+ lastControlY = currentY;
154
+ } else if (type === 'L') {
155
+ ctx.lineTo(translatedArgs[0], translatedArgs[1]);
156
+ currentX = translatedArgs[0];
157
+ currentY = translatedArgs[1];
158
+ lastControlX = currentX;
159
+ lastControlY = currentY;
160
+ } else if (type === 'Q') {
161
+ ctx.quadraticCurveTo(translatedArgs[0], translatedArgs[1], translatedArgs[2], translatedArgs[3]);
162
+ lastControlX = translatedArgs[0];
163
+ lastControlY = translatedArgs[1];
164
+ currentX = translatedArgs[2];
165
+ currentY = translatedArgs[3];
166
+ } else if (type === 'T') {
167
+ // T uses reflection of last control point if previous command was Q or T
168
+ let cpX: number;
169
+ let cpY: number;
170
+ if (lastCommand === 'Q' || lastCommand === 'T') {
171
+ cpX = 2 * currentX - lastControlX;
172
+ cpY = 2 * currentY - lastControlY;
173
+ } else {
174
+ cpX = currentX;
175
+ cpY = currentY;
176
+ }
177
+ ctx.quadraticCurveTo(cpX, cpY, translatedArgs[0], translatedArgs[1]);
178
+ lastControlX = cpX;
179
+ lastControlY = cpY;
180
+ currentX = translatedArgs[0];
181
+ currentY = translatedArgs[1];
182
+ } else if (type === 'C') {
183
+ ctx.bezierCurveTo(translatedArgs[0], translatedArgs[1], translatedArgs[2], translatedArgs[3], translatedArgs[4], translatedArgs[5]);
184
+ lastControlX = translatedArgs[2];
185
+ lastControlY = translatedArgs[3];
186
+ currentX = translatedArgs[4];
187
+ currentY = translatedArgs[5];
188
+ }
189
+ lastCommand = type;
190
+ }
191
+ ctx.fill();
192
+ }
193
+
194
+ /**
195
+ * Converts SVG arc parameters to canvas arc/ellipse calls.
196
+ * Based on the SVG spec's endpoint to center parameterization conversion.
197
+ */
198
+ function drawSvgArc(
199
+ ctx: CanvasRenderingContext2D,
200
+ x1: number, y1: number,
201
+ rx: number, ry: number,
202
+ phi: number,
203
+ largeArcFlag: number,
204
+ sweepFlag: number,
205
+ x2: number, y2: number
206
+ ): void {
207
+ // Handle degenerate cases
208
+ if (rx === 0 || ry === 0) {
209
+ ctx.lineTo(x2, y2);
210
+ return;
211
+ }
212
+
213
+ rx = Math.abs(rx);
214
+ ry = Math.abs(ry);
215
+
216
+ const cosPhi = Math.cos(phi);
217
+ const sinPhi = Math.sin(phi);
218
+
219
+ // Step 1: Compute (x1', y1')
220
+ const dx = (x1 - x2) / 2;
221
+ const dy = (y1 - y2) / 2;
222
+ const x1p = cosPhi * dx + sinPhi * dy;
223
+ const y1p = -sinPhi * dx + cosPhi * dy;
224
+
225
+ // Step 2: Compute (cx', cy')
226
+ let rxSq = rx * rx;
227
+ let rySq = ry * ry;
228
+ const x1pSq = x1p * x1p;
229
+ const y1pSq = y1p * y1p;
230
+
231
+ // Correct radii if necessary
232
+ const lambda = x1pSq / rxSq + y1pSq / rySq;
233
+ if (lambda > 1) {
234
+ const lambdaSqrt = Math.sqrt(lambda);
235
+ rx *= lambdaSqrt;
236
+ ry *= lambdaSqrt;
237
+ rxSq = rx * rx;
238
+ rySq = ry * ry;
239
+ }
240
+
241
+ let sq = (rxSq * rySq - rxSq * y1pSq - rySq * x1pSq) / (rxSq * y1pSq + rySq * x1pSq);
242
+ if (sq < 0) sq = 0;
243
+ const coef = (largeArcFlag === sweepFlag ? -1 : 1) * Math.sqrt(sq);
244
+ const cxp = coef * (rx * y1p / ry);
245
+ const cyp = coef * -(ry * x1p / rx);
246
+
247
+ // Step 3: Compute (cx, cy) from (cx', cy')
248
+ const cx = cosPhi * cxp - sinPhi * cyp + (x1 + x2) / 2;
249
+ const cy = sinPhi * cxp + cosPhi * cyp + (y1 + y2) / 2;
250
+
251
+ // Step 4: Compute angles
252
+ const ux = (x1p - cxp) / rx;
253
+ const uy = (y1p - cyp) / ry;
254
+ const vx = (-x1p - cxp) / rx;
255
+ const vy = (-y1p - cyp) / ry;
256
+
257
+ const startAngle = Math.atan2(uy, ux);
258
+ let dTheta = Math.atan2(vy, vx) - startAngle;
259
+
260
+ if (sweepFlag === 0 && dTheta > 0) {
261
+ dTheta -= 2 * Math.PI;
262
+ } else if (sweepFlag === 1 && dTheta < 0) {
263
+ dTheta += 2 * Math.PI;
264
+ }
265
+
266
+ const endAngle = startAngle + dTheta;
267
+
268
+ ctx.ellipse(cx, cy, rx, ry, phi, startAngle, endAngle, sweepFlag === 0);
269
+ }
270
+
271
+ /**
272
+ * Draws a "negative" path where the background color is used to draw the shape on top of a
273
+ * foreground-filled cell. This creates the appearance of a cutout without using actual
274
+ * transparency, which allows SPAA (subpixel anti-aliasing) to work correctly.
275
+ *
276
+ * @param ctx The canvas rendering context (fillStyle should be set to foreground color)
277
+ * @param charDefinition The vector shape definition for the negative shape
278
+ * @param xOffset The x offset to draw at
279
+ * @param yOffset The y offset to draw at
280
+ * @param deviceCellWidth The width of the cell in device pixels
281
+ * @param deviceCellHeight The height of the cell in device pixels
282
+ * @param devicePixelRatio The device pixel ratio
283
+ * @param backgroundColor The background color to use for the "cutout" portion
284
+ */
285
+ function drawPathNegativeDefinitionCharacter(
286
+ ctx: CanvasRenderingContext2D,
287
+ charDefinition: ICustomGlyphVectorShape,
288
+ xOffset: number,
289
+ yOffset: number,
290
+ deviceCellWidth: number,
291
+ deviceCellHeight: number,
292
+ devicePixelRatio: number,
293
+ backgroundColor?: string
294
+ ): void {
295
+ ctx.save();
296
+
297
+ // First, fill the entire cell with foreground color
298
+ ctx.fillRect(xOffset, yOffset, deviceCellWidth, deviceCellHeight);
299
+
300
+ // Then draw the "negative" shape with the background color
301
+ if (backgroundColor) {
302
+ ctx.fillStyle = backgroundColor;
303
+ ctx.strokeStyle = backgroundColor;
304
+ }
305
+
306
+ ctx.lineWidth = devicePixelRatio;
307
+ ctx.lineCap = 'square';
308
+ ctx.beginPath();
309
+ for (const instruction of charDefinition.d.split(' ')) {
88
310
  const type = instruction[0];
89
311
  const args: string[] = instruction.substring(1).split(',');
90
312
  if (!args[0] || !args[1]) {
@@ -105,7 +327,14 @@ function drawPathDefinitionCharacter(
105
327
  ctx.lineTo(translatedArgs[0], translatedArgs[1]);
106
328
  }
107
329
  }
108
- ctx.fill();
330
+
331
+ if (charDefinition.type === CustomGlyphVectorType.STROKE) {
332
+ ctx.stroke();
333
+ } else {
334
+ ctx.fill();
335
+ }
336
+
337
+ ctx.restore();
109
338
  }
110
339
 
111
340
  const cachedPatterns: Map<CustomGlyphPatternDefinition, Map</* fillStyle */string, CanvasPattern>> = new Map();
@@ -266,6 +495,10 @@ function drawPathDefinitionCharacterWithWeight(
266
495
  }
267
496
  for (const instruction of actualInstructions.split(' ')) {
268
497
  const type = instruction[0];
498
+ if (type === 'Z') {
499
+ ctx.closePath();
500
+ continue;
501
+ }
269
502
  const f = svgToCanvasInstructionMap[type];
270
503
  if (!f) {
271
504
  console.error(`Could not find drawing instructions for "${type}"`);
@@ -282,6 +515,49 @@ function drawPathDefinitionCharacterWithWeight(
282
515
  }
283
516
  }
284
517
 
518
+ /**
519
+ * Draws a pattern clipped to an arbitrary path (for triangular shades, etc.)
520
+ */
521
+ function drawBlockPatternWithClipPath(
522
+ ctx: CanvasRenderingContext2D,
523
+ definition: [pattern: CustomGlyphPatternDefinition, clipPath: string],
524
+ xOffset: number,
525
+ yOffset: number,
526
+ deviceCellWidth: number,
527
+ deviceCellHeight: number
528
+ ): void {
529
+ const [pattern, clipPath] = definition;
530
+
531
+ ctx.save();
532
+
533
+ // Build clip path from SVG-like instructions
534
+ ctx.beginPath();
535
+ for (const instruction of clipPath.split(' ')) {
536
+ const type = instruction[0];
537
+ if (type === 'Z') {
538
+ ctx.closePath();
539
+ continue;
540
+ }
541
+ const args: string[] = instruction.substring(1).split(',');
542
+ if (!args[0] || !args[1]) {
543
+ continue;
544
+ }
545
+ const x = xOffset + parseFloat(args[0]) * deviceCellWidth;
546
+ const y = yOffset + parseFloat(args[1]) * deviceCellHeight;
547
+ if (type === 'M') {
548
+ ctx.moveTo(x, y);
549
+ } else if (type === 'L') {
550
+ ctx.lineTo(x, y);
551
+ }
552
+ }
553
+ ctx.clip();
554
+
555
+ // Draw the pattern
556
+ drawPatternChar(ctx, pattern, xOffset, yOffset, deviceCellWidth, deviceCellHeight);
557
+
558
+ ctx.restore();
559
+ }
560
+
285
561
  function drawVectorShape(
286
562
  ctx: CanvasRenderingContext2D,
287
563
  charDefinition: ICustomGlyphVectorShape,
@@ -303,6 +579,10 @@ function drawVectorShape(
303
579
  ctx.lineWidth = devicePixelRatio * cssLineWidth;
304
580
  for (const instruction of charDefinition.d.split(' ')) {
305
581
  const type = instruction[0];
582
+ if (type === 'Z') {
583
+ ctx.closePath();
584
+ continue;
585
+ }
306
586
  const f = svgToCanvasInstructionMap[type];
307
587
  if (!f) {
308
588
  console.error(`Could not find drawing instructions for "${type}"`);
@@ -340,7 +620,8 @@ function clamp(value: number, max: number, min: number = 0): number {
340
620
  const svgToCanvasInstructionMap: { [index: string]: any } = {
341
621
  'C': (ctx: CanvasRenderingContext2D, args: number[]) => ctx.bezierCurveTo(args[0], args[1], args[2], args[3], args[4], args[5]),
342
622
  'L': (ctx: CanvasRenderingContext2D, args: number[]) => ctx.lineTo(args[0], args[1]),
343
- 'M': (ctx: CanvasRenderingContext2D, args: number[]) => ctx.moveTo(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])
344
625
  };
345
626
 
346
627
  function translateArgs(args: string[], cellWidth: number, cellHeight: number, xOffset: number, yOffset: number, doClamp: boolean, devicePixelRatio: number, leftPadding: number = 0, rightPadding: number = 0): number[] {
@@ -35,9 +35,11 @@ export const enum CustomGlyphDefinitionType {
35
35
  BLOCK_PATTERN,
36
36
  BLOCK_PATTERN_WITH_REGION,
37
37
  BLOCK_PATTERN_WITH_REGION_AND_SOLID_OCTANT_BLOCK_VECTOR,
38
+ BLOCK_PATTERN_WITH_CLIP_PATH,
38
39
  PATH_FUNCTION,
39
40
  PATH_FUNCTION_WITH_WEIGHT,
40
41
  PATH,
42
+ PATH_NEGATIVE,
41
43
  VECTOR_SHAPE,
42
44
  }
43
45
 
@@ -50,8 +52,10 @@ export type CustomGlyphCharacterDefinition = (
50
52
  // TODO: Consolidate these, draws should be possible via regions/clipping instead of special
51
53
  // casing
52
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] } |
53
56
  { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: CustomGlyphPathDrawFunctionDefinition } |
54
57
  { type: CustomGlyphDefinitionType.PATH_FUNCTION_WITH_WEIGHT, data: { [fontWeight: number]: string | CustomGlyphPathDrawFunctionDefinition } } |
55
58
  { type: CustomGlyphDefinitionType.PATH, data: string } |
59
+ { type: CustomGlyphDefinitionType.PATH_NEGATIVE, data: ICustomGlyphVectorShape } |
56
60
  { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: ICustomGlyphVectorShape }
57
61
  );