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 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) 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 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 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) 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.
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 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).