valo
Guides

Canvas2D compatibility

Run Canvas2D-shaped drawing code on Valo and understand the known differences.

The valo-web entry point provides a Canvas2D-compatible recorder over the same renderer as the engine API.

import { createValoCanvas } from 'valo-web';

const context = await createValoCanvas(document.querySelector('canvas')!);
context.fillStyle = 'rgba(200, 255, 61, 0.9)';
context.beginPath();
context.roundRect(24, 24, 200, 120, [34, 10, 34, 10]);
context.fill();

Use this entry point when migrating Canvas2D-shaped code. For a new application that wants Valo-specific features, prefer the JavaScript engine API.

What is covered

The adapter implements the commonly used Canvas2D surface:

  • Drawing state, save/restore, transforms and compositing
  • Paths, fills, strokes, clips, gradients and patterns
  • Images, image smoothing and pixel uploads
  • Shadows and CSS filter chains
  • Text drawing, font shorthand parsing and TextMetrics
  • Path2D, hit testing and transform queries

The conformance suite renders scenes through both Valo and Chrome's Canvas2D, then compares their output. It covers normal drawing as well as invalid, degenerate and generated command sequences.

Known differences

getImageData is not implemented because it requires a synchronous GPU read-back. putImageData is supported.

  • Patterns remain connected to their source instead of taking the browser's creation-time snapshot.
  • putImageData respects the active clip; browser Canvas2D ignores it.
  • The hanging text baseline uses a font-metric fallback rather than the OpenType BASE table.
  • isContextLost() does not yet observe WebGPU device loss.

These are compatibility limits, not behaviour the engine API inherits.

Valo extensions

The adapter exposes a few operations with no standard Canvas2D spelling:

context.saveLayer(alpha, blendMode);
context.setColorMatrix(matrix);
context.backdropBlur(x, y, width, height, sigma);

For difference clips, gradient spread modes, mask blur styles and retained display lists, use valo-web/raw.

Frame control

By default, changes are batched and presented on the next animation frame. Use manual presentation when your application already owns the frame loop:

const context = await createValoCanvas(canvas, { autoPresent: false });

context.beginFrame('#0e0e12');
draw(context);
const stats = context.present();

Run npm run test:conformance in the repository to execute the browser comparison suite.

On this page