@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 +195 -55
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +21 -3
- package/dist/index.d.ts +21 -3
- package/dist/index.js +195 -55
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -2097,7 +2097,13 @@ type CanvasSurface = {
|
|
|
2097
2097
|
/** Device pixels — backing-store size. */
|
|
2098
2098
|
dpr: number;
|
|
2099
2099
|
};
|
|
2100
|
-
|
|
2100
|
+
/**
|
|
2101
|
+
* Resolved DPR for the canvas backing store. Clamped by `maxDpr`
|
|
2102
|
+
* (consumer-supplied) and the absolute `HARD_MAX_DPR` ceiling. When
|
|
2103
|
+
* `maxDpr` is omitted, the tier-based default (see
|
|
2104
|
+
* `defaultMaxDprForSize`) is used — pass `cssW`/`cssH` to enable it.
|
|
2105
|
+
*/
|
|
2106
|
+
declare const getDpr: (maxDpr?: number, cssW?: number, cssH?: number) => number;
|
|
2101
2107
|
/**
|
|
2102
2108
|
* Builds a managed canvas surface. Caller pins the canvas element; we size
|
|
2103
2109
|
* it and reset the 2d context's transform to logical-pixel space.
|
|
@@ -2105,12 +2111,12 @@ declare const getDpr: () => number;
|
|
|
2105
2111
|
* Subsequent calls to `setSize` re-allocate the backing store if the new
|
|
2106
2112
|
* `cssW × cssH × DPR` differs from the current.
|
|
2107
2113
|
*/
|
|
2108
|
-
declare const setupSurface: (canvas: HTMLCanvasElement) => CanvasSurface;
|
|
2114
|
+
declare const setupSurface: (canvas: HTMLCanvasElement, _maxDpr?: number) => CanvasSurface;
|
|
2109
2115
|
/**
|
|
2110
2116
|
* Resizes the surface to a new CSS-pixel size, picking up the current DPR.
|
|
2111
2117
|
* Returns true if anything changed (caller should redraw).
|
|
2112
2118
|
*/
|
|
2113
|
-
declare const sizeSurface: (surface: CanvasSurface, cssW: number, cssH: number) => boolean;
|
|
2119
|
+
declare const sizeSurface: (surface: CanvasSurface, cssW: number, cssH: number, maxDpr?: number) => boolean;
|
|
2114
2120
|
/**
|
|
2115
2121
|
* Clears the entire backing store. Call before setting the camera transform.
|
|
2116
2122
|
*/
|
|
@@ -2202,6 +2208,18 @@ type RendererOptions = {
|
|
|
2202
2208
|
* fill tints via globalAlpha — no parsing needed.
|
|
2203
2209
|
*/
|
|
2204
2210
|
selectionColor?: string;
|
|
2211
|
+
/**
|
|
2212
|
+
* Cap on the canvas backing-store DPR (device-pixel ratio). At
|
|
2213
|
+
* native DPR on hi-DPI displays, the backing buffer can hit
|
|
2214
|
+
* 20-30 megapixels per frame; the GPU-upload step alone dominates
|
|
2215
|
+
* the frame budget. Defaults to `1` for consistent perf across
|
|
2216
|
+
* hardware. Bump to `2` (or `window.devicePixelRatio`) for
|
|
2217
|
+
* pixel-crisp rendering at the cost of FPS on hi-DPI displays.
|
|
2218
|
+
*
|
|
2219
|
+
* Text is unaffected — the text bitmap cache renders glyphs at
|
|
2220
|
+
* its own DPR-aware scale.
|
|
2221
|
+
*/
|
|
2222
|
+
maxDpr?: number;
|
|
2205
2223
|
/**
|
|
2206
2224
|
* Fires when the set of custom nodes that should be rendered in the DOM
|
|
2207
2225
|
* overlay changes. Consumers use this to mount/unmount React subtrees
|
package/dist/index.d.ts
CHANGED
|
@@ -2097,7 +2097,13 @@ type CanvasSurface = {
|
|
|
2097
2097
|
/** Device pixels — backing-store size. */
|
|
2098
2098
|
dpr: number;
|
|
2099
2099
|
};
|
|
2100
|
-
|
|
2100
|
+
/**
|
|
2101
|
+
* Resolved DPR for the canvas backing store. Clamped by `maxDpr`
|
|
2102
|
+
* (consumer-supplied) and the absolute `HARD_MAX_DPR` ceiling. When
|
|
2103
|
+
* `maxDpr` is omitted, the tier-based default (see
|
|
2104
|
+
* `defaultMaxDprForSize`) is used — pass `cssW`/`cssH` to enable it.
|
|
2105
|
+
*/
|
|
2106
|
+
declare const getDpr: (maxDpr?: number, cssW?: number, cssH?: number) => number;
|
|
2101
2107
|
/**
|
|
2102
2108
|
* Builds a managed canvas surface. Caller pins the canvas element; we size
|
|
2103
2109
|
* it and reset the 2d context's transform to logical-pixel space.
|
|
@@ -2105,12 +2111,12 @@ declare const getDpr: () => number;
|
|
|
2105
2111
|
* Subsequent calls to `setSize` re-allocate the backing store if the new
|
|
2106
2112
|
* `cssW × cssH × DPR` differs from the current.
|
|
2107
2113
|
*/
|
|
2108
|
-
declare const setupSurface: (canvas: HTMLCanvasElement) => CanvasSurface;
|
|
2114
|
+
declare const setupSurface: (canvas: HTMLCanvasElement, _maxDpr?: number) => CanvasSurface;
|
|
2109
2115
|
/**
|
|
2110
2116
|
* Resizes the surface to a new CSS-pixel size, picking up the current DPR.
|
|
2111
2117
|
* Returns true if anything changed (caller should redraw).
|
|
2112
2118
|
*/
|
|
2113
|
-
declare const sizeSurface: (surface: CanvasSurface, cssW: number, cssH: number) => boolean;
|
|
2119
|
+
declare const sizeSurface: (surface: CanvasSurface, cssW: number, cssH: number, maxDpr?: number) => boolean;
|
|
2114
2120
|
/**
|
|
2115
2121
|
* Clears the entire backing store. Call before setting the camera transform.
|
|
2116
2122
|
*/
|
|
@@ -2202,6 +2208,18 @@ type RendererOptions = {
|
|
|
2202
2208
|
* fill tints via globalAlpha — no parsing needed.
|
|
2203
2209
|
*/
|
|
2204
2210
|
selectionColor?: string;
|
|
2211
|
+
/**
|
|
2212
|
+
* Cap on the canvas backing-store DPR (device-pixel ratio). At
|
|
2213
|
+
* native DPR on hi-DPI displays, the backing buffer can hit
|
|
2214
|
+
* 20-30 megapixels per frame; the GPU-upload step alone dominates
|
|
2215
|
+
* the frame budget. Defaults to `1` for consistent perf across
|
|
2216
|
+
* hardware. Bump to `2` (or `window.devicePixelRatio`) for
|
|
2217
|
+
* pixel-crisp rendering at the cost of FPS on hi-DPI displays.
|
|
2218
|
+
*
|
|
2219
|
+
* Text is unaffected — the text bitmap cache renders glyphs at
|
|
2220
|
+
* its own DPR-aware scale.
|
|
2221
|
+
*/
|
|
2222
|
+
maxDpr?: number;
|
|
2205
2223
|
/**
|
|
2206
2224
|
* Fires when the set of custom nodes that should be rendered in the DOM
|
|
2207
2225
|
* overlay changes. Consumers use this to mount/unmount React subtrees
|
package/dist/index.js
CHANGED
|
@@ -855,23 +855,12 @@ var darkenHex = (hex) => {
|
|
|
855
855
|
|
|
856
856
|
// src/render/shapes/path-helpers.ts
|
|
857
857
|
var buildRectPath = (ctx, w, h, radius) => {
|
|
858
|
+
ctx.beginPath();
|
|
858
859
|
if (radius <= 0) {
|
|
859
|
-
ctx.beginPath();
|
|
860
860
|
ctx.rect(0, 0, w, h);
|
|
861
861
|
return;
|
|
862
862
|
}
|
|
863
|
-
|
|
864
|
-
ctx.beginPath();
|
|
865
|
-
ctx.moveTo(r, 0);
|
|
866
|
-
ctx.lineTo(w - r, 0);
|
|
867
|
-
ctx.quadraticCurveTo(w, 0, w, r);
|
|
868
|
-
ctx.lineTo(w, h - r);
|
|
869
|
-
ctx.quadraticCurveTo(w, h, w - r, h);
|
|
870
|
-
ctx.lineTo(r, h);
|
|
871
|
-
ctx.quadraticCurveTo(0, h, 0, h - r);
|
|
872
|
-
ctx.lineTo(0, r);
|
|
873
|
-
ctx.quadraticCurveTo(0, 0, r, 0);
|
|
874
|
-
ctx.closePath();
|
|
863
|
+
ctx.roundRect(0, 0, w, h, radius);
|
|
875
864
|
};
|
|
876
865
|
var buildEllipsePath = (ctx, w, h) => {
|
|
877
866
|
const rx = w / 2;
|
|
@@ -4253,13 +4242,21 @@ var storeToJSON = (store) => ({
|
|
|
4253
4242
|
});
|
|
4254
4243
|
|
|
4255
4244
|
// src/render/canvas-setup.ts
|
|
4256
|
-
var
|
|
4257
|
-
var
|
|
4245
|
+
var HARD_MAX_DPR = 3;
|
|
4246
|
+
var defaultMaxDprForSize = (cssW, cssH) => {
|
|
4247
|
+
const cssPx = cssW * cssH;
|
|
4248
|
+
if (cssPx >= 25e5) return 1;
|
|
4249
|
+
if (cssPx >= 15e5) return 1.5;
|
|
4250
|
+
return 2;
|
|
4251
|
+
};
|
|
4252
|
+
var getDpr = (maxDpr, cssW = 0, cssH = 0) => {
|
|
4258
4253
|
if (typeof window === "undefined") return 1;
|
|
4259
4254
|
const raw = window.devicePixelRatio || 1;
|
|
4260
|
-
|
|
4255
|
+
const resolvedMax = maxDpr === void 0 && cssW > 0 && cssH > 0 ? defaultMaxDprForSize(cssW, cssH) : maxDpr ?? 1;
|
|
4256
|
+
const cap = Math.max(1, Math.min(HARD_MAX_DPR, resolvedMax));
|
|
4257
|
+
return Math.max(1, Math.min(cap, raw));
|
|
4261
4258
|
};
|
|
4262
|
-
var setupSurface = (canvas) => {
|
|
4259
|
+
var setupSurface = (canvas, _maxDpr) => {
|
|
4263
4260
|
const ctx = canvas.getContext("2d");
|
|
4264
4261
|
if (!ctx) throw new Error("Canvas 2d context unavailable");
|
|
4265
4262
|
return {
|
|
@@ -4267,11 +4264,12 @@ var setupSurface = (canvas) => {
|
|
|
4267
4264
|
ctx,
|
|
4268
4265
|
cssWidth: 0,
|
|
4269
4266
|
cssHeight: 0,
|
|
4270
|
-
dpr:
|
|
4267
|
+
dpr: 1
|
|
4268
|
+
// placeholder; `sizeSurface` writes the real value
|
|
4271
4269
|
};
|
|
4272
4270
|
};
|
|
4273
|
-
var sizeSurface = (surface, cssW, cssH) => {
|
|
4274
|
-
const dpr = getDpr();
|
|
4271
|
+
var sizeSurface = (surface, cssW, cssH, maxDpr) => {
|
|
4272
|
+
const dpr = getDpr(maxDpr, cssW, cssH);
|
|
4275
4273
|
if (surface.cssWidth === cssW && surface.cssHeight === cssH && surface.dpr === dpr) {
|
|
4276
4274
|
return false;
|
|
4277
4275
|
}
|
|
@@ -4886,22 +4884,47 @@ var drawWithNodeTransform = (ctx, node, fn) => {
|
|
|
4886
4884
|
};
|
|
4887
4885
|
|
|
4888
4886
|
// src/render/renderer.ts
|
|
4889
|
-
var
|
|
4887
|
+
var SCENE_CACHE_MARGIN_PX = 256;
|
|
4890
4888
|
var MIN_ON_SCREEN_SIZE_PX = 1.5;
|
|
4891
4889
|
var MIN_READABLE_FONT_PX = 3;
|
|
4892
4890
|
var createRenderer = (opts) => {
|
|
4893
4891
|
const { store, theme, onOverlayChange } = opts;
|
|
4892
|
+
const maxDpr = opts.maxDpr;
|
|
4894
4893
|
const staticSurface = setupSurface(opts.staticCanvas);
|
|
4895
4894
|
const interactiveSurface = setupSurface(opts.interactiveCanvas);
|
|
4896
4895
|
let background = opts.background;
|
|
4897
4896
|
let selectionColor = opts.selectionColor ?? DEFAULT_SELECTION_COLOR;
|
|
4898
4897
|
let hideFrames = false;
|
|
4899
|
-
sizeSurface(staticSurface, opts.width, opts.height);
|
|
4900
|
-
sizeSurface(interactiveSurface, opts.width, opts.height);
|
|
4898
|
+
sizeSurface(staticSurface, opts.width, opts.height, maxDpr);
|
|
4899
|
+
sizeSurface(interactiveSurface, opts.width, opts.height, maxDpr);
|
|
4901
4900
|
let staticDirty = true;
|
|
4902
4901
|
let interactiveDirty = false;
|
|
4903
4902
|
let overlaySet = /* @__PURE__ */ new Set();
|
|
4904
4903
|
let lastDrawn = 0;
|
|
4904
|
+
let cacheSurface = null;
|
|
4905
|
+
let cacheCamX = 0;
|
|
4906
|
+
let cacheCamY = 0;
|
|
4907
|
+
let cacheCamZ = 1;
|
|
4908
|
+
let cacheStale = true;
|
|
4909
|
+
const ensureCacheSurface = () => {
|
|
4910
|
+
const dpr = staticSurface.dpr;
|
|
4911
|
+
const cssW = staticSurface.cssWidth + 2 * SCENE_CACHE_MARGIN_PX;
|
|
4912
|
+
const cssH = staticSurface.cssHeight + 2 * SCENE_CACHE_MARGIN_PX;
|
|
4913
|
+
if (!cacheSurface) {
|
|
4914
|
+
const canvas = document.createElement("canvas");
|
|
4915
|
+
const ctx = canvas.getContext("2d");
|
|
4916
|
+
if (!ctx) throw new Error("Canvas 2d context unavailable");
|
|
4917
|
+
cacheSurface = { canvas, ctx, cssWidth: 0, cssHeight: 0, dpr: 1 };
|
|
4918
|
+
}
|
|
4919
|
+
if (cacheSurface.cssWidth !== cssW || cacheSurface.cssHeight !== cssH || cacheSurface.dpr !== dpr) {
|
|
4920
|
+
cacheSurface.cssWidth = cssW;
|
|
4921
|
+
cacheSurface.cssHeight = cssH;
|
|
4922
|
+
cacheSurface.dpr = dpr;
|
|
4923
|
+
cacheSurface.canvas.width = Math.max(1, Math.round(cssW * dpr));
|
|
4924
|
+
cacheSurface.canvas.height = Math.max(1, Math.round(cssH * dpr));
|
|
4925
|
+
}
|
|
4926
|
+
return cacheSurface;
|
|
4927
|
+
};
|
|
4905
4928
|
let sortedNodeIdsCache = null;
|
|
4906
4929
|
let sortedEdgeIdsCache = null;
|
|
4907
4930
|
const invalidateSortedCaches = () => {
|
|
@@ -4910,6 +4933,7 @@ var createRenderer = (opts) => {
|
|
|
4910
4933
|
};
|
|
4911
4934
|
const requestRepaint = () => {
|
|
4912
4935
|
staticDirty = true;
|
|
4936
|
+
cacheStale = true;
|
|
4913
4937
|
loop.requestFrame();
|
|
4914
4938
|
};
|
|
4915
4939
|
const assetCache = createAssetCache({ onReady: requestRepaint });
|
|
@@ -4924,16 +4948,12 @@ var createRenderer = (opts) => {
|
|
|
4924
4948
|
interactiveDirty = false;
|
|
4925
4949
|
}
|
|
4926
4950
|
};
|
|
4927
|
-
const
|
|
4928
|
-
const
|
|
4929
|
-
clearSurface(staticSurface);
|
|
4930
|
-
applyCameraTransform(staticSurface, camera);
|
|
4931
|
-
const scale = camera.z * staticSurface.dpr;
|
|
4951
|
+
const paintSceneBody = (surface, camera, viewport, fullRender = true) => {
|
|
4952
|
+
const scale = camera.z * surface.dpr;
|
|
4932
4953
|
const interaction = store.getInteractionState();
|
|
4933
4954
|
const excludedNodes = interaction.mode === "dragging" || interaction.mode === "resizing" ? new Set(interaction.draggedIds) : null;
|
|
4934
4955
|
const excludedEdges = excludedNodes ? incidentEdgeIds(excludedNodes) : null;
|
|
4935
|
-
|
|
4936
|
-
paintBackground(staticSurface.ctx, { viewport, zoom: camera.z, background });
|
|
4956
|
+
paintBackground(surface.ctx, { viewport, zoom: camera.z, background });
|
|
4937
4957
|
const visible = visibleNodes(camera, viewport);
|
|
4938
4958
|
const isMoving2 = interaction.mode === "panning" || interaction.mode === "zooming" || interaction.mode === "dragging" || interaction.mode === "resizing" || interaction.mode === "rotating";
|
|
4939
4959
|
const minOnScreen = MIN_ON_SCREEN_SIZE_PX;
|
|
@@ -4955,8 +4975,8 @@ var createRenderer = (opts) => {
|
|
|
4955
4975
|
for (const node of visible) {
|
|
4956
4976
|
if (node.type !== "frame") continue;
|
|
4957
4977
|
if (excludedNodes?.has(node.id)) continue;
|
|
4958
|
-
drawWithNodeTransform(
|
|
4959
|
-
paintFrameNode(
|
|
4978
|
+
drawWithNodeTransform(surface.ctx, node, () => {
|
|
4979
|
+
paintFrameNode(surface.ctx, node, scale, theme);
|
|
4960
4980
|
});
|
|
4961
4981
|
drawn++;
|
|
4962
4982
|
}
|
|
@@ -4969,52 +4989,53 @@ var createRenderer = (opts) => {
|
|
|
4969
4989
|
const useRough = roughEnabled && (node.style?.roughness ?? 0) > 0;
|
|
4970
4990
|
const roughReady = useRough ? getRoughCanvasCtor() !== null : false;
|
|
4971
4991
|
const composite = isCompositePrimitive(node.type);
|
|
4972
|
-
drawWithNodeTransform(
|
|
4992
|
+
drawWithNodeTransform(surface.ctx, node, () => {
|
|
4973
4993
|
if (useRough && roughReady) {
|
|
4974
4994
|
if (composite) {
|
|
4975
|
-
drawCompositeRough(
|
|
4995
|
+
drawCompositeRough(surface.ctx, node, camera.z, theme);
|
|
4976
4996
|
} else {
|
|
4977
|
-
|
|
4978
|
-
drawShape(
|
|
4979
|
-
|
|
4980
|
-
drawRoughShape(
|
|
4997
|
+
surface.ctx.translate(ROUGH_FILL_MISREGISTER_X, ROUGH_FILL_MISREGISTER_Y);
|
|
4998
|
+
drawShape(surface.ctx, node, scale, theme, { skipStroke: true });
|
|
4999
|
+
surface.ctx.translate(-ROUGH_FILL_MISREGISTER_X, -ROUGH_FILL_MISREGISTER_Y);
|
|
5000
|
+
drawRoughShape(surface.ctx, node, camera.z, theme);
|
|
4981
5001
|
}
|
|
4982
5002
|
} else {
|
|
4983
|
-
drawShape(
|
|
5003
|
+
drawShape(surface.ctx, node, scale, theme);
|
|
4984
5004
|
if (useRough && !roughReady) {
|
|
4985
5005
|
onRoughReady(() => {
|
|
4986
5006
|
staticDirty = true;
|
|
5007
|
+
cacheStale = true;
|
|
4987
5008
|
loop.requestFrame();
|
|
4988
5009
|
});
|
|
4989
5010
|
}
|
|
4990
5011
|
}
|
|
4991
|
-
if (!isEditingThis) paintNodeContent(
|
|
5012
|
+
if (!isEditingThis) paintNodeContent(surface.ctx, node, renderEnv);
|
|
4992
5013
|
});
|
|
4993
5014
|
drawn++;
|
|
4994
5015
|
continue;
|
|
4995
5016
|
}
|
|
4996
5017
|
if (node.type === "image") {
|
|
4997
|
-
drawWithNodeTransform(
|
|
4998
|
-
paintImageNode(
|
|
5018
|
+
drawWithNodeTransform(surface.ctx, node, () => {
|
|
5019
|
+
paintImageNode(surface.ctx, node, assetCache, theme);
|
|
4999
5020
|
});
|
|
5000
5021
|
drawn++;
|
|
5001
5022
|
continue;
|
|
5002
5023
|
}
|
|
5003
5024
|
if (node.type === "icon") {
|
|
5004
|
-
drawWithNodeTransform(
|
|
5005
|
-
paintIconNode(
|
|
5025
|
+
drawWithNodeTransform(surface.ctx, node, () => {
|
|
5026
|
+
paintIconNode(surface.ctx, node, assetCache, scale, theme);
|
|
5006
5027
|
});
|
|
5007
5028
|
drawn++;
|
|
5008
5029
|
continue;
|
|
5009
5030
|
}
|
|
5010
5031
|
if (node.type === "text") {
|
|
5011
|
-
drawWithNodeTransform(
|
|
5032
|
+
drawWithNodeTransform(surface.ctx, node, () => {
|
|
5012
5033
|
if (isEditingThis) return;
|
|
5013
5034
|
const hasContent = node.content && node.content.trim().length > 0;
|
|
5014
5035
|
if (hasContent) {
|
|
5015
|
-
paintNodeContent(
|
|
5036
|
+
paintNodeContent(surface.ctx, node, renderEnv);
|
|
5016
5037
|
} else {
|
|
5017
|
-
paintEmptyTextPlaceholder(
|
|
5038
|
+
paintEmptyTextPlaceholder(surface.ctx, node, camera.z);
|
|
5018
5039
|
}
|
|
5019
5040
|
});
|
|
5020
5041
|
drawn++;
|
|
@@ -5026,7 +5047,7 @@ var createRenderer = (opts) => {
|
|
|
5026
5047
|
if (camera.z < def.lod.minZoomForPlaceholder) continue;
|
|
5027
5048
|
const preferCanvas = camera.z < def.lod.minZoomForReact || isMoving2;
|
|
5028
5049
|
if (preferCanvas) {
|
|
5029
|
-
if (paintCustomCanvasFallback(
|
|
5050
|
+
if (paintCustomCanvasFallback(surface.ctx, node, def, scale, renderEnv)) {
|
|
5030
5051
|
drawn++;
|
|
5031
5052
|
}
|
|
5032
5053
|
continue;
|
|
@@ -5036,10 +5057,10 @@ var createRenderer = (opts) => {
|
|
|
5036
5057
|
continue;
|
|
5037
5058
|
}
|
|
5038
5059
|
if (def.renderCanvas) {
|
|
5039
|
-
drawWithNodeTransform(
|
|
5040
|
-
|
|
5041
|
-
def.renderCanvas(
|
|
5042
|
-
|
|
5060
|
+
drawWithNodeTransform(surface.ctx, node, () => {
|
|
5061
|
+
surface.ctx.save();
|
|
5062
|
+
def.renderCanvas(surface.ctx, node, renderEnv);
|
|
5063
|
+
surface.ctx.restore();
|
|
5043
5064
|
});
|
|
5044
5065
|
drawn++;
|
|
5045
5066
|
}
|
|
@@ -5048,15 +5069,120 @@ var createRenderer = (opts) => {
|
|
|
5048
5069
|
const edgeRoughEnabled = !cameraIsMoving && movingNodeCount <= ROUGH_MAX_MOVING_NODES && camera.z >= ROUGH_MIN_ZOOM && visEdges.length <= ROUGH_MAX_NODES;
|
|
5049
5070
|
for (const edge of visEdges) {
|
|
5050
5071
|
if (excludedEdges?.has(edge.id)) continue;
|
|
5051
|
-
paintOneEdge(
|
|
5072
|
+
paintOneEdge(surface.ctx, edge, scale, edgeRoughEnabled, camera.z, isMoving2);
|
|
5052
5073
|
drawn++;
|
|
5053
5074
|
}
|
|
5075
|
+
if (!fullRender) return;
|
|
5054
5076
|
lastDrawn = drawn;
|
|
5055
5077
|
if (!setsEqual(nextOverlaySet, overlaySet)) {
|
|
5056
5078
|
overlaySet = nextOverlaySet;
|
|
5057
5079
|
onOverlayChange?.([...overlaySet]);
|
|
5058
5080
|
}
|
|
5059
5081
|
};
|
|
5082
|
+
const applyCacheTransform = (cache5, centerX, centerY, z) => {
|
|
5083
|
+
const s = z * cache5.dpr;
|
|
5084
|
+
const m = SCENE_CACHE_MARGIN_PX * cache5.dpr;
|
|
5085
|
+
cache5.ctx.setTransform(s, 0, 0, s, -centerX * s + m, -centerY * s + m);
|
|
5086
|
+
};
|
|
5087
|
+
const renderFullCache = (camera) => {
|
|
5088
|
+
const cache5 = ensureCacheSurface();
|
|
5089
|
+
clearSurface(cache5);
|
|
5090
|
+
applyCacheTransform(cache5, camera.x, camera.y, camera.z);
|
|
5091
|
+
const marginWorld = SCENE_CACHE_MARGIN_PX / camera.z;
|
|
5092
|
+
const viewport = inflateRect(worldViewport(staticSurface, camera), marginWorld);
|
|
5093
|
+
paintSceneBody(cache5, camera, viewport);
|
|
5094
|
+
cacheCamX = camera.x;
|
|
5095
|
+
cacheCamY = camera.y;
|
|
5096
|
+
cacheCamZ = camera.z;
|
|
5097
|
+
cacheStale = false;
|
|
5098
|
+
};
|
|
5099
|
+
const canExtend = (camera) => {
|
|
5100
|
+
if (!cacheSurface) return false;
|
|
5101
|
+
const s = camera.z * staticSurface.dpr;
|
|
5102
|
+
const dx = Math.abs((cacheCamX - camera.x) * s);
|
|
5103
|
+
const dy = Math.abs((cacheCamY - camera.y) * s);
|
|
5104
|
+
return dx < cacheSurface.canvas.width && dy < cacheSurface.canvas.height;
|
|
5105
|
+
};
|
|
5106
|
+
const renderCacheStrip = (cache5, centerX, centerY, z, px, py, pw, ph) => {
|
|
5107
|
+
const ctx = cache5.ctx;
|
|
5108
|
+
const s = z * cache5.dpr;
|
|
5109
|
+
const m = SCENE_CACHE_MARGIN_PX * cache5.dpr;
|
|
5110
|
+
ctx.save();
|
|
5111
|
+
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
5112
|
+
ctx.beginPath();
|
|
5113
|
+
ctx.rect(px, py, pw, ph);
|
|
5114
|
+
ctx.clip();
|
|
5115
|
+
ctx.clearRect(px, py, pw, ph);
|
|
5116
|
+
applyCacheTransform(cache5, centerX, centerY, z);
|
|
5117
|
+
const viewport = {
|
|
5118
|
+
x: (px - m) / s + centerX,
|
|
5119
|
+
y: (py - m) / s + centerY,
|
|
5120
|
+
w: pw / s,
|
|
5121
|
+
h: ph / s
|
|
5122
|
+
};
|
|
5123
|
+
paintSceneBody(cache5, { z }, viewport, false);
|
|
5124
|
+
ctx.restore();
|
|
5125
|
+
};
|
|
5126
|
+
const extendCache = (camera) => {
|
|
5127
|
+
const cache5 = ensureCacheSurface();
|
|
5128
|
+
const s = camera.z * cache5.dpr;
|
|
5129
|
+
const cacheW = cache5.canvas.width;
|
|
5130
|
+
const cacheH = cache5.canvas.height;
|
|
5131
|
+
const dx = Math.round((cacheCamX - camera.x) * s);
|
|
5132
|
+
const dy = Math.round((cacheCamY - camera.y) * s);
|
|
5133
|
+
const newCamX = cacheCamX - dx / s;
|
|
5134
|
+
const newCamY = cacheCamY - dy / s;
|
|
5135
|
+
cache5.ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
5136
|
+
cache5.ctx.drawImage(cache5.canvas, 0, 0, cacheW, cacheH, dx, dy, cacheW, cacheH);
|
|
5137
|
+
cacheCamX = newCamX;
|
|
5138
|
+
cacheCamY = newCamY;
|
|
5139
|
+
cacheCamZ = camera.z;
|
|
5140
|
+
const hw = Math.abs(dx);
|
|
5141
|
+
const vh = Math.abs(dy);
|
|
5142
|
+
const hx = dx > 0 ? 0 : cacheW - hw;
|
|
5143
|
+
const vy = dy > 0 ? 0 : cacheH - vh;
|
|
5144
|
+
const vx = dx > 0 ? hw : 0;
|
|
5145
|
+
const vw = cacheW - hw;
|
|
5146
|
+
if (hw > 0) renderCacheStrip(cache5, newCamX, newCamY, camera.z, hx, 0, hw, cacheH);
|
|
5147
|
+
if (vh > 0 && vw > 0) renderCacheStrip(cache5, newCamX, newCamY, camera.z, vx, vy, vw, vh);
|
|
5148
|
+
};
|
|
5149
|
+
const cacheSourceOffset = (camera) => {
|
|
5150
|
+
const dpr = staticSurface.dpr;
|
|
5151
|
+
return {
|
|
5152
|
+
x: Math.round(((camera.x - cacheCamX) * cacheCamZ + SCENE_CACHE_MARGIN_PX) * dpr),
|
|
5153
|
+
y: Math.round(((camera.y - cacheCamY) * cacheCamZ + SCENE_CACHE_MARGIN_PX) * dpr)
|
|
5154
|
+
};
|
|
5155
|
+
};
|
|
5156
|
+
const viewportFitsInCache = (camera) => {
|
|
5157
|
+
if (!cacheSurface) return false;
|
|
5158
|
+
const { x, y } = cacheSourceOffset(camera);
|
|
5159
|
+
return x >= 0 && y >= 0 && x + staticSurface.canvas.width <= cacheSurface.canvas.width && y + staticSurface.canvas.height <= cacheSurface.canvas.height;
|
|
5160
|
+
};
|
|
5161
|
+
const presentStatic = (camera) => {
|
|
5162
|
+
const cache5 = ensureCacheSurface();
|
|
5163
|
+
const w = staticSurface.canvas.width;
|
|
5164
|
+
const h = staticSurface.canvas.height;
|
|
5165
|
+
const { x: srcX, y: srcY } = cacheSourceOffset(camera);
|
|
5166
|
+
staticSurface.ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
5167
|
+
staticSurface.ctx.clearRect(0, 0, w, h);
|
|
5168
|
+
staticSurface.ctx.drawImage(cache5.canvas, srcX, srcY, w, h, 0, 0, w, h);
|
|
5169
|
+
};
|
|
5170
|
+
const paintStatic = () => {
|
|
5171
|
+
const camera = store.getCamera();
|
|
5172
|
+
if (!cacheStale && camera.z === cacheCamZ) {
|
|
5173
|
+
if (viewportFitsInCache(camera)) {
|
|
5174
|
+
presentStatic(camera);
|
|
5175
|
+
return;
|
|
5176
|
+
}
|
|
5177
|
+
if (canExtend(camera)) {
|
|
5178
|
+
extendCache(camera);
|
|
5179
|
+
presentStatic(camera);
|
|
5180
|
+
return;
|
|
5181
|
+
}
|
|
5182
|
+
}
|
|
5183
|
+
renderFullCache(camera);
|
|
5184
|
+
presentStatic(camera);
|
|
5185
|
+
};
|
|
5060
5186
|
const paintCustomCanvasFallback = (ctx, node, def, drawScale, env) => {
|
|
5061
5187
|
if (def.getSnapshot) {
|
|
5062
5188
|
const snap = def.getSnapshot(node, {
|
|
@@ -5341,6 +5467,7 @@ var createRenderer = (opts) => {
|
|
|
5341
5467
|
const onStoreChange = () => {
|
|
5342
5468
|
invalidateSortedCaches();
|
|
5343
5469
|
staticDirty = true;
|
|
5470
|
+
cacheStale = true;
|
|
5344
5471
|
interactiveDirty = true;
|
|
5345
5472
|
loop.requestFrame();
|
|
5346
5473
|
};
|
|
@@ -5357,6 +5484,7 @@ var createRenderer = (opts) => {
|
|
|
5357
5484
|
interactiveDirty = true;
|
|
5358
5485
|
if (state.mode === "dragging" || state.mode === "resizing" || state.mode === "rotating" || state.mode === "panning" || state.mode === "zooming" || state.mode === "idle") {
|
|
5359
5486
|
staticDirty = true;
|
|
5487
|
+
cacheStale = true;
|
|
5360
5488
|
}
|
|
5361
5489
|
loop.requestFrame();
|
|
5362
5490
|
};
|
|
@@ -5366,16 +5494,19 @@ var createRenderer = (opts) => {
|
|
|
5366
5494
|
const unsubInteraction = store.subscribe("interaction", onInteractionChange);
|
|
5367
5495
|
const unsubFontEpoch = subscribeFontEpoch(() => {
|
|
5368
5496
|
staticDirty = true;
|
|
5497
|
+
cacheStale = true;
|
|
5369
5498
|
loop.requestFrame();
|
|
5370
5499
|
});
|
|
5371
5500
|
const unsubMathEpoch = subscribeMathEpoch(() => {
|
|
5372
5501
|
staticDirty = true;
|
|
5502
|
+
cacheStale = true;
|
|
5373
5503
|
loop.requestFrame();
|
|
5374
5504
|
});
|
|
5375
5505
|
return {
|
|
5376
5506
|
start() {
|
|
5377
5507
|
loop.start();
|
|
5378
5508
|
staticDirty = true;
|
|
5509
|
+
cacheStale = true;
|
|
5379
5510
|
interactiveDirty = isInteractive(store.getInteractionState());
|
|
5380
5511
|
loop.requestFrame();
|
|
5381
5512
|
},
|
|
@@ -5384,14 +5515,16 @@ var createRenderer = (opts) => {
|
|
|
5384
5515
|
},
|
|
5385
5516
|
invalidate() {
|
|
5386
5517
|
staticDirty = true;
|
|
5518
|
+
cacheStale = true;
|
|
5387
5519
|
interactiveDirty = true;
|
|
5388
5520
|
loop.requestFrame();
|
|
5389
5521
|
},
|
|
5390
5522
|
setSize(cssW, cssH) {
|
|
5391
|
-
const a = sizeSurface(staticSurface, cssW, cssH);
|
|
5392
|
-
const b = sizeSurface(interactiveSurface, cssW, cssH);
|
|
5523
|
+
const a = sizeSurface(staticSurface, cssW, cssH, maxDpr);
|
|
5524
|
+
const b = sizeSurface(interactiveSurface, cssW, cssH, maxDpr);
|
|
5393
5525
|
if (a || b) {
|
|
5394
5526
|
staticDirty = true;
|
|
5527
|
+
cacheStale = true;
|
|
5395
5528
|
interactiveDirty = true;
|
|
5396
5529
|
loop.requestFrame();
|
|
5397
5530
|
}
|
|
@@ -5399,6 +5532,7 @@ var createRenderer = (opts) => {
|
|
|
5399
5532
|
setBackground(bg) {
|
|
5400
5533
|
background = bg;
|
|
5401
5534
|
staticDirty = true;
|
|
5535
|
+
cacheStale = true;
|
|
5402
5536
|
loop.requestFrame();
|
|
5403
5537
|
},
|
|
5404
5538
|
setSelectionColor(color) {
|
|
@@ -5409,6 +5543,7 @@ var createRenderer = (opts) => {
|
|
|
5409
5543
|
setHideFrames(hidden) {
|
|
5410
5544
|
hideFrames = hidden;
|
|
5411
5545
|
staticDirty = true;
|
|
5546
|
+
cacheStale = true;
|
|
5412
5547
|
loop.requestFrame();
|
|
5413
5548
|
},
|
|
5414
5549
|
stats: () => loop.stats(),
|
|
@@ -5423,6 +5558,11 @@ var createRenderer = (opts) => {
|
|
|
5423
5558
|
unsubFontEpoch();
|
|
5424
5559
|
unsubMathEpoch();
|
|
5425
5560
|
assetCache.dispose();
|
|
5561
|
+
if (cacheSurface) {
|
|
5562
|
+
cacheSurface.canvas.width = 0;
|
|
5563
|
+
cacheSurface.canvas.height = 0;
|
|
5564
|
+
cacheSurface = null;
|
|
5565
|
+
}
|
|
5426
5566
|
}
|
|
5427
5567
|
};
|
|
5428
5568
|
};
|