paint

package
v0.9.0 Latest Latest
Warning

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

Go to latest
Published: Oct 19, 2026 License: BSD-3-Clause Imports: 21 Imported by: 1

Documentation

Overview

Package paint rasterises a laid-out box tree onto an *image.RGBA using go-opentype for anti-aliased text, go-widgets/painter for backgrounds and go-gfx-decoded bitmaps for <img>. It also provides the Measurer the layout package needs (advances + vertical metrics from real font faces).

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func FontFamilyOf added in v0.4.0

func FontFamilyOf(g css.Generic) css.FontFamily

FontFamilyOf is the bare generic bucket as a FontFamily — the key the bundled families are registered under.

func Paint

func Paint(dst *image.RGBA, box *layout.Box, f *Fonts, imgs map[*dom.Node]image.Image)

Paint draws the box tree onto dst. imgs supplies decoded <img> bitmaps keyed by node (already scaled to the layout size); a missing entry is skipped. The caller owns clearing dst to its base (e.g. white) beforehand. It is equivalent to PaintFull with no background-image bitmaps.

func PaintFull

func PaintFull(dst *image.RGBA, box *layout.Box, f *Fonts, imgs map[*dom.Node]image.Image, bgImgs map[string]image.Image)

PaintFull draws the box tree onto dst, additionally painting CSS background-image layers (gradients and url() bitmaps). bgImgs supplies decoded background bitmaps keyed by their raw url() token; gradients need no bitmap.

Types

type Canvas added in v0.9.0

type Canvas interface {
	// Save pushes the graphics state (the clip); Restore pops it.
	Save()
	Restore()
	// Clip intersects the clip with p, by the nonzero or the even-odd rule.
	Clip(p *Path, evenOdd bool)
	// Fill fills p with c, by the nonzero or the even-odd rule. c's alpha is
	// a constant opacity.
	Fill(p *Path, c css.Color, evenOdd bool)
	// Shade paints a gradient through the current clip.
	Shade(s Shading)
	// Image paints img, scaled, into dst. img follows image.Image's own
	// convention (its RGBA method premultiplied); transparent pixels let the
	// page show through.
	Image(img image.Image, dst Rect)
	// Glyphs draws a run of positioned glyphs.
	Glyphs(run *GlyphRun)
}

Canvas is a vector drawing surface: what PaintVector draws a laid-out page through instead of rasterising it. Coordinates are the layout's own device pixels, origin at the page's top-left, y growing downwards; an implementation maps them to its own space (a PDF page: points, y up).

The operations are the few the box painter needs, chosen so that each maps onto one construct of a page-description language: a filled path, a clip, a shading painted through the clip, an image placed in a rectangle and a run of positioned glyphs. Save and Restore bracket every Clip.

type Fonts

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

Fonts is a registry of parsed font families (per family × bold × italic) with a per-size Face cache. It is not safe for concurrent use (Faces cache glyphs); build one per render.

func NewFonts

func NewFonts() *Fonts

NewFonts parses the bundled families in all four styles: sans = Inter, serif = Lora (both with real Bold/Italic/BoldItalic), mono = Go Mono (Regular only — bold/italic fall back to it, as the family ships no other styles). It panics only if a bundled font fails to parse, which would be a build-time defect in the fonts module, not a runtime condition.

The bundled files are parsed once per process and the parsed fonts shared by every Fonts: an *opentype.Font is read-only once parsed (a Face, which caches glyphs, is what is per-registry). Parsing them per registry — once per render — was most of the time and allocation of rendering a small page.

func (*Fonts) Measure

func (f *Fonts) Measure(text string, fam css.FontFamily, sizePx float64, weight int, italic bool) float64

Measure returns the advance width of text in CSS px at the exact size asked: the shaped glyphs' advances in font units scaled to sizePx, unrounded, for the family's face and for the fallback face on the runs that need it (see Runs). The raster painter still draws with a face at the nearest whole-pixel size and whole-pixel advances, so its glyphs sit on the pixel grid; layout, though, measures the truth — a consumer that draws at the true size (a PDF) then finds the words where the layout put them. Before this, a 14.4 px bold word was measured with a 14 px face, 2.8 % short, and printed at 14.4 px it ran into the space after it.

Shaping (see requiredLigatureFeature) runs per Run, after Runs has already split the text by cmap coverage: a ligature never spans a fallback-font boundary, matching how a real text shaper only ever merges glyphs within one already-selected face.

func (*Fonts) Metrics

func (f *Fonts) Metrics(fam css.FontFamily, sizePx float64, weight int, italic bool) (ascent, lineHeight float64)

Metrics implements layout.Measurer: ascent and line height.

func (*Fonts) Register added in v0.4.0

func (f *Fonts) Register(family string, weight int, italic bool, sfnt []byte) error

Register adds a face a document's own @font-face rule brought, parsed from SFNT bytes, under family (matched case-insensitively against the names a `font-family` declaration gives) at the weight and slant the rule declared.

The slot it occupies is the one the layout will ask for: bold is weight >= 600, the same threshold styleFace applies to a CSS weight, so a 600 and a 700 face compete for one slot and the one nearer 700 keeps it. Registering invalidates the face cache, since a cached Face was built from whichever font answered before.

func (*Fonts) RegisterFont added in v0.8.0

func (f *Fonts) RegisterFont(family string, weight int, italic bool, ft *opentype.Font) error

RegisterFont is Register for a font already parsed, which a caller that parsed it to validate it, or keeps parsed fonts across renders, passes instead of the bytes. The font is only read, never modified, so one *opentype.Font may be registered with any number of registries, concurrently.

func (*Fonts) Registered added in v0.4.0

func (f *Fonts) Registered(family string) bool

Registered reports whether any face has been registered for a family.

func (*Fonts) Runs added in v0.4.0

func (f *Fonts) Runs(s string, fam css.FontFamily, weight int, italic bool) []Run

Runs splits s into the runs the family's face and the fallback face set between them: a character goes to the fallback exactly when the family has no glyph for it and the fallback has; one neither covers stays with the family. A space or other whitespace joins whichever run it is in, so "① ②" is one fallback run rather than three. Measure and the painter walk these runs, and a consumer that sets the same text elsewhere (a PDF exporter embedding both fonts) must walk them too, so its glyphs come from the faces the layout measured with.

type Glyph added in v0.9.0

type Glyph struct {
	ID   opentype.GlyphIndex
	X    float64
	Text string
}

Glyph is one glyph of a GlyphRun: its id in the font, its origin's offset from the run origin, and the text it stands for (several characters for a ligature, empty for a glyph continuing an earlier one's cluster), which is what copying the text out of the document gives.

type GlyphRun added in v0.9.0

type GlyphRun struct {
	Font      *opentype.Font
	Size      float64
	Color     css.Color
	X, Y      float64
	Glyphs    []Glyph
	Invisible bool
}

GlyphRun is a run of glyphs of one font, size and colour, positioned by the layout. X,Y is the run's baseline origin; each glyph's X is its offset along the baseline from it, all in device pixels.

An Invisible run is text that is there but not painted — the text of an element PaintVector had to draw as a bitmap, laid over that bitmap so that it can still be selected, searched and copied (PDF's text rendering mode 3, ISO 32000-1 §9.3.6).

type GradientStop added in v0.9.0

type GradientStop struct {
	Pos   float64
	Color css.Color
}

GradientStop is one stop of a Shading.

type Path added in v0.9.0

type Path struct{ Ops []PathOp }

Path is an outline made of subpaths of lines and cubic Béziers.

func (*Path) Close added in v0.9.0

func (p *Path) Close()

Close closes the current subpath.

func (*Path) CubeTo added in v0.9.0

func (p *Path) CubeTo(x1, y1, x2, y2, x3, y3 float64)

CubeTo adds a cubic Bézier segment.

func (*Path) Ellipse added in v0.9.0

func (p *Path) Ellipse(r Rect)

Ellipse adds the ellipse inscribed in r as a closed subpath.

func (*Path) LineTo added in v0.9.0

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

LineTo adds a straight segment.

func (*Path) MoveTo added in v0.9.0

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

MoveTo starts a subpath.

func (*Path) RoundRect added in v0.9.0

func (p *Path) RoundRect(r Rect, rad float64)

RoundRect adds r as a closed subpath with corners of radius rad, clamped to half the shorter side as CSS and the raster painter clamp it; rad <= 0 is a plain rectangle. The subpath runs clockwise on screen.

type PathOp added in v0.9.0

type PathOp struct {
	Kind PathOpKind
	P    [3]Point
}

PathOp is one operation of a Path.

type PathOpKind added in v0.9.0

type PathOpKind uint8

PathOpKind says what one PathOp does.

const (
	PathMoveTo PathOpKind = iota // start a subpath at P[0]
	PathLineTo                   // straight line to P[0]
	PathCubeTo                   // cubic Bézier through controls P[0], P[1] to P[2]
	PathClose                    // close the subpath
)

The path operations.

type Point added in v0.9.0

type Point struct{ X, Y float64 }

Point is a point in device pixels.

type Rect added in v0.9.0

type Rect struct{ X, Y, W, H float64 }

Rect is a rectangle in device pixels.

type Run added in v0.4.0

type Run struct {
	Text     string
	Fallback bool
}

Run is a maximal stretch of a text set in one face: the family's own, or — Fallback — the last-resort family's, because the family has no glyph for those characters.

type Shading added in v0.9.0

type Shading struct {
	Radial         bool
	X0, Y0, X1, Y1 float64
	CX, CY, RX, RY float64
	R0, R1         float64 // radial: fractions of the ellipse at t=0 and t=1
	Stops          []GradientStop
	ExtendStart    bool
	ExtendEnd      bool
}

Shading is a gradient to paint through the clip. Its stops all carry the same alpha — a constant opacity for the whole shading — and strictly increasing positions in [0,1]; PaintVector splits a CSS gradient into such pieces.

Linear: the colour at position t lies on the line through (X0,Y0)+t·((X1,Y1)−(X0,Y0)), perpendicular to that axis. Radial: on the ellipse centred on (CX,CY) whose radii are (RX,RY) scaled by R0+t·(R1−R0) — a circle of radius R0+t·(R1−R0) under a transform scaling x by RX and y by RY, which is how a PDF draws an ellipse. Extend says whether the end colours continue beyond t=0 and t=1.

type VectorStats added in v0.9.0

type VectorStats struct {
	// Rasterized counts the pieces drawn as bitmaps because no vector form
	// exists for them here: a subtree under opacity < 1, a filter, a
	// mask-image or a rotation; a blurred box-shadow; a gradient whose
	// opacity fades between two stops; a form control; a background image
	// repeated more than maxVectorTiles times.
	Rasterized int
	// RasterPixels is their total area, in device pixels.
	RasterPixels int
	// Skipped counts pieces that needed a bitmap and got none, because the
	// page is larger than maxVectorScratchPixels.
	Skipped int
	// Images counts the bitmaps placed (<img>, background tiles), Glyphs the
	// glyphs drawn as text.
	Images, Glyphs int
}

VectorStats says what PaintVector drew as vectors and what it had to rasterise, so that a caller can tell a page that came out all vector from one that leaned on bitmaps.

func PaintVector added in v0.9.0

func PaintVector(c Canvas, box *layout.Box, f *Fonts, imgs map[*dom.Node]image.Image, bgImgs map[string]image.Image, page image.Rectangle) VectorStats

PaintVector draws the box tree through c: the same painting order, geometry and clipping as PaintFull, but backgrounds, borders and box shadows as paths, gradients as shadings, text as positioned glyphs of the very faces the layout measured with, and images as images placed at their layout box — so a PDF made from it has selectable text and edges that stay sharp at any zoom.

page is the device-pixel rectangle of the page (the viewport): what cannot be drawn as vectors (see VectorStats.Rasterized) is painted by the raster painter into a buffer of that size, cropped to what it painted, and placed as an image at the same position — a per-element fallback, so one filtered icon does not turn the whole page into a bitmap.

The page background (the canvas colour) is the caller's to paint first, as for PaintFull.

Jump to

Keyboard shortcuts

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