Help us shape the future of Go! Take the 2026 Go Developer Survey

raster

package
v0.9.0 Latest Latest
Warning

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

Go to latest
Published: Oct 7, 2026 License: MIT Imports: 15 Imported by: 0

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

View Source
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.

View Source
const Kappa = 0.5522847498

Kappa places a cubic's control points so it traces a quarter circle.

Variables

View Source
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

func Mix

func Mix(a, b color.RGBA, t float64) color.RGBA

Mix is a at weight t over b: 0 is b, 1 is a (CSS color-mix order).

func RGB

func RGB(hex string) color.RGBA

RGB parses a theme's #rrggbb into an opaque color.

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 NewFaces

func NewFaces(fs [3]*opentype.Font, k float64) *Faces

NewFaces makes faces from fs (see Fonts) at k device pixels per CSS pixel.

func (*Faces) Close

func (f *Faces) Close()

Close releases every face made.

func (*Faces) Face

func (f *Faces) Face(s Style) font.Face

Face is the style's face at the Faces' scale, made on first use.

func (*Faces) Fit

func (f *Faces) Fit(st Style, s string, w float64) string

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.

func (*Faces) Width

func (f *Faces) Width(st Style, s string) float64

Width is s's advance in CSS pixels.

func (*Faces) Wrap

func (f *Faces) Wrap(st Style, s string, w float64) []string

Wrap breaks s into lines of at most w CSS pixels, at spaces where it can and inside a word where it must (a long identifier, a URL). Newlines in s are kept as line breaks.

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

func (p *Painter) Box(x, y, w, h, rad, border float64, stroke, fill color.Color)

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

func (p *Painter) Circle(cx, cy, rad float64, col color.Color)

Circle fills a circle.

func (*Painter) Fill

func (p *Painter) Fill(minX, minY, maxX, maxY float64, col color.Color, path PathFn)

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

func (p *Painter) Polyline(pts []Pt, width float64, col color.Color)

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.

func (*Painter) RoundRect

func (p *Painter) RoundRect(x, y, w, h, rad float64, col color.Color)

RoundRect fills a rectangle with corners of radius rad (0 for square).

func (*Painter) Text

func (p *Painter) Text(st Style, col color.Color, x, y float64, s string)

Text draws s with its line box's top at y, the glyphs centred in the line box the way CSS centres them: half the leading above, then the ascent to the baseline.

type PathFn

type PathFn func(move, line func(x, y float64), cube func(x1, y1, x2, y2, x, y float64))

PathFn receives the emitters of one filled path: move starts a subpath, line and cube extend it. Coordinates are CSS pixels.

type Pt

type Pt struct{ X, Y float64 }

Pt is a CSS-pixel point.

func CubicPts

func CubicPts(a, c1, c2, b Pt) []Pt

CubicPts flattens the cubic Bézier a→b with control points c1, c2 into points, a included, b included. 40 steps keeps every turn between two segments under a few degrees even on a self-loop's half circle.

type Style

type Style struct {
	Font int
	Size float64
}

Style is a text style: which font (Regular, Bold, Mono), at what CSS-pixel size.

func (Style) LineH

func (s Style) LineH() float64

LineH is the style's line height: CSS's 1.45, the web UI's own, so a picture sets text the way the page it mirrors does.

Jump to

Keyboard shortcuts

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