ir

package
v0.5.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 4, 2026 License: MIT Imports: 4 Imported by: 0

Documentation

Overview

Package ir defines refract's intermediate representation: a small, backend-agnostic scene description, plus the Backend interface every renderer implements.

The IR is deliberately thin. It maps cleanly onto an immediate-mode canvas (github.com/gogpu/gg), onto a retained scene graph, and onto the built-in SVG emitter, without any of them leaking into the model layer. Geoms, scales and layout produce IR; they never talk to a backend directly.

Stability

The primitive set and the Backend interface are frozen for the v0.1 cycle. Pre-1.0 they may still change between minor releases; see docs/adr/0002.

Coordinates

Device coordinates, origin at the top-left, X right, Y down — the same convention as SVG, HTML canvas and gg. Positions are float32: they are device-space values after the model layer has already done its float64 work, and float32 is what a GPU backend ultimately wants.

Index

Constants

This section is empty.

Variables

View Source
var Identity = Affine{A: 1, D: 1}

Identity is the transform that changes nothing.

View Source
var Transparent = Color{}

Transparent is the fully transparent colour. A fill or stroke with A == 0 is skipped by every backend.

Functions

This section is empty.

Types

type Affine

type Affine struct {
	A, B, C, D, E, F float32
}

Affine is a 2D affine transform in row-major order:

| A C E |
| B D F |
| 0 0 1 |

which is the same element order SVG's matrix(a,b,c,d,e,f) uses.

func Rotate

func Rotate(angle float64) Affine

Rotate returns a rotation transform by angle radians, clockwise in screen coordinates (Y down).

func Scale

func Scale(sx, sy float32) Affine

Scale returns a scaling transform.

func Translate

func Translate(dx, dy float32) Affine

Translate returns a translation transform.

func (Affine) Apply

func (a Affine) Apply(p Point) Point

Apply transforms p by a.

func (Affine) IsIdentity

func (a Affine) IsIdentity() bool

IsIdentity reports whether a is the identity transform.

func (Affine) Mul

func (a Affine) Mul(b Affine) Affine

Mul returns a*b: the transform that applies b first, then a.

type Backend

type Backend interface {
	// Polyline strokes an open sequence of points. It is the fast path for
	// line geoms; it is exactly equivalent to StrokePath of a path built with
	// Path.Polyline, but lets a backend skip path construction entirely.
	Polyline(pts []Point, style Stroke)

	// StrokePath strokes an arbitrary path.
	StrokePath(p *Path, style Stroke)

	// FillPath fills a path's interior according to rule.
	FillPath(p *Path, fill Fill, rule FillRule)

	// Text draws a single-line run. The backend shapes it.
	Text(run TextRun)

	// Markers draws one shape at each of the given positions. This is the
	// scatter fast path: a backend may instance it.
	Markers(shape Marker, at []Point, style MarkerStyle)

	// Image blits a raster into dst, scaling to fit. This carries the
	// density-raster big-data path.
	Image(img image.Image, dst Rect)

	// Push pushes a transform and an optional clip path onto the state stack.
	// The transform composes with whatever is already on the stack; a nil clip
	// means "no additional clipping".
	Push(clip *Path, xform Affine)

	// Pop undoes the most recent Push.
	Pop()

	// Measure reports metrics for a run, from the same font stack that Text
	// will draw with. It must be callable before any drawing, because layout
	// runs first.
	Measure(run TextRun) TextMetrics

	// Flush completes the frame and reports any deferred error. A backend that
	// writes a file writes it here.
	Flush() error
}

Backend is what every renderer implements. It is an immediate-mode drawing sink: refract lowers a chart into calls on a Backend, in paint order.

Stability

This interface is frozen for the v0.1 cycle (docs/adr/0002). It exists so that refract is insulated from any single rendering library: a change in a backend's own API is contained to that backend's adapter.

State

A Backend carries no style state between calls — every drawing call is self-contained. The only state is the transform/clip stack managed by Push and Pop.

Ownership

Everything a drawing call is given — a point slice, a Path, an image.Image — is **lent for the duration of that call**. refract draws from pooled buffers so that a chart redrawn every frame allocates nothing that grows with its data, which means the next call may write over what the last one was handed. A Backend that needs to keep any of it must copy it; Recorder is the worked example.

Concurrency

A Backend is not safe for concurrent use. refract may build IR on several goroutines but always plays it back into a backend from one.

type Color

type Color = color.NRGBA

Color is refract's colour type: 8-bit non-premultiplied sRGBA.

It is an alias for color.NRGBA rather than a new type so that colours pass straight into any stdlib or backend API taking a color.Color, with no conversion layer and no import of refract in code that only wants to name a colour.

func Fade

func Fade(c Color, f float64) Color

Fade returns c with its alpha scaled by f, clamped to [0, 1].

func RGB

func RGB(r, g, b uint8) Color

RGB returns an opaque colour.

func RGBA

func RGBA(r, g, b, a uint8) Color

RGBA returns a colour with explicit alpha.

type Fill

type Fill struct {
	Color Color
	Start Point
	End   Point
	Stops []GradientStop
}

Fill describes how a path's interior is painted.

A Fill with no Stops is a solid Color. With Stops, it is a linear gradient running from Start to End in the same coordinate space as the path; Color is then ignored. Radial and sweep gradients are not in the v0.1 IR — no v0.1 geom needs them, and adding them later is additive.

func Solid

func Solid(c Color) Fill

Solid returns an opaque-rules solid fill of colour c.

func (Fill) IsGradient

func (f Fill) IsGradient() bool

IsGradient reports whether f paints a gradient rather than a solid colour.

func (Fill) Visible

func (f Fill) Visible() bool

Visible reports whether filling with f would put any ink on the canvas.

type FillRule

type FillRule uint8

FillRule selects how a path's interior is determined.

const (
	NonZero FillRule = iota
	EvenOdd
)

The fill rules.

type FontRef

type FontRef struct {
	Family string  // logical family; "" means the backend's default sans
	Size   float64 // em size in device units
	Weight int     // CSS-style numeric weight; 0 means 400
	Italic bool
}

FontRef names a font logically. refract never carries glyph data or font files through the IR: a backend resolves a FontRef with whatever font stack it has (gg's shaper, the SVG viewer, a host framework's text engine).

type GradientStop

type GradientStop struct {
	Offset float32
	Color  Color
}

GradientStop is one colour stop of a linear gradient, at offset t in [0, 1] along the gradient axis.

type HAlign

type HAlign uint8

HAlign is horizontal alignment of a text run relative to its anchor point.

const (
	AlignStart HAlign = iota
	AlignCenter
	AlignEnd
)

The horizontal alignments.

type LineCap

type LineCap uint8

LineCap is how a stroke terminates.

const (
	CapButt LineCap = iota
	CapRound
	CapSquare
)

The line caps.

type LineJoin

type LineJoin uint8

LineJoin is how two stroke segments meet.

const (
	JoinMiter LineJoin = iota
	JoinRound
	JoinBevel
)

The line joins.

type Marker

type Marker uint8

Marker is a scatter-point shape, drawn centred on its position.

const (
	MarkerCircle Marker = iota
	MarkerSquare
	MarkerDiamond
	MarkerTriangle
	MarkerCross
	MarkerPlus
)

The marker shapes.

type MarkerStyle

type MarkerStyle struct {
	Size   float32 // nominal diameter in device units
	Fill   Color
	Stroke Stroke
}

MarkerStyle is the paint applied to every instance of a Markers call.

type Measurer added in v0.4.0

type Measurer interface {
	// Measure reports metrics for a run. See [Backend.Measure].
	Measure(run TextRun) TextMetrics
}

Measurer is the part of a Backend that is needed before anything is drawn.

Layout runs first and needs to know how wide a tick label will be, so text measurement is separable from drawing — and separating it is what lets a recorder stand in for a backend without being able to draw at all.

type Partial added in v0.5.0

type Partial interface {
	// Damage limits the next frame to the given rectangles. An empty list
	// means nothing changed and the frame can be skipped entirely; the
	// limitation lasts until the next Flush.
	Damage(rects []Rect)
}

Partial is implemented by a backend that can repaint part of a frame rather than all of it.

It is the seam damage tracking needs and the only one: refract works out *where* a frame changed, and a backend that can act on that says so by implementing this. A backend that cannot — a file emitter, which writes a whole document or none — simply does not, and gets a full repaint like always.

The rectangles are in device space, the same coordinates a drawing call takes. A backend is free to widen them (to whole pixels, to tiles) and must not narrow them.

type Path

type Path struct {
	Ops []PathOp
	Pts []Point
}

Path is a sequence of subpaths built from MoveTo/LineTo/CubicTo/Close.

Ops and Pts are parallel: walking Ops and consuming Op.Points() entries from Pts per op reconstructs the path. Splitting them this way keeps a path two allocations regardless of length and lets callers reuse both slices via Reset.

func (*Path) Bounds

func (p *Path) Bounds() Rect

Bounds returns the control-point bounding box of p. For paths containing cubics this is conservative: it bounds the control polygon, not the exact curve. That is what layout and clip-culling need, and it is cheap.

func (*Path) Close

func (p *Path) Close() *Path

Close closes the current subpath.

func (*Path) CubicTo

func (p *Path) CubicTo(c1x, c1y, c2x, c2y, x, y float32) *Path

CubicTo appends a cubic Bézier segment with the given control points.

func (*Path) Empty

func (p *Path) Empty() bool

Empty reports whether p contains no ops.

func (*Path) LineTo

func (p *Path) LineTo(x, y float32) *Path

LineTo appends a straight segment to (x, y).

func (*Path) MoveTo

func (p *Path) MoveTo(x, y float32) *Path

MoveTo starts a new subpath at (x, y).

func (*Path) Polyline

func (p *Path) Polyline(pts []Point) *Path

Polyline appends pts as one open subpath. It is a no-op for fewer than two points.

func (*Path) Rect

func (p *Path) Rect(r Rect) *Path

Rect appends a closed rectangular subpath.

func (*Path) Reset

func (p *Path) Reset()

Reset clears p while keeping its capacity, so a caller can reuse it across frames without reallocating.

func (*Path) Walk

func (p *Path) Walk(fn func(op PathOp, pts []Point))

Walk calls fn for each op with the points that op consumes. The slice passed to fn aliases p.Pts and must not be retained.

type PathOp

type PathOp uint8

PathOp is a single path-construction verb.

const (
	OpMoveTo  PathOp = iota // 1 point
	OpLineTo                // 1 point
	OpCubicTo               // 3 points: control 1, control 2, end
	OpClose                 // 0 points
)

The path verbs. The set is deliberately minimal: every curve a chart needs (rounded corners, tension-smoothed lines, arcs) is expressible as cubics, and both SVG and gg consume cubics natively.

func (PathOp) Points

func (op PathOp) Points() int

Points reports how many points in Path.Pts the op consumes.

type Point

type Point struct {
	X, Y float32
}

Point is a position in device space.

The concept document calls this f32.Point; refract keeps it here rather than spending a package on two fields (see docs/adr/0002).

type Recorder added in v0.4.0

type Recorder struct {
	// contains filtered or unexported fields
}

Recorder is a Backend that stores what was drawn on it so that it can be replayed into another Backend later.

It is how refract draws several panels at once. A Backend is not safe for concurrent use, so each goroutine draws into a Recorder of its own and the recordings are replayed into the real backend afterwards, in panel order. Replaying in a fixed order is what makes a parallel render produce exactly what a serial one does rather than merely something equivalent.

Points and path data go into flat arenas rather than one slice per call, so a Recorder that is Recorder.Reset and reused costs nothing per frame after the first.

A Recorder is not safe for concurrent use either — one per goroutine is the whole idea. Measure is forwarded to the Measurer it was built with, which therefore must be safe to call from every goroutine using a Recorder.

func NewRecorder added in v0.4.0

func NewRecorder(m Measurer) *Recorder

NewRecorder returns a Recorder measuring through m. m may be nil, in which case Measure reports zero — which is correct for a recorder used only for a data pass, since layout has already run by then.

func (*Recorder) Bounds added in v0.5.0

func (r *Recorder) Bounds() Rect

Bounds reports the rectangle enclosing everything a recording drew. It is what a caller repaints when Damage says the two frames are not comparable.

func (*Recorder) Calls added in v0.4.0

func (r *Recorder) Calls() int

Calls reports how many drawing operations were recorded.

func (*Recorder) Empty added in v0.4.0

func (r *Recorder) Empty() bool

Empty reports whether nothing has been recorded.

func (*Recorder) FillPath added in v0.4.0

func (r *Recorder) FillPath(p *Path, fill Fill, rule FillRule)

FillPath records a filled path. See Backend.FillPath.

func (*Recorder) Flush added in v0.4.0

func (r *Recorder) Flush() error

Flush completes nothing: a Recorder holds a recording rather than a frame. The recording is finished by Recorder.Replay.

func (*Recorder) Image added in v0.4.0

func (r *Recorder) Image(img image.Image, dst Rect)

Image records an image, copying its pixels. See Backend.Image.

func (*Recorder) Markers added in v0.4.0

func (r *Recorder) Markers(shape Marker, at []Point, style MarkerStyle)

Markers records a set of markers. See Backend.Markers.

func (*Recorder) Measure added in v0.4.0

func (r *Recorder) Measure(run TextRun) TextMetrics

Measure forwards to the Measurer the Recorder was built with.

func (*Recorder) Polyline added in v0.4.0

func (r *Recorder) Polyline(pts []Point, style Stroke)

Polyline records a stroked polyline. See Backend.Polyline.

func (*Recorder) Pop added in v0.4.0

func (r *Recorder) Pop()

Pop records the end of a Push. See Backend.Pop.

func (*Recorder) Push added in v0.4.0

func (r *Recorder) Push(clip *Path, xform Affine)

Push records a transform and clip. See Backend.Push.

func (*Recorder) Replay added in v0.4.0

func (r *Recorder) Replay(b Backend)

Replay makes the recorded calls on b, in the order they were made.

func (*Recorder) Reset added in v0.4.0

func (r *Recorder) Reset()

Reset empties the recording, keeping the memory for the next one.

func (*Recorder) SetMeasurer added in v0.4.0

func (r *Recorder) SetMeasurer(m Measurer)

SetMeasurer points the Recorder at a different Measurer. It exists so that a pooled Recorder can serve one render after another, each with a backend of its own.

func (*Recorder) StrokePath added in v0.4.0

func (r *Recorder) StrokePath(p *Path, style Stroke)

StrokePath records a stroked path. See Backend.StrokePath.

func (*Recorder) Text added in v0.4.0

func (r *Recorder) Text(run TextRun)

Text records a text run. See Backend.Text.

type Rect

type Rect struct {
	Min, Max Point
}

Rect is an axis-aligned rectangle in device space. Min is the top-left corner, Max the bottom-right; a Rect with Max below Min on either axis is empty.

func Damage added in v0.5.0

func Damage(prev, next *Recorder, dst []Rect) (rects []Rect, ok bool)

Damage reports where two recordings differ, in device space.

ok is false when the two are not comparable call for call — a different number of drawing calls, a different kind at the same index, a transform or a clip that moved. That is a chart whose *structure* changed rather than its data, and the honest answer is a full repaint rather than a list of rectangles that describes half of it.

ok is true with an empty list when the two recordings are identical, which is a frame a caller can skip.

Rectangles are appended to dst, which may be nil. Reusing one across frames is what keeps a redraw allocation-free.

func R

func R(x0, y0, x1, y1 float32) Rect

R builds a Rect from its edges.

func (Rect) Contains

func (r Rect) Contains(p Point) bool

Contains reports whether p lies inside r, edges included.

func (Rect) Dx

func (r Rect) Dx() float32

Dx returns the width of r.

func (Rect) Dy

func (r Rect) Dy() float32

Dy returns the height of r.

func (Rect) Empty

func (r Rect) Empty() bool

Empty reports whether r encloses no area.

func (Rect) Inset

func (r Rect) Inset(left, top, right, bottom float32) Rect

Inset returns r shrunk by the given amounts on each side.

type Stroke

type Stroke struct {
	Color      Color
	Width      float32
	Cap        LineCap
	Join       LineJoin
	MiterLimit float32   // 0 means the backend default (4)
	Dash       []float32 // nil or empty means solid
	DashOffset float32
}

Stroke is the full state needed to stroke a polyline or path.

func (Stroke) Visible

func (s Stroke) Visible() bool

Visible reports whether stroking with s would put any ink on the canvas.

type Target

type Target interface {
	// Open returns a Backend drawing into a surface of widthPx by heightPx
	// device pixels. dpr is the device pixel ratio: coordinates handed to the
	// backend are already in device pixels, so dpr is informational — backends
	// use it to pick hinting and stroke-snapping strategies.
	Open(widthPx, heightPx int, dpr float64) (Backend, error)

	// Close finalises the destination. It is called after the Backend's Flush.
	Close() error
}

Target is a render destination: it opens a Backend sized for one chart, and finalises whatever it is writing to when Close is called.

Splitting Target from Backend is what lets refract.Render take a destination before the chart's pixel size is known to that destination, and lets the same backend implementation serve a file, an io.Writer, or a window surface.

type TextMetrics

type TextMetrics struct {
	// Advance is the total advance width of the run.
	Advance float32
	// Ascent and Descent are the font's ascent above and descent below the
	// baseline, both positive. They describe the font box, not this run's ink,
	// so successive runs in one font line up.
	Ascent  float32
	Descent float32
	// Ink is the run's tight bounding box relative to an origin at the
	// baseline start, Y up-negative as usual for screen coordinates.
	Ink Rect
}

TextMetrics is what layout needs back from a backend about a run.

Measure is the only text capability refract requires beyond drawing: layout must size margins, space ticks, and detect label collisions using metrics from the very shaper that will draw the text.

func (TextMetrics) Height

func (m TextMetrics) Height() float32

Height returns the font box height, Ascent + Descent.

type TextRun

type TextRun struct {
	Text     string
	Font     FontRef
	At       Point   // anchor position in device space
	H        HAlign  // horizontal alignment about At
	V        VAlign  // vertical alignment about At
	Rotation float64 // radians, clockwise, about At
	Color    Color
}

TextRun is a single-line string to be drawn.

refract passes strings, never pre-shaped glyphs: shaping belongs to the active backend. refract owns only placement — anchoring, rotation, and overlap avoidance. Paragraph layout is out of scope (see CONCEPT.md §5).

type VAlign

type VAlign uint8

VAlign is vertical alignment of a text run relative to its anchor point.

const (
	AlignBaseline VAlign = iota
	AlignTop
	AlignMiddle
	AlignBottom
)

The vertical alignments. AlignBaseline puts the anchor on the text baseline; the others align the anchor to the run's font box.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL