@vectojs/core 1.38.0 → 1.39.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-QVTCKWDO.mjs → chunk-7PYD5UDX.mjs} +93 -43
- package/dist/{chunk-ZYYP2M5D.js → chunk-KRUDTGNR.js} +168 -76
- package/dist/{chunk-SRXU3ZXK.js → chunk-POPXUPKR.js} +141 -91
- package/dist/{chunk-LWEMD34D.mjs → chunk-RUM2KEDI.mjs} +119 -27
- package/dist/index.js +689 -396
- package/dist/index.mjs +375 -82
- package/dist/renderer/CanvasRenderer.d.ts +6 -0
- package/dist/renderer/SVGRenderer.d.ts +17 -2
- package/dist/renderer/WebGPUParticleSystemManager.d.ts +2 -0
- package/dist/renderer.js +2 -2
- package/dist/renderer.mjs +1 -1
- package/dist/text/MSDFTextEntity.d.ts +0 -1
- package/dist/text.js +2 -2
- package/dist/text.mjs +1 -1
- package/dist/tree/Entity.d.ts +8 -2
- package/dist/tree/Scene.d.ts +106 -0
- package/dist/tree/scene/A11yProjectionManager.d.ts +12 -0
- package/dist/tree/scene/HitTester.d.ts +14 -2
- package/dist/tree/scene/WasmBackendFacade.d.ts +17 -0
- package/dist/tree/scene/keyboard.d.ts +56 -0
- package/dist/wasm/backend.d.ts +7 -7
- package/dist/wasm/vectojs_core.wasm +0 -0
- package/package.json +6 -6
|
@@ -107,6 +107,17 @@ var CanvasRenderer = class _CanvasRenderer {
|
|
|
107
107
|
batchCount = 0;
|
|
108
108
|
_cachedFont = "";
|
|
109
109
|
_cachedFill = "";
|
|
110
|
+
// Stroke-side counterparts of the fill/font caches, read by stroke()'s
|
|
111
|
+
// style-elision branch. Reset wherever the context state can change behind
|
|
112
|
+
// our back: restore(), resize(), contextrestored, dispose().
|
|
113
|
+
_cachedStroke = "";
|
|
114
|
+
_cachedLineWidth = -1;
|
|
115
|
+
_cachedLineCap = "";
|
|
116
|
+
_cachedLineJoin = "";
|
|
117
|
+
// Context-loss listeners, held for removal in dispose() so a canvas that
|
|
118
|
+
// outlives the renderer cannot retain it through these closures.
|
|
119
|
+
_onContextLost;
|
|
120
|
+
_onContextRestored;
|
|
110
121
|
/** Backend discriminator; see {@link IRenderer.kind}. */
|
|
111
122
|
kind = "canvas2d";
|
|
112
123
|
/**
|
|
@@ -160,11 +171,11 @@ var CanvasRenderer = class _CanvasRenderer {
|
|
|
160
171
|
*/
|
|
161
172
|
setupContextLossRecovery() {
|
|
162
173
|
if (typeof this.canvas.addEventListener !== "function") return;
|
|
163
|
-
this.
|
|
174
|
+
this._onContextLost = (e) => {
|
|
164
175
|
e.preventDefault();
|
|
165
176
|
this.contextLost = true;
|
|
166
|
-
}
|
|
167
|
-
this.
|
|
177
|
+
};
|
|
178
|
+
this._onContextRestored = () => {
|
|
168
179
|
const ctx = this.canvas.getContext("2d");
|
|
169
180
|
if (!ctx) return;
|
|
170
181
|
this.ctx = ctx;
|
|
@@ -174,11 +185,17 @@ var CanvasRenderer = class _CanvasRenderer {
|
|
|
174
185
|
ctx.scale(restoredDPR, restoredDPR);
|
|
175
186
|
this._cachedFont = "";
|
|
176
187
|
this._cachedFill = "";
|
|
188
|
+
this._cachedStroke = "";
|
|
189
|
+
this._cachedLineWidth = -1;
|
|
190
|
+
this._cachedLineCap = "";
|
|
191
|
+
this._cachedLineJoin = "";
|
|
177
192
|
this.batchActive = false;
|
|
178
193
|
this.batchCount = 0;
|
|
179
194
|
this.contextLost = false;
|
|
180
195
|
this.contextRestoredCb?.();
|
|
181
|
-
}
|
|
196
|
+
};
|
|
197
|
+
this.canvas.addEventListener("contextlost", this._onContextLost);
|
|
198
|
+
this.canvas.addEventListener("contextrestored", this._onContextRestored);
|
|
182
199
|
}
|
|
183
200
|
/**
|
|
184
201
|
* Expose the underlying `CanvasRenderingContext2D` for operations not
|
|
@@ -228,11 +245,17 @@ var CanvasRenderer = class _CanvasRenderer {
|
|
|
228
245
|
this.height = height;
|
|
229
246
|
this.ctx.canvas.width = width * dpr;
|
|
230
247
|
this.ctx.canvas.height = height * dpr;
|
|
231
|
-
this.ctx.canvas.style
|
|
232
|
-
|
|
248
|
+
if (this.ctx.canvas.style) {
|
|
249
|
+
this.ctx.canvas.style.width = `${width}px`;
|
|
250
|
+
this.ctx.canvas.style.height = `${height}px`;
|
|
251
|
+
}
|
|
233
252
|
this.ctx.scale(dpr, dpr);
|
|
234
253
|
this._cachedFont = "";
|
|
235
254
|
this._cachedFill = "";
|
|
255
|
+
this._cachedStroke = "";
|
|
256
|
+
this._cachedLineWidth = -1;
|
|
257
|
+
this._cachedLineCap = "";
|
|
258
|
+
this._cachedLineJoin = "";
|
|
236
259
|
this.batchActive = false;
|
|
237
260
|
this.batchCount = 0;
|
|
238
261
|
}
|
|
@@ -286,6 +309,10 @@ var CanvasRenderer = class _CanvasRenderer {
|
|
|
286
309
|
this.ctx.restore();
|
|
287
310
|
this._cachedFont = "";
|
|
288
311
|
this._cachedFill = "";
|
|
312
|
+
this._cachedStroke = "";
|
|
313
|
+
this._cachedLineWidth = -1;
|
|
314
|
+
this._cachedLineCap = "";
|
|
315
|
+
this._cachedLineJoin = "";
|
|
289
316
|
}
|
|
290
317
|
/** @inheritdoc */
|
|
291
318
|
translate(x, y) {
|
|
@@ -410,10 +437,17 @@ var CanvasRenderer = class _CanvasRenderer {
|
|
|
410
437
|
stroke(color, lineWidth = 1) {
|
|
411
438
|
this.flush();
|
|
412
439
|
if (this.counters) this.counters.strokes++;
|
|
413
|
-
this.
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
440
|
+
if (this._cachedStroke !== color || this._cachedLineWidth !== lineWidth || this._cachedLineCap !== "round" || this._cachedLineJoin !== "round") {
|
|
441
|
+
if (this.counters) this.counters.stateSwitches++;
|
|
442
|
+
this.ctx.strokeStyle = color;
|
|
443
|
+
this.ctx.lineWidth = lineWidth;
|
|
444
|
+
this.ctx.lineCap = "round";
|
|
445
|
+
this.ctx.lineJoin = "round";
|
|
446
|
+
this._cachedStroke = color;
|
|
447
|
+
this._cachedLineWidth = lineWidth;
|
|
448
|
+
this._cachedLineCap = "round";
|
|
449
|
+
this._cachedLineJoin = "round";
|
|
450
|
+
}
|
|
417
451
|
this.ctx.stroke();
|
|
418
452
|
}
|
|
419
453
|
/** @inheritdoc */
|
|
@@ -452,6 +486,17 @@ var CanvasRenderer = class _CanvasRenderer {
|
|
|
452
486
|
this.batchActive = false;
|
|
453
487
|
this._cachedFont = "";
|
|
454
488
|
this._cachedFill = "";
|
|
489
|
+
this._cachedStroke = "";
|
|
490
|
+
this._cachedLineWidth = -1;
|
|
491
|
+
this._cachedLineCap = "";
|
|
492
|
+
this._cachedLineJoin = "";
|
|
493
|
+
if (typeof this.canvas.removeEventListener === "function") {
|
|
494
|
+
if (this._onContextLost) this.canvas.removeEventListener("contextlost", this._onContextLost);
|
|
495
|
+
if (this._onContextRestored)
|
|
496
|
+
this.canvas.removeEventListener("contextrestored", this._onContextRestored);
|
|
497
|
+
}
|
|
498
|
+
this._onContextLost = void 0;
|
|
499
|
+
this._onContextRestored = void 0;
|
|
455
500
|
}
|
|
456
501
|
};
|
|
457
502
|
|
|
@@ -474,9 +519,13 @@ function hasSafeSchemeOrIsRelative(url) {
|
|
|
474
519
|
return SAFE_SCHEMES.has(`${candidate.toLowerCase()}:`);
|
|
475
520
|
}
|
|
476
521
|
var CHAR_REF = /&(?:#\d+|#x[\da-f]+|colon|tab|newline);/i;
|
|
522
|
+
var MAX_CODE_POINT = 1114111;
|
|
523
|
+
function decodeCodePoint(value) {
|
|
524
|
+
return value <= MAX_CODE_POINT ? String.fromCodePoint(value) : "\uFFFD";
|
|
525
|
+
}
|
|
477
526
|
function decodeCharacterReferences(value) {
|
|
478
527
|
if (value.indexOf("&") < 0 || !CHAR_REF.test(value)) return value;
|
|
479
|
-
return value.replace(/&#x([\da-f]+);/gi, (_, hex) =>
|
|
528
|
+
return value.replace(/&#x([\da-f]+);/gi, (_, hex) => decodeCodePoint(parseInt(hex, 16))).replace(/&#(\d+);/g, (_, dec) => decodeCodePoint(parseInt(dec, 10))).replace(/:/gi, ":").replace(/&tab;/gi, " ").replace(/&newline;/gi, "\n");
|
|
480
529
|
}
|
|
481
530
|
function sanitizeUrl(href) {
|
|
482
531
|
if (typeof href !== "string") return "";
|
|
@@ -578,9 +627,12 @@ var SVGRenderer = class {
|
|
|
578
627
|
// Cache for generated gradient defs
|
|
579
628
|
gradientCounter = 0;
|
|
580
629
|
gradientCache = /* @__PURE__ */ new Map();
|
|
581
|
-
|
|
630
|
+
/** Root font size used to resolve `em`/`rem` font sizes (default 16px). */
|
|
631
|
+
rootFontSize;
|
|
632
|
+
constructor(width, height, options) {
|
|
582
633
|
this.width = width;
|
|
583
634
|
this.height = height;
|
|
635
|
+
this.rootFontSize = options?.rootFontSize ?? 16;
|
|
584
636
|
installRendererDevTraps(this, "SVGRenderer");
|
|
585
637
|
}
|
|
586
638
|
clear() {
|
|
@@ -768,12 +820,22 @@ var SVGRenderer = class {
|
|
|
768
820
|
`<path d="${dStr}" transform="${transformStr}" fill="none" stroke="${strokeVal}" stroke-width="${lineWidth}" stroke-opacity="${this.globalAlpha}" />`
|
|
769
821
|
);
|
|
770
822
|
}
|
|
823
|
+
/**
|
|
824
|
+
* Emit a `<text>` element for a text run.
|
|
825
|
+
*
|
|
826
|
+
* Font-size unit handling: `px` sizes pass through; `em` and `rem` are both
|
|
827
|
+
* scaled against the renderer's `rootFontSize` (constructor option, default
|
|
828
|
+
* 16 — matching the browser default, not necessarily the host page's root,
|
|
829
|
+
* so pass `{ rootFontSize }` when exporting against a non-default root).
|
|
830
|
+
* A percentage size (`'100% Inter'`) has no absolute meaning in this
|
|
831
|
+
* context and silently falls back to the default 16px font size.
|
|
832
|
+
*/
|
|
771
833
|
fillText(text, x, y, font, color) {
|
|
772
834
|
this.flush();
|
|
773
835
|
const sizeToken = parseFontSizeToken(font);
|
|
774
836
|
let fontSize = sizeToken ? sizeToken.value : 16;
|
|
775
|
-
if (sizeToken && sizeToken.unit
|
|
776
|
-
fontSize = fontSize *
|
|
837
|
+
if (sizeToken && (sizeToken.unit === "em" || sizeToken.unit === "rem")) {
|
|
838
|
+
fontSize = fontSize * this.rootFontSize;
|
|
777
839
|
}
|
|
778
840
|
const lowerFont = font.toLowerCase();
|
|
779
841
|
const fontStyle = lowerFont.includes("italic") ? "italic" : lowerFont.includes("oblique") ? "oblique" : "normal";
|
|
@@ -955,11 +1017,11 @@ var SVGRenderer = class {
|
|
|
955
1017
|
}
|
|
956
1018
|
/**
|
|
957
1019
|
* SVGRenderer accumulates strings in memory; nothing external is allocated.
|
|
958
|
-
* Drop the buffers for GC and become idempotent.
|
|
1020
|
+
* Drop the buffers for GC and become idempotent. `clear()` already empties
|
|
1021
|
+
* the gradient cache, so no second clear is needed here.
|
|
959
1022
|
*/
|
|
960
1023
|
dispose() {
|
|
961
1024
|
this.clear();
|
|
962
|
-
this.gradientCache.clear();
|
|
963
1025
|
}
|
|
964
1026
|
};
|
|
965
1027
|
|
|
@@ -1331,13 +1393,22 @@ function createWebGLPointRenderer(canvas) {
|
|
|
1331
1393
|
premultipliedAlpha: false
|
|
1332
1394
|
});
|
|
1333
1395
|
if (!gl) return null;
|
|
1334
|
-
const
|
|
1335
|
-
const
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1396
|
+
const programs = [];
|
|
1397
|
+
const linkTracked = (vsSrc, fsSrc) => {
|
|
1398
|
+
const program = link(gl, vsSrc, fsSrc);
|
|
1399
|
+
if (program) programs.push(program);
|
|
1400
|
+
return program;
|
|
1401
|
+
};
|
|
1402
|
+
const pointProgram = linkTracked(POINT_VERT, POINT_FRAG);
|
|
1403
|
+
const rectProgram = linkTracked(RECT_VERT, RECT_FRAG);
|
|
1404
|
+
const spriteProgram = linkTracked(SPRITE_VERT, SPRITE_FRAG);
|
|
1405
|
+
const msdfProgram = linkTracked(SPRITE_VERT, MSDF_FRAG);
|
|
1406
|
+
const circleQuadProgram = linkTracked(SPRITE_VERT, CIRCLE_QUAD_FRAG);
|
|
1407
|
+
if (!pointProgram || !rectProgram || !spriteProgram || !msdfProgram || !circleQuadProgram) {
|
|
1408
|
+
for (const program of programs) gl.deleteProgram(program);
|
|
1409
|
+
gl.getExtension("WEBGL_lose_context")?.loseContext();
|
|
1340
1410
|
return null;
|
|
1411
|
+
}
|
|
1341
1412
|
gl.enable(gl.BLEND);
|
|
1342
1413
|
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
|
1343
1414
|
const pAPos = gl.getAttribLocation(pointProgram, "a_pos");
|
|
@@ -1517,6 +1588,7 @@ function createWebGLPointRenderer(canvas) {
|
|
|
1517
1588
|
ensureQuadIndices(spriteCount);
|
|
1518
1589
|
gl.drawElements(gl.TRIANGLES, spriteCount * INDICES_PER_QUAD, gl.UNSIGNED_INT, 0);
|
|
1519
1590
|
frameDrawCalls++;
|
|
1591
|
+
gl.bindVertexArray(null);
|
|
1520
1592
|
spriteCount = 0;
|
|
1521
1593
|
};
|
|
1522
1594
|
const drawGlyphs = () => {
|
|
@@ -1795,6 +1867,7 @@ function createWebGLPointRenderer(canvas) {
|
|
|
1795
1867
|
}
|
|
1796
1868
|
|
|
1797
1869
|
// src/renderer/WebGPUParticleSystemManager.ts
|
|
1870
|
+
var recordComputePassScratch = new Float32Array(20);
|
|
1798
1871
|
var COMPUTE_SHADER = `
|
|
1799
1872
|
struct Particle {
|
|
1800
1873
|
position: vec2<f32>,
|
|
@@ -1971,14 +2044,20 @@ var WebGPUParticleSystemManager = class {
|
|
|
1971
2044
|
device;
|
|
1972
2045
|
computePipeline = null;
|
|
1973
2046
|
renderPipeline = null;
|
|
2047
|
+
// Held so destroy() can release them — modules were previously unreachable
|
|
2048
|
+
// after initPipelines and lived until context loss (#684).
|
|
2049
|
+
computeModule = null;
|
|
2050
|
+
renderModule = null;
|
|
1974
2051
|
computeBindGroupLayout = null;
|
|
1975
2052
|
renderBindGroupLayout = null;
|
|
1976
2053
|
constructor(device) {
|
|
1977
2054
|
this.device = device;
|
|
1978
2055
|
}
|
|
1979
2056
|
initPipelines(format) {
|
|
1980
|
-
|
|
1981
|
-
|
|
2057
|
+
this.computeModule = this.device.createShaderModule({ code: COMPUTE_SHADER });
|
|
2058
|
+
this.renderModule = this.device.createShaderModule({ code: RENDER_SHADER });
|
|
2059
|
+
const computeModule = this.computeModule;
|
|
2060
|
+
const renderModule = this.renderModule;
|
|
1982
2061
|
this.computeBindGroupLayout = this.device.createBindGroupLayout({
|
|
1983
2062
|
entries: [
|
|
1984
2063
|
{
|
|
@@ -2037,6 +2116,8 @@ var WebGPUParticleSystemManager = class {
|
|
|
2037
2116
|
});
|
|
2038
2117
|
}
|
|
2039
2118
|
setupEntityResources(entity) {
|
|
2119
|
+
releaseGpuObject(entity.gpuStorageBuffer);
|
|
2120
|
+
releaseGpuObject(entity.gpuUniformBuffer);
|
|
2040
2121
|
const storageSize = entity.maxParticles * 32;
|
|
2041
2122
|
entity.gpuStorageBuffer = this.device.createBuffer({
|
|
2042
2123
|
size: storageSize,
|
|
@@ -2063,7 +2144,7 @@ var WebGPUParticleSystemManager = class {
|
|
|
2063
2144
|
}
|
|
2064
2145
|
recordComputePass(pass, entity, dt, mouseX, mouseY, width, height) {
|
|
2065
2146
|
if (!this.computePipeline || !entity.computeBindGroup) return;
|
|
2066
|
-
const uniformArray =
|
|
2147
|
+
const uniformArray = recordComputePassScratch;
|
|
2067
2148
|
const color = parseColorToRGBA(entity.baseColor);
|
|
2068
2149
|
let opacity = 1;
|
|
2069
2150
|
for (let current = entity; current; current = current.parent) {
|
|
@@ -2107,12 +2188,23 @@ var WebGPUParticleSystemManager = class {
|
|
|
2107
2188
|
pass.draw(6, entity.maxParticles);
|
|
2108
2189
|
}
|
|
2109
2190
|
destroy() {
|
|
2191
|
+
releaseGpuObject(this.computePipeline);
|
|
2192
|
+
releaseGpuObject(this.renderPipeline);
|
|
2193
|
+
releaseGpuObject(this.computeModule);
|
|
2194
|
+
releaseGpuObject(this.renderModule);
|
|
2110
2195
|
this.computePipeline = null;
|
|
2111
2196
|
this.renderPipeline = null;
|
|
2197
|
+
this.computeModule = null;
|
|
2198
|
+
this.renderModule = null;
|
|
2112
2199
|
this.computeBindGroupLayout = null;
|
|
2113
2200
|
this.renderBindGroupLayout = null;
|
|
2114
2201
|
}
|
|
2115
2202
|
};
|
|
2203
|
+
function releaseGpuObject(obj) {
|
|
2204
|
+
if (obj && typeof obj.destroy === "function") {
|
|
2205
|
+
obj.destroy();
|
|
2206
|
+
}
|
|
2207
|
+
}
|
|
2116
2208
|
|
|
2117
2209
|
// src/renderer/GlyphRasterAtlas.ts
|
|
2118
2210
|
var HARD_MAX_SIZE = 8192;
|
|
@@ -2193,7 +2285,7 @@ var GlyphRasterAtlas = class {
|
|
|
2193
2285
|
* (headless, unrasterizable, or too large to pack).
|
|
2194
2286
|
*/
|
|
2195
2287
|
get(font, color, glyph) {
|
|
2196
|
-
const key = font
|
|
2288
|
+
const key = `${font.length}\0${font}${color.length}\0${color}${glyph.length}\0${glyph}`;
|
|
2197
2289
|
const existing = this.slots.get(key);
|
|
2198
2290
|
if (existing !== void 0) {
|
|
2199
2291
|
if (existing) this._hits++;
|
|
@@ -2347,7 +2439,7 @@ var TextRasterCache = class {
|
|
|
2347
2439
|
* fall back to {@link IRenderer.fillText}).
|
|
2348
2440
|
*/
|
|
2349
2441
|
get(font, color, text) {
|
|
2350
|
-
const key = font
|
|
2442
|
+
const key = `${font.length}\0${font}${color.length}\0${color}${text.length}\0${text}`;
|
|
2351
2443
|
const hit = this.cache.get(key);
|
|
2352
2444
|
if (hit) {
|
|
2353
2445
|
this._hits++;
|