Plays a SpriteAnimation by invoking a callback whenever the current frame changes. The callback typically mutates an ImageItem's `sprite` field and triggers a render, which makes animation work identically across all renderers without renderer-specific code. The internal rAF loop only fires the callback when the frame index actually advances, so renders happen at the animation's fps, not at 60fps.
Grid-based spritesheet frame calculator. Maps (col, row) or linear frame indices to pixel source rectangles (SpriteRect) inside a sheet image. Pure calculation — holds no image reference, so it is platform-agnostic and works with every renderer.
const bounds = engine.getVisibleBounds();// { minX: 0, maxX: 10, minY: 0, maxY: 10 }// Use for random placement within visible areaconst x = bounds.minX+Math.floor(Math.random()*(bounds.maxX- bounds.minX));
CanvasTileEngine.setCenter()
method
Move the view center to new world coordinates instantly.
setCenter(newCenter: Coords):void
Parameters
newCenterCoordsThe new center coordinates.
Returnsvoid
CanvasTileEngine.goCenter()
method
Smoothly animate the view center to target world coordinates over the given duration.
durationMs?numberAnimation duration in milliseconds (default: 500ms). Set to 0 for instant move.
onComplete?() => voidOptional callback fired when animation completes.
Returnsvoid
CanvasTileEngine.fitBounds()
method
Fit a world-space rectangle into the viewport: centers the view on the rectangle and picks the largest scale that keeps the whole (padded) area visible, clamped to the scale limits. Animated by default; not related to setBounds, which restricts camera movement.
boundsBoundsRectangle to fit. Every edge must be finite.
options?FitBoundsOptions`padding` in world units (default 0) or `paddingPx` in screen pixels (wins over `padding`), `durationMs` (default 500, 0 = instant), and `onComplete`.
Returnsvoid
Example
// Show the whole board with one cell of marginengine.fitBounds({ minX:0, maxX:32, minY:0, maxY:32},{ padding:1});// 24px of air around any selection, small or hugeengine.fitBounds(selectionBounds,{ paddingPx:24});// Jump to a selection instantlyengine.fitBounds(selectionBounds,{ durationMs:0});
CanvasTileEngine.setEventHandlers()
method
Update event handlers at runtime. This allows you to enable or disable specific interactions dynamically.
bounds{
minX: number;
maxX: number;
minY: number;
maxY: number;
}Boundary limits. Use Infinity/-Infinity to remove limits.
Returnsvoid
Example
// Restrict map to -100 to 100 on both axesengine.setBounds({ minX:-100, maxX:100, minY:-100, maxY:100});// Remove boundariesengine.setBounds({ minX:-Infinity, maxX:Infinity, minY:-Infinity, maxY:Infinity});// Only limit X axisengine.setBounds({ minX:0, maxX:500, minY:-Infinity, maxY:Infinity});
CanvasTileEngine.drawRect()
method
Draw one or many rectangles in world space. Supports rotation via the `rotate` property (degrees, positive = clockwise).
options?RectDrawOptions<TData>Optional `id` (re-registering with the same id replaces the previous registration), `styleOf` (paint-time decoration: the returned fields overlay the item's `style` each frame without re-registering — mutate your state and call `render()`), `visibleOf` (per-item show/hide, same live-read model; a hidden item neither paints nor hit-tests), and `interactiveOf` (per-item hit-test opt-out).
ReturnsDrawHandle
CanvasTileEngine.drawStaticRect()
method
Draw rectangles with pre-rendering cache. Renders all items once to an offscreen canvas, then blits the visible portion each frame. Ideal for large static datasets like mini-maps where items don't change. Supports rotation via the `rotate` property (degrees, positive = clockwise).
cacheKeystringUnique key for this cache (e.g., "minimap-items"). Also acts as the registration id: calling again with the same key replaces the previous registration and invalidates its cache.
layer?numberLayer order (lower draws first).
options?StaticDrawOptionsOptional `hitTest: false` to keep the registration out of hit testing (decorative content).
ReturnsDrawHandle
CanvasTileEngine.drawStaticCircle()
method
Draw circles with pre-rendering cache. Renders all items once to an offscreen canvas, then blits the visible portion each frame. Ideal for large static datasets like mini-maps where items don't change.
cacheKeystringUnique key for this cache (e.g., "minimap-circles"). Also acts as the registration id: calling again with the same key replaces the previous registration and invalidates its cache.
layer?numberLayer order (lower draws first).
options?StaticDrawOptionsOptional `hitTest: false` to keep the registration out of hit testing (decorative content).
ReturnsDrawHandle
CanvasTileEngine.drawStaticImage()
method
Draw images with pre-rendering cache. Renders all items once to an offscreen canvas, then blits the visible portion each frame. Ideal for large static datasets like terrain tiles or static decorations. Supports rotation via the `rotate` property (degrees, positive = clockwise).
itemsArray<ImageItem<TImage>>Array of image definitions with HTMLImageElement.
cacheKeystringUnique key for this cache (e.g., "terrain-cache"). Also acts as the registration id: calling again with the same key replaces the previous registration and invalidates its cache.
layer?numberLayer order (lower draws first).
options?StaticDrawOptionsOptional `hitTest: false` to keep the registration out of hit testing (decorative content).
ReturnsDrawHandle
CanvasTileEngine.clearStaticCache()
method
Clear a static rendering cache.
clearStaticCache(cacheKey?:string):void
Parameters
cacheKey?stringThe cache key to clear, or undefined to clear all caches.
Returnsvoid
CanvasTileEngine.drawLine()
method
Draw one or many lines between world points. Lines participate in hit testing: a point within half the stroke width of a segment (with a minimum tap width for hairlines) hits it.
options?LineDrawOptions<TData>Optional `id` (re-registering with the same id replaces the previous registration), `styleOf` (paint-time decoration overlaid on the call-level `style` per item — also the way to give individual lines their own color), `visibleOf` (per-item show/hide; a hidden line neither paints nor hit-tests), and `interactiveOf` (per-item hit-test opt-out). Decorations cannot change `lineWidth`: the hit-test area derives from the registration-time stroke width.
options?CircleDrawOptions<TData>Optional `id` (re-registering with the same id replaces the previous registration), `styleOf` (paint-time decoration: the returned fields overlay the item's `style` each frame without re-registering — mutate your state and call `render()`), `visibleOf` (per-item show/hide, same live-read model; a hidden item neither paints nor hit-tests), and `interactiveOf` (per-item hit-test opt-out).
Draw one or many free-form paths through world points. Each `PathItem` owns its geometry and style: `points` traces the outline, `closed` joins it back to the start, `style.fillStyle` fills the interior (under `fillRule`), and stroke/dash/corner options follow the shared world-vs-`*Px` unit convention. Paths participate in hit testing: filled paths hit on their interior, unfilled paths on the stroke itself.
options?ImageDrawOptions<TImage, TData>Optional `id` (re-registering with the same id replaces the previous registration), `visibleOf` (per-item show/hide, read live each frame; a hidden item neither paints nor hit-tests), and `interactiveOf` (per-item hit-test opt-out). Images carry no `style`, so there is no `styleOf` — appearance changes go through item fields like `opacity` (mutate + `render()`).
fn(ctx: unknown, coords: Coords, config: Required<CanvasTileEngineConfig>, transform: DrawTransform) => voidFunction receiving canvas context, top-left coords, config, and a `transform` helper — use `transform.worldToScreen(x, y)` to position drawing at world coordinates instead of deriving the pixel math by hand.
layer?numberLayer index (default 1).
options?DrawOptionsOptional `id`: re-registering with the same id replaces the previous registration instead of accumulating alongside it.
ReturnsDrawHandle — DrawHandle for removal.
Example
engine.addDrawFunction((ctx, coords, config, transform)=>{const c = ctx asCanvasRenderingContext2D;const p = transform.worldToScreen(5,3);// center of cell (5, 3) c.fillStyle="red"; c.fillRect(p.x-4, p.y-4,8,8);},4);
CanvasTileEngine.removeDrawHandle()
method
Remove a specific draw callback by handle. Does not clear other callbacks on the same layer.
removeDrawHandle(handle: DrawHandle):void
Parameters
handleDrawHandle
Returnsvoid
CanvasTileEngine.clearLayer()
method
Clear all draw callbacks from a specific layer. Use this before redrawing dynamic content to prevent accumulation.
Clear all draw callbacks from all layers. Useful for complete scene reset.
clearAll():void
Returnsvoid
Example
engine.clearAll();// Redraw everything from scratch
CanvasTileEngine.hitTest()
method
All rect/circle/image/path/line items under a world point, highest visual priority first (higher layer, then later registration, then later item within a draw call). Pass the `coords.raw` value from event callbacks - origin anchoring, image aspect fit, and rotation are handled internally. Line, Path, and Text items are not hit-testable. Like rendering, results reflect item positions as of the draw call: mutating an item's position requires re-registration (style mutation is fine). `padding` (world units) and `paddingPx` (screen pixels, zoom independent) expand every item's hit geometry outward - generous touch targets around small markers without invisible helper items. `TData` types the `data` field of returned items — it is an assertion, not checked at runtime, so only pass it when every hit-testable item carries that data shape (or narrow per hit).
pointCoordsWorld coordinates (e.g. `coords.raw` from onClick/onHover).
opts?HitTestOptionsOptional filters, e.g. `{ layer: 2, padding: 0.5 }`.
ReturnsHitResult<TImage, TData>[]
Example
engine.onClick=(coords)=>{// Accept clicks up to 0.6 world units around each station dotconst hit = engine.hitTestFirst<Station>(coords.raw,{ padding:0.6});if(hit?.item.data)openPanel(hit.item.data);};
CanvasTileEngine.hitTestFirst()
method
The topmost item under a world point, or `undefined`. See hitTest for semantics.
All items whose geometry intersects (default) or lies fully inside a world rectangle — the marquee/box-selection query. Corners may be passed in any order (a drag can travel in any direction); build them from event `coords.raw` values, like `hitTest`. Region tests run on item GEOMETRY — stroke widths are not expanded — and filled paths count interior overlap with holes excluded.
// Marquee selection between drag start and end (raw coords)const hits = engine.hitTestRect({ minX: start.x, minY: start.y, maxX: end.x, maxY: end.y},{ layer:2, mode:"contain"},);select(hits.map((h)=> h.item.data));
Config.get()
method
Get the current configuration as an immutable snapshot. The returned object is deeply frozen and shared — do not mutate it. Runtime updates (`updateEventHandlers`, `updateBounds`) replace the snapshot with a new frozen object, so previously returned references keep their old values. Returning the frozen instance avoids the deep copy this method used to make on every call (it runs on every pointer event and every rendered frame).
onFrame(frame: SpriteRect, index: number) => voidCalled with the new frame whenever the frame changes (including once immediately with the first frame).
onComplete?() => voidCalled when a non-looping animation reaches its last frame.
Returnsvoid
SpriteAnimator.stop()
method
Stop playback. The last applied frame stays drawn.
stop():void
Returnsvoid
SpriteAnimator.isRunning()
method
Whether the animation loop is currently scheduled.
isRunning():boolean
Returnsboolean
SpriteSheet.frame()
method
Source rectangle of the frame at grid position (col, row).
frame(col:number, row:number): SpriteRect
Parameters
colnumber
rownumber
ReturnsSpriteRect
SpriteSheet.frameByIndex()
method
Source rectangle of a frame by linear index (left-to-right, top-to-bottom). Requires `columns` to be set.
frameByIndex(index:number): SpriteRect
Parameters
indexnumber
ReturnsSpriteRect
SpriteSheet.framesInRow()
method
Consecutive frames of a single row, from `startCol` to `endCol` inclusive. Useful as an animation frame list, e.g. `framesInRow(0, 0, 4)` for the frames (0,0) through (4,0).
Callback after each draw frame, on top of all layers. Same signature as `addDrawFunction` callbacks: platform context, top-left world coords, live config, and coordinate transform helpers.
Callback for wheel (desktop) and pinch (touch) zoom gestures. Requires `eventHandlers.zoom`; fires even when the scale is clamped at a limit. Unlike `onZoom` (which reports the resulting scale, including programmatic changes), this reports the input gesture itself with its position. For pinch, the coordinates describe the pinch midpoint and `deltaY` is the wheel delta that would produce the same zoom factor.
Top-left offset of a `w`x`h` box anchored at `pos`. - "cell": nudges the box from the cell center by `origin.x`/`origin.y` (0 to 1) across a `cellSize`-wide cell, independent of the box's own size. - "self": anchors within the box's own `w`/`h`; `cellSize` is unused. `pos`, `w`, `h`, and `cellSize` must share the same unit — screen pixels for renderers (`cellSize` = `camera.scale`), or world units for hit testing (`cellSize` = 1, a cell is exactly one world unit wide).
Compute the rounding arc for the corner `prev → v → next` with the desired `radiusPx`. The radius is clamped so the arc's tangent points never pass the midpoint of either adjacent segment (matching what `ctx.arcTo` would need to stay well-formed on short segments). Returns `null` for degenerate corners: zero-length segments, collinear continuation, or a fold-back.
The scale (pixels per world unit) at which `bounds` exactly fits a `size`-pixel viewport — the math `fitBounds` uses to pick its target scale, exposed as a pure function for config-time use (the `gridToSize` of free-form content). Use it to derive scale limits from content instead of hand-tuning constants: the fit scale tracks the content automatically, and only `maxScale` — a content-resolution quality cap that no bounds can imply — stays a deliberate choice.
boundsBoundsRectangle to fit. Every edge must be finite.
size{
width: number;
height: number;
}Viewport size in logical pixels.
options?FitScaleOptions`padding` in world units or `paddingPx` in screen pixels (wins over `padding`).
Returnsnumber — The fitting scale; unclamped — apply your own min/max policy.
Example
const fit =fitScale(WORLD_BOUNDS,{ width:800, height:600},{ paddingPx:24});const config ={ size:{ width:800, height:600}, scale: fit,// open showing everything minScale: fit *0.8,// small overview slack — your policy maxScale:64,// quality cap — intentionally hand-picked};
flattenPathCommands()
function
Flattens a Canvas2D-style command list into polyline subpaths, following Canvas2D path semantics: each `moveTo` starts a new subpath; `lineTo`/ curves with no open subpath implicitly move first; `arc` draws a connecting line from the current point to the arc start; after `closePath` the current point is the closed subpath's start. All consumers of flattened geometry (hit testing, WebGL, culling) share this one implementation so they agree exactly. `maxSegment` is the max spacing between samples, in the same unit as the coordinates — callers flatten in world units with `ARC_SEGMENT_LENGTH / scale` to sample at the renderers' screen-pixel density.
Convert grid-based dimensions to pixel-based config plus the board center. Integers are cell centers (cell k spans [k-0.5, k+0.5]), so a board of cells 0..N-1 is centered at (N-1)/2 on each axis. Pass the returned `center` to the engine so the board exactly fills the viewport.
Overlay one line style on another, unit-pair by unit-pair. A plain field-by-field spread breaks the world/px precedence rule across layers: a base `lineWidthPx` would survive the merge and shadow an overlay that only sets the world `lineWidth`. Instead, a layer that sets either field of a pair (`lineWidth`/`lineWidthPx`, `lineDash`/`lineDashPx`) replaces the whole pair — the `*Px`-wins rule applies within one layer, never across layers. Used for `Line.style` over the call-level style, and for `styleOf` decorations over both.
Conservative world-space bounds of a command list from its control-point hull: every endpoint and control point, and each arc's full center±radius box. Curves never leave their control hull, so this over-approximates — cheap, camera-independent, and safe for culling. Returns null for an empty list.
Effective dash pattern in screen pixels, or `undefined` for a solid line. Follows Canvas2D `setLineDash` semantics: an odd-length pattern is repeated (doubled), and a pattern that is empty, contains a negative or non-finite value, or sums to zero yields a solid line.
Effective stroke width in screen pixels. `lineWidthPx` wins; `lineWidth` is world units (multiplied by `scale`); neither given falls back to a 1px hairline.
Effective size in world units at the current scale. Note that a `sizePx` item's world extent GROWS as the camera zooms out — culling and hit queries must re-evaluate this per frame instead of caching it.
Replace each interior vertex of a polyline with its rounding arc, flattened into short segments. Uses cornerArc for the exact same clamped geometry the Canvas2D/Skia renderers feed to their native tangent-arc APIs, so flattened consumers (WebGL, hit testing) agree with them. Because the result is a plain (denser) polyline, dash tessellation and distance tests run over it unchanged. Radius, points, and `maxSegment` share one unit (pixels or world).
Closed-ring variant of roundedPolyline: every vertex is a corner with cyclic neighbors, including the joins of the closing segment. The result is still an implicit ring — consumers connect the last sample back to the first.
Replays a PathCommand list into a Canvas2D-style sink, converting world coordinates to screen pixels and degrees to radians at this one boundary so every renderer traces identical geometry. The world→screen transform is uniform (one scale for both axes), so curves and arcs map exactly.
Traces one path item's outline (screen-pixel points) into a Canvas2D-style sink, applying corner rounding via cornerArc so every renderer produces identical geometry. Open paths round interior vertices only; closed paths round every vertex, including the joins of the closing segment. Degenerate corners (collinear, fold-back, zero radius) fall back to straight joins.
Geometry of one rounded polyline corner, in screen pixels. All renderers derive their corner arcs from this single computation so the rounding is identical everywhere: Canvas2D/Skia feed `radius` to their native tangent arc APIs, WebGL flattens the arc from `center`/angles.
interfaceCornerArc{/** Arc radius after per-corner clamping. */ radius:number;/** Tangent point on the incoming segment (arc start). */ t1: Coords;/** Tangent point on the outgoing segment (arc end). */ t2: Coords;/** Arc center. */ center: Coords;/** Angle of `t1` around the center, radians. */ startAngle:number;/** Angle of `t2` around the center, radians. */ endAngle:number;/** Sweep from start to end, radians; negative = counterclockwise. */ sweep:number;}
DrawHandle
interface
No API description is included in the published TypeScript declaration for DrawHandle.
Options accepted by the engine draw helpers (`drawRect`, `drawCircle`, ...).
interfaceDrawOptions{/** * Stable identity for this registration. Calling any draw method again * with the same id atomically replaces the previous registration: the old * draw callback and its hit-test entries are removed (and, for static * draws, the offscreen cache is invalidated) before the new one is added. * Ids share a single namespace across draw kinds and layers; static draw * helpers use their `cacheKey` as the id. Without an id, every call adds * a new registration (remove with the returned handle or `clearLayer`). */ id?:string;/** * Set to `false` to keep this registration out of hit testing — the * `pointer-events: none` of the draw API. Decorative content (floor * tiles, background images, zone overlays) declared once at registration * stops leaking into every `hitTest`/`hitTestFirst`/`hitTestRect` query, * and large decorative sets skip hit-registry bookkeeping entirely. * Default `true`. Text and custom draw functions never enter hit testing * regardless of this flag. */ hitTest?:boolean;}
DrawTransform
interface
Coordinate transform helpers handed to custom draw callbacks (`addDrawFunction` / `onDraw`), so user code never re-derives the `(world - topLeft) * scale` formula or the cell-center offset.
interfaceDrawTransform{/** * Item-space world coordinate → canvas pixel position. Integers are cell * centers (the same space item `x`/`y` live in), so `worldToScreen(k, k)` * is the pixel at the center of cell `k`. */worldToScreen(x:number, y:number): Coords;/** * Canvas pixel position → raw (corner-space) world coordinate — the same * space event payloads report as `coords.raw`. */screenToWorld(x:number, y:number): Coords;}
FitBoundsOptions
interface
Options for the engine's `fitBounds` method.
interfaceFitBoundsOptions{/** * Extra world-unit margin added on every side of the rectangle. Scales * with the content: 10x larger bounds need a 10x larger padding for the * same framing. Ignored when {@link paddingPx} is set. Default 0. */ padding?:number;/** * Screen-pixel margin kept free on every side of the viewport, * independent of the content's world size — "20px of air" frames a * 3-cell selection and a 10k-cell board identically. Takes precedence * over {@link padding}. A value too large for the viewport is clamped so * the fit stays valid. */ paddingPx?:number;/** Animation duration in ms (default 500). Use 0 for an instant jump. */ durationMs?:number;/** Fired when the fit completes (synchronously when instant). */ onComplete?:()=>void;}
FitScaleOptions
interface
Options for fitScale. The same pair `fitBounds` accepts.
interfaceFitScaleOptions{/** * Extra world-unit margin added on every side of the rectangle; scales * with the content. Ignored when {@link paddingPx} is set. Default 0. */ padding?:number;/** * Screen-pixel margin kept free on every side of the viewport, * independent of the content's world size. Takes precedence over * {@link padding}. A value too large for the viewport is clamped so the * result stays valid. */ paddingPx?:number;}
ICamera
interface
No API description is included in the published TypeScript declaration for ICamera.
No API description is included in the published TypeScript declaration for IDrawAPI.
interfaceIDrawAPI<TImage = HTMLImageElement>{addDrawFunction(fn:(ctx:unknown, coords: Coords, config: Required<CanvasTileEngineConfig>, transform: DrawTransform)=>void, layer?:number): DrawHandle;drawRect(items: Rect | Rect[], layer?:number, options?: RendererDrawOptions<Rect, ShapeDecorationStyle>): DrawHandle;drawCircle(items: Circle | Circle[], layer?:number, options?: RendererDrawOptions<Circle, ShapeDecorationStyle>): DrawHandle;drawLine(items: Line | Line[], style?: LineStyle, layer?:number, options?: RendererDrawOptions<Line, LineDecorationStyle>): DrawHandle;drawText(items: Text | Text[], layer?:number, options?: RendererDrawOptions<Text, TextDecorationStyle>): DrawHandle;drawImage(items: ImageItem<TImage>| ImageItem<TImage>[], layer?:number, options?: RendererImageDrawOptions<TImage>): DrawHandle;/** Receives fully normalized items: the engine converts every accepted * `drawPath` input (including the legacy `Coords[]` forms) before * delegating, so renderers only implement the item form. */drawPath(items: PathItem[], layer?:number, options?: RendererDrawOptions<PathItem, PathDecorationStyle>): DrawHandle;drawGridLines(cellSize:number, style:{ lineWidth:number; strokeStyle:string;}, layer?:number): DrawHandle;drawStaticRect(items: Rect[], cacheKey:string, layer?:number): DrawHandle;drawStaticCircle(items: Circle[], cacheKey:string, layer?:number): DrawHandle;drawStaticImage(items: ImageItem<TImage>[], cacheKey:string, layer?:number): DrawHandle;removeDrawHandle(handle: DrawHandle):void;clearLayer(layer:number):void;clearAll():void;clearStaticCache(cacheKey?:string):void;}
IImageLoader
interface
Platform-agnostic image loader interface. Each renderer implements this with platform-specific image handling.
interfaceIImageLoader<TImage = HTMLImageElement>{/** * Load an image from URL, with caching. * @paramsrc Image URL. * @paramretry Retry count on failure. */load(src:string, retry?:number):Promise<TImage>;/** * Get a cached image without loading. */get(src:string): TImage |undefined;/** * Check if an image is already cached. */has(src:string):boolean;/** * Clear all cached images. */clear():void;/** * Register a callback fired when a new image finishes loading. * @returns Unsubscribe function. */onLoad(cb:()=>void):()=>void;}
IRenderer
interface
No API description is included in the published TypeScript declaration for IRenderer.
Options for CanvasTileEngine.drawImage. Images carry no `style`, so there is no `styleOf` — appearance changes go through item fields like `opacity` (read live at paint time; mutate + `render()`).
Options for CanvasTileEngine.drawLine. `styleOf` overlays the call-level `style` per item, which also makes it the way to give individual lines their own color.
interfaceLineDrawOptions<TData =unknown>extendsDrawOptions{/** Paint-time decoration; see {@link StyleOf}. */ styleOf?: StyleOf<Line<TData>, LineDecorationStyle>;/** Per-item visibility; see {@link VisibleOf}. */ visibleOf?: VisibleOf<Line<TData>>;/** Per-item hit-test opt-out; see {@link InteractiveOf}. */ interactiveOf?: InteractiveOf<Line<TData>>;}
LineStyle
interface
No API description is included in the published TypeScript declaration for LineStyle.
interfaceLineStyle{ strokeStyle?:string;/** * Line thickness in world units; scales with zoom. * Ignored when {@link lineWidthPx} is set. Default: 1px hairline. */ lineWidth?:number;/** * Line thickness in screen pixels, independent of zoom. * Takes precedence over {@link lineWidth}. */ lineWidthPx?:number;/** * Dash pattern in world units (dashes are anchored to the world and scale * with zoom). Follows Canvas2D `setLineDash` semantics. Ignored when * {@link lineDashPx} is set. Omit for a solid line. */ lineDash?:number[];/** * Dash pattern in screen pixels, independent of zoom. * Takes precedence over {@link lineDash}. */ lineDashPx?:number[];}
NormalizedPinch
interface
Normalized multi-pointer input for pinch gestures.
interfaceNormalizedPinch{/** First pointer */ pointer1: NormalizedPointer;/** Second pointer */ pointer2: NormalizedPointer;}
NormalizedPointer
interface
Normalized pointer input - renderer-agnostic format. All coordinates should be canvas-relative.
interfaceNormalizedPointer{/** X position relative to canvas */ x:number;/** Y position relative to canvas */ y:number;/** X position relative to viewport (for callbacks) */ clientX:number;/** Y position relative to viewport (for callbacks) */ clientY:number;}
Origin
interface
Normalized origin: `mode` defaults to "cell", `x`/`y` default to 0.5 (center).
interfacePathDrawOptions<TData =unknown>extendsDrawOptions{/** * Paint-time decoration; see {@link StyleOf}. Note: hit testing keeps the * registration-time semantics — decorating an unfilled path with a * `fillStyle` paints a fill but does not switch hit testing from * stroke to interior. */ styleOf?: StyleOf<PathItem<TData>, PathDecorationStyle>;/** Per-item visibility; see {@link VisibleOf}. */ visibleOf?: VisibleOf<PathItem<TData>>;/** Per-item hit-test opt-out; see {@link InteractiveOf}. */ interactiveOf?: InteractiveOf<PathItem<TData>>;}
interfaceRectDrawOptions<TData =unknown>extendsDrawOptions{/** Paint-time decoration; see {@link StyleOf}. */ styleOf?: StyleOf<Rect<TData>, ShapeDecorationStyle>;/** Per-item visibility; see {@link VisibleOf}. */ visibleOf?: VisibleOf<Rect<TData>>;/** Per-item hit-test opt-out; see {@link InteractiveOf}. */ interactiveOf?: InteractiveOf<Rect<TData>>;}
RendererDependencies
interface
Dependencies injected into a renderer's `init`. `TMount` is the platform-specific mount target the engine is constructed with. It defaults to `HTMLDivElement` so existing DOM renderers stay source- and type-compatible; non-DOM renderers (e.g. React Native / Skia) parameterize it with their own mount type.
Paint-time options the engine threads into a renderer's `IDrawAPI` draw methods. Only `styleOf` and `visibleOf` reach renderers — registration concerns (`id`) and hit-test concerns (`interactiveOf`) are resolved in the engine.
interfaceSpriteAnimation{/** Frames to cycle through, in play order (e.g. `SpriteSheet.framesInRow(...)`). */ frames: SpriteRect[];/** Playback speed in frames per second. */ fps:number;/** Restart from the first frame after the last one (default: true). */ loop?:boolean;}
SpriteSheetOptions
interface
No API description is included in the published TypeScript declaration for SpriteSheetOptions.
interfaceSpriteSheetOptions{/** Width of a single frame in sheet pixels. */ frameWidth:number;/** Height of a single frame in sheet pixels. */ frameHeight:number;/** Number of columns in the sheet. Required for index-based lookups. */ columns?:number;/** Outer offset from the sheet edges in pixels (default: 0). */ margin?:number;/** Gap between adjacent frames in pixels (default: 0). */ spacing?:number;}
StaticDrawOptions
interface
Options for the static draw helpers (`drawStaticRect`, ...). Their `cacheKey` already plays the registration-id role, so only the hit-test opt-out applies.
interfaceStaticDrawOptions{/** Set to `false` to keep this registration out of hit testing; see * {@link DrawOptions.hitTest}. */ hitTest?:boolean;}
Subpath
interface
One flattened subpath: a polyline plus whether it closes back to its start.
Details of the zoom gesture that triggered an onWheel callback.
interfaceWheelInfo{/** * Vertical wheel delta in pixels (negative = zoom in). For pinch this is * synthesized: the wheel delta that would produce the same zoom factor, * so both sources read on the same axis. */ deltaY:number;/** Zoom direction implied by the gesture. */ direction:"in"|"out";/** Input source: mouse wheel or two-finger pinch. */ source:"wheel"|"pinch";}
Types
CanvasTileEngineConfig
type
No API description is included in the published TypeScript declaration for CanvasTileEngineConfig.
typeCanvasTileEngineConfig={ scale:number; maxScale?:number; minScale?:number; backgroundColor?:string;/** * When true, the initial center snaps to the nearest grid-aligned value * for pixel-perfect alignment: half-integers (x.5) for even tile counts, * integers for odd. Integers are cell centers (cell k spans * [k-0.5, k+0.5]); exact ties snap down so a center computed as N/2 for * a 0-based N-cell board lands on the true board center (N-1)/2. */ gridAligned?:boolean; size:{ width:number; height:number; minWidth?:number; minHeight?:number; maxWidth?:number; maxHeight?:number;}; responsive?:"preserve-scale"|"preserve-viewport"|false; eventHandlers?: EventHandlers; bounds?:{ minX:number; maxX:number; minY:number; maxY:number;}; coordinates?:{ enabled?:boolean; shownScaleRange?:{ min:number; max:number;};}; debug?:{ enabled?:boolean; hud?:{ enabled?:boolean; topLeftCoordinates?:boolean; coordinates?:boolean; scale?:boolean; tilesInView?:boolean; fps?:boolean;}; eventHandlers?:{ click?:boolean; hover?:boolean; drag?:boolean; zoom?:boolean; resize?:boolean;};};};
Circle
type
No API description is included in the published TypeScript declaration for Circle.
typeCircle<TData =unknown>= Omit<DrawObject<TData>,"rotate"|"radius">&{/** * Fixed diameter in screen pixels, independent of zoom — the marker * pattern (station dots, POI markers) analog of Text's `fontPx`. Takes * precedence over {@link DrawObject.size}. Ignored by `drawStaticCircle`: * static caches replay at their recorded scale, so pixel sizing cannot * hold there. */ sizePx?:number;};
Coords
type
No API description is included in the published TypeScript declaration for Coords.
typeCoords={ x:number; y:number;};
DrawObject
type
No API description is included in the published TypeScript declaration for DrawObject.
typeDrawObject<TData =unknown>={ x:number; y:number; size?:number; origin?:{ mode?:"cell"|"self"; x?:number; y?:number;}; style?:{ fillStyle?:string; strokeStyle?:string;/** * Border width in world units; scales with zoom like the item's * geometry. Ignored when `lineWidthPx` is set. Default: 1px hairline. */ lineWidth?:number;/** * Border width in screen pixels, independent of zoom. * Takes precedence over `lineWidth`. */ lineWidthPx?:number;/** * Border dash pattern in world units (dashes are anchored to the shape * and scale with zoom). Follows Canvas2D `setLineDash` semantics. * Ignored when `lineDashPx` is set. Omit for a solid border. */ lineDash?:number[];/** * Border dash pattern in screen pixels, independent of zoom. * Takes precedence over `lineDash`. */ lineDashPx?:number[];};/** Rotation angle in degrees (0 = no rotation, positive = clockwise) */ rotate?:number;/** * Border radius in world units; scales with zoom so corners stay * proportional to the shape. Single value for all corners, or array for * [topLeft, topRight, bottomRight, bottomLeft]. */ radius?:number|number[];/** * Arbitrary app data attached to the item. Never read by the engine or * renderers; carried through so `hitTest` results can identify the item * without relying on array positions. */ data?: TData;};
EventHandlers
type
No API description is included in the published TypeScript declaration for EventHandlers.
typeEventHandlers={ click?:boolean; rightClick?:boolean; hover?:boolean; drag?:boolean;/** Zoom behavior: `false` disables zoom, `true` is shorthand for `"pointer"`. */ zoom?:boolean| ZoomMode; resize?:boolean;};
A single hit returned by `hitTest`, ordered by visual priority.
typeHitResult<TImage =unknown, TData =unknown>={/** The original item object passed to the draw call. */ item: Rect<TData>| Circle<TData>| ImageItem<TImage, TData>| PathItem<TData>| Line<TData>;/** Which primitive kind the item was drawn as. */ kind: HitKind;/** Layer the item is drawn on. */ layer:number;/** Handle of the draw call that registered the item. */ handle: DrawHandle;/** Position of the item inside its draw call's items array. */ index:number;};
HitTestOptions
type
No API description is included in the published TypeScript declaration for HitTestOptions.
typeHitTestOptions={/** Only test items drawn on this layer. */ layer?:number;/** * Expand every item's hit geometry outward by this many world units - * generous touch targets around small markers without invisible helper * items. Negative values are treated as 0. */ padding?:number;/** * Extra hit padding in screen pixels, independent of zoom. The engine * converts it with the current scale at query time and adds it to * `padding`. Negative values are treated as 0. */ paddingPx?:number;};
HitTestRectOptions
type
No API description is included in the published TypeScript declaration for HitTestRectOptions.
typeHitTestRectOptions={/** Only test items drawn on this layer. */ layer?:number;/** * `"intersect"` (default): any overlap with the rectangle counts. * `"contain"`: the item's full geometry must lie inside the rectangle — * the usual choice for marquee seat/unit selection. */ mode?:"intersect"|"contain";};
ImageItem
type
An image to draw. `TImage` is the platform-specific image handle and defaults to `HTMLImageElement` (DOM); other renderers parameterize it (e.g. `SkImage`).
typeImageItem<TImage = HTMLImageElement, TData =unknown>= Omit<DrawObject<TData>,"style">&{ img: TImage;/** * Source rectangle in sheet pixels. When set, only this sub-region of * `img` is drawn (spritesheet frame); when omitted the whole image is drawn. */ sprite?: SpriteRect;/** * Fixed size in screen pixels, independent of zoom — for marker-style * images that must stay readable at any zoom level. Takes precedence * over {@link DrawObject.size}. Ignored by `drawStaticImage`: static * caches replay at their recorded scale, so pixel sizing cannot hold * there. */ sizePx?:number;/** * Mirror the image horizontally. Unlike `rotate`, flipping produces a * true mirror (a right-facing sprite faces left), which no rotation can. * Applied around the draw box center; combines with `rotate` and * spritesheet frames, and works in `drawStaticImage` too. */ flipX?:boolean;/** Mirror the image vertically. See {@link flipX}. */ flipY?:boolean;/** * Opacity from 0 (transparent) to 1 (opaque). Default: 1. * Useful for ghost/preview placements in editor-style apps. */ opacity?:number;};
InteractiveOf
type
Per-item hit-test opt-out — the item-level counterpart of DrawOptions.hitTest. Runs at hit-query time: return `false` to keep the item out of `hitTest`/`hitTestFirst`/`hitTestRect` while it stays painted (queries fall through to items below it); `true`/`undefined` keeps it interactive. Items hidden by `visibleOf` never hit-test, regardless of this callback. Reads external state live, like StyleOf.
No API description is included in the published TypeScript declaration for Line.
typeLine<TData =unknown>={ from: Coords; to: Coords;/** * Per-item style, overlaying the call-level `style` argument field by * field — mixed-style batches no longer need one `drawLine` call per * style. Registration-time like every item style, so unlike `styleOf` * decorations it may change `lineWidth`/`lineWidthPx`: hit testing * resolves this item's own stroke width. `styleOf` decorations still * overlay both at paint time. */ style?: LineStyle;/** * Arbitrary app data attached to the segment. Never read by the engine or * renderers; carried through so `hitTest` results can identify the item * without relying on array positions. */ data?: TData;};
LineDecorationStyle
type
Decoration fields for `Line`. Excludes `lineWidth`/`lineWidthPx`: a line's hit-test area is derived from its stroke width at registration time, so a paint-time decoration must not change it.
One drawing command of a free-form path, mirroring the Canvas2D path API. Coordinates and radii are world units (item space); angles are degrees (engine convention, like `rotate`) — renderers convert to radians.
typePathCommand={ type:"moveTo"; x:number; y:number;}|{ type:"lineTo"; x:number; y:number;}/** * Center-based circular arc from `startAngle` to `endAngle` (degrees, * 0 = +x axis, positive angles sweep clockwise in screen space). `ccw` * defaults to false. Like Canvas2D, a line connects the current point to * the arc's start when the subpath is already open. */|{ type:"arc"; x:number; y:number; radius:number; startAngle:number; endAngle:number; ccw?:boolean;}|{ type:"quadraticCurveTo"; cpx:number; cpy:number; x:number; y:number;}|{ type:"bezierCurveTo"; cp1x:number; cp1y:number; cp2x:number; cp2y:number; x:number; y:number;}|{ type:"closePath";};
PathDecorationStyle
type
Decoration fields for `PathItem`. Excludes stroke width and corner radius: both feed hit-test geometry resolved at registration time, so a paint-time decoration must not change them.
A free-form path: either a `points` polyline or a Canvas2D-style `commands` list (curves, arcs, multiple subpaths, holes). `points` describes an open polyline; `closed` joins the last point back to the first. `commands` is fully free-form: each `moveTo` starts a new subpath, so one item can be an outline plus holes — under the `"evenodd"` fill rule any overlapping subpath punches a hole; under `"nonzero"` a hole must wind in the opposite direction of its outer ring. Setting `style.fillStyle` fills the shape (open subpaths close implicitly for filling, like Canvas2D `fill()`), and filled paths hit-test against their interior. Unfilled paths hit-test against the stroke itself.
typePathItem<TData =unknown>={/** * Free-form command list. Mutually exclusive with {@link points} — * when both are set, `commands` wins. */ commands?: PathCommand[];/** Polyline vertices in world units (item space: integers are cell centers). */ points?: Coords[];/** * Join the last point back to the first with a closing segment. * `points` form only — with `commands`, use a `closePath` command. */ closed?:boolean;/** * Fill rule used for filling and interior hit testing, mirroring Canvas2D: * `"nonzero"` (default) counts winding, `"evenodd"` alternates — the * difference shows on self-intersecting outlines (e.g. a star polygon). */ fillRule?:"nonzero"|"evenodd"; style?: PathStyle;/** * Arbitrary app data attached to the item. Never read by the engine or * renderers; carried through so `hitTest` results can identify the item * without relying on array positions. */ data?: TData;};
PathStyle
type
Per-item styling for PathItem. Same unit convention as elsewhere: plain values are world units and scale with zoom; `*Px` variants are screen pixels and take precedence over their world counterpart.
typePathStyle={/** Fill color. Setting it makes the path a filled shape: the outline is * implicitly closed for filling and hit testing covers the interior. */ fillStyle?:string; strokeStyle?:string;/** Stroke width in world units; scales with zoom. Ignored when * {@link lineWidthPx} is set. Default: 1px hairline. */ lineWidth?:number;/** Stroke width in screen pixels, independent of zoom. */ lineWidthPx?:number;/** Dash pattern in world units (anchored to the world, scales with zoom). * Ignored when {@link lineDashPx} is set. Omit for a solid line. */ lineDash?:number[];/** Dash pattern in screen pixels, independent of zoom. */ lineDashPx?:number[];/** Corner rounding radius in world units, applied at every interior * vertex of `points` (and the closing corners when `closed`). `points` * form only — with `commands`, draw arcs explicitly. Ignored when * {@link cornerRadiusPx} is set. */ cornerRadius?:number;/** Corner rounding radius in screen pixels, independent of zoom. */ cornerRadiusPx?:number;};
RawOrigin
type
Raw `origin` as authored on a draw item — every field optional.
No API description is included in the published TypeScript declaration for Rect.
typeRect<TData =unknown>= DrawObject<TData>&{/** Width in world units. Defaults to `size`, so square rects are unchanged. */ width?:number;/** Height in world units. Defaults to `size`. */ height?:number;};
ShapeDecorationStyle
type
Decoration fields for `Rect`/`Circle` — the full shape style (stroke width does not feed shape hit testing, so nothing needs to be excluded).
Paint-time decoration callback. Runs for each item on every frame; the returned fields overlay the item's own `style` for that frame only (`undefined` leaves the item untouched). Because it runs at paint time it reads external state live: mutate a selection set and call `render()` — the items are never re-registered and the spatial index never rebuilds. Identify items through `item.data` (the same convention as `hitTest` results); most items should return `undefined`.
No API description is included in the published TypeScript declaration for Text.
typeText<TData =unknown>= Omit<DrawObject<TData>,"radius"|"size">&{ text:string;/** * Font size in world units: the font's em box spans `size` world units, so * rendered pixel height is `size * scale` and text scales with zoom. * Ignored when {@link fontPx} is set. Default: 1 */ size?:number;/** * Fixed font size in CSS pixels, independent of zoom. Use for labels that * must stay readable at any zoom level. Takes precedence over {@link size}. */ fontPx?:number; style?:{ fillStyle?:string;/** Font family (default: "sans-serif") */ fontFamily?:string; textAlign?: TextAlign; textBaseline?: TextBaseline;};};
TextAlign
type
Horizontal text alignment. Mirrors the DOM `CanvasTextAlign` values but is declared locally so core's public types do not require the DOM lib (needed by non-DOM renderers such as React Native / Skia).
Per-item visibility callback. Runs at paint time on every frame (and at hit-query time for hit-tested kinds): return `false` to skip the item for that frame — it is neither painted nor hit-testable; `true`/`undefined` keeps it. Like StyleOf it reads external state live: mutate a filter set and call `render()` — the items are never re-registered and the spatial index never rebuilds.
Anchor point for zoom interactions (wheel and pinch): - `"pointer"` — zoom toward the mouse cursor / pinch midpoint. - `"center"` — zoom toward the center of the canvas.
typeZoomMode="pointer"|"center";
onClickCallback
type
No API description is included in the published TypeScript declaration for onClickCallback.
typeonClickCallback= MouseEventCallback;
onDrawCallback
type
Post-frame draw hook. Mirrors the `addDrawFunction` callback signature: the platform context, the viewport's top-left world coordinate, the live normalized config (current scale and size), and the coordinate transform helpers.
No API description is included in the published TypeScript declaration for onHoverCallback.
typeonHoverCallback= MouseEventCallback;
onMouseDownCallback
type
No API description is included in the published TypeScript declaration for onMouseDownCallback.
typeonMouseDownCallback= MouseEventCallback;
onMouseLeaveCallback
type
No API description is included in the published TypeScript declaration for onMouseLeaveCallback.
typeonMouseLeaveCallback= MouseEventCallback;
onMouseUpCallback
type
No API description is included in the published TypeScript declaration for onMouseUpCallback.
typeonMouseUpCallback= MouseEventCallback;
onRightClickCallback
type
No API description is included in the published TypeScript declaration for onRightClickCallback.
typeonRightClickCallback= MouseEventCallback;
onWheelCallback
type
Fired for wheel (desktop) and pinch (touch) zoom gestures. The coordinate payloads match the other pointer callbacks; for pinch they describe the pinch midpoint.
No API description is included in the published TypeScript declaration for onZoomCallback.
typeonZoomCallback=(scale:number)=>void;
Constants
ARC_SEGMENT_LENGTH
constant
Default max spacing between flattened arc samples. Callers pass values in the same unit as their points: renderers use it as screen pixels, the hit tester divides by the camera scale to sample identically in world units.
constARC_SEGMENT_LENGTH:unknown
COORDINATE_OVERLAY
constant
No API description is included in the published TypeScript declaration for COORDINATE_OVERLAY.
constCOORDINATE_OVERLAY:{/** Coordinate overlay border width in pixels */readonlyBORDER_WIDTH:20;/** Coordinate text opacity */readonlyTEXT_OPACITY:0.8;/** Border overlay opacity */readonlyBORDER_OPACITY:0.1;/** Minimum font size for coordinate labels */readonlyMIN_FONT_SIZE:8;/** Maximum font size for coordinate labels */readonlyMAX_FONT_SIZE:12;/** Font size scale factor relative to camera scale */readonlyFONT_SIZE_SCALE_FACTOR:0.25;}
DEBUG_HUD
constant
No API description is included in the published TypeScript declaration for DEBUG_HUD.
constDEBUG_HUD:{/** Debug HUD panel width in pixels */readonlyPANEL_WIDTH:160;/** Debug HUD padding in pixels */readonlyPADDING:8;/** Debug HUD line height in pixels */readonlyLINE_HEIGHT:16;}
DEFAULT_VALUES
constant
Global constants for the canvas grid engine. Centralizes magic numbers and configuration values.
constDEFAULT_VALUES:{/** Default animation duration in milliseconds */readonlyANIMATION_DURATION_MS:500;/** Pixel offset for centering cells (0.5 = center of pixel) */readonlyCELL_CENTER_OFFSET:0.5;/** Default retry count for image loading */readonlyIMAGE_LOAD_RETRY_COUNT:1;/** Maximum wheel delta value for zoom (prevents extreme zooming) */readonlyMAX_WHEEL_DELTA:100;/** Minimum wheel delta value for zoom */readonlyMIN_WHEEL_DELTA:-100;/** Zoom sensitivity factor */readonlyZOOM_SENSITIVITY:0.001;}
RENDER_DEFAULTS
constant
No API description is included in the published TypeScript declaration for RENDER_DEFAULTS.
constRENDER_DEFAULTS:{/** Default background color */readonlyBACKGROUND_COLOR:"#ffffff";}
SCALE_LIMITS
constant
No API description is included in the published TypeScript declaration for SCALE_LIMITS.
No API description is included in the published TypeScript declaration for SIZE_LIMITS.
constSIZE_LIMITS:{/** Default minimum canvas width in pixels */readonlyMIN_WIDTH:100;/** Default minimum canvas height in pixels */readonlyMIN_HEIGHT:100;/** Default maximum width (infinity means no limit) */readonlyMAX_WIDTH:number;/** Default maximum height (infinity means no limit) */readonlyMAX_HEIGHT:number;}
VISIBILITY_BUFFER
constant
No API description is included in the published TypeScript declaration for VISIBILITY_BUFFER.
constVISIBILITY_BUFFER:{/** Buffer zone for visibility culling (tiles) */readonlyTILE_BUFFER:1;}