@vectojs/core 1.38.0 → 1.39.0

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.
@@ -109,17 +109,28 @@ var CanvasRenderer = (_class = class _CanvasRenderer {
109
109
  __init7() {this.batchCount = 0}
110
110
  __init8() {this._cachedFont = ""}
111
111
  __init9() {this._cachedFill = ""}
112
+ // Stroke-side counterparts of the fill/font caches, read by stroke()'s
113
+ // style-elision branch. Reset wherever the context state can change behind
114
+ // our back: restore(), resize(), contextrestored, dispose().
115
+ __init10() {this._cachedStroke = ""}
116
+ __init11() {this._cachedLineWidth = -1}
117
+ __init12() {this._cachedLineCap = ""}
118
+ __init13() {this._cachedLineJoin = ""}
119
+ // Context-loss listeners, held for removal in dispose() so a canvas that
120
+ // outlives the renderer cannot retain it through these closures.
121
+
122
+
112
123
  /** Backend discriminator; see {@link IRenderer.kind}. */
113
- __init10() {this.kind = "canvas2d"}
124
+ __init14() {this.kind = "canvas2d"}
114
125
  /**
115
126
  * Draw counters, allocated only once counting is enabled.
116
127
  *
117
128
  * Null when off, so the guard on every op is a single null test and an inactive
118
129
  * renderer carries no counter object at all.
119
130
  */
120
- __init11() {this.counters = null}
131
+ __init15() {this.counters = null}
121
132
  /** Accumulated primitive area, kept separately so the ratio is derived on read. */
122
- __init12() {this.drawnArea = 0}
133
+ __init16() {this.drawnArea = 0}
123
134
  /**
124
135
  * @param canvas - The target canvas. Its backing store is resized to the
125
136
  * logical size × devicePixelRatio.
@@ -129,7 +140,7 @@ var CanvasRenderer = (_class = class _CanvasRenderer {
129
140
  * set) so the canvas's own dimensions aren't clobbered by the window's.
130
141
  * @param maxDPR - See {@link maxDPR}.
131
142
  */
132
- constructor(canvas, size, maxDPR) {;_class.prototype.__init.call(this);_class.prototype.__init2.call(this);_class.prototype.__init3.call(this);_class.prototype.__init4.call(this);_class.prototype.__init5.call(this);_class.prototype.__init6.call(this);_class.prototype.__init7.call(this);_class.prototype.__init8.call(this);_class.prototype.__init9.call(this);_class.prototype.__init10.call(this);_class.prototype.__init11.call(this);_class.prototype.__init12.call(this);
143
+ constructor(canvas, size, maxDPR) {;_class.prototype.__init.call(this);_class.prototype.__init2.call(this);_class.prototype.__init3.call(this);_class.prototype.__init4.call(this);_class.prototype.__init5.call(this);_class.prototype.__init6.call(this);_class.prototype.__init7.call(this);_class.prototype.__init8.call(this);_class.prototype.__init9.call(this);_class.prototype.__init10.call(this);_class.prototype.__init11.call(this);_class.prototype.__init12.call(this);_class.prototype.__init13.call(this);_class.prototype.__init14.call(this);_class.prototype.__init15.call(this);_class.prototype.__init16.call(this);
133
144
  this.maxDPR = maxDPR;
134
145
  const dpr = this.effectiveDPR();
135
146
  this.appliedDPR = dpr;
@@ -162,11 +173,11 @@ var CanvasRenderer = (_class = class _CanvasRenderer {
162
173
  */
163
174
  setupContextLossRecovery() {
164
175
  if (typeof this.canvas.addEventListener !== "function") return;
165
- this.canvas.addEventListener("contextlost", (e) => {
176
+ this._onContextLost = (e) => {
166
177
  e.preventDefault();
167
178
  this.contextLost = true;
168
- });
169
- this.canvas.addEventListener("contextrestored", () => {
179
+ };
180
+ this._onContextRestored = () => {
170
181
  const ctx = this.canvas.getContext("2d");
171
182
  if (!ctx) return;
172
183
  this.ctx = ctx;
@@ -176,11 +187,17 @@ var CanvasRenderer = (_class = class _CanvasRenderer {
176
187
  ctx.scale(restoredDPR, restoredDPR);
177
188
  this._cachedFont = "";
178
189
  this._cachedFill = "";
190
+ this._cachedStroke = "";
191
+ this._cachedLineWidth = -1;
192
+ this._cachedLineCap = "";
193
+ this._cachedLineJoin = "";
179
194
  this.batchActive = false;
180
195
  this.batchCount = 0;
181
196
  this.contextLost = false;
182
197
  _optionalChain([this, 'access', _8 => _8.contextRestoredCb, 'optionalCall', _9 => _9()]);
183
- });
198
+ };
199
+ this.canvas.addEventListener("contextlost", this._onContextLost);
200
+ this.canvas.addEventListener("contextrestored", this._onContextRestored);
184
201
  }
185
202
  /**
186
203
  * Expose the underlying `CanvasRenderingContext2D` for operations not
@@ -230,11 +247,17 @@ var CanvasRenderer = (_class = class _CanvasRenderer {
230
247
  this.height = height;
231
248
  this.ctx.canvas.width = width * dpr;
232
249
  this.ctx.canvas.height = height * dpr;
233
- this.ctx.canvas.style.width = `${width}px`;
234
- this.ctx.canvas.style.height = `${height}px`;
250
+ if (this.ctx.canvas.style) {
251
+ this.ctx.canvas.style.width = `${width}px`;
252
+ this.ctx.canvas.style.height = `${height}px`;
253
+ }
235
254
  this.ctx.scale(dpr, dpr);
236
255
  this._cachedFont = "";
237
256
  this._cachedFill = "";
257
+ this._cachedStroke = "";
258
+ this._cachedLineWidth = -1;
259
+ this._cachedLineCap = "";
260
+ this._cachedLineJoin = "";
238
261
  this.batchActive = false;
239
262
  this.batchCount = 0;
240
263
  }
@@ -288,6 +311,10 @@ var CanvasRenderer = (_class = class _CanvasRenderer {
288
311
  this.ctx.restore();
289
312
  this._cachedFont = "";
290
313
  this._cachedFill = "";
314
+ this._cachedStroke = "";
315
+ this._cachedLineWidth = -1;
316
+ this._cachedLineCap = "";
317
+ this._cachedLineJoin = "";
291
318
  }
292
319
  /** @inheritdoc */
293
320
  translate(x, y) {
@@ -412,10 +439,17 @@ var CanvasRenderer = (_class = class _CanvasRenderer {
412
439
  stroke(color, lineWidth = 1) {
413
440
  this.flush();
414
441
  if (this.counters) this.counters.strokes++;
415
- this.ctx.strokeStyle = color;
416
- this.ctx.lineWidth = lineWidth;
417
- this.ctx.lineCap = "round";
418
- this.ctx.lineJoin = "round";
442
+ if (this._cachedStroke !== color || this._cachedLineWidth !== lineWidth || this._cachedLineCap !== "round" || this._cachedLineJoin !== "round") {
443
+ if (this.counters) this.counters.stateSwitches++;
444
+ this.ctx.strokeStyle = color;
445
+ this.ctx.lineWidth = lineWidth;
446
+ this.ctx.lineCap = "round";
447
+ this.ctx.lineJoin = "round";
448
+ this._cachedStroke = color;
449
+ this._cachedLineWidth = lineWidth;
450
+ this._cachedLineCap = "round";
451
+ this._cachedLineJoin = "round";
452
+ }
419
453
  this.ctx.stroke();
420
454
  }
421
455
  /** @inheritdoc */
@@ -454,6 +488,17 @@ var CanvasRenderer = (_class = class _CanvasRenderer {
454
488
  this.batchActive = false;
455
489
  this._cachedFont = "";
456
490
  this._cachedFill = "";
491
+ this._cachedStroke = "";
492
+ this._cachedLineWidth = -1;
493
+ this._cachedLineCap = "";
494
+ this._cachedLineJoin = "";
495
+ if (typeof this.canvas.removeEventListener === "function") {
496
+ if (this._onContextLost) this.canvas.removeEventListener("contextlost", this._onContextLost);
497
+ if (this._onContextRestored)
498
+ this.canvas.removeEventListener("contextrestored", this._onContextRestored);
499
+ }
500
+ this._onContextLost = void 0;
501
+ this._onContextRestored = void 0;
457
502
  }
458
503
  }, _class.__initStatic(), _class);
459
504
 
@@ -476,9 +521,13 @@ function hasSafeSchemeOrIsRelative(url) {
476
521
  return SAFE_SCHEMES.has(`${candidate.toLowerCase()}:`);
477
522
  }
478
523
  var CHAR_REF = /&(?:#\d+|#x[\da-f]+|colon|tab|newline);/i;
524
+ var MAX_CODE_POINT = 1114111;
525
+ function decodeCodePoint(value) {
526
+ return value <= MAX_CODE_POINT ? String.fromCodePoint(value) : "\uFFFD";
527
+ }
479
528
  function decodeCharacterReferences(value) {
480
529
  if (value.indexOf("&") < 0 || !CHAR_REF.test(value)) return value;
481
- return value.replace(/&#x([\da-f]+);/gi, (_, hex) => String.fromCodePoint(parseInt(hex, 16))).replace(/&#(\d+);/g, (_, dec) => String.fromCodePoint(parseInt(dec, 10))).replace(/&colon;/gi, ":").replace(/&tab;/gi, " ").replace(/&newline;/gi, "\n");
530
+ return value.replace(/&#x([\da-f]+);/gi, (_, hex) => decodeCodePoint(parseInt(hex, 16))).replace(/&#(\d+);/g, (_, dec) => decodeCodePoint(parseInt(dec, 10))).replace(/&colon;/gi, ":").replace(/&tab;/gi, " ").replace(/&newline;/gi, "\n");
482
531
  }
483
532
  function sanitizeUrl(href) {
484
533
  if (typeof href !== "string") return "";
@@ -545,44 +594,47 @@ function fontFamilyFromShorthand(font, sizeToken) {
545
594
  }
546
595
  var SVGRenderer = (_class2 = class {
547
596
  /** Backend discriminator; see {@link IRenderer.kind}. */
548
- __init13() {this.kind = "svg"}
597
+ __init17() {this.kind = "svg"}
549
598
 
550
599
 
551
- __init14() {this.buffer = []}
552
- __init15() {this.defsBuffer = []}
553
- __init16() {this.currentPath = []}
600
+ __init18() {this.buffer = []}
601
+ __init19() {this.defsBuffer = []}
602
+ __init20() {this.currentPath = []}
554
603
  // Inline matrix states (3x3 representation)
555
604
  // [a, b, c, d, e, f] corresponding to:
556
605
  // | a c e |
557
606
  // | b d f |
558
607
  // | 0 0 1 |
559
- __init17() {this.mStack = []}
560
- __init18() {this.ma = 1}
561
- __init19() {this.mb = 0}
562
- __init20() {this.mc = 0}
563
- __init21() {this.md = 1}
564
- __init22() {this.me = 0}
565
- __init23() {this.mf = 0}
608
+ __init21() {this.mStack = []}
609
+ __init22() {this.ma = 1}
610
+ __init23() {this.mb = 0}
611
+ __init24() {this.mc = 0}
612
+ __init25() {this.md = 1}
613
+ __init26() {this.me = 0}
614
+ __init27() {this.mf = 0}
566
615
  // Alpha stack
567
- __init24() {this.alphaStack = []}
568
- __init25() {this.globalAlpha = 1}
616
+ __init28() {this.alphaStack = []}
617
+ __init29() {this.globalAlpha = 1}
569
618
  // Clipping stack
570
- __init26() {this.clipDepthStack = []}
571
- __init27() {this.clipDepth = 0}
572
- __init28() {this.clipCounter = 0}
619
+ __init30() {this.clipDepthStack = []}
620
+ __init31() {this.clipDepth = 0}
621
+ __init32() {this.clipCounter = 0}
573
622
  // uniquely identifies clipPaths
574
623
  // Batch circles states (local coordinates)
575
- __init29() {this.batchCircles = []}
576
- __init30() {this.batchMatrix = [1, 0, 0, 1, 0, 0]}
577
- __init31() {this.batchColor = ""}
578
- __init32() {this.batchAlpha = 1}
579
- __init33() {this.batchActive = false}
624
+ __init33() {this.batchCircles = []}
625
+ __init34() {this.batchMatrix = [1, 0, 0, 1, 0, 0]}
626
+ __init35() {this.batchColor = ""}
627
+ __init36() {this.batchAlpha = 1}
628
+ __init37() {this.batchActive = false}
580
629
  // Cache for generated gradient defs
581
- __init34() {this.gradientCounter = 0}
582
- __init35() {this.gradientCache = /* @__PURE__ */ new Map()}
583
- constructor(width, height) {;_class2.prototype.__init13.call(this);_class2.prototype.__init14.call(this);_class2.prototype.__init15.call(this);_class2.prototype.__init16.call(this);_class2.prototype.__init17.call(this);_class2.prototype.__init18.call(this);_class2.prototype.__init19.call(this);_class2.prototype.__init20.call(this);_class2.prototype.__init21.call(this);_class2.prototype.__init22.call(this);_class2.prototype.__init23.call(this);_class2.prototype.__init24.call(this);_class2.prototype.__init25.call(this);_class2.prototype.__init26.call(this);_class2.prototype.__init27.call(this);_class2.prototype.__init28.call(this);_class2.prototype.__init29.call(this);_class2.prototype.__init30.call(this);_class2.prototype.__init31.call(this);_class2.prototype.__init32.call(this);_class2.prototype.__init33.call(this);_class2.prototype.__init34.call(this);_class2.prototype.__init35.call(this);
630
+ __init38() {this.gradientCounter = 0}
631
+ __init39() {this.gradientCache = /* @__PURE__ */ new Map()}
632
+ /** Root font size used to resolve `em`/`rem` font sizes (default 16px). */
633
+
634
+ constructor(width, height, options) {;_class2.prototype.__init17.call(this);_class2.prototype.__init18.call(this);_class2.prototype.__init19.call(this);_class2.prototype.__init20.call(this);_class2.prototype.__init21.call(this);_class2.prototype.__init22.call(this);_class2.prototype.__init23.call(this);_class2.prototype.__init24.call(this);_class2.prototype.__init25.call(this);_class2.prototype.__init26.call(this);_class2.prototype.__init27.call(this);_class2.prototype.__init28.call(this);_class2.prototype.__init29.call(this);_class2.prototype.__init30.call(this);_class2.prototype.__init31.call(this);_class2.prototype.__init32.call(this);_class2.prototype.__init33.call(this);_class2.prototype.__init34.call(this);_class2.prototype.__init35.call(this);_class2.prototype.__init36.call(this);_class2.prototype.__init37.call(this);_class2.prototype.__init38.call(this);_class2.prototype.__init39.call(this);
584
635
  this.width = width;
585
636
  this.height = height;
637
+ this.rootFontSize = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _10 => _10.rootFontSize]), () => ( 16));
586
638
  installRendererDevTraps(this, "SVGRenderer");
587
639
  }
588
640
  clear() {
@@ -770,12 +822,22 @@ var SVGRenderer = (_class2 = class {
770
822
  `<path d="${dStr}" transform="${transformStr}" fill="none" stroke="${strokeVal}" stroke-width="${lineWidth}" stroke-opacity="${this.globalAlpha}" />`
771
823
  );
772
824
  }
825
+ /**
826
+ * Emit a `<text>` element for a text run.
827
+ *
828
+ * Font-size unit handling: `px` sizes pass through; `em` and `rem` are both
829
+ * scaled against the renderer's `rootFontSize` (constructor option, default
830
+ * 16 — matching the browser default, not necessarily the host page's root,
831
+ * so pass `{ rootFontSize }` when exporting against a non-default root).
832
+ * A percentage size (`'100% Inter'`) has no absolute meaning in this
833
+ * context and silently falls back to the default 16px font size.
834
+ */
773
835
  fillText(text, x, y, font, color) {
774
836
  this.flush();
775
837
  const sizeToken = parseFontSizeToken(font);
776
838
  let fontSize = sizeToken ? sizeToken.value : 16;
777
- if (sizeToken && sizeToken.unit !== "px") {
778
- fontSize = fontSize * 16;
839
+ if (sizeToken && (sizeToken.unit === "em" || sizeToken.unit === "rem")) {
840
+ fontSize = fontSize * this.rootFontSize;
779
841
  }
780
842
  const lowerFont = font.toLowerCase();
781
843
  const fontStyle = lowerFont.includes("italic") ? "italic" : lowerFont.includes("oblique") ? "oblique" : "normal";
@@ -805,8 +867,8 @@ var SVGRenderer = (_class2 = class {
805
867
  }
806
868
  drawImage(source, dx, dy, dw, dh) {
807
869
  this.flush();
808
- const fromCanvas2 = typeof _optionalChain([source, 'optionalAccess', _10 => _10.toDataURL]) === "function";
809
- const rawHref = fromCanvas2 ? source.toDataURL() : _optionalChain([source, 'optionalAccess', _11 => _11.src]) || "";
870
+ const fromCanvas2 = typeof _optionalChain([source, 'optionalAccess', _11 => _11.toDataURL]) === "function";
871
+ const rawHref = fromCanvas2 ? source.toDataURL() : _optionalChain([source, 'optionalAccess', _12 => _12.src]) || "";
810
872
  const href = fromCanvas2 && this.isSafeRasterDataUrl(rawHref) ? rawHref : sanitizeUrl(String(rawHref));
811
873
  const transformStr = `matrix(${this.ma},${this.mb},${this.mc},${this.md},${this.me},${this.mf})`;
812
874
  if (href) {
@@ -957,11 +1019,11 @@ var SVGRenderer = (_class2 = class {
957
1019
  }
958
1020
  /**
959
1021
  * SVGRenderer accumulates strings in memory; nothing external is allocated.
960
- * Drop the buffers for GC and become idempotent.
1022
+ * Drop the buffers for GC and become idempotent. `clear()` already empties
1023
+ * the gradient cache, so no second clear is needed here.
961
1024
  */
962
1025
  dispose() {
963
1026
  this.clear();
964
- this.gradientCache.clear();
965
1027
  }
966
1028
  }, _class2);
967
1029
 
@@ -1333,13 +1395,22 @@ function createWebGLPointRenderer(canvas) {
1333
1395
  premultipliedAlpha: false
1334
1396
  });
1335
1397
  if (!gl) return null;
1336
- const pointProgram = link(gl, POINT_VERT, POINT_FRAG);
1337
- const rectProgram = link(gl, RECT_VERT, RECT_FRAG);
1338
- const spriteProgram = link(gl, SPRITE_VERT, SPRITE_FRAG);
1339
- const msdfProgram = link(gl, SPRITE_VERT, MSDF_FRAG);
1340
- const circleQuadProgram = link(gl, SPRITE_VERT, CIRCLE_QUAD_FRAG);
1341
- if (!pointProgram || !rectProgram || !spriteProgram || !msdfProgram || !circleQuadProgram)
1398
+ const programs = [];
1399
+ const linkTracked = (vsSrc, fsSrc) => {
1400
+ const program = link(gl, vsSrc, fsSrc);
1401
+ if (program) programs.push(program);
1402
+ return program;
1403
+ };
1404
+ const pointProgram = linkTracked(POINT_VERT, POINT_FRAG);
1405
+ const rectProgram = linkTracked(RECT_VERT, RECT_FRAG);
1406
+ const spriteProgram = linkTracked(SPRITE_VERT, SPRITE_FRAG);
1407
+ const msdfProgram = linkTracked(SPRITE_VERT, MSDF_FRAG);
1408
+ const circleQuadProgram = linkTracked(SPRITE_VERT, CIRCLE_QUAD_FRAG);
1409
+ if (!pointProgram || !rectProgram || !spriteProgram || !msdfProgram || !circleQuadProgram) {
1410
+ for (const program of programs) gl.deleteProgram(program);
1411
+ _optionalChain([gl, 'access', _13 => _13.getExtension, 'call', _14 => _14("WEBGL_lose_context"), 'optionalAccess', _15 => _15.loseContext, 'call', _16 => _16()]);
1342
1412
  return null;
1413
+ }
1343
1414
  gl.enable(gl.BLEND);
1344
1415
  gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
1345
1416
  const pAPos = gl.getAttribLocation(pointProgram, "a_pos");
@@ -1519,6 +1590,7 @@ function createWebGLPointRenderer(canvas) {
1519
1590
  ensureQuadIndices(spriteCount);
1520
1591
  gl.drawElements(gl.TRIANGLES, spriteCount * INDICES_PER_QUAD, gl.UNSIGNED_INT, 0);
1521
1592
  frameDrawCalls++;
1593
+ gl.bindVertexArray(null);
1522
1594
  spriteCount = 0;
1523
1595
  };
1524
1596
  const drawGlyphs = () => {
@@ -1797,6 +1869,7 @@ function createWebGLPointRenderer(canvas) {
1797
1869
  }
1798
1870
 
1799
1871
  // src/renderer/WebGPUParticleSystemManager.ts
1872
+ var recordComputePassScratch = new Float32Array(20);
1800
1873
  var COMPUTE_SHADER = `
1801
1874
  struct Particle {
1802
1875
  position: vec2<f32>,
@@ -1971,16 +2044,22 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
1971
2044
  `;
1972
2045
  var WebGPUParticleSystemManager = (_class3 = class {
1973
2046
 
1974
- __init36() {this.computePipeline = null}
1975
- __init37() {this.renderPipeline = null}
1976
- __init38() {this.computeBindGroupLayout = null}
1977
- __init39() {this.renderBindGroupLayout = null}
1978
- constructor(device) {;_class3.prototype.__init36.call(this);_class3.prototype.__init37.call(this);_class3.prototype.__init38.call(this);_class3.prototype.__init39.call(this);
2047
+ __init40() {this.computePipeline = null}
2048
+ __init41() {this.renderPipeline = null}
2049
+ // Held so destroy() can release them — modules were previously unreachable
2050
+ // after initPipelines and lived until context loss (#684).
2051
+ __init42() {this.computeModule = null}
2052
+ __init43() {this.renderModule = null}
2053
+ __init44() {this.computeBindGroupLayout = null}
2054
+ __init45() {this.renderBindGroupLayout = null}
2055
+ constructor(device) {;_class3.prototype.__init40.call(this);_class3.prototype.__init41.call(this);_class3.prototype.__init42.call(this);_class3.prototype.__init43.call(this);_class3.prototype.__init44.call(this);_class3.prototype.__init45.call(this);
1979
2056
  this.device = device;
1980
2057
  }
1981
2058
  initPipelines(format) {
1982
- const computeModule = this.device.createShaderModule({ code: COMPUTE_SHADER });
1983
- const renderModule = this.device.createShaderModule({ code: RENDER_SHADER });
2059
+ this.computeModule = this.device.createShaderModule({ code: COMPUTE_SHADER });
2060
+ this.renderModule = this.device.createShaderModule({ code: RENDER_SHADER });
2061
+ const computeModule = this.computeModule;
2062
+ const renderModule = this.renderModule;
1984
2063
  this.computeBindGroupLayout = this.device.createBindGroupLayout({
1985
2064
  entries: [
1986
2065
  {
@@ -2039,6 +2118,8 @@ var WebGPUParticleSystemManager = (_class3 = class {
2039
2118
  });
2040
2119
  }
2041
2120
  setupEntityResources(entity) {
2121
+ releaseGpuObject(entity.gpuStorageBuffer);
2122
+ releaseGpuObject(entity.gpuUniformBuffer);
2042
2123
  const storageSize = entity.maxParticles * 32;
2043
2124
  entity.gpuStorageBuffer = this.device.createBuffer({
2044
2125
  size: storageSize,
@@ -2065,7 +2146,7 @@ var WebGPUParticleSystemManager = (_class3 = class {
2065
2146
  }
2066
2147
  recordComputePass(pass, entity, dt, mouseX, mouseY, width, height) {
2067
2148
  if (!this.computePipeline || !entity.computeBindGroup) return;
2068
- const uniformArray = new Float32Array(20);
2149
+ const uniformArray = recordComputePassScratch;
2069
2150
  const color = parseColorToRGBA(entity.baseColor);
2070
2151
  let opacity = 1;
2071
2152
  for (let current = entity; current; current = current.parent) {
@@ -2109,34 +2190,45 @@ var WebGPUParticleSystemManager = (_class3 = class {
2109
2190
  pass.draw(6, entity.maxParticles);
2110
2191
  }
2111
2192
  destroy() {
2193
+ releaseGpuObject(this.computePipeline);
2194
+ releaseGpuObject(this.renderPipeline);
2195
+ releaseGpuObject(this.computeModule);
2196
+ releaseGpuObject(this.renderModule);
2112
2197
  this.computePipeline = null;
2113
2198
  this.renderPipeline = null;
2199
+ this.computeModule = null;
2200
+ this.renderModule = null;
2114
2201
  this.computeBindGroupLayout = null;
2115
2202
  this.renderBindGroupLayout = null;
2116
2203
  }
2117
2204
  }, _class3);
2205
+ function releaseGpuObject(obj) {
2206
+ if (obj && typeof obj.destroy === "function") {
2207
+ obj.destroy();
2208
+ }
2209
+ }
2118
2210
 
2119
2211
  // src/renderer/GlyphRasterAtlas.ts
2120
2212
  var HARD_MAX_SIZE = 8192;
2121
2213
  var PAD = 2;
2122
2214
  var GlyphRasterAtlas = (_class4 = class {
2123
- __init40() {this.slots = /* @__PURE__ */ new Map()}
2215
+ __init46() {this.slots = /* @__PURE__ */ new Map()}
2124
2216
 
2125
2217
 
2126
- __init41() {this.canvas = null}
2127
- __init42() {this.ctx = null}
2218
+ __init47() {this.canvas = null}
2219
+ __init48() {this.ctx = null}
2128
2220
  /**
2129
2221
  * Shelf packing: glyphs land left-to-right on a row, then a new row starts.
2130
2222
  * A monospace grid produces near-uniform widths, so shelves waste very little
2131
2223
  * and cost one comparison per insert — a real 2D packer would buy nothing here.
2132
2224
  */
2133
- __init43() {this.penX = PAD}
2134
- __init44() {this.penY = PAD}
2135
- __init45() {this.rowHeight = 0}
2136
- __init46() {this._hits = 0}
2137
- __init47() {this._misses = 0}
2138
- __init48() {this._resets = 0}
2139
- constructor(options = {}) {;_class4.prototype.__init40.call(this);_class4.prototype.__init41.call(this);_class4.prototype.__init42.call(this);_class4.prototype.__init43.call(this);_class4.prototype.__init44.call(this);_class4.prototype.__init45.call(this);_class4.prototype.__init46.call(this);_class4.prototype.__init47.call(this);_class4.prototype.__init48.call(this);
2225
+ __init49() {this.penX = PAD}
2226
+ __init50() {this.penY = PAD}
2227
+ __init51() {this.rowHeight = 0}
2228
+ __init52() {this._hits = 0}
2229
+ __init53() {this._misses = 0}
2230
+ __init54() {this._resets = 0}
2231
+ constructor(options = {}) {;_class4.prototype.__init46.call(this);_class4.prototype.__init47.call(this);_class4.prototype.__init48.call(this);_class4.prototype.__init49.call(this);_class4.prototype.__init50.call(this);_class4.prototype.__init51.call(this);_class4.prototype.__init52.call(this);_class4.prototype.__init53.call(this);_class4.prototype.__init54.call(this);
2140
2232
  this.dpr = Math.max(1, _nullishCoalesce(options.dpr, () => ( 1)));
2141
2233
  this.maxSize = Math.min(HARD_MAX_SIZE, Math.max(256, _nullishCoalesce(options.maxSize, () => ( 2048))));
2142
2234
  }
@@ -2195,7 +2287,7 @@ var GlyphRasterAtlas = (_class4 = class {
2195
2287
  * (headless, unrasterizable, or too large to pack).
2196
2288
  */
2197
2289
  get(font, color, glyph) {
2198
- const key = font + "\0" + color + "\0" + glyph;
2290
+ const key = `${font.length}\0${font}${color.length}\0${color}${glyph.length}\0${glyph}`;
2199
2291
  const existing = this.slots.get(key);
2200
2292
  if (existing !== void 0) {
2201
2293
  if (existing) this._hits++;
@@ -2318,13 +2410,13 @@ var GlyphRasterAtlas = (_class4 = class {
2318
2410
 
2319
2411
  // src/renderer/TextRasterCache.ts
2320
2412
  var TextRasterCache = (_class5 = class {
2321
- __init49() {this.cache = /* @__PURE__ */ new Map()}
2413
+ __init55() {this.cache = /* @__PURE__ */ new Map()}
2322
2414
 
2323
2415
 
2324
- __init50() {this.scratchCtx = null}
2325
- __init51() {this._hits = 0}
2326
- __init52() {this._misses = 0}
2327
- constructor(options = {}) {;_class5.prototype.__init49.call(this);_class5.prototype.__init50.call(this);_class5.prototype.__init51.call(this);_class5.prototype.__init52.call(this);
2416
+ __init56() {this.scratchCtx = null}
2417
+ __init57() {this._hits = 0}
2418
+ __init58() {this._misses = 0}
2419
+ constructor(options = {}) {;_class5.prototype.__init55.call(this);_class5.prototype.__init56.call(this);_class5.prototype.__init57.call(this);_class5.prototype.__init58.call(this);
2328
2420
  this.maxEntries = Math.max(1, _nullishCoalesce(options.maxEntries, () => ( 4096)));
2329
2421
  this.dpr = Math.max(1, _nullishCoalesce(options.dpr, () => ( 1)));
2330
2422
  }
@@ -2349,7 +2441,7 @@ var TextRasterCache = (_class5 = class {
2349
2441
  * fall back to {@link IRenderer.fillText}).
2350
2442
  */
2351
2443
  get(font, color, text) {
2352
- const key = font + "\0" + color + "\0" + text;
2444
+ const key = `${font.length}\0${font}${color.length}\0${color}${text.length}\0${text}`;
2353
2445
  const hit = this.cache.get(key);
2354
2446
  if (hit) {
2355
2447
  this._hits++;