niso-canvas
Generated from crates/niso-canvas/CONTRACT.md when the site was built at 7aa23e2.
Purpose
Section titled “Purpose”HTML’s 2D context over a bitmap: CanvasRenderingContext2D’s state (the state stack, transforms, line styles, fill and stroke styles, compositing, shadows, filters, image smoothing, text styles), paths and Path2D, fills, strokes and clips, isPointInPath and isPointInStroke, gradients and patterns, drawImage, image data, text through Parley, PNG encoding, and the canvas’s own platform objects (Path2D, CanvasGradient, CanvasPattern, ImageBitmap, OffscreenCanvas and its context). Drawing is rasterized on the CPU with vello_cpu.
It does not parse CSS: a host turns colours, fonts and filter strings into
Color, Font and FilterFn with its own CSS engine and hands them over
typed, so a colour that needs an element’s currentColor or a font’s em is
the host’s to resolve. It does not decode images (a host hands over RGBA or
a Pixmap), own the canvas element, or know how a host shows a bitmap.
In niso, niso-core does all three (crates/niso-core/src/canvas.rs and
src/api/canvas.rs, behind its canvas feature), and tells which images
are origin-clean through Dom::cors_same_origin.
Public surface
Section titled “Public surface”- Rust:
Canvas(the context and its bitmap),State,Path,Radii,Paint,Color,Gradient,GradientKind,Pattern,Repetition,Composite,LineCap,LineJoin,SmoothingQuality,FilterFn,ImageRects,Error,Result,MAX_SIDE,MAX_AREA,Budget; image helpersimage_from_rgba,image_rgba,image_size,crop_image;text(Font,TextStyle,TextMetrics,layout,Laid);objects(the platform objects,register,offscreen_context). It re-exportskurboandvello_cpu::Pixmap. - C: none.
- IDL: idl/canvas.webidl; the bindings call the core’s api/canvas.rs, which calls this crate.
- Glue: glue/canvas.js, the Canvas 2D section of the glue (argument conversion the IDL cannot say, dictionaries, ImageBitmap options).
Host requirements
Section titled “Host requirements”- A
parley::FontContextandLayoutContextfor text; the host chooses the fonts. niso-core shares its document’s font collection with it. - For the platform objects, niso-host’s registrar and
Cx(crates/niso-host/CONTRACT.md).
May assume from dependencies
Section titled “May assume from dependencies”- niso-base:
Obj. - niso-host:
Cx,InterfaceInfo,Obj,PlatformObject,Registrar.
Guarantees
Section titled “Guarantees”- A canvas larger than
MAX_SIDEon a side orMAX_AREApixels has no bitmap: it draws nothing, reads as transparent black and takes no memory. basics::huge_canvases_have_no_bitmap. - Paints are resolved when drawn, so a gradient or pattern follows the transform current at the draw, and a stroke’s width scales with it. basics::gradient_follows_the_transform_at_draw_time, basics::stroke_width_scales_with_the_transform.
- Compositing other than source-over is unbounded within the clip, as the standard says: pixels the shape does not cover are composited with transparent black. basics::composite_is_unbounded_within_the_clip, basics::composite_with_two_clips.
- Drawing is batched, and reading the bitmap flushes it: image data, a
snapshot, PNG and
presentsee every draw before them, and drawing with the bitmap as the backdrop keeps its pixels exact. basics::backdrop_keeps_pixels_exact, basics::fill_rect_and_read. presentreturns the rectangles drawn since the last call, andpixelsgives straight (not premultiplied) RGBA. basics::present_reports_damage_and_straight_pixels.- Nothing a page asks for aborts the process or takes memory it doesn’t
have. Every buffer the size of a bitmap or of image data is allocated
fallibly and, for bitmaps and the copy hosts take, charged to the
canvas’s
Budget(one per document;Budget::DEFAULTis 384 MiB on Android and iOS, 1 GiB elsewhere). A canvas whose bitmap the budget or memory can’t hold behaves like one pastMAX_AREA, andget_image_datareturns None, which the core throws as RangeError. Arcs, ellipses, arcTo and roundRect become cubics of at most a quarter turn each, as many as 0.1 device pixels of error needs and never more than 4096 a call; a dashed stroke that would have more than 100,000 dashes in its length is stroked solid, and so is isPointInStroke; curves a stroke can’t reach the view through are drawn as lines. A clip costs its path and bounds, plus a coverage mask only when unbounded compositing meets more than one clip. Drawing batched past 16 MiB renders, so an unread canvas holds no more.hidefrees the copypresentkeeps for a canvas no frame shows. limits::*. - A canvas, a pattern and an ImageBitmap carry an origin-clean flag: a canvas loses it when it draws an image, pattern or bitmap that isn’t origin-clean, and nothing, a reset included, gives it back. The host decides which images are origin-clean and refuses reads of a canvas that isn’t. limits::origin_clean_survives_resets, limits::patterns_keep_their_image_origin.
- Conformance is measured by WPT html/canvas/element and html/canvas/offscreen (tests/wpt/expectations*.json), and drawing against Chrome by crates/niso-quickjs/tests/canvas.rs.
Threads
Section titled “Threads”Canvas holds gradients and patterns in Rc, so it is not Send: a
canvas lives on its agent’s thread. A Pixmap is shared as Arc<Pixmap>
and may move between threads, which is what transferring an ImageBitmap
would need.
Performance and memory
Section titled “Performance and memory”A bitmap is 4 bytes a pixel, allocated on first draw, and the copy hosts
take another 4 while the canvas is shown; heap_size reports both, a
snapshot only the canvas holds, the batched scene and the clips. Drawing is single-threaded. On a Core Ultra 7 265K,
1024x768: vello_cpu alone fills 1.3 to 1.5 million 20x20 rectangles a
second, and a 1000-path stroke takes 130 ms; through QuickJS, setting
fillStyle and calling fillRect runs about 520,000 times a second, where the
binding, not the rasterizer, takes most of the time. Chrome 158 does 1.15
to 1.8 million and 600 to 950 ms. Linked into niso-run, the canvas adds
2.8 MB stripped: vello_cpu with its SIMD and glyph crates about 0.8 MB,
skrifa’s outlines 0.7 MB, this crate and its bindings 0.2 MB.
cargo test -p niso-canvas runs in about 7 seconds in a debug build,
most of it the limits tests.
None needed: the crate draws into memory.
Standalone use
Section titled “Standalone use”Draw and save a PNG from any Rust program:
let budget = niso_canvas::Budget::new(niso_canvas::Budget::DEFAULT);let mut c = niso_canvas::Canvas::new(100, 50, true, &budget);c.state.fill = niso_canvas::Paint::Color(niso_canvas::Color([255, 0, 0, 255]));c.fill_rect(10.0, 10.0, 30.0, 30.0);std::fs::write("out.png", c.to_png().unwrap())?;