@vectojs/core 0.2.5 → 0.2.6

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/index.mjs CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  LayoutResultBuffer,
4
4
  computeLineSegments,
5
5
  createCanvasMeasurer
6
- } from "./chunk-SB3SCWLT.mjs";
6
+ } from "./chunk-SSOP3F5F.mjs";
7
7
  import {
8
8
  CanvasRenderer,
9
9
  SVGRenderer,
@@ -12,7 +12,7 @@ import {
12
12
  isSafeUrl,
13
13
  parseColorToRGBA,
14
14
  sanitizeUrl
15
- } from "./chunk-WEQRBXT2.mjs";
15
+ } from "./chunk-PK5EYWIT.mjs";
16
16
  import {
17
17
  Easing,
18
18
  Entity,
@@ -24,12 +24,12 @@ import {
24
24
  TweenDriver,
25
25
  VectoJSEvent,
26
26
  isTweenConfig
27
- } from "./chunk-P6MDWIEX.mjs";
27
+ } from "./chunk-SL654OMF.mjs";
28
28
  import {
29
29
  ArabicShaper,
30
30
  BidiResolver,
31
31
  LayoutWorkerManager
32
- } from "./chunk-B3Z3JEJH.mjs";
32
+ } from "./chunk-STWPTWO4.mjs";
33
33
 
34
34
  // src/tree/ComputeParticleEntity.ts
35
35
  var PARTICLE_STRIDE_FLOATS = 8;
@@ -386,6 +386,8 @@ var Scene = class _Scene {
386
386
  initializingWebGPU = false;
387
387
  gpuCanvas = null;
388
388
  gpuContext = null;
389
+ /** True while the GPU canvas holds a presented particle frame (needs clearing when they leave). */
390
+ gpuHasContent = false;
389
391
  mouseX = -9999;
390
392
  mouseY = -9999;
391
393
  pointerMoveListener = null;
@@ -430,7 +432,10 @@ var Scene = class _Scene {
430
432
  if (options.renderer) {
431
433
  this.renderer = options.renderer;
432
434
  } else {
433
- this.renderer = new CanvasRenderer(canvas);
435
+ this.renderer = new CanvasRenderer(
436
+ canvas,
437
+ this.disableWindowResize ? { width: this.width, height: this.height } : void 0
438
+ );
434
439
  }
435
440
  if (typeof document !== "undefined") {
436
441
  this.a11yRoot = document.createElement("div");
@@ -619,6 +624,10 @@ var Scene = class _Scene {
619
624
  this.manager.destroy();
620
625
  this.manager = null;
621
626
  }
627
+ if (this.device) {
628
+ this.device.destroy?.();
629
+ this.device = null;
630
+ }
622
631
  }
623
632
  setupEvents() {
624
633
  if (typeof window !== "undefined" && !this.disableWindowResize) {
@@ -1164,8 +1173,8 @@ var Scene = class _Scene {
1164
1173
  loop(time) {
1165
1174
  if (!this.isRunning) return;
1166
1175
  let cap = this.effectiveMaxFPS();
1167
- const isStatic = this.autoThrottle && !this.dirty && !this.hasAnyPendingAnimation(this.root) && !this.hasAnyPendingAnimation(this.overlayRoot);
1168
- if (isStatic && this.renderMode === "always" && this.maxFPS > 0) {
1176
+ const isIdle = !this.dirty && !this.hasAnyPendingAnimation(this.root) && !this.hasAnyPendingAnimation(this.overlayRoot);
1177
+ if (isIdle && this.autoThrottle && this.renderMode === "always" && this.maxFPS > 0) {
1169
1178
  cap = Math.min(cap, 2);
1170
1179
  }
1171
1180
  if (cap > 0 && time - this.lastTime < 1e3 / cap - 1) {
@@ -1174,10 +1183,11 @@ var Scene = class _Scene {
1174
1183
  }
1175
1184
  const dt = time - this.lastTime;
1176
1185
  this.lastTime = time;
1177
- if (this.renderMode === "onDemand" && isStatic) {
1186
+ if (this.renderMode === "onDemand" && isIdle) {
1178
1187
  this.scheduleFrame();
1179
1188
  return;
1180
1189
  }
1190
+ this.dirty = false;
1181
1191
  this.render(this.renderer, dt, time);
1182
1192
  const hasActiveAnimation = this.hasAnyPendingAnimation(this.root) || this.hasAnyPendingAnimation(this.overlayRoot);
1183
1193
  const hasInteractive = this.hasAnyInteractive(this.root) || this.hasAnyInteractive(this.overlayRoot);
@@ -1192,7 +1202,6 @@ var Scene = class _Scene {
1192
1202
  } else if (hasActiveAnimation) {
1193
1203
  this.a11yPendingSyncAfterAnimation = true;
1194
1204
  }
1195
- this.dirty = false;
1196
1205
  this.scheduleFrame();
1197
1206
  }
1198
1207
  /**
@@ -1304,6 +1313,7 @@ var Scene = class _Scene {
1304
1313
  renderPass.end();
1305
1314
  }
1306
1315
  this.device.queue.submit([commandEncoder.finish()]);
1316
+ if (this.gpuContext) this.gpuHasContent = true;
1307
1317
  } catch (e) {
1308
1318
  console.error("WebGPU frame execution failed. Falling back.", e);
1309
1319
  this.deviceLost = true;
@@ -1312,9 +1322,23 @@ var Scene = class _Scene {
1312
1322
  }
1313
1323
  } else if (isMainRenderPath) {
1314
1324
  for (const entity of computeEntities) {
1315
- entity.updateCPU(dt / 1e3, this.mouseX, this.mouseY, this.width, this.height);
1325
+ let mx = this.mouseX;
1326
+ let my = this.mouseY;
1327
+ if (mx > -9e3 && my > -9e3) {
1328
+ const local = entity.worldToLocal(mx, my);
1329
+ if (local) {
1330
+ mx = local.x;
1331
+ my = local.y;
1332
+ } else {
1333
+ mx = -9999;
1334
+ my = -9999;
1335
+ }
1336
+ }
1337
+ entity.updateCPU(dt / 1e3, mx, my, this.width, this.height);
1316
1338
  }
1317
1339
  }
1340
+ } else if (isMainRenderer) {
1341
+ this.clearGPUCanvasIfStale();
1318
1342
  }
1319
1343
  renderer.clear();
1320
1344
  if (isMainRenderer) {
@@ -1412,7 +1436,19 @@ var Scene = class _Scene {
1412
1436
  if (visible) {
1413
1437
  if (node instanceof ComputeParticleEntity) {
1414
1438
  if (this.deviceLost || this.webgpuDisabled || !this.device || !this.manager) {
1415
- this.renderCPUParticles(renderer, node, worldOpacity);
1439
+ this.renderCPUParticles(
1440
+ renderer,
1441
+ node,
1442
+ worldOpacity,
1443
+ a,
1444
+ b,
1445
+ c,
1446
+ d,
1447
+ te,
1448
+ tf,
1449
+ worldScaleX,
1450
+ isSimilarityTransform
1451
+ );
1416
1452
  }
1417
1453
  } else {
1418
1454
  node.render(renderer);
@@ -1436,6 +1472,7 @@ var Scene = class _Scene {
1436
1472
  if (isMainRenderer) {
1437
1473
  this.pointRenderer?.flush();
1438
1474
  }
1475
+ renderer.present?.();
1439
1476
  }
1440
1477
  /**
1441
1478
  * Export the current scene state to a lightweight, flat SVG XML string.
@@ -1455,6 +1492,10 @@ var Scene = class _Scene {
1455
1492
  this.renderer.resize(width, height);
1456
1493
  }
1457
1494
  this.pointRenderer?.resize(width, height);
1495
+ if (this.gpuCanvas) {
1496
+ this.gpuCanvas.width = width;
1497
+ this.gpuCanvas.height = height;
1498
+ }
1458
1499
  this.markDirty();
1459
1500
  }
1460
1501
  /**
@@ -1477,6 +1518,27 @@ var Scene = class _Scene {
1477
1518
  if (overlayHit) return overlayHit;
1478
1519
  return this.findHitRecursively(this.root, x, y);
1479
1520
  }
1521
+ /** Submit one transparent clear pass when particle content lingers on the GPU canvas. */
1522
+ clearGPUCanvasIfStale() {
1523
+ if (!this.gpuHasContent || !this.device || !this.gpuContext || this.deviceLost) return;
1524
+ try {
1525
+ const encoder = this.device.createCommandEncoder();
1526
+ const pass = encoder.beginRenderPass({
1527
+ colorAttachments: [
1528
+ {
1529
+ view: this.gpuContext.getCurrentTexture().createView(),
1530
+ clearValue: { r: 0, g: 0, b: 0, a: 0 },
1531
+ loadOp: "clear",
1532
+ storeOp: "store"
1533
+ }
1534
+ ]
1535
+ });
1536
+ pass.end();
1537
+ this.device.queue.submit([encoder.finish()]);
1538
+ } catch {
1539
+ }
1540
+ this.gpuHasContent = false;
1541
+ }
1480
1542
  async initWebGPUContext(entities) {
1481
1543
  if (!navigator.gpu) {
1482
1544
  throw new Error("WebGPU not supported on this platform.");
@@ -1569,10 +1631,10 @@ var Scene = class _Scene {
1569
1631
  }).catch(() => this.recreateWebGPUDeviceWithRetry(entities, attempt + 1));
1570
1632
  }, backoff);
1571
1633
  }
1572
- renderCPUParticles(renderer, entity, worldOpacity) {
1634
+ renderCPUParticles(renderer, entity, worldOpacity, a, b, c, d, e, f, worldScale, isSimilarityTransform) {
1573
1635
  const data = entity.particleData;
1574
1636
  const size = entity.maxParticles;
1575
- const isMain = renderer === this.renderer;
1637
+ const useGL = renderer === this.renderer && !!this.pointRenderer && isSimilarityTransform;
1576
1638
  for (let i = 0; i < size; i++) {
1577
1639
  const idx = i * 8;
1578
1640
  const x = data[idx];
@@ -1582,8 +1644,14 @@ var Scene = class _Scene {
1582
1644
  if (life === 0) continue;
1583
1645
  const opacity = life < 0 ? worldOpacity : worldOpacity * Math.min(1, life);
1584
1646
  const scale = life >= 0 ? Math.min(1, life) : 1;
1585
- if (isMain && this.pointRenderer) {
1586
- this.pointRenderer.addCircle(x, y, pSize * scale, entity.baseColor, opacity);
1647
+ if (useGL) {
1648
+ this.pointRenderer.addCircle(
1649
+ a * x + c * y + e,
1650
+ b * x + d * y + f,
1651
+ pSize * scale * worldScale,
1652
+ entity.baseColor,
1653
+ opacity
1654
+ );
1587
1655
  } else {
1588
1656
  renderer.fillCircle(x, y, pSize * scale, entity.baseColor, opacity);
1589
1657
  }
@@ -1809,6 +1877,11 @@ var SplineEntity = class extends Entity {
1809
1877
  bounds;
1810
1878
  offscreen = null;
1811
1879
  baked = false;
1880
+ /** Logical (CSS-pixel) size of the baked bitmap — the blit destination size. */
1881
+ bakedWidth = 0;
1882
+ bakedHeight = 0;
1883
+ /** Gradient strokes can't be baked to a solid-color bitmap; they render per-frame. */
1884
+ containsGradient;
1812
1885
  /** Lazily-flattened polylines (one Float32Array of [x,y,...] per segment) for hit-testing. */
1813
1886
  polylines = null;
1814
1887
  /**
@@ -1828,6 +1901,8 @@ var SplineEntity = class extends Entity {
1828
1901
  this.bounds = this.computeBounds();
1829
1902
  this.width = this.bounds.width;
1830
1903
  this.height = this.bounds.height;
1904
+ const isGradient = (c) => c !== null && !Array.isArray(c);
1905
+ this.containsGradient = (this.doc.equations?.some((eq) => isGradient(eq.color_rgb)) ?? false) || (this.doc.paths?.some((p) => isGradient(p.color_rgb)) ?? false);
1831
1906
  this.interactive = true;
1832
1907
  }
1833
1908
  computeBounds() {
@@ -1987,18 +2062,22 @@ var SplineEntity = class extends Entity {
1987
2062
  const pad = this.lineWidth + 2;
1988
2063
  const w = Math.max(1, Math.ceil(this.bounds.width) + pad * 2);
1989
2064
  const h = Math.max(1, Math.ceil(this.bounds.height) + pad * 2);
2065
+ this.bakedWidth = w;
2066
+ this.bakedHeight = h;
2067
+ const dpr = typeof window !== "undefined" && typeof window.devicePixelRatio === "number" ? window.devicePixelRatio || 1 : 1;
1990
2068
  let canvas;
1991
2069
  if (typeof OffscreenCanvas !== "undefined") {
1992
- canvas = new OffscreenCanvas(w, h);
2070
+ canvas = new OffscreenCanvas(w * dpr, h * dpr);
1993
2071
  } else if (typeof document !== "undefined") {
1994
2072
  canvas = document.createElement("canvas");
1995
- canvas.width = w;
1996
- canvas.height = h;
2073
+ canvas.width = w * dpr;
2074
+ canvas.height = h * dpr;
1997
2075
  } else {
1998
2076
  return;
1999
2077
  }
2000
2078
  const ctx = canvas.getContext("2d");
2001
2079
  if (!ctx) return;
2080
+ ctx.scale(dpr, dpr);
2002
2081
  ctx.translate(pad - this.bounds.x, pad - this.bounds.y);
2003
2082
  ctx.lineWidth = this.lineWidth;
2004
2083
  ctx.lineCap = "round";
@@ -2032,7 +2111,7 @@ var SplineEntity = class extends Entity {
2032
2111
  }
2033
2112
  render(r) {
2034
2113
  let rendered = false;
2035
- if (this.cache) {
2114
+ if (this.cache && !this.containsGradient) {
2036
2115
  if (!this.baked) this.bake();
2037
2116
  if (this.offscreen) {
2038
2117
  const pad = this.lineWidth + 2;
@@ -2040,8 +2119,9 @@ var SplineEntity = class extends Entity {
2040
2119
  this.offscreen,
2041
2120
  this.bounds.x - pad,
2042
2121
  this.bounds.y - pad,
2043
- this.offscreen.width,
2044
- this.offscreen.height
2122
+ // Logical size, not the (DPR-scaled) bitmap size.
2123
+ this.bakedWidth,
2124
+ this.bakedHeight
2045
2125
  );
2046
2126
  rendered = true;
2047
2127
  }
@@ -1 +1 @@
1
- export declare const 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";
1
+ export declare const WORKER_SOURCE_STRING = "\"use strict\";(()=>{var C=new Map;function f(t){return typeof t==\"number\"&&Number.isFinite(t)}function U(t){return t.origin?t.origin===self.location.origin:!0}function j(t){if(!t||typeof t!=\"object\")return!1;let e=t;return typeof e.id==\"string\"&&f(e.seqId)&&typeof e.text==\"string\"&&typeof e.fontId==\"string\"&&(e.fontData===void 0||typeof e.fontData==\"object\")&&f(e.maxWidth)&&f(e.maxHeight)&&f(e.fontSize)&&(e.lineHeight===void 0||f(e.lineHeight))&&(e.letterSpacing===void 0||f(e.letterSpacing))}self.onmessage=t=>{if(!U(t)||!j(t.data))return;let{id:e,seqId:D,text:L,fontId:k,fontData:S,maxWidth:H,maxHeight:z,fontSize:d,lineHeight:I,letterSpacing:M}=t.data;S&&C.set(k,S);let g=C.get(k);if(!g)return;let m=[],i=[],h=[],w=[],n=0,c=0,r=0,o=-1,p=g.metrics?.ascender??.8,R=g.metrics?.descender??-.2,b=I??d*(p-R),P=M??0,F=new Map;for(let s of g.glyphs??[])F.set(s.unicode,s.advance);let x=()=>{n>r&&(r=n),n=0,c++,o=-1},A=Array.from(L);for(let s=0;s<A.length;s++){let a=A[s].codePointAt(0);if(a===10){x();continue}let W=(F.get(a)??1)*d,q=a>=11904;if(q&&(o=-1),n+W>H&&n>0){if(a===32){x();continue}if(o>=0&&i[o]>0){let l=i[o];l>r&&(r=l),c++;let v=c*b+p*d;for(let y=o;y<i.length;y++)i[y]-=l,h[y]=v;n-=l}else x()}a===32?o=-1:o===-1&&!q&&(o=m.length),m.push(a),i.push(n),h.push(c*b+p*d),w.push(-256),n+=W+P}n>r&&(r=n);let u={id:e,seqId:D,width:r,height:(c+1)*b,codePoints:new Uint32Array(m),xCoords:new Float32Array(i),yCoords:new Float32Array(h),packedStyles:new Uint32Array(w)};self.postMessage(u,[u.codePoints.buffer,u.xCoords.buffer,u.yCoords.buffer,u.packedStyles.buffer])};})();\n";
package/dist/layout.js CHANGED
@@ -3,14 +3,14 @@
3
3
 
4
4
 
5
5
 
6
- var _chunk5KLB6BEZjs = require('./chunk-5KLB6BEZ.js');
6
+ var _chunkBWLNEJJWjs = require('./chunk-BWLNEJJW.js');
7
7
 
8
8
 
9
- var _chunk76NMTLYLjs = require('./chunk-76NMTLYL.js');
9
+ var _chunkCTZQOM5Zjs = require('./chunk-CTZQOM5Z.js');
10
10
 
11
11
 
12
12
 
13
13
 
14
14
 
15
15
 
16
- exports.LayoutEngine = _chunk5KLB6BEZjs.LayoutEngine; exports.LayoutResultBuffer = _chunk5KLB6BEZjs.LayoutResultBuffer; exports.LayoutWorkerManager = _chunk76NMTLYLjs.LayoutWorkerManager; exports.computeLineSegments = _chunk5KLB6BEZjs.computeLineSegments; exports.createCanvasMeasurer = _chunk5KLB6BEZjs.createCanvasMeasurer;
16
+ exports.LayoutEngine = _chunkBWLNEJJWjs.LayoutEngine; exports.LayoutResultBuffer = _chunkBWLNEJJWjs.LayoutResultBuffer; exports.LayoutWorkerManager = _chunkCTZQOM5Zjs.LayoutWorkerManager; exports.computeLineSegments = _chunkBWLNEJJWjs.computeLineSegments; exports.createCanvasMeasurer = _chunkBWLNEJJWjs.createCanvasMeasurer;
package/dist/layout.mjs CHANGED
@@ -3,10 +3,10 @@ import {
3
3
  LayoutResultBuffer,
4
4
  computeLineSegments,
5
5
  createCanvasMeasurer
6
- } from "./chunk-SB3SCWLT.mjs";
6
+ } from "./chunk-SSOP3F5F.mjs";
7
7
  import {
8
8
  LayoutWorkerManager
9
- } from "./chunk-B3Z3JEJH.mjs";
9
+ } from "./chunk-STWPTWO4.mjs";
10
10
  export {
11
11
  LayoutEngine,
12
12
  LayoutResultBuffer,
@@ -14,7 +14,18 @@ export declare class CanvasRenderer implements IRenderer {
14
14
  private batchColor;
15
15
  private batchAlpha;
16
16
  private batchCount;
17
- constructor(canvas: HTMLCanvasElement);
17
+ /**
18
+ * @param canvas - The target canvas. Its backing store is resized to the
19
+ * logical size × devicePixelRatio.
20
+ * @param size - Explicit logical size. Without it the renderer assumes a
21
+ * fullscreen canvas and sizes to the window — pass this for embedded /
22
+ * custom-container canvases (the Scene does when `disableWindowResize` is
23
+ * set) so the canvas's own dimensions aren't clobbered by the window's.
24
+ */
25
+ constructor(canvas: HTMLCanvasElement, size?: {
26
+ width: number;
27
+ height: number;
28
+ });
18
29
  /**
19
30
  * Expose the underlying `CanvasRenderingContext2D` for operations not
20
31
  * covered by the {@link IRenderer} interface.
@@ -175,6 +175,13 @@ export interface IRenderer {
175
175
  stop: number;
176
176
  color: string;
177
177
  }[]): any;
178
+ /**
179
+ * Present the completed frame, called by {@link Scene} exactly once at the
180
+ * end of each render pass (after the final {@link flush}). Retained-scene
181
+ * backends (e.g. `@vectojs/three`) do their single real GL render here;
182
+ * immediate-mode backends (Canvas2D, SVG) don't need it. Optional.
183
+ */
184
+ present?(): void;
178
185
  /**
179
186
  * Release any backend-owned GPU textures / GL contexts / caches.
180
187
  *
@@ -18,7 +18,7 @@
18
18
  * returns it verbatim — relative navigation is never script-injectable.
19
19
  * 4. If the URL parses with a scheme NOT in {@link SAFE_SCHEMES}, returns `'#'`
20
20
  * to keep the link non-empty but inert.
21
- * 5. Otherwise returns the canonical `URL.toString()` form.
21
+ * 5. Otherwise returns the trimmed input unchanged (no canonicalization).
22
22
  *
23
23
  * The function never throws; malformed input falls back to `'#'`.
24
24
  */
package/dist/renderer.js CHANGED
@@ -4,11 +4,11 @@
4
4
 
5
5
 
6
6
 
7
- var _chunkTPNADFNNjs = require('./chunk-TPNADFNN.js');
7
+ var _chunkGADBN53Zjs = require('./chunk-GADBN53Z.js');
8
8
 
9
9
 
10
10
 
11
11
 
12
12
 
13
13
 
14
- exports.CanvasRenderer = _chunkTPNADFNNjs.CanvasRenderer; exports.SVGRenderer = _chunkTPNADFNNjs.SVGRenderer; exports.WebGPUParticleSystemManager = _chunkTPNADFNNjs.WebGPUParticleSystemManager; exports.createWebGLPointRenderer = _chunkTPNADFNNjs.createWebGLPointRenderer; exports.parseColorToRGBA = _chunkTPNADFNNjs.parseColorToRGBA;
14
+ exports.CanvasRenderer = _chunkGADBN53Zjs.CanvasRenderer; exports.SVGRenderer = _chunkGADBN53Zjs.SVGRenderer; exports.WebGPUParticleSystemManager = _chunkGADBN53Zjs.WebGPUParticleSystemManager; exports.createWebGLPointRenderer = _chunkGADBN53Zjs.createWebGLPointRenderer; exports.parseColorToRGBA = _chunkGADBN53Zjs.parseColorToRGBA;
package/dist/renderer.mjs CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  WebGPUParticleSystemManager,
5
5
  createWebGLPointRenderer,
6
6
  parseColorToRGBA
7
- } from "./chunk-WEQRBXT2.mjs";
7
+ } from "./chunk-PK5EYWIT.mjs";
8
8
  export {
9
9
  CanvasRenderer,
10
10
  SVGRenderer,
@@ -8,6 +8,10 @@ export interface MSDFTextEntityOptions {
8
8
  color?: string;
9
9
  lineHeight?: number;
10
10
  letterSpacing?: number;
11
+ /** Wrap boundary in logical pixels. Defaults to 1000. */
12
+ maxWidth?: number;
13
+ /** Layout height limit in logical pixels. Defaults to 1000. */
14
+ maxHeight?: number;
11
15
  }
12
16
  export declare class MSDFTextEntity extends Entity {
13
17
  private font;
@@ -17,13 +21,18 @@ export declare class MSDFTextEntity extends Entity {
17
21
  color: string;
18
22
  private letterSpacing;
19
23
  private lineHeight?;
24
+ private maxWidth;
25
+ private maxHeight;
20
26
  private text;
21
27
  private lastRenderedSeqId;
22
28
  private rgbColorCache;
23
29
  private fontStringCache;
24
30
  private layoutResult;
25
31
  constructor(text: string, options: MSDFTextEntityOptions);
32
+ /** Change the wrap boundary and re-run layout for the current text. */
33
+ setMaxWidth(maxWidth: number): void;
26
34
  setText(text: string): void;
35
+ private queueLayout;
27
36
  isPointInside(globalX: number, globalY: number): boolean;
28
37
  render(renderer: any): void;
29
38
  destroy(): void;
package/dist/text.js CHANGED
@@ -2,15 +2,15 @@
2
2
 
3
3
 
4
4
 
5
- var _chunkTQ4H357Qjs = require('./chunk-TQ4H357Q.js');
5
+ var _chunk4BO5XBJLjs = require('./chunk-4BO5XBJL.js');
6
6
 
7
7
 
8
8
 
9
- var _chunk76NMTLYLjs = require('./chunk-76NMTLYL.js');
9
+ var _chunkCTZQOM5Zjs = require('./chunk-CTZQOM5Z.js');
10
10
 
11
11
 
12
12
 
13
13
 
14
14
 
15
15
 
16
- exports.ArabicShaper = _chunk76NMTLYLjs.ArabicShaper; exports.BidiResolver = _chunk76NMTLYLjs.BidiResolver; exports.MSDFFont = _chunkTQ4H357Qjs.MSDFFont; exports.MSDFTextEntity = _chunkTQ4H357Qjs.MSDFTextEntity; exports.SVGEntity = _chunkTQ4H357Qjs.SVGEntity;
16
+ exports.ArabicShaper = _chunkCTZQOM5Zjs.ArabicShaper; exports.BidiResolver = _chunkCTZQOM5Zjs.BidiResolver; exports.MSDFFont = _chunk4BO5XBJLjs.MSDFFont; exports.MSDFTextEntity = _chunk4BO5XBJLjs.MSDFTextEntity; exports.SVGEntity = _chunk4BO5XBJLjs.SVGEntity;
package/dist/text.mjs CHANGED
@@ -2,11 +2,11 @@ import {
2
2
  MSDFFont,
3
3
  MSDFTextEntity,
4
4
  SVGEntity
5
- } from "./chunk-P6MDWIEX.mjs";
5
+ } from "./chunk-SL654OMF.mjs";
6
6
  import {
7
7
  ArabicShaper,
8
8
  BidiResolver
9
- } from "./chunk-B3Z3JEJH.mjs";
9
+ } from "./chunk-STWPTWO4.mjs";
10
10
  export {
11
11
  ArabicShaper,
12
12
  BidiResolver,
@@ -41,9 +41,9 @@ export interface SceneOptions {
41
41
  debugA11y?: boolean;
42
42
  /**
43
43
  * Cap the render loop to at most this many frames per second (power saving —
44
- * e.g. a quieter fan in a library). `0` (default) means uncapped (native
45
- * refresh rate). Continuous animations still run, just less often. Also
46
- * settable later via {@link Scene.maxFPS}.
44
+ * e.g. a quieter fan in a library). `0` means uncapped (native refresh
45
+ * rate). Defaults to `60` (`0` under test runners). Continuous animations
46
+ * still run, just less often. Also settable later via {@link Scene.maxFPS}.
47
47
  */
48
48
  maxFPS?: number;
49
49
  /**
@@ -177,6 +177,8 @@ export declare class Scene {
177
177
  private initializingWebGPU;
178
178
  private gpuCanvas;
179
179
  private gpuContext;
180
+ /** True while the GPU canvas holds a presented particle frame (needs clearing when they leave). */
181
+ private gpuHasContent;
180
182
  private mouseX;
181
183
  private mouseY;
182
184
  private pointerMoveListener;
@@ -308,6 +310,8 @@ export declare class Scene {
308
310
  * Finds the topmost interactive entity at the given coordinates.
309
311
  */
310
312
  findEntityAt(x: number, y: number): Entity | null;
313
+ /** Submit one transparent clear pass when particle content lingers on the GPU canvas. */
314
+ private clearGPUCanvasIfStale;
311
315
  private initWebGPUContext;
312
316
  private setupDeviceLostHandler;
313
317
  private recreateWebGPUDeviceWithRetry;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vectojs/core",
3
- "version": "0.2.5",
3
+ "version": "0.2.6",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },