msgProc: Paint

Canvas PAC components separate the decision to repaint from the act of painting. Rather than drawing directly in response to events or data changes, a component marks itself dirty via invalidateRect() and lets the toolkit schedule a single MSG_PAINT on the next frame — coalescing any number of invalidations into one draw pass. Canvas containers support 2D, WebGL, and WebGL2 rendering contexts via the data-pac-context attribute. For a mapping of Win32 concepts to their underlying browser primitives, see Win32 Concepts Applied to the DOM in the msgProc Reference.

paint flow

Messages

MSG_PAINT

MSG_PAINT fires when a component calls wakaPAC.invalidateRect(). The toolkit defers the repaint to the next display refresh cycle, coalescing any further invalidations that arrive in the meantime into a single dispatch. MSG_PAINT is only dispatched to <canvas> containers — it will not fire for regular DOM elements.

Message Parameters

Parameter Type Description
wParam number Reserved (always 0)
lParam number Reserved (always 0)

Example: Basic Paint Handler

msgProc(event) {
    if (event.message === wakaPAC.MSG_PAINT) {
        const hdc  = wakaPAC.getDC(this.pacId);
        const size = wakaPAC.getCanvasSize(this.pacId);

        hdc.clearRect(0, 0, size.width, size.height);

        hdc.fillStyle = 'steelblue';
        hdc.fillRect(10, 10, size.width - 20, size.height - 20);

        wakaPAC.releaseDC(hdc);
    }
}

MSG_WEBGL_READY

MSG_WEBGL_READY fires once per component lifetime, immediately after the first MSG_SIZE for WebGL/WebGL2 canvas components. It marks the point where the WebGL context becomes available and GPU resources can be created. Use this event for one-time initialization such as compiling shaders, creating buffers, and uploading geometry. Rendering itself occurs during MSG_PAINT.

Message Parameters

Parameter Type Description
wParam number Reserved (always 0)
lParam number Reserved (always 0)
event.detail.glContext WebGLRenderingContext | WebGL2RenderingContext The GL context, ready to use. Uses this to set up shader compilation and other resource setup.

Example: WebGL2 Component

wakaPAC('#scene', {
    init() {
        // Safe for non-GL state only — context not yet valid
        this._rx = 0;
        this._ry = 0;
    },

    msgProc(event) {
        switch (event.message) {

            case wakaPAC.MSG_WEBGL_READY: {
                // event.detail.glContext is the GL context, ready to use immediately
                const gl = event.detail.glContext;
                this._prog = buildShaderProgram(gl);
                this._vao  = uploadGeometry(gl);
                gl.enable(gl.DEPTH_TEST);
                wakaPAC.releaseDC(gl); // no-op for WebGL, called for consistency
                break;
            }

            case wakaPAC.MSG_PAINT: {
                const gl = wakaPAC.getDC(this.pacId);
                gl.viewport(0, 0, this._w, this._h);
                gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
                // draw...
                wakaPAC.releaseDC(gl); // no-op for WebGL, called for consistency
                break;
            }
        }
    }
}, {
    dcAttributes: { antialias: true, depth: true },
    renderLoop:   true
});

MSG_WEBGL_CONTEXT_LOST

MSG_WEBGL_CONTEXT_LOST fires when the browser reclaims the WebGL context — most commonly on mobile when another app takes the GPU, or when too many contexts exist simultaneously. All GL objects (programs, buffers, textures, VAOs, framebuffers) are invalid after this point and must not be used. The render loop is suspended automatically by WakaPAC for the duration of the loss.

WakaPAC calls preventDefault() on the underlying browser event automatically — this is required for the browser to attempt context restoration. Components do not need to do this themselves.

Message Parameters

Parameter Type Description
wParam number Reserved (always 0)
lParam number Reserved (always 0)

MSG_WEBGL_CONTEXT_RESTORED

MSG_WEBGL_CONTEXT_RESTORED fires when the browser has successfully recreated the WebGL context after a loss. All GL resources must be rebuilt from scratch — the restored context is a clean slate with no programs, buffers, or textures. The render loop is resumed automatically by WakaPAC after this message is dispatched.

Message Parameters

Parameter Type Description
wParam number Reserved (always 0)
lParam number Reserved (always 0)
event.detail.glContext WebGLRenderingContext | WebGL2RenderingContext The fresh GL context. Use this to recompile shaders and re-upload all geometry and textures. Mirrors the event.detail.glContext provided by MSG_WEBGL_READY.

Example: Context Loss and Recovery

wakaPAC('#scene', {
    msgProc(event) {
        switch (event.message) {

            case wakaPAC.MSG_WEBGL_READY: {
                this._setupGL(event.detail.glContext);
                wakaPAC.releaseDC(event.detail.glContext);
                break;
            }

            case wakaPAC.MSG_WEBGL_CONTEXT_LOST: {
                // Null out all GL handles — they are invalid until context is restored.
                // Do not call any GL functions here.
                this._prog = null;
                this._vao  = null;
                this._tex  = null;
                break;
            }

            case wakaPAC.MSG_WEBGL_CONTEXT_RESTORED: {
                // event.detail.glContext is a fresh context — rebuild everything from scratch
                this._setupGL(event.detail.glContext);
                wakaPAC.releaseDC(event.detail.glContext);
                break;
            }

            case wakaPAC.MSG_PAINT: {
                const gl = wakaPAC.getDC(this.pacId);
                // draw...
                wakaPAC.releaseDC(gl);
                break;
            }
        }
    },

    _setupGL(gl) {
        this._prog = buildShaderProgram(gl);
        this._vao  = uploadGeometry(gl);
        this._tex  = uploadTexture(gl);
        gl.enable(gl.DEPTH_TEST);
    }
}, {
    dcAttributes: { antialias: true, depth: true },
    renderLoop: true
});

API

getDC()

Returns the device context (DC) for a canvas PAC container. The context type is determined by the data-pac-context attribute on the canvas element — '2d' (default), 'webgl', or 'webgl2'. The toolkit caches the DC internally — repeated calls return the same DC with no overhead. The DC is released when the canvas is removed from the page. When called inside a MSG_PAINT handler for a 2D canvas, getDC() automatically clips drawing to the dirty region — the set of rectangles accumulated by invalidateRect() since the last paint. The clip is released by releaseDC(). Outside of MSG_PAINT, or for WebGL canvases, the DC is returned unmodified.

wakaPAC.getDC(pacId)
Parameter Type Description
pacId string The data-pac-id of the target canvas container

DC attributes are declared via the dcAttributes option at registration time and applied when the DC is first created. See dcAttributes below.

releaseDC()

Releases a device context previously obtained with getDC(). Must be called once for every getDC() call made inside a MSG_PAINT handler for 2D canvases. Omitting it leaks the DC save stack — after roughly 1024 frames the stack overflows and clipping state becomes corrupted. Outside of MSG_PAINT, or for WebGL/WebGL2 contexts, releaseDC() is a no-op and safe to call unconditionally.

wakaPAC.releaseDC(hdc)
Parameter Type Description
hdc DC The device context returned by getDC()

createCompatibleDC()

Creates an off-screen compatible device context sized to match the current canvas backing store. The context type matches the source canvas's data-pac-context attribute — a WebGL2 canvas produces a WebGL2 OffscreenCanvas context, a 2D canvas produces a 2D one. The returned DC behaves identically to the one returned by getDC() for drawing purposes. The component owns the returned DC. Compatible DCs are not resized automatically — recreate them in MSG_SIZE after calling resizeCanvas(). Returns null if the pacId does not refer to a canvas container.

wakaPAC.createCompatibleDC(pacId)
Parameter Type Description
pacId string The data-pac-id of the target canvas container

deleteCompatibleDC()

Releases a compatible DC created by createCompatibleDC(). Zeroes the backing store dimensions so the pixel buffer is reclaimed promptly. Safe to call with null.

wakaPAC.deleteCompatibleDC(dc)
Parameter Type Description
dc compatible DC DC returned by createCompatibleDC()

Example: Cached Background

wakaPAC('#myCanvas', {
    init() {
        this.bgDC = null;
    },

    msgProc(event) {
        switch (event.message) {

            case wakaPAC.MSG_SIZE: {
                const width  = wakaPAC.LOWORD(event.lParam);
                const height = wakaPAC.HIWORD(event.lParam);

                wakaPAC.resizeCanvas(this.pacId, width, height);
                this.layoutWidth  = width;
                this.layoutHeight = height;

                // Recreate the compatible DC at the new size and redraw the background
                wakaPAC.deleteCompatibleDC(this.bgDC);
                this.bgDC = wakaPAC.createCompatibleDC(this.pacId);
                drawBackground(this.bgDC, width, height);
                break;
            }

            case wakaPAC.MSG_PAINT: {
                const hdc = wakaPAC.getDC(this.pacId);

                // Blit the cached background, then draw dynamic content on top
                wakaPAC.bitBlt(hdc, this.bgDC, 0, 0);
                drawForeground(hdc, this.layoutWidth, this.layoutHeight);

                wakaPAC.releaseDC(hdc);
                break;
            }
        }
    }
});

getDCFromElement()

Returns the rendering context for any <canvas> element — not tied to a PAC container. Use this to obtain a drawing context for canvas elements inside foreach item templates or other non-container canvases, where getDC() is not available. The context type is determined by the element's data-pac-context attribute ('2d' by default). An optional attributes argument is passed through to getContext().

wakaPAC.getDCFromElement(canvasElement, attributes?)
Parameter Type Description
canvasElement HTMLCanvasElement The canvas element to get a context for
attributes object Optional context attributes passed to getContext(). See dcAttributes

Returns a CanvasRenderingContext2D, WebGLRenderingContext, or WebGL2RenderingContext depending on data-pac-context, or null if the element is not a canvas.

invalidateRect()

Marks a canvas container as needing repaint. Multiple calls before the next frame are accumulated into a dirty region list — only a single MSG_PAINT fires. Incoming rectangles that are fully contained within an already-queued rectangle are dropped. Use getUpdateRgn() during MSG_PAINT to retrieve the discrete dirty rectangles.

wakaPAC.invalidateRect(pacId, rect?)
Parameter Type Description
pacId string The data-pac-id of the target canvas container
rect object | null Optional rectangle to invalidate in canvas-local pixel coordinates: { x, y, width, height }. Omit or pass null to invalidate the entire canvas surface

Example: Invalidate on Data Change

wakaPAC('#myChart', {
    init() {
        // Fetch data, then request repaint
        fetchChartData().then(data => {
            this.data = data;
            wakaPAC.invalidateRect(this.pacId);
        });
    },

    msgProc(event) {
        if (event.message === wakaPAC.MSG_PAINT) {
            const hdc = wakaPAC.getDC(this.pacId);
            drawChart(hdc, this.data);
            wakaPAC.releaseDC(hdc);
        }
    }
});

requestRender()

Schedules a single MSG_PAINT for a WebGL canvas component on the next animation frame. The WebGL equivalent of invalidateRect() — use this to trigger an on-demand redraw on a WebGL canvas that does not use renderLoop: true. Safe to call multiple times before the next frame; only one MSG_PAINT will fire. Has no effect if the pacId does not refer to a WebGL/WebGL2 canvas container.

wakaPAC.requestRender(pacId)
Parameter Type Description
pacId string The data-pac-id of the target WebGL canvas container

Example: On-Demand WebGL Redraw

wakaPAC('#scene', {
    msgProc(event) {
        switch (event.message) {

            case wakaPAC.MSG_WEBGL_READY: {
                const gl = event.detail.glContext;
                // set up shaders, geometry...
                wakaPAC.releaseDC(gl);
                // Trigger the first render
                wakaPAC.requestRender(this.pacId);
                break;
            }

            case wakaPAC.MSG_LCLICK: {
                // Redraw in response to interaction
                this.angle += 0.1;
                wakaPAC.requestRender(this.pacId);
                break;
            }

            case wakaPAC.MSG_PAINT: {
                const gl = wakaPAC.getDC(this.pacId);
                // draw...
                wakaPAC.releaseDC(gl);
                break;
            }
        }
    }
}, {
    dcAttributes: { antialias: true, depth: true }
    // no renderLoop — redraws only when requested
});

getInvalidatedRect()

Returns the bounding rectangle of the current dirty region for a canvas container, or null if no repaint is pending. The rect is the union of all rectangles accumulated by invalidateRect() since the last paint — it is the coarsest possible bound and may include clean areas between discrete dirty rectangles. Returns { x, y, width, height } or null. The returned object is a copy — mutating it has no effect on internal state.

For partial clears and targeted redraws, prefer getUpdateRgn(), which returns the discrete dirty rectangles rather than their bounding union.

wakaPAC.getInvalidatedRect(pacId)

getUpdateRgn()

Returns the list of discrete dirty rectangles for a canvas container, or null if no repaint is pending. Each entry is a rectangle in canvas-local pixel coordinates: { x, y, width, height }.

Unlike getInvalidatedRect(), which returns the bounding union of all dirty areas, getUpdateRgn() gives you the individual rectangles that were passed to invalidateRect(). This lets you skip clear and redraw work in the clean gaps between dirty regions. The toolkit also clips drawing to these discrete rectangles — iterating the region and clearing only each dirty rect avoids touching pixels that do not need to change.

Rectangles that are fully contained within an already-queued rectangle are not added to the list. The returned array is a shallow copy — mutating it has no effect on internal state.

wakaPAC.getUpdateRgn(pacId)

Example: Partial Repaint

msgProc(event) {
    if (event.message === wakaPAC.MSG_PAINT) {
        const hdc   = wakaPAC.getDC(this.pacId);
        const rects = wakaPAC.getUpdateRgn(this.pacId);

        for (const rc of rects) {
            // Clear and redraw each discrete dirty rectangle
            hdc.clearRect(rc.x, rc.y, rc.width, rc.height);
            drawGridRegion(hdc, rc);
        }

        wakaPAC.releaseDC(hdc);
    }
}

resizeCanvas()

Resizes the backing store of a canvas PAC container. For 2D canvases, resizing always triggers a repaint — MSG_PAINT fires automatically afterward. For WebGL/WebGL2 canvases, the backing store is resized but MSG_PAINT is not scheduled, since WebGL components drive their own render loop via requestAnimationFrame.

wakaPAC.resizeCanvas(pacId, width, height)
Parameter Type Description
pacId string The data-pac-id of the target canvas container
width number New backing store width in pixels
height number New backing store height in pixels

getCanvasSize()

Returns the current backing store dimensions of a canvas PAC container. These reflect the actual pixel dimensions of the canvas element, which may differ from the CSS layout size when display scaling is in use.

wakaPAC.getCanvasSize(pacId)
Parameter Type Description
pacId string The data-pac-id of the target canvas container

Returns { width, height }, or null if the pacId does not refer to a canvas container.

bitBlt()

Blits a source DC or bitmap handle onto a destination DC at the given offset. If dw and dh are omitted the source is copied at its natural dimensions. Supports mixed 2D ↔ WebGL copies:

Source Destination Behaviour
2D 2D drawImage() — straightforward pixel copy
WebGL 2D drawImage() on the WebGL canvas — requires preserveDrawingBuffer: true in dcAttributes, otherwise the source drawing buffer will have been cleared by the browser after compositing and the copy produces a blank result
2D WebGL texImage2D() onto the currently bound TEXTURE_2D — the caller must bind the target texture before calling bitBlt()
WebGL WebGL drawImage() via the source canvas — requires preserveDrawingBuffer: true
wakaPAC.bitBlt(destDC, srcDC, dx, dy, cx?, cy?, sx?, sy?)
Parameter Type Description
destDC DC Destination device context
srcDC DC or bitmap handle Source DC created by createCompatibleDC(), or a bitmap handle from loadBitmap()
dx number Destination X coordinate
dy number Destination Y coordinate
cx number Width of the rectangle to copy, in pixels. Applied to both source and destination — no scaling occurs. Defaults to the full source width
cy number Height of the rectangle to copy, in pixels. Applied to both source and destination — no scaling occurs. Defaults to the full source height
sx number X coordinate of the top-left corner of the source rectangle. Default: 0
sy number Y coordinate of the top-left corner of the source rectangle. Default: 0

stretchBlt()

Blits a source DC onto a destination DC, scaling the source to fill the destination rectangle. Unlike bitBlt(), which copies at the source's natural dimensions, stretchBlt() always stretches the entire source to fit (dw, dh). Use this when the offscreen DC dimensions differ from the target canvas — for example, when blitting a sparkline generated at one size onto a canvas element of a different size.

When the destination is a WebGL/WebGL2 context, stretchBlt() behaves identically to bitBlt() — the source canvas is uploaded via texImage2D() onto the currently bound TEXTURE_2D and dx, dy, dw, dh are ignored. Scaling is the caller's responsibility via their shader and geometry. The same preserveDrawingBuffer requirement applies when the source is also a WebGL canvas.

wakaPAC.stretchBlt(destDC, srcDC, dx, dy, dw, dh)
Parameter Type Description
destDC DC Destination device context
srcDC DC Source device context — the full surface is used as the source rect
dx number Destination X coordinate
dy number Destination Y coordinate
dw number Destination width
dh number Destination height

loadBitmap()

Loads a bitmap from a variety of sources, returning an opaque bitmap handle ready to pass to bitBlt(), getBitmapSize(), saveBitmap(), and deleteBitmap(). The caller owns the returned handle and must call deleteBitmap() when done. Returns null on failure — unsupported source type, network error, CORS block, or image not yet loaded.

await wakaPAC.loadBitmap(source)
Parameter Type Description
source string URL or data URI — fetched and decoded. CORS applies for cross-origin URLs.
source <img> element Must be fully loaded before passing. Returns null if the image is not yet decoded.
source ImageBitmap Used directly. Caller retains ownership and must call .close() themselves.
source ImageData Raw RGBA pixel buffer, drawn at its natural dimensions.
source Blob Decoded and drawn at its natural dimensions.
source <canvas> element Snapshot of the canvas at call time.
source OffscreenCanvas Snapshot of the offscreen canvas at call time.

Example

// Load from URL
const hBitmap = await wakaPAC.loadBitmap('/assets/sprite.png');

// Load from an <img> element
const hBitmap = await wakaPAC.loadBitmap(img);

// Blit onto the canvas DC in MSG_PAINT
wakaPAC.bitBlt(hdc, hBitmap, x, y);

// Release when done
wakaPAC.deleteBitmap(hBitmap);

saveBitmap()

Saves the contents of a DC or bitmap handle to an image file, triggering a download. Works with compatible DCs from createCompatibleDC(), bitmap handles from loadBitmap(), and DCs from getDC(). The image format is derived from the filename extension: .png, .jpg/.jpeg, or .webp. Defaults to PNG if the extension is absent or unrecognised. Not available in Web Workers.

await wakaPAC.saveBitmap(dc, filename?)
Parameter Type Description
dc DC or bitmap handle The DC or bitmap handle to save
filename string Output filename. Defaults to 'bitmap.png'

Returns true on success, false on failure.

Example

const hdc = wakaPAC.getDC(this.pacId);
await wakaPAC.saveBitmap(hdc, 'screenshot.png');   // PNG
await wakaPAC.saveBitmap(hdc, 'screenshot.jpg');   // JPEG
await wakaPAC.saveBitmap(hdc, 'screenshot.webp');  // WebP
wakaPAC.releaseDC(hdc);

getBitmapSize()

Returns the pixel dimensions of a bitmap handle created by loadBitmap(). Returns null if the handle is invalid.

wakaPAC.getBitmapSize(hBitmap)
Parameter Type Description
hBitmap bitmap handle Handle returned by loadBitmap()

Returns { width, height }, or null if the handle is invalid.

deleteBitmap()

Releases a bitmap handle created by loadBitmap(). The handle must not be used after calling this. Equivalent to deleteCompatibleDC() — provided as a semantically matching counterpart to loadBitmap().

wakaPAC.deleteBitmap(hBitmap)
Parameter Type Description
hBitmap bitmap handle Handle returned by loadBitmap()

Metafile API

A metafile is a display list — an array of drawing instruction objects produced by chart functions or other display list producers. Metafiles separate the description of drawing from its execution, enabling caching, hit testing, and compositing of multiple charts onto a single canvas. The concept mirrors Win32's Enhanced Metafile (EMF) format.

MetaFile

A fluent builder for constructing display lists without manually assembling raw op objects. Each method appends one drawing instruction and returns this, so calls can be chained. The API mirrors CanvasRenderingContext2D so sequences of drawing instructions read naturally. Call build() to retrieve the completed array, which can then be passed to playMetaFile() and metaFileHitTest() like any other display list.

const dl = new wakaPAC.MetaFile()
    .setFillStyle('#4e79a7')
    .beginPath()
    .arc(cx, cy, r, startAngle, endAngle)
    .fill()
    .build();

For conditional ops, break the chain — the builder accumulates state across separate statements and build() can be called at any point afterward:

const dl = new wakaPAC.MetaFile();

dl.setFillStyle(color).beginPath().arc(cx, cy, r, startAngle, endAngle).closePath().fill();

if (o.gap > 0) {
    dl.setStrokeStyle('#ffffff').setLineWidth(o.gap).stroke();
}

return dl.build();

State setters

Method Description
setFillStyle(value)Sets fillStyle
setStrokeStyle(value)Sets strokeStyle
setLineWidth(value)Sets lineWidth
setLineCap(value)Sets lineCap
setLineJoin(value)Sets lineJoin
setLineDashOffset(value)Sets lineDashOffset
setMiterLimit(value)Sets miterLimit
setGlobalAlpha(value)Sets globalAlpha
setGlobalComposite(value)Sets globalCompositeOperation
setFont(value)Sets font
setTextAlign(value)Sets textAlign
setTextBaseline(value)Sets textBaseline
setTextRendering(value)Sets textRendering
setLetterSpacing(value)Sets letterSpacing
setWordSpacing(value)Sets wordSpacing

No-argument methods

Method Description
save()Saves canvas state
restore()Restores canvas state
beginPath()Begins a new path
closePath()Closes the current path
stroke()Strokes the current path

Drawing commands

Method Description
fill(rule?)Fills the current path. rule is 'nonzero' (default) or 'evenodd'
clip(rule?)Clips to the current path. Same fill rule options as fill()
moveTo(x, y)Moves the current point
lineTo(x, y)Draws a line to the given point
arc(cx, cy, r, startAngle, endAngle, ccw?)Appends an arc. ccw defaults to false
arcTo(x1, y1, x2, y2, r)Appends an arc defined by two tangent lines
ellipse(cx, cy, rx, ry, rotation, startAngle, endAngle, ccw?)Appends an ellipse
rect(x, y, w, h)Appends a rectangle subpath (does not fill or stroke)
roundRect(x, y, w, h, r)Appends a rounded rectangle subpath
bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)Appends a cubic Bézier curve
quadraticCurveTo(cpx, cpy, x, y)Appends a quadratic Bézier curve
fillRect(x, y, w, h)Fills a rectangle directly (no current path involved)
strokeRect(x, y, w, h)Strokes a rectangle directly
clearRect(x, y, w, h)Clears a rectangle to transparent
fillText(text, x, y, maxWidth?)Fills text at the given position
strokeText(text, x, y, maxWidth?)Strokes text at the given position
drawImage(image, dx, dy, dw, dh)Draws an image or DC onto the canvas

Transforms

Method Description
translate(x, y)Translates the current transform
rotate(angle)Rotates the current transform by angle radians
scale(x, y)Scales the current transform
transform(a, b, c, d, e, f)Multiplies the current transform by the given matrix
setTransform(a, b, c, d, e, f)Replaces the current transform with the given matrix
resetTransform()Resets the transform to the identity matrix

Miscellaneous

Method Description
setLineDash(value)Sets the line dash pattern. Pass an empty array to reset to a solid line
setShadow(color, blur, offsetX?, offsetY?)Sets shadow properties in one call. offsetX and offsetY default to 0
clearShadow()Clears all shadow properties
setImageSmoothing(enabled, quality?)Controls image smoothing. quality is 'low', 'medium', or 'high'

Hit areas

Hit area entries are not drawn — they are consulted by metaFileHitTest() to associate a region of the canvas with a data payload. Two shapes are supported.

// Axis-aligned rectangle
dl.hitArea('rect', { x, y, w, h, data: { /* payload */ } });

// Pie/donut sector
dl.hitArea('sector', { cx, cy, r, innerR, startAngle, endAngle, data: { /* payload */ } });
Parameter Type Description
shape string 'rect' or 'sector'
params object Shape-specific geometry fields plus a data payload returned by metaFileHitTest() on a match

Terminal

Method Description
build() Returns the completed display list array. The builder should not be used after calling build().

playMetaFile()

Executes a display list onto a canvas context. Drawing instructions are translated directly to CanvasRenderingContext2D calls. The offset is applied via ctx.translate so all coordinates in the display list are naturally relative to (0, 0). State is saved and restored around the entire playback — the caller's context is unaffected. Non-drawing instructions (such as hitArea entries) are silently skipped.

wakaPAC.playMetaFile(ctx, dl, offsetX?, offsetY?)
Parameter Type Description
ctx DC The canvas context to draw onto
dl Array Display list returned by a chart or drawing function
offsetX number X offset applied to the entire display list. Default: 0
offsetY number Y offset applied to the entire display list. Default: 0
Clipping: When playMetaFile is called inside a MSG_PAINT handler after getDC(), drawing is automatically clipped to the dirty region set up by getDC(). The metafile's own save()/restore() wrapping sits inside that clip and does not affect it.

metaFileHitTest()

Tests a point against the hitArea entries in a display list. Returns the data payload of the first matching hit area, or null if no area was hit. Supported hit area shapes are rect (axis-aligned rectangle) and sector (pie/donut slice). Pass the same offsetX/offsetY that were passed to playMetaFile — coordinates are adjusted internally so the caller works in container coordinates throughout.

wakaPAC.metaFileHitTest(dl, x, y, offsetX?, offsetY?)
Parameter Type Description
dl Array Display list to test against
x number X coordinate to test (container coordinates)
y number Y coordinate to test (container coordinates)
offsetX number X offset passed to playMetaFile. Default: 0
offsetY number Y offset passed to playMetaFile. Default: 0

ptInMetaFile()

Returns true if the point falls within any hitArea entry in the display list. Equivalent to ptInElement() but for metafile hit areas. Use this for hover detection and cursor changes when you need to know whether a point hits anything but not what it hits. For retrieving the hit data, use metaFileHitTest() instead.

wakaPAC.ptInMetaFile(dl, x, y, offsetX?, offsetY?)
Parameter Type Description
dl Array Display list to test against
x number X coordinate to test (container coordinates)
y number Y coordinate to test (container coordinates)
offsetX number X offset passed to playMetaFile. Default: 0
offsetY number Y offset passed to playMetaFile. Default: 0

Returns true if the point is within any hit area, false otherwise.

Attribute: data-pac-context

Declares the rendering context type for a canvas PAC container. Set directly on the <canvas> element. If absent, the container defaults to a 2D context. The context type is locked on first use — it cannot be changed after getDC() has been called.

Value Description
2d Standard 2D canvas context (CanvasRenderingContext2D). Default if the attribute is absent.
webgl WebGL 1 context (WebGLRenderingContext).
webgl2 WebGL 2 context (WebGL2RenderingContext). Preferred over webgl for new components — WebGL2 has near-universal browser support and is a strict superset of WebGL1.

Example: WebGL2 Component

<canvas data-pac-id="myScene" data-pac-context="webgl2"></canvas>
wakaPAC('#myScene', {
    msgProc(event) {
        switch (event.message) {

            case wakaPAC.MSG_WEBGL_READY: {
                // event.detail.glContext is the GL context — safe to compile shaders here
                const gl = event.detail.glContext;
                this.program = buildShaderProgram(gl);
                this.buffer  = createGeometry(gl);
                wakaPAC.releaseDC(gl);
                break;
            }

            case wakaPAC.MSG_PAINT: {
                const gl   = wakaPAC.getDC(this.pacId);
                const size = wakaPAC.getCanvasSize(this.pacId);

                gl.viewport(0, 0, size.width, size.height);
                gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
                gl.useProgram(this.program);
                gl.drawArrays(gl.TRIANGLES, 0, 3);
    
                wakaPAC.releaseDC(gl);
                break;
            }

            case wakaPAC.MSG_SIZE: {
                const width  = wakaPAC.LOWORD(event.lParam);
                const height = wakaPAC.HIWORD(event.lParam);

                // Resize the backing store — MSG_PAINT is not scheduled for WebGL
                wakaPAC.resizeCanvas(this.pacId, width, height);
                break;
            }
        }
    }
}, {
    dcAttributes: { antialias: true, depth: true },
    renderLoop: true
});

Example: Layered 2D + WebGL

A single canvas element can only hold one context type. To composite 2D content (e.g. a HUD or overlay) with a 3D WebGL scene, use two separate canvas elements positioned on top of each other via CSS, each registered as its own PAC component. Use bitBlt() to copy content between them when needed.

<div style="position: relative; width: 800px; height: 600px;">
    <canvas data-pac-id="scene3d" data-pac-context="webgl2"
            style="position: absolute; inset: 0;"></canvas>
    <canvas data-pac-id="hud2d"
            style="position: absolute; inset: 0; pointer-events: none;"></canvas>
</div>

Option: renderLoop

When true, WakaPAC automatically starts a requestAnimationFrame loop for the component and dispatches MSG_PAINT on every frame. The loop is cancelled automatically when the component is destroyed. Only valid for WebGL/WebGL2 canvases — setting renderLoop: true on a 2D canvas is ignored with a console warning.

Omit renderLoop (or set it to false) for WebGL components that redraw only in response to data changes or user interaction rather than continuously.

Value Type Description
true boolean Start a rAF loop on component creation. MSG_PAINT fires every frame.
false / absent boolean No render loop. The component is responsible for triggering its own redraws.

Option: dcAttributes

Declares the rendering attributes for a canvas PAC container's device context. These are applied when the DC is first created and cannot be changed afterward — they must be declared upfront at registration time. The same dcAttributes key is used regardless of context type; the browser ignores attributes that are not relevant to the active context.

2D context attributes

Attribute Type Description
alpha boolean Set false if the canvas has no transparency, enabling compositing optimisations. Defaults to true
colorSpace string "srgb" (default) or "display-p3"
colorType string "unorm8" (default) or "float16"
desynchronized boolean Reduces latency by decoupling DC updates from the event loop
willReadFrequently boolean Optimises for frequent pixel readback by using a software renderer

WebGL/WebGL2 context attributes

Attribute Type Description
alpha boolean Whether the canvas contains an alpha channel. Defaults to true
antialias boolean Whether multi-sample anti-aliasing is performed. Defaults to true
depth boolean Whether a depth buffer of at least 16 bits is allocated. Defaults to true
stencil boolean Whether a stencil buffer of at least 8 bits is allocated. Defaults to false
preserveDrawingBuffer boolean When true, the drawing buffer is preserved between frames. Required when using bitBlt() or saveBitmap() with a WebGL source, otherwise the buffer will have been cleared by the browser after compositing and the operation produces a blank result. Defaults to false.
powerPreference string "default", "high-performance", or "low-power". Hints to the browser which GPU to use on multi-GPU systems
desynchronized boolean Reduces latency by decoupling canvas updates from the event loop. Defaults to false

Example

wakaPAC('#myCanvas', {
    msgProc(event) {
        if (event.message === wakaPAC.MSG_PAINT) {
            const hdc = wakaPAC.getDC(this.pacId);
            // hdc was created with alpha: false, willReadFrequently: true
        }
    }
}, {
    dcAttributes: { alpha: false, willReadFrequently: true }
});

Resize and Repaint

When a canvas container is resized, MSG_SIZE fires first, followed automatically by MSG_PAINT on the same frame. Handle MSG_SIZE to update the canvas backing store dimensions and any layout-dependent state. MSG_PAINT will follow and redraw the content at the new size.

msgProc(event) {
    switch (event.message) {

        case wakaPAC.MSG_SIZE: {
            const width  = wakaPAC.LOWORD(event.lParam);
            const height = wakaPAC.HIWORD(event.lParam);

            // Sync canvas backing store to container size
            wakaPAC.resizeCanvas(this.pacId, width, height);

            // Update any layout state that depends on dimensions
            this.layoutWidth  = width;
            this.layoutHeight = height;
            break;
        }

        case wakaPAC.MSG_PAINT: {
            const hdc = wakaPAC.getDC(this.pacId);
            hdc.clearRect(0, 0, this.layoutWidth, this.layoutHeight);
            drawContent(hdc, this.layoutWidth, this.layoutHeight);
            wakaPAC.releaseDC(hdc);
            break;
        }
    }
}

Best Practices

  • Never draw outside MSG_PAINT (2D): Always call invalidateRect() to request a repaint rather than drawing directly from event handlers, timers, or data callbacks. This ensures paints are coalesced and synchronised with the display refresh rate.
  • WebGL components use renderLoop or manage their own rendering: For continuously animated WebGL components, set renderLoop: true in the options object. WakaPAC will start a requestAnimationFrame loop automatically and dispatch MSG_PAINT each frame, cancelling the loop when the component is destroyed. For WebGL components that only redraw in response to data changes or interaction, omit renderLoop and call getDC() directly when needed. Do not use invalidateRect() for WebGL canvases. Setting renderLoop: true on a 2D canvas is ignored with a warning.
  • Initialize GL resources in MSG_WEBGL_READY, not init(): For WebGL canvas components, MSG_WEBGL_READY is dispatched once after the first MSG_SIZE, at which point the canvas is laid out and the GL context is valid. The context is provided directly as event.detail.glContext — compile shaders, create buffers, and upload geometry here without a separate getDC() call. The init() method runs at registration time before the canvas has a layout — calling getDC() there is not safe for WebGL components. Use init() only for non-GL state that does not require a valid context.
  • Handle MSG_WEBGL_CONTEXT_LOST and MSG_WEBGL_CONTEXT_RESTORED: The browser can reclaim the WebGL context at any time — particularly on mobile. When MSG_WEBGL_CONTEXT_LOST fires, null out all GL resource handles (programs, buffers, textures) and stop issuing GL commands. WakaPAC calls preventDefault() on the underlying browser event automatically and suspends the render loop. When MSG_WEBGL_CONTEXT_RESTORED fires, rebuild all GL resources using event.detail.glContext, exactly as you would in MSG_WEBGL_READY. Not handling context loss will leave the canvas permanently black after a loss event.
  • Keep MSG_PAINT handlers fast: MSG_PAINT runs inside the display refresh callback. Slow handlers will drop frames. Expensive calculations should happen outside MSG_PAINT — in init(), event handlers, or data callbacks — and their results stored on this. The MSG_PAINT handler should only draw pre-computed state, never compute it.
  • Set DC attributes at registration: Declare dcAttributes in the options object when registering the component. They are applied on first DC creation and have no effect if set later.
  • Set preserveDrawingBuffer: true when blitting from WebGL: If you use bitBlt(), stretchBlt(), or saveBitmap() with a WebGL canvas as the source, declare preserveDrawingBuffer: true in dcAttributes. Without it, the drawing buffer is cleared by the browser after compositing and the operation produces a blank result. This carries a performance cost — only set it when needed.
  • Bind the target texture before blitting to WebGL: When calling bitBlt() or stretchBlt() with a WebGL destination, the source is uploaded via texImage2D() to the currently bound TEXTURE_2D. Bind your target texture with gl.bindTexture() before calling the blit function.
  • Sync canvas dimensions in MSG_SIZE: Call wakaPAC.resizeCanvas() in the MSG_SIZE handler, not in MSG_PAINT. For 2D canvases MSG_PAINT follows automatically. For WebGL canvases, update the viewport in your render loop by reading the new size with getCanvasSize().
  • Recreate compatible DCs on resize: A compatible DC is sized at creation time and does not resize automatically. In MSG_SIZE, call resizeCanvas() first, then deleteCompatibleDC() on the old DC and createCompatibleDC() to get a new one at the updated dimensions. Compatible DCs match the context type of the source canvas.
  • Always pair getDC() with releaseDC() for 2D: Every getDC() call inside a MSG_PAINT handler for a 2D canvas must be balanced with a matching releaseDC(). Omitting it leaks the DC save stack. For WebGL canvases, releaseDC() is a no-op.
  • Use getUpdateRgn() for partial clears: During MSG_PAINT the toolkit automatically clips drawing to the dirty region — you do not need getUpdateRgn() to restrict rendering. Use it when you also want to limit clearRect() or other explicit operations to each discrete dirty rectangle, skipping the clean gaps between them. getInvalidatedRect() is available when only the bounding union is needed, but getUpdateRgn() is preferred for targeted redraws.