@canvas-tile-engine/renderer-canvas 0.1.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.
- package/LICENSE +21 -0
- package/README.md +103 -0
- package/dist/index.d.mts +71 -0
- package/dist/index.d.ts +71 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +2 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +60 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 ENES YÜKSEL
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
20
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
21
|
+
DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# @canvas-tile-engine/renderer-canvas
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@canvas-tile-engine/renderer-canvas)
|
|
4
|
+
[](https://bundlephobia.com/package/@canvas-tile-engine/renderer-canvas)
|
|
5
|
+
[](../../LICENSE)
|
|
6
|
+
|
|
7
|
+
Canvas2D renderer for [Canvas Tile Engine](https://github.com/ArdaGnsrn/canvas-tile-engine). This package provides the default rendering implementation using the HTML Canvas 2D API.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @canvas-tile-engine/renderer-canvas
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
### With Core (Vanilla JS/TS)
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import { CanvasTileEngine } from "@canvas-tile-engine/core";
|
|
21
|
+
import { RendererCanvas } from "@canvas-tile-engine/renderer-canvas";
|
|
22
|
+
|
|
23
|
+
const engine = new CanvasTileEngine(
|
|
24
|
+
wrapper,
|
|
25
|
+
config,
|
|
26
|
+
new RendererCanvas(),
|
|
27
|
+
{ x: 0, y: 0 }
|
|
28
|
+
);
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### With React
|
|
32
|
+
|
|
33
|
+
```tsx
|
|
34
|
+
import { CanvasTileEngine, useCanvasTileEngine } from "@canvas-tile-engine/react";
|
|
35
|
+
import { RendererCanvas } from "@canvas-tile-engine/renderer-canvas";
|
|
36
|
+
|
|
37
|
+
function App() {
|
|
38
|
+
const engine = useCanvasTileEngine();
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<CanvasTileEngine
|
|
42
|
+
engine={engine}
|
|
43
|
+
config={config}
|
|
44
|
+
renderer={new RendererCanvas()}
|
|
45
|
+
>
|
|
46
|
+
{/* children */}
|
|
47
|
+
</CanvasTileEngine>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Features
|
|
53
|
+
|
|
54
|
+
- Full support for all drawing primitives (rect, circle, image, text, path, line)
|
|
55
|
+
- Static caching for large datasets (offscreen canvas)
|
|
56
|
+
- Layer-based rendering with configurable draw order
|
|
57
|
+
- Coordinate overlay and debug HUD
|
|
58
|
+
- High DPI (retina) display support
|
|
59
|
+
|
|
60
|
+
## Custom Drawing
|
|
61
|
+
|
|
62
|
+
When using `addDrawFunction` or `onDraw`, cast the context to `CanvasRenderingContext2D`:
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
engine.addDrawFunction((ctx, coords, config) => {
|
|
66
|
+
const context = ctx as CanvasRenderingContext2D;
|
|
67
|
+
|
|
68
|
+
context.fillStyle = "red";
|
|
69
|
+
context.fillRect(0, 0, 100, 100);
|
|
70
|
+
}, 2);
|
|
71
|
+
|
|
72
|
+
engine.onDraw = (ctx, info) => {
|
|
73
|
+
const context = ctx as CanvasRenderingContext2D;
|
|
74
|
+
|
|
75
|
+
context.strokeStyle = "blue";
|
|
76
|
+
context.strokeRect(0, 0, info.width, info.height);
|
|
77
|
+
};
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Architecture
|
|
81
|
+
|
|
82
|
+
This renderer implements the `IRenderer` interface from `@canvas-tile-engine/core`:
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
@canvas-tile-engine/core
|
|
86
|
+
│
|
|
87
|
+
▼
|
|
88
|
+
IRenderer ◄─── Interface
|
|
89
|
+
│
|
|
90
|
+
▼
|
|
91
|
+
┌─────────────────┐
|
|
92
|
+
│ RendererCanvas │ ◄─── This package
|
|
93
|
+
│ (Canvas2D) │
|
|
94
|
+
└─────────────────┘
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Documentation
|
|
98
|
+
|
|
99
|
+
Full documentation: [canvastileengine.dev](https://canvastileengine.dev)
|
|
100
|
+
|
|
101
|
+
## License
|
|
102
|
+
|
|
103
|
+
MIT
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { IRenderer, onDrawCallback, onClickCallback, onRightClickCallback, onHoverCallback, onMouseDownCallback, onMouseUpCallback, onMouseLeaveCallback, onZoomCallback, RendererDependencies, IDrawAPI, IImageLoader } from '@canvas-tile-engine/core';
|
|
2
|
+
|
|
3
|
+
declare class RendererCanvas implements IRenderer {
|
|
4
|
+
private canvasWrapper;
|
|
5
|
+
private canvas;
|
|
6
|
+
private canvasContext;
|
|
7
|
+
private camera;
|
|
8
|
+
private config;
|
|
9
|
+
private viewport;
|
|
10
|
+
private layers;
|
|
11
|
+
private drawAPI;
|
|
12
|
+
private transformer;
|
|
13
|
+
private coordinateOverlayRenderer;
|
|
14
|
+
private debugOverlay?;
|
|
15
|
+
private gestureProcessor;
|
|
16
|
+
private eventBinder;
|
|
17
|
+
private resizeWatcher?;
|
|
18
|
+
private responsiveWatcher?;
|
|
19
|
+
private eventsAttached;
|
|
20
|
+
private sizeController;
|
|
21
|
+
private animationController;
|
|
22
|
+
private imageLoader;
|
|
23
|
+
/** Optional user-provided draw hook executed after engine layers. */
|
|
24
|
+
onDraw?: onDrawCallback;
|
|
25
|
+
/** Optional callback fired when canvas is resized. */
|
|
26
|
+
onResize?: () => void;
|
|
27
|
+
get onClick(): onClickCallback | undefined;
|
|
28
|
+
set onClick(cb: onClickCallback | undefined);
|
|
29
|
+
get onRightClick(): onRightClickCallback | undefined;
|
|
30
|
+
set onRightClick(cb: onRightClickCallback | undefined);
|
|
31
|
+
get onHover(): onHoverCallback | undefined;
|
|
32
|
+
set onHover(cb: onHoverCallback | undefined);
|
|
33
|
+
get onMouseDown(): onMouseDownCallback | undefined;
|
|
34
|
+
set onMouseDown(cb: onMouseDownCallback | undefined);
|
|
35
|
+
get onMouseUp(): onMouseUpCallback | undefined;
|
|
36
|
+
set onMouseUp(cb: onMouseUpCallback | undefined);
|
|
37
|
+
get onMouseLeave(): onMouseLeaveCallback | undefined;
|
|
38
|
+
set onMouseLeave(cb: onMouseLeaveCallback | undefined);
|
|
39
|
+
get onZoom(): onZoomCallback | undefined;
|
|
40
|
+
set onZoom(cb: onZoomCallback | undefined);
|
|
41
|
+
/** Callback fired when camera position changes (drag/zoom). */
|
|
42
|
+
onCameraChange?: () => void;
|
|
43
|
+
init(deps: RendererDependencies): void;
|
|
44
|
+
setupEvents(): void;
|
|
45
|
+
private normalizePointer;
|
|
46
|
+
private normalizeTouches;
|
|
47
|
+
private handleClick;
|
|
48
|
+
private handleContextMenu;
|
|
49
|
+
private handleMouseDown;
|
|
50
|
+
private handleMouseMove;
|
|
51
|
+
private handleMouseUp;
|
|
52
|
+
private handleMouseLeave;
|
|
53
|
+
private handleWheel;
|
|
54
|
+
private handleTouchStart;
|
|
55
|
+
private handleTouchMove;
|
|
56
|
+
private handleTouchEnd;
|
|
57
|
+
getDrawAPI(): IDrawAPI;
|
|
58
|
+
getImageLoader(): IImageLoader<HTMLImageElement>;
|
|
59
|
+
render(): void;
|
|
60
|
+
resize(width: number, height: number): void;
|
|
61
|
+
resizeWithAnimation(width: number, height: number, durationMs: number, onComplete?: () => void): void;
|
|
62
|
+
destroy(): void;
|
|
63
|
+
private applyCanvasSize;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
interface DrawHandle {
|
|
67
|
+
layer: number;
|
|
68
|
+
id: symbol;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export { type DrawHandle, RendererCanvas };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { IRenderer, onDrawCallback, onClickCallback, onRightClickCallback, onHoverCallback, onMouseDownCallback, onMouseUpCallback, onMouseLeaveCallback, onZoomCallback, RendererDependencies, IDrawAPI, IImageLoader } from '@canvas-tile-engine/core';
|
|
2
|
+
|
|
3
|
+
declare class RendererCanvas implements IRenderer {
|
|
4
|
+
private canvasWrapper;
|
|
5
|
+
private canvas;
|
|
6
|
+
private canvasContext;
|
|
7
|
+
private camera;
|
|
8
|
+
private config;
|
|
9
|
+
private viewport;
|
|
10
|
+
private layers;
|
|
11
|
+
private drawAPI;
|
|
12
|
+
private transformer;
|
|
13
|
+
private coordinateOverlayRenderer;
|
|
14
|
+
private debugOverlay?;
|
|
15
|
+
private gestureProcessor;
|
|
16
|
+
private eventBinder;
|
|
17
|
+
private resizeWatcher?;
|
|
18
|
+
private responsiveWatcher?;
|
|
19
|
+
private eventsAttached;
|
|
20
|
+
private sizeController;
|
|
21
|
+
private animationController;
|
|
22
|
+
private imageLoader;
|
|
23
|
+
/** Optional user-provided draw hook executed after engine layers. */
|
|
24
|
+
onDraw?: onDrawCallback;
|
|
25
|
+
/** Optional callback fired when canvas is resized. */
|
|
26
|
+
onResize?: () => void;
|
|
27
|
+
get onClick(): onClickCallback | undefined;
|
|
28
|
+
set onClick(cb: onClickCallback | undefined);
|
|
29
|
+
get onRightClick(): onRightClickCallback | undefined;
|
|
30
|
+
set onRightClick(cb: onRightClickCallback | undefined);
|
|
31
|
+
get onHover(): onHoverCallback | undefined;
|
|
32
|
+
set onHover(cb: onHoverCallback | undefined);
|
|
33
|
+
get onMouseDown(): onMouseDownCallback | undefined;
|
|
34
|
+
set onMouseDown(cb: onMouseDownCallback | undefined);
|
|
35
|
+
get onMouseUp(): onMouseUpCallback | undefined;
|
|
36
|
+
set onMouseUp(cb: onMouseUpCallback | undefined);
|
|
37
|
+
get onMouseLeave(): onMouseLeaveCallback | undefined;
|
|
38
|
+
set onMouseLeave(cb: onMouseLeaveCallback | undefined);
|
|
39
|
+
get onZoom(): onZoomCallback | undefined;
|
|
40
|
+
set onZoom(cb: onZoomCallback | undefined);
|
|
41
|
+
/** Callback fired when camera position changes (drag/zoom). */
|
|
42
|
+
onCameraChange?: () => void;
|
|
43
|
+
init(deps: RendererDependencies): void;
|
|
44
|
+
setupEvents(): void;
|
|
45
|
+
private normalizePointer;
|
|
46
|
+
private normalizeTouches;
|
|
47
|
+
private handleClick;
|
|
48
|
+
private handleContextMenu;
|
|
49
|
+
private handleMouseDown;
|
|
50
|
+
private handleMouseMove;
|
|
51
|
+
private handleMouseUp;
|
|
52
|
+
private handleMouseLeave;
|
|
53
|
+
private handleWheel;
|
|
54
|
+
private handleTouchStart;
|
|
55
|
+
private handleTouchMove;
|
|
56
|
+
private handleTouchEnd;
|
|
57
|
+
getDrawAPI(): IDrawAPI;
|
|
58
|
+
getImageLoader(): IImageLoader<HTMLImageElement>;
|
|
59
|
+
render(): void;
|
|
60
|
+
resize(width: number, height: number): void;
|
|
61
|
+
resizeWithAnimation(width: number, height: number, durationMs: number, onComplete?: () => void): void;
|
|
62
|
+
destroy(): void;
|
|
63
|
+
private applyCanvasSize;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
interface DrawHandle {
|
|
67
|
+
layer: number;
|
|
68
|
+
id: symbol;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export { type DrawHandle, RendererCanvas };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";var B=Object.defineProperty;var G=Object.getOwnPropertyDescriptor;var Z=Object.getOwnPropertyNames;var q=Object.prototype.hasOwnProperty;var j=(C,e)=>{for(var t in e)B(C,t,{get:e[t],enumerable:!0})},J=(C,e,t,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of Z(e))!q.call(C,r)&&r!==t&&B(C,r,{get:()=>e[r],enumerable:!(s=G(e,r))||s.enumerable});return C};var Q=C=>J(B({},"__esModule",{value:!0}),C);var te={};j(te,{RendererCanvas:()=>_});module.exports=Q(te);var $=require("@canvas-tile-engine/core");var b=require("@canvas-tile-engine/core");function V(C,e,t,s,r){t?Object.assign(C.style,{position:"relative",overflow:"hidden"}):Object.assign(C.style,{position:"relative",overflow:"hidden",width:s+"px",height:r+"px"}),Object.assign(e.style,{position:"absolute",top:"0",left:"0"})}function I(C,e){if(e>=1)return C.lineWidth=e,()=>{};let t=Math.max(0,Math.min(e,1));return C.lineWidth=1,C.globalAlpha=t,()=>{C.globalAlpha=1}}var L=500,U=16384,z=class{constructor(e,t,s){this.layers=e;this.transformer=t;this.camera=s;this.staticCacheSupported=typeof OffscreenCanvas<"u"||typeof document<"u"}staticCaches=new Map;staticCacheSupported;warnedStaticCacheDisabled=!1;isVisible(e,t,s,r,n){let i=n.size.width/n.scale,a=n.size.height/n.scale,p=r.x-b.VISIBILITY_BUFFER.TILE_BUFFER,h=r.y-b.VISIBILITY_BUFFER.TILE_BUFFER,c=r.x+i+b.VISIBILITY_BUFFER.TILE_BUFFER,o=r.y+a+b.VISIBILITY_BUFFER.TILE_BUFFER;return e+s>=p&&e-s<=c&&t+s>=h&&t-s<=o}getViewportBounds(e,t){let s=t.size.width/t.scale,r=t.size.height/t.scale;return{minX:e.x-b.VISIBILITY_BUFFER.TILE_BUFFER,minY:e.y-b.VISIBILITY_BUFFER.TILE_BUFFER,maxX:e.x+s+b.VISIBILITY_BUFFER.TILE_BUFFER,maxY:e.y+r+b.VISIBILITY_BUFFER.TILE_BUFFER}}addDrawFunction(e,t=1){return this.layers.add(t,({ctx:s,config:r,topLeft:n})=>{e(s,n,r)})}drawRect(e,t=1){let s=Array.isArray(e)?e:[e],n=s.length>L?b.SpatialIndex.fromArray(s):null;return this.layers.add(t,({ctx:i,config:a,topLeft:p})=>{let h=this.getViewportBounds(p,a),c=n?n.query(h.minX,h.minY,h.maxX,h.maxY):s;i.save();let o,m,u;for(let l of c){let d=l.size??1,f={mode:l.origin?.mode==="self"?"self":"cell",x:l.origin?.x??.5,y:l.origin?.y??.5},v=l.style;if(!n&&!this.isVisible(l.x,l.y,d/2,p,a))continue;let w=this.transformer.worldToScreen(l.x,l.y),g=d*this.camera.scale,{x:y,y:x}=this.computeOriginOffset(w,g,f,this.camera);v?.fillStyle&&v.fillStyle!==o&&(i.fillStyle=v.fillStyle,o=v.fillStyle),v?.strokeStyle&&v.strokeStyle!==m&&(i.strokeStyle=v.strokeStyle,m=v.strokeStyle);let S;v?.lineWidth&&v.lineWidth!==u&&(S=I(i,v.lineWidth),u=v.lineWidth);let R=l.rotate??0,Y=R*(Math.PI/180),D=l.radius;if(R!==0){let X=y+g/2,N=x+g/2;i.save(),i.translate(X,N),i.rotate(Y),i.beginPath(),D&&i.roundRect?i.roundRect(-g/2,-g/2,g,g,D):i.rect(-g/2,-g/2,g,g),v?.fillStyle&&i.fill(),v?.strokeStyle&&i.stroke(),i.restore()}else i.beginPath(),D&&i.roundRect?i.roundRect(y,x,g,g,D):i.rect(y,x,g,g),v?.fillStyle&&i.fill(),v?.strokeStyle&&i.stroke();S?.()}i.restore()})}drawLine(e,t,s=1){let r=Array.isArray(e)?e:[e];return this.layers.add(s,({ctx:n,config:i,topLeft:a})=>{n.save(),t?.strokeStyle&&(n.strokeStyle=t.strokeStyle);let p=t?.lineWidth?I(n,t.lineWidth):void 0;n.beginPath();for(let h of r){let c=(h.from.x+h.to.x)/2,o=(h.from.y+h.to.y)/2,m=Math.max(Math.abs(h.from.x-h.to.x),Math.abs(h.from.y-h.to.y))/2;if(!this.isVisible(c,o,m,a,i))continue;let u=this.transformer.worldToScreen(h.from.x,h.from.y),l=this.transformer.worldToScreen(h.to.x,h.to.y);n.moveTo(u.x,u.y),n.lineTo(l.x,l.y)}n.stroke(),p?.(),n.restore()})}drawCircle(e,t=1){let s=Array.isArray(e)?e:[e],n=s.length>L?b.SpatialIndex.fromArray(s):null;return this.layers.add(t,({ctx:i,config:a,topLeft:p})=>{let h=this.getViewportBounds(p,a),c=n?n.query(h.minX,h.minY,h.maxX,h.maxY):s;i.save();let o,m,u;for(let l of c){let d=l.size??1,f={mode:l.origin?.mode==="self"?"self":"cell",x:l.origin?.x??.5,y:l.origin?.y??.5},v=l.style;if(!n&&!this.isVisible(l.x,l.y,d/2,p,a))continue;let w=this.transformer.worldToScreen(l.x,l.y),g=d*this.camera.scale,y=g/2,{x,y:S}=this.computeOriginOffset(w,g,f,this.camera);v?.fillStyle&&v.fillStyle!==o&&(i.fillStyle=v.fillStyle,o=v.fillStyle),v?.strokeStyle&&v.strokeStyle!==m&&(i.strokeStyle=v.strokeStyle,m=v.strokeStyle);let R;v?.lineWidth&&v.lineWidth!==u&&(R=I(i,v.lineWidth),u=v.lineWidth),i.beginPath(),i.arc(x+y,S+y,y,0,Math.PI*2),v?.fillStyle&&i.fill(),v?.strokeStyle&&i.stroke(),R?.()}i.restore()})}drawText(e,t=2){let s=Array.isArray(e)?e:[e],n=s.length>L?b.SpatialIndex.fromArray(s):null;return this.layers.add(t,({ctx:i,config:a,topLeft:p})=>{let h=this.getViewportBounds(p,a),c=n?n.query(h.minX,h.minY,h.maxX,h.maxY):s;i.save();for(let o of c){let m=o.size??1,u=o.style;if(!n&&!this.isVisible(o.x,o.y,m,p,a))continue;let l=m*this.camera.scale*.3,d=u?.fontFamily??"sans-serif";i.font=`${l}px ${d}`,u?.fillStyle&&(i.fillStyle=u.fillStyle),i.textAlign=u?.textAlign??"center",i.textBaseline=u?.textBaseline??"middle";let f=this.transformer.worldToScreen(o.x,o.y),v=o.rotate??0;if(v!==0){let w=v*(Math.PI/180);i.save(),i.translate(f.x,f.y),i.rotate(w),i.fillText(o.text,0,0),i.restore()}else i.fillText(o.text,f.x,f.y)}i.restore()})}drawPath(e,t,s=1){let r=Array.isArray(e[0])?e:[e];return this.layers.add(s,({ctx:n,config:i,topLeft:a})=>{n.save(),t?.strokeStyle&&(n.strokeStyle=t.strokeStyle);let p=t?.lineWidth?I(n,t.lineWidth):void 0;n.beginPath();for(let h of r){if(!h.length)continue;let c=h.map(y=>y.x),o=h.map(y=>y.y),m=Math.min(...c),u=Math.max(...c),l=Math.min(...o),d=Math.max(...o),f=(m+u)/2,v=(l+d)/2,w=Math.max(u-m,d-l)/2;if(!this.isVisible(f,v,w,a,i))continue;let g=this.transformer.worldToScreen(h[0].x,h[0].y);n.moveTo(g.x,g.y);for(let y=1;y<h.length;y++){let x=this.transformer.worldToScreen(h[y].x,h[y].y);n.lineTo(x.x,x.y)}}n.stroke(),p?.(),n.restore()})}drawImage(e,t=1){let s=Array.isArray(e)?e:[e],n=s.length>L?b.SpatialIndex.fromArray(s):null;return this.layers.add(t,({ctx:i,config:a,topLeft:p})=>{let h=this.getViewportBounds(p,a),c=n?n.query(h.minX,h.minY,h.maxX,h.maxY):s;for(let o of c){let m=o.size??1,u={mode:o.origin?.mode==="self"?"self":"cell",x:o.origin?.x??.5,y:o.origin?.y??.5};if(!n&&!this.isVisible(o.x,o.y,m/2,p,a))continue;let l=this.transformer.worldToScreen(o.x,o.y),d=m*this.camera.scale,f=o.img.width/o.img.height,v=d,w=d;f>1?w=d/f:v=d*f;let{x:g,y}=this.computeOriginOffset(l,d,u,this.camera),x=g+(d-v)/2,S=y+(d-w)/2,R=o.rotate??0,Y=R*(Math.PI/180);if(R!==0){let D=x+v/2,X=S+w/2;i.save(),i.translate(D,X),i.rotate(Y),i.drawImage(o.img,-v/2,-w/2,v,w),i.restore()}else i.drawImage(o.img,x,S,v,w)}})}drawGridLines(e,t,s=0){return this.layers.add(s,({ctx:r,config:n,topLeft:i})=>{let a=n.size.width/n.scale,p=n.size.height/n.scale,h=Math.floor(i.x/e)*e-b.DEFAULT_VALUES.CELL_CENTER_OFFSET,c=Math.ceil((i.x+a)/e)*e-b.DEFAULT_VALUES.CELL_CENTER_OFFSET,o=Math.floor(i.y/e)*e-b.DEFAULT_VALUES.CELL_CENTER_OFFSET,m=Math.ceil((i.y+p)/e)*e-b.DEFAULT_VALUES.CELL_CENTER_OFFSET;r.save(),r.strokeStyle=t.strokeStyle;let u=I(r,t.lineWidth);r.beginPath();for(let l=h;l<=c;l+=e){let d=this.transformer.worldToScreen(l,o),f=this.transformer.worldToScreen(l,m);r.moveTo(d.x,d.y),r.lineTo(f.x,f.y)}for(let l=o;l<=m;l+=e){let d=this.transformer.worldToScreen(h,l),f=this.transformer.worldToScreen(c,l);r.moveTo(d.x,d.y),r.lineTo(f.x,f.y)}r.stroke(),u(),r.restore()})}computeOriginOffset(e,t,s,r){if(s.mode==="cell"){let n=r.scale;return{x:e.x-n/2+s.x*n-t/2,y:e.y-n/2+s.y*n-t/2}}return{x:e.x-s.x*t,y:e.y-s.y*t}}getOrCreateStaticCache(e,t,s){if(!this.staticCacheSupported)return this.warnedStaticCacheDisabled||(console.warn("[CanvasDraw] Static cache disabled: OffscreenCanvas not available."),this.warnedStaticCacheDisabled=!0),null;let r=1/0,n=-1/0,i=1/0,a=-1/0;for(let d of e){let f=d.size??1;d.x-f/2<r&&(r=d.x-f/2),d.x+f/2>n&&(n=d.x+f/2),d.y-f/2<i&&(i=d.y-f/2),d.y+f/2>a&&(a=d.y+f/2)}r-=1,i-=1,n+=1,a+=1;let p=n-r,h=a-i,c=this.camera.scale,o=Math.ceil(p*c),m=Math.ceil(h*c);if(o>U||m>U)return this.warnedStaticCacheDisabled||(console.warn(`Static cache disabled: offscreen canvas too large (${o}x${m}).`),this.warnedStaticCacheDisabled=!0),null;let u=this.staticCaches.get(t);if(!u||u.scale!==c||u.worldBounds.minX!==r||u.worldBounds.maxX!==n||u.worldBounds.minY!==i||u.worldBounds.maxY!==a){let d=typeof OffscreenCanvas<"u"?new OffscreenCanvas(o,m):document.createElement("canvas");typeof OffscreenCanvas<"u"&&d instanceof OffscreenCanvas||(d.width=o,d.height=m);let v=d.getContext("2d");if(!v)return this.warnedStaticCacheDisabled||(console.warn("[CanvasDraw] Static cache disabled: 2D context unavailable."),this.warnedStaticCacheDisabled=!0),null;for(let w of e){let y=(w.size??1)*c,x=(w.x+b.DEFAULT_VALUES.CELL_CENTER_OFFSET-r)*c-y/2,S=(w.y+b.DEFAULT_VALUES.CELL_CENTER_OFFSET-i)*c-y/2;s(v,w,x,S,y)}u={canvas:d,ctx:v,worldBounds:{minX:r,minY:i,maxX:n,maxY:a},scale:c},this.staticCaches.set(t,u)}return u??null}addStaticCacheLayer(e,t){if(!e)return null;let s=e.canvas,r=e.worldBounds,n=e.scale;return this.layers.add(t,({ctx:i,config:a,topLeft:p})=>{let h=a.size.width/a.scale,c=a.size.height/a.scale,o=(p.x-r.minX)*n,m=(p.y-r.minY)*n,u=h*n,l=c*n,d=0,f=0,v=a.size.width,w=a.size.height,g=s.width,y=s.height;if(o<0&&(d=-o/n*a.scale,v-=d,u+=o,o=0),m<0&&(f=-m/n*a.scale,w-=f,l+=m,m=0),o+u>g){let S=(o+u-g)/n;u=g-o,v-=S*a.scale}if(m+l>y){let S=(m+l-y)/n;l=y-m,w-=S*a.scale}u>0&&l>0&&v>0&&w>0&&i.drawImage(s,o,m,u,l,d,f,v,w)})}drawStaticRect(e,t,s=1){let r,n=this.getOrCreateStaticCache(e,t,(i,a,p,h,c)=>{let o=a.style,m=a.rotate??0,u=m*(Math.PI/180),l=a.radius;if(o?.fillStyle&&o.fillStyle!==r&&(i.fillStyle=o.fillStyle,r=o.fillStyle),m!==0){let d=p+c/2,f=h+c/2;i.save(),i.translate(d,f),i.rotate(u),l&&i.roundRect?(i.beginPath(),i.roundRect(-c/2,-c/2,c,c,l),i.fill()):i.fillRect(-c/2,-c/2,c,c),i.restore()}else l&&i.roundRect?(i.beginPath(),i.roundRect(p,h,c,c,l),i.fill()):i.fillRect(p,h,c,c)});return n?this.addStaticCacheLayer(n,s):this.drawRect(e,s)}drawStaticImage(e,t,s=1){let r=this.getOrCreateStaticCache(e,t,(n,i,a,p,h)=>{let c=i.img,o=i.rotate??0,m=o*(Math.PI/180),u=c.width/c.height,l=h,d=h;u>1?d=h/u:l=h*u;let f=a+(h-l)/2,v=p+(h-d)/2;if(o!==0){let w=f+l/2,g=v+d/2;n.save(),n.translate(w,g),n.rotate(m),n.drawImage(c,-l/2,-d/2,l,d),n.restore()}else n.drawImage(c,f,v,l,d)});return r?this.addStaticCacheLayer(r,s):this.drawImage(e,s)}drawStaticCircle(e,t,s=1){let r,n=this.getOrCreateStaticCache(e,t,(i,a,p,h,c)=>{let o=a.style,m=c/2;o?.fillStyle&&o.fillStyle!==r&&(i.fillStyle=o.fillStyle,r=o.fillStyle),i.beginPath(),i.arc(p+m,h+m,m,0,Math.PI*2),i.fill()});return n?this.addStaticCacheLayer(n,s):this.drawCircle(e,s)}clearStaticCache(e){e?this.staticCaches.delete(e):this.staticCaches.clear()}removeDrawHandle(e){if(!this.layers)throw new Error("removeDrawHandle is only available when renderer is set to 'canvas'.");this.layers.remove(e)}clearLayer(e){if(!this.layers)throw new Error("clearLayer is only available when renderer is set to 'canvas'.");this.layers.clear(e)}clearAll(){if(!this.layers)throw new Error("clearAll is only available when renderer is set to 'canvas'.");this.layers.clear()}destroy(){this.staticCaches.clear(),this.layers.clear()}};var M=class{layers=new Map;add(e,t){let s=Symbol("layer-callback"),r={id:s,fn:t};return this.layers.has(e)||this.layers.set(e,[]),this.layers.get(e).push(r),{layer:e,id:s}}remove(e){let t=this.layers.get(e.layer);t&&this.layers.set(e.layer,t.filter(s=>s.id!==e.id))}clear(e){if(e===void 0){this.layers.clear();return}this.layers.set(e,[])}drawAll(e){let t=[...this.layers.keys()].sort((s,r)=>s-r);for(let s of t){let r=this.layers.get(s);if(r)for(let{fn:n}of r)e.ctx.save(),n(e),e.ctx.restore()}}};var E=require("@canvas-tile-engine/core"),W=class{ctx;camera;config;viewport;constructor(e,t,s,r){this.ctx=e,this.camera=t,this.config=s,this.viewport=r}draw(){this.ctx.save(),this.ctx.fillStyle=`rgba(0, 0, 0, ${E.COORDINATE_OVERLAY.BORDER_OPACITY})`;let{width:e,height:t}=this.viewport.getSize();this.ctx.fillRect(0,0,E.COORDINATE_OVERLAY.BORDER_WIDTH,t),this.ctx.fillRect(E.COORDINATE_OVERLAY.BORDER_WIDTH,t-E.COORDINATE_OVERLAY.BORDER_WIDTH,e,E.COORDINATE_OVERLAY.BORDER_WIDTH),this.ctx.fillStyle=`rgba(255, 255, 255, ${E.COORDINATE_OVERLAY.TEXT_OPACITY})`;let s=Math.min(E.COORDINATE_OVERLAY.MAX_FONT_SIZE,Math.max(E.COORDINATE_OVERLAY.MIN_FONT_SIZE,this.camera.scale*E.COORDINATE_OVERLAY.FONT_SIZE_SCALE_FACTOR));this.ctx.font=`${s}px Arial`,this.ctx.textAlign="center",this.ctx.textBaseline="middle";let r=this.camera.scale,n=e/r,i=t/r;for(let a=0-this.camera.y%1;a<=i+1;a++)this.ctx.fillText(Math.round(this.camera.y+a).toString(),10,r*a+r/2);for(let a=0-this.camera.x%1;a<=n+1;a++)this.ctx.fillText(Math.round(this.camera.x+a).toString(),r*a+r/2,t-10);this.ctx.restore()}shouldDraw(e){let t=this.config.get().coordinates;if(!t.enabled||!t.shownScaleRange)return!1;let{min:s,max:r}=t.shownScaleRange;return e>=s&&e<=r}};var T=require("@canvas-tile-engine/core"),K=10,H=class{ctx;camera;config;viewport;frameTimes=[];lastFrameTime=0;currentFps=0;fpsLoopRunning=!1;onFpsUpdate=null;constructor(e,t,s,r){this.ctx=e,this.camera=t,this.config=s,this.viewport=r}setFpsUpdateCallback(e){this.onFpsUpdate=e}startFpsLoop(){this.fpsLoopRunning||(this.fpsLoopRunning=!0,this.lastFrameTime=performance.now(),this.fpsLoop())}stopFpsLoop(){this.fpsLoopRunning=!1}fpsLoop(){if(!this.fpsLoopRunning)return;let e=performance.now(),t=e-this.lastFrameTime;this.lastFrameTime=e,this.frameTimes.push(t),this.frameTimes.length>K&&this.frameTimes.shift();let s=this.frameTimes.reduce((n,i)=>n+i,0)/this.frameTimes.length,r=Math.round(1e3/s);r!==this.currentFps&&(this.currentFps=r,this.onFpsUpdate?.()),requestAnimationFrame(()=>this.fpsLoop())}draw(){this.drawHud()}destroy(){this.stopFpsLoop(),this.onFpsUpdate=null}drawHud(){let e=this.config.get();if(!e.debug.hud||!e.debug.hud.enabled)return;let t=[],s={x:this.camera.x,y:this.camera.y};if(e.debug.hud.topLeftCoordinates&&t.push(`TopLeft: ${s.x.toFixed(2)}, ${s.y.toFixed(2)}`),e.debug.hud.coordinates){let{width:n,height:i}=this.viewport.getSize(),a=this.camera.getCenter(n,i);t.push(`Coords: ${a.x.toFixed(2)}, ${a.y.toFixed(2)}`)}if(e.debug.hud.scale&&t.push(`Scale: ${this.camera.scale.toFixed(2)}`),e.debug.hud.tilesInView){let{width:n,height:i}=this.viewport.getSize();t.push(`Tiles in view: ${Math.ceil(n/this.camera.scale)} x ${Math.ceil(i/this.camera.scale)}`)}e.debug.hud.fps&&t.push(`FPS: ${this.currentFps}`);let{width:r}=this.viewport.getSize();this.ctx.save(),this.ctx.fillStyle="rgba(0,0,0,0.5)",this.ctx.fillRect(r-T.DEBUG_HUD.PANEL_WIDTH-T.DEBUG_HUD.PADDING,T.DEBUG_HUD.PADDING/2,T.DEBUG_HUD.PANEL_WIDTH,t.length*T.DEBUG_HUD.LINE_HEIGHT+T.DEBUG_HUD.PADDING),this.ctx.fillStyle="#00ff99",this.ctx.font="12px monospace";for(let n=0;n<t.length;n++)this.ctx.fillText(t[n],r-T.DEBUG_HUD.PANEL_WIDTH-T.DEBUG_HUD.PADDING+5,18+n*T.DEBUG_HUD.LINE_HEIGHT);this.ctx.restore()}};var P=class{constructor(e,t){this.canvas=e;this.handlers=t}attach(){this.handlers.click&&this.canvas.addEventListener("click",this.handlers.click),this.handlers.contextmenu&&this.canvas.addEventListener("contextmenu",this.handlers.contextmenu),this.handlers.mousedown&&this.canvas.addEventListener("mousedown",this.handlers.mousedown),this.handlers.mousemove&&this.canvas.addEventListener("mousemove",this.handlers.mousemove),this.handlers.mouseup&&this.canvas.addEventListener("mouseup",this.handlers.mouseup),this.handlers.mouseleave&&this.canvas.addEventListener("mouseleave",this.handlers.mouseleave),this.handlers.wheel&&this.canvas.addEventListener("wheel",this.handlers.wheel,{passive:!1}),this.handlers.touchstart&&this.canvas.addEventListener("touchstart",this.handlers.touchstart,{passive:!1}),this.handlers.touchmove&&this.canvas.addEventListener("touchmove",this.handlers.touchmove,{passive:!1}),this.handlers.touchend&&this.canvas.addEventListener("touchend",this.handlers.touchend,{passive:!1})}detach(){this.handlers.click&&this.canvas.removeEventListener("click",this.handlers.click),this.handlers.contextmenu&&this.canvas.removeEventListener("contextmenu",this.handlers.contextmenu),this.handlers.mousedown&&this.canvas.removeEventListener("mousedown",this.handlers.mousedown),this.handlers.mousemove&&this.canvas.removeEventListener("mousemove",this.handlers.mousemove),this.handlers.mouseup&&this.canvas.removeEventListener("mouseup",this.handlers.mouseup),this.handlers.mouseleave&&this.canvas.removeEventListener("mouseleave",this.handlers.mouseleave),this.handlers.wheel&&this.canvas.removeEventListener("wheel",this.handlers.wheel),this.handlers.touchstart&&this.canvas.removeEventListener("touchstart",this.handlers.touchstart),this.handlers.touchmove&&this.canvas.removeEventListener("touchmove",this.handlers.touchmove),this.handlers.touchend&&this.canvas.removeEventListener("touchend",this.handlers.touchend)}};var A=class{constructor(e,t,s,r,n,i){this.wrapper=e;this.canvas=t;this.viewport=s;this.camera=r;this.config=n;this.onCameraChange=i;this.currentDpr=this.viewport.dpr}resizeObserver;handleWindowResize;currentDpr;onResize;start(){this.viewport.updateDpr(),this.currentDpr=this.viewport.dpr;let e=this.viewport.getSize(),t=this.config.get().size,s=t?.maxWidth,r=t?.maxHeight,n=t?.minWidth,i=t?.minHeight;e.width=this.clamp(e.width,n,s),e.height=this.clamp(e.height,i,r),Object.assign(this.wrapper.style,{resize:"both",overflow:"hidden",width:`${e.width}px`,height:`${e.height}px`,touchAction:"none",position:"relative",maxWidth:s?`${s}px`:"",maxHeight:r?`${r}px`:"",minWidth:n?`${n}px`:"",minHeight:i?`${i}px`:""}),this.resizeObserver=new ResizeObserver(a=>{for(let p of a){let{width:h,height:c}=p.contentRect,o=this.clamp(h,n,s),m=this.clamp(c,i,r),u=this.viewport.getSize();if(o===u.width&&m===u.height)continue;let l=o-u.width,d=m-u.height,f=this.viewport.dpr;this.camera.adjustForResize(l,d),this.viewport.setSize(o,m),this.canvas.width=o*f,this.canvas.height=m*f,this.canvas.style.width=`${o}px`,this.canvas.style.height=`${m}px`,this.wrapper.style.width=`${o}px`,this.wrapper.style.height=`${m}px`,this.onResize&&this.onResize(),this.onCameraChange()}}),this.resizeObserver.observe(this.wrapper),this.attachDprWatcher()}stop(){this.resizeObserver&&(this.resizeObserver.unobserve(this.wrapper),this.resizeObserver.disconnect()),this.resizeObserver=void 0,this.handleWindowResize&&(window.removeEventListener("resize",this.handleWindowResize),this.handleWindowResize=void 0)}clamp(e,t,s){let r=e;return t!==void 0&&(r=Math.max(t,r)),s!==void 0&&(r=Math.min(s,r)),r}attachDprWatcher(){typeof window>"u"||(this.handleWindowResize=()=>{let e=this.currentDpr;this.viewport.updateDpr();let t=this.viewport.dpr;if(t===e)return;this.currentDpr=t;let{width:s,height:r}=this.viewport.getSize();this.canvas.width=s*t,this.canvas.height=r*t,this.canvas.style.width=`${s}px`,this.canvas.style.height=`${r}px`,this.onResize&&this.onResize(),this.onCameraChange()},window.addEventListener("resize",this.handleWindowResize,{passive:!0}))}};var k=class{constructor(e,t,s,r,n,i){this.wrapper=e;this.canvas=t;this.camera=s;this.viewport=r;this.config=n;this.onRender=i;this.currentDpr=this.viewport.dpr;let a=this.config.get();this.initialVisibleTiles={x:a.size.width/a.scale,y:a.size.height/a.scale},this.widthLimits={min:a.minScale*this.initialVisibleTiles.x,max:a.maxScale*this.initialVisibleTiles.x}}resizeObserver;handleWindowResize;currentDpr;initialVisibleTiles;widthLimits;onResize;start(){let e=this.config.get().responsive;if(!e)return;if(this.viewport.updateDpr(),this.currentDpr=this.viewport.dpr,e==="preserve-viewport"){let n=this.initialVisibleTiles.y/this.initialVisibleTiles.x;this.wrapper.style.width="100%",this.wrapper.style.minWidth=`${this.widthLimits.min}px`,this.wrapper.style.maxWidth=`${this.widthLimits.max}px`,this.wrapper.style.minHeight=`${this.widthLimits.min*n}px`,this.wrapper.style.maxHeight=`${this.widthLimits.max*n}px`}else{let n=this.config.get();this.wrapper.style.width="100%",this.wrapper.style.height=`${n.size.height}px`}let t=this.wrapper.getBoundingClientRect(),s=Math.round(t.width),r=Math.round(t.height);this.applySize(s,r,e),this.resizeObserver=new ResizeObserver(n=>{for(let i of n){let{width:a,height:p}=i.contentRect,h=Math.round(a),c=Math.round(p);if(h<=0||c<=0)continue;let o=this.viewport.getSize();h===o.width&&c===o.height||(this.applySize(h,c,e),this.onResize?.(),this.onRender())}}),this.resizeObserver.observe(this.wrapper),this.attachDprWatcher()}stop(){this.resizeObserver&&(this.resizeObserver.unobserve(this.wrapper),this.resizeObserver.disconnect(),this.resizeObserver=void 0),this.handleWindowResize&&(window.removeEventListener("resize",this.handleWindowResize),this.handleWindowResize=void 0)}applySize(e,t,s){let r=this.viewport.dpr,n=this.viewport.getSize();if(s==="preserve-viewport"){let i=e/this.initialVisibleTiles.x,a=Math.round(this.initialVisibleTiles.y*i);t=a,this.wrapper.style.height=`${a}px`;let p=this.camera.getCenter(n.width,n.height);this.camera.setScale(i),this.camera.setCenter(p,e,t)}else{let i=e-n.width,a=t-n.height;this.camera.adjustForResize(i,a)}this.viewport.setSize(e,t),this.canvas.width=e*r,this.canvas.height=t*r,this.canvas.style.width=`${e}px`,this.canvas.style.height=`${t}px`}attachDprWatcher(){typeof window>"u"||(this.handleWindowResize=()=>{let e=this.currentDpr;this.viewport.updateDpr();let t=this.viewport.dpr;if(t===e)return;this.currentDpr=t;let{width:s,height:r}=this.viewport.getSize();this.canvas.width=s*t,this.canvas.height=r*t,this.canvas.style.width=`${s}px`,this.canvas.style.height=`${r}px`,this.onResize?.(),this.onRender()},window.addEventListener("resize",this.handleWindowResize,{passive:!0}))}};var ee=1,O=class{cache=new Map;inflight=new Map;listeners=new Set;onLoad(e){return this.listeners.add(e),()=>this.listeners.delete(e)}notifyLoaded(){for(let e of this.listeners)e()}async load(e,t=ee){if(this.cache.has(e))return this.cache.get(e);if(this.inflight.has(e))return this.inflight.get(e);let s=new Promise((r,n)=>{let i=new Image;i.crossOrigin="anonymous",i.decoding="async",i.loading="eager",i.onload=async()=>{try{"decode"in i&&await i.decode?.()}catch{}this.cache.set(e,i),this.inflight.delete(e),this.notifyLoaded(),r(i)},i.onerror=a=>{if(this.inflight.delete(e),t>0)console.warn(`Retrying image: ${e}`),r(this.load(e,t-1));else{console.error(`Image failed to load: ${e}`,a);let p=a instanceof Error?a.message:typeof a=="string"?a:JSON.stringify(a);n(new Error(`Image failed to load: ${e}. Reason: ${p}`))}},i.src=e});return this.inflight.set(e,s),s}get(e){return this.cache.get(e)}has(e){return this.cache.has(e)}clear(){this.cache.clear(),this.inflight.clear(),this.listeners.clear()}};var F=class{constructor(e,t,s,r,n,i){this.canvasWrapper=e;this.canvas=t;this.camera=s;this.viewport=r;this.config=n;this.onRender=i}resizeWithAnimation(e,t,s,r,n){if(e<=0||t<=0)return;let i=this.config.get().size,a=(p,h,c)=>{let o=p;return h!==void 0&&(o=Math.max(h,o)),c!==void 0&&(o=Math.min(c,o)),o};e=a(e,i?.minWidth,i?.maxWidth),t=a(t,i?.minHeight,i?.maxHeight),r.animateResize(e,t,s,(p,h,c)=>this.applySize(p,h,c),n)}applySize(e,t,s){let r=Math.round(e),n=Math.round(t),i=this.viewport.dpr;this.viewport.setSize(r,n),this.canvasWrapper.style.width=`${r}px`,this.canvasWrapper.style.height=`${n}px`,this.canvas.width=r*i,this.canvas.height=n*i,this.canvas.style.width=`${r}px`,this.canvas.style.height=`${n}px`,this.camera.setCenter(s,r,n),this.onRender()}};var _=class{canvasWrapper;canvas;canvasContext;camera;config;viewport;layers;drawAPI;transformer;coordinateOverlayRenderer;debugOverlay;gestureProcessor;eventBinder;resizeWatcher;responsiveWatcher;eventsAttached=!1;sizeController;animationController;imageLoader=new O;onDraw;onResize;get onClick(){return this.gestureProcessor?.onClick}set onClick(e){this.gestureProcessor&&(this.gestureProcessor.onClick=e)}get onRightClick(){return this.gestureProcessor?.onRightClick}set onRightClick(e){this.gestureProcessor&&(this.gestureProcessor.onRightClick=e)}get onHover(){return this.gestureProcessor?.onHover}set onHover(e){this.gestureProcessor&&(this.gestureProcessor.onHover=e)}get onMouseDown(){return this.gestureProcessor?.onMouseDown}set onMouseDown(e){this.gestureProcessor&&(this.gestureProcessor.onMouseDown=e)}get onMouseUp(){return this.gestureProcessor?.onMouseUp}set onMouseUp(e){this.gestureProcessor&&(this.gestureProcessor.onMouseUp=e)}get onMouseLeave(){return this.gestureProcessor?.onMouseLeave}set onMouseLeave(e){this.gestureProcessor&&(this.gestureProcessor.onMouseLeave=e)}get onZoom(){return this.gestureProcessor?.onZoom}set onZoom(e){this.gestureProcessor&&(this.gestureProcessor.onZoom=e)}onCameraChange;init(e){if(this.config=e.config,this.canvasWrapper=e.wrapper,this.canvas=this.canvasWrapper.querySelector("canvas"),!this.canvas)throw new Error("Canvas element not found in wrapper");if(V(this.canvasWrapper,this.canvas,this.config.get().responsive,this.config.get().size.width,this.config.get().size.height),this.canvasContext=this.canvas.getContext("2d"),!this.canvasContext)throw new Error("Failed to get 2D canvas context");this.transformer=e.transformer,this.viewport=e.viewport,this.camera=e.camera,this.layers=new M,this.drawAPI=new z(this.layers,e.transformer,e.camera),this.applyCanvasSize(),this.coordinateOverlayRenderer=new W(this.canvasContext,this.camera,this.config,this.viewport),this.config.get().debug?.enabled&&(this.debugOverlay=new H(this.canvasContext,this.camera,this.config,this.viewport),this.config.get().debug?.hud?.fps&&(this.debugOverlay.setFpsUpdateCallback(()=>this.render()),this.debugOverlay.startFpsLoop())),this.gestureProcessor=new $.GestureProcessor(this.camera,this.config,this.transformer,()=>this.canvas.getBoundingClientRect(),()=>{this.onCameraChange?.(),this.render()}),this.eventBinder=new P(this.canvas,{click:this.handleClick,contextmenu:this.handleContextMenu,mousedown:this.handleMouseDown,mousemove:this.handleMouseMove,mouseup:this.handleMouseUp,mouseleave:this.handleMouseLeave,wheel:this.handleWheel,touchstart:this.handleTouchStart,touchmove:this.handleTouchMove,touchend:this.handleTouchEnd}),this.animationController=new $.AnimationController(this.camera,this.viewport,()=>this.render()),this.sizeController=new F(this.canvasWrapper,this.canvas,this.camera,this.viewport,this.config,()=>this.render())}setupEvents(){this.eventsAttached||(this.eventBinder.attach(),this.eventsAttached=!0,this.config.get().responsive?(this.config.get().eventHandlers?.resize&&console.warn("Canvas Tile Engine: eventHandlers.resize is ignored when responsive mode is enabled. Resizing is handled automatically."),this.responsiveWatcher=new k(this.canvasWrapper,this.canvas,this.camera,this.viewport,this.config,()=>this.render()),this.responsiveWatcher.onResize=()=>{this.onResize&&this.onResize()},this.responsiveWatcher.start()):this.config.get().eventHandlers?.resize&&(this.resizeWatcher=new A(this.canvasWrapper,this.canvas,this.viewport,this.camera,this.config,()=>this.render()),this.resizeWatcher.onResize=()=>{this.onResize&&this.onResize()},this.resizeWatcher.start()))}normalizePointer(e){let t=this.canvas.getBoundingClientRect();return{x:e.clientX-t.left,y:e.clientY-t.top,clientX:e.clientX,clientY:e.clientY}}normalizeTouches(e){return Array.from(e).map(t=>this.normalizePointer(t))}handleClick=e=>{this.gestureProcessor.handleClick(this.normalizePointer(e))};handleContextMenu=e=>{e.preventDefault(),this.gestureProcessor.handleRightClick(this.normalizePointer(e))};handleMouseDown=e=>{this.gestureProcessor.handlePointerDown(this.normalizePointer(e))};handleMouseMove=e=>{this.gestureProcessor.handlePointerMove(this.normalizePointer(e))};handleMouseUp=e=>{this.gestureProcessor.handlePointerUp(this.normalizePointer(e))};handleMouseLeave=e=>{this.gestureProcessor.handlePointerLeave(this.normalizePointer(e))};handleWheel=e=>{e.preventDefault(),this.gestureProcessor.handleWheel(this.normalizePointer(e),e.deltaY)};handleTouchStart=e=>{e.preventDefault(),this.gestureProcessor.handleTouchStart(this.normalizeTouches(e.touches))};handleTouchMove=e=>{e.preventDefault(),this.gestureProcessor.handleTouchMove(this.normalizeTouches(e.touches))};handleTouchEnd=e=>{e.preventDefault();let t=this.normalizeTouches(e.touches),s=e.changedTouches.length>0?this.normalizePointer(e.changedTouches[0]):void 0;this.gestureProcessor.handleTouchEnd(t,s)};getDrawAPI(){return this.drawAPI}getImageLoader(){return this.imageLoader}render(){let e=this.viewport.getSize(),t=this.viewport.dpr,s={...this.config.get(),size:{...e},scale:this.camera.scale},r={x:this.camera.x,y:this.camera.y};this.canvasContext.setTransform(t,0,0,t,0,0),this.canvasContext.clearRect(0,0,s.size.width,s.size.height),this.canvasContext.fillStyle=s.backgroundColor,this.canvasContext.fillRect(0,0,s.size.width,s.size.height),this.layers.drawAll({ctx:this.canvasContext,camera:this.camera,transformer:this.transformer,config:s,topLeft:r}),this.onDraw?.(this.canvasContext,{scale:this.camera.scale,width:s.size.width,height:s.size.height,coords:r}),this.coordinateOverlayRenderer.shouldDraw(this.camera.scale)&&this.coordinateOverlayRenderer.draw(),s.debug?.enabled&&this.debugOverlay&&(s.debug?.hud?.fps&&(this.debugOverlay.setFpsUpdateCallback(()=>this.render()),this.debugOverlay.startFpsLoop()),this.debugOverlay.draw())}resize(e,t){let s=this.viewport.dpr;this.viewport.setSize(e,t),this.canvas.width=e*s,this.canvas.height=t*s,this.canvas.style.width=`${e}px`,this.canvas.style.height=`${t}px`,this.canvasContext.setTransform(s,0,0,s,0,0)}resizeWithAnimation(e,t,s,r){if(this.config.get().responsive){console.warn("Canvas Tile Engine: resizeWithAnimation() is disabled when responsive mode is enabled. Canvas size is controlled by the wrapper element.");return}this.sizeController.resizeWithAnimation(e,t,s,this.animationController,()=>{this.onResize?.(),r?.()})}destroy(){this.eventsAttached&&(this.eventBinder.detach(),this.eventsAttached=!1),this.resizeWatcher?.stop(),this.resizeWatcher=void 0,this.responsiveWatcher?.stop(),this.responsiveWatcher=void 0,this.animationController.cancelAll(),this.drawAPI.destroy(),this.layers.clear(),this.debugOverlay?.destroy(),this.imageLoader.clear()}applyCanvasSize(){let e=this.viewport.getSize(),t=this.viewport.dpr;this.canvas.width=e.width*t,this.canvas.height=e.height*t,this.canvas.style.width=`${e.width}px`,this.canvas.style.height=`${e.height}px`,this.canvasContext.setTransform(t,0,0,t,0,0)}};0&&(module.exports={RendererCanvas});
|
|
2
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/RendererCanvas.ts","../src/modules/CanvasDraw.ts","../src/utils/canvas.ts","../src/modules/Layer.ts","../src/modules/CoordinateOverlayRenderer.ts","../src/modules/CanvasDebug.ts","../src/modules/EventBinder.ts","../src/modules/ResizeWatcher.ts","../src/modules/ResponsiveWatcher.ts","../src/modules/ImageLoader.ts","../src/modules/SizeController.ts"],"sourcesContent":["// Main renderer export\nexport { RendererCanvas } from \"./RendererCanvas\";\n\n// Types only - internal classes not exported\nexport type { DrawHandle } from \"./modules/Layer\";\n","import {\n AnimationController,\n Config,\n CoordinateTransformer,\n Coords,\n GestureProcessor,\n ICamera,\n IDrawAPI,\n IImageLoader,\n IRenderer,\n NormalizedPointer,\n onClickCallback,\n onDrawCallback,\n onHoverCallback,\n onMouseDownCallback,\n onMouseLeaveCallback,\n onMouseUpCallback,\n onRightClickCallback,\n onZoomCallback,\n RendererDependencies,\n ViewportState,\n} from \"@canvas-tile-engine/core\";\nimport { CanvasDraw } from \"./modules/CanvasDraw\";\nimport { Layer } from \"./modules/Layer\";\nimport { CoordinateOverlayRenderer } from \"./modules/CoordinateOverlayRenderer\";\nimport { CanvasDebug } from \"./modules/CanvasDebug\";\nimport { EventBinder } from \"./modules/EventBinder\";\nimport { ResizeWatcher } from \"./modules/ResizeWatcher\";\nimport { ResponsiveWatcher } from \"./modules/ResponsiveWatcher\";\nimport { ImageLoader } from \"./modules/ImageLoader\";\nimport { SizeController } from \"./modules/SizeController\";\nimport { initStyles } from \"./utils/canvas\";\n\nexport class RendererCanvas implements IRenderer {\n //\n private canvasWrapper!: HTMLDivElement;\n private canvas!: HTMLCanvasElement;\n private canvasContext!: CanvasRenderingContext2D;\n private camera!: ICamera;\n private config!: Config;\n private viewport!: ViewportState;\n private layers!: Layer;\n private drawAPI!: CanvasDraw;\n private transformer!: CoordinateTransformer;\n private coordinateOverlayRenderer!: CoordinateOverlayRenderer;\n private debugOverlay?: CanvasDebug;\n\n // Event handling\n private gestureProcessor!: GestureProcessor;\n private eventBinder!: EventBinder;\n private resizeWatcher?: ResizeWatcher;\n private responsiveWatcher?: ResponsiveWatcher;\n private eventsAttached = false;\n\n // Size control\n private sizeController!: SizeController;\n private animationController!: AnimationController;\n\n // Image loading\n private imageLoader = new ImageLoader();\n\n /** Optional user-provided draw hook executed after engine layers. */\n public onDraw?: onDrawCallback;\n\n /** Optional callback fired when canvas is resized. */\n public onResize?: () => void;\n\n // ─── Callback Getters/Setters (proxy to GestureProcessor) ───\n\n get onClick(): onClickCallback | undefined {\n return this.gestureProcessor?.onClick;\n }\n set onClick(cb: onClickCallback | undefined) {\n if (this.gestureProcessor) this.gestureProcessor.onClick = cb;\n }\n\n get onRightClick(): onRightClickCallback | undefined {\n return this.gestureProcessor?.onRightClick;\n }\n set onRightClick(cb: onRightClickCallback | undefined) {\n if (this.gestureProcessor) this.gestureProcessor.onRightClick = cb;\n }\n\n get onHover(): onHoverCallback | undefined {\n return this.gestureProcessor?.onHover;\n }\n set onHover(cb: onHoverCallback | undefined) {\n if (this.gestureProcessor) this.gestureProcessor.onHover = cb;\n }\n\n get onMouseDown(): onMouseDownCallback | undefined {\n return this.gestureProcessor?.onMouseDown;\n }\n set onMouseDown(cb: onMouseDownCallback | undefined) {\n if (this.gestureProcessor) this.gestureProcessor.onMouseDown = cb;\n }\n\n get onMouseUp(): onMouseUpCallback | undefined {\n return this.gestureProcessor?.onMouseUp;\n }\n set onMouseUp(cb: onMouseUpCallback | undefined) {\n if (this.gestureProcessor) this.gestureProcessor.onMouseUp = cb;\n }\n\n get onMouseLeave(): onMouseLeaveCallback | undefined {\n return this.gestureProcessor?.onMouseLeave;\n }\n set onMouseLeave(cb: onMouseLeaveCallback | undefined) {\n if (this.gestureProcessor) this.gestureProcessor.onMouseLeave = cb;\n }\n\n get onZoom(): onZoomCallback | undefined {\n return this.gestureProcessor?.onZoom;\n }\n set onZoom(cb: onZoomCallback | undefined) {\n if (this.gestureProcessor) this.gestureProcessor.onZoom = cb;\n }\n\n /** Callback fired when camera position changes (drag/zoom). */\n public onCameraChange?: () => void;\n\n init(deps: RendererDependencies) {\n this.config = deps.config;\n // Initialize canvas\n this.canvasWrapper = deps.wrapper;\n this.canvas = this.canvasWrapper.querySelector(\"canvas\") as HTMLCanvasElement;\n if (!this.canvas) {\n throw new Error(\"Canvas element not found in wrapper\");\n }\n\n initStyles(\n this.canvasWrapper,\n this.canvas,\n this.config.get().responsive,\n this.config.get().size.width,\n this.config.get().size.height\n );\n\n this.canvasContext = this.canvas.getContext(\"2d\")!;\n\n if (!this.canvasContext) {\n throw new Error(\"Failed to get 2D canvas context\");\n }\n\n this.transformer = deps.transformer;\n this.viewport = deps.viewport;\n this.camera = deps.camera;\n this.layers = new Layer();\n this.drawAPI = new CanvasDraw(this.layers, deps.transformer, deps.camera);\n\n this.applyCanvasSize();\n\n this.coordinateOverlayRenderer = new CoordinateOverlayRenderer(\n this.canvasContext,\n this.camera,\n this.config,\n this.viewport\n );\n\n if (this.config.get().debug?.enabled) {\n this.debugOverlay = new CanvasDebug(this.canvasContext, this.camera, this.config, this.viewport);\n // Start FPS loop if fps hud is enabled\n if (this.config.get().debug?.hud?.fps) {\n this.debugOverlay.setFpsUpdateCallback(() => this.render());\n this.debugOverlay.startFpsLoop();\n }\n }\n\n // Initialize GestureProcessor\n this.gestureProcessor = new GestureProcessor(\n this.camera,\n this.config,\n this.transformer,\n () => this.canvas.getBoundingClientRect(),\n () => {\n this.onCameraChange?.();\n this.render();\n }\n );\n\n // Initialize EventBinder with normalized handlers\n this.eventBinder = new EventBinder(this.canvas, {\n click: this.handleClick,\n contextmenu: this.handleContextMenu,\n mousedown: this.handleMouseDown,\n mousemove: this.handleMouseMove,\n mouseup: this.handleMouseUp,\n mouseleave: this.handleMouseLeave,\n wheel: this.handleWheel,\n touchstart: this.handleTouchStart,\n touchmove: this.handleTouchMove,\n touchend: this.handleTouchEnd,\n });\n\n // Initialize AnimationController and SizeController\n this.animationController = new AnimationController(this.camera, this.viewport, () => this.render());\n this.sizeController = new SizeController(\n this.canvasWrapper,\n this.canvas,\n this.camera,\n this.viewport,\n this.config,\n () => this.render()\n );\n }\n\n // ─── Event Setup ───\n\n setupEvents(): void {\n if (this.eventsAttached) return;\n this.eventBinder.attach();\n this.eventsAttached = true;\n\n // Setup responsive or resize watcher based on config\n if (this.config.get().responsive) {\n // Responsive mode - use ResponsiveWatcher\n if (this.config.get().eventHandlers?.resize) {\n console.warn(\n \"Canvas Tile Engine: eventHandlers.resize is ignored when responsive mode is enabled. \" +\n \"Resizing is handled automatically.\"\n );\n }\n this.responsiveWatcher = new ResponsiveWatcher(\n this.canvasWrapper,\n this.canvas,\n this.camera,\n this.viewport,\n this.config,\n () => this.render()\n );\n this.responsiveWatcher.onResize = () => {\n if (this.onResize) {\n this.onResize();\n }\n };\n this.responsiveWatcher.start();\n } else if (this.config.get().eventHandlers?.resize) {\n // Non-responsive mode with resize enabled - use ResizeWatcher\n this.resizeWatcher = new ResizeWatcher(\n this.canvasWrapper,\n this.canvas,\n this.viewport,\n this.camera,\n this.config,\n () => this.render()\n );\n this.resizeWatcher.onResize = () => {\n if (this.onResize) {\n this.onResize();\n }\n };\n this.resizeWatcher.start();\n }\n }\n\n // ─── Normalize Helpers ───\n\n private normalizePointer(e: MouseEvent | Touch): NormalizedPointer {\n const rect = this.canvas.getBoundingClientRect();\n return {\n x: e.clientX - rect.left,\n y: e.clientY - rect.top,\n clientX: e.clientX,\n clientY: e.clientY,\n };\n }\n\n private normalizeTouches(touches: TouchList): NormalizedPointer[] {\n return Array.from(touches).map((t) => this.normalizePointer(t));\n }\n\n // ─── Event Handlers (DOM → Normalize → GestureProcessor) ───\n\n private handleClick = (e: MouseEvent): void => {\n this.gestureProcessor.handleClick(this.normalizePointer(e));\n };\n\n private handleContextMenu = (e: MouseEvent): void => {\n e.preventDefault();\n this.gestureProcessor.handleRightClick(this.normalizePointer(e));\n };\n\n private handleMouseDown = (e: MouseEvent): void => {\n this.gestureProcessor.handlePointerDown(this.normalizePointer(e));\n };\n\n private handleMouseMove = (e: MouseEvent): void => {\n this.gestureProcessor.handlePointerMove(this.normalizePointer(e));\n };\n\n private handleMouseUp = (e: MouseEvent): void => {\n this.gestureProcessor.handlePointerUp(this.normalizePointer(e));\n };\n\n private handleMouseLeave = (e: MouseEvent): void => {\n this.gestureProcessor.handlePointerLeave(this.normalizePointer(e));\n };\n\n private handleWheel = (e: WheelEvent): void => {\n e.preventDefault();\n this.gestureProcessor.handleWheel(this.normalizePointer(e), e.deltaY);\n };\n\n private handleTouchStart = (e: TouchEvent): void => {\n e.preventDefault();\n this.gestureProcessor.handleTouchStart(this.normalizeTouches(e.touches));\n };\n\n private handleTouchMove = (e: TouchEvent): void => {\n e.preventDefault();\n this.gestureProcessor.handleTouchMove(this.normalizeTouches(e.touches));\n };\n\n private handleTouchEnd = (e: TouchEvent): void => {\n e.preventDefault();\n const remaining = this.normalizeTouches(e.touches);\n const changed = e.changedTouches.length > 0 ? this.normalizePointer(e.changedTouches[0]) : undefined;\n this.gestureProcessor.handleTouchEnd(remaining, changed);\n };\n\n getDrawAPI(): IDrawAPI {\n return this.drawAPI;\n }\n\n getImageLoader(): IImageLoader<HTMLImageElement> {\n return this.imageLoader;\n }\n\n render(): void {\n const size = this.viewport.getSize();\n const dpr = this.viewport.dpr;\n const config = { ...this.config.get(), size: { ...size }, scale: this.camera.scale };\n const topLeft: Coords = { x: this.camera.x, y: this.camera.y };\n\n // Reset transform for HiDPI support (canvas.width/height changes reset transform)\n this.canvasContext.setTransform(dpr, 0, 0, dpr, 0, 0);\n\n // Clear background\n this.canvasContext.clearRect(0, 0, config.size.width, config.size.height);\n this.canvasContext.fillStyle = config.backgroundColor;\n this.canvasContext.fillRect(0, 0, config.size.width, config.size.height);\n\n // Draw engine layers\n this.layers.drawAll({\n ctx: this.canvasContext,\n camera: this.camera,\n transformer: this.transformer,\n config,\n topLeft,\n });\n\n // User custom draw callback (optional)\n this.onDraw?.(this.canvasContext, {\n scale: this.camera.scale,\n width: config.size.width,\n height: config.size.height,\n coords: topLeft,\n });\n\n // Coordinate overlay\n if (this.coordinateOverlayRenderer.shouldDraw(this.camera.scale)) {\n this.coordinateOverlayRenderer.draw();\n }\n\n // Debug overlay\n if (config.debug?.enabled && this.debugOverlay) {\n if (config.debug?.hud?.fps) {\n this.debugOverlay.setFpsUpdateCallback(() => this.render());\n this.debugOverlay.startFpsLoop();\n }\n this.debugOverlay.draw();\n }\n }\n\n resize(width: number, height: number): void {\n const dpr = this.viewport.dpr;\n\n this.viewport.setSize(width, height);\n\n // Set actual canvas resolution (physical pixels)\n this.canvas.width = width * dpr;\n this.canvas.height = height * dpr;\n\n // Set display size via CSS (logical pixels)\n this.canvas.style.width = `${width}px`;\n this.canvas.style.height = `${height}px`;\n\n // Scale context to match DPR\n this.canvasContext.setTransform(dpr, 0, 0, dpr, 0, 0);\n }\n\n resizeWithAnimation(width: number, height: number, durationMs: number, onComplete?: () => void): void {\n if (this.config.get().responsive) {\n console.warn(\n \"Canvas Tile Engine: resizeWithAnimation() is disabled when responsive mode is enabled. \" +\n \"Canvas size is controlled by the wrapper element.\"\n );\n return;\n }\n this.sizeController.resizeWithAnimation(width, height, durationMs, this.animationController, () => {\n // Trigger onResize callback after programmatic resize completes\n this.onResize?.();\n onComplete?.();\n });\n }\n\n destroy(): void {\n // Detach events\n if (this.eventsAttached) {\n this.eventBinder.detach();\n this.eventsAttached = false;\n }\n this.resizeWatcher?.stop();\n this.resizeWatcher = undefined;\n this.responsiveWatcher?.stop();\n this.responsiveWatcher = undefined;\n\n // Cancel animations\n this.animationController.cancelAll();\n\n // Cleanup drawing\n this.drawAPI.destroy();\n this.layers.clear();\n this.debugOverlay?.destroy();\n this.imageLoader.clear();\n }\n\n private applyCanvasSize() {\n const size = this.viewport.getSize();\n const dpr = this.viewport.dpr;\n\n // Set actual canvas resolution (physical pixels)\n this.canvas.width = size.width * dpr;\n this.canvas.height = size.height * dpr;\n\n // Set display size via CSS (logical pixels)\n this.canvas.style.width = `${size.width}px`;\n this.canvas.style.height = `${size.height}px`;\n\n // Scale context to match DPR\n this.canvasContext.setTransform(dpr, 0, 0, dpr, 0, 0);\n }\n}\n","import {\n CanvasTileEngineConfig,\n Circle,\n CoordinateTransformer,\n Coords,\n DEFAULT_VALUES,\n DrawHandle,\n ICamera,\n ImageItem,\n Line,\n Path,\n Rect,\n SpatialIndex,\n Text,\n VISIBILITY_BUFFER,\n} from \"@canvas-tile-engine/core\";\nimport { Layer } from \"./Layer\";\nimport { applyLineWidth } from \"../utils/canvas\";\n\n// Threshold for using spatial indexing (below this, linear scan is faster)\nconst SPATIAL_INDEX_THRESHOLD = 500;\n// Conservative max dimension for offscreen static cache (browser limits often 16384 or 32767)\nconst MAX_STATIC_CANVAS_DIMENSION = 16384;\n\n// Cache for static layers (pre-rendered offscreen canvases)\ninterface StaticCache {\n canvas: OffscreenCanvas | HTMLCanvasElement;\n ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D;\n worldBounds: { minX: number; minY: number; maxX: number; maxY: number };\n scale: number;\n}\n\n/**\n * Canvas-specific helpers for adding draw callbacks to the layer stack.\n * @internal\n */\nexport class CanvasDraw {\n private staticCaches = new Map<string, StaticCache>();\n private staticCacheSupported: boolean;\n private warnedStaticCacheDisabled = false;\n\n constructor(private layers: Layer, private transformer: CoordinateTransformer, private camera: ICamera) {\n this.staticCacheSupported = typeof OffscreenCanvas !== \"undefined\" || typeof document !== \"undefined\";\n }\n\n /**\n * Register a generic draw callback; receives raw context, current coords, and config.\n * @param fn Callback invoked during render.\n * @param layer Layer order (lower draws first).\n */\n\n private isVisible(\n x: number,\n y: number,\n sizeWorld: number,\n topLeft: Coords,\n config: Required<CanvasTileEngineConfig>\n ) {\n const viewW = config.size.width / config.scale;\n const viewH = config.size.height / config.scale;\n const minX = topLeft.x - VISIBILITY_BUFFER.TILE_BUFFER;\n const minY = topLeft.y - VISIBILITY_BUFFER.TILE_BUFFER;\n const maxX = topLeft.x + viewW + VISIBILITY_BUFFER.TILE_BUFFER;\n const maxY = topLeft.y + viewH + VISIBILITY_BUFFER.TILE_BUFFER;\n return x + sizeWorld >= minX && x - sizeWorld <= maxX && y + sizeWorld >= minY && y - sizeWorld <= maxY;\n }\n\n private getViewportBounds(topLeft: Coords, config: Required<CanvasTileEngineConfig>) {\n const viewW = config.size.width / config.scale;\n const viewH = config.size.height / config.scale;\n return {\n minX: topLeft.x - VISIBILITY_BUFFER.TILE_BUFFER,\n minY: topLeft.y - VISIBILITY_BUFFER.TILE_BUFFER,\n maxX: topLeft.x + viewW + VISIBILITY_BUFFER.TILE_BUFFER,\n maxY: topLeft.y + viewH + VISIBILITY_BUFFER.TILE_BUFFER,\n };\n }\n\n addDrawFunction(\n fn: (ctx: CanvasRenderingContext2D, coords: Coords, config: Required<CanvasTileEngineConfig>) => void,\n layer: number = 1\n ): DrawHandle {\n return this.layers.add(layer, ({ ctx, config, topLeft }) => {\n fn(ctx, topLeft, config);\n });\n }\n\n drawRect(items: Array<Rect> | Rect, layer: number = 1): DrawHandle {\n const list = Array.isArray(items) ? items : [items];\n\n // Build spatial index for large datasets (RBush R-Tree)\n const useSpatialIndex = list.length > SPATIAL_INDEX_THRESHOLD;\n const spatialIndex = useSpatialIndex ? SpatialIndex.fromArray(list) : null;\n\n return this.layers.add(layer, ({ ctx, config, topLeft }) => {\n const bounds = this.getViewportBounds(topLeft, config);\n const visibleItems = spatialIndex\n ? spatialIndex.query(bounds.minX, bounds.minY, bounds.maxX, bounds.maxY)\n : list;\n\n ctx.save();\n let lastFillStyle: string | undefined;\n let lastStrokeStyle: string | undefined;\n let lastLineWidth: number | undefined;\n\n for (const item of visibleItems) {\n const size = item.size ?? 1;\n const origin = {\n mode: item.origin?.mode === \"self\" ? \"self\" : (\"cell\" as \"cell\" | \"self\"),\n x: item.origin?.x ?? 0.5,\n y: item.origin?.y ?? 0.5,\n };\n const style = item.style;\n\n // Skip visibility check if using spatial index (already filtered)\n if (!spatialIndex && !this.isVisible(item.x, item.y, size / 2, topLeft, config)) continue;\n\n const pos = this.transformer.worldToScreen(item.x, item.y);\n const pxSize = size * this.camera.scale;\n const { x: drawX, y: drawY } = this.computeOriginOffset(pos, pxSize, origin, this.camera);\n\n // Only update style when changed (reduces state changes)\n if (style?.fillStyle && style.fillStyle !== lastFillStyle) {\n ctx.fillStyle = style.fillStyle;\n lastFillStyle = style.fillStyle;\n }\n if (style?.strokeStyle && style.strokeStyle !== lastStrokeStyle) {\n ctx.strokeStyle = style.strokeStyle;\n lastStrokeStyle = style.strokeStyle;\n }\n\n let resetAlpha: (() => void) | undefined;\n if (style?.lineWidth && style.lineWidth !== lastLineWidth) {\n resetAlpha = applyLineWidth(ctx, style.lineWidth);\n lastLineWidth = style.lineWidth;\n }\n\n const rotationDeg = item.rotate ?? 0;\n const rotation = rotationDeg * (Math.PI / 180);\n\n const radius = item.radius;\n\n if (rotationDeg !== 0) {\n const centerX = drawX + pxSize / 2;\n const centerY = drawY + pxSize / 2;\n ctx.save();\n ctx.translate(centerX, centerY);\n ctx.rotate(rotation);\n ctx.beginPath();\n if (radius && ctx.roundRect) {\n ctx.roundRect(-pxSize / 2, -pxSize / 2, pxSize, pxSize, radius);\n } else {\n ctx.rect(-pxSize / 2, -pxSize / 2, pxSize, pxSize);\n }\n if (style?.fillStyle) ctx.fill();\n if (style?.strokeStyle) ctx.stroke();\n ctx.restore();\n } else {\n ctx.beginPath();\n if (radius && ctx.roundRect) {\n ctx.roundRect(drawX, drawY, pxSize, pxSize, radius);\n } else {\n ctx.rect(drawX, drawY, pxSize, pxSize);\n }\n if (style?.fillStyle) ctx.fill();\n if (style?.strokeStyle) ctx.stroke();\n }\n\n resetAlpha?.();\n }\n ctx.restore();\n });\n }\n\n drawLine(\n items: Array<Line> | Line,\n style?: { strokeStyle?: string; lineWidth?: number },\n layer: number = 1\n ): DrawHandle {\n const list = Array.isArray(items) ? items : [items];\n\n return this.layers.add(layer, ({ ctx, config, topLeft }) => {\n ctx.save();\n if (style?.strokeStyle) ctx.strokeStyle = style.strokeStyle;\n\n const resetAlpha = style?.lineWidth ? applyLineWidth(ctx, style.lineWidth) : undefined;\n\n ctx.beginPath();\n for (const item of list) {\n const centerX = (item.from.x + item.to.x) / 2;\n const centerY = (item.from.y + item.to.y) / 2;\n const halfExtent = Math.max(Math.abs(item.from.x - item.to.x), Math.abs(item.from.y - item.to.y)) / 2;\n if (!this.isVisible(centerX, centerY, halfExtent, topLeft, config)) continue;\n\n const a = this.transformer.worldToScreen(item.from.x, item.from.y);\n const b = this.transformer.worldToScreen(item.to.x, item.to.y);\n\n ctx.moveTo(a.x, a.y);\n ctx.lineTo(b.x, b.y);\n }\n ctx.stroke();\n\n resetAlpha?.();\n ctx.restore();\n });\n }\n\n drawCircle(items: Array<Circle> | Circle, layer: number = 1): DrawHandle {\n const list = Array.isArray(items) ? items : [items];\n\n // Build spatial index for large datasets (RBush R-Tree)\n const useSpatialIndex = list.length > SPATIAL_INDEX_THRESHOLD;\n const spatialIndex = useSpatialIndex ? SpatialIndex.fromArray(list) : null;\n\n return this.layers.add(layer, ({ ctx, config, topLeft }) => {\n const bounds = this.getViewportBounds(topLeft, config);\n const visibleItems = spatialIndex\n ? spatialIndex.query(bounds.minX, bounds.minY, bounds.maxX, bounds.maxY)\n : list;\n\n ctx.save();\n let lastFillStyle: string | undefined;\n let lastStrokeStyle: string | undefined;\n let lastLineWidth: number | undefined;\n\n for (const item of visibleItems) {\n const size = item.size ?? 1;\n const origin = {\n mode: item.origin?.mode === \"self\" ? \"self\" : (\"cell\" as \"cell\" | \"self\"),\n x: item.origin?.x ?? 0.5,\n y: item.origin?.y ?? 0.5,\n };\n const style = item.style;\n\n // Skip visibility check if using spatial index (already filtered)\n if (!spatialIndex && !this.isVisible(item.x, item.y, size / 2, topLeft, config)) continue;\n\n const pos = this.transformer.worldToScreen(item.x, item.y);\n const pxSize = size * this.camera.scale;\n const radius = pxSize / 2;\n const { x: drawX, y: drawY } = this.computeOriginOffset(pos, pxSize, origin, this.camera);\n\n // Only update style when changed\n if (style?.fillStyle && style.fillStyle !== lastFillStyle) {\n ctx.fillStyle = style.fillStyle;\n lastFillStyle = style.fillStyle;\n }\n if (style?.strokeStyle && style.strokeStyle !== lastStrokeStyle) {\n ctx.strokeStyle = style.strokeStyle;\n lastStrokeStyle = style.strokeStyle;\n }\n\n let resetAlpha: (() => void) | undefined;\n if (style?.lineWidth && style.lineWidth !== lastLineWidth) {\n resetAlpha = applyLineWidth(ctx, style.lineWidth);\n lastLineWidth = style.lineWidth;\n }\n\n ctx.beginPath();\n ctx.arc(drawX + radius, drawY + radius, radius, 0, Math.PI * 2);\n if (style?.fillStyle) ctx.fill();\n if (style?.strokeStyle) ctx.stroke();\n\n resetAlpha?.();\n }\n ctx.restore();\n });\n }\n\n drawText(items: Array<Text> | Text, layer: number = 2): DrawHandle {\n const list = Array.isArray(items) ? items : [items];\n\n // Build spatial index for large datasets (RBush R-Tree)\n const useSpatialIndex = list.length > SPATIAL_INDEX_THRESHOLD;\n const spatialIndex = useSpatialIndex ? SpatialIndex.fromArray(list) : null;\n\n return this.layers.add(layer, ({ ctx, config, topLeft }) => {\n const bounds = this.getViewportBounds(topLeft, config);\n const visibleItems = spatialIndex\n ? spatialIndex.query(bounds.minX, bounds.minY, bounds.maxX, bounds.maxY)\n : list;\n\n ctx.save();\n\n for (const item of visibleItems) {\n const size = item.size ?? 1;\n const style = item.style;\n\n // Skip visibility check if using spatial index (already filtered)\n if (!spatialIndex && !this.isVisible(item.x, item.y, size, topLeft, config)) continue;\n\n // Scale-aware font size (world units)\n const pxSize = size * this.camera.scale * 0.3;\n const family = style?.fontFamily ?? \"sans-serif\";\n ctx.font = `${pxSize}px ${family}`;\n\n if (style?.fillStyle) ctx.fillStyle = style.fillStyle;\n ctx.textAlign = style?.textAlign ?? \"center\";\n ctx.textBaseline = style?.textBaseline ?? \"middle\";\n\n const pos = this.transformer.worldToScreen(item.x, item.y);\n\n const rotationDeg = item.rotate ?? 0;\n if (rotationDeg !== 0) {\n const rotation = rotationDeg * (Math.PI / 180);\n ctx.save();\n ctx.translate(pos.x, pos.y);\n ctx.rotate(rotation);\n ctx.fillText(item.text, 0, 0);\n ctx.restore();\n } else {\n ctx.fillText(item.text, pos.x, pos.y);\n }\n }\n ctx.restore();\n });\n }\n\n drawPath(\n items: Array<Path> | Path,\n style?: { strokeStyle?: string; lineWidth?: number },\n layer: number = 1\n ): DrawHandle {\n const list = Array.isArray(items[0]) ? (items as Array<Coords[]>) : [items as Coords[]];\n\n return this.layers.add(layer, ({ ctx, config, topLeft }) => {\n ctx.save();\n if (style?.strokeStyle) ctx.strokeStyle = style.strokeStyle;\n\n const resetAlpha = style?.lineWidth ? applyLineWidth(ctx, style.lineWidth) : undefined;\n\n ctx.beginPath();\n for (const points of list) {\n if (!points.length) continue;\n const xs = points.map((p) => p.x);\n const ys = points.map((p) => p.y);\n const minX = Math.min(...xs);\n const maxX = Math.max(...xs);\n const minY = Math.min(...ys);\n const maxY = Math.max(...ys);\n const centerX = (minX + maxX) / 2;\n const centerY = (minY + maxY) / 2;\n const halfExtent = Math.max(maxX - minX, maxY - minY) / 2;\n if (!this.isVisible(centerX, centerY, halfExtent, topLeft, config)) continue;\n\n const first = this.transformer.worldToScreen(points[0].x, points[0].y);\n ctx.moveTo(first.x, first.y);\n\n for (let i = 1; i < points.length; i++) {\n const p = this.transformer.worldToScreen(points[i].x, points[i].y);\n ctx.lineTo(p.x, p.y);\n }\n }\n ctx.stroke();\n\n resetAlpha?.();\n ctx.restore();\n });\n }\n\n drawImage(items: Array<ImageItem> | ImageItem, layer: number = 1): DrawHandle {\n const list = Array.isArray(items) ? items : [items];\n\n // Build spatial index for large datasets (RBush R-Tree)\n const useSpatialIndex = list.length > SPATIAL_INDEX_THRESHOLD;\n const spatialIndex = useSpatialIndex ? SpatialIndex.fromArray(list) : null;\n\n return this.layers.add(layer, ({ ctx, config, topLeft }) => {\n const bounds = this.getViewportBounds(topLeft, config);\n const visibleItems = spatialIndex\n ? spatialIndex.query(bounds.minX, bounds.minY, bounds.maxX, bounds.maxY)\n : list;\n\n for (const item of visibleItems) {\n const size = item.size ?? 1;\n const origin = {\n mode: item.origin?.mode === \"self\" ? \"self\" : (\"cell\" as \"cell\" | \"self\"),\n x: item.origin?.x ?? 0.5,\n y: item.origin?.y ?? 0.5,\n };\n\n // Skip visibility check if using spatial index (already filtered)\n if (!spatialIndex && !this.isVisible(item.x, item.y, size / 2, topLeft, config)) continue;\n\n const pos = this.transformer.worldToScreen(item.x, item.y);\n const pxSize = size * this.camera.scale;\n\n // preserve aspect\n const aspect = item.img.width / item.img.height;\n\n let drawW = pxSize;\n let drawH = pxSize;\n\n if (aspect > 1) drawH = pxSize / aspect;\n else drawW = pxSize * aspect;\n\n // origin SELF/CELL\n const { x: baseX, y: baseY } = this.computeOriginOffset(pos, pxSize, origin, this.camera);\n\n const offsetX = baseX + (pxSize - drawW) / 2;\n const offsetY = baseY + (pxSize - drawH) / 2;\n\n const rotationDeg = item.rotate ?? 0;\n const rotation = rotationDeg * (Math.PI / 180);\n\n if (rotationDeg !== 0) {\n const centerX = offsetX + drawW / 2;\n const centerY = offsetY + drawH / 2;\n ctx.save();\n ctx.translate(centerX, centerY);\n ctx.rotate(rotation);\n ctx.drawImage(item.img, -drawW / 2, -drawH / 2, drawW, drawH);\n ctx.restore();\n } else {\n ctx.drawImage(item.img, offsetX, offsetY, drawW, drawH);\n }\n }\n });\n }\n\n drawGridLines(cellSize: number, style: { strokeStyle: string; lineWidth: number }, layer: number = 0): DrawHandle {\n return this.layers.add(layer, ({ ctx, config, topLeft }) => {\n const viewW = config.size.width / config.scale;\n const viewH = config.size.height / config.scale;\n\n const startX = Math.floor(topLeft.x / cellSize) * cellSize - DEFAULT_VALUES.CELL_CENTER_OFFSET;\n const endX = Math.ceil((topLeft.x + viewW) / cellSize) * cellSize - DEFAULT_VALUES.CELL_CENTER_OFFSET;\n const startY = Math.floor(topLeft.y / cellSize) * cellSize - DEFAULT_VALUES.CELL_CENTER_OFFSET;\n const endY = Math.ceil((topLeft.y + viewH) / cellSize) * cellSize - DEFAULT_VALUES.CELL_CENTER_OFFSET;\n\n ctx.save();\n\n ctx.strokeStyle = style.strokeStyle;\n const resetAlpha = applyLineWidth(ctx, style.lineWidth);\n\n ctx.beginPath();\n\n for (let x = startX; x <= endX; x += cellSize) {\n const p1 = this.transformer.worldToScreen(x, startY);\n const p2 = this.transformer.worldToScreen(x, endY);\n ctx.moveTo(p1.x, p1.y);\n ctx.lineTo(p2.x, p2.y);\n }\n\n for (let y = startY; y <= endY; y += cellSize) {\n const p1 = this.transformer.worldToScreen(startX, y);\n const p2 = this.transformer.worldToScreen(endX, y);\n ctx.moveTo(p1.x, p1.y);\n ctx.lineTo(p2.x, p2.y);\n }\n\n ctx.stroke();\n resetAlpha();\n ctx.restore();\n });\n }\n\n private computeOriginOffset(\n pos: Coords,\n pxSize: number,\n origin: { mode: \"cell\" | \"self\"; x: number; y: number },\n camera: ICamera\n ) {\n if (origin.mode === \"cell\") {\n const cell = camera.scale;\n return {\n x: pos.x - cell / 2 + origin.x * cell - pxSize / 2,\n y: pos.y - cell / 2 + origin.y * cell - pxSize / 2,\n };\n }\n\n return {\n x: pos.x - origin.x * pxSize,\n y: pos.y - origin.y * pxSize,\n };\n }\n\n /**\n * Helper to create or get a static cache for pre-rendered content.\n * Handles bounds calculation, canvas creation, and rebuild logic.\n */\n private getOrCreateStaticCache<T extends { x: number; y: number; size?: number; radius?: number | number[] }>(\n items: T[],\n cacheKey: string,\n renderFn: (\n ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,\n item: T,\n x: number,\n y: number,\n pxSize: number\n ) => void\n ): StaticCache | null {\n if (!this.staticCacheSupported) {\n if (!this.warnedStaticCacheDisabled) {\n console.warn(\"[CanvasDraw] Static cache disabled: OffscreenCanvas not available.\");\n this.warnedStaticCacheDisabled = true;\n }\n return null;\n }\n\n // Calculate world bounds from items\n let minX = Infinity,\n maxX = -Infinity,\n minY = Infinity,\n maxY = -Infinity;\n\n for (const item of items) {\n const size = item.size ?? 1;\n if (item.x - size / 2 < minX) minX = item.x - size / 2;\n if (item.x + size / 2 > maxX) maxX = item.x + size / 2;\n if (item.y - size / 2 < minY) minY = item.y - size / 2;\n if (item.y + size / 2 > maxY) maxY = item.y + size / 2;\n }\n\n // Add padding\n minX -= 1;\n minY -= 1;\n maxX += 1;\n maxY += 1;\n\n const worldWidth = maxX - minX;\n const worldHeight = maxY - minY;\n\n // Use current scale for rendering\n const renderScale = this.camera.scale;\n const canvasWidth = Math.ceil(worldWidth * renderScale);\n const canvasHeight = Math.ceil(worldHeight * renderScale);\n\n if (canvasWidth > MAX_STATIC_CANVAS_DIMENSION || canvasHeight > MAX_STATIC_CANVAS_DIMENSION) {\n if (!this.warnedStaticCacheDisabled) {\n console.warn(`Static cache disabled: offscreen canvas too large (${canvasWidth}x${canvasHeight}).`);\n this.warnedStaticCacheDisabled = true;\n }\n return null;\n }\n\n // Check if we need to create or update cache\n let cache = this.staticCaches.get(cacheKey);\n const needsRebuild =\n !cache ||\n cache.scale !== renderScale ||\n cache.worldBounds.minX !== minX ||\n cache.worldBounds.maxX !== maxX ||\n cache.worldBounds.minY !== minY ||\n cache.worldBounds.maxY !== maxY;\n\n if (needsRebuild) {\n // Create offscreen canvas\n const offscreen =\n typeof OffscreenCanvas !== \"undefined\"\n ? new OffscreenCanvas(canvasWidth, canvasHeight)\n : document.createElement(\"canvas\");\n\n // Guard instanceof with typeof to avoid ReferenceError when OffscreenCanvas is undefined (e.g., jsdom)\n const isOffscreenCanvas = typeof OffscreenCanvas !== \"undefined\" && offscreen instanceof OffscreenCanvas;\n\n if (!isOffscreenCanvas) {\n (offscreen as HTMLCanvasElement).width = canvasWidth;\n (offscreen as HTMLCanvasElement).height = canvasHeight;\n }\n\n const offCtx = offscreen.getContext(\"2d\") as\n | CanvasRenderingContext2D\n | OffscreenCanvasRenderingContext2D\n | null;\n\n if (!offCtx) {\n if (!this.warnedStaticCacheDisabled) {\n console.warn(\"[CanvasDraw] Static cache disabled: 2D context unavailable.\");\n this.warnedStaticCacheDisabled = true;\n }\n return null;\n }\n\n // Render all items using the provided render function\n for (const item of items) {\n const size = item.size ?? 1;\n const pxSize = size * renderScale;\n const x = (item.x + DEFAULT_VALUES.CELL_CENTER_OFFSET - minX) * renderScale - pxSize / 2;\n const y = (item.y + DEFAULT_VALUES.CELL_CENTER_OFFSET - minY) * renderScale - pxSize / 2;\n\n renderFn(offCtx, item, x, y, pxSize);\n }\n\n cache = {\n canvas: offscreen,\n ctx: offCtx,\n worldBounds: { minX, minY, maxX, maxY },\n scale: renderScale,\n };\n\n this.staticCaches.set(cacheKey, cache);\n }\n\n return cache ?? null;\n }\n\n /**\n * Helper to add a layer callback that blits from a static cache.\n */\n private addStaticCacheLayer(cache: StaticCache | null, layer: number): DrawHandle | null {\n if (!cache) {\n return null;\n }\n const cachedCanvas = cache.canvas;\n const cachedBounds = cache.worldBounds;\n const cachedScale = cache.scale;\n\n return this.layers.add(layer, ({ ctx, config, topLeft }) => {\n const viewportWidth = config.size.width / config.scale;\n const viewportHeight = config.size.height / config.scale;\n\n // === Source Rectangle (cached canvas coordinates) ===\n // These define which region of the offscreen cache canvas to copy FROM\n // Calculated by finding viewport position relative to cache origin, then scaling to cache resolution\n let cacheSourceX = (topLeft.x - cachedBounds.minX) * cachedScale;\n let cacheSourceY = (topLeft.y - cachedBounds.minY) * cachedScale;\n let cacheSourceWidth = viewportWidth * cachedScale;\n let cacheSourceHeight = viewportHeight * cachedScale;\n\n // === Destination Rectangle (screen coordinates) ===\n // These define where to draw the copied region TO on the visible canvas\n // Note: These values get adjusted below when viewport extends beyond cached bounds\n let screenDestX = 0;\n let screenDestY = 0;\n let screenDestWidth = config.size.width;\n let screenDestHeight = config.size.height;\n\n // === Bounds Clamping ===\n // Problem: When viewport pans beyond the cached area, source coordinates become invalid\n // (negative or exceeding cache dimensions). Mobile browsers\n // fail silently or render incorrectly with out-of-bounds drawImage coordinates.\n // Solution: Clamp source rect to valid cache bounds and adjust destination rect to\n // maintain correct positioning - only draw the portion that exists in cache.\n const cacheWidth = cachedCanvas.width;\n const cacheHeight = cachedCanvas.height;\n\n // Clamp left/top: When viewport is beyond cache's top-left corner\n // Shift destination right/down to compensate for the missing cached area\n if (cacheSourceX < 0) {\n const offsetWorld = -cacheSourceX / cachedScale;\n screenDestX = offsetWorld * config.scale;\n screenDestWidth -= screenDestX;\n cacheSourceWidth += cacheSourceX;\n cacheSourceX = 0;\n }\n if (cacheSourceY < 0) {\n const offsetWorld = -cacheSourceY / cachedScale;\n screenDestY = offsetWorld * config.scale;\n screenDestHeight -= screenDestY;\n cacheSourceHeight += cacheSourceY;\n cacheSourceY = 0;\n }\n\n // Clamp right/bottom: When viewport extends past cache's bottom-right corner\n // Shrink destination to avoid stretching the cached content\n if (cacheSourceX + cacheSourceWidth > cacheWidth) {\n const excess = cacheSourceX + cacheSourceWidth - cacheWidth;\n const excessWorld = excess / cachedScale;\n cacheSourceWidth = cacheWidth - cacheSourceX;\n screenDestWidth -= excessWorld * config.scale;\n }\n if (cacheSourceY + cacheSourceHeight > cacheHeight) {\n const excess = cacheSourceY + cacheSourceHeight - cacheHeight;\n const excessWorld = excess / cachedScale;\n cacheSourceHeight = cacheHeight - cacheSourceY;\n screenDestHeight -= excessWorld * config.scale;\n }\n\n // Only draw if there's something to draw\n if (cacheSourceWidth > 0 && cacheSourceHeight > 0 && screenDestWidth > 0 && screenDestHeight > 0) {\n ctx.drawImage(\n cachedCanvas,\n cacheSourceX,\n cacheSourceY,\n cacheSourceWidth,\n cacheSourceHeight,\n screenDestX,\n screenDestY,\n screenDestWidth,\n screenDestHeight\n );\n }\n });\n }\n\n /**\n * Draw rectangles with pre-rendering cache.\n * Renders all items once to an offscreen canvas, then blits the visible portion each frame.\n * Ideal for large static datasets like mini-maps.\n * @param items Array of draw objects\n * @param cacheKey Unique key for this cache (e.g., \"minimap-items\")\n * @param layer Layer order\n */\n drawStaticRect(items: Array<Rect>, cacheKey: string, layer: number = 1): DrawHandle {\n let lastFillStyle: string | undefined;\n\n const cache = this.getOrCreateStaticCache(items, cacheKey, (ctx, item, x, y, pxSize) => {\n const style = item.style;\n const rotationDeg = item.rotate ?? 0;\n const rotation = rotationDeg * (Math.PI / 180);\n const radius = item.radius;\n\n if (style?.fillStyle && style.fillStyle !== lastFillStyle) {\n ctx.fillStyle = style.fillStyle;\n lastFillStyle = style.fillStyle;\n }\n\n if (rotationDeg !== 0) {\n const centerX = x + pxSize / 2;\n const centerY = y + pxSize / 2;\n ctx.save();\n ctx.translate(centerX, centerY);\n ctx.rotate(rotation);\n if (radius && ctx.roundRect) {\n ctx.beginPath();\n ctx.roundRect(-pxSize / 2, -pxSize / 2, pxSize, pxSize, radius);\n ctx.fill();\n } else {\n ctx.fillRect(-pxSize / 2, -pxSize / 2, pxSize, pxSize);\n }\n ctx.restore();\n } else {\n if (radius && ctx.roundRect) {\n ctx.beginPath();\n ctx.roundRect(x, y, pxSize, pxSize, radius);\n ctx.fill();\n } else {\n ctx.fillRect(x, y, pxSize, pxSize);\n }\n }\n });\n\n if (!cache) {\n return this.drawRect(items, layer);\n }\n\n return this.addStaticCacheLayer(cache, layer)!;\n }\n\n /**\n * Draw images with pre-rendering cache.\n * Renders all items once to an offscreen canvas, then blits the visible portion each frame.\n * Ideal for large static datasets like terrain tiles or static decorations.\n * @param items Array of image objects with position and HTMLImageElement\n * @param cacheKey Unique key for this cache (e.g., \"terrain-cache\")\n * @param layer Layer order\n */\n drawStaticImage(items: Array<ImageItem>, cacheKey: string, layer: number = 1): DrawHandle {\n const cache = this.getOrCreateStaticCache(items, cacheKey, (ctx, item, x, y, pxSize) => {\n const img = (item as { img: HTMLImageElement }).img;\n const rotationDeg = (item as { rotate?: number }).rotate ?? 0;\n const rotation = rotationDeg * (Math.PI / 180);\n const aspect = img.width / img.height;\n let drawW = pxSize;\n let drawH = pxSize;\n\n if (aspect > 1) drawH = pxSize / aspect;\n else drawW = pxSize * aspect;\n\n // x, y are top-left of pxSize box, need to center image within it\n const imgX = x + (pxSize - drawW) / 2;\n const imgY = y + (pxSize - drawH) / 2;\n\n if (rotationDeg !== 0) {\n const centerX = imgX + drawW / 2;\n const centerY = imgY + drawH / 2;\n ctx.save();\n ctx.translate(centerX, centerY);\n ctx.rotate(rotation);\n ctx.drawImage(img, -drawW / 2, -drawH / 2, drawW, drawH);\n ctx.restore();\n } else {\n ctx.drawImage(img, imgX, imgY, drawW, drawH);\n }\n });\n\n if (!cache) {\n return this.drawImage(items, layer);\n }\n\n return this.addStaticCacheLayer(cache, layer)!;\n }\n\n /**\n * Draw circles with pre-rendering cache.\n * Renders all items once to an offscreen canvas, then blits the visible portion each frame.\n * Ideal for large static datasets like mini-maps.\n * @param items Array of draw objects\n * @param cacheKey Unique key for this cache (e.g., \"minimap-circles\")\n * @param layer Layer order\n */\n drawStaticCircle(items: Array<Circle>, cacheKey: string, layer: number = 1): DrawHandle {\n let lastFillStyle: string | undefined;\n\n const cache = this.getOrCreateStaticCache(items, cacheKey, (ctx, item, x, y, pxSize) => {\n const style = item.style;\n const radius = pxSize / 2;\n\n if (style?.fillStyle && style.fillStyle !== lastFillStyle) {\n ctx.fillStyle = style.fillStyle;\n lastFillStyle = style.fillStyle;\n }\n\n ctx.beginPath();\n ctx.arc(x + radius, y + radius, radius, 0, Math.PI * 2);\n ctx.fill();\n });\n\n if (!cache) {\n return this.drawCircle(items, layer);\n }\n\n return this.addStaticCacheLayer(cache, layer)!;\n }\n\n /**\n * Clear a static cache\n * @param cacheKey The cache key to clear, or undefined to clear all\n */\n clearStaticCache(cacheKey?: string) {\n if (cacheKey) {\n this.staticCaches.delete(cacheKey);\n } else {\n this.staticCaches.clear();\n }\n }\n\n /**\n * LAYER METHODS\n */\n /**\n * Remove a specific draw callback by handle (canvas renderer only).\n * Does not clear other callbacks on the same layer.\n */\n removeDrawHandle(handle: DrawHandle) {\n if (!this.layers) {\n throw new Error(\"removeDrawHandle is only available when renderer is set to 'canvas'.\");\n }\n this.layers.remove(handle);\n }\n\n /**\n * Clear all draw callbacks from a specific layer (canvas renderer only).\n * Use this before redrawing dynamic content to prevent accumulation.\n * @param layer Layer index to clear.\n * @example\n * ```ts\n * engine.clearLayer(1);\n * engine.drawRect(newRects, 1);\n * engine.render();\n * ```\n */\n clearLayer(layer: number) {\n if (!this.layers) {\n throw new Error(\"clearLayer is only available when renderer is set to 'canvas'.\");\n }\n this.layers.clear(layer);\n }\n\n /**\n * Clear all draw callbacks from all layers (canvas renderer only).\n * Useful for complete scene reset.\n * @example\n * ```ts\n * engine.clearAll();\n * // Redraw everything from scratch\n * ```\n */\n clearAll() {\n if (!this.layers) {\n throw new Error(\"clearAll is only available when renderer is set to 'canvas'.\");\n }\n this.layers.clear();\n }\n\n /**\n * Release cached canvases and layer callbacks.\n */\n destroy() {\n this.staticCaches.clear();\n this.layers.clear();\n }\n}\n","import { CanvasTileEngineConfig } from \"@canvas-tile-engine/core\";\n\nexport function initStyles(\n canvasWrapper: HTMLDivElement,\n canvas: HTMLCanvasElement,\n isResponsive: CanvasTileEngineConfig[\"responsive\"],\n width?: number,\n height?: number\n) {\n if (isResponsive) {\n Object.assign(canvasWrapper.style, {\n position: \"relative\",\n overflow: \"hidden\",\n });\n } else {\n Object.assign(canvasWrapper.style, {\n position: \"relative\",\n overflow: \"hidden\",\n width: width + \"px\",\n height: height + \"px\",\n });\n }\n\n Object.assign(canvas.style, {\n position: \"absolute\",\n top: \"0\",\n left: \"0\",\n });\n}\n\n/**\n * Apply lineWidth to canvas context with alpha fallback for values < 1.\n * For lineWidth < 1, uses globalAlpha to simulate thinner lines while keeping lineWidth at 1.\n * This ensures consistent rendering across browsers and DPR settings.\n *\n * @param ctx Canvas rendering context\n * @param lineWidth Desired line width (can be < 1 for semi-transparent thin lines)\n * @returns Cleanup function that resets globalAlpha to 1\n *\n */\nexport function applyLineWidth(\n ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,\n lineWidth: number\n): () => void {\n if (lineWidth >= 1) {\n ctx.lineWidth = lineWidth;\n return () => {}; // No cleanup needed\n }\n\n // For lineWidth < 1, use alpha to simulate thinner lines\n const alpha = Math.max(0, Math.min(lineWidth, 1));\n ctx.lineWidth = 1;\n ctx.globalAlpha = alpha;\n\n return () => {\n ctx.globalAlpha = 1;\n };\n}\n","import { CanvasTileEngineConfig, CoordinateTransformer, Coords, ICamera } from \"@canvas-tile-engine/core\";\n\n/** @internal */\nexport type DrawContext = {\n ctx: CanvasRenderingContext2D;\n camera: ICamera;\n transformer: CoordinateTransformer;\n config: Required<CanvasTileEngineConfig>;\n topLeft: Coords;\n};\n\n/** @internal */\nexport type DrawCallback = (dc: DrawContext) => void;\n\nexport interface DrawHandle {\n layer: number;\n id: symbol;\n}\n\n/**\n * Manages ordered draw callbacks for canvas rendering.\n * @internal\n */\nexport class Layer {\n private layers = new Map<number, { id: symbol; fn: DrawCallback }[]>();\n\n /**\n * Register a draw callback at a specific layer index.\n * @param layer Layer order; lower numbers draw first.\n * @param fn Callback receiving drawing context.\n */\n add(layer: number, fn: DrawCallback): DrawHandle {\n const id = Symbol(\"layer-callback\");\n const entry = { id, fn };\n if (!this.layers.has(layer)) this.layers.set(layer, []);\n this.layers.get(layer)!.push(entry);\n return { layer, id };\n }\n\n /**\n * Remove a previously registered callback.\n * Safe to call multiple times; no-op if not found.\n */\n remove(handle: DrawHandle) {\n const list = this.layers.get(handle.layer);\n if (!list) return;\n this.layers.set(\n handle.layer,\n list.filter((entry) => entry.id !== handle.id)\n );\n }\n\n /**\n * Clear callbacks for a layer or all layers.\n * @param layer Layer to clear; clears all when omitted.\n */\n clear(layer?: number) {\n if (layer === undefined) {\n this.layers.clear();\n return;\n }\n this.layers.set(layer, []);\n }\n\n /**\n * Draw all registered callbacks in layer order.\n * @param dc Drawing context shared with callbacks.\n */\n drawAll(dc: DrawContext) {\n const keys = [...this.layers.keys()].sort((a, b) => a - b);\n for (const layer of keys) {\n const fns = this.layers.get(layer);\n if (!fns) continue;\n for (const { fn } of fns) {\n dc.ctx.save();\n fn(dc);\n dc.ctx.restore();\n }\n }\n }\n}\n","import { Config, COORDINATE_OVERLAY, ICamera, ViewportState } from \"@canvas-tile-engine/core\";\n\n/**\n * Renders a coordinate overlay (axes and labels) on top of the canvas.\n * @internal\n */\nexport class CoordinateOverlayRenderer {\n private ctx: CanvasRenderingContext2D;\n private camera: ICamera;\n private config: Config;\n private viewport: ViewportState;\n\n /**\n * @param ctx Canvas context to draw on.\n * @param camera Active camera for position/scale.\n * @param config Normalized grid engine configuration store.\n * @param viewport Mutable viewport size store.\n */\n constructor(ctx: CanvasRenderingContext2D, camera: ICamera, config: Config, viewport: ViewportState) {\n this.ctx = ctx;\n this.camera = camera;\n this.config = config;\n this.viewport = viewport;\n }\n\n /**\n * Draw overlay borders and coordinate labels based on current camera view.\n */\n draw() {\n // Save the current canvas state\n this.ctx.save();\n\n // Set fill style to black with configured opacity\n this.ctx.fillStyle = `rgba(0, 0, 0, ${COORDINATE_OVERLAY.BORDER_OPACITY})`;\n\n // Draw left border - 20px wide, full height\n const { width, height } = this.viewport.getSize();\n this.ctx.fillRect(0, 0, COORDINATE_OVERLAY.BORDER_WIDTH, height);\n\n // Draw bottom border - full width, 20px high\n this.ctx.fillRect(\n COORDINATE_OVERLAY.BORDER_WIDTH,\n height - COORDINATE_OVERLAY.BORDER_WIDTH,\n width,\n COORDINATE_OVERLAY.BORDER_WIDTH\n );\n\n // Set text properties for coordinates\n this.ctx.fillStyle = `rgba(255, 255, 255, ${COORDINATE_OVERLAY.TEXT_OPACITY})`;\n\n // Adjust font size based on scale (min 8px, max 12px)\n const fontSize = Math.min(\n COORDINATE_OVERLAY.MAX_FONT_SIZE,\n Math.max(COORDINATE_OVERLAY.MIN_FONT_SIZE, this.camera.scale * COORDINATE_OVERLAY.FONT_SIZE_SCALE_FACTOR)\n );\n this.ctx.font = `${fontSize}px Arial`;\n this.ctx.textAlign = \"center\";\n this.ctx.textBaseline = \"middle\";\n\n const cordGap = this.camera.scale;\n const visibleAreaWidthInCords = width / cordGap;\n const visibleAreaHeightInCords = height / cordGap;\n\n // Draw Y coordinates (left side)\n for (let i = 0 - (this.camera.y % 1); i <= visibleAreaHeightInCords + 1; i++) {\n this.ctx.fillText(Math.round(this.camera.y + i).toString(), 10, cordGap * i + cordGap / 2);\n }\n\n // Draw X coordinates (bottom)\n for (let i = 0 - (this.camera.x % 1); i <= visibleAreaWidthInCords + 1; i++) {\n this.ctx.fillText(Math.round(this.camera.x + i).toString(), cordGap * i + cordGap / 2, height - 10);\n }\n\n // Restore the canvas state\n this.ctx.restore();\n }\n\n /**\n * Decide whether overlay should be drawn at current scale and config.\n * @param scale Current camera scale.\n * @param coordsConfig Coordinate overlay config.\n * @returns True if overlay is enabled and scale is within range.\n */\n shouldDraw(scale: number): boolean {\n const coordsConfig = this.config.get().coordinates;\n\n if (!coordsConfig.enabled) {\n return false;\n }\n\n if (!coordsConfig.shownScaleRange) {\n return false;\n }\n\n const { min, max } = coordsConfig.shownScaleRange;\n\n return scale >= min && scale <= max;\n }\n}\n","import { Config, DEBUG_HUD, ICamera, ViewportState } from \"@canvas-tile-engine/core\";\n\nconst FPS_SAMPLE_SIZE = 10;\n\n/**\n * Canvas-only debug overlay: draws grid and HUD information.\n * @internal\n */\nexport class CanvasDebug {\n private ctx: CanvasRenderingContext2D;\n private camera: ICamera;\n private config: Config;\n private viewport: ViewportState;\n\n // FPS tracking (runs continuously via rAF)\n private frameTimes: number[] = [];\n private lastFrameTime = 0;\n private currentFps = 0;\n private fpsLoopRunning = false;\n private onFpsUpdate: (() => void) | null = null;\n\n constructor(ctx: CanvasRenderingContext2D, camera: ICamera, config: Config, viewport: ViewportState) {\n this.ctx = ctx;\n this.camera = camera;\n this.config = config;\n this.viewport = viewport;\n }\n\n /**\n * Set callback for FPS updates (triggers re-render)\n */\n setFpsUpdateCallback(callback: () => void) {\n this.onFpsUpdate = callback;\n }\n\n /**\n * Start FPS monitoring loop\n */\n startFpsLoop() {\n if (this.fpsLoopRunning) return;\n this.fpsLoopRunning = true;\n this.lastFrameTime = performance.now();\n this.fpsLoop();\n }\n\n /**\n * Stop FPS monitoring loop\n */\n stopFpsLoop() {\n this.fpsLoopRunning = false;\n }\n\n private fpsLoop() {\n if (!this.fpsLoopRunning) return;\n\n const now = performance.now();\n const delta = now - this.lastFrameTime;\n this.lastFrameTime = now;\n\n this.frameTimes.push(delta);\n if (this.frameTimes.length > FPS_SAMPLE_SIZE) {\n this.frameTimes.shift();\n }\n\n const avgDelta = this.frameTimes.reduce((a, b) => a + b, 0) / this.frameTimes.length;\n const newFps = Math.round(1000 / avgDelta);\n\n // Only trigger update if FPS changed\n if (newFps !== this.currentFps) {\n this.currentFps = newFps;\n this.onFpsUpdate?.();\n }\n\n requestAnimationFrame(() => this.fpsLoop());\n }\n\n draw() {\n this.drawHud();\n }\n\n /**\n * Stop FPS tracking and release callbacks.\n */\n destroy() {\n this.stopFpsLoop();\n this.onFpsUpdate = null;\n }\n\n private drawHud() {\n const config = this.config.get();\n\n if (!config.debug.hud) {\n return;\n }\n\n if (!config.debug.hud.enabled) {\n return;\n }\n\n const datas = [];\n\n const topLeft = { x: this.camera.x, y: this.camera.y };\n\n if (config.debug.hud.topLeftCoordinates) {\n datas.push(`TopLeft: ${topLeft.x.toFixed(2)}, ${topLeft.y.toFixed(2)}`);\n }\n\n if (config.debug.hud.coordinates) {\n const { width, height } = this.viewport.getSize();\n const center = this.camera.getCenter(width, height);\n datas.push(`Coords: ${center.x.toFixed(2)}, ${center.y.toFixed(2)}`);\n }\n\n if (config.debug.hud.scale) {\n datas.push(`Scale: ${this.camera.scale.toFixed(2)}`);\n }\n\n if (config.debug.hud.tilesInView) {\n const { width, height } = this.viewport.getSize();\n datas.push(\n `Tiles in view: ${Math.ceil(width / this.camera.scale)} x ${Math.ceil(height / this.camera.scale)}`\n );\n }\n\n if (config.debug.hud.fps) {\n datas.push(`FPS: ${this.currentFps}`);\n }\n\n const { width } = this.viewport.getSize();\n\n this.ctx.save();\n this.ctx.fillStyle = \"rgba(0,0,0,0.5)\";\n this.ctx.fillRect(\n width - DEBUG_HUD.PANEL_WIDTH - DEBUG_HUD.PADDING,\n DEBUG_HUD.PADDING / 2,\n DEBUG_HUD.PANEL_WIDTH,\n datas.length * DEBUG_HUD.LINE_HEIGHT + DEBUG_HUD.PADDING\n );\n\n this.ctx.fillStyle = \"#00ff99\";\n this.ctx.font = \"12px monospace\";\n\n for (let i = 0; i < datas.length; i++) {\n this.ctx.fillText(\n datas[i],\n width - DEBUG_HUD.PANEL_WIDTH - DEBUG_HUD.PADDING + 5,\n 18 + i * DEBUG_HUD.LINE_HEIGHT\n );\n }\n\n this.ctx.restore();\n }\n}\n","type HandlerMap = {\n click?: (e: MouseEvent) => void;\n contextmenu?: (e: MouseEvent) => void;\n mousedown?: (e: MouseEvent) => void;\n mousemove?: (e: MouseEvent) => void;\n mouseup?: (e: MouseEvent) => void;\n mouseleave?: (e: MouseEvent) => void;\n wheel?: (e: WheelEvent) => void;\n touchstart?: (e: TouchEvent) => void;\n touchmove?: (e: TouchEvent) => void;\n touchend?: (e: TouchEvent) => void;\n};\n\n/**\n * Thin wrapper to attach/detach DOM event listeners on the canvas.\n * @internal\n */\nexport class EventBinder {\n constructor(private canvas: HTMLCanvasElement, private handlers: HandlerMap) {}\n\n attach() {\n if (this.handlers.click) {\n this.canvas.addEventListener(\"click\", this.handlers.click);\n }\n\n if (this.handlers.contextmenu) {\n this.canvas.addEventListener(\"contextmenu\", this.handlers.contextmenu);\n }\n\n if (this.handlers.mousedown) {\n this.canvas.addEventListener(\"mousedown\", this.handlers.mousedown);\n }\n\n if (this.handlers.mousemove) {\n this.canvas.addEventListener(\"mousemove\", this.handlers.mousemove);\n }\n\n if (this.handlers.mouseup) {\n this.canvas.addEventListener(\"mouseup\", this.handlers.mouseup);\n }\n\n if (this.handlers.mouseleave) {\n this.canvas.addEventListener(\"mouseleave\", this.handlers.mouseleave);\n }\n\n if (this.handlers.wheel) {\n this.canvas.addEventListener(\"wheel\", this.handlers.wheel, { passive: false });\n }\n\n if (this.handlers.touchstart) {\n this.canvas.addEventListener(\"touchstart\", this.handlers.touchstart, { passive: false });\n }\n\n if (this.handlers.touchmove) {\n this.canvas.addEventListener(\"touchmove\", this.handlers.touchmove, { passive: false });\n }\n\n if (this.handlers.touchend) {\n this.canvas.addEventListener(\"touchend\", this.handlers.touchend, { passive: false });\n }\n }\n\n detach() {\n if (this.handlers.click) {\n this.canvas.removeEventListener(\"click\", this.handlers.click);\n }\n\n if (this.handlers.contextmenu) {\n this.canvas.removeEventListener(\"contextmenu\", this.handlers.contextmenu);\n }\n\n if (this.handlers.mousedown) {\n this.canvas.removeEventListener(\"mousedown\", this.handlers.mousedown);\n }\n\n if (this.handlers.mousemove) {\n this.canvas.removeEventListener(\"mousemove\", this.handlers.mousemove);\n }\n\n if (this.handlers.mouseup) {\n this.canvas.removeEventListener(\"mouseup\", this.handlers.mouseup);\n }\n\n if (this.handlers.mouseleave) {\n this.canvas.removeEventListener(\"mouseleave\", this.handlers.mouseleave);\n }\n\n if (this.handlers.wheel) {\n this.canvas.removeEventListener(\"wheel\", this.handlers.wheel);\n }\n\n if (this.handlers.touchstart) {\n this.canvas.removeEventListener(\"touchstart\", this.handlers.touchstart);\n }\n\n if (this.handlers.touchmove) {\n this.canvas.removeEventListener(\"touchmove\", this.handlers.touchmove);\n }\n\n if (this.handlers.touchend) {\n this.canvas.removeEventListener(\"touchend\", this.handlers.touchend);\n }\n }\n}\n","import { Config, ICamera, ViewportState } from \"@canvas-tile-engine/core\";\n\n/**\n * Observes canvas resizing and keeps viewport/camera in sync.\n * @internal\n */\nexport class ResizeWatcher {\n private resizeObserver?: ResizeObserver;\n private handleWindowResize?: () => void;\n private currentDpr: number;\n\n public onResize?: () => void;\n\n constructor(\n private wrapper: HTMLDivElement,\n private canvas: HTMLCanvasElement,\n private viewport: ViewportState,\n private camera: ICamera,\n private config: Config,\n private onCameraChange: () => void\n ) {\n this.currentDpr = this.viewport.dpr;\n }\n\n start() {\n // Ensure DPR is up to date before sizing\n this.viewport.updateDpr();\n this.currentDpr = this.viewport.dpr;\n\n const size = this.viewport.getSize();\n\n const configSize = this.config.get().size;\n\n const maxWidth = configSize?.maxWidth;\n const maxHeight = configSize?.maxHeight;\n\n const minWidth = configSize?.minWidth;\n const minHeight = configSize?.minHeight;\n\n size.width = this.clamp(size.width, minWidth, maxWidth);\n size.height = this.clamp(size.height, minHeight, maxHeight);\n\n Object.assign(this.wrapper.style, {\n resize: \"both\",\n overflow: \"hidden\",\n width: `${size.width}px`,\n height: `${size.height}px`,\n touchAction: \"none\",\n position: \"relative\",\n maxWidth: maxWidth ? `${maxWidth}px` : \"\",\n maxHeight: maxHeight ? `${maxHeight}px` : \"\",\n minWidth: minWidth ? `${minWidth}px` : \"\",\n minHeight: minHeight ? `${minHeight}px` : \"\",\n });\n\n this.resizeObserver = new ResizeObserver((entries) => {\n for (const entry of entries) {\n const { width: rawW, height: rawH } = entry.contentRect;\n const width = this.clamp(rawW, minWidth, maxWidth);\n const height = this.clamp(rawH, minHeight, maxHeight);\n const prev = this.viewport.getSize();\n\n if (width === prev.width && height === prev.height) {\n // No effective size change after clamping\n continue;\n }\n\n const diffW = width - prev.width;\n const diffH = height - prev.height;\n const dpr = this.viewport.dpr;\n\n this.camera.adjustForResize(diffW, diffH);\n this.viewport.setSize(width, height);\n\n // Set canvas resolution (physical pixels for HiDPI)\n this.canvas.width = width * dpr;\n this.canvas.height = height * dpr;\n\n // Set CSS size (logical pixels)\n this.canvas.style.width = `${width}px`;\n this.canvas.style.height = `${height}px`;\n\n this.wrapper.style.width = `${width}px`;\n this.wrapper.style.height = `${height}px`;\n\n if (this.onResize) {\n this.onResize();\n }\n this.onCameraChange();\n }\n });\n\n this.resizeObserver.observe(this.wrapper);\n\n this.attachDprWatcher();\n }\n\n stop() {\n if (this.resizeObserver) {\n this.resizeObserver.unobserve(this.wrapper);\n this.resizeObserver.disconnect();\n }\n\n this.resizeObserver = undefined;\n\n if (this.handleWindowResize) {\n window.removeEventListener(\"resize\", this.handleWindowResize);\n this.handleWindowResize = undefined;\n }\n }\n\n private clamp(value: number, min?: number, max?: number) {\n let result = value;\n if (min !== undefined) result = Math.max(min, result);\n if (max !== undefined) result = Math.min(max, result);\n return result;\n }\n\n /**\n * Listen for devicePixelRatio changes (e.g., monitor switch) and rescale canvas.\n */\n private attachDprWatcher() {\n if (typeof window === \"undefined\") {\n return;\n }\n\n this.handleWindowResize = () => {\n const prevDpr = this.currentDpr;\n this.viewport.updateDpr();\n const nextDpr = this.viewport.dpr;\n\n if (nextDpr === prevDpr) {\n return;\n }\n\n this.currentDpr = nextDpr;\n const { width, height } = this.viewport.getSize();\n\n // Update canvas resolution for new DPR while keeping logical size\n this.canvas.width = width * nextDpr;\n this.canvas.height = height * nextDpr;\n this.canvas.style.width = `${width}px`;\n this.canvas.style.height = `${height}px`;\n\n if (this.onResize) {\n this.onResize();\n }\n\n this.onCameraChange();\n };\n\n window.addEventListener(\"resize\", this.handleWindowResize, { passive: true });\n }\n}\n","import { Config, ICamera, ViewportState } from \"@canvas-tile-engine/core\";\n\n/**\n * Watches wrapper element size changes and handles responsive resizing.\n * Supports two modes:\n * - \"preserve-scale\": Scale stays constant, visible tile count changes\n * - \"preserve-viewport\": Visible tile count stays constant, scale changes\n * @internal\n */\nexport class ResponsiveWatcher {\n private resizeObserver?: ResizeObserver;\n private handleWindowResize?: () => void;\n private currentDpr: number;\n\n /** Initial visible tiles (used for preserve-viewport mode) */\n private initialVisibleTiles: { x: number; y: number };\n\n /** Width limits derived from scale limits (preserve-viewport mode) */\n private widthLimits: { min: number; max: number };\n\n /** Callback fired after responsive resize */\n public onResize?: () => void;\n\n constructor(\n private wrapper: HTMLDivElement,\n private canvas: HTMLCanvasElement,\n private camera: ICamera,\n private viewport: ViewportState,\n private config: Config,\n private onRender: () => void\n ) {\n this.currentDpr = this.viewport.dpr;\n\n // Calculate initial visible tiles from config (reference for preserve-viewport)\n const cfg = this.config.get();\n this.initialVisibleTiles = {\n x: cfg.size.width / cfg.scale,\n y: cfg.size.height / cfg.scale,\n };\n\n // Calculate width limits from scale limits (for preserve-viewport mode)\n this.widthLimits = {\n min: cfg.minScale * this.initialVisibleTiles.x,\n max: cfg.maxScale * this.initialVisibleTiles.x,\n };\n }\n\n start() {\n const responsiveMode = this.config.get().responsive;\n if (!responsiveMode) {\n return;\n }\n\n // Ensure DPR is up to date\n this.viewport.updateDpr();\n this.currentDpr = this.viewport.dpr;\n\n // Set wrapper dimensions based on responsive mode\n if (responsiveMode === \"preserve-viewport\") {\n const aspectRatio = this.initialVisibleTiles.y / this.initialVisibleTiles.x;\n this.wrapper.style.width = \"100%\";\n this.wrapper.style.minWidth = `${this.widthLimits.min}px`;\n this.wrapper.style.maxWidth = `${this.widthLimits.max}px`;\n this.wrapper.style.minHeight = `${this.widthLimits.min * aspectRatio}px`;\n this.wrapper.style.maxHeight = `${this.widthLimits.max * aspectRatio}px`;\n } else {\n // preserve-scale: width is responsive, height stays at initial config value\n const cfg = this.config.get();\n this.wrapper.style.width = \"100%\";\n this.wrapper.style.height = `${cfg.size.height}px`;\n }\n\n // Get initial size from wrapper (user controls via CSS)\n const wrapperRect = this.wrapper.getBoundingClientRect();\n const initialWidth = Math.round(wrapperRect.width);\n const initialHeight = Math.round(wrapperRect.height);\n\n // Apply initial size\n this.applySize(initialWidth, initialHeight, responsiveMode);\n\n // Setup ResizeObserver to watch wrapper\n this.resizeObserver = new ResizeObserver((entries) => {\n for (const entry of entries) {\n const { width: rawW, height: rawH } = entry.contentRect;\n const width = Math.round(rawW);\n const height = Math.round(rawH);\n\n if (width <= 0 || height <= 0) {\n continue;\n }\n\n const prev = this.viewport.getSize();\n if (width === prev.width && height === prev.height) {\n continue;\n }\n\n this.applySize(width, height, responsiveMode);\n\n this.onResize?.();\n this.onRender();\n }\n });\n\n this.resizeObserver.observe(this.wrapper);\n this.attachDprWatcher();\n }\n\n stop() {\n if (this.resizeObserver) {\n this.resizeObserver.unobserve(this.wrapper);\n this.resizeObserver.disconnect();\n this.resizeObserver = undefined;\n }\n\n if (this.handleWindowResize) {\n window.removeEventListener(\"resize\", this.handleWindowResize);\n this.handleWindowResize = undefined;\n }\n }\n\n private applySize(width: number, height: number, mode: \"preserve-scale\" | \"preserve-viewport\") {\n const dpr = this.viewport.dpr;\n const prev = this.viewport.getSize();\n\n if (mode === \"preserve-viewport\") {\n // Calculate new scale to maintain the same visible tile count\n const newScale = width / this.initialVisibleTiles.x;\n\n // Calculate height based on configured tile ratio\n const calculatedHeight = Math.round(this.initialVisibleTiles.y * newScale);\n height = calculatedHeight;\n\n // Apply calculated height to wrapper (width is 100%, max-width handles limits)\n this.wrapper.style.height = `${calculatedHeight}px`;\n\n // Save current center before changing scale\n const currentCenter = this.camera.getCenter(prev.width, prev.height);\n\n this.camera.setScale(newScale);\n\n // Restore center after scale change (must use new dimensions)\n this.camera.setCenter(currentCenter, width, height);\n } else {\n // preserve-scale: adjust camera position to keep center stable\n const diffW = width - prev.width;\n const diffH = height - prev.height;\n this.camera.adjustForResize(diffW, diffH);\n }\n\n // Update viewport\n this.viewport.setSize(width, height);\n\n // Update canvas resolution (physical pixels for HiDPI)\n this.canvas.width = width * dpr;\n this.canvas.height = height * dpr;\n\n // Update canvas CSS size (logical pixels)\n this.canvas.style.width = `${width}px`;\n this.canvas.style.height = `${height}px`;\n }\n\n /**\n * Listen for devicePixelRatio changes (e.g., monitor switch) and rescale canvas.\n */\n private attachDprWatcher() {\n if (typeof window === \"undefined\") {\n return;\n }\n\n this.handleWindowResize = () => {\n const prevDpr = this.currentDpr;\n this.viewport.updateDpr();\n const nextDpr = this.viewport.dpr;\n\n if (nextDpr === prevDpr) {\n return;\n }\n\n this.currentDpr = nextDpr;\n const { width, height } = this.viewport.getSize();\n\n // Update canvas resolution for new DPR while keeping logical size\n this.canvas.width = width * nextDpr;\n this.canvas.height = height * nextDpr;\n this.canvas.style.width = `${width}px`;\n this.canvas.style.height = `${height}px`;\n\n this.onResize?.();\n this.onRender();\n };\n\n window.addEventListener(\"resize\", this.handleWindowResize, { passive: true });\n }\n}\n","import { IImageLoader } from \"@canvas-tile-engine/core\";\n\nconst DEFAULT_IMAGE_LOAD_RETRY_COUNT = 1;\n\n/**\n * DOM-based image loader with in-memory caching to avoid duplicate network requests.\n * Implements IImageLoader for HTMLImageElement.\n */\nexport class ImageLoader implements IImageLoader<HTMLImageElement> {\n private cache = new Map<string, HTMLImageElement>();\n private inflight = new Map<string, Promise<HTMLImageElement>>();\n private listeners = new Set<() => void>();\n\n /**\n * Register a callback fired when a new image finishes loading.\n */\n onLoad(cb: () => void): () => void {\n this.listeners.add(cb);\n return () => this.listeners.delete(cb);\n }\n\n private notifyLoaded() {\n for (const cb of this.listeners) {\n cb();\n }\n }\n\n /**\n * Load an image, reusing cache when possible.\n * @param src Image URL.\n * @param retry How many times to retry on error (default: 1).\n * @returns Promise resolving to the loaded image element.\n */\n async load(src: string, retry: number = DEFAULT_IMAGE_LOAD_RETRY_COUNT): Promise<HTMLImageElement> {\n // Cached\n if (this.cache.has(src)) {\n return this.cache.get(src)!;\n }\n\n // Inflight\n if (this.inflight.has(src)) {\n return this.inflight.get(src)!;\n }\n\n const task = new Promise<HTMLImageElement>((resolve, reject) => {\n const img = new Image();\n img.crossOrigin = \"anonymous\";\n img.decoding = \"async\";\n img.loading = \"eager\";\n\n img.onload = async () => {\n try {\n // Wait for decode to finish if supported\n if (\"decode\" in img) {\n await (img as HTMLImageElement & { decode?: () => Promise<void> }).decode?.();\n }\n } catch {\n // ignore decode errors; draw will still attempt\n }\n\n this.cache.set(src, img);\n this.inflight.delete(src);\n this.notifyLoaded();\n resolve(img);\n };\n\n img.onerror = (err) => {\n this.inflight.delete(src);\n if (retry > 0) {\n console.warn(`Retrying image: ${src}`);\n resolve(this.load(src, retry - 1));\n } else {\n console.error(`Image failed to load: ${src}`, err);\n const reason =\n err instanceof Error ? err.message : typeof err === \"string\" ? err : JSON.stringify(err);\n reject(new Error(`Image failed to load: ${src}. Reason: ${reason}`));\n }\n };\n\n img.src = src;\n });\n\n this.inflight.set(src, task);\n return task;\n }\n\n /**\n * Get a cached image without loading.\n * @param src Image URL key.\n */\n get(src: string): HTMLImageElement | undefined {\n return this.cache.get(src);\n }\n\n /**\n * Check if an image is already cached.\n * @param src Image URL key.\n */\n has(src: string): boolean {\n return this.cache.has(src);\n }\n\n /**\n * Clear all cached and inflight images/listeners to free memory.\n */\n clear() {\n this.cache.clear();\n this.inflight.clear();\n this.listeners.clear();\n }\n}\n","import { AnimationController, Config, Coords, ICamera, ViewportState } from \"@canvas-tile-engine/core\";\n\n/**\n * Controls canvas size and handles resize animations.\n * Manages DOM manipulation for wrapper and canvas elements.\n */\nexport class SizeController {\n constructor(\n private canvasWrapper: HTMLDivElement,\n private canvas: HTMLCanvasElement,\n private camera: ICamera,\n private viewport: ViewportState,\n private config: Config,\n private onRender: () => void\n ) {}\n\n /**\n * Manually update canvas size using AnimationController for smooth transitions.\n * @param width New canvas width in pixels.\n * @param height New canvas height in pixels.\n * @param durationMs Animation duration in ms (default 500). Use 0 for instant resize.\n * @param animationController AnimationController instance to handle the animation.\n * @param onComplete Optional callback fired when resize animation completes.\n */\n resizeWithAnimation(\n width: number,\n height: number,\n durationMs: number,\n animationController: AnimationController,\n onComplete?: () => void\n ) {\n if (width <= 0 || height <= 0) {\n return;\n }\n\n const configSize = this.config.get().size;\n const clamp = (value: number, min?: number, max?: number) => {\n let result = value;\n if (min !== undefined) {\n result = Math.max(min, result);\n }\n if (max !== undefined) {\n result = Math.min(max, result);\n }\n return result;\n };\n\n // Clamp to min/max values\n width = clamp(width, configSize?.minWidth, configSize?.maxWidth);\n height = clamp(height, configSize?.minHeight, configSize?.maxHeight);\n\n // Delegate to AnimationController\n animationController.animateResize(\n width,\n height,\n durationMs,\n (w: number, h: number, center: Coords) => this.applySize(w, h, center),\n onComplete\n );\n }\n\n /**\n * Apply size directly without animation.\n * @param width New canvas width in pixels.\n * @param height New canvas height in pixels.\n * @param center Center coordinates to maintain after resize.\n */\n applySize(width: number, height: number, center: Coords) {\n const roundedW = Math.round(width);\n const roundedH = Math.round(height);\n const dpr = this.viewport.dpr;\n\n this.viewport.setSize(roundedW, roundedH);\n\n // CSS size (logical pixels)\n this.canvasWrapper.style.width = `${roundedW}px`;\n this.canvasWrapper.style.height = `${roundedH}px`;\n\n // Canvas resolution (physical pixels for HiDPI)\n this.canvas.width = roundedW * dpr;\n this.canvas.height = roundedH * dpr;\n this.canvas.style.width = `${roundedW}px`;\n this.canvas.style.height = `${roundedH}px`;\n\n this.camera.setCenter(center, roundedW, roundedH);\n this.onRender();\n }\n}\n"],"mappings":"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","names":["index_exports","__export","RendererCanvas","__toCommonJS","import_core","import_core","initStyles","canvasWrapper","canvas","isResponsive","width","height","applyLineWidth","ctx","lineWidth","alpha","SPATIAL_INDEX_THRESHOLD","MAX_STATIC_CANVAS_DIMENSION","CanvasDraw","layers","transformer","camera","x","y","sizeWorld","topLeft","config","viewW","viewH","minX","minY","maxX","maxY","fn","layer","ctx","items","list","spatialIndex","bounds","visibleItems","lastFillStyle","lastStrokeStyle","lastLineWidth","item","size","origin","style","pos","pxSize","drawX","drawY","resetAlpha","applyLineWidth","rotationDeg","rotation","radius","centerX","centerY","halfExtent","a","b","family","points","xs","p","ys","first","i","aspect","drawW","drawH","baseX","baseY","offsetX","offsetY","cellSize","startX","endX","startY","endY","p1","p2","cell","cacheKey","renderFn","worldWidth","worldHeight","renderScale","canvasWidth","canvasHeight","cache","offscreen","offCtx","cachedCanvas","cachedBounds","cachedScale","viewportWidth","viewportHeight","cacheSourceX","cacheSourceY","cacheSourceWidth","cacheSourceHeight","screenDestX","screenDestY","screenDestWidth","screenDestHeight","cacheWidth","cacheHeight","excessWorld","img","imgX","imgY","handle","Layer","layer","fn","id","entry","handle","list","dc","keys","a","b","fns","import_core","CoordinateOverlayRenderer","ctx","camera","config","viewport","width","height","fontSize","cordGap","visibleAreaWidthInCords","visibleAreaHeightInCords","i","scale","coordsConfig","min","max","import_core","FPS_SAMPLE_SIZE","CanvasDebug","ctx","camera","config","viewport","callback","now","delta","avgDelta","a","b","newFps","datas","topLeft","width","height","center","i","EventBinder","canvas","handlers","ResizeWatcher","wrapper","canvas","viewport","camera","config","onCameraChange","size","configSize","maxWidth","maxHeight","minWidth","minHeight","entries","entry","rawW","rawH","width","height","prev","diffW","diffH","dpr","value","min","max","result","prevDpr","nextDpr","ResponsiveWatcher","wrapper","canvas","camera","viewport","config","onRender","cfg","responsiveMode","aspectRatio","wrapperRect","initialWidth","initialHeight","entries","entry","rawW","rawH","width","height","prev","mode","dpr","newScale","calculatedHeight","currentCenter","diffW","diffH","prevDpr","nextDpr","DEFAULT_IMAGE_LOAD_RETRY_COUNT","ImageLoader","cb","src","retry","task","resolve","reject","img","err","reason","SizeController","canvasWrapper","canvas","camera","viewport","config","onRender","width","height","durationMs","animationController","onComplete","configSize","clamp","value","min","max","result","w","center","roundedW","roundedH","dpr","RendererCanvas","ImageLoader","cb","deps","initStyles","Layer","CanvasDraw","CoordinateOverlayRenderer","CanvasDebug","EventBinder","SizeController","ResponsiveWatcher","ResizeWatcher","rect","touches","remaining","changed","size","dpr","config","topLeft","width","height","durationMs","onComplete"]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{AnimationController as q,GestureProcessor as j}from"@canvas-tile-engine/core";import{DEFAULT_VALUES as L,SpatialIndex as z,VISIBILITY_BUFFER as R}from"@canvas-tile-engine/core";function V(C,e,t,s,n){t?Object.assign(C.style,{position:"relative",overflow:"hidden"}):Object.assign(C.style,{position:"relative",overflow:"hidden",width:s+"px",height:n+"px"}),Object.assign(e.style,{position:"absolute",top:"0",left:"0"})}function I(C,e){if(e>=1)return C.lineWidth=e,()=>{};let t=Math.max(0,Math.min(e,1));return C.lineWidth=1,C.globalAlpha=t,()=>{C.globalAlpha=1}}var M=500,U=16384,W=class{constructor(e,t,s){this.layers=e;this.transformer=t;this.camera=s;this.staticCacheSupported=typeof OffscreenCanvas<"u"||typeof document<"u"}staticCaches=new Map;staticCacheSupported;warnedStaticCacheDisabled=!1;isVisible(e,t,s,n,r){let i=r.size.width/r.scale,a=r.size.height/r.scale,p=n.x-R.TILE_BUFFER,h=n.y-R.TILE_BUFFER,c=n.x+i+R.TILE_BUFFER,o=n.y+a+R.TILE_BUFFER;return e+s>=p&&e-s<=c&&t+s>=h&&t-s<=o}getViewportBounds(e,t){let s=t.size.width/t.scale,n=t.size.height/t.scale;return{minX:e.x-R.TILE_BUFFER,minY:e.y-R.TILE_BUFFER,maxX:e.x+s+R.TILE_BUFFER,maxY:e.y+n+R.TILE_BUFFER}}addDrawFunction(e,t=1){return this.layers.add(t,({ctx:s,config:n,topLeft:r})=>{e(s,r,n)})}drawRect(e,t=1){let s=Array.isArray(e)?e:[e],r=s.length>M?z.fromArray(s):null;return this.layers.add(t,({ctx:i,config:a,topLeft:p})=>{let h=this.getViewportBounds(p,a),c=r?r.query(h.minX,h.minY,h.maxX,h.maxY):s;i.save();let o,m,u;for(let l of c){let d=l.size??1,f={mode:l.origin?.mode==="self"?"self":"cell",x:l.origin?.x??.5,y:l.origin?.y??.5},v=l.style;if(!r&&!this.isVisible(l.x,l.y,d/2,p,a))continue;let w=this.transformer.worldToScreen(l.x,l.y),g=d*this.camera.scale,{x:y,y:b}=this.computeOriginOffset(w,g,f,this.camera);v?.fillStyle&&v.fillStyle!==o&&(i.fillStyle=v.fillStyle,o=v.fillStyle),v?.strokeStyle&&v.strokeStyle!==m&&(i.strokeStyle=v.strokeStyle,m=v.strokeStyle);let x;v?.lineWidth&&v.lineWidth!==u&&(x=I(i,v.lineWidth),u=v.lineWidth);let T=l.rotate??0,Y=T*(Math.PI/180),D=l.radius;if(T!==0){let X=y+g/2,N=b+g/2;i.save(),i.translate(X,N),i.rotate(Y),i.beginPath(),D&&i.roundRect?i.roundRect(-g/2,-g/2,g,g,D):i.rect(-g/2,-g/2,g,g),v?.fillStyle&&i.fill(),v?.strokeStyle&&i.stroke(),i.restore()}else i.beginPath(),D&&i.roundRect?i.roundRect(y,b,g,g,D):i.rect(y,b,g,g),v?.fillStyle&&i.fill(),v?.strokeStyle&&i.stroke();x?.()}i.restore()})}drawLine(e,t,s=1){let n=Array.isArray(e)?e:[e];return this.layers.add(s,({ctx:r,config:i,topLeft:a})=>{r.save(),t?.strokeStyle&&(r.strokeStyle=t.strokeStyle);let p=t?.lineWidth?I(r,t.lineWidth):void 0;r.beginPath();for(let h of n){let c=(h.from.x+h.to.x)/2,o=(h.from.y+h.to.y)/2,m=Math.max(Math.abs(h.from.x-h.to.x),Math.abs(h.from.y-h.to.y))/2;if(!this.isVisible(c,o,m,a,i))continue;let u=this.transformer.worldToScreen(h.from.x,h.from.y),l=this.transformer.worldToScreen(h.to.x,h.to.y);r.moveTo(u.x,u.y),r.lineTo(l.x,l.y)}r.stroke(),p?.(),r.restore()})}drawCircle(e,t=1){let s=Array.isArray(e)?e:[e],r=s.length>M?z.fromArray(s):null;return this.layers.add(t,({ctx:i,config:a,topLeft:p})=>{let h=this.getViewportBounds(p,a),c=r?r.query(h.minX,h.minY,h.maxX,h.maxY):s;i.save();let o,m,u;for(let l of c){let d=l.size??1,f={mode:l.origin?.mode==="self"?"self":"cell",x:l.origin?.x??.5,y:l.origin?.y??.5},v=l.style;if(!r&&!this.isVisible(l.x,l.y,d/2,p,a))continue;let w=this.transformer.worldToScreen(l.x,l.y),g=d*this.camera.scale,y=g/2,{x:b,y:x}=this.computeOriginOffset(w,g,f,this.camera);v?.fillStyle&&v.fillStyle!==o&&(i.fillStyle=v.fillStyle,o=v.fillStyle),v?.strokeStyle&&v.strokeStyle!==m&&(i.strokeStyle=v.strokeStyle,m=v.strokeStyle);let T;v?.lineWidth&&v.lineWidth!==u&&(T=I(i,v.lineWidth),u=v.lineWidth),i.beginPath(),i.arc(b+y,x+y,y,0,Math.PI*2),v?.fillStyle&&i.fill(),v?.strokeStyle&&i.stroke(),T?.()}i.restore()})}drawText(e,t=2){let s=Array.isArray(e)?e:[e],r=s.length>M?z.fromArray(s):null;return this.layers.add(t,({ctx:i,config:a,topLeft:p})=>{let h=this.getViewportBounds(p,a),c=r?r.query(h.minX,h.minY,h.maxX,h.maxY):s;i.save();for(let o of c){let m=o.size??1,u=o.style;if(!r&&!this.isVisible(o.x,o.y,m,p,a))continue;let l=m*this.camera.scale*.3,d=u?.fontFamily??"sans-serif";i.font=`${l}px ${d}`,u?.fillStyle&&(i.fillStyle=u.fillStyle),i.textAlign=u?.textAlign??"center",i.textBaseline=u?.textBaseline??"middle";let f=this.transformer.worldToScreen(o.x,o.y),v=o.rotate??0;if(v!==0){let w=v*(Math.PI/180);i.save(),i.translate(f.x,f.y),i.rotate(w),i.fillText(o.text,0,0),i.restore()}else i.fillText(o.text,f.x,f.y)}i.restore()})}drawPath(e,t,s=1){let n=Array.isArray(e[0])?e:[e];return this.layers.add(s,({ctx:r,config:i,topLeft:a})=>{r.save(),t?.strokeStyle&&(r.strokeStyle=t.strokeStyle);let p=t?.lineWidth?I(r,t.lineWidth):void 0;r.beginPath();for(let h of n){if(!h.length)continue;let c=h.map(y=>y.x),o=h.map(y=>y.y),m=Math.min(...c),u=Math.max(...c),l=Math.min(...o),d=Math.max(...o),f=(m+u)/2,v=(l+d)/2,w=Math.max(u-m,d-l)/2;if(!this.isVisible(f,v,w,a,i))continue;let g=this.transformer.worldToScreen(h[0].x,h[0].y);r.moveTo(g.x,g.y);for(let y=1;y<h.length;y++){let b=this.transformer.worldToScreen(h[y].x,h[y].y);r.lineTo(b.x,b.y)}}r.stroke(),p?.(),r.restore()})}drawImage(e,t=1){let s=Array.isArray(e)?e:[e],r=s.length>M?z.fromArray(s):null;return this.layers.add(t,({ctx:i,config:a,topLeft:p})=>{let h=this.getViewportBounds(p,a),c=r?r.query(h.minX,h.minY,h.maxX,h.maxY):s;for(let o of c){let m=o.size??1,u={mode:o.origin?.mode==="self"?"self":"cell",x:o.origin?.x??.5,y:o.origin?.y??.5};if(!r&&!this.isVisible(o.x,o.y,m/2,p,a))continue;let l=this.transformer.worldToScreen(o.x,o.y),d=m*this.camera.scale,f=o.img.width/o.img.height,v=d,w=d;f>1?w=d/f:v=d*f;let{x:g,y}=this.computeOriginOffset(l,d,u,this.camera),b=g+(d-v)/2,x=y+(d-w)/2,T=o.rotate??0,Y=T*(Math.PI/180);if(T!==0){let D=b+v/2,X=x+w/2;i.save(),i.translate(D,X),i.rotate(Y),i.drawImage(o.img,-v/2,-w/2,v,w),i.restore()}else i.drawImage(o.img,b,x,v,w)}})}drawGridLines(e,t,s=0){return this.layers.add(s,({ctx:n,config:r,topLeft:i})=>{let a=r.size.width/r.scale,p=r.size.height/r.scale,h=Math.floor(i.x/e)*e-L.CELL_CENTER_OFFSET,c=Math.ceil((i.x+a)/e)*e-L.CELL_CENTER_OFFSET,o=Math.floor(i.y/e)*e-L.CELL_CENTER_OFFSET,m=Math.ceil((i.y+p)/e)*e-L.CELL_CENTER_OFFSET;n.save(),n.strokeStyle=t.strokeStyle;let u=I(n,t.lineWidth);n.beginPath();for(let l=h;l<=c;l+=e){let d=this.transformer.worldToScreen(l,o),f=this.transformer.worldToScreen(l,m);n.moveTo(d.x,d.y),n.lineTo(f.x,f.y)}for(let l=o;l<=m;l+=e){let d=this.transformer.worldToScreen(h,l),f=this.transformer.worldToScreen(c,l);n.moveTo(d.x,d.y),n.lineTo(f.x,f.y)}n.stroke(),u(),n.restore()})}computeOriginOffset(e,t,s,n){if(s.mode==="cell"){let r=n.scale;return{x:e.x-r/2+s.x*r-t/2,y:e.y-r/2+s.y*r-t/2}}return{x:e.x-s.x*t,y:e.y-s.y*t}}getOrCreateStaticCache(e,t,s){if(!this.staticCacheSupported)return this.warnedStaticCacheDisabled||(console.warn("[CanvasDraw] Static cache disabled: OffscreenCanvas not available."),this.warnedStaticCacheDisabled=!0),null;let n=1/0,r=-1/0,i=1/0,a=-1/0;for(let d of e){let f=d.size??1;d.x-f/2<n&&(n=d.x-f/2),d.x+f/2>r&&(r=d.x+f/2),d.y-f/2<i&&(i=d.y-f/2),d.y+f/2>a&&(a=d.y+f/2)}n-=1,i-=1,r+=1,a+=1;let p=r-n,h=a-i,c=this.camera.scale,o=Math.ceil(p*c),m=Math.ceil(h*c);if(o>U||m>U)return this.warnedStaticCacheDisabled||(console.warn(`Static cache disabled: offscreen canvas too large (${o}x${m}).`),this.warnedStaticCacheDisabled=!0),null;let u=this.staticCaches.get(t);if(!u||u.scale!==c||u.worldBounds.minX!==n||u.worldBounds.maxX!==r||u.worldBounds.minY!==i||u.worldBounds.maxY!==a){let d=typeof OffscreenCanvas<"u"?new OffscreenCanvas(o,m):document.createElement("canvas");typeof OffscreenCanvas<"u"&&d instanceof OffscreenCanvas||(d.width=o,d.height=m);let v=d.getContext("2d");if(!v)return this.warnedStaticCacheDisabled||(console.warn("[CanvasDraw] Static cache disabled: 2D context unavailable."),this.warnedStaticCacheDisabled=!0),null;for(let w of e){let y=(w.size??1)*c,b=(w.x+L.CELL_CENTER_OFFSET-n)*c-y/2,x=(w.y+L.CELL_CENTER_OFFSET-i)*c-y/2;s(v,w,b,x,y)}u={canvas:d,ctx:v,worldBounds:{minX:n,minY:i,maxX:r,maxY:a},scale:c},this.staticCaches.set(t,u)}return u??null}addStaticCacheLayer(e,t){if(!e)return null;let s=e.canvas,n=e.worldBounds,r=e.scale;return this.layers.add(t,({ctx:i,config:a,topLeft:p})=>{let h=a.size.width/a.scale,c=a.size.height/a.scale,o=(p.x-n.minX)*r,m=(p.y-n.minY)*r,u=h*r,l=c*r,d=0,f=0,v=a.size.width,w=a.size.height,g=s.width,y=s.height;if(o<0&&(d=-o/r*a.scale,v-=d,u+=o,o=0),m<0&&(f=-m/r*a.scale,w-=f,l+=m,m=0),o+u>g){let x=(o+u-g)/r;u=g-o,v-=x*a.scale}if(m+l>y){let x=(m+l-y)/r;l=y-m,w-=x*a.scale}u>0&&l>0&&v>0&&w>0&&i.drawImage(s,o,m,u,l,d,f,v,w)})}drawStaticRect(e,t,s=1){let n,r=this.getOrCreateStaticCache(e,t,(i,a,p,h,c)=>{let o=a.style,m=a.rotate??0,u=m*(Math.PI/180),l=a.radius;if(o?.fillStyle&&o.fillStyle!==n&&(i.fillStyle=o.fillStyle,n=o.fillStyle),m!==0){let d=p+c/2,f=h+c/2;i.save(),i.translate(d,f),i.rotate(u),l&&i.roundRect?(i.beginPath(),i.roundRect(-c/2,-c/2,c,c,l),i.fill()):i.fillRect(-c/2,-c/2,c,c),i.restore()}else l&&i.roundRect?(i.beginPath(),i.roundRect(p,h,c,c,l),i.fill()):i.fillRect(p,h,c,c)});return r?this.addStaticCacheLayer(r,s):this.drawRect(e,s)}drawStaticImage(e,t,s=1){let n=this.getOrCreateStaticCache(e,t,(r,i,a,p,h)=>{let c=i.img,o=i.rotate??0,m=o*(Math.PI/180),u=c.width/c.height,l=h,d=h;u>1?d=h/u:l=h*u;let f=a+(h-l)/2,v=p+(h-d)/2;if(o!==0){let w=f+l/2,g=v+d/2;r.save(),r.translate(w,g),r.rotate(m),r.drawImage(c,-l/2,-d/2,l,d),r.restore()}else r.drawImage(c,f,v,l,d)});return n?this.addStaticCacheLayer(n,s):this.drawImage(e,s)}drawStaticCircle(e,t,s=1){let n,r=this.getOrCreateStaticCache(e,t,(i,a,p,h,c)=>{let o=a.style,m=c/2;o?.fillStyle&&o.fillStyle!==n&&(i.fillStyle=o.fillStyle,n=o.fillStyle),i.beginPath(),i.arc(p+m,h+m,m,0,Math.PI*2),i.fill()});return r?this.addStaticCacheLayer(r,s):this.drawCircle(e,s)}clearStaticCache(e){e?this.staticCaches.delete(e):this.staticCaches.clear()}removeDrawHandle(e){if(!this.layers)throw new Error("removeDrawHandle is only available when renderer is set to 'canvas'.");this.layers.remove(e)}clearLayer(e){if(!this.layers)throw new Error("clearLayer is only available when renderer is set to 'canvas'.");this.layers.clear(e)}clearAll(){if(!this.layers)throw new Error("clearAll is only available when renderer is set to 'canvas'.");this.layers.clear()}destroy(){this.staticCaches.clear(),this.layers.clear()}};var H=class{layers=new Map;add(e,t){let s=Symbol("layer-callback"),n={id:s,fn:t};return this.layers.has(e)||this.layers.set(e,[]),this.layers.get(e).push(n),{layer:e,id:s}}remove(e){let t=this.layers.get(e.layer);t&&this.layers.set(e.layer,t.filter(s=>s.id!==e.id))}clear(e){if(e===void 0){this.layers.clear();return}this.layers.set(e,[])}drawAll(e){let t=[...this.layers.keys()].sort((s,n)=>s-n);for(let s of t){let n=this.layers.get(s);if(n)for(let{fn:r}of n)e.ctx.save(),r(e),e.ctx.restore()}}};import{COORDINATE_OVERLAY as S}from"@canvas-tile-engine/core";var P=class{ctx;camera;config;viewport;constructor(e,t,s,n){this.ctx=e,this.camera=t,this.config=s,this.viewport=n}draw(){this.ctx.save(),this.ctx.fillStyle=`rgba(0, 0, 0, ${S.BORDER_OPACITY})`;let{width:e,height:t}=this.viewport.getSize();this.ctx.fillRect(0,0,S.BORDER_WIDTH,t),this.ctx.fillRect(S.BORDER_WIDTH,t-S.BORDER_WIDTH,e,S.BORDER_WIDTH),this.ctx.fillStyle=`rgba(255, 255, 255, ${S.TEXT_OPACITY})`;let s=Math.min(S.MAX_FONT_SIZE,Math.max(S.MIN_FONT_SIZE,this.camera.scale*S.FONT_SIZE_SCALE_FACTOR));this.ctx.font=`${s}px Arial`,this.ctx.textAlign="center",this.ctx.textBaseline="middle";let n=this.camera.scale,r=e/n,i=t/n;for(let a=0-this.camera.y%1;a<=i+1;a++)this.ctx.fillText(Math.round(this.camera.y+a).toString(),10,n*a+n/2);for(let a=0-this.camera.x%1;a<=r+1;a++)this.ctx.fillText(Math.round(this.camera.x+a).toString(),n*a+n/2,t-10);this.ctx.restore()}shouldDraw(e){let t=this.config.get().coordinates;if(!t.enabled||!t.shownScaleRange)return!1;let{min:s,max:n}=t.shownScaleRange;return e>=s&&e<=n}};import{DEBUG_HUD as E}from"@canvas-tile-engine/core";var G=10,A=class{ctx;camera;config;viewport;frameTimes=[];lastFrameTime=0;currentFps=0;fpsLoopRunning=!1;onFpsUpdate=null;constructor(e,t,s,n){this.ctx=e,this.camera=t,this.config=s,this.viewport=n}setFpsUpdateCallback(e){this.onFpsUpdate=e}startFpsLoop(){this.fpsLoopRunning||(this.fpsLoopRunning=!0,this.lastFrameTime=performance.now(),this.fpsLoop())}stopFpsLoop(){this.fpsLoopRunning=!1}fpsLoop(){if(!this.fpsLoopRunning)return;let e=performance.now(),t=e-this.lastFrameTime;this.lastFrameTime=e,this.frameTimes.push(t),this.frameTimes.length>G&&this.frameTimes.shift();let s=this.frameTimes.reduce((r,i)=>r+i,0)/this.frameTimes.length,n=Math.round(1e3/s);n!==this.currentFps&&(this.currentFps=n,this.onFpsUpdate?.()),requestAnimationFrame(()=>this.fpsLoop())}draw(){this.drawHud()}destroy(){this.stopFpsLoop(),this.onFpsUpdate=null}drawHud(){let e=this.config.get();if(!e.debug.hud||!e.debug.hud.enabled)return;let t=[],s={x:this.camera.x,y:this.camera.y};if(e.debug.hud.topLeftCoordinates&&t.push(`TopLeft: ${s.x.toFixed(2)}, ${s.y.toFixed(2)}`),e.debug.hud.coordinates){let{width:r,height:i}=this.viewport.getSize(),a=this.camera.getCenter(r,i);t.push(`Coords: ${a.x.toFixed(2)}, ${a.y.toFixed(2)}`)}if(e.debug.hud.scale&&t.push(`Scale: ${this.camera.scale.toFixed(2)}`),e.debug.hud.tilesInView){let{width:r,height:i}=this.viewport.getSize();t.push(`Tiles in view: ${Math.ceil(r/this.camera.scale)} x ${Math.ceil(i/this.camera.scale)}`)}e.debug.hud.fps&&t.push(`FPS: ${this.currentFps}`);let{width:n}=this.viewport.getSize();this.ctx.save(),this.ctx.fillStyle="rgba(0,0,0,0.5)",this.ctx.fillRect(n-E.PANEL_WIDTH-E.PADDING,E.PADDING/2,E.PANEL_WIDTH,t.length*E.LINE_HEIGHT+E.PADDING),this.ctx.fillStyle="#00ff99",this.ctx.font="12px monospace";for(let r=0;r<t.length;r++)this.ctx.fillText(t[r],n-E.PANEL_WIDTH-E.PADDING+5,18+r*E.LINE_HEIGHT);this.ctx.restore()}};var k=class{constructor(e,t){this.canvas=e;this.handlers=t}attach(){this.handlers.click&&this.canvas.addEventListener("click",this.handlers.click),this.handlers.contextmenu&&this.canvas.addEventListener("contextmenu",this.handlers.contextmenu),this.handlers.mousedown&&this.canvas.addEventListener("mousedown",this.handlers.mousedown),this.handlers.mousemove&&this.canvas.addEventListener("mousemove",this.handlers.mousemove),this.handlers.mouseup&&this.canvas.addEventListener("mouseup",this.handlers.mouseup),this.handlers.mouseleave&&this.canvas.addEventListener("mouseleave",this.handlers.mouseleave),this.handlers.wheel&&this.canvas.addEventListener("wheel",this.handlers.wheel,{passive:!1}),this.handlers.touchstart&&this.canvas.addEventListener("touchstart",this.handlers.touchstart,{passive:!1}),this.handlers.touchmove&&this.canvas.addEventListener("touchmove",this.handlers.touchmove,{passive:!1}),this.handlers.touchend&&this.canvas.addEventListener("touchend",this.handlers.touchend,{passive:!1})}detach(){this.handlers.click&&this.canvas.removeEventListener("click",this.handlers.click),this.handlers.contextmenu&&this.canvas.removeEventListener("contextmenu",this.handlers.contextmenu),this.handlers.mousedown&&this.canvas.removeEventListener("mousedown",this.handlers.mousedown),this.handlers.mousemove&&this.canvas.removeEventListener("mousemove",this.handlers.mousemove),this.handlers.mouseup&&this.canvas.removeEventListener("mouseup",this.handlers.mouseup),this.handlers.mouseleave&&this.canvas.removeEventListener("mouseleave",this.handlers.mouseleave),this.handlers.wheel&&this.canvas.removeEventListener("wheel",this.handlers.wheel),this.handlers.touchstart&&this.canvas.removeEventListener("touchstart",this.handlers.touchstart),this.handlers.touchmove&&this.canvas.removeEventListener("touchmove",this.handlers.touchmove),this.handlers.touchend&&this.canvas.removeEventListener("touchend",this.handlers.touchend)}};var O=class{constructor(e,t,s,n,r,i){this.wrapper=e;this.canvas=t;this.viewport=s;this.camera=n;this.config=r;this.onCameraChange=i;this.currentDpr=this.viewport.dpr}resizeObserver;handleWindowResize;currentDpr;onResize;start(){this.viewport.updateDpr(),this.currentDpr=this.viewport.dpr;let e=this.viewport.getSize(),t=this.config.get().size,s=t?.maxWidth,n=t?.maxHeight,r=t?.minWidth,i=t?.minHeight;e.width=this.clamp(e.width,r,s),e.height=this.clamp(e.height,i,n),Object.assign(this.wrapper.style,{resize:"both",overflow:"hidden",width:`${e.width}px`,height:`${e.height}px`,touchAction:"none",position:"relative",maxWidth:s?`${s}px`:"",maxHeight:n?`${n}px`:"",minWidth:r?`${r}px`:"",minHeight:i?`${i}px`:""}),this.resizeObserver=new ResizeObserver(a=>{for(let p of a){let{width:h,height:c}=p.contentRect,o=this.clamp(h,r,s),m=this.clamp(c,i,n),u=this.viewport.getSize();if(o===u.width&&m===u.height)continue;let l=o-u.width,d=m-u.height,f=this.viewport.dpr;this.camera.adjustForResize(l,d),this.viewport.setSize(o,m),this.canvas.width=o*f,this.canvas.height=m*f,this.canvas.style.width=`${o}px`,this.canvas.style.height=`${m}px`,this.wrapper.style.width=`${o}px`,this.wrapper.style.height=`${m}px`,this.onResize&&this.onResize(),this.onCameraChange()}}),this.resizeObserver.observe(this.wrapper),this.attachDprWatcher()}stop(){this.resizeObserver&&(this.resizeObserver.unobserve(this.wrapper),this.resizeObserver.disconnect()),this.resizeObserver=void 0,this.handleWindowResize&&(window.removeEventListener("resize",this.handleWindowResize),this.handleWindowResize=void 0)}clamp(e,t,s){let n=e;return t!==void 0&&(n=Math.max(t,n)),s!==void 0&&(n=Math.min(s,n)),n}attachDprWatcher(){typeof window>"u"||(this.handleWindowResize=()=>{let e=this.currentDpr;this.viewport.updateDpr();let t=this.viewport.dpr;if(t===e)return;this.currentDpr=t;let{width:s,height:n}=this.viewport.getSize();this.canvas.width=s*t,this.canvas.height=n*t,this.canvas.style.width=`${s}px`,this.canvas.style.height=`${n}px`,this.onResize&&this.onResize(),this.onCameraChange()},window.addEventListener("resize",this.handleWindowResize,{passive:!0}))}};var F=class{constructor(e,t,s,n,r,i){this.wrapper=e;this.canvas=t;this.camera=s;this.viewport=n;this.config=r;this.onRender=i;this.currentDpr=this.viewport.dpr;let a=this.config.get();this.initialVisibleTiles={x:a.size.width/a.scale,y:a.size.height/a.scale},this.widthLimits={min:a.minScale*this.initialVisibleTiles.x,max:a.maxScale*this.initialVisibleTiles.x}}resizeObserver;handleWindowResize;currentDpr;initialVisibleTiles;widthLimits;onResize;start(){let e=this.config.get().responsive;if(!e)return;if(this.viewport.updateDpr(),this.currentDpr=this.viewport.dpr,e==="preserve-viewport"){let r=this.initialVisibleTiles.y/this.initialVisibleTiles.x;this.wrapper.style.width="100%",this.wrapper.style.minWidth=`${this.widthLimits.min}px`,this.wrapper.style.maxWidth=`${this.widthLimits.max}px`,this.wrapper.style.minHeight=`${this.widthLimits.min*r}px`,this.wrapper.style.maxHeight=`${this.widthLimits.max*r}px`}else{let r=this.config.get();this.wrapper.style.width="100%",this.wrapper.style.height=`${r.size.height}px`}let t=this.wrapper.getBoundingClientRect(),s=Math.round(t.width),n=Math.round(t.height);this.applySize(s,n,e),this.resizeObserver=new ResizeObserver(r=>{for(let i of r){let{width:a,height:p}=i.contentRect,h=Math.round(a),c=Math.round(p);if(h<=0||c<=0)continue;let o=this.viewport.getSize();h===o.width&&c===o.height||(this.applySize(h,c,e),this.onResize?.(),this.onRender())}}),this.resizeObserver.observe(this.wrapper),this.attachDprWatcher()}stop(){this.resizeObserver&&(this.resizeObserver.unobserve(this.wrapper),this.resizeObserver.disconnect(),this.resizeObserver=void 0),this.handleWindowResize&&(window.removeEventListener("resize",this.handleWindowResize),this.handleWindowResize=void 0)}applySize(e,t,s){let n=this.viewport.dpr,r=this.viewport.getSize();if(s==="preserve-viewport"){let i=e/this.initialVisibleTiles.x,a=Math.round(this.initialVisibleTiles.y*i);t=a,this.wrapper.style.height=`${a}px`;let p=this.camera.getCenter(r.width,r.height);this.camera.setScale(i),this.camera.setCenter(p,e,t)}else{let i=e-r.width,a=t-r.height;this.camera.adjustForResize(i,a)}this.viewport.setSize(e,t),this.canvas.width=e*n,this.canvas.height=t*n,this.canvas.style.width=`${e}px`,this.canvas.style.height=`${t}px`}attachDprWatcher(){typeof window>"u"||(this.handleWindowResize=()=>{let e=this.currentDpr;this.viewport.updateDpr();let t=this.viewport.dpr;if(t===e)return;this.currentDpr=t;let{width:s,height:n}=this.viewport.getSize();this.canvas.width=s*t,this.canvas.height=n*t,this.canvas.style.width=`${s}px`,this.canvas.style.height=`${n}px`,this.onResize?.(),this.onRender()},window.addEventListener("resize",this.handleWindowResize,{passive:!0}))}};var Z=1,_=class{cache=new Map;inflight=new Map;listeners=new Set;onLoad(e){return this.listeners.add(e),()=>this.listeners.delete(e)}notifyLoaded(){for(let e of this.listeners)e()}async load(e,t=Z){if(this.cache.has(e))return this.cache.get(e);if(this.inflight.has(e))return this.inflight.get(e);let s=new Promise((n,r)=>{let i=new Image;i.crossOrigin="anonymous",i.decoding="async",i.loading="eager",i.onload=async()=>{try{"decode"in i&&await i.decode?.()}catch{}this.cache.set(e,i),this.inflight.delete(e),this.notifyLoaded(),n(i)},i.onerror=a=>{if(this.inflight.delete(e),t>0)console.warn(`Retrying image: ${e}`),n(this.load(e,t-1));else{console.error(`Image failed to load: ${e}`,a);let p=a instanceof Error?a.message:typeof a=="string"?a:JSON.stringify(a);r(new Error(`Image failed to load: ${e}. Reason: ${p}`))}},i.src=e});return this.inflight.set(e,s),s}get(e){return this.cache.get(e)}has(e){return this.cache.has(e)}clear(){this.cache.clear(),this.inflight.clear(),this.listeners.clear()}};var $=class{constructor(e,t,s,n,r,i){this.canvasWrapper=e;this.canvas=t;this.camera=s;this.viewport=n;this.config=r;this.onRender=i}resizeWithAnimation(e,t,s,n,r){if(e<=0||t<=0)return;let i=this.config.get().size,a=(p,h,c)=>{let o=p;return h!==void 0&&(o=Math.max(h,o)),c!==void 0&&(o=Math.min(c,o)),o};e=a(e,i?.minWidth,i?.maxWidth),t=a(t,i?.minHeight,i?.maxHeight),n.animateResize(e,t,s,(p,h,c)=>this.applySize(p,h,c),r)}applySize(e,t,s){let n=Math.round(e),r=Math.round(t),i=this.viewport.dpr;this.viewport.setSize(n,r),this.canvasWrapper.style.width=`${n}px`,this.canvasWrapper.style.height=`${r}px`,this.canvas.width=n*i,this.canvas.height=r*i,this.canvas.style.width=`${n}px`,this.canvas.style.height=`${r}px`,this.camera.setCenter(s,n,r),this.onRender()}};var B=class{canvasWrapper;canvas;canvasContext;camera;config;viewport;layers;drawAPI;transformer;coordinateOverlayRenderer;debugOverlay;gestureProcessor;eventBinder;resizeWatcher;responsiveWatcher;eventsAttached=!1;sizeController;animationController;imageLoader=new _;onDraw;onResize;get onClick(){return this.gestureProcessor?.onClick}set onClick(e){this.gestureProcessor&&(this.gestureProcessor.onClick=e)}get onRightClick(){return this.gestureProcessor?.onRightClick}set onRightClick(e){this.gestureProcessor&&(this.gestureProcessor.onRightClick=e)}get onHover(){return this.gestureProcessor?.onHover}set onHover(e){this.gestureProcessor&&(this.gestureProcessor.onHover=e)}get onMouseDown(){return this.gestureProcessor?.onMouseDown}set onMouseDown(e){this.gestureProcessor&&(this.gestureProcessor.onMouseDown=e)}get onMouseUp(){return this.gestureProcessor?.onMouseUp}set onMouseUp(e){this.gestureProcessor&&(this.gestureProcessor.onMouseUp=e)}get onMouseLeave(){return this.gestureProcessor?.onMouseLeave}set onMouseLeave(e){this.gestureProcessor&&(this.gestureProcessor.onMouseLeave=e)}get onZoom(){return this.gestureProcessor?.onZoom}set onZoom(e){this.gestureProcessor&&(this.gestureProcessor.onZoom=e)}onCameraChange;init(e){if(this.config=e.config,this.canvasWrapper=e.wrapper,this.canvas=this.canvasWrapper.querySelector("canvas"),!this.canvas)throw new Error("Canvas element not found in wrapper");if(V(this.canvasWrapper,this.canvas,this.config.get().responsive,this.config.get().size.width,this.config.get().size.height),this.canvasContext=this.canvas.getContext("2d"),!this.canvasContext)throw new Error("Failed to get 2D canvas context");this.transformer=e.transformer,this.viewport=e.viewport,this.camera=e.camera,this.layers=new H,this.drawAPI=new W(this.layers,e.transformer,e.camera),this.applyCanvasSize(),this.coordinateOverlayRenderer=new P(this.canvasContext,this.camera,this.config,this.viewport),this.config.get().debug?.enabled&&(this.debugOverlay=new A(this.canvasContext,this.camera,this.config,this.viewport),this.config.get().debug?.hud?.fps&&(this.debugOverlay.setFpsUpdateCallback(()=>this.render()),this.debugOverlay.startFpsLoop())),this.gestureProcessor=new j(this.camera,this.config,this.transformer,()=>this.canvas.getBoundingClientRect(),()=>{this.onCameraChange?.(),this.render()}),this.eventBinder=new k(this.canvas,{click:this.handleClick,contextmenu:this.handleContextMenu,mousedown:this.handleMouseDown,mousemove:this.handleMouseMove,mouseup:this.handleMouseUp,mouseleave:this.handleMouseLeave,wheel:this.handleWheel,touchstart:this.handleTouchStart,touchmove:this.handleTouchMove,touchend:this.handleTouchEnd}),this.animationController=new q(this.camera,this.viewport,()=>this.render()),this.sizeController=new $(this.canvasWrapper,this.canvas,this.camera,this.viewport,this.config,()=>this.render())}setupEvents(){this.eventsAttached||(this.eventBinder.attach(),this.eventsAttached=!0,this.config.get().responsive?(this.config.get().eventHandlers?.resize&&console.warn("Canvas Tile Engine: eventHandlers.resize is ignored when responsive mode is enabled. Resizing is handled automatically."),this.responsiveWatcher=new F(this.canvasWrapper,this.canvas,this.camera,this.viewport,this.config,()=>this.render()),this.responsiveWatcher.onResize=()=>{this.onResize&&this.onResize()},this.responsiveWatcher.start()):this.config.get().eventHandlers?.resize&&(this.resizeWatcher=new O(this.canvasWrapper,this.canvas,this.viewport,this.camera,this.config,()=>this.render()),this.resizeWatcher.onResize=()=>{this.onResize&&this.onResize()},this.resizeWatcher.start()))}normalizePointer(e){let t=this.canvas.getBoundingClientRect();return{x:e.clientX-t.left,y:e.clientY-t.top,clientX:e.clientX,clientY:e.clientY}}normalizeTouches(e){return Array.from(e).map(t=>this.normalizePointer(t))}handleClick=e=>{this.gestureProcessor.handleClick(this.normalizePointer(e))};handleContextMenu=e=>{e.preventDefault(),this.gestureProcessor.handleRightClick(this.normalizePointer(e))};handleMouseDown=e=>{this.gestureProcessor.handlePointerDown(this.normalizePointer(e))};handleMouseMove=e=>{this.gestureProcessor.handlePointerMove(this.normalizePointer(e))};handleMouseUp=e=>{this.gestureProcessor.handlePointerUp(this.normalizePointer(e))};handleMouseLeave=e=>{this.gestureProcessor.handlePointerLeave(this.normalizePointer(e))};handleWheel=e=>{e.preventDefault(),this.gestureProcessor.handleWheel(this.normalizePointer(e),e.deltaY)};handleTouchStart=e=>{e.preventDefault(),this.gestureProcessor.handleTouchStart(this.normalizeTouches(e.touches))};handleTouchMove=e=>{e.preventDefault(),this.gestureProcessor.handleTouchMove(this.normalizeTouches(e.touches))};handleTouchEnd=e=>{e.preventDefault();let t=this.normalizeTouches(e.touches),s=e.changedTouches.length>0?this.normalizePointer(e.changedTouches[0]):void 0;this.gestureProcessor.handleTouchEnd(t,s)};getDrawAPI(){return this.drawAPI}getImageLoader(){return this.imageLoader}render(){let e=this.viewport.getSize(),t=this.viewport.dpr,s={...this.config.get(),size:{...e},scale:this.camera.scale},n={x:this.camera.x,y:this.camera.y};this.canvasContext.setTransform(t,0,0,t,0,0),this.canvasContext.clearRect(0,0,s.size.width,s.size.height),this.canvasContext.fillStyle=s.backgroundColor,this.canvasContext.fillRect(0,0,s.size.width,s.size.height),this.layers.drawAll({ctx:this.canvasContext,camera:this.camera,transformer:this.transformer,config:s,topLeft:n}),this.onDraw?.(this.canvasContext,{scale:this.camera.scale,width:s.size.width,height:s.size.height,coords:n}),this.coordinateOverlayRenderer.shouldDraw(this.camera.scale)&&this.coordinateOverlayRenderer.draw(),s.debug?.enabled&&this.debugOverlay&&(s.debug?.hud?.fps&&(this.debugOverlay.setFpsUpdateCallback(()=>this.render()),this.debugOverlay.startFpsLoop()),this.debugOverlay.draw())}resize(e,t){let s=this.viewport.dpr;this.viewport.setSize(e,t),this.canvas.width=e*s,this.canvas.height=t*s,this.canvas.style.width=`${e}px`,this.canvas.style.height=`${t}px`,this.canvasContext.setTransform(s,0,0,s,0,0)}resizeWithAnimation(e,t,s,n){if(this.config.get().responsive){console.warn("Canvas Tile Engine: resizeWithAnimation() is disabled when responsive mode is enabled. Canvas size is controlled by the wrapper element.");return}this.sizeController.resizeWithAnimation(e,t,s,this.animationController,()=>{this.onResize?.(),n?.()})}destroy(){this.eventsAttached&&(this.eventBinder.detach(),this.eventsAttached=!1),this.resizeWatcher?.stop(),this.resizeWatcher=void 0,this.responsiveWatcher?.stop(),this.responsiveWatcher=void 0,this.animationController.cancelAll(),this.drawAPI.destroy(),this.layers.clear(),this.debugOverlay?.destroy(),this.imageLoader.clear()}applyCanvasSize(){let e=this.viewport.getSize(),t=this.viewport.dpr;this.canvas.width=e.width*t,this.canvas.height=e.height*t,this.canvas.style.width=`${e.width}px`,this.canvas.style.height=`${e.height}px`,this.canvasContext.setTransform(t,0,0,t,0,0)}};export{B as RendererCanvas};
|
|
2
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/RendererCanvas.ts","../src/modules/CanvasDraw.ts","../src/utils/canvas.ts","../src/modules/Layer.ts","../src/modules/CoordinateOverlayRenderer.ts","../src/modules/CanvasDebug.ts","../src/modules/EventBinder.ts","../src/modules/ResizeWatcher.ts","../src/modules/ResponsiveWatcher.ts","../src/modules/ImageLoader.ts","../src/modules/SizeController.ts"],"sourcesContent":["import {\n AnimationController,\n Config,\n CoordinateTransformer,\n Coords,\n GestureProcessor,\n ICamera,\n IDrawAPI,\n IImageLoader,\n IRenderer,\n NormalizedPointer,\n onClickCallback,\n onDrawCallback,\n onHoverCallback,\n onMouseDownCallback,\n onMouseLeaveCallback,\n onMouseUpCallback,\n onRightClickCallback,\n onZoomCallback,\n RendererDependencies,\n ViewportState,\n} from \"@canvas-tile-engine/core\";\nimport { CanvasDraw } from \"./modules/CanvasDraw\";\nimport { Layer } from \"./modules/Layer\";\nimport { CoordinateOverlayRenderer } from \"./modules/CoordinateOverlayRenderer\";\nimport { CanvasDebug } from \"./modules/CanvasDebug\";\nimport { EventBinder } from \"./modules/EventBinder\";\nimport { ResizeWatcher } from \"./modules/ResizeWatcher\";\nimport { ResponsiveWatcher } from \"./modules/ResponsiveWatcher\";\nimport { ImageLoader } from \"./modules/ImageLoader\";\nimport { SizeController } from \"./modules/SizeController\";\nimport { initStyles } from \"./utils/canvas\";\n\nexport class RendererCanvas implements IRenderer {\n //\n private canvasWrapper!: HTMLDivElement;\n private canvas!: HTMLCanvasElement;\n private canvasContext!: CanvasRenderingContext2D;\n private camera!: ICamera;\n private config!: Config;\n private viewport!: ViewportState;\n private layers!: Layer;\n private drawAPI!: CanvasDraw;\n private transformer!: CoordinateTransformer;\n private coordinateOverlayRenderer!: CoordinateOverlayRenderer;\n private debugOverlay?: CanvasDebug;\n\n // Event handling\n private gestureProcessor!: GestureProcessor;\n private eventBinder!: EventBinder;\n private resizeWatcher?: ResizeWatcher;\n private responsiveWatcher?: ResponsiveWatcher;\n private eventsAttached = false;\n\n // Size control\n private sizeController!: SizeController;\n private animationController!: AnimationController;\n\n // Image loading\n private imageLoader = new ImageLoader();\n\n /** Optional user-provided draw hook executed after engine layers. */\n public onDraw?: onDrawCallback;\n\n /** Optional callback fired when canvas is resized. */\n public onResize?: () => void;\n\n // ─── Callback Getters/Setters (proxy to GestureProcessor) ───\n\n get onClick(): onClickCallback | undefined {\n return this.gestureProcessor?.onClick;\n }\n set onClick(cb: onClickCallback | undefined) {\n if (this.gestureProcessor) this.gestureProcessor.onClick = cb;\n }\n\n get onRightClick(): onRightClickCallback | undefined {\n return this.gestureProcessor?.onRightClick;\n }\n set onRightClick(cb: onRightClickCallback | undefined) {\n if (this.gestureProcessor) this.gestureProcessor.onRightClick = cb;\n }\n\n get onHover(): onHoverCallback | undefined {\n return this.gestureProcessor?.onHover;\n }\n set onHover(cb: onHoverCallback | undefined) {\n if (this.gestureProcessor) this.gestureProcessor.onHover = cb;\n }\n\n get onMouseDown(): onMouseDownCallback | undefined {\n return this.gestureProcessor?.onMouseDown;\n }\n set onMouseDown(cb: onMouseDownCallback | undefined) {\n if (this.gestureProcessor) this.gestureProcessor.onMouseDown = cb;\n }\n\n get onMouseUp(): onMouseUpCallback | undefined {\n return this.gestureProcessor?.onMouseUp;\n }\n set onMouseUp(cb: onMouseUpCallback | undefined) {\n if (this.gestureProcessor) this.gestureProcessor.onMouseUp = cb;\n }\n\n get onMouseLeave(): onMouseLeaveCallback | undefined {\n return this.gestureProcessor?.onMouseLeave;\n }\n set onMouseLeave(cb: onMouseLeaveCallback | undefined) {\n if (this.gestureProcessor) this.gestureProcessor.onMouseLeave = cb;\n }\n\n get onZoom(): onZoomCallback | undefined {\n return this.gestureProcessor?.onZoom;\n }\n set onZoom(cb: onZoomCallback | undefined) {\n if (this.gestureProcessor) this.gestureProcessor.onZoom = cb;\n }\n\n /** Callback fired when camera position changes (drag/zoom). */\n public onCameraChange?: () => void;\n\n init(deps: RendererDependencies) {\n this.config = deps.config;\n // Initialize canvas\n this.canvasWrapper = deps.wrapper;\n this.canvas = this.canvasWrapper.querySelector(\"canvas\") as HTMLCanvasElement;\n if (!this.canvas) {\n throw new Error(\"Canvas element not found in wrapper\");\n }\n\n initStyles(\n this.canvasWrapper,\n this.canvas,\n this.config.get().responsive,\n this.config.get().size.width,\n this.config.get().size.height\n );\n\n this.canvasContext = this.canvas.getContext(\"2d\")!;\n\n if (!this.canvasContext) {\n throw new Error(\"Failed to get 2D canvas context\");\n }\n\n this.transformer = deps.transformer;\n this.viewport = deps.viewport;\n this.camera = deps.camera;\n this.layers = new Layer();\n this.drawAPI = new CanvasDraw(this.layers, deps.transformer, deps.camera);\n\n this.applyCanvasSize();\n\n this.coordinateOverlayRenderer = new CoordinateOverlayRenderer(\n this.canvasContext,\n this.camera,\n this.config,\n this.viewport\n );\n\n if (this.config.get().debug?.enabled) {\n this.debugOverlay = new CanvasDebug(this.canvasContext, this.camera, this.config, this.viewport);\n // Start FPS loop if fps hud is enabled\n if (this.config.get().debug?.hud?.fps) {\n this.debugOverlay.setFpsUpdateCallback(() => this.render());\n this.debugOverlay.startFpsLoop();\n }\n }\n\n // Initialize GestureProcessor\n this.gestureProcessor = new GestureProcessor(\n this.camera,\n this.config,\n this.transformer,\n () => this.canvas.getBoundingClientRect(),\n () => {\n this.onCameraChange?.();\n this.render();\n }\n );\n\n // Initialize EventBinder with normalized handlers\n this.eventBinder = new EventBinder(this.canvas, {\n click: this.handleClick,\n contextmenu: this.handleContextMenu,\n mousedown: this.handleMouseDown,\n mousemove: this.handleMouseMove,\n mouseup: this.handleMouseUp,\n mouseleave: this.handleMouseLeave,\n wheel: this.handleWheel,\n touchstart: this.handleTouchStart,\n touchmove: this.handleTouchMove,\n touchend: this.handleTouchEnd,\n });\n\n // Initialize AnimationController and SizeController\n this.animationController = new AnimationController(this.camera, this.viewport, () => this.render());\n this.sizeController = new SizeController(\n this.canvasWrapper,\n this.canvas,\n this.camera,\n this.viewport,\n this.config,\n () => this.render()\n );\n }\n\n // ─── Event Setup ───\n\n setupEvents(): void {\n if (this.eventsAttached) return;\n this.eventBinder.attach();\n this.eventsAttached = true;\n\n // Setup responsive or resize watcher based on config\n if (this.config.get().responsive) {\n // Responsive mode - use ResponsiveWatcher\n if (this.config.get().eventHandlers?.resize) {\n console.warn(\n \"Canvas Tile Engine: eventHandlers.resize is ignored when responsive mode is enabled. \" +\n \"Resizing is handled automatically.\"\n );\n }\n this.responsiveWatcher = new ResponsiveWatcher(\n this.canvasWrapper,\n this.canvas,\n this.camera,\n this.viewport,\n this.config,\n () => this.render()\n );\n this.responsiveWatcher.onResize = () => {\n if (this.onResize) {\n this.onResize();\n }\n };\n this.responsiveWatcher.start();\n } else if (this.config.get().eventHandlers?.resize) {\n // Non-responsive mode with resize enabled - use ResizeWatcher\n this.resizeWatcher = new ResizeWatcher(\n this.canvasWrapper,\n this.canvas,\n this.viewport,\n this.camera,\n this.config,\n () => this.render()\n );\n this.resizeWatcher.onResize = () => {\n if (this.onResize) {\n this.onResize();\n }\n };\n this.resizeWatcher.start();\n }\n }\n\n // ─── Normalize Helpers ───\n\n private normalizePointer(e: MouseEvent | Touch): NormalizedPointer {\n const rect = this.canvas.getBoundingClientRect();\n return {\n x: e.clientX - rect.left,\n y: e.clientY - rect.top,\n clientX: e.clientX,\n clientY: e.clientY,\n };\n }\n\n private normalizeTouches(touches: TouchList): NormalizedPointer[] {\n return Array.from(touches).map((t) => this.normalizePointer(t));\n }\n\n // ─── Event Handlers (DOM → Normalize → GestureProcessor) ───\n\n private handleClick = (e: MouseEvent): void => {\n this.gestureProcessor.handleClick(this.normalizePointer(e));\n };\n\n private handleContextMenu = (e: MouseEvent): void => {\n e.preventDefault();\n this.gestureProcessor.handleRightClick(this.normalizePointer(e));\n };\n\n private handleMouseDown = (e: MouseEvent): void => {\n this.gestureProcessor.handlePointerDown(this.normalizePointer(e));\n };\n\n private handleMouseMove = (e: MouseEvent): void => {\n this.gestureProcessor.handlePointerMove(this.normalizePointer(e));\n };\n\n private handleMouseUp = (e: MouseEvent): void => {\n this.gestureProcessor.handlePointerUp(this.normalizePointer(e));\n };\n\n private handleMouseLeave = (e: MouseEvent): void => {\n this.gestureProcessor.handlePointerLeave(this.normalizePointer(e));\n };\n\n private handleWheel = (e: WheelEvent): void => {\n e.preventDefault();\n this.gestureProcessor.handleWheel(this.normalizePointer(e), e.deltaY);\n };\n\n private handleTouchStart = (e: TouchEvent): void => {\n e.preventDefault();\n this.gestureProcessor.handleTouchStart(this.normalizeTouches(e.touches));\n };\n\n private handleTouchMove = (e: TouchEvent): void => {\n e.preventDefault();\n this.gestureProcessor.handleTouchMove(this.normalizeTouches(e.touches));\n };\n\n private handleTouchEnd = (e: TouchEvent): void => {\n e.preventDefault();\n const remaining = this.normalizeTouches(e.touches);\n const changed = e.changedTouches.length > 0 ? this.normalizePointer(e.changedTouches[0]) : undefined;\n this.gestureProcessor.handleTouchEnd(remaining, changed);\n };\n\n getDrawAPI(): IDrawAPI {\n return this.drawAPI;\n }\n\n getImageLoader(): IImageLoader<HTMLImageElement> {\n return this.imageLoader;\n }\n\n render(): void {\n const size = this.viewport.getSize();\n const dpr = this.viewport.dpr;\n const config = { ...this.config.get(), size: { ...size }, scale: this.camera.scale };\n const topLeft: Coords = { x: this.camera.x, y: this.camera.y };\n\n // Reset transform for HiDPI support (canvas.width/height changes reset transform)\n this.canvasContext.setTransform(dpr, 0, 0, dpr, 0, 0);\n\n // Clear background\n this.canvasContext.clearRect(0, 0, config.size.width, config.size.height);\n this.canvasContext.fillStyle = config.backgroundColor;\n this.canvasContext.fillRect(0, 0, config.size.width, config.size.height);\n\n // Draw engine layers\n this.layers.drawAll({\n ctx: this.canvasContext,\n camera: this.camera,\n transformer: this.transformer,\n config,\n topLeft,\n });\n\n // User custom draw callback (optional)\n this.onDraw?.(this.canvasContext, {\n scale: this.camera.scale,\n width: config.size.width,\n height: config.size.height,\n coords: topLeft,\n });\n\n // Coordinate overlay\n if (this.coordinateOverlayRenderer.shouldDraw(this.camera.scale)) {\n this.coordinateOverlayRenderer.draw();\n }\n\n // Debug overlay\n if (config.debug?.enabled && this.debugOverlay) {\n if (config.debug?.hud?.fps) {\n this.debugOverlay.setFpsUpdateCallback(() => this.render());\n this.debugOverlay.startFpsLoop();\n }\n this.debugOverlay.draw();\n }\n }\n\n resize(width: number, height: number): void {\n const dpr = this.viewport.dpr;\n\n this.viewport.setSize(width, height);\n\n // Set actual canvas resolution (physical pixels)\n this.canvas.width = width * dpr;\n this.canvas.height = height * dpr;\n\n // Set display size via CSS (logical pixels)\n this.canvas.style.width = `${width}px`;\n this.canvas.style.height = `${height}px`;\n\n // Scale context to match DPR\n this.canvasContext.setTransform(dpr, 0, 0, dpr, 0, 0);\n }\n\n resizeWithAnimation(width: number, height: number, durationMs: number, onComplete?: () => void): void {\n if (this.config.get().responsive) {\n console.warn(\n \"Canvas Tile Engine: resizeWithAnimation() is disabled when responsive mode is enabled. \" +\n \"Canvas size is controlled by the wrapper element.\"\n );\n return;\n }\n this.sizeController.resizeWithAnimation(width, height, durationMs, this.animationController, () => {\n // Trigger onResize callback after programmatic resize completes\n this.onResize?.();\n onComplete?.();\n });\n }\n\n destroy(): void {\n // Detach events\n if (this.eventsAttached) {\n this.eventBinder.detach();\n this.eventsAttached = false;\n }\n this.resizeWatcher?.stop();\n this.resizeWatcher = undefined;\n this.responsiveWatcher?.stop();\n this.responsiveWatcher = undefined;\n\n // Cancel animations\n this.animationController.cancelAll();\n\n // Cleanup drawing\n this.drawAPI.destroy();\n this.layers.clear();\n this.debugOverlay?.destroy();\n this.imageLoader.clear();\n }\n\n private applyCanvasSize() {\n const size = this.viewport.getSize();\n const dpr = this.viewport.dpr;\n\n // Set actual canvas resolution (physical pixels)\n this.canvas.width = size.width * dpr;\n this.canvas.height = size.height * dpr;\n\n // Set display size via CSS (logical pixels)\n this.canvas.style.width = `${size.width}px`;\n this.canvas.style.height = `${size.height}px`;\n\n // Scale context to match DPR\n this.canvasContext.setTransform(dpr, 0, 0, dpr, 0, 0);\n }\n}\n","import {\n CanvasTileEngineConfig,\n Circle,\n CoordinateTransformer,\n Coords,\n DEFAULT_VALUES,\n DrawHandle,\n ICamera,\n ImageItem,\n Line,\n Path,\n Rect,\n SpatialIndex,\n Text,\n VISIBILITY_BUFFER,\n} from \"@canvas-tile-engine/core\";\nimport { Layer } from \"./Layer\";\nimport { applyLineWidth } from \"../utils/canvas\";\n\n// Threshold for using spatial indexing (below this, linear scan is faster)\nconst SPATIAL_INDEX_THRESHOLD = 500;\n// Conservative max dimension for offscreen static cache (browser limits often 16384 or 32767)\nconst MAX_STATIC_CANVAS_DIMENSION = 16384;\n\n// Cache for static layers (pre-rendered offscreen canvases)\ninterface StaticCache {\n canvas: OffscreenCanvas | HTMLCanvasElement;\n ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D;\n worldBounds: { minX: number; minY: number; maxX: number; maxY: number };\n scale: number;\n}\n\n/**\n * Canvas-specific helpers for adding draw callbacks to the layer stack.\n * @internal\n */\nexport class CanvasDraw {\n private staticCaches = new Map<string, StaticCache>();\n private staticCacheSupported: boolean;\n private warnedStaticCacheDisabled = false;\n\n constructor(private layers: Layer, private transformer: CoordinateTransformer, private camera: ICamera) {\n this.staticCacheSupported = typeof OffscreenCanvas !== \"undefined\" || typeof document !== \"undefined\";\n }\n\n /**\n * Register a generic draw callback; receives raw context, current coords, and config.\n * @param fn Callback invoked during render.\n * @param layer Layer order (lower draws first).\n */\n\n private isVisible(\n x: number,\n y: number,\n sizeWorld: number,\n topLeft: Coords,\n config: Required<CanvasTileEngineConfig>\n ) {\n const viewW = config.size.width / config.scale;\n const viewH = config.size.height / config.scale;\n const minX = topLeft.x - VISIBILITY_BUFFER.TILE_BUFFER;\n const minY = topLeft.y - VISIBILITY_BUFFER.TILE_BUFFER;\n const maxX = topLeft.x + viewW + VISIBILITY_BUFFER.TILE_BUFFER;\n const maxY = topLeft.y + viewH + VISIBILITY_BUFFER.TILE_BUFFER;\n return x + sizeWorld >= minX && x - sizeWorld <= maxX && y + sizeWorld >= minY && y - sizeWorld <= maxY;\n }\n\n private getViewportBounds(topLeft: Coords, config: Required<CanvasTileEngineConfig>) {\n const viewW = config.size.width / config.scale;\n const viewH = config.size.height / config.scale;\n return {\n minX: topLeft.x - VISIBILITY_BUFFER.TILE_BUFFER,\n minY: topLeft.y - VISIBILITY_BUFFER.TILE_BUFFER,\n maxX: topLeft.x + viewW + VISIBILITY_BUFFER.TILE_BUFFER,\n maxY: topLeft.y + viewH + VISIBILITY_BUFFER.TILE_BUFFER,\n };\n }\n\n addDrawFunction(\n fn: (ctx: CanvasRenderingContext2D, coords: Coords, config: Required<CanvasTileEngineConfig>) => void,\n layer: number = 1\n ): DrawHandle {\n return this.layers.add(layer, ({ ctx, config, topLeft }) => {\n fn(ctx, topLeft, config);\n });\n }\n\n drawRect(items: Array<Rect> | Rect, layer: number = 1): DrawHandle {\n const list = Array.isArray(items) ? items : [items];\n\n // Build spatial index for large datasets (RBush R-Tree)\n const useSpatialIndex = list.length > SPATIAL_INDEX_THRESHOLD;\n const spatialIndex = useSpatialIndex ? SpatialIndex.fromArray(list) : null;\n\n return this.layers.add(layer, ({ ctx, config, topLeft }) => {\n const bounds = this.getViewportBounds(topLeft, config);\n const visibleItems = spatialIndex\n ? spatialIndex.query(bounds.minX, bounds.minY, bounds.maxX, bounds.maxY)\n : list;\n\n ctx.save();\n let lastFillStyle: string | undefined;\n let lastStrokeStyle: string | undefined;\n let lastLineWidth: number | undefined;\n\n for (const item of visibleItems) {\n const size = item.size ?? 1;\n const origin = {\n mode: item.origin?.mode === \"self\" ? \"self\" : (\"cell\" as \"cell\" | \"self\"),\n x: item.origin?.x ?? 0.5,\n y: item.origin?.y ?? 0.5,\n };\n const style = item.style;\n\n // Skip visibility check if using spatial index (already filtered)\n if (!spatialIndex && !this.isVisible(item.x, item.y, size / 2, topLeft, config)) continue;\n\n const pos = this.transformer.worldToScreen(item.x, item.y);\n const pxSize = size * this.camera.scale;\n const { x: drawX, y: drawY } = this.computeOriginOffset(pos, pxSize, origin, this.camera);\n\n // Only update style when changed (reduces state changes)\n if (style?.fillStyle && style.fillStyle !== lastFillStyle) {\n ctx.fillStyle = style.fillStyle;\n lastFillStyle = style.fillStyle;\n }\n if (style?.strokeStyle && style.strokeStyle !== lastStrokeStyle) {\n ctx.strokeStyle = style.strokeStyle;\n lastStrokeStyle = style.strokeStyle;\n }\n\n let resetAlpha: (() => void) | undefined;\n if (style?.lineWidth && style.lineWidth !== lastLineWidth) {\n resetAlpha = applyLineWidth(ctx, style.lineWidth);\n lastLineWidth = style.lineWidth;\n }\n\n const rotationDeg = item.rotate ?? 0;\n const rotation = rotationDeg * (Math.PI / 180);\n\n const radius = item.radius;\n\n if (rotationDeg !== 0) {\n const centerX = drawX + pxSize / 2;\n const centerY = drawY + pxSize / 2;\n ctx.save();\n ctx.translate(centerX, centerY);\n ctx.rotate(rotation);\n ctx.beginPath();\n if (radius && ctx.roundRect) {\n ctx.roundRect(-pxSize / 2, -pxSize / 2, pxSize, pxSize, radius);\n } else {\n ctx.rect(-pxSize / 2, -pxSize / 2, pxSize, pxSize);\n }\n if (style?.fillStyle) ctx.fill();\n if (style?.strokeStyle) ctx.stroke();\n ctx.restore();\n } else {\n ctx.beginPath();\n if (radius && ctx.roundRect) {\n ctx.roundRect(drawX, drawY, pxSize, pxSize, radius);\n } else {\n ctx.rect(drawX, drawY, pxSize, pxSize);\n }\n if (style?.fillStyle) ctx.fill();\n if (style?.strokeStyle) ctx.stroke();\n }\n\n resetAlpha?.();\n }\n ctx.restore();\n });\n }\n\n drawLine(\n items: Array<Line> | Line,\n style?: { strokeStyle?: string; lineWidth?: number },\n layer: number = 1\n ): DrawHandle {\n const list = Array.isArray(items) ? items : [items];\n\n return this.layers.add(layer, ({ ctx, config, topLeft }) => {\n ctx.save();\n if (style?.strokeStyle) ctx.strokeStyle = style.strokeStyle;\n\n const resetAlpha = style?.lineWidth ? applyLineWidth(ctx, style.lineWidth) : undefined;\n\n ctx.beginPath();\n for (const item of list) {\n const centerX = (item.from.x + item.to.x) / 2;\n const centerY = (item.from.y + item.to.y) / 2;\n const halfExtent = Math.max(Math.abs(item.from.x - item.to.x), Math.abs(item.from.y - item.to.y)) / 2;\n if (!this.isVisible(centerX, centerY, halfExtent, topLeft, config)) continue;\n\n const a = this.transformer.worldToScreen(item.from.x, item.from.y);\n const b = this.transformer.worldToScreen(item.to.x, item.to.y);\n\n ctx.moveTo(a.x, a.y);\n ctx.lineTo(b.x, b.y);\n }\n ctx.stroke();\n\n resetAlpha?.();\n ctx.restore();\n });\n }\n\n drawCircle(items: Array<Circle> | Circle, layer: number = 1): DrawHandle {\n const list = Array.isArray(items) ? items : [items];\n\n // Build spatial index for large datasets (RBush R-Tree)\n const useSpatialIndex = list.length > SPATIAL_INDEX_THRESHOLD;\n const spatialIndex = useSpatialIndex ? SpatialIndex.fromArray(list) : null;\n\n return this.layers.add(layer, ({ ctx, config, topLeft }) => {\n const bounds = this.getViewportBounds(topLeft, config);\n const visibleItems = spatialIndex\n ? spatialIndex.query(bounds.minX, bounds.minY, bounds.maxX, bounds.maxY)\n : list;\n\n ctx.save();\n let lastFillStyle: string | undefined;\n let lastStrokeStyle: string | undefined;\n let lastLineWidth: number | undefined;\n\n for (const item of visibleItems) {\n const size = item.size ?? 1;\n const origin = {\n mode: item.origin?.mode === \"self\" ? \"self\" : (\"cell\" as \"cell\" | \"self\"),\n x: item.origin?.x ?? 0.5,\n y: item.origin?.y ?? 0.5,\n };\n const style = item.style;\n\n // Skip visibility check if using spatial index (already filtered)\n if (!spatialIndex && !this.isVisible(item.x, item.y, size / 2, topLeft, config)) continue;\n\n const pos = this.transformer.worldToScreen(item.x, item.y);\n const pxSize = size * this.camera.scale;\n const radius = pxSize / 2;\n const { x: drawX, y: drawY } = this.computeOriginOffset(pos, pxSize, origin, this.camera);\n\n // Only update style when changed\n if (style?.fillStyle && style.fillStyle !== lastFillStyle) {\n ctx.fillStyle = style.fillStyle;\n lastFillStyle = style.fillStyle;\n }\n if (style?.strokeStyle && style.strokeStyle !== lastStrokeStyle) {\n ctx.strokeStyle = style.strokeStyle;\n lastStrokeStyle = style.strokeStyle;\n }\n\n let resetAlpha: (() => void) | undefined;\n if (style?.lineWidth && style.lineWidth !== lastLineWidth) {\n resetAlpha = applyLineWidth(ctx, style.lineWidth);\n lastLineWidth = style.lineWidth;\n }\n\n ctx.beginPath();\n ctx.arc(drawX + radius, drawY + radius, radius, 0, Math.PI * 2);\n if (style?.fillStyle) ctx.fill();\n if (style?.strokeStyle) ctx.stroke();\n\n resetAlpha?.();\n }\n ctx.restore();\n });\n }\n\n drawText(items: Array<Text> | Text, layer: number = 2): DrawHandle {\n const list = Array.isArray(items) ? items : [items];\n\n // Build spatial index for large datasets (RBush R-Tree)\n const useSpatialIndex = list.length > SPATIAL_INDEX_THRESHOLD;\n const spatialIndex = useSpatialIndex ? SpatialIndex.fromArray(list) : null;\n\n return this.layers.add(layer, ({ ctx, config, topLeft }) => {\n const bounds = this.getViewportBounds(topLeft, config);\n const visibleItems = spatialIndex\n ? spatialIndex.query(bounds.minX, bounds.minY, bounds.maxX, bounds.maxY)\n : list;\n\n ctx.save();\n\n for (const item of visibleItems) {\n const size = item.size ?? 1;\n const style = item.style;\n\n // Skip visibility check if using spatial index (already filtered)\n if (!spatialIndex && !this.isVisible(item.x, item.y, size, topLeft, config)) continue;\n\n // Scale-aware font size (world units)\n const pxSize = size * this.camera.scale * 0.3;\n const family = style?.fontFamily ?? \"sans-serif\";\n ctx.font = `${pxSize}px ${family}`;\n\n if (style?.fillStyle) ctx.fillStyle = style.fillStyle;\n ctx.textAlign = style?.textAlign ?? \"center\";\n ctx.textBaseline = style?.textBaseline ?? \"middle\";\n\n const pos = this.transformer.worldToScreen(item.x, item.y);\n\n const rotationDeg = item.rotate ?? 0;\n if (rotationDeg !== 0) {\n const rotation = rotationDeg * (Math.PI / 180);\n ctx.save();\n ctx.translate(pos.x, pos.y);\n ctx.rotate(rotation);\n ctx.fillText(item.text, 0, 0);\n ctx.restore();\n } else {\n ctx.fillText(item.text, pos.x, pos.y);\n }\n }\n ctx.restore();\n });\n }\n\n drawPath(\n items: Array<Path> | Path,\n style?: { strokeStyle?: string; lineWidth?: number },\n layer: number = 1\n ): DrawHandle {\n const list = Array.isArray(items[0]) ? (items as Array<Coords[]>) : [items as Coords[]];\n\n return this.layers.add(layer, ({ ctx, config, topLeft }) => {\n ctx.save();\n if (style?.strokeStyle) ctx.strokeStyle = style.strokeStyle;\n\n const resetAlpha = style?.lineWidth ? applyLineWidth(ctx, style.lineWidth) : undefined;\n\n ctx.beginPath();\n for (const points of list) {\n if (!points.length) continue;\n const xs = points.map((p) => p.x);\n const ys = points.map((p) => p.y);\n const minX = Math.min(...xs);\n const maxX = Math.max(...xs);\n const minY = Math.min(...ys);\n const maxY = Math.max(...ys);\n const centerX = (minX + maxX) / 2;\n const centerY = (minY + maxY) / 2;\n const halfExtent = Math.max(maxX - minX, maxY - minY) / 2;\n if (!this.isVisible(centerX, centerY, halfExtent, topLeft, config)) continue;\n\n const first = this.transformer.worldToScreen(points[0].x, points[0].y);\n ctx.moveTo(first.x, first.y);\n\n for (let i = 1; i < points.length; i++) {\n const p = this.transformer.worldToScreen(points[i].x, points[i].y);\n ctx.lineTo(p.x, p.y);\n }\n }\n ctx.stroke();\n\n resetAlpha?.();\n ctx.restore();\n });\n }\n\n drawImage(items: Array<ImageItem> | ImageItem, layer: number = 1): DrawHandle {\n const list = Array.isArray(items) ? items : [items];\n\n // Build spatial index for large datasets (RBush R-Tree)\n const useSpatialIndex = list.length > SPATIAL_INDEX_THRESHOLD;\n const spatialIndex = useSpatialIndex ? SpatialIndex.fromArray(list) : null;\n\n return this.layers.add(layer, ({ ctx, config, topLeft }) => {\n const bounds = this.getViewportBounds(topLeft, config);\n const visibleItems = spatialIndex\n ? spatialIndex.query(bounds.minX, bounds.minY, bounds.maxX, bounds.maxY)\n : list;\n\n for (const item of visibleItems) {\n const size = item.size ?? 1;\n const origin = {\n mode: item.origin?.mode === \"self\" ? \"self\" : (\"cell\" as \"cell\" | \"self\"),\n x: item.origin?.x ?? 0.5,\n y: item.origin?.y ?? 0.5,\n };\n\n // Skip visibility check if using spatial index (already filtered)\n if (!spatialIndex && !this.isVisible(item.x, item.y, size / 2, topLeft, config)) continue;\n\n const pos = this.transformer.worldToScreen(item.x, item.y);\n const pxSize = size * this.camera.scale;\n\n // preserve aspect\n const aspect = item.img.width / item.img.height;\n\n let drawW = pxSize;\n let drawH = pxSize;\n\n if (aspect > 1) drawH = pxSize / aspect;\n else drawW = pxSize * aspect;\n\n // origin SELF/CELL\n const { x: baseX, y: baseY } = this.computeOriginOffset(pos, pxSize, origin, this.camera);\n\n const offsetX = baseX + (pxSize - drawW) / 2;\n const offsetY = baseY + (pxSize - drawH) / 2;\n\n const rotationDeg = item.rotate ?? 0;\n const rotation = rotationDeg * (Math.PI / 180);\n\n if (rotationDeg !== 0) {\n const centerX = offsetX + drawW / 2;\n const centerY = offsetY + drawH / 2;\n ctx.save();\n ctx.translate(centerX, centerY);\n ctx.rotate(rotation);\n ctx.drawImage(item.img, -drawW / 2, -drawH / 2, drawW, drawH);\n ctx.restore();\n } else {\n ctx.drawImage(item.img, offsetX, offsetY, drawW, drawH);\n }\n }\n });\n }\n\n drawGridLines(cellSize: number, style: { strokeStyle: string; lineWidth: number }, layer: number = 0): DrawHandle {\n return this.layers.add(layer, ({ ctx, config, topLeft }) => {\n const viewW = config.size.width / config.scale;\n const viewH = config.size.height / config.scale;\n\n const startX = Math.floor(topLeft.x / cellSize) * cellSize - DEFAULT_VALUES.CELL_CENTER_OFFSET;\n const endX = Math.ceil((topLeft.x + viewW) / cellSize) * cellSize - DEFAULT_VALUES.CELL_CENTER_OFFSET;\n const startY = Math.floor(topLeft.y / cellSize) * cellSize - DEFAULT_VALUES.CELL_CENTER_OFFSET;\n const endY = Math.ceil((topLeft.y + viewH) / cellSize) * cellSize - DEFAULT_VALUES.CELL_CENTER_OFFSET;\n\n ctx.save();\n\n ctx.strokeStyle = style.strokeStyle;\n const resetAlpha = applyLineWidth(ctx, style.lineWidth);\n\n ctx.beginPath();\n\n for (let x = startX; x <= endX; x += cellSize) {\n const p1 = this.transformer.worldToScreen(x, startY);\n const p2 = this.transformer.worldToScreen(x, endY);\n ctx.moveTo(p1.x, p1.y);\n ctx.lineTo(p2.x, p2.y);\n }\n\n for (let y = startY; y <= endY; y += cellSize) {\n const p1 = this.transformer.worldToScreen(startX, y);\n const p2 = this.transformer.worldToScreen(endX, y);\n ctx.moveTo(p1.x, p1.y);\n ctx.lineTo(p2.x, p2.y);\n }\n\n ctx.stroke();\n resetAlpha();\n ctx.restore();\n });\n }\n\n private computeOriginOffset(\n pos: Coords,\n pxSize: number,\n origin: { mode: \"cell\" | \"self\"; x: number; y: number },\n camera: ICamera\n ) {\n if (origin.mode === \"cell\") {\n const cell = camera.scale;\n return {\n x: pos.x - cell / 2 + origin.x * cell - pxSize / 2,\n y: pos.y - cell / 2 + origin.y * cell - pxSize / 2,\n };\n }\n\n return {\n x: pos.x - origin.x * pxSize,\n y: pos.y - origin.y * pxSize,\n };\n }\n\n /**\n * Helper to create or get a static cache for pre-rendered content.\n * Handles bounds calculation, canvas creation, and rebuild logic.\n */\n private getOrCreateStaticCache<T extends { x: number; y: number; size?: number; radius?: number | number[] }>(\n items: T[],\n cacheKey: string,\n renderFn: (\n ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,\n item: T,\n x: number,\n y: number,\n pxSize: number\n ) => void\n ): StaticCache | null {\n if (!this.staticCacheSupported) {\n if (!this.warnedStaticCacheDisabled) {\n console.warn(\"[CanvasDraw] Static cache disabled: OffscreenCanvas not available.\");\n this.warnedStaticCacheDisabled = true;\n }\n return null;\n }\n\n // Calculate world bounds from items\n let minX = Infinity,\n maxX = -Infinity,\n minY = Infinity,\n maxY = -Infinity;\n\n for (const item of items) {\n const size = item.size ?? 1;\n if (item.x - size / 2 < minX) minX = item.x - size / 2;\n if (item.x + size / 2 > maxX) maxX = item.x + size / 2;\n if (item.y - size / 2 < minY) minY = item.y - size / 2;\n if (item.y + size / 2 > maxY) maxY = item.y + size / 2;\n }\n\n // Add padding\n minX -= 1;\n minY -= 1;\n maxX += 1;\n maxY += 1;\n\n const worldWidth = maxX - minX;\n const worldHeight = maxY - minY;\n\n // Use current scale for rendering\n const renderScale = this.camera.scale;\n const canvasWidth = Math.ceil(worldWidth * renderScale);\n const canvasHeight = Math.ceil(worldHeight * renderScale);\n\n if (canvasWidth > MAX_STATIC_CANVAS_DIMENSION || canvasHeight > MAX_STATIC_CANVAS_DIMENSION) {\n if (!this.warnedStaticCacheDisabled) {\n console.warn(`Static cache disabled: offscreen canvas too large (${canvasWidth}x${canvasHeight}).`);\n this.warnedStaticCacheDisabled = true;\n }\n return null;\n }\n\n // Check if we need to create or update cache\n let cache = this.staticCaches.get(cacheKey);\n const needsRebuild =\n !cache ||\n cache.scale !== renderScale ||\n cache.worldBounds.minX !== minX ||\n cache.worldBounds.maxX !== maxX ||\n cache.worldBounds.minY !== minY ||\n cache.worldBounds.maxY !== maxY;\n\n if (needsRebuild) {\n // Create offscreen canvas\n const offscreen =\n typeof OffscreenCanvas !== \"undefined\"\n ? new OffscreenCanvas(canvasWidth, canvasHeight)\n : document.createElement(\"canvas\");\n\n // Guard instanceof with typeof to avoid ReferenceError when OffscreenCanvas is undefined (e.g., jsdom)\n const isOffscreenCanvas = typeof OffscreenCanvas !== \"undefined\" && offscreen instanceof OffscreenCanvas;\n\n if (!isOffscreenCanvas) {\n (offscreen as HTMLCanvasElement).width = canvasWidth;\n (offscreen as HTMLCanvasElement).height = canvasHeight;\n }\n\n const offCtx = offscreen.getContext(\"2d\") as\n | CanvasRenderingContext2D\n | OffscreenCanvasRenderingContext2D\n | null;\n\n if (!offCtx) {\n if (!this.warnedStaticCacheDisabled) {\n console.warn(\"[CanvasDraw] Static cache disabled: 2D context unavailable.\");\n this.warnedStaticCacheDisabled = true;\n }\n return null;\n }\n\n // Render all items using the provided render function\n for (const item of items) {\n const size = item.size ?? 1;\n const pxSize = size * renderScale;\n const x = (item.x + DEFAULT_VALUES.CELL_CENTER_OFFSET - minX) * renderScale - pxSize / 2;\n const y = (item.y + DEFAULT_VALUES.CELL_CENTER_OFFSET - minY) * renderScale - pxSize / 2;\n\n renderFn(offCtx, item, x, y, pxSize);\n }\n\n cache = {\n canvas: offscreen,\n ctx: offCtx,\n worldBounds: { minX, minY, maxX, maxY },\n scale: renderScale,\n };\n\n this.staticCaches.set(cacheKey, cache);\n }\n\n return cache ?? null;\n }\n\n /**\n * Helper to add a layer callback that blits from a static cache.\n */\n private addStaticCacheLayer(cache: StaticCache | null, layer: number): DrawHandle | null {\n if (!cache) {\n return null;\n }\n const cachedCanvas = cache.canvas;\n const cachedBounds = cache.worldBounds;\n const cachedScale = cache.scale;\n\n return this.layers.add(layer, ({ ctx, config, topLeft }) => {\n const viewportWidth = config.size.width / config.scale;\n const viewportHeight = config.size.height / config.scale;\n\n // === Source Rectangle (cached canvas coordinates) ===\n // These define which region of the offscreen cache canvas to copy FROM\n // Calculated by finding viewport position relative to cache origin, then scaling to cache resolution\n let cacheSourceX = (topLeft.x - cachedBounds.minX) * cachedScale;\n let cacheSourceY = (topLeft.y - cachedBounds.minY) * cachedScale;\n let cacheSourceWidth = viewportWidth * cachedScale;\n let cacheSourceHeight = viewportHeight * cachedScale;\n\n // === Destination Rectangle (screen coordinates) ===\n // These define where to draw the copied region TO on the visible canvas\n // Note: These values get adjusted below when viewport extends beyond cached bounds\n let screenDestX = 0;\n let screenDestY = 0;\n let screenDestWidth = config.size.width;\n let screenDestHeight = config.size.height;\n\n // === Bounds Clamping ===\n // Problem: When viewport pans beyond the cached area, source coordinates become invalid\n // (negative or exceeding cache dimensions). Mobile browsers\n // fail silently or render incorrectly with out-of-bounds drawImage coordinates.\n // Solution: Clamp source rect to valid cache bounds and adjust destination rect to\n // maintain correct positioning - only draw the portion that exists in cache.\n const cacheWidth = cachedCanvas.width;\n const cacheHeight = cachedCanvas.height;\n\n // Clamp left/top: When viewport is beyond cache's top-left corner\n // Shift destination right/down to compensate for the missing cached area\n if (cacheSourceX < 0) {\n const offsetWorld = -cacheSourceX / cachedScale;\n screenDestX = offsetWorld * config.scale;\n screenDestWidth -= screenDestX;\n cacheSourceWidth += cacheSourceX;\n cacheSourceX = 0;\n }\n if (cacheSourceY < 0) {\n const offsetWorld = -cacheSourceY / cachedScale;\n screenDestY = offsetWorld * config.scale;\n screenDestHeight -= screenDestY;\n cacheSourceHeight += cacheSourceY;\n cacheSourceY = 0;\n }\n\n // Clamp right/bottom: When viewport extends past cache's bottom-right corner\n // Shrink destination to avoid stretching the cached content\n if (cacheSourceX + cacheSourceWidth > cacheWidth) {\n const excess = cacheSourceX + cacheSourceWidth - cacheWidth;\n const excessWorld = excess / cachedScale;\n cacheSourceWidth = cacheWidth - cacheSourceX;\n screenDestWidth -= excessWorld * config.scale;\n }\n if (cacheSourceY + cacheSourceHeight > cacheHeight) {\n const excess = cacheSourceY + cacheSourceHeight - cacheHeight;\n const excessWorld = excess / cachedScale;\n cacheSourceHeight = cacheHeight - cacheSourceY;\n screenDestHeight -= excessWorld * config.scale;\n }\n\n // Only draw if there's something to draw\n if (cacheSourceWidth > 0 && cacheSourceHeight > 0 && screenDestWidth > 0 && screenDestHeight > 0) {\n ctx.drawImage(\n cachedCanvas,\n cacheSourceX,\n cacheSourceY,\n cacheSourceWidth,\n cacheSourceHeight,\n screenDestX,\n screenDestY,\n screenDestWidth,\n screenDestHeight\n );\n }\n });\n }\n\n /**\n * Draw rectangles with pre-rendering cache.\n * Renders all items once to an offscreen canvas, then blits the visible portion each frame.\n * Ideal for large static datasets like mini-maps.\n * @param items Array of draw objects\n * @param cacheKey Unique key for this cache (e.g., \"minimap-items\")\n * @param layer Layer order\n */\n drawStaticRect(items: Array<Rect>, cacheKey: string, layer: number = 1): DrawHandle {\n let lastFillStyle: string | undefined;\n\n const cache = this.getOrCreateStaticCache(items, cacheKey, (ctx, item, x, y, pxSize) => {\n const style = item.style;\n const rotationDeg = item.rotate ?? 0;\n const rotation = rotationDeg * (Math.PI / 180);\n const radius = item.radius;\n\n if (style?.fillStyle && style.fillStyle !== lastFillStyle) {\n ctx.fillStyle = style.fillStyle;\n lastFillStyle = style.fillStyle;\n }\n\n if (rotationDeg !== 0) {\n const centerX = x + pxSize / 2;\n const centerY = y + pxSize / 2;\n ctx.save();\n ctx.translate(centerX, centerY);\n ctx.rotate(rotation);\n if (radius && ctx.roundRect) {\n ctx.beginPath();\n ctx.roundRect(-pxSize / 2, -pxSize / 2, pxSize, pxSize, radius);\n ctx.fill();\n } else {\n ctx.fillRect(-pxSize / 2, -pxSize / 2, pxSize, pxSize);\n }\n ctx.restore();\n } else {\n if (radius && ctx.roundRect) {\n ctx.beginPath();\n ctx.roundRect(x, y, pxSize, pxSize, radius);\n ctx.fill();\n } else {\n ctx.fillRect(x, y, pxSize, pxSize);\n }\n }\n });\n\n if (!cache) {\n return this.drawRect(items, layer);\n }\n\n return this.addStaticCacheLayer(cache, layer)!;\n }\n\n /**\n * Draw images with pre-rendering cache.\n * Renders all items once to an offscreen canvas, then blits the visible portion each frame.\n * Ideal for large static datasets like terrain tiles or static decorations.\n * @param items Array of image objects with position and HTMLImageElement\n * @param cacheKey Unique key for this cache (e.g., \"terrain-cache\")\n * @param layer Layer order\n */\n drawStaticImage(items: Array<ImageItem>, cacheKey: string, layer: number = 1): DrawHandle {\n const cache = this.getOrCreateStaticCache(items, cacheKey, (ctx, item, x, y, pxSize) => {\n const img = (item as { img: HTMLImageElement }).img;\n const rotationDeg = (item as { rotate?: number }).rotate ?? 0;\n const rotation = rotationDeg * (Math.PI / 180);\n const aspect = img.width / img.height;\n let drawW = pxSize;\n let drawH = pxSize;\n\n if (aspect > 1) drawH = pxSize / aspect;\n else drawW = pxSize * aspect;\n\n // x, y are top-left of pxSize box, need to center image within it\n const imgX = x + (pxSize - drawW) / 2;\n const imgY = y + (pxSize - drawH) / 2;\n\n if (rotationDeg !== 0) {\n const centerX = imgX + drawW / 2;\n const centerY = imgY + drawH / 2;\n ctx.save();\n ctx.translate(centerX, centerY);\n ctx.rotate(rotation);\n ctx.drawImage(img, -drawW / 2, -drawH / 2, drawW, drawH);\n ctx.restore();\n } else {\n ctx.drawImage(img, imgX, imgY, drawW, drawH);\n }\n });\n\n if (!cache) {\n return this.drawImage(items, layer);\n }\n\n return this.addStaticCacheLayer(cache, layer)!;\n }\n\n /**\n * Draw circles with pre-rendering cache.\n * Renders all items once to an offscreen canvas, then blits the visible portion each frame.\n * Ideal for large static datasets like mini-maps.\n * @param items Array of draw objects\n * @param cacheKey Unique key for this cache (e.g., \"minimap-circles\")\n * @param layer Layer order\n */\n drawStaticCircle(items: Array<Circle>, cacheKey: string, layer: number = 1): DrawHandle {\n let lastFillStyle: string | undefined;\n\n const cache = this.getOrCreateStaticCache(items, cacheKey, (ctx, item, x, y, pxSize) => {\n const style = item.style;\n const radius = pxSize / 2;\n\n if (style?.fillStyle && style.fillStyle !== lastFillStyle) {\n ctx.fillStyle = style.fillStyle;\n lastFillStyle = style.fillStyle;\n }\n\n ctx.beginPath();\n ctx.arc(x + radius, y + radius, radius, 0, Math.PI * 2);\n ctx.fill();\n });\n\n if (!cache) {\n return this.drawCircle(items, layer);\n }\n\n return this.addStaticCacheLayer(cache, layer)!;\n }\n\n /**\n * Clear a static cache\n * @param cacheKey The cache key to clear, or undefined to clear all\n */\n clearStaticCache(cacheKey?: string) {\n if (cacheKey) {\n this.staticCaches.delete(cacheKey);\n } else {\n this.staticCaches.clear();\n }\n }\n\n /**\n * LAYER METHODS\n */\n /**\n * Remove a specific draw callback by handle (canvas renderer only).\n * Does not clear other callbacks on the same layer.\n */\n removeDrawHandle(handle: DrawHandle) {\n if (!this.layers) {\n throw new Error(\"removeDrawHandle is only available when renderer is set to 'canvas'.\");\n }\n this.layers.remove(handle);\n }\n\n /**\n * Clear all draw callbacks from a specific layer (canvas renderer only).\n * Use this before redrawing dynamic content to prevent accumulation.\n * @param layer Layer index to clear.\n * @example\n * ```ts\n * engine.clearLayer(1);\n * engine.drawRect(newRects, 1);\n * engine.render();\n * ```\n */\n clearLayer(layer: number) {\n if (!this.layers) {\n throw new Error(\"clearLayer is only available when renderer is set to 'canvas'.\");\n }\n this.layers.clear(layer);\n }\n\n /**\n * Clear all draw callbacks from all layers (canvas renderer only).\n * Useful for complete scene reset.\n * @example\n * ```ts\n * engine.clearAll();\n * // Redraw everything from scratch\n * ```\n */\n clearAll() {\n if (!this.layers) {\n throw new Error(\"clearAll is only available when renderer is set to 'canvas'.\");\n }\n this.layers.clear();\n }\n\n /**\n * Release cached canvases and layer callbacks.\n */\n destroy() {\n this.staticCaches.clear();\n this.layers.clear();\n }\n}\n","import { CanvasTileEngineConfig } from \"@canvas-tile-engine/core\";\n\nexport function initStyles(\n canvasWrapper: HTMLDivElement,\n canvas: HTMLCanvasElement,\n isResponsive: CanvasTileEngineConfig[\"responsive\"],\n width?: number,\n height?: number\n) {\n if (isResponsive) {\n Object.assign(canvasWrapper.style, {\n position: \"relative\",\n overflow: \"hidden\",\n });\n } else {\n Object.assign(canvasWrapper.style, {\n position: \"relative\",\n overflow: \"hidden\",\n width: width + \"px\",\n height: height + \"px\",\n });\n }\n\n Object.assign(canvas.style, {\n position: \"absolute\",\n top: \"0\",\n left: \"0\",\n });\n}\n\n/**\n * Apply lineWidth to canvas context with alpha fallback for values < 1.\n * For lineWidth < 1, uses globalAlpha to simulate thinner lines while keeping lineWidth at 1.\n * This ensures consistent rendering across browsers and DPR settings.\n *\n * @param ctx Canvas rendering context\n * @param lineWidth Desired line width (can be < 1 for semi-transparent thin lines)\n * @returns Cleanup function that resets globalAlpha to 1\n *\n */\nexport function applyLineWidth(\n ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,\n lineWidth: number\n): () => void {\n if (lineWidth >= 1) {\n ctx.lineWidth = lineWidth;\n return () => {}; // No cleanup needed\n }\n\n // For lineWidth < 1, use alpha to simulate thinner lines\n const alpha = Math.max(0, Math.min(lineWidth, 1));\n ctx.lineWidth = 1;\n ctx.globalAlpha = alpha;\n\n return () => {\n ctx.globalAlpha = 1;\n };\n}\n","import { CanvasTileEngineConfig, CoordinateTransformer, Coords, ICamera } from \"@canvas-tile-engine/core\";\n\n/** @internal */\nexport type DrawContext = {\n ctx: CanvasRenderingContext2D;\n camera: ICamera;\n transformer: CoordinateTransformer;\n config: Required<CanvasTileEngineConfig>;\n topLeft: Coords;\n};\n\n/** @internal */\nexport type DrawCallback = (dc: DrawContext) => void;\n\nexport interface DrawHandle {\n layer: number;\n id: symbol;\n}\n\n/**\n * Manages ordered draw callbacks for canvas rendering.\n * @internal\n */\nexport class Layer {\n private layers = new Map<number, { id: symbol; fn: DrawCallback }[]>();\n\n /**\n * Register a draw callback at a specific layer index.\n * @param layer Layer order; lower numbers draw first.\n * @param fn Callback receiving drawing context.\n */\n add(layer: number, fn: DrawCallback): DrawHandle {\n const id = Symbol(\"layer-callback\");\n const entry = { id, fn };\n if (!this.layers.has(layer)) this.layers.set(layer, []);\n this.layers.get(layer)!.push(entry);\n return { layer, id };\n }\n\n /**\n * Remove a previously registered callback.\n * Safe to call multiple times; no-op if not found.\n */\n remove(handle: DrawHandle) {\n const list = this.layers.get(handle.layer);\n if (!list) return;\n this.layers.set(\n handle.layer,\n list.filter((entry) => entry.id !== handle.id)\n );\n }\n\n /**\n * Clear callbacks for a layer or all layers.\n * @param layer Layer to clear; clears all when omitted.\n */\n clear(layer?: number) {\n if (layer === undefined) {\n this.layers.clear();\n return;\n }\n this.layers.set(layer, []);\n }\n\n /**\n * Draw all registered callbacks in layer order.\n * @param dc Drawing context shared with callbacks.\n */\n drawAll(dc: DrawContext) {\n const keys = [...this.layers.keys()].sort((a, b) => a - b);\n for (const layer of keys) {\n const fns = this.layers.get(layer);\n if (!fns) continue;\n for (const { fn } of fns) {\n dc.ctx.save();\n fn(dc);\n dc.ctx.restore();\n }\n }\n }\n}\n","import { Config, COORDINATE_OVERLAY, ICamera, ViewportState } from \"@canvas-tile-engine/core\";\n\n/**\n * Renders a coordinate overlay (axes and labels) on top of the canvas.\n * @internal\n */\nexport class CoordinateOverlayRenderer {\n private ctx: CanvasRenderingContext2D;\n private camera: ICamera;\n private config: Config;\n private viewport: ViewportState;\n\n /**\n * @param ctx Canvas context to draw on.\n * @param camera Active camera for position/scale.\n * @param config Normalized grid engine configuration store.\n * @param viewport Mutable viewport size store.\n */\n constructor(ctx: CanvasRenderingContext2D, camera: ICamera, config: Config, viewport: ViewportState) {\n this.ctx = ctx;\n this.camera = camera;\n this.config = config;\n this.viewport = viewport;\n }\n\n /**\n * Draw overlay borders and coordinate labels based on current camera view.\n */\n draw() {\n // Save the current canvas state\n this.ctx.save();\n\n // Set fill style to black with configured opacity\n this.ctx.fillStyle = `rgba(0, 0, 0, ${COORDINATE_OVERLAY.BORDER_OPACITY})`;\n\n // Draw left border - 20px wide, full height\n const { width, height } = this.viewport.getSize();\n this.ctx.fillRect(0, 0, COORDINATE_OVERLAY.BORDER_WIDTH, height);\n\n // Draw bottom border - full width, 20px high\n this.ctx.fillRect(\n COORDINATE_OVERLAY.BORDER_WIDTH,\n height - COORDINATE_OVERLAY.BORDER_WIDTH,\n width,\n COORDINATE_OVERLAY.BORDER_WIDTH\n );\n\n // Set text properties for coordinates\n this.ctx.fillStyle = `rgba(255, 255, 255, ${COORDINATE_OVERLAY.TEXT_OPACITY})`;\n\n // Adjust font size based on scale (min 8px, max 12px)\n const fontSize = Math.min(\n COORDINATE_OVERLAY.MAX_FONT_SIZE,\n Math.max(COORDINATE_OVERLAY.MIN_FONT_SIZE, this.camera.scale * COORDINATE_OVERLAY.FONT_SIZE_SCALE_FACTOR)\n );\n this.ctx.font = `${fontSize}px Arial`;\n this.ctx.textAlign = \"center\";\n this.ctx.textBaseline = \"middle\";\n\n const cordGap = this.camera.scale;\n const visibleAreaWidthInCords = width / cordGap;\n const visibleAreaHeightInCords = height / cordGap;\n\n // Draw Y coordinates (left side)\n for (let i = 0 - (this.camera.y % 1); i <= visibleAreaHeightInCords + 1; i++) {\n this.ctx.fillText(Math.round(this.camera.y + i).toString(), 10, cordGap * i + cordGap / 2);\n }\n\n // Draw X coordinates (bottom)\n for (let i = 0 - (this.camera.x % 1); i <= visibleAreaWidthInCords + 1; i++) {\n this.ctx.fillText(Math.round(this.camera.x + i).toString(), cordGap * i + cordGap / 2, height - 10);\n }\n\n // Restore the canvas state\n this.ctx.restore();\n }\n\n /**\n * Decide whether overlay should be drawn at current scale and config.\n * @param scale Current camera scale.\n * @param coordsConfig Coordinate overlay config.\n * @returns True if overlay is enabled and scale is within range.\n */\n shouldDraw(scale: number): boolean {\n const coordsConfig = this.config.get().coordinates;\n\n if (!coordsConfig.enabled) {\n return false;\n }\n\n if (!coordsConfig.shownScaleRange) {\n return false;\n }\n\n const { min, max } = coordsConfig.shownScaleRange;\n\n return scale >= min && scale <= max;\n }\n}\n","import { Config, DEBUG_HUD, ICamera, ViewportState } from \"@canvas-tile-engine/core\";\n\nconst FPS_SAMPLE_SIZE = 10;\n\n/**\n * Canvas-only debug overlay: draws grid and HUD information.\n * @internal\n */\nexport class CanvasDebug {\n private ctx: CanvasRenderingContext2D;\n private camera: ICamera;\n private config: Config;\n private viewport: ViewportState;\n\n // FPS tracking (runs continuously via rAF)\n private frameTimes: number[] = [];\n private lastFrameTime = 0;\n private currentFps = 0;\n private fpsLoopRunning = false;\n private onFpsUpdate: (() => void) | null = null;\n\n constructor(ctx: CanvasRenderingContext2D, camera: ICamera, config: Config, viewport: ViewportState) {\n this.ctx = ctx;\n this.camera = camera;\n this.config = config;\n this.viewport = viewport;\n }\n\n /**\n * Set callback for FPS updates (triggers re-render)\n */\n setFpsUpdateCallback(callback: () => void) {\n this.onFpsUpdate = callback;\n }\n\n /**\n * Start FPS monitoring loop\n */\n startFpsLoop() {\n if (this.fpsLoopRunning) return;\n this.fpsLoopRunning = true;\n this.lastFrameTime = performance.now();\n this.fpsLoop();\n }\n\n /**\n * Stop FPS monitoring loop\n */\n stopFpsLoop() {\n this.fpsLoopRunning = false;\n }\n\n private fpsLoop() {\n if (!this.fpsLoopRunning) return;\n\n const now = performance.now();\n const delta = now - this.lastFrameTime;\n this.lastFrameTime = now;\n\n this.frameTimes.push(delta);\n if (this.frameTimes.length > FPS_SAMPLE_SIZE) {\n this.frameTimes.shift();\n }\n\n const avgDelta = this.frameTimes.reduce((a, b) => a + b, 0) / this.frameTimes.length;\n const newFps = Math.round(1000 / avgDelta);\n\n // Only trigger update if FPS changed\n if (newFps !== this.currentFps) {\n this.currentFps = newFps;\n this.onFpsUpdate?.();\n }\n\n requestAnimationFrame(() => this.fpsLoop());\n }\n\n draw() {\n this.drawHud();\n }\n\n /**\n * Stop FPS tracking and release callbacks.\n */\n destroy() {\n this.stopFpsLoop();\n this.onFpsUpdate = null;\n }\n\n private drawHud() {\n const config = this.config.get();\n\n if (!config.debug.hud) {\n return;\n }\n\n if (!config.debug.hud.enabled) {\n return;\n }\n\n const datas = [];\n\n const topLeft = { x: this.camera.x, y: this.camera.y };\n\n if (config.debug.hud.topLeftCoordinates) {\n datas.push(`TopLeft: ${topLeft.x.toFixed(2)}, ${topLeft.y.toFixed(2)}`);\n }\n\n if (config.debug.hud.coordinates) {\n const { width, height } = this.viewport.getSize();\n const center = this.camera.getCenter(width, height);\n datas.push(`Coords: ${center.x.toFixed(2)}, ${center.y.toFixed(2)}`);\n }\n\n if (config.debug.hud.scale) {\n datas.push(`Scale: ${this.camera.scale.toFixed(2)}`);\n }\n\n if (config.debug.hud.tilesInView) {\n const { width, height } = this.viewport.getSize();\n datas.push(\n `Tiles in view: ${Math.ceil(width / this.camera.scale)} x ${Math.ceil(height / this.camera.scale)}`\n );\n }\n\n if (config.debug.hud.fps) {\n datas.push(`FPS: ${this.currentFps}`);\n }\n\n const { width } = this.viewport.getSize();\n\n this.ctx.save();\n this.ctx.fillStyle = \"rgba(0,0,0,0.5)\";\n this.ctx.fillRect(\n width - DEBUG_HUD.PANEL_WIDTH - DEBUG_HUD.PADDING,\n DEBUG_HUD.PADDING / 2,\n DEBUG_HUD.PANEL_WIDTH,\n datas.length * DEBUG_HUD.LINE_HEIGHT + DEBUG_HUD.PADDING\n );\n\n this.ctx.fillStyle = \"#00ff99\";\n this.ctx.font = \"12px monospace\";\n\n for (let i = 0; i < datas.length; i++) {\n this.ctx.fillText(\n datas[i],\n width - DEBUG_HUD.PANEL_WIDTH - DEBUG_HUD.PADDING + 5,\n 18 + i * DEBUG_HUD.LINE_HEIGHT\n );\n }\n\n this.ctx.restore();\n }\n}\n","type HandlerMap = {\n click?: (e: MouseEvent) => void;\n contextmenu?: (e: MouseEvent) => void;\n mousedown?: (e: MouseEvent) => void;\n mousemove?: (e: MouseEvent) => void;\n mouseup?: (e: MouseEvent) => void;\n mouseleave?: (e: MouseEvent) => void;\n wheel?: (e: WheelEvent) => void;\n touchstart?: (e: TouchEvent) => void;\n touchmove?: (e: TouchEvent) => void;\n touchend?: (e: TouchEvent) => void;\n};\n\n/**\n * Thin wrapper to attach/detach DOM event listeners on the canvas.\n * @internal\n */\nexport class EventBinder {\n constructor(private canvas: HTMLCanvasElement, private handlers: HandlerMap) {}\n\n attach() {\n if (this.handlers.click) {\n this.canvas.addEventListener(\"click\", this.handlers.click);\n }\n\n if (this.handlers.contextmenu) {\n this.canvas.addEventListener(\"contextmenu\", this.handlers.contextmenu);\n }\n\n if (this.handlers.mousedown) {\n this.canvas.addEventListener(\"mousedown\", this.handlers.mousedown);\n }\n\n if (this.handlers.mousemove) {\n this.canvas.addEventListener(\"mousemove\", this.handlers.mousemove);\n }\n\n if (this.handlers.mouseup) {\n this.canvas.addEventListener(\"mouseup\", this.handlers.mouseup);\n }\n\n if (this.handlers.mouseleave) {\n this.canvas.addEventListener(\"mouseleave\", this.handlers.mouseleave);\n }\n\n if (this.handlers.wheel) {\n this.canvas.addEventListener(\"wheel\", this.handlers.wheel, { passive: false });\n }\n\n if (this.handlers.touchstart) {\n this.canvas.addEventListener(\"touchstart\", this.handlers.touchstart, { passive: false });\n }\n\n if (this.handlers.touchmove) {\n this.canvas.addEventListener(\"touchmove\", this.handlers.touchmove, { passive: false });\n }\n\n if (this.handlers.touchend) {\n this.canvas.addEventListener(\"touchend\", this.handlers.touchend, { passive: false });\n }\n }\n\n detach() {\n if (this.handlers.click) {\n this.canvas.removeEventListener(\"click\", this.handlers.click);\n }\n\n if (this.handlers.contextmenu) {\n this.canvas.removeEventListener(\"contextmenu\", this.handlers.contextmenu);\n }\n\n if (this.handlers.mousedown) {\n this.canvas.removeEventListener(\"mousedown\", this.handlers.mousedown);\n }\n\n if (this.handlers.mousemove) {\n this.canvas.removeEventListener(\"mousemove\", this.handlers.mousemove);\n }\n\n if (this.handlers.mouseup) {\n this.canvas.removeEventListener(\"mouseup\", this.handlers.mouseup);\n }\n\n if (this.handlers.mouseleave) {\n this.canvas.removeEventListener(\"mouseleave\", this.handlers.mouseleave);\n }\n\n if (this.handlers.wheel) {\n this.canvas.removeEventListener(\"wheel\", this.handlers.wheel);\n }\n\n if (this.handlers.touchstart) {\n this.canvas.removeEventListener(\"touchstart\", this.handlers.touchstart);\n }\n\n if (this.handlers.touchmove) {\n this.canvas.removeEventListener(\"touchmove\", this.handlers.touchmove);\n }\n\n if (this.handlers.touchend) {\n this.canvas.removeEventListener(\"touchend\", this.handlers.touchend);\n }\n }\n}\n","import { Config, ICamera, ViewportState } from \"@canvas-tile-engine/core\";\n\n/**\n * Observes canvas resizing and keeps viewport/camera in sync.\n * @internal\n */\nexport class ResizeWatcher {\n private resizeObserver?: ResizeObserver;\n private handleWindowResize?: () => void;\n private currentDpr: number;\n\n public onResize?: () => void;\n\n constructor(\n private wrapper: HTMLDivElement,\n private canvas: HTMLCanvasElement,\n private viewport: ViewportState,\n private camera: ICamera,\n private config: Config,\n private onCameraChange: () => void\n ) {\n this.currentDpr = this.viewport.dpr;\n }\n\n start() {\n // Ensure DPR is up to date before sizing\n this.viewport.updateDpr();\n this.currentDpr = this.viewport.dpr;\n\n const size = this.viewport.getSize();\n\n const configSize = this.config.get().size;\n\n const maxWidth = configSize?.maxWidth;\n const maxHeight = configSize?.maxHeight;\n\n const minWidth = configSize?.minWidth;\n const minHeight = configSize?.minHeight;\n\n size.width = this.clamp(size.width, minWidth, maxWidth);\n size.height = this.clamp(size.height, minHeight, maxHeight);\n\n Object.assign(this.wrapper.style, {\n resize: \"both\",\n overflow: \"hidden\",\n width: `${size.width}px`,\n height: `${size.height}px`,\n touchAction: \"none\",\n position: \"relative\",\n maxWidth: maxWidth ? `${maxWidth}px` : \"\",\n maxHeight: maxHeight ? `${maxHeight}px` : \"\",\n minWidth: minWidth ? `${minWidth}px` : \"\",\n minHeight: minHeight ? `${minHeight}px` : \"\",\n });\n\n this.resizeObserver = new ResizeObserver((entries) => {\n for (const entry of entries) {\n const { width: rawW, height: rawH } = entry.contentRect;\n const width = this.clamp(rawW, minWidth, maxWidth);\n const height = this.clamp(rawH, minHeight, maxHeight);\n const prev = this.viewport.getSize();\n\n if (width === prev.width && height === prev.height) {\n // No effective size change after clamping\n continue;\n }\n\n const diffW = width - prev.width;\n const diffH = height - prev.height;\n const dpr = this.viewport.dpr;\n\n this.camera.adjustForResize(diffW, diffH);\n this.viewport.setSize(width, height);\n\n // Set canvas resolution (physical pixels for HiDPI)\n this.canvas.width = width * dpr;\n this.canvas.height = height * dpr;\n\n // Set CSS size (logical pixels)\n this.canvas.style.width = `${width}px`;\n this.canvas.style.height = `${height}px`;\n\n this.wrapper.style.width = `${width}px`;\n this.wrapper.style.height = `${height}px`;\n\n if (this.onResize) {\n this.onResize();\n }\n this.onCameraChange();\n }\n });\n\n this.resizeObserver.observe(this.wrapper);\n\n this.attachDprWatcher();\n }\n\n stop() {\n if (this.resizeObserver) {\n this.resizeObserver.unobserve(this.wrapper);\n this.resizeObserver.disconnect();\n }\n\n this.resizeObserver = undefined;\n\n if (this.handleWindowResize) {\n window.removeEventListener(\"resize\", this.handleWindowResize);\n this.handleWindowResize = undefined;\n }\n }\n\n private clamp(value: number, min?: number, max?: number) {\n let result = value;\n if (min !== undefined) result = Math.max(min, result);\n if (max !== undefined) result = Math.min(max, result);\n return result;\n }\n\n /**\n * Listen for devicePixelRatio changes (e.g., monitor switch) and rescale canvas.\n */\n private attachDprWatcher() {\n if (typeof window === \"undefined\") {\n return;\n }\n\n this.handleWindowResize = () => {\n const prevDpr = this.currentDpr;\n this.viewport.updateDpr();\n const nextDpr = this.viewport.dpr;\n\n if (nextDpr === prevDpr) {\n return;\n }\n\n this.currentDpr = nextDpr;\n const { width, height } = this.viewport.getSize();\n\n // Update canvas resolution for new DPR while keeping logical size\n this.canvas.width = width * nextDpr;\n this.canvas.height = height * nextDpr;\n this.canvas.style.width = `${width}px`;\n this.canvas.style.height = `${height}px`;\n\n if (this.onResize) {\n this.onResize();\n }\n\n this.onCameraChange();\n };\n\n window.addEventListener(\"resize\", this.handleWindowResize, { passive: true });\n }\n}\n","import { Config, ICamera, ViewportState } from \"@canvas-tile-engine/core\";\n\n/**\n * Watches wrapper element size changes and handles responsive resizing.\n * Supports two modes:\n * - \"preserve-scale\": Scale stays constant, visible tile count changes\n * - \"preserve-viewport\": Visible tile count stays constant, scale changes\n * @internal\n */\nexport class ResponsiveWatcher {\n private resizeObserver?: ResizeObserver;\n private handleWindowResize?: () => void;\n private currentDpr: number;\n\n /** Initial visible tiles (used for preserve-viewport mode) */\n private initialVisibleTiles: { x: number; y: number };\n\n /** Width limits derived from scale limits (preserve-viewport mode) */\n private widthLimits: { min: number; max: number };\n\n /** Callback fired after responsive resize */\n public onResize?: () => void;\n\n constructor(\n private wrapper: HTMLDivElement,\n private canvas: HTMLCanvasElement,\n private camera: ICamera,\n private viewport: ViewportState,\n private config: Config,\n private onRender: () => void\n ) {\n this.currentDpr = this.viewport.dpr;\n\n // Calculate initial visible tiles from config (reference for preserve-viewport)\n const cfg = this.config.get();\n this.initialVisibleTiles = {\n x: cfg.size.width / cfg.scale,\n y: cfg.size.height / cfg.scale,\n };\n\n // Calculate width limits from scale limits (for preserve-viewport mode)\n this.widthLimits = {\n min: cfg.minScale * this.initialVisibleTiles.x,\n max: cfg.maxScale * this.initialVisibleTiles.x,\n };\n }\n\n start() {\n const responsiveMode = this.config.get().responsive;\n if (!responsiveMode) {\n return;\n }\n\n // Ensure DPR is up to date\n this.viewport.updateDpr();\n this.currentDpr = this.viewport.dpr;\n\n // Set wrapper dimensions based on responsive mode\n if (responsiveMode === \"preserve-viewport\") {\n const aspectRatio = this.initialVisibleTiles.y / this.initialVisibleTiles.x;\n this.wrapper.style.width = \"100%\";\n this.wrapper.style.minWidth = `${this.widthLimits.min}px`;\n this.wrapper.style.maxWidth = `${this.widthLimits.max}px`;\n this.wrapper.style.minHeight = `${this.widthLimits.min * aspectRatio}px`;\n this.wrapper.style.maxHeight = `${this.widthLimits.max * aspectRatio}px`;\n } else {\n // preserve-scale: width is responsive, height stays at initial config value\n const cfg = this.config.get();\n this.wrapper.style.width = \"100%\";\n this.wrapper.style.height = `${cfg.size.height}px`;\n }\n\n // Get initial size from wrapper (user controls via CSS)\n const wrapperRect = this.wrapper.getBoundingClientRect();\n const initialWidth = Math.round(wrapperRect.width);\n const initialHeight = Math.round(wrapperRect.height);\n\n // Apply initial size\n this.applySize(initialWidth, initialHeight, responsiveMode);\n\n // Setup ResizeObserver to watch wrapper\n this.resizeObserver = new ResizeObserver((entries) => {\n for (const entry of entries) {\n const { width: rawW, height: rawH } = entry.contentRect;\n const width = Math.round(rawW);\n const height = Math.round(rawH);\n\n if (width <= 0 || height <= 0) {\n continue;\n }\n\n const prev = this.viewport.getSize();\n if (width === prev.width && height === prev.height) {\n continue;\n }\n\n this.applySize(width, height, responsiveMode);\n\n this.onResize?.();\n this.onRender();\n }\n });\n\n this.resizeObserver.observe(this.wrapper);\n this.attachDprWatcher();\n }\n\n stop() {\n if (this.resizeObserver) {\n this.resizeObserver.unobserve(this.wrapper);\n this.resizeObserver.disconnect();\n this.resizeObserver = undefined;\n }\n\n if (this.handleWindowResize) {\n window.removeEventListener(\"resize\", this.handleWindowResize);\n this.handleWindowResize = undefined;\n }\n }\n\n private applySize(width: number, height: number, mode: \"preserve-scale\" | \"preserve-viewport\") {\n const dpr = this.viewport.dpr;\n const prev = this.viewport.getSize();\n\n if (mode === \"preserve-viewport\") {\n // Calculate new scale to maintain the same visible tile count\n const newScale = width / this.initialVisibleTiles.x;\n\n // Calculate height based on configured tile ratio\n const calculatedHeight = Math.round(this.initialVisibleTiles.y * newScale);\n height = calculatedHeight;\n\n // Apply calculated height to wrapper (width is 100%, max-width handles limits)\n this.wrapper.style.height = `${calculatedHeight}px`;\n\n // Save current center before changing scale\n const currentCenter = this.camera.getCenter(prev.width, prev.height);\n\n this.camera.setScale(newScale);\n\n // Restore center after scale change (must use new dimensions)\n this.camera.setCenter(currentCenter, width, height);\n } else {\n // preserve-scale: adjust camera position to keep center stable\n const diffW = width - prev.width;\n const diffH = height - prev.height;\n this.camera.adjustForResize(diffW, diffH);\n }\n\n // Update viewport\n this.viewport.setSize(width, height);\n\n // Update canvas resolution (physical pixels for HiDPI)\n this.canvas.width = width * dpr;\n this.canvas.height = height * dpr;\n\n // Update canvas CSS size (logical pixels)\n this.canvas.style.width = `${width}px`;\n this.canvas.style.height = `${height}px`;\n }\n\n /**\n * Listen for devicePixelRatio changes (e.g., monitor switch) and rescale canvas.\n */\n private attachDprWatcher() {\n if (typeof window === \"undefined\") {\n return;\n }\n\n this.handleWindowResize = () => {\n const prevDpr = this.currentDpr;\n this.viewport.updateDpr();\n const nextDpr = this.viewport.dpr;\n\n if (nextDpr === prevDpr) {\n return;\n }\n\n this.currentDpr = nextDpr;\n const { width, height } = this.viewport.getSize();\n\n // Update canvas resolution for new DPR while keeping logical size\n this.canvas.width = width * nextDpr;\n this.canvas.height = height * nextDpr;\n this.canvas.style.width = `${width}px`;\n this.canvas.style.height = `${height}px`;\n\n this.onResize?.();\n this.onRender();\n };\n\n window.addEventListener(\"resize\", this.handleWindowResize, { passive: true });\n }\n}\n","import { IImageLoader } from \"@canvas-tile-engine/core\";\n\nconst DEFAULT_IMAGE_LOAD_RETRY_COUNT = 1;\n\n/**\n * DOM-based image loader with in-memory caching to avoid duplicate network requests.\n * Implements IImageLoader for HTMLImageElement.\n */\nexport class ImageLoader implements IImageLoader<HTMLImageElement> {\n private cache = new Map<string, HTMLImageElement>();\n private inflight = new Map<string, Promise<HTMLImageElement>>();\n private listeners = new Set<() => void>();\n\n /**\n * Register a callback fired when a new image finishes loading.\n */\n onLoad(cb: () => void): () => void {\n this.listeners.add(cb);\n return () => this.listeners.delete(cb);\n }\n\n private notifyLoaded() {\n for (const cb of this.listeners) {\n cb();\n }\n }\n\n /**\n * Load an image, reusing cache when possible.\n * @param src Image URL.\n * @param retry How many times to retry on error (default: 1).\n * @returns Promise resolving to the loaded image element.\n */\n async load(src: string, retry: number = DEFAULT_IMAGE_LOAD_RETRY_COUNT): Promise<HTMLImageElement> {\n // Cached\n if (this.cache.has(src)) {\n return this.cache.get(src)!;\n }\n\n // Inflight\n if (this.inflight.has(src)) {\n return this.inflight.get(src)!;\n }\n\n const task = new Promise<HTMLImageElement>((resolve, reject) => {\n const img = new Image();\n img.crossOrigin = \"anonymous\";\n img.decoding = \"async\";\n img.loading = \"eager\";\n\n img.onload = async () => {\n try {\n // Wait for decode to finish if supported\n if (\"decode\" in img) {\n await (img as HTMLImageElement & { decode?: () => Promise<void> }).decode?.();\n }\n } catch {\n // ignore decode errors; draw will still attempt\n }\n\n this.cache.set(src, img);\n this.inflight.delete(src);\n this.notifyLoaded();\n resolve(img);\n };\n\n img.onerror = (err) => {\n this.inflight.delete(src);\n if (retry > 0) {\n console.warn(`Retrying image: ${src}`);\n resolve(this.load(src, retry - 1));\n } else {\n console.error(`Image failed to load: ${src}`, err);\n const reason =\n err instanceof Error ? err.message : typeof err === \"string\" ? err : JSON.stringify(err);\n reject(new Error(`Image failed to load: ${src}. Reason: ${reason}`));\n }\n };\n\n img.src = src;\n });\n\n this.inflight.set(src, task);\n return task;\n }\n\n /**\n * Get a cached image without loading.\n * @param src Image URL key.\n */\n get(src: string): HTMLImageElement | undefined {\n return this.cache.get(src);\n }\n\n /**\n * Check if an image is already cached.\n * @param src Image URL key.\n */\n has(src: string): boolean {\n return this.cache.has(src);\n }\n\n /**\n * Clear all cached and inflight images/listeners to free memory.\n */\n clear() {\n this.cache.clear();\n this.inflight.clear();\n this.listeners.clear();\n }\n}\n","import { AnimationController, Config, Coords, ICamera, ViewportState } from \"@canvas-tile-engine/core\";\n\n/**\n * Controls canvas size and handles resize animations.\n * Manages DOM manipulation for wrapper and canvas elements.\n */\nexport class SizeController {\n constructor(\n private canvasWrapper: HTMLDivElement,\n private canvas: HTMLCanvasElement,\n private camera: ICamera,\n private viewport: ViewportState,\n private config: Config,\n private onRender: () => void\n ) {}\n\n /**\n * Manually update canvas size using AnimationController for smooth transitions.\n * @param width New canvas width in pixels.\n * @param height New canvas height in pixels.\n * @param durationMs Animation duration in ms (default 500). Use 0 for instant resize.\n * @param animationController AnimationController instance to handle the animation.\n * @param onComplete Optional callback fired when resize animation completes.\n */\n resizeWithAnimation(\n width: number,\n height: number,\n durationMs: number,\n animationController: AnimationController,\n onComplete?: () => void\n ) {\n if (width <= 0 || height <= 0) {\n return;\n }\n\n const configSize = this.config.get().size;\n const clamp = (value: number, min?: number, max?: number) => {\n let result = value;\n if (min !== undefined) {\n result = Math.max(min, result);\n }\n if (max !== undefined) {\n result = Math.min(max, result);\n }\n return result;\n };\n\n // Clamp to min/max values\n width = clamp(width, configSize?.minWidth, configSize?.maxWidth);\n height = clamp(height, configSize?.minHeight, configSize?.maxHeight);\n\n // Delegate to AnimationController\n animationController.animateResize(\n width,\n height,\n durationMs,\n (w: number, h: number, center: Coords) => this.applySize(w, h, center),\n onComplete\n );\n }\n\n /**\n * Apply size directly without animation.\n * @param width New canvas width in pixels.\n * @param height New canvas height in pixels.\n * @param center Center coordinates to maintain after resize.\n */\n applySize(width: number, height: number, center: Coords) {\n const roundedW = Math.round(width);\n const roundedH = Math.round(height);\n const dpr = this.viewport.dpr;\n\n this.viewport.setSize(roundedW, roundedH);\n\n // CSS size (logical pixels)\n this.canvasWrapper.style.width = `${roundedW}px`;\n this.canvasWrapper.style.height = `${roundedH}px`;\n\n // Canvas resolution (physical pixels for HiDPI)\n this.canvas.width = roundedW * dpr;\n this.canvas.height = roundedH * dpr;\n this.canvas.style.width = `${roundedW}px`;\n this.canvas.style.height = `${roundedH}px`;\n\n this.camera.setCenter(center, roundedW, roundedH);\n this.onRender();\n }\n}\n"],"mappings":"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","names":["AnimationController","GestureProcessor","DEFAULT_VALUES","SpatialIndex","VISIBILITY_BUFFER","initStyles","canvasWrapper","canvas","isResponsive","width","height","applyLineWidth","ctx","lineWidth","alpha","SPATIAL_INDEX_THRESHOLD","MAX_STATIC_CANVAS_DIMENSION","CanvasDraw","layers","transformer","camera","x","y","sizeWorld","topLeft","config","viewW","viewH","minX","VISIBILITY_BUFFER","minY","maxX","maxY","fn","layer","ctx","items","list","spatialIndex","SpatialIndex","bounds","visibleItems","lastFillStyle","lastStrokeStyle","lastLineWidth","item","size","origin","style","pos","pxSize","drawX","drawY","resetAlpha","applyLineWidth","rotationDeg","rotation","radius","centerX","centerY","halfExtent","a","b","family","points","xs","p","ys","first","i","aspect","drawW","drawH","baseX","baseY","offsetX","offsetY","cellSize","startX","DEFAULT_VALUES","endX","startY","endY","p1","p2","cell","cacheKey","renderFn","worldWidth","worldHeight","renderScale","canvasWidth","canvasHeight","cache","offscreen","offCtx","cachedCanvas","cachedBounds","cachedScale","viewportWidth","viewportHeight","cacheSourceX","cacheSourceY","cacheSourceWidth","cacheSourceHeight","screenDestX","screenDestY","screenDestWidth","screenDestHeight","cacheWidth","cacheHeight","excessWorld","img","imgX","imgY","handle","Layer","layer","fn","id","entry","handle","list","dc","keys","a","b","fns","COORDINATE_OVERLAY","CoordinateOverlayRenderer","ctx","camera","config","viewport","width","height","fontSize","cordGap","visibleAreaWidthInCords","visibleAreaHeightInCords","i","scale","coordsConfig","min","max","DEBUG_HUD","FPS_SAMPLE_SIZE","CanvasDebug","ctx","camera","config","viewport","callback","now","delta","avgDelta","a","b","newFps","datas","topLeft","width","height","center","i","EventBinder","canvas","handlers","ResizeWatcher","wrapper","canvas","viewport","camera","config","onCameraChange","size","configSize","maxWidth","maxHeight","minWidth","minHeight","entries","entry","rawW","rawH","width","height","prev","diffW","diffH","dpr","value","min","max","result","prevDpr","nextDpr","ResponsiveWatcher","wrapper","canvas","camera","viewport","config","onRender","cfg","responsiveMode","aspectRatio","wrapperRect","initialWidth","initialHeight","entries","entry","rawW","rawH","width","height","prev","mode","dpr","newScale","calculatedHeight","currentCenter","diffW","diffH","prevDpr","nextDpr","DEFAULT_IMAGE_LOAD_RETRY_COUNT","ImageLoader","cb","src","retry","task","resolve","reject","img","err","reason","SizeController","canvasWrapper","canvas","camera","viewport","config","onRender","width","height","durationMs","animationController","onComplete","configSize","clamp","value","min","max","result","w","center","roundedW","roundedH","dpr","RendererCanvas","ImageLoader","cb","deps","initStyles","Layer","CanvasDraw","CoordinateOverlayRenderer","CanvasDebug","GestureProcessor","EventBinder","AnimationController","SizeController","ResponsiveWatcher","ResizeWatcher","rect","touches","remaining","changed","size","dpr","config","topLeft","width","height","durationMs","onComplete"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@canvas-tile-engine/renderer-canvas",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Lightweight library for building interactive 2D grid-based maps and visualizations with Canvas",
|
|
5
|
+
"author": "Enes Yüksel",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/enesyukselx/canvas-tile-engine.git",
|
|
10
|
+
"directory": "packages/renderer-canvas"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://canvastileengine.dev",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/enesyukselx/canvas-tile-engine/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"canvas",
|
|
18
|
+
"tile",
|
|
19
|
+
"engine",
|
|
20
|
+
"2d",
|
|
21
|
+
"map",
|
|
22
|
+
"game",
|
|
23
|
+
"grid",
|
|
24
|
+
"visualization",
|
|
25
|
+
"interactive",
|
|
26
|
+
"pan",
|
|
27
|
+
"zoom"
|
|
28
|
+
],
|
|
29
|
+
"sideEffects": false,
|
|
30
|
+
"main": "dist/index.cjs",
|
|
31
|
+
"module": "dist/index.mjs",
|
|
32
|
+
"types": "dist/index.d.ts",
|
|
33
|
+
"exports": {
|
|
34
|
+
".": {
|
|
35
|
+
"types": "./dist/index.d.ts",
|
|
36
|
+
"import": "./dist/index.mjs",
|
|
37
|
+
"require": "./dist/index.cjs"
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"files": [
|
|
41
|
+
"dist"
|
|
42
|
+
],
|
|
43
|
+
"peerDependencies": {
|
|
44
|
+
"@canvas-tile-engine/core": ">=0.3.0"
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"rbush": "^4.0.1",
|
|
48
|
+
"@canvas-tile-engine/core": "0.3.0"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@types/rbush": "^4.0.0"
|
|
52
|
+
},
|
|
53
|
+
"scripts": {
|
|
54
|
+
"build": "tsup",
|
|
55
|
+
"dev": "tsup --watch",
|
|
56
|
+
"typecheck": "tsc --noEmit",
|
|
57
|
+
"test": "vitest",
|
|
58
|
+
"test:coverage": "vitest run --coverage"
|
|
59
|
+
}
|
|
60
|
+
}
|