@vectojs/core 0.2.2 → 0.2.4

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/README.md CHANGED
@@ -1,56 +1,132 @@
1
1
  # @vectojs/core
2
2
 
3
- > The Zero-DOM, Canvas-native rendering engine behind **VectoJS** ECS + Virtual Math Tree,
4
- > with an accessibility/automation shadow layer.
3
+ > Scene, layout, interaction, text, rendering, and semantic projection for canvas-native interfaces.
5
4
 
6
- Part of the [VectoJS](https://github.com/vectojs/vectojs) ecosystem.
5
+ [![npm](https://img.shields.io/npm/v/@vectojs/core?color=22d3ee)](https://www.npmjs.com/package/@vectojs/core)
6
+ [![CI](https://github.com/vectojs/vectojs/actions/workflows/ci.yml/badge.svg)](https://github.com/vectojs/vectojs/actions/workflows/ci.yml)
7
+ [![MIT](https://img.shields.io/badge/license-MIT-6366f1.svg)](https://github.com/vectojs/vectojs/blob/main/LICENSE)
7
8
 
8
- ## What it does
9
+ `@vectojs/core` is the runtime beneath VectoJS. It owns the retained `Scene`/`Entity` tree, affine
10
+ transforms, render scheduling, layout, text flow, spatial hit-testing, event propagation, renderer
11
+ backends, and the accessibility/automation projection layer.
9
12
 
10
- `@vectojs/core` renders a whole UI onto one `<canvas>`: layout, hit-testing, animation and
11
- physics are pure math on a Virtual Math Tree, dispatched to a Canvas 2D (or WebGL2) renderer —
12
- **no per-element DOM, no reflow, no style recalc**. Interactive entities project a real,
13
- transparent DOM node through the **`a11yRoot`** shadow layer, so a pure-canvas page stays
14
- accessible and drivable by assistive tech and AI agents.
13
+ [Core guide](https://vectojs.xuepoo.xyz/learn/core-scene/) ·
14
+ [API reference](https://vectojs.xuepoo.xyz/reference/core-api/) ·
15
+ [Main repository](https://github.com/vectojs/vectojs)
15
16
 
16
- Includes: `Scene` (render loop + a11y sync), `Entity` (ECS base), `LayoutEngine` (Intl.Segmenter
17
- with a cold/hot `prepare`/`layoutPrepared` split), `SpatialHashGrid`, `LayoutResultBuffer`
18
- (zero-GC), `SplineEntity` (native vectomancy math-curve rendering + curve-accurate hit-testing),
19
- `CanvasRenderer`, and a `WebGLPointRenderer` point/rect batch layer.
17
+ ## Install
20
18
 
21
- ## Performance
19
+ ```bash
20
+ bun add @vectojs/core
21
+ ```
22
+
23
+ ## Minimal scene
22
24
 
23
- See the [main README](https://github.com/vectojs/vectojs#measured-performance) for measured,
24
- reproducible numbers (`bun run benchmark` / `bun run compare:dom`). Headline levers: viewport
25
- culling, on-demand redraw, draw-call batching, a WebGL2 point layer, and a cold/hot text layout
26
- split (~3.5× faster reflow). No fabricated comparisons — numbers are per-machine and
27
- complexity-dependent.
25
+ ```ts
26
+ import { Entity, type IRenderer, Scene } from '@vectojs/core';
28
27
 
29
- ## Quick Start
28
+ class Dot extends Entity {
29
+ constructor() {
30
+ super();
31
+ this.width = 48;
32
+ this.height = 48;
33
+ this.interactive = true;
34
+ this.on('click', () => this.animate({ scaleX: 1.25, scaleY: 1.25 }, 120));
35
+ }
30
36
 
31
- ```typescript
32
- import { Scene, Entity, IRenderer } from '@vectojs/core';
37
+ isPointInside(globalX: number, globalY: number): boolean {
38
+ const local = this.worldToLocal(globalX, globalY);
39
+ return !!local && Math.hypot(local.x - 24, local.y - 24) <= 24;
40
+ }
33
41
 
34
- class CircleEntity extends Entity {
35
- isPointInside(x: number, y: number) {
36
- return Math.hypot(x - this.x, y - this.y) < 50;
42
+ getA11yAttributes() {
43
+ return { tag: 'button' as const, role: 'button', label: 'Animated dot' };
37
44
  }
38
- render(r: IRenderer) {
39
- r.beginPath();
40
- r.arc(0, 0, 50, 0, Math.PI * 2);
41
- r.fill('#38bdf8');
45
+
46
+ render(renderer: IRenderer): void {
47
+ renderer.beginPath();
48
+ renderer.arc(24, 24, 24, 0, Math.PI * 2);
49
+ renderer.fill('#22d3ee');
42
50
  }
43
51
  }
44
52
 
45
- const canvas = document.querySelector('canvas')!;
53
+ const canvas = document.querySelector<HTMLCanvasElement>('canvas')!;
46
54
  const scene = new Scene(canvas);
47
- scene.add(new CircleEntity().setPosition(100, 100));
55
+ scene.renderMode = 'onDemand';
56
+ scene.add(new Dot().setPosition(80, 80));
57
+ scene.start();
58
+ ```
59
+
60
+ ## Runtime building blocks
61
+
62
+ | Area | Main APIs | Purpose |
63
+ | ----------- | ----------------------------------------------- | ---------------------------------------------------------------------- |
64
+ | Scene graph | `Scene`, `Entity` | ownership, transforms, lifecycle, update/render traversal |
65
+ | Layout | `LayoutEngine`, layout subpath | prepared text/rich-text layout, wrapping, exclusions, reusable buffers |
66
+ | Interaction | entity events, `SpatialHashGrid` | hit-testing and DOM-like capture/bubble dispatch |
67
+ | Text | `TextEntity`, `MSDFTextEntity`, `SplineEntity` | Canvas text, GPU text, and mathematical curves |
68
+ | Rendering | `IRenderer`, `CanvasRenderer`, renderer subpath | backend-neutral drawing contract and concrete renderers |
69
+ | GPU paths | WebGL point batching, WebGPU particles | high-volume points/rects and compute-driven particles |
70
+ | Semantics | `A11yAttributes`, Scene projection | role/name/state, native inputs, screen readers, Playwright/agents |
71
+ | Animation | `animate`, springs, transitions, `Scene.step()` | real-time or deterministic fixed-step motion |
72
+
73
+ ## Scene lifecycle
74
+
75
+ ```ts
76
+ const scene = new Scene(canvas, {
77
+ maxFPS: 60,
78
+ pointBackend: 'canvas', // or 'webgl'
79
+ particleBackend: 'cpu', // or 'webgpu' when supported
80
+ });
81
+ scene.renderMode = 'onDemand'; // redraw only when dirty
82
+
83
+ scene.resize(width, height); // logical CSS pixels
84
+ scene.markDirty();
48
85
  scene.start();
86
+ scene.stop();
87
+ scene.step(1000 / 60); // deterministic single step
88
+ scene.destroy(); // release renderers, workers, observers, and semantic DOM
49
89
  ```
50
90
 
51
- For high-level accessible components (Button, Input, Card…), see
52
- [`@vectojs/ui`](https://www.npmjs.com/package/@vectojs/ui).
91
+ Always call `destroy()` when a framework component unmounts. A `Scene` owns browser observers,
92
+ renderer resources, layout work, and projected DOM nodes.
93
+
94
+ ## Package entry points
95
+
96
+ ```ts
97
+ import { Scene, Entity } from '@vectojs/core';
98
+ import { LayoutEngine } from '@vectojs/core/layout';
99
+ import type { IRenderer } from '@vectojs/core/renderer';
100
+ import { TextEntity } from '@vectojs/core/text';
101
+ ```
102
+
103
+ Both ESM and CommonJS outputs are published with TypeScript declarations.
104
+
105
+ ## Accessibility and automation
106
+
107
+ Canvas pixels have no semantics. Interactive entities can implement `getA11yAttributes()`; the
108
+ Scene projects transparent DOM nodes over their world-space bounds and forwards native input back
109
+ into the VectoJS event system.
110
+
111
+ This projection is intentionally thin. Applications still own accessible names, keyboard behavior,
112
+ focus order, contrast, and correct control semantics. See the
113
+ [accessibility guide](https://vectojs.xuepoo.xyz/learn/accessibility/).
114
+
115
+ ## Performance model
116
+
117
+ Useful levers include on-demand rendering, viewport culling, spatial hashing, prepared text layout,
118
+ typed reusable buffers, batched WebGL points, and optional WebGPU particle compute. None makes every
119
+ workload allocation-free or GPU-bound; profile the renderer and entity types used by your app.
120
+
121
+ Run the repository benchmarks with `bun run benchmark`, `bun run compare:dom`, and
122
+ `bun run compare`.
123
+
124
+ ## Related packages
125
+
126
+ - [`@vectojs/ui`](https://github.com/vectojs/vectojs/tree/main/packages/ui) — high-level accessible components
127
+ - [`@vectojs/three`](https://github.com/vectojs/vectojs/tree/main/packages/three) — Three.js/WebXR projection and raycast routing
128
+ - [`@vectojs/video-exporter`](https://github.com/vectojs/vectojs/tree/main/packages/video-exporter) — deterministic H.264 capture
53
129
 
54
130
  ## License
55
131
 
56
- MIT © 2026 Xuepoo
132
+ [MIT](https://github.com/vectojs/vectojs/blob/main/LICENSE) © 2026 Xuepoo
@@ -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(); } } var _class; var _class2;// src/text/ArabicShaper.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;// src/text/ArabicShaper.ts
2
2
  var ArabicShaper = (_class = class _ArabicShaper {
3
3
  static __initStatic() {this.MAPPINGS = {
4
4
  1569: { isolated: 65152, initial: 65152, medial: 65152, final: 65152, joining: "U" },
@@ -296,29 +296,62 @@ var BidiResolver = class _BidiResolver {
296
296
  };
297
297
 
298
298
  // src/layout/LayoutWorkerSource.ts
299
- var WORKER_SOURCE_STRING = '"use strict";(()=>{var b=new Map;self.onmessage=x=>{let{id:S,seqId:k,text:A,fontId:i,fontData:c,maxWidth:d,maxHeight:H,fontSize:o,lineHeight:F,letterSpacing:C}=x.data;c&&b.set(i,c);let n=b.get(i);if(!n)return;let f=[],u=[],y=[],l=[],e=0,r=0,h=n.metrics?.ascender??.8,D=n.metrics?.descender??-.2,m=F??o*(h-D),p=Array.from(A);for(let s=0;s<p.length;s++){let a=p[s].codePointAt(0),g=(n.glyphs?.find(w=>w.unicode===a)?.advance??1)*o;e+g>d&&a===32&&(e=0,r++),f.push(a),u.push(e);let M=r*m+h*o;y.push(M),l.push(-256),e+=g+(C??0)}let t={id:S,seqId:k,width:Math.min(e,d),height:(r+1)*m,codePoints:new Uint32Array(f),xCoords:new Float32Array(u),yCoords:new Float32Array(y),packedStyles:new Uint32Array(l)};self.postMessage(t,[t.codePoints.buffer,t.xCoords.buffer,t.yCoords.buffer,t.packedStyles.buffer])};})();\n';
299
+ var WORKER_SOURCE_STRING = '"use strict";(()=>{var k=new Map;function n(t){return typeof t=="number"&&Number.isFinite(t)}function H(t){return t.origin?t.origin===self.location.origin:!0}function M(t){if(!t||typeof t!="object")return!1;let e=t;return typeof e.id=="string"&&n(e.seqId)&&typeof e.text=="string"&&typeof e.fontId=="string"&&(e.fontData===void 0||typeof e.fontData=="object")&&n(e.maxWidth)&&n(e.maxHeight)&&n(e.fontSize)&&(e.lineHeight===void 0||n(e.lineHeight))&&(e.letterSpacing===void 0||n(e.letterSpacing))}self.onmessage=t=>{if(!H(t)||!M(t.data))return;let{id:e,seqId:F,text:q,fontId:d,fontData:f,maxWidth:y,maxHeight:I,fontSize:i,lineHeight:A,letterSpacing:D}=t.data;f&&k.set(d,f);let s=k.get(d);if(!s)return;let g=[],l=[],p=[],m=[],o=0,a=0,h=s.metrics?.ascender??.8,W=s.metrics?.descender??-.2,b=A??i*(h-W),x=Array.from(q);for(let c=0;c<x.length;c++){let u=x[c].codePointAt(0),S=(s.glyphs?.find(C=>C.unicode===u)?.advance??1)*i;o+S>y&&u===32&&(o=0,a++),g.push(u),l.push(o);let w=a*b+h*i;p.push(w),m.push(-256),o+=S+(D??0)}let r={id:e,seqId:F,width:Math.min(o,y),height:(a+1)*b,codePoints:new Uint32Array(g),xCoords:new Float32Array(l),yCoords:new Float32Array(p),packedStyles:new Uint32Array(m)};self.postMessage(r,[r.codePoints.buffer,r.xCoords.buffer,r.yCoords.buffer,r.packedStyles.buffer])};})();\n';
300
300
 
301
301
  // src/layout/LayoutWorkerManager.ts
302
302
  var LayoutWorkerManager = (_class2 = class _LayoutWorkerManager {
303
303
 
304
-
305
- __init() {this.registeredFonts = /* @__PURE__ */ new Set()}
306
- __init2() {this.pendingCallbacks = /* @__PURE__ */ new Map()}
307
- __init3() {this.seqIdCounter = /* @__PURE__ */ new Map()}
308
- __init4() {this.debounceTimers = /* @__PURE__ */ new Map()}
309
- constructor() {;_class2.prototype.__init.call(this);_class2.prototype.__init2.call(this);_class2.prototype.__init3.call(this);_class2.prototype.__init4.call(this);
304
+ __init() {this.worker = null}
305
+ __init2() {this.registeredFonts = /* @__PURE__ */ new Set()}
306
+ __init3() {this.pendingCallbacks = /* @__PURE__ */ new Map()}
307
+ __init4() {this.seqIdCounter = /* @__PURE__ */ new Map()}
308
+ __init5() {this.debounceTimers = /* @__PURE__ */ new Map()}
309
+ constructor() {;_class2.prototype.__init.call(this);_class2.prototype.__init2.call(this);_class2.prototype.__init3.call(this);_class2.prototype.__init4.call(this);_class2.prototype.__init5.call(this);
310
+ this.worker = this.createWorker();
311
+ }
312
+ createWorker() {
310
313
  const workerBlob = new Blob([WORKER_SOURCE_STRING], { type: "application/javascript" });
311
314
  const workerURL = URL.createObjectURL(workerBlob);
312
- this.worker = new Worker(workerURL);
313
- setTimeout(() => URL.revokeObjectURL(workerURL), 2e3);
314
- this.worker.onmessage = (e) => {
315
+ let worker;
316
+ try {
317
+ worker = new Worker(workerURL);
318
+ } finally {
319
+ URL.revokeObjectURL(workerURL);
320
+ }
321
+ worker.onmessage = (e) => {
322
+ if (this.worker !== worker) return;
315
323
  const response = e.data;
316
- const callback = this.pendingCallbacks.get(`${response.id}-${response.seqId}`);
324
+ const key = `${response.id}-${response.seqId}`;
325
+ const callback = this.pendingCallbacks.get(key);
317
326
  if (callback) {
327
+ this.pendingCallbacks.delete(key);
318
328
  callback(response);
319
- this.pendingCallbacks.delete(`${response.id}-${response.seqId}`);
320
329
  }
321
330
  };
331
+ worker.onerror = () => this.handleWorkerFailure(worker);
332
+ worker.onmessageerror = () => this.handleWorkerFailure(worker);
333
+ return worker;
334
+ }
335
+ ensureWorker() {
336
+ if (!this.worker) this.worker = this.createWorker();
337
+ return this.worker;
338
+ }
339
+ handleWorkerFailure(worker) {
340
+ if (this.worker !== worker) return;
341
+ worker.terminate();
342
+ this.worker = null;
343
+ this.pendingCallbacks.clear();
344
+ this.registeredFonts.clear();
345
+ }
346
+ destroy() {
347
+ for (const timer of this.debounceTimers.values()) clearTimeout(timer);
348
+ this.debounceTimers.clear();
349
+ this.pendingCallbacks.clear();
350
+ this.seqIdCounter.clear();
351
+ this.registeredFonts.clear();
352
+ _optionalChain([this, 'access', _ => _.worker, 'optionalAccess', _2 => _2.terminate, 'call', _3 => _3()]);
353
+ this.worker = null;
354
+ if (_LayoutWorkerManager.instance === this) _LayoutWorkerManager.instance = void 0;
322
355
  }
323
356
  static getInstance() {
324
357
  if (!_LayoutWorkerManager.instance) {
@@ -332,6 +365,7 @@ var LayoutWorkerManager = (_class2 = class _LayoutWorkerManager {
332
365
  clearTimeout(existingTimer);
333
366
  }
334
367
  const runLayout = () => {
368
+ this.debounceTimers.delete(entityId);
335
369
  const nextSeqId = (_nullishCoalesce(this.seqIdCounter.get(entityId), () => ( 0))) + 1;
336
370
  this.seqIdCounter.set(entityId, nextSeqId);
337
371
  const request = {
@@ -350,7 +384,12 @@ var LayoutWorkerManager = (_class2 = class _LayoutWorkerManager {
350
384
  this.registeredFonts.add(options.fontId);
351
385
  }
352
386
  this.pendingCallbacks.set(`${entityId}-${nextSeqId}`, options.callback);
353
- this.worker.postMessage(request);
387
+ try {
388
+ this.ensureWorker().postMessage(request);
389
+ } catch (e2) {
390
+ const worker = this.worker;
391
+ if (worker) this.handleWorkerFailure(worker);
392
+ }
354
393
  };
355
394
  if (!this.seqIdCounter.has(entityId)) {
356
395
  runLayout();
@@ -365,6 +404,9 @@ var LayoutWorkerManager = (_class2 = class _LayoutWorkerManager {
365
404
  clearTimeout(existingTimer);
366
405
  this.debounceTimers.delete(entityId);
367
406
  }
407
+ for (const key of this.pendingCallbacks.keys()) {
408
+ if (key.startsWith(`${entityId}-`)) this.pendingCallbacks.delete(key);
409
+ }
368
410
  this.seqIdCounter.delete(entityId);
369
411
  }
370
412
  }, _class2);
@@ -296,29 +296,62 @@ var BidiResolver = class _BidiResolver {
296
296
  };
297
297
 
298
298
  // src/layout/LayoutWorkerSource.ts
299
- var WORKER_SOURCE_STRING = '"use strict";(()=>{var b=new Map;self.onmessage=x=>{let{id:S,seqId:k,text:A,fontId:i,fontData:c,maxWidth:d,maxHeight:H,fontSize:o,lineHeight:F,letterSpacing:C}=x.data;c&&b.set(i,c);let n=b.get(i);if(!n)return;let f=[],u=[],y=[],l=[],e=0,r=0,h=n.metrics?.ascender??.8,D=n.metrics?.descender??-.2,m=F??o*(h-D),p=Array.from(A);for(let s=0;s<p.length;s++){let a=p[s].codePointAt(0),g=(n.glyphs?.find(w=>w.unicode===a)?.advance??1)*o;e+g>d&&a===32&&(e=0,r++),f.push(a),u.push(e);let M=r*m+h*o;y.push(M),l.push(-256),e+=g+(C??0)}let t={id:S,seqId:k,width:Math.min(e,d),height:(r+1)*m,codePoints:new Uint32Array(f),xCoords:new Float32Array(u),yCoords:new Float32Array(y),packedStyles:new Uint32Array(l)};self.postMessage(t,[t.codePoints.buffer,t.xCoords.buffer,t.yCoords.buffer,t.packedStyles.buffer])};})();\n';
299
+ var WORKER_SOURCE_STRING = '"use strict";(()=>{var k=new Map;function n(t){return typeof t=="number"&&Number.isFinite(t)}function H(t){return t.origin?t.origin===self.location.origin:!0}function M(t){if(!t||typeof t!="object")return!1;let e=t;return typeof e.id=="string"&&n(e.seqId)&&typeof e.text=="string"&&typeof e.fontId=="string"&&(e.fontData===void 0||typeof e.fontData=="object")&&n(e.maxWidth)&&n(e.maxHeight)&&n(e.fontSize)&&(e.lineHeight===void 0||n(e.lineHeight))&&(e.letterSpacing===void 0||n(e.letterSpacing))}self.onmessage=t=>{if(!H(t)||!M(t.data))return;let{id:e,seqId:F,text:q,fontId:d,fontData:f,maxWidth:y,maxHeight:I,fontSize:i,lineHeight:A,letterSpacing:D}=t.data;f&&k.set(d,f);let s=k.get(d);if(!s)return;let g=[],l=[],p=[],m=[],o=0,a=0,h=s.metrics?.ascender??.8,W=s.metrics?.descender??-.2,b=A??i*(h-W),x=Array.from(q);for(let c=0;c<x.length;c++){let u=x[c].codePointAt(0),S=(s.glyphs?.find(C=>C.unicode===u)?.advance??1)*i;o+S>y&&u===32&&(o=0,a++),g.push(u),l.push(o);let w=a*b+h*i;p.push(w),m.push(-256),o+=S+(D??0)}let r={id:e,seqId:F,width:Math.min(o,y),height:(a+1)*b,codePoints:new Uint32Array(g),xCoords:new Float32Array(l),yCoords:new Float32Array(p),packedStyles:new Uint32Array(m)};self.postMessage(r,[r.codePoints.buffer,r.xCoords.buffer,r.yCoords.buffer,r.packedStyles.buffer])};})();\n';
300
300
 
301
301
  // src/layout/LayoutWorkerManager.ts
302
302
  var LayoutWorkerManager = class _LayoutWorkerManager {
303
303
  static instance;
304
- worker;
304
+ worker = null;
305
305
  registeredFonts = /* @__PURE__ */ new Set();
306
306
  pendingCallbacks = /* @__PURE__ */ new Map();
307
307
  seqIdCounter = /* @__PURE__ */ new Map();
308
308
  debounceTimers = /* @__PURE__ */ new Map();
309
309
  constructor() {
310
+ this.worker = this.createWorker();
311
+ }
312
+ createWorker() {
310
313
  const workerBlob = new Blob([WORKER_SOURCE_STRING], { type: "application/javascript" });
311
314
  const workerURL = URL.createObjectURL(workerBlob);
312
- this.worker = new Worker(workerURL);
313
- setTimeout(() => URL.revokeObjectURL(workerURL), 2e3);
314
- this.worker.onmessage = (e) => {
315
+ let worker;
316
+ try {
317
+ worker = new Worker(workerURL);
318
+ } finally {
319
+ URL.revokeObjectURL(workerURL);
320
+ }
321
+ worker.onmessage = (e) => {
322
+ if (this.worker !== worker) return;
315
323
  const response = e.data;
316
- const callback = this.pendingCallbacks.get(`${response.id}-${response.seqId}`);
324
+ const key = `${response.id}-${response.seqId}`;
325
+ const callback = this.pendingCallbacks.get(key);
317
326
  if (callback) {
327
+ this.pendingCallbacks.delete(key);
318
328
  callback(response);
319
- this.pendingCallbacks.delete(`${response.id}-${response.seqId}`);
320
329
  }
321
330
  };
331
+ worker.onerror = () => this.handleWorkerFailure(worker);
332
+ worker.onmessageerror = () => this.handleWorkerFailure(worker);
333
+ return worker;
334
+ }
335
+ ensureWorker() {
336
+ if (!this.worker) this.worker = this.createWorker();
337
+ return this.worker;
338
+ }
339
+ handleWorkerFailure(worker) {
340
+ if (this.worker !== worker) return;
341
+ worker.terminate();
342
+ this.worker = null;
343
+ this.pendingCallbacks.clear();
344
+ this.registeredFonts.clear();
345
+ }
346
+ destroy() {
347
+ for (const timer of this.debounceTimers.values()) clearTimeout(timer);
348
+ this.debounceTimers.clear();
349
+ this.pendingCallbacks.clear();
350
+ this.seqIdCounter.clear();
351
+ this.registeredFonts.clear();
352
+ this.worker?.terminate();
353
+ this.worker = null;
354
+ if (_LayoutWorkerManager.instance === this) _LayoutWorkerManager.instance = void 0;
322
355
  }
323
356
  static getInstance() {
324
357
  if (!_LayoutWorkerManager.instance) {
@@ -332,6 +365,7 @@ var LayoutWorkerManager = class _LayoutWorkerManager {
332
365
  clearTimeout(existingTimer);
333
366
  }
334
367
  const runLayout = () => {
368
+ this.debounceTimers.delete(entityId);
335
369
  const nextSeqId = (this.seqIdCounter.get(entityId) ?? 0) + 1;
336
370
  this.seqIdCounter.set(entityId, nextSeqId);
337
371
  const request = {
@@ -350,7 +384,12 @@ var LayoutWorkerManager = class _LayoutWorkerManager {
350
384
  this.registeredFonts.add(options.fontId);
351
385
  }
352
386
  this.pendingCallbacks.set(`${entityId}-${nextSeqId}`, options.callback);
353
- this.worker.postMessage(request);
387
+ try {
388
+ this.ensureWorker().postMessage(request);
389
+ } catch {
390
+ const worker = this.worker;
391
+ if (worker) this.handleWorkerFailure(worker);
392
+ }
354
393
  };
355
394
  if (!this.seqIdCounter.has(entityId)) {
356
395
  runLayout();
@@ -365,6 +404,9 @@ var LayoutWorkerManager = class _LayoutWorkerManager {
365
404
  clearTimeout(existingTimer);
366
405
  this.debounceTimers.delete(entityId);
367
406
  }
407
+ for (const key of this.pendingCallbacks.keys()) {
408
+ if (key.startsWith(`${entityId}-`)) this.pendingCallbacks.delete(key);
409
+ }
368
410
  this.seqIdCounter.delete(entityId);
369
411
  }
370
412
  };
@@ -1,7 +1,7 @@
1
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;
2
2
 
3
3
 
4
- var _chunkRW6NC4RBjs = require('./chunk-RW6NC4RB.js');
4
+ var _chunk76NMTLYLjs = require('./chunk-76NMTLYL.js');
5
5
 
6
6
  // src/layout/LayoutEngine.ts
7
7
  function computeLineSegments(top, bottom, maxWidth, exclusions) {
@@ -142,8 +142,8 @@ var LayoutEngine = (_class = class {
142
142
  offset += paragraph.length + 1;
143
143
  continue;
144
144
  }
145
- const { shapedText, indexMap } = _chunkRW6NC4RBjs.ArabicShaper.shapeArabic(paragraph);
146
- const levels = _chunkRW6NC4RBjs.BidiResolver.resolveLevels(shapedText);
145
+ const { shapedText, indexMap } = _chunk76NMTLYLjs.ArabicShaper.shapeArabic(paragraph);
146
+ const levels = _chunk76NMTLYLjs.BidiResolver.resolveLevels(shapedText);
147
147
  const words = [];
148
148
  let shapedCharIdx = 0;
149
149
  let pFallback = false;
@@ -192,7 +192,7 @@ var LayoutEngine = (_class = class {
192
192
  words,
193
193
  isEmpty: false,
194
194
  fallbackToCanvas: pFallback || void 0,
195
- baseLevel: _chunkRW6NC4RBjs.BidiResolver.getBaseLevel(shapedText)
195
+ baseLevel: _chunk76NMTLYLjs.BidiResolver.getBaseLevel(shapedText)
196
196
  };
197
197
  if (this.paragraphCache.size > 1e3) this.paragraphCache.clear();
198
198
  this.paragraphCache.set(key, prepared);
@@ -264,8 +264,8 @@ var LayoutEngine = (_class = class {
264
264
  offset += paragraph.length + 1;
265
265
  continue;
266
266
  }
267
- const { shapedText, indexMap } = _chunkRW6NC4RBjs.ArabicShaper.shapeArabic(paragraph);
268
- const levels = _chunkRW6NC4RBjs.BidiResolver.resolveLevels(shapedText);
267
+ const { shapedText, indexMap } = _chunk76NMTLYLjs.ArabicShaper.shapeArabic(paragraph);
268
+ const levels = _chunk76NMTLYLjs.BidiResolver.resolveLevels(shapedText);
269
269
  const words = [];
270
270
  let shapedCharIdx = 0;
271
271
  let pFallback = false;
@@ -317,7 +317,7 @@ var LayoutEngine = (_class = class {
317
317
  words,
318
318
  isEmpty: false,
319
319
  fallbackToCanvas: pFallback || void 0,
320
- baseLevel: _chunkRW6NC4RBjs.BidiResolver.getBaseLevel(shapedText)
320
+ baseLevel: _chunk76NMTLYLjs.BidiResolver.getBaseLevel(shapedText)
321
321
  };
322
322
  if (this.richParagraphCache.size > 1e3) this.richParagraphCache.clear();
323
323
  this.richParagraphCache.set(key, prepared);
@@ -372,7 +372,7 @@ var LayoutEngine = (_class = class {
372
372
  }
373
373
  for (const run of runs) {
374
374
  const runStartX = run[0].x;
375
- _chunkRW6NC4RBjs.BidiResolver.reorderVisual(run, paragraphBaseLevel);
375
+ _chunk76NMTLYLjs.BidiResolver.reorderVisual(run, paragraphBaseLevel);
376
376
  let x = runStartX;
377
377
  for (const node of run) {
378
378
  node.x = x;
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  ArabicShaper,
3
3
  BidiResolver
4
- } from "./chunk-YA2J5ZH7.mjs";
4
+ } from "./chunk-B3Z3JEJH.mjs";
5
5
 
6
6
  // src/layout/LayoutEngine.ts
7
7
  function computeLineSegments(top, bottom, maxWidth, exclusions) {