@canvas-harness/core 0.1.3 → 0.1.5

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.cjs CHANGED
@@ -857,23 +857,12 @@ var darkenHex = (hex) => {
857
857
 
858
858
  // src/render/shapes/path-helpers.ts
859
859
  var buildRectPath = (ctx, w, h, radius) => {
860
+ ctx.beginPath();
860
861
  if (radius <= 0) {
861
- ctx.beginPath();
862
862
  ctx.rect(0, 0, w, h);
863
863
  return;
864
864
  }
865
- const r = Math.min(radius, w / 2, h / 2);
866
- ctx.beginPath();
867
- ctx.moveTo(r, 0);
868
- ctx.lineTo(w - r, 0);
869
- ctx.quadraticCurveTo(w, 0, w, r);
870
- ctx.lineTo(w, h - r);
871
- ctx.quadraticCurveTo(w, h, w - r, h);
872
- ctx.lineTo(r, h);
873
- ctx.quadraticCurveTo(0, h, 0, h - r);
874
- ctx.lineTo(0, r);
875
- ctx.quadraticCurveTo(0, 0, r, 0);
876
- ctx.closePath();
865
+ ctx.roundRect(0, 0, w, h, radius);
877
866
  };
878
867
  var buildEllipsePath = (ctx, w, h) => {
879
868
  const rx = w / 2;
@@ -4255,13 +4244,21 @@ var storeToJSON = (store) => ({
4255
4244
  });
4256
4245
 
4257
4246
  // src/render/canvas-setup.ts
4258
- var MAX_DPR = 3;
4259
- var getDpr = () => {
4247
+ var HARD_MAX_DPR = 3;
4248
+ var defaultMaxDprForSize = (cssW, cssH) => {
4249
+ const cssPx = cssW * cssH;
4250
+ if (cssPx >= 25e5) return 1;
4251
+ if (cssPx >= 15e5) return 1.5;
4252
+ return 2;
4253
+ };
4254
+ var getDpr = (maxDpr, cssW = 0, cssH = 0) => {
4260
4255
  if (typeof window === "undefined") return 1;
4261
4256
  const raw = window.devicePixelRatio || 1;
4262
- return Math.max(1, Math.min(MAX_DPR, raw));
4257
+ const resolvedMax = maxDpr === void 0 && cssW > 0 && cssH > 0 ? defaultMaxDprForSize(cssW, cssH) : maxDpr ?? 1;
4258
+ const cap = Math.max(1, Math.min(HARD_MAX_DPR, resolvedMax));
4259
+ return Math.max(1, Math.min(cap, raw));
4263
4260
  };
4264
- var setupSurface = (canvas) => {
4261
+ var setupSurface = (canvas, _maxDpr) => {
4265
4262
  const ctx = canvas.getContext("2d");
4266
4263
  if (!ctx) throw new Error("Canvas 2d context unavailable");
4267
4264
  return {
@@ -4269,11 +4266,12 @@ var setupSurface = (canvas) => {
4269
4266
  ctx,
4270
4267
  cssWidth: 0,
4271
4268
  cssHeight: 0,
4272
- dpr: getDpr()
4269
+ dpr: 1
4270
+ // placeholder; `sizeSurface` writes the real value
4273
4271
  };
4274
4272
  };
4275
- var sizeSurface = (surface, cssW, cssH) => {
4276
- const dpr = getDpr();
4273
+ var sizeSurface = (surface, cssW, cssH, maxDpr) => {
4274
+ const dpr = getDpr(maxDpr, cssW, cssH);
4277
4275
  if (surface.cssWidth === cssW && surface.cssHeight === cssH && surface.dpr === dpr) {
4278
4276
  return false;
4279
4277
  }
@@ -4888,22 +4886,47 @@ var drawWithNodeTransform = (ctx, node, fn) => {
4888
4886
  };
4889
4887
 
4890
4888
  // src/render/renderer.ts
4891
- var VIEWPORT_OVERSCAN_PX = 64;
4889
+ var SCENE_CACHE_MARGIN_PX = 256;
4892
4890
  var MIN_ON_SCREEN_SIZE_PX = 1.5;
4893
4891
  var MIN_READABLE_FONT_PX = 3;
4894
4892
  var createRenderer = (opts) => {
4895
4893
  const { store, theme, onOverlayChange } = opts;
4894
+ const maxDpr = opts.maxDpr;
4896
4895
  const staticSurface = setupSurface(opts.staticCanvas);
4897
4896
  const interactiveSurface = setupSurface(opts.interactiveCanvas);
4898
4897
  let background = opts.background;
4899
4898
  let selectionColor = opts.selectionColor ?? DEFAULT_SELECTION_COLOR;
4900
4899
  let hideFrames = false;
4901
- sizeSurface(staticSurface, opts.width, opts.height);
4902
- sizeSurface(interactiveSurface, opts.width, opts.height);
4900
+ sizeSurface(staticSurface, opts.width, opts.height, maxDpr);
4901
+ sizeSurface(interactiveSurface, opts.width, opts.height, maxDpr);
4903
4902
  let staticDirty = true;
4904
4903
  let interactiveDirty = false;
4905
4904
  let overlaySet = /* @__PURE__ */ new Set();
4906
4905
  let lastDrawn = 0;
4906
+ let cacheSurface = null;
4907
+ let cacheCamX = 0;
4908
+ let cacheCamY = 0;
4909
+ let cacheCamZ = 1;
4910
+ let cacheStale = true;
4911
+ const ensureCacheSurface = () => {
4912
+ const dpr = staticSurface.dpr;
4913
+ const cssW = staticSurface.cssWidth + 2 * SCENE_CACHE_MARGIN_PX;
4914
+ const cssH = staticSurface.cssHeight + 2 * SCENE_CACHE_MARGIN_PX;
4915
+ if (!cacheSurface) {
4916
+ const canvas = document.createElement("canvas");
4917
+ const ctx = canvas.getContext("2d");
4918
+ if (!ctx) throw new Error("Canvas 2d context unavailable");
4919
+ cacheSurface = { canvas, ctx, cssWidth: 0, cssHeight: 0, dpr: 1 };
4920
+ }
4921
+ if (cacheSurface.cssWidth !== cssW || cacheSurface.cssHeight !== cssH || cacheSurface.dpr !== dpr) {
4922
+ cacheSurface.cssWidth = cssW;
4923
+ cacheSurface.cssHeight = cssH;
4924
+ cacheSurface.dpr = dpr;
4925
+ cacheSurface.canvas.width = Math.max(1, Math.round(cssW * dpr));
4926
+ cacheSurface.canvas.height = Math.max(1, Math.round(cssH * dpr));
4927
+ }
4928
+ return cacheSurface;
4929
+ };
4907
4930
  let sortedNodeIdsCache = null;
4908
4931
  let sortedEdgeIdsCache = null;
4909
4932
  const invalidateSortedCaches = () => {
@@ -4912,6 +4935,7 @@ var createRenderer = (opts) => {
4912
4935
  };
4913
4936
  const requestRepaint = () => {
4914
4937
  staticDirty = true;
4938
+ cacheStale = true;
4915
4939
  loop.requestFrame();
4916
4940
  };
4917
4941
  const assetCache = createAssetCache({ onReady: requestRepaint });
@@ -4926,16 +4950,12 @@ var createRenderer = (opts) => {
4926
4950
  interactiveDirty = false;
4927
4951
  }
4928
4952
  };
4929
- const paintStatic = () => {
4930
- const camera = store.getCamera();
4931
- clearSurface(staticSurface);
4932
- applyCameraTransform(staticSurface, camera);
4933
- const scale = camera.z * staticSurface.dpr;
4953
+ const paintSceneBody = (surface, camera, viewport, fullRender = true) => {
4954
+ const scale = camera.z * surface.dpr;
4934
4955
  const interaction = store.getInteractionState();
4935
4956
  const excludedNodes = interaction.mode === "dragging" || interaction.mode === "resizing" ? new Set(interaction.draggedIds) : null;
4936
4957
  const excludedEdges = excludedNodes ? incidentEdgeIds(excludedNodes) : null;
4937
- const viewport = inflateRect(worldViewport(staticSurface, camera), VIEWPORT_OVERSCAN_PX);
4938
- paintBackground(staticSurface.ctx, { viewport, zoom: camera.z, background });
4958
+ paintBackground(surface.ctx, { viewport, zoom: camera.z, background });
4939
4959
  const visible = visibleNodes(camera, viewport);
4940
4960
  const isMoving2 = interaction.mode === "panning" || interaction.mode === "zooming" || interaction.mode === "dragging" || interaction.mode === "resizing" || interaction.mode === "rotating";
4941
4961
  const minOnScreen = MIN_ON_SCREEN_SIZE_PX;
@@ -4957,8 +4977,8 @@ var createRenderer = (opts) => {
4957
4977
  for (const node of visible) {
4958
4978
  if (node.type !== "frame") continue;
4959
4979
  if (excludedNodes?.has(node.id)) continue;
4960
- drawWithNodeTransform(staticSurface.ctx, node, () => {
4961
- paintFrameNode(staticSurface.ctx, node, scale, theme);
4980
+ drawWithNodeTransform(surface.ctx, node, () => {
4981
+ paintFrameNode(surface.ctx, node, scale, theme);
4962
4982
  });
4963
4983
  drawn++;
4964
4984
  }
@@ -4971,52 +4991,53 @@ var createRenderer = (opts) => {
4971
4991
  const useRough = roughEnabled && (node.style?.roughness ?? 0) > 0;
4972
4992
  const roughReady = useRough ? getRoughCanvasCtor() !== null : false;
4973
4993
  const composite = isCompositePrimitive(node.type);
4974
- drawWithNodeTransform(staticSurface.ctx, node, () => {
4994
+ drawWithNodeTransform(surface.ctx, node, () => {
4975
4995
  if (useRough && roughReady) {
4976
4996
  if (composite) {
4977
- drawCompositeRough(staticSurface.ctx, node, camera.z, theme);
4997
+ drawCompositeRough(surface.ctx, node, camera.z, theme);
4978
4998
  } else {
4979
- staticSurface.ctx.translate(ROUGH_FILL_MISREGISTER_X, ROUGH_FILL_MISREGISTER_Y);
4980
- drawShape(staticSurface.ctx, node, scale, theme, { skipStroke: true });
4981
- staticSurface.ctx.translate(-ROUGH_FILL_MISREGISTER_X, -ROUGH_FILL_MISREGISTER_Y);
4982
- drawRoughShape(staticSurface.ctx, node, camera.z, theme);
4999
+ surface.ctx.translate(ROUGH_FILL_MISREGISTER_X, ROUGH_FILL_MISREGISTER_Y);
5000
+ drawShape(surface.ctx, node, scale, theme, { skipStroke: true });
5001
+ surface.ctx.translate(-ROUGH_FILL_MISREGISTER_X, -ROUGH_FILL_MISREGISTER_Y);
5002
+ drawRoughShape(surface.ctx, node, camera.z, theme);
4983
5003
  }
4984
5004
  } else {
4985
- drawShape(staticSurface.ctx, node, scale, theme);
5005
+ drawShape(surface.ctx, node, scale, theme);
4986
5006
  if (useRough && !roughReady) {
4987
5007
  onRoughReady(() => {
4988
5008
  staticDirty = true;
5009
+ cacheStale = true;
4989
5010
  loop.requestFrame();
4990
5011
  });
4991
5012
  }
4992
5013
  }
4993
- if (!isEditingThis) paintNodeContent(staticSurface.ctx, node, renderEnv);
5014
+ if (!isEditingThis) paintNodeContent(surface.ctx, node, renderEnv);
4994
5015
  });
4995
5016
  drawn++;
4996
5017
  continue;
4997
5018
  }
4998
5019
  if (node.type === "image") {
4999
- drawWithNodeTransform(staticSurface.ctx, node, () => {
5000
- paintImageNode(staticSurface.ctx, node, assetCache, theme);
5020
+ drawWithNodeTransform(surface.ctx, node, () => {
5021
+ paintImageNode(surface.ctx, node, assetCache, theme);
5001
5022
  });
5002
5023
  drawn++;
5003
5024
  continue;
5004
5025
  }
5005
5026
  if (node.type === "icon") {
5006
- drawWithNodeTransform(staticSurface.ctx, node, () => {
5007
- paintIconNode(staticSurface.ctx, node, assetCache, scale, theme);
5027
+ drawWithNodeTransform(surface.ctx, node, () => {
5028
+ paintIconNode(surface.ctx, node, assetCache, scale, theme);
5008
5029
  });
5009
5030
  drawn++;
5010
5031
  continue;
5011
5032
  }
5012
5033
  if (node.type === "text") {
5013
- drawWithNodeTransform(staticSurface.ctx, node, () => {
5034
+ drawWithNodeTransform(surface.ctx, node, () => {
5014
5035
  if (isEditingThis) return;
5015
5036
  const hasContent = node.content && node.content.trim().length > 0;
5016
5037
  if (hasContent) {
5017
- paintNodeContent(staticSurface.ctx, node, renderEnv);
5038
+ paintNodeContent(surface.ctx, node, renderEnv);
5018
5039
  } else {
5019
- paintEmptyTextPlaceholder(staticSurface.ctx, node, camera.z);
5040
+ paintEmptyTextPlaceholder(surface.ctx, node, camera.z);
5020
5041
  }
5021
5042
  });
5022
5043
  drawn++;
@@ -5028,7 +5049,7 @@ var createRenderer = (opts) => {
5028
5049
  if (camera.z < def.lod.minZoomForPlaceholder) continue;
5029
5050
  const preferCanvas = camera.z < def.lod.minZoomForReact || isMoving2;
5030
5051
  if (preferCanvas) {
5031
- if (paintCustomCanvasFallback(staticSurface.ctx, node, def, scale, renderEnv)) {
5052
+ if (paintCustomCanvasFallback(surface.ctx, node, def, scale, renderEnv)) {
5032
5053
  drawn++;
5033
5054
  }
5034
5055
  continue;
@@ -5038,10 +5059,10 @@ var createRenderer = (opts) => {
5038
5059
  continue;
5039
5060
  }
5040
5061
  if (def.renderCanvas) {
5041
- drawWithNodeTransform(staticSurface.ctx, node, () => {
5042
- staticSurface.ctx.save();
5043
- def.renderCanvas(staticSurface.ctx, node, renderEnv);
5044
- staticSurface.ctx.restore();
5062
+ drawWithNodeTransform(surface.ctx, node, () => {
5063
+ surface.ctx.save();
5064
+ def.renderCanvas(surface.ctx, node, renderEnv);
5065
+ surface.ctx.restore();
5045
5066
  });
5046
5067
  drawn++;
5047
5068
  }
@@ -5050,15 +5071,120 @@ var createRenderer = (opts) => {
5050
5071
  const edgeRoughEnabled = !cameraIsMoving && movingNodeCount <= ROUGH_MAX_MOVING_NODES && camera.z >= ROUGH_MIN_ZOOM && visEdges.length <= ROUGH_MAX_NODES;
5051
5072
  for (const edge of visEdges) {
5052
5073
  if (excludedEdges?.has(edge.id)) continue;
5053
- paintOneEdge(staticSurface.ctx, edge, scale, edgeRoughEnabled, camera.z, isMoving2);
5074
+ paintOneEdge(surface.ctx, edge, scale, edgeRoughEnabled, camera.z, isMoving2);
5054
5075
  drawn++;
5055
5076
  }
5077
+ if (!fullRender) return;
5056
5078
  lastDrawn = drawn;
5057
5079
  if (!setsEqual(nextOverlaySet, overlaySet)) {
5058
5080
  overlaySet = nextOverlaySet;
5059
5081
  onOverlayChange?.([...overlaySet]);
5060
5082
  }
5061
5083
  };
5084
+ const applyCacheTransform = (cache5, centerX, centerY, z) => {
5085
+ const s = z * cache5.dpr;
5086
+ const m = SCENE_CACHE_MARGIN_PX * cache5.dpr;
5087
+ cache5.ctx.setTransform(s, 0, 0, s, -centerX * s + m, -centerY * s + m);
5088
+ };
5089
+ const renderFullCache = (camera) => {
5090
+ const cache5 = ensureCacheSurface();
5091
+ clearSurface(cache5);
5092
+ applyCacheTransform(cache5, camera.x, camera.y, camera.z);
5093
+ const marginWorld = SCENE_CACHE_MARGIN_PX / camera.z;
5094
+ const viewport = inflateRect(worldViewport(staticSurface, camera), marginWorld);
5095
+ paintSceneBody(cache5, camera, viewport);
5096
+ cacheCamX = camera.x;
5097
+ cacheCamY = camera.y;
5098
+ cacheCamZ = camera.z;
5099
+ cacheStale = false;
5100
+ };
5101
+ const canExtend = (camera) => {
5102
+ if (!cacheSurface) return false;
5103
+ const s = camera.z * staticSurface.dpr;
5104
+ const dx = Math.abs((cacheCamX - camera.x) * s);
5105
+ const dy = Math.abs((cacheCamY - camera.y) * s);
5106
+ return dx < cacheSurface.canvas.width && dy < cacheSurface.canvas.height;
5107
+ };
5108
+ const renderCacheStrip = (cache5, centerX, centerY, z, px, py, pw, ph) => {
5109
+ const ctx = cache5.ctx;
5110
+ const s = z * cache5.dpr;
5111
+ const m = SCENE_CACHE_MARGIN_PX * cache5.dpr;
5112
+ ctx.save();
5113
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
5114
+ ctx.beginPath();
5115
+ ctx.rect(px, py, pw, ph);
5116
+ ctx.clip();
5117
+ ctx.clearRect(px, py, pw, ph);
5118
+ applyCacheTransform(cache5, centerX, centerY, z);
5119
+ const viewport = {
5120
+ x: (px - m) / s + centerX,
5121
+ y: (py - m) / s + centerY,
5122
+ w: pw / s,
5123
+ h: ph / s
5124
+ };
5125
+ paintSceneBody(cache5, { z }, viewport, false);
5126
+ ctx.restore();
5127
+ };
5128
+ const extendCache = (camera) => {
5129
+ const cache5 = ensureCacheSurface();
5130
+ const s = camera.z * cache5.dpr;
5131
+ const cacheW = cache5.canvas.width;
5132
+ const cacheH = cache5.canvas.height;
5133
+ const dx = Math.round((cacheCamX - camera.x) * s);
5134
+ const dy = Math.round((cacheCamY - camera.y) * s);
5135
+ const newCamX = cacheCamX - dx / s;
5136
+ const newCamY = cacheCamY - dy / s;
5137
+ cache5.ctx.setTransform(1, 0, 0, 1, 0, 0);
5138
+ cache5.ctx.drawImage(cache5.canvas, 0, 0, cacheW, cacheH, dx, dy, cacheW, cacheH);
5139
+ cacheCamX = newCamX;
5140
+ cacheCamY = newCamY;
5141
+ cacheCamZ = camera.z;
5142
+ const hw = Math.abs(dx);
5143
+ const vh = Math.abs(dy);
5144
+ const hx = dx > 0 ? 0 : cacheW - hw;
5145
+ const vy = dy > 0 ? 0 : cacheH - vh;
5146
+ const vx = dx > 0 ? hw : 0;
5147
+ const vw = cacheW - hw;
5148
+ if (hw > 0) renderCacheStrip(cache5, newCamX, newCamY, camera.z, hx, 0, hw, cacheH);
5149
+ if (vh > 0 && vw > 0) renderCacheStrip(cache5, newCamX, newCamY, camera.z, vx, vy, vw, vh);
5150
+ };
5151
+ const cacheSourceOffset = (camera) => {
5152
+ const dpr = staticSurface.dpr;
5153
+ return {
5154
+ x: Math.round(((camera.x - cacheCamX) * cacheCamZ + SCENE_CACHE_MARGIN_PX) * dpr),
5155
+ y: Math.round(((camera.y - cacheCamY) * cacheCamZ + SCENE_CACHE_MARGIN_PX) * dpr)
5156
+ };
5157
+ };
5158
+ const viewportFitsInCache = (camera) => {
5159
+ if (!cacheSurface) return false;
5160
+ const { x, y } = cacheSourceOffset(camera);
5161
+ return x >= 0 && y >= 0 && x + staticSurface.canvas.width <= cacheSurface.canvas.width && y + staticSurface.canvas.height <= cacheSurface.canvas.height;
5162
+ };
5163
+ const presentStatic = (camera) => {
5164
+ const cache5 = ensureCacheSurface();
5165
+ const w = staticSurface.canvas.width;
5166
+ const h = staticSurface.canvas.height;
5167
+ const { x: srcX, y: srcY } = cacheSourceOffset(camera);
5168
+ staticSurface.ctx.setTransform(1, 0, 0, 1, 0, 0);
5169
+ staticSurface.ctx.clearRect(0, 0, w, h);
5170
+ staticSurface.ctx.drawImage(cache5.canvas, srcX, srcY, w, h, 0, 0, w, h);
5171
+ };
5172
+ const paintStatic = () => {
5173
+ const camera = store.getCamera();
5174
+ if (!cacheStale && camera.z === cacheCamZ) {
5175
+ if (viewportFitsInCache(camera)) {
5176
+ presentStatic(camera);
5177
+ return;
5178
+ }
5179
+ if (canExtend(camera)) {
5180
+ extendCache(camera);
5181
+ presentStatic(camera);
5182
+ return;
5183
+ }
5184
+ }
5185
+ renderFullCache(camera);
5186
+ presentStatic(camera);
5187
+ };
5062
5188
  const paintCustomCanvasFallback = (ctx, node, def, drawScale, env) => {
5063
5189
  if (def.getSnapshot) {
5064
5190
  const snap = def.getSnapshot(node, {
@@ -5343,6 +5469,7 @@ var createRenderer = (opts) => {
5343
5469
  const onStoreChange = () => {
5344
5470
  invalidateSortedCaches();
5345
5471
  staticDirty = true;
5472
+ cacheStale = true;
5346
5473
  interactiveDirty = true;
5347
5474
  loop.requestFrame();
5348
5475
  };
@@ -5359,6 +5486,7 @@ var createRenderer = (opts) => {
5359
5486
  interactiveDirty = true;
5360
5487
  if (state.mode === "dragging" || state.mode === "resizing" || state.mode === "rotating" || state.mode === "panning" || state.mode === "zooming" || state.mode === "idle") {
5361
5488
  staticDirty = true;
5489
+ cacheStale = true;
5362
5490
  }
5363
5491
  loop.requestFrame();
5364
5492
  };
@@ -5368,16 +5496,19 @@ var createRenderer = (opts) => {
5368
5496
  const unsubInteraction = store.subscribe("interaction", onInteractionChange);
5369
5497
  const unsubFontEpoch = subscribeFontEpoch(() => {
5370
5498
  staticDirty = true;
5499
+ cacheStale = true;
5371
5500
  loop.requestFrame();
5372
5501
  });
5373
5502
  const unsubMathEpoch = subscribeMathEpoch(() => {
5374
5503
  staticDirty = true;
5504
+ cacheStale = true;
5375
5505
  loop.requestFrame();
5376
5506
  });
5377
5507
  return {
5378
5508
  start() {
5379
5509
  loop.start();
5380
5510
  staticDirty = true;
5511
+ cacheStale = true;
5381
5512
  interactiveDirty = isInteractive(store.getInteractionState());
5382
5513
  loop.requestFrame();
5383
5514
  },
@@ -5386,14 +5517,16 @@ var createRenderer = (opts) => {
5386
5517
  },
5387
5518
  invalidate() {
5388
5519
  staticDirty = true;
5520
+ cacheStale = true;
5389
5521
  interactiveDirty = true;
5390
5522
  loop.requestFrame();
5391
5523
  },
5392
5524
  setSize(cssW, cssH) {
5393
- const a = sizeSurface(staticSurface, cssW, cssH);
5394
- const b = sizeSurface(interactiveSurface, cssW, cssH);
5525
+ const a = sizeSurface(staticSurface, cssW, cssH, maxDpr);
5526
+ const b = sizeSurface(interactiveSurface, cssW, cssH, maxDpr);
5395
5527
  if (a || b) {
5396
5528
  staticDirty = true;
5529
+ cacheStale = true;
5397
5530
  interactiveDirty = true;
5398
5531
  loop.requestFrame();
5399
5532
  }
@@ -5401,6 +5534,7 @@ var createRenderer = (opts) => {
5401
5534
  setBackground(bg) {
5402
5535
  background = bg;
5403
5536
  staticDirty = true;
5537
+ cacheStale = true;
5404
5538
  loop.requestFrame();
5405
5539
  },
5406
5540
  setSelectionColor(color) {
@@ -5411,6 +5545,7 @@ var createRenderer = (opts) => {
5411
5545
  setHideFrames(hidden) {
5412
5546
  hideFrames = hidden;
5413
5547
  staticDirty = true;
5548
+ cacheStale = true;
5414
5549
  loop.requestFrame();
5415
5550
  },
5416
5551
  stats: () => loop.stats(),
@@ -5425,6 +5560,11 @@ var createRenderer = (opts) => {
5425
5560
  unsubFontEpoch();
5426
5561
  unsubMathEpoch();
5427
5562
  assetCache.dispose();
5563
+ if (cacheSurface) {
5564
+ cacheSurface.canvas.width = 0;
5565
+ cacheSurface.canvas.height = 0;
5566
+ cacheSurface = null;
5567
+ }
5428
5568
  }
5429
5569
  };
5430
5570
  };