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 ¶
- Variables
- type Affine
- type Backend
- type Color
- type Description
- type Fill
- type FillRule
- type FontRef
- type GradientStop
- type HAlign
- type LineCap
- type LineJoin
- type Marker
- type MarkerStyle
- type Measurer
- type Partial
- type Path
- func (p *Path) Bounds() Rect
- func (p *Path) Close() *Path
- func (p *Path) CubicTo(c1x, c1y, c2x, c2y, x, y float32) *Path
- func (p *Path) Empty() bool
- func (p *Path) LineTo(x, y float32) *Path
- func (p *Path) MoveTo(x, y float32) *Path
- func (p *Path) Polyline(pts []Point) *Path
- func (p *Path) Rect(r Rect) *Path
- func (p *Path) Reset()
- func (p *Path) Walk(fn func(op PathOp, pts []Point))
- type PathOp
- type Point
- type Recorder
- func (r *Recorder) Bounds() Rect
- func (r *Recorder) Calls() int
- func (r *Recorder) Describe(d Description)
- func (r *Recorder) Description() Description
- func (r *Recorder) Empty() bool
- func (r *Recorder) FillPath(p *Path, fill Fill, rule FillRule)
- func (r *Recorder) Flush() error
- func (r *Recorder) Image(img image.Image, dst Rect)
- func (r *Recorder) Markers(shape Marker, at []Point, style MarkerStyle)
- func (r *Recorder) Measure(run TextRun) TextMetrics
- func (r *Recorder) Polyline(pts []Point, style Stroke)
- func (r *Recorder) Pop()
- func (r *Recorder) Push(clip *Path, xform Affine)
- func (r *Recorder) Replay(b Backend)
- func (r *Recorder) Reset()
- func (r *Recorder) SetMeasurer(m Measurer)
- func (r *Recorder) StrokePath(p *Path, style Stroke)
- func (r *Recorder) Text(run TextRun)
- type Rect
- type Resizer
- type Semantics
- type Stroke
- type Target
- type TextMetrics
- type TextRun
- type VAlign
Constants ¶
This section is empty.
Variables ¶
var Identity = Affine{A: 1, D: 1}
Identity is the transform that changes nothing.
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 ¶
Rotate returns a rotation transform by angle radians, clockwise in screen coordinates (Y down).
func (Affine) IsIdentity ¶
IsIdentity reports whether a is the identity transform.
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 ¶
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.
type Description ¶ added in v0.6.0
Description is what a chart says about itself in words.
It carries no geometry and changes nothing about what is drawn. It is what a backend needs in order to be readable by something other than an eye: an SVG's <title> and <desc>, a PDF's document title, a canvas element's aria-label, a window's title bar.
Title is a short label — a line of text, the chart's own title where it has one. Detail is the longer reading of the chart: what it plots, over what range, and how much of it there is. Either may be empty, and a backend writes nothing for an empty one rather than an empty element.
func (Description) Empty ¶ added in v0.6.0
func (d Description) Empty() bool
Empty reports whether d says nothing.
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 (Fill) IsGradient ¶
IsGradient reports whether f paints a gradient rather than a solid colour.
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 ¶
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.
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 ¶
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 ¶
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) Polyline ¶
Polyline appends pts as one open subpath. It is a no-op for fewer than two points.
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.
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
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
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) Describe ¶ added in v0.6.0
func (r *Recorder) Describe(d Description)
Describe implements Semantics: a recording carries what the chart says about itself, so that a description survives being recorded and replayed.
Without it a chart drawn through a Recorder — which is every panel of a parallel render and every frame of a [Live] one — would lose its title and its description on the way to the backend, and an interactive chart exported to SVG would be the one with no accessible name.
func (*Recorder) Description ¶ added in v0.6.0
func (r *Recorder) Description() Description
Description reports what was recorded by Recorder.Describe.
func (*Recorder) FillPath ¶ added in v0.4.0
FillPath records a filled path. See Backend.FillPath.
func (*Recorder) Flush ¶ added in v0.4.0
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
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
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
Push records a transform and clip. See Backend.Push.
func (*Recorder) Replay ¶ added in v0.4.0
Replay makes the recorded calls on b, in the order they were made.
A description recorded along the way is announced first, before any drawing, which is the order Semantics promises a backend.
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
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
StrokePath records a stroked path. See Backend.StrokePath.
func (*Recorder) Text ¶ added in v0.4.0
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
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.
type Resizer ¶ added in v0.6.0
type Resizer interface {
// Resize sets the surface's size in device pixels. The arguments mean what
// [Target.Open]'s do, and the backend keeps whatever it was drawing into
// where that is possible. It takes effect on the next frame.
Resize(widthPx, heightPx int, dpr float64) error
}
Resizer is an optional Backend interface: a backend drawing into a surface whose size can change while it is open implements it.
A file backend never needs it — a document is opened at one size and written once — and a surface that stays open does: a window is dragged wider, a canvas element reflows, a terminal is resized. Rather than closing the target and opening another, which would lose the frame on screen and every zoom in the scales, the caller tells the backend its new size and draws again.
It is optional for the reason every interface in this package is: adding Resize to Backend would break every third-party backend that has no surface to resize.
type Semantics ¶ added in v0.6.0
type Semantics interface {
// Describe attaches a description to the frame about to be drawn.
Describe(d Description)
}
Semantics is an optional Backend interface: a backend whose output can carry a description of what it draws implements it.
refract calls Describe once, before any drawing call, so that a backend writing a document header has the description in hand when it writes one. A backend that has nowhere to put words — a raster, which is pixels and nothing else — does not implement it, and the description is simply not written.
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.
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).