@xterm/addon-webgl 0.20.0-beta.5 → 0.20.0-beta.7
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.
- package/lib/addon-webgl.js +1 -1
- package/lib/addon-webgl.js.map +1 -1
- package/lib/addon-webgl.mjs +15 -15
- package/lib/addon-webgl.mjs.map +3 -3
- package/package.json +3 -3
- package/src/TextureAtlas.ts +1 -1
- package/src/customGlyphs/CustomGlyphDefinitions.ts +271 -13
- package/src/customGlyphs/CustomGlyphRasterizer.ts +284 -3
- package/src/customGlyphs/Types.ts +4 -0
|
@@ -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
|
-
|
|
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
|
);
|