@vectojs/core 0.2.2 → 0.2.3
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 +8 -9
- package/dist/{chunk-RW6NC4RB.js → chunk-6I53LI3Z.js} +55 -13
- package/dist/{chunk-H3QIE77O.mjs → chunk-ISGOYXPF.mjs} +177 -52
- package/dist/{chunk-LA3FJLP2.js → chunk-KPWVPGHR.js} +187 -62
- package/dist/{chunk-T456DL4P.mjs → chunk-LIOJ37MH.mjs} +1 -1
- package/dist/{chunk-2Y45S4JK.mjs → chunk-NKOQV3RM.mjs} +84 -5
- package/dist/{chunk-LIX7DJTI.js → chunk-TXA3LZDM.js} +85 -6
- package/dist/{chunk-72WVPMSJ.js → chunk-VVOQNUBK.js} +8 -8
- package/dist/{chunk-YA2J5ZH7.mjs → chunk-YSS44ADQ.mjs} +49 -7
- package/dist/index.d.ts +1 -0
- package/dist/index.js +184 -119
- package/dist/index.mjs +146 -81
- package/dist/layout/LayoutWorkerManager.d.ts +4 -0
- 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,7 +1,7 @@
|
|
|
1
1
|
# @vectojs/core
|
|
2
2
|
|
|
3
|
-
> The
|
|
4
|
-
> with an accessibility/automation
|
|
3
|
+
> The Canvas-native rendering engine behind **VectoJS** — an entity scene graph and Virtual Math
|
|
4
|
+
> Tree with an accessibility/automation projection layer.
|
|
5
5
|
|
|
6
6
|
Part of the [VectoJS](https://github.com/vectojs/vectojs) ecosystem.
|
|
7
7
|
|
|
@@ -13,18 +13,17 @@ physics are pure math on a Virtual Math Tree, dispatched to a Canvas 2D (or WebG
|
|
|
13
13
|
transparent DOM node through the **`a11yRoot`** shadow layer, so a pure-canvas page stays
|
|
14
14
|
accessible and drivable by assistive tech and AI agents.
|
|
15
15
|
|
|
16
|
-
Includes: `Scene` (render loop + a11y sync), `Entity` (
|
|
16
|
+
Includes: `Scene` (render loop + a11y sync), `Entity` (scene-graph base), `LayoutEngine` (Intl.Segmenter
|
|
17
17
|
with a cold/hot `prepare`/`layoutPrepared` split), `SpatialHashGrid`, `LayoutResultBuffer`
|
|
18
|
-
(
|
|
18
|
+
(reusable typed storage), `SplineEntity` (native vectomancy math-curve rendering + curve-accurate hit-testing),
|
|
19
19
|
`CanvasRenderer`, and a `WebGLPointRenderer` point/rect batch layer.
|
|
20
20
|
|
|
21
21
|
## Performance
|
|
22
22
|
|
|
23
|
-
See the [main README](https://github.com/vectojs/vectojs#
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
complexity-dependent.
|
|
23
|
+
See the [main README](https://github.com/vectojs/vectojs#testing--quality) for reproducible
|
|
24
|
+
workloads (`bun run benchmark` / `bun run compare:dom`). Headline levers include viewport culling,
|
|
25
|
+
on-demand redraw, draw-call batching, a WebGL2 point layer, and a cold/hot text layout split.
|
|
26
|
+
Results are machine- and workload-dependent.
|
|
28
27
|
|
|
29
28
|
## Quick Start
|
|
30
29
|
|
|
@@ -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" },
|
|
@@ -301,24 +301,57 @@ var WORKER_SOURCE_STRING = '"use strict";(()=>{var b=new Map;self.onmessage=x=>{
|
|
|
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);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
LayoutWorkerManager
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-YSS44ADQ.mjs";
|
|
4
4
|
|
|
5
5
|
// src/math/SpringPhysics.ts
|
|
6
6
|
var SpringPhysics = class {
|
|
@@ -128,14 +128,16 @@ var VectoJSEvent = class {
|
|
|
128
128
|
nativeEvent;
|
|
129
129
|
/** Whether the event bubbles past its target (capture always runs). */
|
|
130
130
|
bubbles;
|
|
131
|
+
explicitScenePoint;
|
|
131
132
|
stopped = false;
|
|
132
133
|
stoppedImmediate = false;
|
|
133
|
-
constructor(type, target, nativeEvent, bubbles = true) {
|
|
134
|
+
constructor(type, target, nativeEvent, bubbles = true, scenePoint) {
|
|
134
135
|
this.type = type;
|
|
135
136
|
this.target = target;
|
|
136
137
|
this.currentTarget = target;
|
|
137
138
|
this.nativeEvent = nativeEvent;
|
|
138
139
|
this.bubbles = bubbles;
|
|
140
|
+
this.explicitScenePoint = scenePoint;
|
|
139
141
|
}
|
|
140
142
|
/** Stop the event from reaching the next node in the propagation path. */
|
|
141
143
|
stopPropagation() {
|
|
@@ -178,6 +180,51 @@ var VectoJSEvent = class {
|
|
|
178
180
|
get clientY() {
|
|
179
181
|
return this.nativeEvent?.clientY;
|
|
180
182
|
}
|
|
183
|
+
get resolvedScenePoint() {
|
|
184
|
+
if (this.explicitScenePoint) return this.explicitScenePoint;
|
|
185
|
+
const native = this.nativeEvent;
|
|
186
|
+
if (native?.vectoSceneX !== void 0 && native.vectoSceneY !== void 0) {
|
|
187
|
+
return { x: native.vectoSceneX, y: native.vectoSceneY };
|
|
188
|
+
}
|
|
189
|
+
if (native?.clientX === void 0 || native.clientY === void 0) return void 0;
|
|
190
|
+
const scene = this.target.scene;
|
|
191
|
+
return scene?.clientToScene?.(native.clientX, native.clientY) ?? {
|
|
192
|
+
x: native.clientX,
|
|
193
|
+
y: native.clientY
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
/** Pointer X in the Scene's logical coordinate space. */
|
|
197
|
+
get sceneX() {
|
|
198
|
+
return this.resolvedScenePoint?.x;
|
|
199
|
+
}
|
|
200
|
+
/** Pointer Y in the Scene's logical coordinate space. */
|
|
201
|
+
get sceneY() {
|
|
202
|
+
return this.resolvedScenePoint?.y;
|
|
203
|
+
}
|
|
204
|
+
/** Pointer X local to the entity whose listener is currently running. */
|
|
205
|
+
get localX() {
|
|
206
|
+
const point = this.resolvedScenePoint;
|
|
207
|
+
if (!point) return void 0;
|
|
208
|
+
return this.currentTarget.worldToLocal(point.x, point.y)?.x;
|
|
209
|
+
}
|
|
210
|
+
/** Pointer Y local to the entity whose listener is currently running. */
|
|
211
|
+
get localY() {
|
|
212
|
+
const point = this.resolvedScenePoint;
|
|
213
|
+
if (!point) return void 0;
|
|
214
|
+
return this.currentTarget.worldToLocal(point.x, point.y)?.y;
|
|
215
|
+
}
|
|
216
|
+
get shiftKey() {
|
|
217
|
+
return !!this.nativeEvent?.shiftKey;
|
|
218
|
+
}
|
|
219
|
+
get ctrlKey() {
|
|
220
|
+
return !!this.nativeEvent?.ctrlKey;
|
|
221
|
+
}
|
|
222
|
+
get altKey() {
|
|
223
|
+
return !!this.nativeEvent?.altKey;
|
|
224
|
+
}
|
|
225
|
+
get metaKey() {
|
|
226
|
+
return !!this.nativeEvent?.metaKey;
|
|
227
|
+
}
|
|
181
228
|
/** Native key, if this wraps a keyboard event. */
|
|
182
229
|
get key() {
|
|
183
230
|
return this.nativeEvent?.key;
|
|
@@ -412,17 +459,28 @@ var Entity = class {
|
|
|
412
459
|
* that need to seed a starting state (e.g. the presence helper's enter `from`).
|
|
413
460
|
*/
|
|
414
461
|
setImmediate(prop, v) {
|
|
462
|
+
const existing = this._drivers.get(prop);
|
|
463
|
+
if (existing) this._settleDriver(existing);
|
|
415
464
|
this._drivers.delete(prop);
|
|
416
465
|
this._applyAnimated(prop, v);
|
|
417
466
|
}
|
|
467
|
+
_settleDriver(driver) {
|
|
468
|
+
const active = driver;
|
|
469
|
+
const onDone = active.onDone;
|
|
470
|
+
active.onDone = void 0;
|
|
471
|
+
onDone?.();
|
|
472
|
+
}
|
|
418
473
|
_spawnDriver(prop, to, cfg) {
|
|
419
474
|
if (prop !== "opacity" && this.scene?.prefersReducedMotion) {
|
|
475
|
+
const existing2 = this._drivers.get(prop);
|
|
476
|
+
if (existing2) this._settleDriver(existing2);
|
|
420
477
|
this._drivers.delete(prop);
|
|
421
478
|
this._applyAnimated(prop, to);
|
|
422
479
|
return;
|
|
423
480
|
}
|
|
424
481
|
const existing = this._drivers.get(prop);
|
|
425
482
|
if (existing) {
|
|
483
|
+
this._settleDriver(existing);
|
|
426
484
|
existing.retarget(to);
|
|
427
485
|
return;
|
|
428
486
|
}
|
|
@@ -477,7 +535,7 @@ var Entity = class {
|
|
|
477
535
|
driver.tick(dt);
|
|
478
536
|
if (driver.isDone()) {
|
|
479
537
|
this._applyAnimated(prop, driver.target);
|
|
480
|
-
|
|
538
|
+
this._settleDriver(driver);
|
|
481
539
|
this._drivers.delete(prop);
|
|
482
540
|
} else {
|
|
483
541
|
this._applyAnimated(prop, driver.value);
|
|
@@ -621,19 +679,95 @@ var Entity = class {
|
|
|
621
679
|
* @returns World-space {@link Point} for this entity.
|
|
622
680
|
*/
|
|
623
681
|
getGlobalPosition() {
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
682
|
+
return this.localToWorld(0, 0);
|
|
683
|
+
}
|
|
684
|
+
/**
|
|
685
|
+
* Return the exact accumulated Canvas `T * S * R` transform for this entity.
|
|
686
|
+
*/
|
|
687
|
+
getWorldTransform() {
|
|
688
|
+
const path = this.id === "root" ? [] : [this];
|
|
689
|
+
let ancestor = this.parent;
|
|
690
|
+
while (ancestor && ancestor.id !== "root") {
|
|
691
|
+
path.push(ancestor);
|
|
692
|
+
ancestor = ancestor.parent;
|
|
693
|
+
}
|
|
694
|
+
let a = 1;
|
|
695
|
+
let b = 0;
|
|
696
|
+
let c = 0;
|
|
697
|
+
let d = 1;
|
|
698
|
+
let e = 0;
|
|
699
|
+
let f = 0;
|
|
700
|
+
for (let i = path.length - 1; i >= 0; i--) {
|
|
701
|
+
const node = path[i];
|
|
702
|
+
const cos = Math.cos(node.rotation);
|
|
703
|
+
const sin = Math.sin(node.rotation);
|
|
704
|
+
const la = node.scaleX * cos;
|
|
705
|
+
const lb = node.scaleY * sin;
|
|
706
|
+
const lc = -node.scaleX * sin;
|
|
707
|
+
const ld = node.scaleY * cos;
|
|
708
|
+
const le = node.x;
|
|
709
|
+
const lf = node.y;
|
|
710
|
+
const nextA = a * la + c * lb;
|
|
711
|
+
const nextB = b * la + d * lb;
|
|
712
|
+
const nextC = a * lc + c * ld;
|
|
713
|
+
const nextD = b * lc + d * ld;
|
|
714
|
+
const nextE = a * le + c * lf + e;
|
|
715
|
+
const nextF = b * le + d * lf + f;
|
|
716
|
+
a = nextA;
|
|
717
|
+
b = nextB;
|
|
718
|
+
c = nextC;
|
|
719
|
+
d = nextD;
|
|
720
|
+
e = nextE;
|
|
721
|
+
f = nextF;
|
|
635
722
|
}
|
|
636
|
-
return {
|
|
723
|
+
return { a, b, c, d, e, f };
|
|
724
|
+
}
|
|
725
|
+
/** Convert a point from this entity's local space to Scene/world space. */
|
|
726
|
+
localToWorld(localX, localY) {
|
|
727
|
+
const { a, b, c, d, e, f } = this.getWorldTransform();
|
|
728
|
+
return {
|
|
729
|
+
x: a * localX + c * localY + e,
|
|
730
|
+
y: b * localX + d * localY + f
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
/**
|
|
734
|
+
* Convert a Scene/world point into this entity's local space.
|
|
735
|
+
* Returns `null` when the accumulated transform is singular.
|
|
736
|
+
*/
|
|
737
|
+
worldToLocal(worldX, worldY) {
|
|
738
|
+
const { a, b, c, d, e, f } = this.getWorldTransform();
|
|
739
|
+
const determinant = a * d - b * c;
|
|
740
|
+
if (!Number.isFinite(determinant) || Math.abs(determinant) < 1e-12) return null;
|
|
741
|
+
const x = worldX - e;
|
|
742
|
+
const y = worldY - f;
|
|
743
|
+
return {
|
|
744
|
+
x: (d * x - c * y) / determinant,
|
|
745
|
+
y: (-b * x + a * y) / determinant
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
/**
|
|
749
|
+
* Return the entity's local bounds transformed into a world-space AABB.
|
|
750
|
+
* Falls back to the entity's `[0, 0, width, height]` box when `getBounds()`
|
|
751
|
+
* does not provide a render-specific box.
|
|
752
|
+
*/
|
|
753
|
+
getWorldBounds() {
|
|
754
|
+
const bounds = this.getBounds() ?? { x: 0, y: 0, width: this.width, height: this.height };
|
|
755
|
+
const { a, b, c, d, e, f } = this.getWorldTransform();
|
|
756
|
+
let minX = Infinity;
|
|
757
|
+
let minY = Infinity;
|
|
758
|
+
let maxX = -Infinity;
|
|
759
|
+
let maxY = -Infinity;
|
|
760
|
+
for (let i = 0; i < 4; i++) {
|
|
761
|
+
const localX = i & 1 ? bounds.x + bounds.width : bounds.x;
|
|
762
|
+
const localY = i & 2 ? bounds.y + bounds.height : bounds.y;
|
|
763
|
+
const worldX = a * localX + c * localY + e;
|
|
764
|
+
const worldY = b * localX + d * localY + f;
|
|
765
|
+
minX = Math.min(minX, worldX);
|
|
766
|
+
minY = Math.min(minY, worldY);
|
|
767
|
+
maxX = Math.max(maxX, worldX);
|
|
768
|
+
maxY = Math.max(maxY, worldY);
|
|
769
|
+
}
|
|
770
|
+
return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
|
|
637
771
|
}
|
|
638
772
|
/**
|
|
639
773
|
* Accumulated world scale factors: this entity's own `scaleX`/`scaleY` times
|
|
@@ -705,12 +839,12 @@ var Entity = class {
|
|
|
705
839
|
* Opt into the renderer's draw-call batching fast-path for point-cloud /
|
|
706
840
|
* particle entities that draw as a single filled circle at their local origin.
|
|
707
841
|
*
|
|
708
|
-
* When a leaf entity returns a {@link BatchCircle} and
|
|
709
|
-
* {@link Scene}
|
|
710
|
-
* `restore` and
|
|
711
|
-
* {@link
|
|
712
|
-
*
|
|
713
|
-
* frame, so
|
|
842
|
+
* When a leaf entity returns a {@link BatchCircle} and its accumulated
|
|
843
|
+
* transform is representable by the selected batch backend, the {@link Scene}
|
|
844
|
+
* skips its per-entity `save`/`translate`/`scale`/`rotate`/`restore` and
|
|
845
|
+
* {@link render}. Canvas mode or an unsupported affine transform uses the
|
|
846
|
+
* normal render path, so implementations must keep {@link render} correct.
|
|
847
|
+
* Returns `null` by default. Read each frame, so animated color/radius works.
|
|
714
848
|
*
|
|
715
849
|
* @returns The circle to batch, or `null` to use the normal {@link render} path.
|
|
716
850
|
*/
|
|
@@ -893,22 +1027,21 @@ var MSDFTextEntity = class extends Entity {
|
|
|
893
1027
|
}
|
|
894
1028
|
isPointInside(globalX, globalY) {
|
|
895
1029
|
if (!this.layoutResult) return false;
|
|
896
|
-
const
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
const ly = (globalY - pos.y) / scale.y;
|
|
900
|
-
return lx >= 0 && lx <= this.layoutResult.width && ly >= 0 && ly <= this.layoutResult.height;
|
|
1030
|
+
const local = this.worldToLocal(globalX, globalY);
|
|
1031
|
+
if (!local) return false;
|
|
1032
|
+
return local.x >= 0 && local.x <= this.layoutResult.width && local.y >= 0 && local.y <= this.layoutResult.height;
|
|
901
1033
|
}
|
|
902
1034
|
render(renderer) {
|
|
903
1035
|
if (!this.layoutResult) return;
|
|
904
1036
|
const scene = this.scene;
|
|
905
|
-
|
|
1037
|
+
const world = this.getWorldTransform();
|
|
1038
|
+
const worldScaleX = Math.hypot(world.a, world.b);
|
|
1039
|
+
const worldScaleY = Math.hypot(world.c, world.d);
|
|
1040
|
+
const orthogonalTolerance = Math.max(1, worldScaleX * worldScaleY) * 1e-6;
|
|
1041
|
+
const canUsePointGlyphs = Number.isFinite(worldScaleX) && Number.isFinite(worldScaleY) && world.a * world.d - world.b * world.c >= 0 && Math.abs(world.a * world.c + world.b * world.d) <= orthogonalTolerance;
|
|
1042
|
+
if (scene && scene.pointRenderer && scene.glCanvas && canUsePointGlyphs) {
|
|
906
1043
|
scene.pointRenderer.setMSDFTexture(this.texture, this.font.distanceRange);
|
|
907
|
-
const
|
|
908
|
-
const scale = this.getWorldScale();
|
|
909
|
-
const worldRot = this.getWorldRotation();
|
|
910
|
-
const rCos = Math.cos(worldRot);
|
|
911
|
-
const rSin = Math.sin(worldRot);
|
|
1044
|
+
const worldRot = Math.atan2(world.b, world.a);
|
|
912
1045
|
const len2 = this.layoutResult.codePoints.length;
|
|
913
1046
|
for (let i = 0; i < len2; i++) {
|
|
914
1047
|
const code = this.layoutResult.codePoints[i];
|
|
@@ -920,12 +1053,12 @@ var MSDFTextEntity = class extends Entity {
|
|
|
920
1053
|
const { atlasBounds: ab, planeBounds: pb } = def;
|
|
921
1054
|
const aw = this.font.atlasWidth;
|
|
922
1055
|
const ah = this.font.atlasHeight;
|
|
923
|
-
const lx =
|
|
924
|
-
const ly =
|
|
925
|
-
const glyphX =
|
|
926
|
-
const glyphY =
|
|
927
|
-
const glyphW = (pb.right - pb.left) * this.fontSize *
|
|
928
|
-
const glyphH = (pb.top - pb.bottom) * this.fontSize *
|
|
1056
|
+
const lx = nodeX + pb.left * this.fontSize;
|
|
1057
|
+
const ly = nodeY - pb.top * this.fontSize;
|
|
1058
|
+
const glyphX = world.a * lx + world.c * ly + world.e;
|
|
1059
|
+
const glyphY = world.b * lx + world.d * ly + world.f;
|
|
1060
|
+
const glyphW = (pb.right - pb.left) * this.fontSize * worldScaleX;
|
|
1061
|
+
const glyphH = (pb.top - pb.bottom) * this.fontSize * worldScaleY;
|
|
929
1062
|
const v0 = this.font.data.atlas.yOrigin === "bottom" ? 1 - ab.top / ah : ab.top / ah;
|
|
930
1063
|
const v1 = this.font.data.atlas.yOrigin === "bottom" ? 1 - ab.bottom / ah : ab.bottom / ah;
|
|
931
1064
|
const colorVal = packedStyle >>> 8;
|
|
@@ -1138,22 +1271,14 @@ var SVGEntity = class extends Entity {
|
|
|
1138
1271
|
img.src = this.blobURL;
|
|
1139
1272
|
}
|
|
1140
1273
|
isPointInside(globalX, globalY) {
|
|
1141
|
-
const
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
const dx = globalX - pos.x;
|
|
1145
|
-
const dy = globalY - pos.y;
|
|
1146
|
-
const cos = Math.cos(-rot);
|
|
1147
|
-
const sin = Math.sin(-rot);
|
|
1148
|
-
const lx = (dx * cos - dy * sin) / scale.x;
|
|
1149
|
-
const ly = (dx * sin + dy * cos) / scale.y;
|
|
1150
|
-
return lx >= 0 && lx <= this.width && ly >= 0 && ly <= this.height;
|
|
1274
|
+
const local = this.worldToLocal(globalX, globalY);
|
|
1275
|
+
if (!local) return false;
|
|
1276
|
+
return local.x >= 0 && local.x <= this.width && local.y >= 0 && local.y <= this.height;
|
|
1151
1277
|
}
|
|
1152
1278
|
render(r) {
|
|
1153
|
-
const
|
|
1154
|
-
if (
|
|
1155
|
-
|
|
1156
|
-
r.drawImage({ src: dataUri }, 0, 0, this.width, this.height);
|
|
1279
|
+
const svgRenderer = r;
|
|
1280
|
+
if (typeof svgRenderer.drawSVG === "function") {
|
|
1281
|
+
svgRenderer.drawSVG(this.svgSource, 0, 0, this.width, this.height);
|
|
1157
1282
|
return;
|
|
1158
1283
|
}
|
|
1159
1284
|
const scale = this.getWorldScale();
|