@vectojs/core 0.2.4 → 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.
@@ -1,8 +1,10 @@
1
1
  import {
2
2
  LayoutWorkerManager
3
- } from "./chunk-B3Z3JEJH.mjs";
3
+ } from "./chunk-STWPTWO4.mjs";
4
4
 
5
5
  // src/math/SpringPhysics.ts
6
+ var MAX_FRAME_DT = 0.25;
7
+ var MAX_STEP_DT = 1 / 120;
6
8
  var SpringPhysics = class {
7
9
  value;
8
10
  target;
@@ -22,11 +24,22 @@ var SpringPhysics = class {
22
24
  this.velocity = 0;
23
25
  return;
24
26
  }
25
- const forceSpring = -this.stiffness * (this.value - this.target);
26
- const forceDamping = -this.damping * this.velocity;
27
- const acceleration = (forceSpring + forceDamping) / this.mass;
28
- this.velocity += acceleration * dt;
29
- this.value += this.velocity * dt;
27
+ if (!(dt > 0)) return;
28
+ let remaining = dt < MAX_FRAME_DT ? dt : MAX_FRAME_DT;
29
+ while (remaining > 0) {
30
+ const step = remaining < MAX_STEP_DT ? remaining : MAX_STEP_DT;
31
+ const forceSpring = -this.stiffness * (this.value - this.target);
32
+ const forceDamping = -this.damping * this.velocity;
33
+ const acceleration = (forceSpring + forceDamping) / this.mass;
34
+ this.velocity += acceleration * step;
35
+ this.value += this.velocity * step;
36
+ remaining -= step;
37
+ if (this.isAtRest()) {
38
+ this.value = this.target;
39
+ this.velocity = 0;
40
+ return;
41
+ }
42
+ }
30
43
  }
31
44
  isAtRest() {
32
45
  return Math.abs(this.value - this.target) < this.valEpsilon && Math.abs(this.velocity) < this.velEpsilon;
@@ -117,6 +130,14 @@ var SpringDriver = class {
117
130
  };
118
131
 
119
132
  // src/tree/Entity.ts
133
+ var ANIMATABLE_PROPS = /* @__PURE__ */ new Set([
134
+ "x",
135
+ "y",
136
+ "scaleX",
137
+ "scaleY",
138
+ "rotation",
139
+ "opacity"
140
+ ]);
120
141
  var VectoJSEvent = class {
121
142
  /** The event name. */
122
143
  type;
@@ -568,7 +589,12 @@ var Entity = class {
568
589
  const end = anim.target[key];
569
590
  if (typeof start === "number" && typeof end === "number") {
570
591
  const easeOut = progress * (2 - progress);
571
- this[key] = start + (end - start) * easeOut;
592
+ const value = start + (end - start) * easeOut;
593
+ if (ANIMATABLE_PROPS.has(key)) {
594
+ this._applyAnimated(key, value);
595
+ } else {
596
+ this[key] = value;
597
+ }
572
598
  }
573
599
  }
574
600
  if (progress >= 1) {
@@ -619,6 +645,10 @@ var Entity = class {
619
645
  */
620
646
  destroy() {
621
647
  this.animations = [];
648
+ for (const driver of this._drivers.values()) {
649
+ this._settleDriver(driver);
650
+ }
651
+ this._drivers.clear();
622
652
  this.listeners.clear();
623
653
  this.captureListeners.clear();
624
654
  if (this.parent) {
@@ -989,6 +1019,8 @@ var MSDFTextEntity = class extends Entity {
989
1019
  color;
990
1020
  letterSpacing;
991
1021
  lineHeight;
1022
+ maxWidth;
1023
+ maxHeight;
992
1024
  text = "";
993
1025
  lastRenderedSeqId = 0;
994
1026
  rgbColorCache = /* @__PURE__ */ new Map();
@@ -1003,17 +1035,27 @@ var MSDFTextEntity = class extends Entity {
1003
1035
  this.color = options.color ?? "#ffffff";
1004
1036
  this.letterSpacing = options.letterSpacing ?? 0;
1005
1037
  this.lineHeight = options.lineHeight;
1038
+ this.maxWidth = options.maxWidth ?? 1e3;
1039
+ this.maxHeight = options.maxHeight ?? 1e3;
1006
1040
  this.setText(text);
1007
1041
  }
1042
+ /** Change the wrap boundary and re-run layout for the current text. */
1043
+ setMaxWidth(maxWidth) {
1044
+ if (this.maxWidth === maxWidth) return;
1045
+ this.maxWidth = maxWidth;
1046
+ this.queueLayout();
1047
+ }
1008
1048
  setText(text) {
1009
1049
  if (this.text === text && this.layoutResult) return;
1010
1050
  this.text = text;
1051
+ this.queueLayout();
1052
+ }
1053
+ queueLayout() {
1011
1054
  LayoutWorkerManager.getInstance().queueLayout(this.id, this.text, {
1012
1055
  fontId: this.font.id,
1013
1056
  fontSize: this.fontSize,
1014
- maxWidth: 1e3,
1015
- // standard wrap boundary
1016
- maxHeight: 1e3,
1057
+ maxWidth: this.maxWidth,
1058
+ maxHeight: this.maxHeight,
1017
1059
  fontData: this.font.data,
1018
1060
  letterSpacing: this.letterSpacing,
1019
1061
  lineHeight: this.lineHeight,
@@ -1042,6 +1084,8 @@ var MSDFTextEntity = class extends Entity {
1042
1084
  if (scene && scene.pointRenderer && scene.glCanvas && canUsePointGlyphs) {
1043
1085
  scene.pointRenderer.setMSDFTexture(this.texture, this.font.distanceRange);
1044
1086
  const worldRot = Math.atan2(world.b, world.a);
1087
+ let worldOpacity = this.opacity;
1088
+ for (let p = this.parent; p; p = p.parent) worldOpacity *= p.opacity;
1045
1089
  const len2 = this.layoutResult.codePoints.length;
1046
1090
  for (let i = 0; i < len2; i++) {
1047
1091
  const code = this.layoutResult.codePoints[i];
@@ -1080,7 +1124,7 @@ var MSDFTextEntity = class extends Entity {
1080
1124
  ab.right / aw,
1081
1125
  v1,
1082
1126
  runColor,
1083
- this.opacity,
1127
+ worldOpacity,
1084
1128
  worldRot
1085
1129
  );
1086
1130
  }
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  ArabicShaper,
3
3
  BidiResolver
4
- } from "./chunk-B3Z3JEJH.mjs";
4
+ } from "./chunk-STWPTWO4.mjs";
5
5
 
6
6
  // src/layout/LayoutEngine.ts
7
7
  function computeLineSegments(top, bottom, maxWidth, exclusions) {
@@ -79,6 +79,12 @@ var LayoutEngine = class {
79
79
  if (this.measurer) return this.measurer.measure(char, fontSize);
80
80
  return fontSize * 0.5;
81
81
  }
82
+ glyphKeyFor(grapheme, fontAtlas) {
83
+ if (fontAtlas[grapheme]) return grapheme;
84
+ const firstCodePoint = Array.from(grapheme)[0];
85
+ if (firstCodePoint && fontAtlas[firstCodePoint]) return firstCodePoint;
86
+ return grapheme;
87
+ }
82
88
  getGraphemes(word) {
83
89
  const cached = this.graphemeCache.get(word);
84
90
  if (cached) return cached;
@@ -158,25 +164,20 @@ var LayoutEngine = class {
158
164
  const rawEnd = visualEnd === shapedText.length ? paragraph.length : indexMap[visualEnd];
159
165
  const sourceIndex = offset + rawStart;
160
166
  const sourceLength = rawEnd - rawStart;
161
- const baseChar = char[0];
167
+ const glyphKey = this.glyphKeyFor(char, fontAtlas);
162
168
  const level = levels[visualStart];
163
- const hasGlyph = !!fontAtlas[char] || !!fontAtlas[baseChar];
169
+ const hasGlyph = !!fontAtlas[glyphKey];
164
170
  if (char.trim().length > 0 && !hasGlyph) {
165
171
  pFallback = true;
166
172
  fallbackToCanvas = true;
167
173
  }
168
- const w = this.glyphWidth(baseChar, fontAtlas, fontSize);
169
- const combining = [];
170
- for (let cIdx = 1; cIdx < char.length; cIdx++) {
171
- combining.push(char[cIdx]);
172
- }
174
+ const w = this.glyphWidth(glyphKey, fontAtlas, fontSize);
173
175
  glyphs.push({
174
- char: baseChar,
176
+ char,
175
177
  width: w,
176
178
  level,
177
179
  sourceIndex,
178
- sourceLength,
179
- combining: combining.length > 0 ? combining : void 0
180
+ sourceLength
180
181
  });
181
182
  width += w;
182
183
  shapedCharIdx += char.length;
@@ -280,28 +281,23 @@ var LayoutEngine = class {
280
281
  const rawEnd = visualEnd === shapedText.length ? paragraph.length : indexMap[visualEnd];
281
282
  const sourceIndex = offset + rawStart;
282
283
  const sourceLength = rawEnd - rawStart;
283
- const baseChar = char[0];
284
+ const glyphKey = this.glyphKeyFor(char, fontAtlas);
284
285
  const level = levels[visualStart];
285
286
  const style = styleAt[offset + rawStart];
286
287
  const gfs = style?.fontSize ?? baseFontSize;
287
- const hasGlyph = !!fontAtlas[char] || !!fontAtlas[baseChar];
288
+ const hasGlyph = !!fontAtlas[glyphKey];
288
289
  if (char.trim().length > 0 && !hasGlyph) {
289
290
  pFallback = true;
290
291
  fallbackToCanvas = true;
291
292
  }
292
- const w = this.glyphWidth(baseChar, fontAtlas, gfs);
293
- const combining = [];
294
- for (let cIdx = 1; cIdx < char.length; cIdx++) {
295
- combining.push(char[cIdx]);
296
- }
293
+ const w = this.glyphWidth(glyphKey, fontAtlas, gfs);
297
294
  glyphs.push({
298
- char: baseChar,
295
+ char,
299
296
  width: w,
300
297
  style,
301
298
  level,
302
299
  sourceIndex,
303
- sourceLength,
304
- combining: combining.length > 0 ? combining : void 0
300
+ sourceLength
305
301
  });
306
302
  width += w;
307
303
  shapedCharIdx += char.length;
@@ -296,7 +296,7 @@ var BidiResolver = class _BidiResolver {
296
296
  };
297
297
 
298
298
  // src/layout/LayoutWorkerSource.ts
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';
299
+ var 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';
300
300
 
301
301
  // src/layout/LayoutWorkerManager.ts
302
302
  var LayoutWorkerManager = class _LayoutWorkerManager {
@@ -96,6 +96,11 @@ export declare class SplineEntity extends Entity {
96
96
  private bounds;
97
97
  private offscreen;
98
98
  private baked;
99
+ /** Logical (CSS-pixel) size of the baked bitmap — the blit destination size. */
100
+ private bakedWidth;
101
+ private bakedHeight;
102
+ /** Gradient strokes can't be baked to a solid-color bitmap; they render per-frame. */
103
+ private readonly containsGradient;
99
104
  /** Lazily-flattened polylines (one Float32Array of [x,y,...] per segment) for hit-testing. */
100
105
  private polylines;
101
106
  /**