Kite

canvas

Low-level drawing, into a <canvas>.

HTML and CSS describe a document, and a document is described rather than drawn. This is the other door: a chart, a game or a visualisation knows where its own marks go, and needs to put them there without describing anything first.

It is a named surface over the drawing calls, not a graphics library. Every function here is one of the calls a renderer implements, or something assembled from them — and where a shape cannot be assembled, it is absent rather than approximated. There is no line, no path and no polygon, because the boundary has no line, path or polygon and faking one out of rectangles would produce something that looked wrong on every backend in a different way. What is here is exact everywhere.

Coordinates are floats, with the origin at the top left of the surface. Colours are 0xRRGGBB.

use std/canvas

fn chart(x: float, y: float, w: float, h: float) {
    canvas.fill(x, y, w, h, 0x101216)
    canvas.circle(x + w / 2.0, y + h / 2.0, 40.0, 0x3b82f6)
}

The surface is the host's, and there is one. These calls are compiler builtins that go to whatever surface the runtime supplies, so a program cannot yet say which <canvas> it is drawing into. Choosing one means passing a dom.Element through to the drawing builtins, which is a change to the boundary rather than to this file.

fill

pub fn fill(x: float, y: float, w: float, h: float, colour: int)

A filled rectangle.

rounded

pub fn rounded(x: float, y: float, w: float, h: float, radius: float, colour: int)

A filled rectangle with rounded corners.

The radius is clamped by the renderer to half the shorter side, so a radius larger than the box gives a stadium rather than an error.

circle

pub fn circle(cx: float, cy: float, radius: float, colour: int)

A circle centred on (cx, cy).

A rounded rectangle as wide as it is tall, with the radius at half the side — which is a circle exactly, on every backend, because that is the case the clamp in rrect was already handling.

ring

pub fn ring(x: float, y: float, w: float, h: float, radius: float, width: float, colour: int)

The ring between a box and the same box inset by width — a border, drawn without filling the middle.

This is what makes an outline independent of a fill: the two are separate calls, so an outlined shape does not have to paint its own interior in whatever colour it happens to be sitting on.

circle_ring

pub fn circle_ring(cx: float, cy: float, radius: float, width: float, colour: int)

The ring of a circle: an outlined circle of the given stroke width.

text

pub fn text(x: float, y: float, body: str, colour: int)

A run of text, with (x, y) at the top left of its line box.

One run on one line: this draws what it is given and does not wrap. Wrapping is a measurement rather than a drawing decision, so it is text.wrap's: hand it [width_of] as the measurement and draw the lines it answers, one [line_height] apart.

font

pub fn font(size: float, weight: int)

Select the size and weight that subsequent text is drawn and measured in.

Measurement follows the same selection, which is what keeps a caller's own layout arithmetic agreeing with what appears.

width_of

pub fn width_of(body: str) -> float

How wide a run is in the font currently selected.

line_height

pub fn line_height() -> float

How tall one line is in the font currently selected: ascent plus descent plus leading.

image

pub fn image(x: float, y: float, w: float, h: float, src: str)

A picture, fitted into the box without distorting it.

src is whatever the host can resolve — a URL, a path, a data: URI. A backend that has not loaded it yet draws nothing and asks to be called again when it has; a backend that cannot draw pictures at all records that one goes here.

clip

pub fn clip(x: float, y: float, w: float, h: float)

Confine everything drawn until [unclip] to a rectangle.

Clips nest, and every one has to be closed. Nothing closes one for you: a caller that clips and returns leaves everything drawn afterwards confined to a box it has forgotten about.

unclip

pub fn unclip()

alpha

pub fn alpha(a: float)

Draw everything after this at a of full opacity, from 0.0 to 1.0.

It applies until it is set back. A caller that sets it and returns leaves every later mark translucent, which is a bug that shows up somewhere else entirely — so set it back explicitly.

opaque

pub fn opaque()