@vectojs/core 1.20.0 → 1.22.0
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/dist/{chunk-YLH7F4ZV.js → chunk-AGP4VLF4.js} +32 -0
- package/dist/{chunk-TJCXB2F6.mjs → chunk-FRMLD4PP.mjs} +32 -0
- package/dist/{chunk-L4SWVP2H.js → chunk-LHONR3GO.js} +336 -38
- package/dist/{chunk-QS3CUV7H.mjs → chunk-UPULSLKA.mjs} +298 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +451 -218
- package/dist/index.mjs +253 -20
- package/dist/renderer/CanvasRenderer.d.ts +20 -1
- package/dist/renderer/GlyphRasterAtlas.d.ts +186 -0
- package/dist/renderer/IRenderer.d.ts +94 -0
- package/dist/renderer/SVGRenderer.d.ts +2 -0
- package/dist/renderer/WebGLPointRenderer.d.ts +33 -0
- package/dist/renderer/index.d.ts +1 -0
- package/dist/renderer.js +4 -2
- package/dist/renderer.mjs +3 -1
- package/dist/text.js +2 -2
- package/dist/text.mjs +1 -1
- package/dist/tree/Entity.d.ts +128 -0
- package/dist/tree/Scene.d.ts +132 -1
- package/package.json +2 -2
|
@@ -1,4 +1,19 @@
|
|
|
1
1
|
// src/renderer/CanvasRenderer.ts
|
|
2
|
+
function emptyDrawCounters() {
|
|
3
|
+
return {
|
|
4
|
+
fills: 0,
|
|
5
|
+
strokes: 0,
|
|
6
|
+
texts: 0,
|
|
7
|
+
images: 0,
|
|
8
|
+
circles: 0,
|
|
9
|
+
flushes: 0,
|
|
10
|
+
saves: 0,
|
|
11
|
+
restores: 0,
|
|
12
|
+
clips: 0,
|
|
13
|
+
stateSwitches: 0,
|
|
14
|
+
overdrawRatio: 0
|
|
15
|
+
};
|
|
16
|
+
}
|
|
2
17
|
var TWO_PI = Math.PI * 2;
|
|
3
18
|
function getDevicePixelRatio() {
|
|
4
19
|
return typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1;
|
|
@@ -39,6 +54,17 @@ var CanvasRenderer = class _CanvasRenderer {
|
|
|
39
54
|
batchCount = 0;
|
|
40
55
|
_cachedFont = "";
|
|
41
56
|
_cachedFill = "";
|
|
57
|
+
/** Backend discriminator; see {@link IRenderer.kind}. */
|
|
58
|
+
kind = "canvas2d";
|
|
59
|
+
/**
|
|
60
|
+
* Draw counters, allocated only once counting is enabled.
|
|
61
|
+
*
|
|
62
|
+
* Null when off, so the guard on every op is a single null test and an inactive
|
|
63
|
+
* renderer carries no counter object at all.
|
|
64
|
+
*/
|
|
65
|
+
counters = null;
|
|
66
|
+
/** Accumulated primitive area, kept separately so the ratio is derived on read. */
|
|
67
|
+
drawnArea = 0;
|
|
42
68
|
/**
|
|
43
69
|
* @param canvas - The target canvas. Its backing store is resized to the
|
|
44
70
|
* logical size × devicePixelRatio.
|
|
@@ -135,19 +161,47 @@ var CanvasRenderer = class _CanvasRenderer {
|
|
|
135
161
|
return this.contextLost;
|
|
136
162
|
}
|
|
137
163
|
/** @inheritdoc */
|
|
164
|
+
/** @inheritdoc */
|
|
165
|
+
setDrawCounters(enabled) {
|
|
166
|
+
if (!enabled) {
|
|
167
|
+
this.counters = null;
|
|
168
|
+
this.drawnArea = 0;
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
if (!this.counters) this.counters = emptyDrawCounters();
|
|
172
|
+
}
|
|
173
|
+
/** @inheritdoc */
|
|
174
|
+
getDrawCounters() {
|
|
175
|
+
if (!this.counters) return null;
|
|
176
|
+
const area = this.width * this.height;
|
|
177
|
+
return {
|
|
178
|
+
...this.counters,
|
|
179
|
+
// Derived on read rather than maintained: the ratio is only meaningful
|
|
180
|
+
// against the current surface size, which can change under resize.
|
|
181
|
+
overdrawRatio: area > 0 ? Math.round(this.drawnArea / area * 100) / 100 : 0
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
/** @inheritdoc */
|
|
185
|
+
clearDrawCounters() {
|
|
186
|
+
if (this.counters) this.counters = emptyDrawCounters();
|
|
187
|
+
this.drawnArea = 0;
|
|
188
|
+
}
|
|
138
189
|
clear() {
|
|
139
190
|
if (this.contextLost) return;
|
|
140
191
|
this.flush();
|
|
192
|
+
if (this.counters) this.drawnArea = 0;
|
|
141
193
|
this.ctx.clearRect(0, 0, this.width, this.height);
|
|
142
194
|
}
|
|
143
195
|
/** @inheritdoc */
|
|
144
196
|
save() {
|
|
145
197
|
this.flush();
|
|
198
|
+
if (this.counters) this.counters.saves++;
|
|
146
199
|
this.ctx.save();
|
|
147
200
|
}
|
|
148
201
|
/** @inheritdoc */
|
|
149
202
|
restore() {
|
|
150
203
|
this.flush();
|
|
204
|
+
if (this.counters) this.counters.restores++;
|
|
151
205
|
this.ctx.restore();
|
|
152
206
|
this._cachedFont = "";
|
|
153
207
|
this._cachedFill = "";
|
|
@@ -171,6 +225,7 @@ var CanvasRenderer = class _CanvasRenderer {
|
|
|
171
225
|
/** @inheritdoc */
|
|
172
226
|
clip(x, y, width, height) {
|
|
173
227
|
this.flush();
|
|
228
|
+
if (this.counters) this.counters.clips++;
|
|
174
229
|
this.ctx.beginPath();
|
|
175
230
|
this.ctx.rect(x, y, width, height);
|
|
176
231
|
this.ctx.clip();
|
|
@@ -207,9 +262,22 @@ var CanvasRenderer = class _CanvasRenderer {
|
|
|
207
262
|
/** @inheritdoc */
|
|
208
263
|
drawImage(source, dx, dy, dw, dh) {
|
|
209
264
|
this.flush();
|
|
265
|
+
if (this.counters) {
|
|
266
|
+
this.counters.images++;
|
|
267
|
+
this.drawnArea += Math.abs(dw * dh);
|
|
268
|
+
}
|
|
210
269
|
this.ctx.drawImage(source, dx, dy, dw, dh);
|
|
211
270
|
}
|
|
212
271
|
/** @inheritdoc */
|
|
272
|
+
drawImageRect(source, sx, sy, sw, sh, dx, dy, dw, dh) {
|
|
273
|
+
this.flush();
|
|
274
|
+
if (this.counters) {
|
|
275
|
+
this.counters.images++;
|
|
276
|
+
this.drawnArea += Math.abs(dw * dh);
|
|
277
|
+
}
|
|
278
|
+
this.ctx.drawImage(source, sx, sy, sw, sh, dx, dy, dw, dh);
|
|
279
|
+
}
|
|
280
|
+
/** @inheritdoc */
|
|
213
281
|
fillCircle(cx, cy, radius, color, alpha = 1) {
|
|
214
282
|
if (this.batchActive && (color !== this.batchColor || alpha !== this.batchAlpha)) {
|
|
215
283
|
this.flush();
|
|
@@ -220,6 +288,10 @@ var CanvasRenderer = class _CanvasRenderer {
|
|
|
220
288
|
this.batchColor = color;
|
|
221
289
|
this.batchAlpha = alpha;
|
|
222
290
|
}
|
|
291
|
+
if (this.counters) {
|
|
292
|
+
this.counters.circles++;
|
|
293
|
+
this.drawnArea += Math.PI * radius * radius;
|
|
294
|
+
}
|
|
223
295
|
this.ctx.moveTo(cx + radius, cy);
|
|
224
296
|
this.ctx.arc(cx, cy, radius, 0, TWO_PI);
|
|
225
297
|
this.batchCount++;
|
|
@@ -228,6 +300,7 @@ var CanvasRenderer = class _CanvasRenderer {
|
|
|
228
300
|
/** @inheritdoc */
|
|
229
301
|
flush() {
|
|
230
302
|
if (!this.batchActive) return;
|
|
303
|
+
if (this.counters) this.counters.flushes++;
|
|
231
304
|
this.ctx.globalAlpha = this.batchAlpha;
|
|
232
305
|
this.ctx.fillStyle = this.batchColor;
|
|
233
306
|
this.ctx.fill();
|
|
@@ -238,7 +311,9 @@ var CanvasRenderer = class _CanvasRenderer {
|
|
|
238
311
|
/** @inheritdoc */
|
|
239
312
|
fill(color) {
|
|
240
313
|
this.flush();
|
|
314
|
+
if (this.counters) this.counters.fills++;
|
|
241
315
|
if (this._cachedFill !== color) {
|
|
316
|
+
if (this.counters) this.counters.stateSwitches++;
|
|
242
317
|
this.ctx.fillStyle = color;
|
|
243
318
|
this._cachedFill = color;
|
|
244
319
|
}
|
|
@@ -247,6 +322,7 @@ var CanvasRenderer = class _CanvasRenderer {
|
|
|
247
322
|
/** @inheritdoc */
|
|
248
323
|
stroke(color, lineWidth = 1) {
|
|
249
324
|
this.flush();
|
|
325
|
+
if (this.counters) this.counters.strokes++;
|
|
250
326
|
this.ctx.strokeStyle = color;
|
|
251
327
|
this.ctx.lineWidth = lineWidth;
|
|
252
328
|
this.ctx.lineCap = "round";
|
|
@@ -256,11 +332,14 @@ var CanvasRenderer = class _CanvasRenderer {
|
|
|
256
332
|
/** @inheritdoc */
|
|
257
333
|
fillText(text, x, y, font, color) {
|
|
258
334
|
this.flush();
|
|
335
|
+
if (this.counters) this.counters.texts++;
|
|
259
336
|
if (this._cachedFont !== font) {
|
|
337
|
+
if (this.counters) this.counters.stateSwitches++;
|
|
260
338
|
this.ctx.font = font;
|
|
261
339
|
this._cachedFont = font;
|
|
262
340
|
}
|
|
263
341
|
if (this._cachedFill !== color) {
|
|
342
|
+
if (this.counters) this.counters.stateSwitches++;
|
|
264
343
|
this.ctx.fillStyle = color;
|
|
265
344
|
this._cachedFill = color;
|
|
266
345
|
}
|
|
@@ -371,6 +450,8 @@ function fontFamilyFromShorthand(font, sizeToken) {
|
|
|
371
450
|
return family || "sans-serif";
|
|
372
451
|
}
|
|
373
452
|
var SVGRenderer = class {
|
|
453
|
+
/** Backend discriminator; see {@link IRenderer.kind}. */
|
|
454
|
+
kind = "svg";
|
|
374
455
|
width;
|
|
375
456
|
height;
|
|
376
457
|
buffer = [];
|
|
@@ -1223,6 +1304,12 @@ function createWebGLPointRenderer(canvas) {
|
|
|
1223
1304
|
let spriteCount = 0;
|
|
1224
1305
|
let glyphData = new Float32Array(FLOATS_PER_SPRITE_VERT * VERTS_PER_QUAD * 1024);
|
|
1225
1306
|
let glyphCount = 0;
|
|
1307
|
+
let frameDrawCalls = 0;
|
|
1308
|
+
let lastFrameDrawCalls = 0;
|
|
1309
|
+
let totalDrawCalls = 0;
|
|
1310
|
+
let atlasSwitches = 0;
|
|
1311
|
+
let circleQuadFallbacks = 0;
|
|
1312
|
+
let circlePoints = 0;
|
|
1226
1313
|
let circleQuadData = new Float32Array(FLOATS_PER_SPRITE_VERT * VERTS_PER_QUAD * 64);
|
|
1227
1314
|
let circleQuadCount = 0;
|
|
1228
1315
|
let logicalW = 0;
|
|
@@ -1245,6 +1332,7 @@ function createWebGLPointRenderer(canvas) {
|
|
|
1245
1332
|
gl.uniform1f(gURange, distanceRange);
|
|
1246
1333
|
ensureQuadIndices(glyphCount);
|
|
1247
1334
|
gl.drawElements(gl.TRIANGLES, glyphCount * INDICES_PER_QUAD, gl.UNSIGNED_INT, 0);
|
|
1335
|
+
frameDrawCalls++;
|
|
1248
1336
|
gl.bindVertexArray(null);
|
|
1249
1337
|
glyphCount = 0;
|
|
1250
1338
|
};
|
|
@@ -1261,7 +1349,23 @@ function createWebGLPointRenderer(canvas) {
|
|
|
1261
1349
|
canvas.style.height = `${height}px`;
|
|
1262
1350
|
gl.viewport(0, 0, canvas.width, canvas.height);
|
|
1263
1351
|
},
|
|
1352
|
+
stats() {
|
|
1353
|
+
return {
|
|
1354
|
+
drawCalls: lastFrameDrawCalls,
|
|
1355
|
+
totalDrawCalls,
|
|
1356
|
+
atlasSwitches,
|
|
1357
|
+
// Fixed at creation: five programs compiled once, never per frame, so
|
|
1358
|
+
// these are capability facts rather than counters.
|
|
1359
|
+
programs: 5,
|
|
1360
|
+
textures: (texture ? 1 : 0) + (msdfTexture ? 1 : 0),
|
|
1361
|
+
circleQuadFallbacks,
|
|
1362
|
+
circlePoints
|
|
1363
|
+
};
|
|
1364
|
+
},
|
|
1264
1365
|
begin() {
|
|
1366
|
+
lastFrameDrawCalls = frameDrawCalls;
|
|
1367
|
+
totalDrawCalls += frameDrawCalls;
|
|
1368
|
+
frameDrawCalls = 0;
|
|
1265
1369
|
pointCount = 0;
|
|
1266
1370
|
rectCount = 0;
|
|
1267
1371
|
spriteCount = 0;
|
|
@@ -1314,6 +1418,7 @@ function createWebGLPointRenderer(canvas) {
|
|
|
1314
1418
|
distanceRange = range;
|
|
1315
1419
|
return;
|
|
1316
1420
|
}
|
|
1421
|
+
atlasSwitches++;
|
|
1317
1422
|
drawGlyphs();
|
|
1318
1423
|
distanceRange = range;
|
|
1319
1424
|
if (!msdfTexture) {
|
|
@@ -1356,6 +1461,7 @@ function createWebGLPointRenderer(canvas) {
|
|
|
1356
1461
|
addCircle(x, y, radius, color, alpha = 1) {
|
|
1357
1462
|
const needsQuad = logicalW > 0 && (x < radius || y < radius || x > logicalW - radius || y > logicalH - radius) || radius * 2 * dpr > maxPointSize;
|
|
1358
1463
|
if (needsQuad) {
|
|
1464
|
+
circleQuadFallbacks++;
|
|
1359
1465
|
const stride = FLOATS_PER_SPRITE_VERT * VERTS_PER_QUAD;
|
|
1360
1466
|
circleQuadData = grow(circleQuadData, (circleQuadCount + 1) * stride);
|
|
1361
1467
|
const rgba = parseColorToRGBA(color);
|
|
@@ -1380,6 +1486,7 @@ function createWebGLPointRenderer(canvas) {
|
|
|
1380
1486
|
circleQuadCount++;
|
|
1381
1487
|
return;
|
|
1382
1488
|
}
|
|
1489
|
+
circlePoints++;
|
|
1383
1490
|
pointData = grow(pointData, (pointCount + 1) * FLOATS_PER_POINT);
|
|
1384
1491
|
const [r, g, b, a] = parseColorToRGBA(color);
|
|
1385
1492
|
const o = pointCount * FLOATS_PER_POINT;
|
|
@@ -1421,6 +1528,7 @@ function createWebGLPointRenderer(canvas) {
|
|
|
1421
1528
|
gl.uniform2f(rURes, logicalW, logicalH);
|
|
1422
1529
|
ensureQuadIndices(rectCount);
|
|
1423
1530
|
gl.drawElements(gl.TRIANGLES, rectCount * INDICES_PER_QUAD, gl.UNSIGNED_INT, 0);
|
|
1531
|
+
frameDrawCalls++;
|
|
1424
1532
|
}
|
|
1425
1533
|
if (pointCount > 0) {
|
|
1426
1534
|
gl.useProgram(pointProgram);
|
|
@@ -1434,6 +1542,7 @@ function createWebGLPointRenderer(canvas) {
|
|
|
1434
1542
|
gl.uniform2f(pURes, logicalW, logicalH);
|
|
1435
1543
|
gl.uniform1f(pUDpr, dpr);
|
|
1436
1544
|
gl.drawArrays(gl.POINTS, 0, pointCount);
|
|
1545
|
+
frameDrawCalls++;
|
|
1437
1546
|
}
|
|
1438
1547
|
if (circleQuadCount > 0) {
|
|
1439
1548
|
const floats = circleQuadCount * VERTS_PER_QUAD * FLOATS_PER_SPRITE_VERT;
|
|
@@ -1444,6 +1553,7 @@ function createWebGLPointRenderer(canvas) {
|
|
|
1444
1553
|
gl.uniform2f(cURes, logicalW, logicalH);
|
|
1445
1554
|
ensureQuadIndices(circleQuadCount);
|
|
1446
1555
|
gl.drawElements(gl.TRIANGLES, circleQuadCount * INDICES_PER_QUAD, gl.UNSIGNED_INT, 0);
|
|
1556
|
+
frameDrawCalls++;
|
|
1447
1557
|
}
|
|
1448
1558
|
if (spriteCount > 0 && texture) {
|
|
1449
1559
|
const floats = spriteCount * VERTS_PER_QUAD * FLOATS_PER_SPRITE_VERT;
|
|
@@ -1457,6 +1567,7 @@ function createWebGLPointRenderer(canvas) {
|
|
|
1457
1567
|
gl.uniform2f(sURes, logicalW, logicalH);
|
|
1458
1568
|
ensureQuadIndices(spriteCount);
|
|
1459
1569
|
gl.drawElements(gl.TRIANGLES, spriteCount * INDICES_PER_QUAD, gl.UNSIGNED_INT, 0);
|
|
1570
|
+
frameDrawCalls++;
|
|
1460
1571
|
}
|
|
1461
1572
|
gl.bindVertexArray(null);
|
|
1462
1573
|
drawGlyphs();
|
|
@@ -1807,6 +1918,192 @@ var WebGPUParticleSystemManager = class {
|
|
|
1807
1918
|
}
|
|
1808
1919
|
};
|
|
1809
1920
|
|
|
1921
|
+
// src/renderer/GlyphRasterAtlas.ts
|
|
1922
|
+
var HARD_MAX_SIZE = 8192;
|
|
1923
|
+
var PAD = 2;
|
|
1924
|
+
var GlyphRasterAtlas = class {
|
|
1925
|
+
slots = /* @__PURE__ */ new Map();
|
|
1926
|
+
dpr;
|
|
1927
|
+
maxSize;
|
|
1928
|
+
canvas = null;
|
|
1929
|
+
ctx = null;
|
|
1930
|
+
/**
|
|
1931
|
+
* Shelf packing: glyphs land left-to-right on a row, then a new row starts.
|
|
1932
|
+
* A monospace grid produces near-uniform widths, so shelves waste very little
|
|
1933
|
+
* and cost one comparison per insert — a real 2D packer would buy nothing here.
|
|
1934
|
+
*/
|
|
1935
|
+
penX = PAD;
|
|
1936
|
+
penY = PAD;
|
|
1937
|
+
rowHeight = 0;
|
|
1938
|
+
_hits = 0;
|
|
1939
|
+
_misses = 0;
|
|
1940
|
+
_resets = 0;
|
|
1941
|
+
constructor(options = {}) {
|
|
1942
|
+
this.dpr = Math.max(1, options.dpr ?? 1);
|
|
1943
|
+
this.maxSize = Math.min(HARD_MAX_SIZE, Math.max(256, options.maxSize ?? 2048));
|
|
1944
|
+
}
|
|
1945
|
+
/** Live instrumentation snapshot. */
|
|
1946
|
+
get stats() {
|
|
1947
|
+
return {
|
|
1948
|
+
hits: this._hits,
|
|
1949
|
+
misses: this._misses,
|
|
1950
|
+
size: this.slots.size,
|
|
1951
|
+
resets: this._resets
|
|
1952
|
+
};
|
|
1953
|
+
}
|
|
1954
|
+
/**
|
|
1955
|
+
* The atlas canvas, to pass as the blit source.
|
|
1956
|
+
*
|
|
1957
|
+
* `null` until the first successful {@link get}, and in any non-DOM context.
|
|
1958
|
+
*/
|
|
1959
|
+
get source() {
|
|
1960
|
+
return this.canvas;
|
|
1961
|
+
}
|
|
1962
|
+
ensureCanvas() {
|
|
1963
|
+
if (this.ctx) return true;
|
|
1964
|
+
if (typeof document === "undefined") return false;
|
|
1965
|
+
const canvas = document.createElement("canvas");
|
|
1966
|
+
canvas.width = this.maxSize;
|
|
1967
|
+
canvas.height = this.maxSize;
|
|
1968
|
+
const ctx = canvas.getContext("2d");
|
|
1969
|
+
if (!ctx) return false;
|
|
1970
|
+
this.canvas = canvas;
|
|
1971
|
+
this.ctx = ctx;
|
|
1972
|
+
return true;
|
|
1973
|
+
}
|
|
1974
|
+
/**
|
|
1975
|
+
* Look up a glyph, rasterizing it into the atlas on first request.
|
|
1976
|
+
*
|
|
1977
|
+
* @param font - Full CSS `font` shorthand, used for measuring and painting.
|
|
1978
|
+
* @param color - CSS color baked into the pixels.
|
|
1979
|
+
* @param glyph - A single grapheme cluster. Long strings are rejected
|
|
1980
|
+
* (`null`): they defeat the atlas's fixed-slot packing and belong in
|
|
1981
|
+
* `fillText` or {@link TextRasterCache}.
|
|
1982
|
+
* @returns The slot, or `null` when the caller must fall back to `fillText`
|
|
1983
|
+
* (headless, unrasterizable, or too large to pack).
|
|
1984
|
+
*/
|
|
1985
|
+
get(font, color, glyph) {
|
|
1986
|
+
const key = font + "\0" + color + "\0" + glyph;
|
|
1987
|
+
const existing = this.slots.get(key);
|
|
1988
|
+
if (existing !== void 0) {
|
|
1989
|
+
if (existing) this._hits++;
|
|
1990
|
+
return existing;
|
|
1991
|
+
}
|
|
1992
|
+
this._misses++;
|
|
1993
|
+
if (glyph.length === 0 || glyph.length > 8) {
|
|
1994
|
+
this.slots.set(key, null);
|
|
1995
|
+
return null;
|
|
1996
|
+
}
|
|
1997
|
+
if (!this.ensureCanvas()) return null;
|
|
1998
|
+
const ctx = this.ctx;
|
|
1999
|
+
ctx.font = font;
|
|
2000
|
+
const metrics = ctx.measureText(glyph);
|
|
2001
|
+
const advance = metrics.width;
|
|
2002
|
+
const ascent = metrics.actualBoundingBoxAscent || metrics.fontBoundingBoxAscent || advance * 0.9;
|
|
2003
|
+
const descent = metrics.actualBoundingBoxDescent || metrics.fontBoundingBoxDescent || advance * 0.25;
|
|
2004
|
+
const left = metrics.actualBoundingBoxLeft || 0;
|
|
2005
|
+
const right = metrics.actualBoundingBoxRight || advance;
|
|
2006
|
+
const offsetX = Math.ceil(left) + PAD;
|
|
2007
|
+
const offsetY = Math.ceil(ascent) + PAD;
|
|
2008
|
+
const w = offsetX + Math.ceil(right) + PAD;
|
|
2009
|
+
const h = offsetY + Math.ceil(descent) + PAD;
|
|
2010
|
+
if (!(w > 0) || !(h > 0)) {
|
|
2011
|
+
this.slots.set(key, null);
|
|
2012
|
+
return null;
|
|
2013
|
+
}
|
|
2014
|
+
const dpr = this.dpr;
|
|
2015
|
+
const dw = Math.ceil(w * dpr);
|
|
2016
|
+
const dh = Math.ceil(h * dpr);
|
|
2017
|
+
if (dw + PAD * 2 > this.maxSize || dh + PAD * 2 > this.maxSize) {
|
|
2018
|
+
this.slots.set(key, null);
|
|
2019
|
+
return null;
|
|
2020
|
+
}
|
|
2021
|
+
if (this.penX + dw + PAD > this.maxSize) {
|
|
2022
|
+
this.penX = PAD;
|
|
2023
|
+
this.penY += this.rowHeight + PAD;
|
|
2024
|
+
this.rowHeight = 0;
|
|
2025
|
+
}
|
|
2026
|
+
if (this.penY + dh + PAD > this.maxSize) {
|
|
2027
|
+
this.reset();
|
|
2028
|
+
if (!this.ensureCanvas()) return null;
|
|
2029
|
+
this.ctx.font = font;
|
|
2030
|
+
}
|
|
2031
|
+
const sx = this.penX;
|
|
2032
|
+
const sy = this.penY;
|
|
2033
|
+
const c = this.ctx;
|
|
2034
|
+
c.save();
|
|
2035
|
+
c.translate(sx, sy);
|
|
2036
|
+
if (dpr !== 1) c.scale(dpr, dpr);
|
|
2037
|
+
c.font = font;
|
|
2038
|
+
c.textBaseline = "alphabetic";
|
|
2039
|
+
c.fillStyle = color;
|
|
2040
|
+
c.fillText(glyph, offsetX, offsetY);
|
|
2041
|
+
c.restore();
|
|
2042
|
+
this.penX += dw + PAD;
|
|
2043
|
+
if (dh > this.rowHeight) this.rowHeight = dh;
|
|
2044
|
+
const slot = {
|
|
2045
|
+
sx,
|
|
2046
|
+
sy,
|
|
2047
|
+
sw: dw,
|
|
2048
|
+
sh: dh,
|
|
2049
|
+
w,
|
|
2050
|
+
h,
|
|
2051
|
+
offsetX,
|
|
2052
|
+
offsetY,
|
|
2053
|
+
glyph,
|
|
2054
|
+
font,
|
|
2055
|
+
advance,
|
|
2056
|
+
left,
|
|
2057
|
+
right
|
|
2058
|
+
};
|
|
2059
|
+
this.slots.set(key, slot);
|
|
2060
|
+
return slot;
|
|
2061
|
+
}
|
|
2062
|
+
/**
|
|
2063
|
+
* Find the slot occupying a source position, or `null`.
|
|
2064
|
+
*
|
|
2065
|
+
* The inverse of {@link get}: it maps a blit back to the glyph it drew. Exists
|
|
2066
|
+
* for instrumentation — a test or devtool that traces `drawImage` calls sees
|
|
2067
|
+
* only a source rect, and needs this to recover which cluster was painted and
|
|
2068
|
+
* with what metrics. Linear over resident slots, so it is a diagnostic, not a
|
|
2069
|
+
* per-frame call.
|
|
2070
|
+
*/
|
|
2071
|
+
slotAt(sx, sy) {
|
|
2072
|
+
for (const slot of this.slots.values()) {
|
|
2073
|
+
if (slot && slot.sx === sx && slot.sy === sy) return slot;
|
|
2074
|
+
}
|
|
2075
|
+
return null;
|
|
2076
|
+
}
|
|
2077
|
+
/**
|
|
2078
|
+
* Drop every glyph and reuse the canvas.
|
|
2079
|
+
*
|
|
2080
|
+
* Call after a font or theme change: slots are keyed by `(font, color, glyph)`
|
|
2081
|
+
* so stale entries are never *returned* wrongly, but they do occupy space.
|
|
2082
|
+
*/
|
|
2083
|
+
reset() {
|
|
2084
|
+
this.slots.clear();
|
|
2085
|
+
this.penX = PAD;
|
|
2086
|
+
this.penY = PAD;
|
|
2087
|
+
this.rowHeight = 0;
|
|
2088
|
+
this._resets++;
|
|
2089
|
+
if (this.ctx && this.canvas) {
|
|
2090
|
+
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
|
2091
|
+
}
|
|
2092
|
+
}
|
|
2093
|
+
/** Release the backing canvas and all slots. */
|
|
2094
|
+
destroy() {
|
|
2095
|
+
this.slots.clear();
|
|
2096
|
+
this.canvas = null;
|
|
2097
|
+
this.ctx = null;
|
|
2098
|
+
this.penX = PAD;
|
|
2099
|
+
this.penY = PAD;
|
|
2100
|
+
this.rowHeight = 0;
|
|
2101
|
+
this._hits = 0;
|
|
2102
|
+
this._misses = 0;
|
|
2103
|
+
this._resets = 0;
|
|
2104
|
+
}
|
|
2105
|
+
};
|
|
2106
|
+
|
|
1810
2107
|
// src/renderer/TextRasterCache.ts
|
|
1811
2108
|
var TextRasterCache = class {
|
|
1812
2109
|
cache = /* @__PURE__ */ new Map();
|
|
@@ -1900,5 +2197,6 @@ export {
|
|
|
1900
2197
|
parseColorToRGBA,
|
|
1901
2198
|
createWebGLPointRenderer,
|
|
1902
2199
|
WebGPUParticleSystemManager,
|
|
2200
|
+
GlyphRasterAtlas,
|
|
1903
2201
|
TextRasterCache
|
|
1904
2202
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export * from './renderer/WebGLPointRenderer';
|
|
|
5
5
|
export * from './renderer/WebGPUParticleSystemManager';
|
|
6
6
|
export * from './renderer/colorParse';
|
|
7
7
|
export * from './renderer/url';
|
|
8
|
+
export * from './renderer/GlyphRasterAtlas';
|
|
8
9
|
export * from './renderer/TextRasterCache';
|
|
9
10
|
export * from './tree/Entity';
|
|
10
11
|
export * from './tree/Scene';
|