Documentation
¶
Overview ¶
Package raster is the drawing kit dbc's pictures are painted with: the Go fonts, text measured and fitted in CSS pixels, and anti-aliased filled shapes and strokes — all pure Go, on golang.org/x/image's vector rasterizer.
explain (a plan's picture) ──┐
├──► raster: Fonts · Faces · Painter
erd (a schema's diagram) ────┘ Style · Pt · Mix · RGB
Each picture keeps its own layout, text styles and palette; what they share is only how a shape or a string gets onto the pixels. That part was written once for the plan and copied for the diagram (about 150 lines); it lives here so a fix to either (the rasterizer's bounding box, a stroke's joints) reaches both, and a third picture starts from it.
UNITS. Every coordinate a caller passes is in CSS pixels. A Painter draws them at K device pixels per CSS pixel, and a Faces measures text at its own K; layouts measure with a Faces at 1× (unhinted advances scale linearly), so a layout does not depend on the scale it is drawn at.
WHY NOT A GENERAL 2D LIBRARY. gg, canvas and friends bring cgo, a different font stack, or a much larger API than rounded rectangles, polylines and one-line text; x/image is already a dependency and pure Go, so the shell, the TUI and dbc web render with no C toolchain.
Index ¶
- Constants
- Variables
- func Mix(a, b color.RGBA, t float64) color.RGBA
- func RGB(hex string) color.RGBA
- type Faces
- type Painter
- func (p *Painter) Box(x, y, w, h, rad, border float64, stroke, fill color.Color)
- func (p *Painter) Circle(cx, cy, rad float64, col color.Color)
- func (p *Painter) Fill(minX, minY, maxX, maxY float64, col color.Color, path PathFn)
- func (p *Painter) Polyline(pts []Pt, width float64, col color.Color)
- func (p *Painter) RoundRect(x, y, w, h, rad float64, col color.Color)
- func (p *Painter) Text(st Style, col color.Color, x, y float64, s string)
- type PathFn
- type Pt
- type Style
Constants ¶
const ( Regular = iota Bold Mono )
The Go fonts: a humanist sans for prose, its bold for names, and the monospace for SQL and types. They are embedded in golang.org/x/image, so a picture looks the same on every machine and needs no system font.
const Kappa = 0.5522847498
Kappa places a cubic's control points so it traces a quarter circle.
Variables ¶
var Fonts = sync.OnceValues(func() ([3]*opentype.Font, error) { var out [3]*opentype.Font for i, ttf := range [][]byte{goregular.TTF, gobold.TTF, gomono.TTF} { f, err := opentype.Parse(ttf) if err != nil { return out, serr.Wrap(err, "op", "parse the go fonts") } out[i] = f } return out, nil })
Fonts are the three Go fonts, parsed once per process. A parsed font is read-only and safe to share; the faces made from it (Faces) are not.
Functions ¶
Types ¶
type Faces ¶
type Faces struct {
// contains filtered or unexported fields
}
Faces caches one font.Face per style at one scale. A face is not safe for concurrent use, so every render makes its own and closes it.
func (*Faces) Fit ¶
Fit shortens s with an ellipsis until it is at most w CSS pixels wide. The longest prefix that fits with its "…" is found by binary search: measuring is the cost, and a long SQL fragment or type name would otherwise be measured once per rune dropped.
type Painter ¶
type Painter struct {
Img *image.RGBA
K float64
Faces *Faces
// contains filtered or unexported fields
}
Painter draws onto Img at K device pixels per CSS pixel. Faces must be at the same K. A Painter is not safe for concurrent use: its rasterizer is reused from shape to shape.
func (*Painter) Box ¶
Box is a bordered rounded rectangle: the border color filled, then the fill inset by the border's width — cheaper and crisper than stroking.
func (*Painter) Fill ¶
Fill rasterizes one path, anti-aliased, over the picture. The rasterizer is sized to the path's own bounding box (minX…maxY, CSS px), not the picture: Reset clears every cell it covers, so a picture-sized one would make each of a big picture's thousands of shapes cost a pass over millions of pixels.
The rasterizer does not clip to the destination. Everything is inside the margins by construction; a shape that is not is clipped to the picture rather than allowed to write out of bounds.
Several subpaths in one call are accumulated together, and vector clamps the accumulated coverage, so overlapping subpaths of the SAME winding fill once, not twice — which Polyline relies on.
func (*Painter) Polyline ¶
Polyline strokes the line through pts at the given width.
vector only fills, so the stroke is built from one quad per segment (the segment offset half the width to either side), all in one path. Every quad is wound the same way — its first side is always the segment's LEFT offset — so where neighbouring quads overlap at a joint the coverage clamps rather than cancels. That makes it safe for a tight U-turn (a self-referencing key's loop), where a single offset outline would fold over itself and punch a hole. The joints' outer corners leave a sliver on a sharp turn; curves flattened by CubicPts are fine enough that no turn is sharp.
The line is rasterized in chunks of a few segments, each with its own small bounding box. One path for the whole line would size the rasterizer to the line's bounding box, and a line from a hub table to a child forty rows down spans millions of pixels that would all be cleared and accumulated for a 1.6 px stroke — that made a 400-table diagram take seconds. Chunks share their joint segment's end, so the only cost is a joint's anti-aliased fringe being composited twice, which does not show.
type PathFn ¶
PathFn receives the emitters of one filled path: move starts a subpath, line and cube extend it. Coordinates are CSS pixels.