@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 +110 -34
- package/dist/{chunk-RW6NC4RB.js → chunk-76NMTLYL.js} +56 -14
- package/dist/{chunk-YA2J5ZH7.mjs → chunk-B3Z3JEJH.mjs} +50 -8
- package/dist/{chunk-72WVPMSJ.js → chunk-IKLYTHAL.js} +8 -8
- package/dist/{chunk-T456DL4P.mjs → chunk-MCULB5VC.mjs} +1 -1
- package/dist/{chunk-H3QIE77O.mjs → chunk-P6MDWIEX.mjs} +216 -61
- package/dist/{chunk-LIX7DJTI.js → chunk-TPNADFNN.js} +143 -15
- package/dist/{chunk-LA3FJLP2.js → chunk-TQ4H357Q.js} +226 -71
- package/dist/{chunk-2Y45S4JK.mjs → chunk-WEQRBXT2.mjs} +142 -14
- package/dist/index.d.ts +1 -0
- package/dist/index.js +185 -119
- package/dist/index.mjs +147 -81
- package/dist/layout/LayoutWorkerManager.d.ts +4 -0
- package/dist/layout/LayoutWorkerSource.d.ts +1 -1
- package/dist/layout.js +3 -3
- package/dist/layout.mjs +2 -2
- package/dist/renderer/CanvasRenderer.d.ts +6 -0
- package/dist/renderer/IRenderer.d.ts +9 -0
- package/dist/renderer/SVGRenderer.d.ts +14 -0
- package/dist/renderer/url.d.ts +31 -0
- package/dist/renderer.js +2 -2
- package/dist/renderer.mjs +1 -1
- package/dist/text.js +3 -3
- package/dist/text.mjs +2 -2
- package/dist/tree/DOMPortalEntity.d.ts +2 -1
- package/dist/tree/Entity.d.ts +48 -7
- package/dist/tree/Scene.d.ts +9 -1
- package/package.json +2 -4
package/README.md
CHANGED
|
@@ -1,56 +1,132 @@
|
|
|
1
1
|
# @vectojs/core
|
|
2
2
|
|
|
3
|
-
>
|
|
4
|
-
> with an accessibility/automation shadow layer.
|
|
3
|
+
> Scene, layout, interaction, text, rendering, and semantic projection for canvas-native interfaces.
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
[](https://www.npmjs.com/package/@vectojs/core)
|
|
6
|
+
[](https://github.com/vectojs/vectojs/actions/workflows/ci.yml)
|
|
7
|
+
[](https://github.com/vectojs/vectojs/blob/main/LICENSE)
|
|
7
8
|
|
|
8
|
-
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
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
|
-
|
|
19
|
+
```bash
|
|
20
|
+
bun add @vectojs/core
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Minimal scene
|
|
22
24
|
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
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
|
-
|
|
32
|
-
|
|
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
|
-
|
|
35
|
-
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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.
|
|
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
|
-
|
|
52
|
-
|
|
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
|
|
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
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
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
|
-
|
|
313
|
-
|
|
314
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
313
|
-
|
|
314
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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 } =
|
|
146
|
-
const levels =
|
|
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:
|
|
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 } =
|
|
268
|
-
const levels =
|
|
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:
|
|
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
|
-
|
|
375
|
+
_chunk76NMTLYLjs.BidiResolver.reorderVisual(run, paragraphBaseLevel);
|
|
376
376
|
let x = runStartX;
|
|
377
377
|
for (const node of run) {
|
|
378
378
|
node.x = x;
|