@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.
@@ -1,8 +1,10 @@
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; var _class3; var _class4; var _class5; var _class6; var _class7;
2
2
 
3
- var _chunk76NMTLYLjs = require('./chunk-76NMTLYL.js');
3
+ var _chunkCTZQOM5Zjs = require('./chunk-CTZQOM5Z.js');
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 = class {
7
9
 
8
10
 
@@ -22,11 +24,22 @@ var SpringPhysics = (_class = 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 = (_class3 = class {
121
142
  /** The event name. */
122
143
 
@@ -568,7 +589,12 @@ var Entity = (_class4 = 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 = (_class4 = 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 = (_class6 = class extends Entity {
989
1019
 
990
1020
 
991
1021
 
1022
+
1023
+
992
1024
  __init35() {this.text = ""}
993
1025
  __init36() {this.lastRenderedSeqId = 0}
994
1026
  __init37() {this.rgbColorCache = /* @__PURE__ */ new Map()}
@@ -1003,17 +1035,27 @@ var MSDFTextEntity = (_class6 = class extends Entity {
1003
1035
  this.color = _nullishCoalesce(options.color, () => ( "#ffffff"));
1004
1036
  this.letterSpacing = _nullishCoalesce(options.letterSpacing, () => ( 0));
1005
1037
  this.lineHeight = options.lineHeight;
1038
+ this.maxWidth = _nullishCoalesce(options.maxWidth, () => ( 1e3));
1039
+ this.maxHeight = _nullishCoalesce(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;
1011
- _chunk76NMTLYLjs.LayoutWorkerManager.getInstance().queueLayout(this.id, this.text, {
1051
+ this.queueLayout();
1052
+ }
1053
+ queueLayout() {
1054
+ _chunkCTZQOM5Zjs.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 = (_class6 = 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 = (_class6 = 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
  }
@@ -1112,7 +1156,7 @@ var MSDFTextEntity = (_class6 = class extends Entity {
1112
1156
  }
1113
1157
  }
1114
1158
  destroy() {
1115
- _chunk76NMTLYLjs.LayoutWorkerManager.getInstance().cancelLayout(this.id);
1159
+ _chunkCTZQOM5Zjs.LayoutWorkerManager.getInstance().cancelLayout(this.id);
1116
1160
  super.destroy();
1117
1161
  }
1118
1162
  }, _class6);
@@ -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 _chunk76NMTLYLjs = require('./chunk-76NMTLYL.js');
4
+ var _chunkCTZQOM5Zjs = require('./chunk-CTZQOM5Z.js');
5
5
 
6
6
  // src/layout/LayoutEngine.ts
7
7
  function computeLineSegments(top, bottom, maxWidth, exclusions) {
@@ -148,8 +148,8 @@ var LayoutEngine = (_class = class {
148
148
  offset += paragraph.length + 1;
149
149
  continue;
150
150
  }
151
- const { shapedText, indexMap } = _chunk76NMTLYLjs.ArabicShaper.shapeArabic(paragraph);
152
- const levels = _chunk76NMTLYLjs.BidiResolver.resolveLevels(shapedText);
151
+ const { shapedText, indexMap } = _chunkCTZQOM5Zjs.ArabicShaper.shapeArabic(paragraph);
152
+ const levels = _chunkCTZQOM5Zjs.BidiResolver.resolveLevels(shapedText);
153
153
  const words = [];
154
154
  let shapedCharIdx = 0;
155
155
  let pFallback = false;
@@ -193,7 +193,7 @@ var LayoutEngine = (_class = class {
193
193
  words,
194
194
  isEmpty: false,
195
195
  fallbackToCanvas: pFallback || void 0,
196
- baseLevel: _chunk76NMTLYLjs.BidiResolver.getBaseLevel(shapedText)
196
+ baseLevel: _chunkCTZQOM5Zjs.BidiResolver.getBaseLevel(shapedText)
197
197
  };
198
198
  if (this.paragraphCache.size > 1e3) this.paragraphCache.clear();
199
199
  this.paragraphCache.set(key, prepared);
@@ -265,8 +265,8 @@ var LayoutEngine = (_class = class {
265
265
  offset += paragraph.length + 1;
266
266
  continue;
267
267
  }
268
- const { shapedText, indexMap } = _chunk76NMTLYLjs.ArabicShaper.shapeArabic(paragraph);
269
- const levels = _chunk76NMTLYLjs.BidiResolver.resolveLevels(shapedText);
268
+ const { shapedText, indexMap } = _chunkCTZQOM5Zjs.ArabicShaper.shapeArabic(paragraph);
269
+ const levels = _chunkCTZQOM5Zjs.BidiResolver.resolveLevels(shapedText);
270
270
  const words = [];
271
271
  let shapedCharIdx = 0;
272
272
  let pFallback = false;
@@ -313,7 +313,7 @@ var LayoutEngine = (_class = class {
313
313
  words,
314
314
  isEmpty: false,
315
315
  fallbackToCanvas: pFallback || void 0,
316
- baseLevel: _chunk76NMTLYLjs.BidiResolver.getBaseLevel(shapedText)
316
+ baseLevel: _chunkCTZQOM5Zjs.BidiResolver.getBaseLevel(shapedText)
317
317
  };
318
318
  if (this.richParagraphCache.size > 1e3) this.richParagraphCache.clear();
319
319
  this.richParagraphCache.set(key, prepared);
@@ -368,7 +368,7 @@ var LayoutEngine = (_class = class {
368
368
  }
369
369
  for (const run of runs) {
370
370
  const runStartX = run[0].x;
371
- _chunk76NMTLYLjs.BidiResolver.reorderVisual(run, paragraphBaseLevel);
371
+ _chunkCTZQOM5Zjs.BidiResolver.reorderVisual(run, paragraphBaseLevel);
372
372
  let x = runStartX;
373
373
  for (const node of run) {
374
374
  node.x = x;
@@ -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 = (_class2 = class _LayoutWorkerManager {
@@ -20,10 +20,18 @@ var CanvasRenderer = (_class = class _CanvasRenderer {
20
20
  __init2() {this.batchColor = ""}
21
21
  __init3() {this.batchAlpha = 1}
22
22
  __init4() {this.batchCount = 0}
23
- constructor(canvas) {;_class.prototype.__init.call(this);_class.prototype.__init2.call(this);_class.prototype.__init3.call(this);_class.prototype.__init4.call(this);
23
+ /**
24
+ * @param canvas - The target canvas. Its backing store is resized to the
25
+ * logical size × devicePixelRatio.
26
+ * @param size - Explicit logical size. Without it the renderer assumes a
27
+ * fullscreen canvas and sizes to the window — pass this for embedded /
28
+ * custom-container canvases (the Scene does when `disableWindowResize` is
29
+ * set) so the canvas's own dimensions aren't clobbered by the window's.
30
+ */
31
+ constructor(canvas, size) {;_class.prototype.__init.call(this);_class.prototype.__init2.call(this);_class.prototype.__init3.call(this);_class.prototype.__init4.call(this);
24
32
  const dpr = getDevicePixelRatio();
25
- this.width = typeof window !== "undefined" ? window.innerWidth : canvas.width || 0;
26
- this.height = typeof window !== "undefined" ? window.innerHeight : canvas.height || 0;
33
+ this.width = _nullishCoalesce(_optionalChain([size, 'optionalAccess', _ => _.width]), () => ( (typeof window !== "undefined" ? window.innerWidth : canvas.width || 0)));
34
+ this.height = _nullishCoalesce(_optionalChain([size, 'optionalAccess', _2 => _2.height]), () => ( (typeof window !== "undefined" ? window.innerHeight : canvas.height || 0)));
27
35
  canvas.width = this.width * dpr;
28
36
  canvas.height = this.height * dpr;
29
37
  const ctx = canvas.getContext("2d");
@@ -414,18 +422,19 @@ var SVGRenderer = (_class2 = class {
414
422
  } else {
415
423
  this.currentPath.push(`L ${xs} ${ys}`);
416
424
  }
417
- const deltaAngle = Math.abs(endAngle - startAngle);
418
- if (deltaAngle >= Math.PI * 2 - 1e-4) {
425
+ const TWO_PI2 = Math.PI * 2;
426
+ const directedDelta = ccw ? startAngle - endAngle : endAngle - startAngle;
427
+ const sweepAngle = directedDelta >= TWO_PI2 ? TWO_PI2 : (directedDelta % TWO_PI2 + TWO_PI2) % TWO_PI2;
428
+ const sweep = ccw ? 0 : 1;
429
+ if (sweepAngle >= TWO_PI2 - 1e-4) {
419
430
  const xm = x - r * Math.cos(startAngle);
420
431
  const ym = y - r * Math.sin(startAngle);
421
- const sweep = ccw ? 0 : 1;
422
432
  this.currentPath.push(`A ${r} ${r} 0 0 ${sweep} ${xm} ${ym}`);
423
433
  this.currentPath.push(`A ${r} ${r} 0 0 ${sweep} ${xs} ${ys}`);
424
434
  } else {
425
435
  const xe = x + r * Math.cos(endAngle);
426
436
  const ye = y + r * Math.sin(endAngle);
427
- const largeArc = deltaAngle > Math.PI ? 1 : 0;
428
- const sweep = ccw ? 0 : 1;
437
+ const largeArc = sweepAngle > Math.PI ? 1 : 0;
429
438
  this.currentPath.push(`A ${r} ${r} 0 ${largeArc} ${sweep} ${xe} ${ye}`);
430
439
  }
431
440
  }
@@ -537,8 +546,8 @@ var SVGRenderer = (_class2 = class {
537
546
  }
538
547
  drawImage(source, dx, dy, dw, dh) {
539
548
  this.flush();
540
- const fromCanvas2 = typeof _optionalChain([source, 'optionalAccess', _ => _.toDataURL]) === "function";
541
- const rawHref = fromCanvas2 ? source.toDataURL() : _optionalChain([source, 'optionalAccess', _2 => _2.src]) || "";
549
+ const fromCanvas2 = typeof _optionalChain([source, 'optionalAccess', _3 => _3.toDataURL]) === "function";
550
+ const rawHref = fromCanvas2 ? source.toDataURL() : _optionalChain([source, 'optionalAccess', _4 => _4.src]) || "";
542
551
  const href = fromCanvas2 && this.isSafeRasterDataUrl(rawHref) ? rawHref : sanitizeUrl(String(rawHref));
543
552
  const transformStr = `matrix(${this.ma},${this.mb},${this.mc},${this.md},${this.me},${this.mf})`;
544
553
  if (href) {
@@ -723,6 +732,7 @@ function fromCanvas(css) {
723
732
  if (typeof document === "undefined") return null;
724
733
  if (!fallbackCtx) fallbackCtx = document.createElement("canvas").getContext("2d");
725
734
  if (!fallbackCtx) return null;
735
+ fallbackCtx.clearRect(0, 0, 1, 1);
726
736
  fallbackCtx.fillStyle = css;
727
737
  fallbackCtx.fillRect(0, 0, 1, 1);
728
738
  const d = fallbackCtx.getImageData(0, 0, 1, 1).data;
@@ -869,7 +879,9 @@ function grow(data, needed) {
869
879
  return grown;
870
880
  }
871
881
  function createWebGLPointRenderer(canvas) {
872
- const gl = canvas.getContext("webgl2");
882
+ const gl = canvas.getContext("webgl2", {
883
+ premultipliedAlpha: false
884
+ });
873
885
  if (!gl) return null;
874
886
  const pointProgram = link(gl, POINT_VERT, POINT_FRAG);
875
887
  const rectProgram = link(gl, RECT_VERT, RECT_FRAG);
@@ -937,7 +949,9 @@ function createWebGLPointRenderer(canvas) {
937
949
  gl.vertexAttribPointer(gATint, 4, gl.FLOAT, false, SPRITE_VERT_STRIDE, 16);
938
950
  gl.bindVertexArray(null);
939
951
  let texture = null;
952
+ let textureSource = null;
940
953
  let msdfTexture = null;
954
+ let msdfSource = null;
941
955
  let distanceRange = 4;
942
956
  let pointData = new Float32Array(FLOATS_PER_POINT * 1024);
943
957
  let pointCount = 0;
@@ -951,6 +965,22 @@ function createWebGLPointRenderer(canvas) {
951
965
  let logicalH = 0;
952
966
  let dpr = 1;
953
967
  let destroyed = false;
968
+ const drawGlyphs = () => {
969
+ if (glyphCount === 0 || !msdfTexture) return;
970
+ const floats = glyphCount * VERTS_PER_SPRITE * FLOATS_PER_SPRITE_VERT;
971
+ gl.useProgram(msdfProgram);
972
+ gl.bindVertexArray(glyphVAO);
973
+ gl.bindBuffer(gl.ARRAY_BUFFER, glyphBuffer);
974
+ gl.bufferData(gl.ARRAY_BUFFER, glyphData.subarray(0, floats), gl.DYNAMIC_DRAW);
975
+ gl.activeTexture(gl.TEXTURE0);
976
+ gl.bindTexture(gl.TEXTURE_2D, msdfTexture);
977
+ gl.uniform1i(gUTex, 0);
978
+ gl.uniform2f(gURes, logicalW, logicalH);
979
+ gl.uniform1f(gURange, distanceRange);
980
+ gl.drawArrays(gl.TRIANGLES, 0, glyphCount * VERTS_PER_SPRITE);
981
+ gl.bindVertexArray(null);
982
+ glyphCount = 0;
983
+ };
954
984
  return {
955
985
  resize(width, height) {
956
986
  logicalW = width;
@@ -967,8 +997,11 @@ function createWebGLPointRenderer(canvas) {
967
997
  rectCount = 0;
968
998
  spriteCount = 0;
969
999
  glyphCount = 0;
1000
+ gl.clearColor(0, 0, 0, 0);
1001
+ gl.clear(gl.COLOR_BUFFER_BIT);
970
1002
  },
971
1003
  setTexture(source) {
1004
+ if (source === textureSource && texture) return;
972
1005
  if (!texture) {
973
1006
  texture = gl.createTexture();
974
1007
  gl.bindTexture(gl.TEXTURE_2D, texture);
@@ -980,6 +1013,7 @@ function createWebGLPointRenderer(canvas) {
980
1013
  gl.bindTexture(gl.TEXTURE_2D, texture);
981
1014
  }
982
1015
  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, source);
1016
+ textureSource = source;
983
1017
  },
984
1018
  addSprite(x, y, width, height, u0, v0, u1, v1, color = "#ffffff", alpha = 1, rotation = 0) {
985
1019
  if (!texture) return;
@@ -1016,6 +1050,11 @@ function createWebGLPointRenderer(canvas) {
1016
1050
  spriteCount++;
1017
1051
  },
1018
1052
  setMSDFTexture(source, range) {
1053
+ if (source === msdfSource && msdfTexture) {
1054
+ distanceRange = range;
1055
+ return;
1056
+ }
1057
+ drawGlyphs();
1019
1058
  distanceRange = range;
1020
1059
  if (!msdfTexture) {
1021
1060
  msdfTexture = gl.createTexture();
@@ -1028,6 +1067,7 @@ function createWebGLPointRenderer(canvas) {
1028
1067
  gl.bindTexture(gl.TEXTURE_2D, msdfTexture);
1029
1068
  }
1030
1069
  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, source);
1070
+ msdfSource = source;
1031
1071
  },
1032
1072
  addGlyph(x, y, width, height, u0, v0, u1, v1, color = "#ffffff", alpha = 1, rotation = 0) {
1033
1073
  if (!msdfTexture) return;
@@ -1105,8 +1145,6 @@ function createWebGLPointRenderer(canvas) {
1105
1145
  rectCount++;
1106
1146
  },
1107
1147
  flush() {
1108
- gl.clearColor(0, 0, 0, 0);
1109
- gl.clear(gl.COLOR_BUFFER_BIT);
1110
1148
  if (rectCount > 0) {
1111
1149
  const floats = rectCount * VERTS_PER_RECT * FLOATS_PER_RECT_VERT;
1112
1150
  gl.useProgram(rectProgram);
@@ -1141,20 +1179,8 @@ function createWebGLPointRenderer(canvas) {
1141
1179
  gl.uniform2f(sURes, logicalW, logicalH);
1142
1180
  gl.drawArrays(gl.TRIANGLES, 0, spriteCount * VERTS_PER_SPRITE);
1143
1181
  }
1144
- if (glyphCount > 0 && msdfTexture) {
1145
- const floats = glyphCount * VERTS_PER_SPRITE * FLOATS_PER_SPRITE_VERT;
1146
- gl.useProgram(msdfProgram);
1147
- gl.bindVertexArray(glyphVAO);
1148
- gl.bindBuffer(gl.ARRAY_BUFFER, glyphBuffer);
1149
- gl.bufferData(gl.ARRAY_BUFFER, glyphData.subarray(0, floats), gl.DYNAMIC_DRAW);
1150
- gl.activeTexture(gl.TEXTURE0);
1151
- gl.bindTexture(gl.TEXTURE_2D, msdfTexture);
1152
- gl.uniform1i(gUTex, 0);
1153
- gl.uniform2f(gURes, logicalW, logicalH);
1154
- gl.uniform1f(gURange, distanceRange);
1155
- gl.drawArrays(gl.TRIANGLES, 0, glyphCount * VERTS_PER_SPRITE);
1156
- }
1157
1182
  gl.bindVertexArray(null);
1183
+ drawGlyphs();
1158
1184
  },
1159
1185
  destroy() {
1160
1186
  if (destroyed) return;
@@ -20,10 +20,18 @@ var CanvasRenderer = class _CanvasRenderer {
20
20
  batchColor = "";
21
21
  batchAlpha = 1;
22
22
  batchCount = 0;
23
- constructor(canvas) {
23
+ /**
24
+ * @param canvas - The target canvas. Its backing store is resized to the
25
+ * logical size × devicePixelRatio.
26
+ * @param size - Explicit logical size. Without it the renderer assumes a
27
+ * fullscreen canvas and sizes to the window — pass this for embedded /
28
+ * custom-container canvases (the Scene does when `disableWindowResize` is
29
+ * set) so the canvas's own dimensions aren't clobbered by the window's.
30
+ */
31
+ constructor(canvas, size) {
24
32
  const dpr = getDevicePixelRatio();
25
- this.width = typeof window !== "undefined" ? window.innerWidth : canvas.width || 0;
26
- this.height = typeof window !== "undefined" ? window.innerHeight : canvas.height || 0;
33
+ this.width = size?.width ?? (typeof window !== "undefined" ? window.innerWidth : canvas.width || 0);
34
+ this.height = size?.height ?? (typeof window !== "undefined" ? window.innerHeight : canvas.height || 0);
27
35
  canvas.width = this.width * dpr;
28
36
  canvas.height = this.height * dpr;
29
37
  const ctx = canvas.getContext("2d");
@@ -414,18 +422,19 @@ var SVGRenderer = class {
414
422
  } else {
415
423
  this.currentPath.push(`L ${xs} ${ys}`);
416
424
  }
417
- const deltaAngle = Math.abs(endAngle - startAngle);
418
- if (deltaAngle >= Math.PI * 2 - 1e-4) {
425
+ const TWO_PI2 = Math.PI * 2;
426
+ const directedDelta = ccw ? startAngle - endAngle : endAngle - startAngle;
427
+ const sweepAngle = directedDelta >= TWO_PI2 ? TWO_PI2 : (directedDelta % TWO_PI2 + TWO_PI2) % TWO_PI2;
428
+ const sweep = ccw ? 0 : 1;
429
+ if (sweepAngle >= TWO_PI2 - 1e-4) {
419
430
  const xm = x - r * Math.cos(startAngle);
420
431
  const ym = y - r * Math.sin(startAngle);
421
- const sweep = ccw ? 0 : 1;
422
432
  this.currentPath.push(`A ${r} ${r} 0 0 ${sweep} ${xm} ${ym}`);
423
433
  this.currentPath.push(`A ${r} ${r} 0 0 ${sweep} ${xs} ${ys}`);
424
434
  } else {
425
435
  const xe = x + r * Math.cos(endAngle);
426
436
  const ye = y + r * Math.sin(endAngle);
427
- const largeArc = deltaAngle > Math.PI ? 1 : 0;
428
- const sweep = ccw ? 0 : 1;
437
+ const largeArc = sweepAngle > Math.PI ? 1 : 0;
429
438
  this.currentPath.push(`A ${r} ${r} 0 ${largeArc} ${sweep} ${xe} ${ye}`);
430
439
  }
431
440
  }
@@ -723,6 +732,7 @@ function fromCanvas(css) {
723
732
  if (typeof document === "undefined") return null;
724
733
  if (!fallbackCtx) fallbackCtx = document.createElement("canvas").getContext("2d");
725
734
  if (!fallbackCtx) return null;
735
+ fallbackCtx.clearRect(0, 0, 1, 1);
726
736
  fallbackCtx.fillStyle = css;
727
737
  fallbackCtx.fillRect(0, 0, 1, 1);
728
738
  const d = fallbackCtx.getImageData(0, 0, 1, 1).data;
@@ -869,7 +879,9 @@ function grow(data, needed) {
869
879
  return grown;
870
880
  }
871
881
  function createWebGLPointRenderer(canvas) {
872
- const gl = canvas.getContext("webgl2");
882
+ const gl = canvas.getContext("webgl2", {
883
+ premultipliedAlpha: false
884
+ });
873
885
  if (!gl) return null;
874
886
  const pointProgram = link(gl, POINT_VERT, POINT_FRAG);
875
887
  const rectProgram = link(gl, RECT_VERT, RECT_FRAG);
@@ -937,7 +949,9 @@ function createWebGLPointRenderer(canvas) {
937
949
  gl.vertexAttribPointer(gATint, 4, gl.FLOAT, false, SPRITE_VERT_STRIDE, 16);
938
950
  gl.bindVertexArray(null);
939
951
  let texture = null;
952
+ let textureSource = null;
940
953
  let msdfTexture = null;
954
+ let msdfSource = null;
941
955
  let distanceRange = 4;
942
956
  let pointData = new Float32Array(FLOATS_PER_POINT * 1024);
943
957
  let pointCount = 0;
@@ -951,6 +965,22 @@ function createWebGLPointRenderer(canvas) {
951
965
  let logicalH = 0;
952
966
  let dpr = 1;
953
967
  let destroyed = false;
968
+ const drawGlyphs = () => {
969
+ if (glyphCount === 0 || !msdfTexture) return;
970
+ const floats = glyphCount * VERTS_PER_SPRITE * FLOATS_PER_SPRITE_VERT;
971
+ gl.useProgram(msdfProgram);
972
+ gl.bindVertexArray(glyphVAO);
973
+ gl.bindBuffer(gl.ARRAY_BUFFER, glyphBuffer);
974
+ gl.bufferData(gl.ARRAY_BUFFER, glyphData.subarray(0, floats), gl.DYNAMIC_DRAW);
975
+ gl.activeTexture(gl.TEXTURE0);
976
+ gl.bindTexture(gl.TEXTURE_2D, msdfTexture);
977
+ gl.uniform1i(gUTex, 0);
978
+ gl.uniform2f(gURes, logicalW, logicalH);
979
+ gl.uniform1f(gURange, distanceRange);
980
+ gl.drawArrays(gl.TRIANGLES, 0, glyphCount * VERTS_PER_SPRITE);
981
+ gl.bindVertexArray(null);
982
+ glyphCount = 0;
983
+ };
954
984
  return {
955
985
  resize(width, height) {
956
986
  logicalW = width;
@@ -967,8 +997,11 @@ function createWebGLPointRenderer(canvas) {
967
997
  rectCount = 0;
968
998
  spriteCount = 0;
969
999
  glyphCount = 0;
1000
+ gl.clearColor(0, 0, 0, 0);
1001
+ gl.clear(gl.COLOR_BUFFER_BIT);
970
1002
  },
971
1003
  setTexture(source) {
1004
+ if (source === textureSource && texture) return;
972
1005
  if (!texture) {
973
1006
  texture = gl.createTexture();
974
1007
  gl.bindTexture(gl.TEXTURE_2D, texture);
@@ -980,6 +1013,7 @@ function createWebGLPointRenderer(canvas) {
980
1013
  gl.bindTexture(gl.TEXTURE_2D, texture);
981
1014
  }
982
1015
  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, source);
1016
+ textureSource = source;
983
1017
  },
984
1018
  addSprite(x, y, width, height, u0, v0, u1, v1, color = "#ffffff", alpha = 1, rotation = 0) {
985
1019
  if (!texture) return;
@@ -1016,6 +1050,11 @@ function createWebGLPointRenderer(canvas) {
1016
1050
  spriteCount++;
1017
1051
  },
1018
1052
  setMSDFTexture(source, range) {
1053
+ if (source === msdfSource && msdfTexture) {
1054
+ distanceRange = range;
1055
+ return;
1056
+ }
1057
+ drawGlyphs();
1019
1058
  distanceRange = range;
1020
1059
  if (!msdfTexture) {
1021
1060
  msdfTexture = gl.createTexture();
@@ -1028,6 +1067,7 @@ function createWebGLPointRenderer(canvas) {
1028
1067
  gl.bindTexture(gl.TEXTURE_2D, msdfTexture);
1029
1068
  }
1030
1069
  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, source);
1070
+ msdfSource = source;
1031
1071
  },
1032
1072
  addGlyph(x, y, width, height, u0, v0, u1, v1, color = "#ffffff", alpha = 1, rotation = 0) {
1033
1073
  if (!msdfTexture) return;
@@ -1105,8 +1145,6 @@ function createWebGLPointRenderer(canvas) {
1105
1145
  rectCount++;
1106
1146
  },
1107
1147
  flush() {
1108
- gl.clearColor(0, 0, 0, 0);
1109
- gl.clear(gl.COLOR_BUFFER_BIT);
1110
1148
  if (rectCount > 0) {
1111
1149
  const floats = rectCount * VERTS_PER_RECT * FLOATS_PER_RECT_VERT;
1112
1150
  gl.useProgram(rectProgram);
@@ -1141,20 +1179,8 @@ function createWebGLPointRenderer(canvas) {
1141
1179
  gl.uniform2f(sURes, logicalW, logicalH);
1142
1180
  gl.drawArrays(gl.TRIANGLES, 0, spriteCount * VERTS_PER_SPRITE);
1143
1181
  }
1144
- if (glyphCount > 0 && msdfTexture) {
1145
- const floats = glyphCount * VERTS_PER_SPRITE * FLOATS_PER_SPRITE_VERT;
1146
- gl.useProgram(msdfProgram);
1147
- gl.bindVertexArray(glyphVAO);
1148
- gl.bindBuffer(gl.ARRAY_BUFFER, glyphBuffer);
1149
- gl.bufferData(gl.ARRAY_BUFFER, glyphData.subarray(0, floats), gl.DYNAMIC_DRAW);
1150
- gl.activeTexture(gl.TEXTURE0);
1151
- gl.bindTexture(gl.TEXTURE_2D, msdfTexture);
1152
- gl.uniform1i(gUTex, 0);
1153
- gl.uniform2f(gURes, logicalW, logicalH);
1154
- gl.uniform1f(gURange, distanceRange);
1155
- gl.drawArrays(gl.TRIANGLES, 0, glyphCount * VERTS_PER_SPRITE);
1156
- }
1157
1182
  gl.bindVertexArray(null);
1183
+ drawGlyphs();
1158
1184
  },
1159
1185
  destroy() {
1160
1186
  if (destroyed) return;