@vectojs/core 1.11.2 → 1.12.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-ORCPCIJ7.js → chunk-BEUIB3U7.js} +135 -34
- package/dist/{chunk-XZEPHCDZ.mjs → chunk-L5BCKFQE.mjs} +105 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.js +40 -7
- package/dist/index.mjs +34 -1
- package/dist/renderer/CanvasRenderer.d.ts +2 -0
- package/dist/renderer/TextRasterCache.d.ts +105 -0
- package/dist/renderer/index.d.ts +1 -0
- package/dist/renderer.js +4 -2
- package/dist/renderer.mjs +3 -1
- package/dist/tree/ComputeParticleEntity.d.ts +17 -0
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class; var _class2; var _class3;// src/renderer/CanvasRenderer.ts
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class; var _class2; var _class3; var _class4;// src/renderer/CanvasRenderer.ts
|
|
2
2
|
var TWO_PI = Math.PI * 2;
|
|
3
3
|
function getDevicePixelRatio() {
|
|
4
4
|
return typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1;
|
|
@@ -29,6 +29,8 @@ var CanvasRenderer = (_class = class _CanvasRenderer {
|
|
|
29
29
|
__init2() {this.batchColor = ""}
|
|
30
30
|
__init3() {this.batchAlpha = 1}
|
|
31
31
|
__init4() {this.batchCount = 0}
|
|
32
|
+
__init5() {this._cachedFont = ""}
|
|
33
|
+
__init6() {this._cachedFill = ""}
|
|
32
34
|
/**
|
|
33
35
|
* @param canvas - The target canvas. Its backing store is resized to the
|
|
34
36
|
* logical size × devicePixelRatio.
|
|
@@ -38,7 +40,7 @@ var CanvasRenderer = (_class = class _CanvasRenderer {
|
|
|
38
40
|
* set) so the canvas's own dimensions aren't clobbered by the window's.
|
|
39
41
|
* @param maxDPR - See {@link maxDPR}.
|
|
40
42
|
*/
|
|
41
|
-
constructor(canvas, size, maxDPR) {;_class.prototype.__init.call(this);_class.prototype.__init2.call(this);_class.prototype.__init3.call(this);_class.prototype.__init4.call(this);
|
|
43
|
+
constructor(canvas, size, maxDPR) {;_class.prototype.__init.call(this);_class.prototype.__init2.call(this);_class.prototype.__init3.call(this);_class.prototype.__init4.call(this);_class.prototype.__init5.call(this);_class.prototype.__init6.call(this);
|
|
42
44
|
this.maxDPR = maxDPR;
|
|
43
45
|
const dpr = this.effectiveDPR();
|
|
44
46
|
this.width = _nullishCoalesce(_optionalChain([size, 'optionalAccess', _ => _.width]), () => ( (typeof window !== "undefined" ? window.innerWidth : canvas.width || 0)));
|
|
@@ -99,6 +101,8 @@ var CanvasRenderer = (_class = class _CanvasRenderer {
|
|
|
99
101
|
restore() {
|
|
100
102
|
this.flush();
|
|
101
103
|
this.ctx.restore();
|
|
104
|
+
this._cachedFont = "";
|
|
105
|
+
this._cachedFill = "";
|
|
102
106
|
}
|
|
103
107
|
/** @inheritdoc */
|
|
104
108
|
translate(x, y) {
|
|
@@ -186,7 +190,10 @@ var CanvasRenderer = (_class = class _CanvasRenderer {
|
|
|
186
190
|
/** @inheritdoc */
|
|
187
191
|
fill(color) {
|
|
188
192
|
this.flush();
|
|
189
|
-
this.
|
|
193
|
+
if (this._cachedFill !== color) {
|
|
194
|
+
this.ctx.fillStyle = color;
|
|
195
|
+
this._cachedFill = color;
|
|
196
|
+
}
|
|
190
197
|
this.ctx.fill();
|
|
191
198
|
}
|
|
192
199
|
/** @inheritdoc */
|
|
@@ -201,8 +208,14 @@ var CanvasRenderer = (_class = class _CanvasRenderer {
|
|
|
201
208
|
/** @inheritdoc */
|
|
202
209
|
fillText(text, x, y, font, color) {
|
|
203
210
|
this.flush();
|
|
204
|
-
this.
|
|
205
|
-
|
|
211
|
+
if (this._cachedFont !== font) {
|
|
212
|
+
this.ctx.font = font;
|
|
213
|
+
this._cachedFont = font;
|
|
214
|
+
}
|
|
215
|
+
if (this._cachedFill !== color) {
|
|
216
|
+
this.ctx.fillStyle = color;
|
|
217
|
+
this._cachedFill = color;
|
|
218
|
+
}
|
|
206
219
|
this.ctx.fillText(text, x, y);
|
|
207
220
|
}
|
|
208
221
|
/** @inheritdoc */
|
|
@@ -223,6 +236,8 @@ var CanvasRenderer = (_class = class _CanvasRenderer {
|
|
|
223
236
|
this.batchColor = "";
|
|
224
237
|
this.batchAlpha = 1;
|
|
225
238
|
this.batchActive = false;
|
|
239
|
+
this._cachedFont = "";
|
|
240
|
+
this._cachedFill = "";
|
|
226
241
|
}
|
|
227
242
|
}, _class.__initStatic(), _class);
|
|
228
243
|
|
|
@@ -310,39 +325,39 @@ function fontFamilyFromShorthand(font, sizeToken) {
|
|
|
310
325
|
var SVGRenderer = (_class2 = class {
|
|
311
326
|
|
|
312
327
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
328
|
+
__init7() {this.buffer = []}
|
|
329
|
+
__init8() {this.defsBuffer = []}
|
|
330
|
+
__init9() {this.currentPath = []}
|
|
316
331
|
// Inline matrix states (3x3 representation)
|
|
317
332
|
// [a, b, c, d, e, f] corresponding to:
|
|
318
333
|
// | a c e |
|
|
319
334
|
// | b d f |
|
|
320
335
|
// | 0 0 1 |
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
336
|
+
__init10() {this.mStack = []}
|
|
337
|
+
__init11() {this.ma = 1}
|
|
338
|
+
__init12() {this.mb = 0}
|
|
339
|
+
__init13() {this.mc = 0}
|
|
340
|
+
__init14() {this.md = 1}
|
|
341
|
+
__init15() {this.me = 0}
|
|
342
|
+
__init16() {this.mf = 0}
|
|
328
343
|
// Alpha stack
|
|
329
|
-
|
|
330
|
-
|
|
344
|
+
__init17() {this.alphaStack = []}
|
|
345
|
+
__init18() {this.globalAlpha = 1}
|
|
331
346
|
// Clipping stack
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
347
|
+
__init19() {this.clipDepthStack = []}
|
|
348
|
+
__init20() {this.clipDepth = 0}
|
|
349
|
+
__init21() {this.clipCounter = 0}
|
|
335
350
|
// uniquely identifies clipPaths
|
|
336
351
|
// Batch circles states (local coordinates)
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
352
|
+
__init22() {this.batchCircles = []}
|
|
353
|
+
__init23() {this.batchMatrix = [1, 0, 0, 1, 0, 0]}
|
|
354
|
+
__init24() {this.batchColor = ""}
|
|
355
|
+
__init25() {this.batchAlpha = 1}
|
|
356
|
+
__init26() {this.batchActive = false}
|
|
342
357
|
// Cache for generated gradient defs
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
constructor(width, height) {;_class2.prototype.
|
|
358
|
+
__init27() {this.gradientCounter = 0}
|
|
359
|
+
__init28() {this.gradientCache = /* @__PURE__ */ new Map()}
|
|
360
|
+
constructor(width, height) {;_class2.prototype.__init7.call(this);_class2.prototype.__init8.call(this);_class2.prototype.__init9.call(this);_class2.prototype.__init10.call(this);_class2.prototype.__init11.call(this);_class2.prototype.__init12.call(this);_class2.prototype.__init13.call(this);_class2.prototype.__init14.call(this);_class2.prototype.__init15.call(this);_class2.prototype.__init16.call(this);_class2.prototype.__init17.call(this);_class2.prototype.__init18.call(this);_class2.prototype.__init19.call(this);_class2.prototype.__init20.call(this);_class2.prototype.__init21.call(this);_class2.prototype.__init22.call(this);_class2.prototype.__init23.call(this);_class2.prototype.__init24.call(this);_class2.prototype.__init25.call(this);_class2.prototype.__init26.call(this);_class2.prototype.__init27.call(this);_class2.prototype.__init28.call(this);
|
|
346
361
|
this.width = width;
|
|
347
362
|
this.height = height;
|
|
348
363
|
}
|
|
@@ -1486,11 +1501,11 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
|
|
1486
1501
|
`;
|
|
1487
1502
|
var WebGPUParticleSystemManager = (_class3 = class {
|
|
1488
1503
|
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
constructor(device) {;_class3.prototype.
|
|
1504
|
+
__init29() {this.computePipeline = null}
|
|
1505
|
+
__init30() {this.renderPipeline = null}
|
|
1506
|
+
__init31() {this.computeBindGroupLayout = null}
|
|
1507
|
+
__init32() {this.renderBindGroupLayout = null}
|
|
1508
|
+
constructor(device) {;_class3.prototype.__init29.call(this);_class3.prototype.__init30.call(this);_class3.prototype.__init31.call(this);_class3.prototype.__init32.call(this);
|
|
1494
1509
|
this.device = device;
|
|
1495
1510
|
}
|
|
1496
1511
|
initPipelines(format) {
|
|
@@ -1631,6 +1646,92 @@ var WebGPUParticleSystemManager = (_class3 = class {
|
|
|
1631
1646
|
}
|
|
1632
1647
|
}, _class3);
|
|
1633
1648
|
|
|
1649
|
+
// src/renderer/TextRasterCache.ts
|
|
1650
|
+
var TextRasterCache = (_class4 = class {
|
|
1651
|
+
__init33() {this.cache = /* @__PURE__ */ new Map()}
|
|
1652
|
+
|
|
1653
|
+
|
|
1654
|
+
__init34() {this.scratchCtx = null}
|
|
1655
|
+
__init35() {this._hits = 0}
|
|
1656
|
+
__init36() {this._misses = 0}
|
|
1657
|
+
constructor(options = {}) {;_class4.prototype.__init33.call(this);_class4.prototype.__init34.call(this);_class4.prototype.__init35.call(this);_class4.prototype.__init36.call(this);
|
|
1658
|
+
this.maxEntries = Math.max(1, _nullishCoalesce(options.maxEntries, () => ( 4096)));
|
|
1659
|
+
this.dpr = Math.max(1, _nullishCoalesce(options.dpr, () => ( 1)));
|
|
1660
|
+
}
|
|
1661
|
+
/** Live instrumentation snapshot. */
|
|
1662
|
+
get stats() {
|
|
1663
|
+
return { hits: this._hits, misses: this._misses, size: this.cache.size };
|
|
1664
|
+
}
|
|
1665
|
+
measureCtx() {
|
|
1666
|
+
if (!this.scratchCtx) {
|
|
1667
|
+
if (typeof document === "undefined") return null;
|
|
1668
|
+
this.scratchCtx = document.createElement("canvas").getContext("2d");
|
|
1669
|
+
}
|
|
1670
|
+
return this.scratchCtx;
|
|
1671
|
+
}
|
|
1672
|
+
/**
|
|
1673
|
+
* Return the cached raster for a text run, rasterizing it on first request.
|
|
1674
|
+
*
|
|
1675
|
+
* @param font - Full CSS `font` shorthand, used for both measuring and painting.
|
|
1676
|
+
* @param color - CSS color baked into the raster.
|
|
1677
|
+
* @param text - The run to rasterize (may contain CJK / emoji).
|
|
1678
|
+
* @returns The raster, or `null` in a non-DOM/headless context (caller should
|
|
1679
|
+
* fall back to {@link IRenderer.fillText}).
|
|
1680
|
+
*/
|
|
1681
|
+
get(font, color, text) {
|
|
1682
|
+
const key = font + "\0" + color + "\0" + text;
|
|
1683
|
+
const hit = this.cache.get(key);
|
|
1684
|
+
if (hit) {
|
|
1685
|
+
this._hits++;
|
|
1686
|
+
return hit;
|
|
1687
|
+
}
|
|
1688
|
+
this._misses++;
|
|
1689
|
+
const m = this.measureCtx();
|
|
1690
|
+
if (!m) return null;
|
|
1691
|
+
m.font = font;
|
|
1692
|
+
const metrics = m.measureText(text);
|
|
1693
|
+
const approx = metrics.width || text.length * 8;
|
|
1694
|
+
const ascent = metrics.actualBoundingBoxAscent || metrics.fontBoundingBoxAscent || approx * 0.8;
|
|
1695
|
+
const descent = metrics.actualBoundingBoxDescent || metrics.fontBoundingBoxDescent || approx * 0.2;
|
|
1696
|
+
const left = metrics.actualBoundingBoxLeft || 0;
|
|
1697
|
+
const right = metrics.actualBoundingBoxRight || metrics.width;
|
|
1698
|
+
const pad = 2;
|
|
1699
|
+
const offsetX = Math.ceil(left) + pad;
|
|
1700
|
+
const offsetY = Math.ceil(ascent) + pad;
|
|
1701
|
+
const width = offsetX + Math.ceil(right) + pad;
|
|
1702
|
+
const height = offsetY + Math.ceil(descent) + pad;
|
|
1703
|
+
if (typeof document === "undefined") return null;
|
|
1704
|
+
const canvas = document.createElement("canvas");
|
|
1705
|
+
const dpr = this.dpr;
|
|
1706
|
+
canvas.width = Math.max(1, Math.ceil(width * dpr));
|
|
1707
|
+
canvas.height = Math.max(1, Math.ceil(height * dpr));
|
|
1708
|
+
const ctx = canvas.getContext("2d");
|
|
1709
|
+
if (!ctx) return null;
|
|
1710
|
+
if (dpr !== 1) ctx.scale(dpr, dpr);
|
|
1711
|
+
ctx.font = font;
|
|
1712
|
+
ctx.textBaseline = "alphabetic";
|
|
1713
|
+
ctx.fillStyle = color;
|
|
1714
|
+
ctx.fillText(text, offsetX, offsetY);
|
|
1715
|
+
const raster = { canvas, width, height, offsetX, offsetY };
|
|
1716
|
+
if (this.cache.size >= this.maxEntries) {
|
|
1717
|
+
let toDrop = Math.ceil(this.maxEntries * 0.1);
|
|
1718
|
+
for (const k of this.cache.keys()) {
|
|
1719
|
+
this.cache.delete(k);
|
|
1720
|
+
if (--toDrop <= 0) break;
|
|
1721
|
+
}
|
|
1722
|
+
}
|
|
1723
|
+
this.cache.set(key, raster);
|
|
1724
|
+
return raster;
|
|
1725
|
+
}
|
|
1726
|
+
/** Drop all cached rasters and reset instrumentation. */
|
|
1727
|
+
clear() {
|
|
1728
|
+
this.cache.clear();
|
|
1729
|
+
this._hits = 0;
|
|
1730
|
+
this._misses = 0;
|
|
1731
|
+
}
|
|
1732
|
+
}, _class4);
|
|
1733
|
+
|
|
1734
|
+
|
|
1634
1735
|
|
|
1635
1736
|
|
|
1636
1737
|
|
|
@@ -1639,4 +1740,4 @@ var WebGPUParticleSystemManager = (_class3 = class {
|
|
|
1639
1740
|
|
|
1640
1741
|
|
|
1641
1742
|
|
|
1642
|
-
exports.CanvasRenderer = CanvasRenderer; exports.sanitizeUrl = sanitizeUrl; exports.isSafeUrl = isSafeUrl; exports.SVGRenderer = SVGRenderer; exports.parseColorToRGBA = parseColorToRGBA; exports.createWebGLPointRenderer = createWebGLPointRenderer; exports.WebGPUParticleSystemManager = WebGPUParticleSystemManager;
|
|
1743
|
+
exports.CanvasRenderer = CanvasRenderer; exports.sanitizeUrl = sanitizeUrl; exports.isSafeUrl = isSafeUrl; exports.SVGRenderer = SVGRenderer; exports.parseColorToRGBA = parseColorToRGBA; exports.createWebGLPointRenderer = createWebGLPointRenderer; exports.WebGPUParticleSystemManager = WebGPUParticleSystemManager; exports.TextRasterCache = TextRasterCache;
|
|
@@ -29,6 +29,8 @@ var CanvasRenderer = class _CanvasRenderer {
|
|
|
29
29
|
batchColor = "";
|
|
30
30
|
batchAlpha = 1;
|
|
31
31
|
batchCount = 0;
|
|
32
|
+
_cachedFont = "";
|
|
33
|
+
_cachedFill = "";
|
|
32
34
|
/**
|
|
33
35
|
* @param canvas - The target canvas. Its backing store is resized to the
|
|
34
36
|
* logical size × devicePixelRatio.
|
|
@@ -99,6 +101,8 @@ var CanvasRenderer = class _CanvasRenderer {
|
|
|
99
101
|
restore() {
|
|
100
102
|
this.flush();
|
|
101
103
|
this.ctx.restore();
|
|
104
|
+
this._cachedFont = "";
|
|
105
|
+
this._cachedFill = "";
|
|
102
106
|
}
|
|
103
107
|
/** @inheritdoc */
|
|
104
108
|
translate(x, y) {
|
|
@@ -186,7 +190,10 @@ var CanvasRenderer = class _CanvasRenderer {
|
|
|
186
190
|
/** @inheritdoc */
|
|
187
191
|
fill(color) {
|
|
188
192
|
this.flush();
|
|
189
|
-
this.
|
|
193
|
+
if (this._cachedFill !== color) {
|
|
194
|
+
this.ctx.fillStyle = color;
|
|
195
|
+
this._cachedFill = color;
|
|
196
|
+
}
|
|
190
197
|
this.ctx.fill();
|
|
191
198
|
}
|
|
192
199
|
/** @inheritdoc */
|
|
@@ -201,8 +208,14 @@ var CanvasRenderer = class _CanvasRenderer {
|
|
|
201
208
|
/** @inheritdoc */
|
|
202
209
|
fillText(text, x, y, font, color) {
|
|
203
210
|
this.flush();
|
|
204
|
-
this.
|
|
205
|
-
|
|
211
|
+
if (this._cachedFont !== font) {
|
|
212
|
+
this.ctx.font = font;
|
|
213
|
+
this._cachedFont = font;
|
|
214
|
+
}
|
|
215
|
+
if (this._cachedFill !== color) {
|
|
216
|
+
this.ctx.fillStyle = color;
|
|
217
|
+
this._cachedFill = color;
|
|
218
|
+
}
|
|
206
219
|
this.ctx.fillText(text, x, y);
|
|
207
220
|
}
|
|
208
221
|
/** @inheritdoc */
|
|
@@ -223,6 +236,8 @@ var CanvasRenderer = class _CanvasRenderer {
|
|
|
223
236
|
this.batchColor = "";
|
|
224
237
|
this.batchAlpha = 1;
|
|
225
238
|
this.batchActive = false;
|
|
239
|
+
this._cachedFont = "";
|
|
240
|
+
this._cachedFill = "";
|
|
226
241
|
}
|
|
227
242
|
};
|
|
228
243
|
|
|
@@ -1631,6 +1646,91 @@ var WebGPUParticleSystemManager = class {
|
|
|
1631
1646
|
}
|
|
1632
1647
|
};
|
|
1633
1648
|
|
|
1649
|
+
// src/renderer/TextRasterCache.ts
|
|
1650
|
+
var TextRasterCache = class {
|
|
1651
|
+
cache = /* @__PURE__ */ new Map();
|
|
1652
|
+
maxEntries;
|
|
1653
|
+
dpr;
|
|
1654
|
+
scratchCtx = null;
|
|
1655
|
+
_hits = 0;
|
|
1656
|
+
_misses = 0;
|
|
1657
|
+
constructor(options = {}) {
|
|
1658
|
+
this.maxEntries = Math.max(1, options.maxEntries ?? 4096);
|
|
1659
|
+
this.dpr = Math.max(1, options.dpr ?? 1);
|
|
1660
|
+
}
|
|
1661
|
+
/** Live instrumentation snapshot. */
|
|
1662
|
+
get stats() {
|
|
1663
|
+
return { hits: this._hits, misses: this._misses, size: this.cache.size };
|
|
1664
|
+
}
|
|
1665
|
+
measureCtx() {
|
|
1666
|
+
if (!this.scratchCtx) {
|
|
1667
|
+
if (typeof document === "undefined") return null;
|
|
1668
|
+
this.scratchCtx = document.createElement("canvas").getContext("2d");
|
|
1669
|
+
}
|
|
1670
|
+
return this.scratchCtx;
|
|
1671
|
+
}
|
|
1672
|
+
/**
|
|
1673
|
+
* Return the cached raster for a text run, rasterizing it on first request.
|
|
1674
|
+
*
|
|
1675
|
+
* @param font - Full CSS `font` shorthand, used for both measuring and painting.
|
|
1676
|
+
* @param color - CSS color baked into the raster.
|
|
1677
|
+
* @param text - The run to rasterize (may contain CJK / emoji).
|
|
1678
|
+
* @returns The raster, or `null` in a non-DOM/headless context (caller should
|
|
1679
|
+
* fall back to {@link IRenderer.fillText}).
|
|
1680
|
+
*/
|
|
1681
|
+
get(font, color, text) {
|
|
1682
|
+
const key = font + "\0" + color + "\0" + text;
|
|
1683
|
+
const hit = this.cache.get(key);
|
|
1684
|
+
if (hit) {
|
|
1685
|
+
this._hits++;
|
|
1686
|
+
return hit;
|
|
1687
|
+
}
|
|
1688
|
+
this._misses++;
|
|
1689
|
+
const m = this.measureCtx();
|
|
1690
|
+
if (!m) return null;
|
|
1691
|
+
m.font = font;
|
|
1692
|
+
const metrics = m.measureText(text);
|
|
1693
|
+
const approx = metrics.width || text.length * 8;
|
|
1694
|
+
const ascent = metrics.actualBoundingBoxAscent || metrics.fontBoundingBoxAscent || approx * 0.8;
|
|
1695
|
+
const descent = metrics.actualBoundingBoxDescent || metrics.fontBoundingBoxDescent || approx * 0.2;
|
|
1696
|
+
const left = metrics.actualBoundingBoxLeft || 0;
|
|
1697
|
+
const right = metrics.actualBoundingBoxRight || metrics.width;
|
|
1698
|
+
const pad = 2;
|
|
1699
|
+
const offsetX = Math.ceil(left) + pad;
|
|
1700
|
+
const offsetY = Math.ceil(ascent) + pad;
|
|
1701
|
+
const width = offsetX + Math.ceil(right) + pad;
|
|
1702
|
+
const height = offsetY + Math.ceil(descent) + pad;
|
|
1703
|
+
if (typeof document === "undefined") return null;
|
|
1704
|
+
const canvas = document.createElement("canvas");
|
|
1705
|
+
const dpr = this.dpr;
|
|
1706
|
+
canvas.width = Math.max(1, Math.ceil(width * dpr));
|
|
1707
|
+
canvas.height = Math.max(1, Math.ceil(height * dpr));
|
|
1708
|
+
const ctx = canvas.getContext("2d");
|
|
1709
|
+
if (!ctx) return null;
|
|
1710
|
+
if (dpr !== 1) ctx.scale(dpr, dpr);
|
|
1711
|
+
ctx.font = font;
|
|
1712
|
+
ctx.textBaseline = "alphabetic";
|
|
1713
|
+
ctx.fillStyle = color;
|
|
1714
|
+
ctx.fillText(text, offsetX, offsetY);
|
|
1715
|
+
const raster = { canvas, width, height, offsetX, offsetY };
|
|
1716
|
+
if (this.cache.size >= this.maxEntries) {
|
|
1717
|
+
let toDrop = Math.ceil(this.maxEntries * 0.1);
|
|
1718
|
+
for (const k of this.cache.keys()) {
|
|
1719
|
+
this.cache.delete(k);
|
|
1720
|
+
if (--toDrop <= 0) break;
|
|
1721
|
+
}
|
|
1722
|
+
}
|
|
1723
|
+
this.cache.set(key, raster);
|
|
1724
|
+
return raster;
|
|
1725
|
+
}
|
|
1726
|
+
/** Drop all cached rasters and reset instrumentation. */
|
|
1727
|
+
clear() {
|
|
1728
|
+
this.cache.clear();
|
|
1729
|
+
this._hits = 0;
|
|
1730
|
+
this._misses = 0;
|
|
1731
|
+
}
|
|
1732
|
+
};
|
|
1733
|
+
|
|
1634
1734
|
export {
|
|
1635
1735
|
CanvasRenderer,
|
|
1636
1736
|
sanitizeUrl,
|
|
@@ -1638,5 +1738,6 @@ export {
|
|
|
1638
1738
|
SVGRenderer,
|
|
1639
1739
|
parseColorToRGBA,
|
|
1640
1740
|
createWebGLPointRenderer,
|
|
1641
|
-
WebGPUParticleSystemManager
|
|
1741
|
+
WebGPUParticleSystemManager,
|
|
1742
|
+
TextRasterCache
|
|
1642
1743
|
};
|
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/TextRasterCache';
|
|
8
9
|
export * from './tree/Entity';
|
|
9
10
|
export * from './tree/Scene';
|
|
10
11
|
export * from './components/TextEntity';
|
package/dist/index.js
CHANGED
|
@@ -12,7 +12,8 @@ var _chunkBA5HUUDFjs = require('./chunk-BA5HUUDF.js');
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
|
|
16
|
+
var _chunkBEUIB3U7js = require('./chunk-BEUIB3U7.js');
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
|
|
@@ -179,6 +180,37 @@ var ComputeParticleEntity = (_class = class extends _chunk2Z23LTH3js.Entity {
|
|
|
179
180
|
}
|
|
180
181
|
render(_r) {
|
|
181
182
|
}
|
|
183
|
+
/**
|
|
184
|
+
* True while any live particle still has a visually meaningful velocity or
|
|
185
|
+
* sits meaningfully away from its spring target.
|
|
186
|
+
*
|
|
187
|
+
* The actual per-frame simulation (WebGPU compute pass / {@link updateCPU})
|
|
188
|
+
* runs through a dedicated Scene-level pre-pass outside the normal
|
|
189
|
+
* render-tree walk, but this entity is still a normal tree member that
|
|
190
|
+
* walk visits — without this override, the base `Entity.hasPendingAnimations()`
|
|
191
|
+
* (always `false`) is what Scene sees, so `renderMode: 'always'`'s idle
|
|
192
|
+
* auto-throttle drops the WHOLE scene to ~2fps the instant nothing else in
|
|
193
|
+
* the tree is animating, even while thousands of particles are visibly
|
|
194
|
+
* drifting or spring-settling. A spring+damping system asymptotically
|
|
195
|
+
* approaches zero velocity but never reaches it exactly, so this checks
|
|
196
|
+
* against a small epsilon rather than `!== 0` — otherwise this would
|
|
197
|
+
* always return `true` and defeat the idle throttle entirely.
|
|
198
|
+
*/
|
|
199
|
+
hasPendingAnimations() {
|
|
200
|
+
const EPS_VELOCITY = 0.5;
|
|
201
|
+
const EPS_DISTANCE = 0.5;
|
|
202
|
+
for (let i = 0; i < this.maxParticles; i++) {
|
|
203
|
+
const idx = i * PARTICLE_STRIDE_FLOATS;
|
|
204
|
+
if (this.particleData[idx + PARTICLE_OFFSET_LIFE] === 0) continue;
|
|
205
|
+
const vx = this.particleData[idx + PARTICLE_OFFSET_VELOCITY_X];
|
|
206
|
+
const vy = this.particleData[idx + PARTICLE_OFFSET_VELOCITY_Y];
|
|
207
|
+
if (vx * vx + vy * vy > EPS_VELOCITY * EPS_VELOCITY) return true;
|
|
208
|
+
const dx = this.particleData[idx + PARTICLE_OFFSET_POSITION_X] - this.particleData[idx + PARTICLE_OFFSET_ORIGIN_X];
|
|
209
|
+
const dy = this.particleData[idx + PARTICLE_OFFSET_POSITION_Y] - this.particleData[idx + PARTICLE_OFFSET_ORIGIN_Y];
|
|
210
|
+
if (dx * dx + dy * dy > EPS_DISTANCE * EPS_DISTANCE) return true;
|
|
211
|
+
}
|
|
212
|
+
return this.pendingExplosion !== null;
|
|
213
|
+
}
|
|
182
214
|
/**
|
|
183
215
|
* Updates particle simulation on the CPU.
|
|
184
216
|
* Handles spring forces, mouse repulsion, explosion impulses, velocity capping, and bounds bouncing/clamping.
|
|
@@ -847,7 +879,7 @@ var Scene = (_class2 = class _Scene {
|
|
|
847
879
|
if (options.renderer) {
|
|
848
880
|
this.renderer = options.renderer;
|
|
849
881
|
} else {
|
|
850
|
-
this.renderer = new (0,
|
|
882
|
+
this.renderer = new (0, _chunkBEUIB3U7js.CanvasRenderer)(
|
|
851
883
|
canvas,
|
|
852
884
|
this.disableWindowResize ? { width: this.width, height: this.height } : void 0,
|
|
853
885
|
this.maxDPR
|
|
@@ -1490,7 +1522,7 @@ var Scene = (_class2 = class _Scene {
|
|
|
1490
1522
|
this.syncOptionalAttribute(
|
|
1491
1523
|
el,
|
|
1492
1524
|
"href",
|
|
1493
|
-
attrs.href === void 0 ? void 0 :
|
|
1525
|
+
attrs.href === void 0 ? void 0 : _chunkBEUIB3U7js.sanitizeUrl.call(void 0, attrs.href)
|
|
1494
1526
|
);
|
|
1495
1527
|
this.syncOptionalAttribute(el, "target", attrs.target);
|
|
1496
1528
|
}
|
|
@@ -2519,7 +2551,7 @@ var Scene = (_class2 = class _Scene {
|
|
|
2519
2551
|
* Export the current scene state to a lightweight, flat SVG XML string.
|
|
2520
2552
|
*/
|
|
2521
2553
|
toSVG() {
|
|
2522
|
-
const renderer = new (0,
|
|
2554
|
+
const renderer = new (0, _chunkBEUIB3U7js.SVGRenderer)(this.width, this.height);
|
|
2523
2555
|
this.render(renderer, 0, 0);
|
|
2524
2556
|
return renderer.toXMLString();
|
|
2525
2557
|
}
|
|
@@ -3536,8 +3568,9 @@ var DOMPortalEntity = (_class7 = class extends _chunk2Z23LTH3js.Entity {
|
|
|
3536
3568
|
}, _class7);
|
|
3537
3569
|
|
|
3538
3570
|
// src/index.ts
|
|
3539
|
-
Scene.registerWebGLPointRendererCreator(
|
|
3540
|
-
Scene.registerWebGPUParticleSystemManager(
|
|
3571
|
+
Scene.registerWebGLPointRendererCreator(_chunkBEUIB3U7js.createWebGLPointRenderer);
|
|
3572
|
+
Scene.registerWebGPUParticleSystemManager(_chunkBEUIB3U7js.WebGPUParticleSystemManager);
|
|
3573
|
+
|
|
3541
3574
|
|
|
3542
3575
|
|
|
3543
3576
|
|
|
@@ -3588,4 +3621,4 @@ Scene.registerWebGPUParticleSystemManager(_chunkORCPCIJ7js.WebGPUParticleSystemM
|
|
|
3588
3621
|
|
|
3589
3622
|
|
|
3590
3623
|
|
|
3591
|
-
exports.ArabicShaper = _chunk4AR425ARjs.ArabicShaper; exports.BidiResolver = _chunk4AR425ARjs.BidiResolver; exports.CanvasRenderer =
|
|
3624
|
+
exports.ArabicShaper = _chunk4AR425ARjs.ArabicShaper; exports.BidiResolver = _chunk4AR425ARjs.BidiResolver; exports.CanvasRenderer = _chunkBEUIB3U7js.CanvasRenderer; exports.Circle = Circle; exports.ComputeParticleEntity = ComputeParticleEntity; exports.DOMPortalEntity = DOMPortalEntity; exports.Easing = _chunk2Z23LTH3js.Easing; exports.Entity = _chunk2Z23LTH3js.Entity; exports.GridTextEntity = GridTextEntity; exports.Group = Group; exports.LayoutEngine = _chunkBA5HUUDFjs.LayoutEngine; exports.LayoutResultBuffer = _chunkBA5HUUDFjs.LayoutResultBuffer; exports.LayoutWorkerManager = _chunk4AR425ARjs.LayoutWorkerManager; exports.MSDFFont = _chunk2Z23LTH3js.MSDFFont; exports.MSDFTextEntity = _chunk2Z23LTH3js.MSDFTextEntity; exports.PARTICLE_OFFSET_LIFE = PARTICLE_OFFSET_LIFE; exports.PARTICLE_OFFSET_ORIGIN_X = PARTICLE_OFFSET_ORIGIN_X; exports.PARTICLE_OFFSET_ORIGIN_Y = PARTICLE_OFFSET_ORIGIN_Y; exports.PARTICLE_OFFSET_POSITION_X = PARTICLE_OFFSET_POSITION_X; exports.PARTICLE_OFFSET_POSITION_Y = PARTICLE_OFFSET_POSITION_Y; exports.PARTICLE_OFFSET_SIZE = PARTICLE_OFFSET_SIZE; exports.PARTICLE_OFFSET_VELOCITY_X = PARTICLE_OFFSET_VELOCITY_X; exports.PARTICLE_OFFSET_VELOCITY_Y = PARTICLE_OFFSET_VELOCITY_Y; exports.PARTICLE_STRIDE_FLOATS = PARTICLE_STRIDE_FLOATS; exports.REDUCED_MOTION_FPS = REDUCED_MOTION_FPS; exports.Rect = Rect; exports.SVGEntity = _chunk2Z23LTH3js.SVGEntity; exports.SVGRenderer = _chunkBEUIB3U7js.SVGRenderer; exports.Scene = Scene; exports.SpatialHashGrid = SpatialHashGrid; exports.SplineEntity = SplineEntity; exports.SpringDriver = _chunk2Z23LTH3js.SpringDriver; exports.SpringPhysics = _chunk2Z23LTH3js.SpringPhysics; exports.TextEntity = TextEntity; exports.TextRasterCache = _chunkBEUIB3U7js.TextRasterCache; exports.TweenDriver = _chunk2Z23LTH3js.TweenDriver; exports.VectoJSEvent = _chunk2Z23LTH3js.VectoJSEvent; exports.WebGPUParticleSystemManager = _chunkBEUIB3U7js.WebGPUParticleSystemManager; exports.clearCssLineBoxMetrics = _chunk2Z23LTH3js.clearCssLineBoxMetrics; exports.computeLineSegments = _chunkBA5HUUDFjs.computeLineSegments; exports.createCanvasMeasurer = _chunkBA5HUUDFjs.createCanvasMeasurer; exports.createWebGLPointRenderer = _chunkBEUIB3U7js.createWebGLPointRenderer; exports.cssLineBoxBaseline = _chunk2Z23LTH3js.cssLineBoxBaseline; exports.isSafeUrl = _chunkBEUIB3U7js.isSafeUrl; exports.isTweenConfig = _chunk2Z23LTH3js.isTweenConfig; exports.loadSpline = loadSpline; exports.parseColorToRGBA = _chunkBEUIB3U7js.parseColorToRGBA; exports.polySegmentToBezier = polySegmentToBezier; exports.prepareContentGrid = _chunk2Z23LTH3js.prepareContentGrid; exports.sanitizeUrl = _chunkBEUIB3U7js.sanitizeUrl;
|
package/dist/index.mjs
CHANGED
|
@@ -7,12 +7,13 @@ import {
|
|
|
7
7
|
import {
|
|
8
8
|
CanvasRenderer,
|
|
9
9
|
SVGRenderer,
|
|
10
|
+
TextRasterCache,
|
|
10
11
|
WebGPUParticleSystemManager,
|
|
11
12
|
createWebGLPointRenderer,
|
|
12
13
|
isSafeUrl,
|
|
13
14
|
parseColorToRGBA,
|
|
14
15
|
sanitizeUrl
|
|
15
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-L5BCKFQE.mjs";
|
|
16
17
|
import {
|
|
17
18
|
Easing,
|
|
18
19
|
Entity,
|
|
@@ -179,6 +180,37 @@ var ComputeParticleEntity = class extends Entity {
|
|
|
179
180
|
}
|
|
180
181
|
render(_r) {
|
|
181
182
|
}
|
|
183
|
+
/**
|
|
184
|
+
* True while any live particle still has a visually meaningful velocity or
|
|
185
|
+
* sits meaningfully away from its spring target.
|
|
186
|
+
*
|
|
187
|
+
* The actual per-frame simulation (WebGPU compute pass / {@link updateCPU})
|
|
188
|
+
* runs through a dedicated Scene-level pre-pass outside the normal
|
|
189
|
+
* render-tree walk, but this entity is still a normal tree member that
|
|
190
|
+
* walk visits — without this override, the base `Entity.hasPendingAnimations()`
|
|
191
|
+
* (always `false`) is what Scene sees, so `renderMode: 'always'`'s idle
|
|
192
|
+
* auto-throttle drops the WHOLE scene to ~2fps the instant nothing else in
|
|
193
|
+
* the tree is animating, even while thousands of particles are visibly
|
|
194
|
+
* drifting or spring-settling. A spring+damping system asymptotically
|
|
195
|
+
* approaches zero velocity but never reaches it exactly, so this checks
|
|
196
|
+
* against a small epsilon rather than `!== 0` — otherwise this would
|
|
197
|
+
* always return `true` and defeat the idle throttle entirely.
|
|
198
|
+
*/
|
|
199
|
+
hasPendingAnimations() {
|
|
200
|
+
const EPS_VELOCITY = 0.5;
|
|
201
|
+
const EPS_DISTANCE = 0.5;
|
|
202
|
+
for (let i = 0; i < this.maxParticles; i++) {
|
|
203
|
+
const idx = i * PARTICLE_STRIDE_FLOATS;
|
|
204
|
+
if (this.particleData[idx + PARTICLE_OFFSET_LIFE] === 0) continue;
|
|
205
|
+
const vx = this.particleData[idx + PARTICLE_OFFSET_VELOCITY_X];
|
|
206
|
+
const vy = this.particleData[idx + PARTICLE_OFFSET_VELOCITY_Y];
|
|
207
|
+
if (vx * vx + vy * vy > EPS_VELOCITY * EPS_VELOCITY) return true;
|
|
208
|
+
const dx = this.particleData[idx + PARTICLE_OFFSET_POSITION_X] - this.particleData[idx + PARTICLE_OFFSET_ORIGIN_X];
|
|
209
|
+
const dy = this.particleData[idx + PARTICLE_OFFSET_POSITION_Y] - this.particleData[idx + PARTICLE_OFFSET_ORIGIN_Y];
|
|
210
|
+
if (dx * dx + dy * dy > EPS_DISTANCE * EPS_DISTANCE) return true;
|
|
211
|
+
}
|
|
212
|
+
return this.pendingExplosion !== null;
|
|
213
|
+
}
|
|
182
214
|
/**
|
|
183
215
|
* Updates particle simulation on the CPU.
|
|
184
216
|
* Handles spring forces, mouse repulsion, explosion impulses, velocity capping, and bounds bouncing/clamping.
|
|
@@ -3573,6 +3605,7 @@ export {
|
|
|
3573
3605
|
SpringDriver,
|
|
3574
3606
|
SpringPhysics,
|
|
3575
3607
|
TextEntity,
|
|
3608
|
+
TextRasterCache,
|
|
3576
3609
|
TweenDriver,
|
|
3577
3610
|
VectoJSEvent,
|
|
3578
3611
|
WebGPUParticleSystemManager,
|
|
@@ -23,6 +23,8 @@ export declare class CanvasRenderer implements IRenderer {
|
|
|
23
23
|
private batchColor;
|
|
24
24
|
private batchAlpha;
|
|
25
25
|
private batchCount;
|
|
26
|
+
private _cachedFont;
|
|
27
|
+
private _cachedFill;
|
|
26
28
|
/**
|
|
27
29
|
* @param canvas - The target canvas. Its backing store is resized to the
|
|
28
30
|
* logical size × devicePixelRatio.
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A cache of pre-rasterized text runs, so a scene that draws the *same short
|
|
3
|
+
* strings* thousands of times per frame pays the Canvas2D text cost once per
|
|
4
|
+
* unique run instead of once per draw.
|
|
5
|
+
*
|
|
6
|
+
* ## Why
|
|
7
|
+
*
|
|
8
|
+
* `ctx.fillText()` is deceptively expensive at scale: each call re-shapes the
|
|
9
|
+
* string, re-parses the CSS `fillStyle` color, and rasterizes glyphs on the CPU
|
|
10
|
+
* main thread. When a view draws thousands of independently-positioned text
|
|
11
|
+
* runs per frame (danmaku/barrage, chat/log tails, data-grid cells, particle
|
|
12
|
+
* labels), that per-call shaping dominates the frame — a CPU profile shows the
|
|
13
|
+
* main thread pegged in native (`(program)`) code while the GPU sits idle,
|
|
14
|
+
* because the GPU can't be fed fast enough.
|
|
15
|
+
*
|
|
16
|
+
* This cache rasterizes each distinct `(font, color, text)` run to a small
|
|
17
|
+
* offscreen canvas exactly once. Every subsequent frame the caller blits it
|
|
18
|
+
* with a single {@link IRenderer.drawImage}, turning CPU text shaping into a
|
|
19
|
+
* GPU-friendly bitmap copy. When the set of distinct runs is bounded (a fixed
|
|
20
|
+
* phrase library, a small palette, a handful of font sizes) the steady-state
|
|
21
|
+
* hit rate approaches 100%; an insertion-order eviction cap bounds memory
|
|
22
|
+
* against unbounded (e.g. user-typed) content.
|
|
23
|
+
*
|
|
24
|
+
* It does **not** replace `fillText` for large or highly-varied text — the win
|
|
25
|
+
* comes from *reuse*. Emoji and color glyphs rasterize fine (they're baked into
|
|
26
|
+
* the bitmap), but a run that's only ever drawn once is pure overhead.
|
|
27
|
+
*
|
|
28
|
+
* ## Use
|
|
29
|
+
*
|
|
30
|
+
* ```ts
|
|
31
|
+
* const cache = new TextRasterCache();
|
|
32
|
+
* // in the frame loop, per text run:
|
|
33
|
+
* const r = cache.get('600 24px system-ui', '#38bdf8', label);
|
|
34
|
+
* if (r) renderer.drawImage(r.canvas, x - r.offsetX, baselineY - r.offsetY, r.width, r.height);
|
|
35
|
+
* else renderer.fillText(label, x, baselineY, '600 24px system-ui', '#38bdf8'); // headless fallback
|
|
36
|
+
* ```
|
|
37
|
+
*
|
|
38
|
+
* The blit position mirrors the `fillText` baseline: pass the glyph origin `x`
|
|
39
|
+
* and the text baseline `y`, then subtract the raster's `offsetX`/`offsetY`.
|
|
40
|
+
*/
|
|
41
|
+
/** A rasterized text run plus the offsets needed to blit it at a baseline. */
|
|
42
|
+
export interface TextRaster {
|
|
43
|
+
/** The offscreen canvas holding the rasterized run. */
|
|
44
|
+
canvas: HTMLCanvasElement;
|
|
45
|
+
/** Blit destination width in CSS pixels. */
|
|
46
|
+
width: number;
|
|
47
|
+
/** Blit destination height in CSS pixels. */
|
|
48
|
+
height: number;
|
|
49
|
+
/** Left inset (CSS px) of the glyph origin inside the canvas. */
|
|
50
|
+
offsetX: number;
|
|
51
|
+
/** Distance (CSS px) from the canvas top down to the text baseline. */
|
|
52
|
+
offsetY: number;
|
|
53
|
+
}
|
|
54
|
+
/** Instrumentation counters for the cache (e.g. to surface a HUD hit rate). */
|
|
55
|
+
export interface TextRasterStats {
|
|
56
|
+
/** Requests served from an existing raster. */
|
|
57
|
+
hits: number;
|
|
58
|
+
/** Requests that had to rasterize a new run. */
|
|
59
|
+
misses: number;
|
|
60
|
+
/** Current number of cached rasters. */
|
|
61
|
+
size: number;
|
|
62
|
+
}
|
|
63
|
+
/** Options for {@link TextRasterCache}. */
|
|
64
|
+
export interface TextRasterCacheOptions {
|
|
65
|
+
/**
|
|
66
|
+
* Hard cap on cached rasters. On overflow the oldest ~10% (insertion order,
|
|
67
|
+
* which `Map` preserves) are evicted; evicted runs are re-rasterized cheaply
|
|
68
|
+
* on their next miss. Default `4096`.
|
|
69
|
+
*/
|
|
70
|
+
maxEntries?: number;
|
|
71
|
+
/**
|
|
72
|
+
* Device-pixel-ratio the rasters are rendered at. `> 1` keeps text crisp on
|
|
73
|
+
* HiDPI displays at the cost of larger bitmaps; the returned `width`/`height`
|
|
74
|
+
* stay in CSS pixels so the blit is unchanged. Default `1`.
|
|
75
|
+
*/
|
|
76
|
+
dpr?: number;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* An isolated text-raster cache. Create one per renderer/scene (instances don't
|
|
80
|
+
* share state, so multiple scenes or an SSR pass never collide).
|
|
81
|
+
*/
|
|
82
|
+
export declare class TextRasterCache {
|
|
83
|
+
private readonly cache;
|
|
84
|
+
private readonly maxEntries;
|
|
85
|
+
private readonly dpr;
|
|
86
|
+
private scratchCtx;
|
|
87
|
+
private _hits;
|
|
88
|
+
private _misses;
|
|
89
|
+
constructor(options?: TextRasterCacheOptions);
|
|
90
|
+
/** Live instrumentation snapshot. */
|
|
91
|
+
get stats(): TextRasterStats;
|
|
92
|
+
private measureCtx;
|
|
93
|
+
/**
|
|
94
|
+
* Return the cached raster for a text run, rasterizing it on first request.
|
|
95
|
+
*
|
|
96
|
+
* @param font - Full CSS `font` shorthand, used for both measuring and painting.
|
|
97
|
+
* @param color - CSS color baked into the raster.
|
|
98
|
+
* @param text - The run to rasterize (may contain CJK / emoji).
|
|
99
|
+
* @returns The raster, or `null` in a non-DOM/headless context (caller should
|
|
100
|
+
* fall back to {@link IRenderer.fillText}).
|
|
101
|
+
*/
|
|
102
|
+
get(font: string, color: string, text: string): TextRaster | null;
|
|
103
|
+
/** Drop all cached rasters and reset instrumentation. */
|
|
104
|
+
clear(): void;
|
|
105
|
+
}
|
package/dist/renderer/index.d.ts
CHANGED
package/dist/renderer.js
CHANGED
|
@@ -4,11 +4,13 @@
|
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
var _chunkORCPCIJ7js = require('./chunk-ORCPCIJ7.js');
|
|
8
7
|
|
|
8
|
+
var _chunkBEUIB3U7js = require('./chunk-BEUIB3U7.js');
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
exports.CanvasRenderer = _chunkBEUIB3U7js.CanvasRenderer; exports.SVGRenderer = _chunkBEUIB3U7js.SVGRenderer; exports.TextRasterCache = _chunkBEUIB3U7js.TextRasterCache; exports.WebGPUParticleSystemManager = _chunkBEUIB3U7js.WebGPUParticleSystemManager; exports.createWebGLPointRenderer = _chunkBEUIB3U7js.createWebGLPointRenderer; exports.parseColorToRGBA = _chunkBEUIB3U7js.parseColorToRGBA;
|
package/dist/renderer.mjs
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
CanvasRenderer,
|
|
3
3
|
SVGRenderer,
|
|
4
|
+
TextRasterCache,
|
|
4
5
|
WebGPUParticleSystemManager,
|
|
5
6
|
createWebGLPointRenderer,
|
|
6
7
|
parseColorToRGBA
|
|
7
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-L5BCKFQE.mjs";
|
|
8
9
|
export {
|
|
9
10
|
CanvasRenderer,
|
|
10
11
|
SVGRenderer,
|
|
12
|
+
TextRasterCache,
|
|
11
13
|
WebGPUParticleSystemManager,
|
|
12
14
|
createWebGLPointRenderer,
|
|
13
15
|
parseColorToRGBA
|
|
@@ -99,6 +99,23 @@ export declare class ComputeParticleEntity extends Entity {
|
|
|
99
99
|
triggerExplosion(x: number, y: number, force: number): void;
|
|
100
100
|
isPointInside(_x: number, _y: number): boolean;
|
|
101
101
|
render(_r: IRenderer): void;
|
|
102
|
+
/**
|
|
103
|
+
* True while any live particle still has a visually meaningful velocity or
|
|
104
|
+
* sits meaningfully away from its spring target.
|
|
105
|
+
*
|
|
106
|
+
* The actual per-frame simulation (WebGPU compute pass / {@link updateCPU})
|
|
107
|
+
* runs through a dedicated Scene-level pre-pass outside the normal
|
|
108
|
+
* render-tree walk, but this entity is still a normal tree member that
|
|
109
|
+
* walk visits — without this override, the base `Entity.hasPendingAnimations()`
|
|
110
|
+
* (always `false`) is what Scene sees, so `renderMode: 'always'`'s idle
|
|
111
|
+
* auto-throttle drops the WHOLE scene to ~2fps the instant nothing else in
|
|
112
|
+
* the tree is animating, even while thousands of particles are visibly
|
|
113
|
+
* drifting or spring-settling. A spring+damping system asymptotically
|
|
114
|
+
* approaches zero velocity but never reaches it exactly, so this checks
|
|
115
|
+
* against a small epsilon rather than `!== 0` — otherwise this would
|
|
116
|
+
* always return `true` and defeat the idle throttle entirely.
|
|
117
|
+
*/
|
|
118
|
+
hasPendingAnimations(): boolean;
|
|
102
119
|
/**
|
|
103
120
|
* Updates particle simulation on the CPU.
|
|
104
121
|
* Handles spring forces, mouse repulsion, explosion impulses, velocity capping, and bounds bouncing/clamping.
|