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

decker

package module
v0.7.1 Latest Latest
Warning

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

Go to latest
Published: Oct 19, 2026 License: MIT Imports: 59 Imported by: 0

README

decker

Slide decks as Go programs, presented in your terminal.

CI Go version Version Go Reference

Decker draws big antialiased type, block letters, diagrams and animation on a pixel canvas displayed with terminal half-block characters. Your talk brings its theme and slide functions; the engine provides navigation, build steps, transitions, a presenter view, live reload, PNG snapshots and video export.

Built on Bubble Tea, Lip Gloss and Harmonica.

Quick start · Guide · CLI · API reference · Troubleshooting · Contributing

What you can make

These clips come from the showcase deck, recorded with video.

Block-letter titles and animated text:

A block-letter DECKER title decrypting in, a sentence rising word by word, and animated letter effects.

Stats and charts that build in on a step:

Three stat cards counting up, then a line chart drawing on and a donut chart sweeping in.

Code with a moving highlight, a diagram, and a magic move to the next slide:

Highlighted Go code with a moving focus bar, a three-box diagram drawing on, and the boxes morphing into a column.

Requirements

  • Go 1.27.0 or later, as specified in go.mod.
  • A Unix environment for the engine's signals and dev process replacement. CI runs on Linux; use WSL for Windows. Native Windows builds are unsupported by the current implementation; macOS is not checked in CI.
  • A true-color terminal with Unicode half-block support for live talks. Try Ghostty, kitty, WezTerm or iTerm2. A smaller terminal font gives smoother slides because it increases the pixel count.
  • FFmpeg with libx264, only for video export. Snapshots, contact sheets, PDFs and slide lists run without an interactive terminal.

Decker is a Go library. A talk is a separate main package that calls decker.Main; decks are authored in Go, with their own layouts and assets.

Quick start

Create a new talk module:

mkdir my-talk
cd my-talk
go mod init example.com/my-talk
go get github.com/austinlparker/decker@latest

Save this complete program as main.go:

package main

import "github.com/austinlparker/decker"

func main() { decker.Main(talk()) }

func talk() decker.Deck {
	theme := &decker.Theme{
		Background: decker.Hex("#101820"),
		Text:       decker.Hex("#F0F0F0"),
		Muted:      decker.Hex("#A0A8B0"),
		Faint:      decker.Hex("#384048"),
		Accent:     decker.Hex("#40C0FF"),
		Accent2:    decker.Hex("#FFB000"),
		Warn:       decker.Hex("#FF6060"),
		Good:       decker.Hex("#70D050"),
		Panel:      decker.Hex("#0A1016"),
		Display:    decker.StockFont("SpaceGrotesk-Bold"),
		Body:       decker.StockFont("SpaceGrotesk-Medium"),
		Mono:       decker.StockFont("JetBrainsMono-ExtraBold"),
	}
	return decker.Deck{
		Name: "hello-decker", Theme: theme,
		Notes: "## Hello, decker\n\nPress space to reveal the second line. Press q to quit.",
		Slides: []decker.Slide{{
			Title: "Hello, decker", Steps: 2,
			View: func(c decker.Ctx, sc *decker.Scene) {
				const title = "Hello, decker"
				t := decker.Text{Font: c.Theme.Display, Size: c.Size(0.18), Color: c.Theme.Accent}.
					Fit(title, c.X(0.84), c.Y(0.3))
				t.FX = decker.RiseIn(c.T, 0.02, t.Size)
				t.Draw(sc.Px, title, c.X(0.08), c.Y(0.2))
				if c.Reached(1) {
					decker.Text{Font: c.Theme.Body, Size: c.SmallText(c.Theme.Body), Color: c.Theme.Text,
						FX: decker.FadeUp(c.Since(1), 0.4, c.SmallText(c.Theme.Body))}.
						Draw(sc.Px, "Your slides are Go code.", c.X(0.08), c.Y(0.65))
				}
			},
		}},
	}
}

Run your talk:

go run .

You should see Hello, decker animate in. Press space to reveal the second line, Your slides are Go code. Press q to quit. The three theme fonts are required and are loaded once, outside the frame function.

Already have this repository checked out? Run the same hello example from its root:

go run ./examples/hello

Run, rehearse and export

These commands run from your talk's directory:

go run . --dev                        # rebuild on save; keep your slide and step
go run . present --length 45m         # notes, timer and previews; p opens the deck
go run . list                         # titles, step counts and sections
go run . review review                # every build at three sizes: what's clipped or unreadable
go run . handout handout              # handout.md: thumbnails, notes and sources
go run . snapshot --step 2 -w 160 -h 45 --png frame.png
go run . sheet sheet.png              # all slides at their final step
go run . pdf talk.pdf                 # a page per slide; --builds for every build
go run . video talk.mp4 --fps 30      # silent video; needs ffmpeg

In Ghostty 1.3+ on macOS, start present and press p to open the deck in a separate window at 4pt. Change that size with --presentation-font-size 5. The presenter keeps its normal font size; move the deck window to your projector and navigate from the presenter. Full-resolution image previews are automatic in Ghostty and kitty; --previews cells selects half-block previews instead.

You can also start the deck and present manually in separate terminals. They connect using the deck's name and reconnect after a dev rebuild. See the CLI reference for every command and flag, default, export behavior and socket options.

Key Action
space / → / pgdn Next build step or slide
← / pgup Previous build step or slide
] / [ Next / previous slide, skipping builds
12g / 12 then enter Jump to slide 12
r Replay this slide from its first step
b / w Blank to black / white; press again to resume
? / q Help / quit

The full key table includes clicker bindings and presenter-only timer controls.

Learn more

Task Documentation
Understand the canvas, fonts and presentation setup Rendering and type
Choose bundled block fonts or supply your own Choosing and loading fonts
Build a theme and your own slide templates Anatomy of a deck
Find text, charts, shapes, code blocks and diagrams Toolbox and API reference
Animate builds and move elements between slides Element animations and magic move
Write a talk, or have an agent write one: what to build, what not to, and the review loop Authoring a talk and the starter
Find clipped code, dropped rows and text off the screen Reviewing a deck
Test your talk and pin its rendered output Deck tests
Resolve setup, presenter or export problems Troubleshooting
Change the engine Contributing, invariants and file map
Understand versions and automated releases Releasing and release notes

Rendering contract

A frame depends only on Ctx: use c.T, c.StepT, c.Since and Hash01 for animation and noise. This lets replay, snapshots, video and reference tests render the same moment. Go build indexes start at 0; CLI slide and step numbers start at 1. Steps: 2 means two build states.

Video uses the pixel canvas and has no audio. Native character-layer content (Scene.Text, Put, Sprite) appears in terminal and PNG output, but is omitted from video. Use Text, Rich or Block for text needed in every output. See export behavior.

Licensing and assets

Decker's code, documentation, examples and SVG icon use the MIT license.

Bundled Space Grotesk and JetBrains Mono fonts remain under SIL OFL 1.1; retain their notices when distributing them or binaries that embed them. The five default FIGlet fonts are Spleen conversions under BSD-2-Clause; five optional display-font conversions use SIL OFL 1.1. Select them with StockFigletFont; enumerate choices with StockFigletFontNames. Consumers can load their own fonts with LoadFigletFont or ParseFigletFont. Third-party fonts keep their own terms separately from MIT; see the font inventory and license findings for notices and provenance.

Documentation

Overview

Package decker is a terminal slide-deck engine: a talk is a Go program, and every slide is a function that draws each frame onto a pixel canvas.

A Deck holds a Theme and a list of slides. Each Slide has a View, a function that draws one frame from a Ctx onto a Scene, and Main gives the deck its command line: present, presenter view, dev reload, a review of every build for clipped or unreadable content, snapshots, contact sheets, handouts, PDF and video. The presenter can open a deck window in Ghostty on macOS with its own terminal font size; Ghostty and kitty also support full-resolution previews.

A minimal talk loads its fonts once and draws from the frame's context:

func main() {
	theme := &decker.Theme{
		Background: decker.Hex("#101820"),
		Text:       decker.Hex("#F0F0F0"),
		Display:    decker.StockFont("SpaceGrotesk-Bold"),
		Body:       decker.StockFont("SpaceGrotesk-Medium"),
		Mono:       decker.StockFont("JetBrainsMono-ExtraBold"),
	}
	decker.Main(decker.Deck{Name: "hello", Theme: theme, Slides: []decker.Slide{{
		Title: "Hello",
		View: func(c decker.Ctx, sc *decker.Scene) {
			decker.Text{Font: c.Theme.Display, Size: c.Size(0.15), Color: c.Theme.Text}.
				Draw(sc.Px, "Hello", c.X(0.08), c.Y(0.2))
		},
	}}})
}

Import github.com/austinlparker/decker from a separate main package. See the hello example for a complete program with animation and build steps, the guide for slide authoring, and the CLI reference for flags and exports. This checkout requires Go 1.27.0 or later and uses Unix signals and process replacement. Video export additionally requires ffmpeg with libx264.

Pixel canvas

A slide paints into Pixels, a true-color framebuffer shown with half-block characters, so each terminal cell is two pixels tall and a 240x67 terminal is a 240x134 display. Sizes are fractions of the screen (Ctx.X, Ctx.Y, Ctx.Size), so a slide looks the same at any terminal size.

Frame purity

A frame depends only on its Ctx: no clocks, no math/rand. Any moment can then be replayed, snapshotted, rendered to video and checked against reference images. Use Hash01 for repeatable noise and Ctx.T, Ctx.Step and Ctx.StepT for time.

Main types

  • Deck, Slide: the talk with its speaker notes in Markdown, and one slide with its steps, Source citations and Transition.
  • Publisher, Session, Thread, Delivery, Post, Media: posting the talk as a thread as it's given, from the post blocks in its notes: a Publisher checks posts and signs in, its Session opens a talk's posts, and the Thread posts them.
  • Ctx: what changes between frames (T, Step, StepT), the slide's position (Index, Count, Section), plus layout helpers.
  • Rect: a box on the canvas; layout cuts and splits rects (Rect.CutTop, Rect.Cols, Rect.Grid).
  • Scale: data values mapped onto pixels, with round ticks from NiceScale, for axes a slide draws itself.
  • Theme: colors, typefaces, chart series colors and an optional overlay the engine uses. Display, Body and Mono fonts are required.
  • Scene, Pixels: what a View draws on (the engine makes the scene and draws the theme's overlay on it), and its pixel canvas. Scene also has a native character layer; that layer is omitted from video exports. Scene.Place hands the engine a keyed element to draw instead, which TransitionMorph can move from one slide to the next.
  • Text, Font: smooth antialiased type, wrapped and fitted to a box (Text.Fit) and measured before drawing (Text.Measure).
  • Rich, Span: the same type with mixed fonts, colors and decorations inside a line (ParseSpans reads a light markup).
  • Block, FigletFont: FIGlet block letters scaled to fill a box (FitBlock); choose bundled faces with StockFigletFont and StockFigletFontNames, or supply fonts with LoadFigletFont and ParseFigletFont. Smooth type also accepts consumer fonts through LoadFont and ParseFont.
  • GlyphEffect, BlockEffect: per-letter and per-cell animations, combined with Chain and BlockChain.
  • Table, TableReveal: a grid of text fitted to a Rect at one size, built up by row or column over steps.
  • Composite: a group drawn on a layer and faded, moved, scaled and clipped as one; FadeIn, FlyIn, Pop, WipeOut, Shake and the rest are its animations, built into a frame by AppearAt.
  • Transition, Direction: how a slide enters (push, cover, uncover, wipe, split, fade, fade-through, dissolve, iris, zoom, pixelate, glitch, morph, none), for how long (Transition.Over) and from which side (Transition.From).
  • Connector: a line, elbow or curve joining two [Rect]s, with arrowheads that sit on the edges and a Connector.Prog that draws it on.
  • Issue: a problem Deck.Review finds in a build: Code lines clipped, Table rows dropped, text off the canvas or too small to read. Stock components report what they can't fit; a slide's own drawing reports through Ctx.Fits. The review command prints them.

Shapes beyond the basics (Pixels.Polygon, Pixels.Ellipse, Pixels.Polyline, Pixels.Bezier, Pixels.DashedLine) are in shapes.go. Stock components (Panel, Arrow, CycleDiagram, BulletList, PageNumber, ProgressBar, Timeline, Process, Table, and Code with its SyntaxColors, LineRange focus, CodeOverflow modes and Code.Measure) and easing helpers (Ease, CubicBezier, Spring) are in draw.go, diagram.go, table.go, code.go and anim.go. Charts (BarChart, LineChart, DonutChart, Sparkline) and the counting Stat are in chart.go; their series colors are Theme.Series. The guide covers the command line, keys and presenter view; the README starts with a runnable talk, and the authoring guide covers writing one, by hand or with an agent.

Index

Examples

Constants

View Source
const DefaultLeading = 1.1

DefaultLeading is the line height, as a multiple of size, used when Leading is 0.

View Source
const MinText = 0.068

MinText is the smallest text size, as a canvas-height fraction, readable at projector resolutions.

View Source
const MorphDuration = 0.8

MorphDuration is how long TransitionMorph takes by default, in seconds: moving things need longer to read than a cut between frames.

View Source
const Settled = 1000.0

Settled is a time past every entrance animation; slides entered backwards or past a step use it to appear fully built.

View Source
const TransitionDuration = 0.45

TransitionDuration is how long a transition takes by default, in seconds; TransitionMorph takes MorphDuration.

Variables

View Source
var (
	// BlockShadow is Spleen 6x12 with a baked-in shaded shadow.
	BlockShadow = stockFig("Spleen 6x12 Shadow")
	// BlockSolid is Spleen 6x12, solid pixel lettering for headings.
	BlockSolid = stockFig("Spleen 6x12")
	// BlockSmall is Spleen 5x8, compact pixel lettering.
	BlockSmall = stockFig("Spleen 5x8")
	// BlockHuge is Spleen 8x16, larger pixel lettering.
	BlockHuge = stockFig("Spleen 8x16")
	// BlockFancy is Spleen 12x24, more detailed pixel lettering.
	BlockFancy = stockFig("Spleen 12x24")
)

The default block fonts use Spleen under BSD-2-Clause; see fonts/figlet.

View Source
var (
	TransitionDefault  = Transition{}                                             // DefaultTransition, but Over still sets the time
	TransitionNone     = Transition{/* contains filtered or unexported fields */} // hard cut
	TransitionPush     = Transition{/* contains filtered or unexported fields */} // new slide pushes the old one sideways
	TransitionDissolve = Transition{/* contains filtered or unexported fields */} // cells flip from old to new at random
	TransitionWipe     = Transition{/* contains filtered or unexported fields */} // a bright edge sweeps across

	// TransitionMorph moves elements placed with the same key (Scene.Place)
	// to their new rects, and cross-fades the rest. It takes MorphDuration.
	TransitionMorph = Transition{/* contains filtered or unexported fields */}

	TransitionFade        = Transition{/* contains filtered or unexported fields */} // cross-fade; characters switch half-way
	TransitionFadeThrough = Transition{/* contains filtered or unexported fields */} // old fades to the background, then new fades in
	TransitionCover       = Transition{/* contains filtered or unexported fields */} // new slide slides in over the old one, which stays put
	TransitionUncover     = Transition{/* contains filtered or unexported fields */} // old slide slides away off the new one, which stays put
	TransitionSplit       = Transition{/* contains filtered or unexported fields */} // old slide opens like barn doors from the center
	TransitionIris        = Transition{/* contains filtered or unexported fields */} // a circle of the new slide grows from the center
	TransitionZoom        = Transition{/* contains filtered or unexported fields */} // old zooms in and fades out, new grows into place
	TransitionPixelate    = Transition{/* contains filtered or unexported fields */} // old breaks into big blocks, new resolves from them
	TransitionGlitch      = Transition{/* contains filtered or unexported fields */} // noisy slices jump sideways, split in color, and flip to new
)

The transitions. A slide's Transition is one of these, optionally with Over.

View Source
var DefaultTransition = TransitionPush

DefaultTransition applies to slides that don't set Transition.

Functions

func Arrow

func Arrow(p *Pixels, x0, y0, x1, y1, width float64, col RGB, prog float64)

Arrow strokes a line with an arrowhead at (x1, y1), revealed up to fraction prog (0..1).

func BulletList

func BulletList(c Ctx, p *Pixels, lines []string, x, y, w, h float64, firstStep int)

BulletList draws lines as bullets, line i revealed at step i+firstStep, at one size that fits the box.

func Chip

func Chip(c Ctx, p *Pixels, s string, x, y float64, fg, bg RGB, alpha float64) float64

Chip draws a small rounded tag with centered text and returns its width.

func ChipHeight

func ChipHeight(c Ctx) float64

ChipHeight is the height of a Chip; use it to lay out rows of chips.

func Clamp01

func Clamp01(p float64) float64

Clamp01 clamps p to [0, 1].

func Coverage

func Coverage(d float64) float64

Coverage maps a signed distance (negative inside) to antialiased opacity.

func CubicBezier

func CubicBezier(x1, y1, x2, y2 float64) func(float64) float64

CubicBezier returns the easing of CSS's cubic-bezier(x1, y1, x2, y2): a curve from (0,0) to (1,1) through two control points, with x1 and x2 in 0..1. y may leave 0..1 to overshoot. CubicBezier(0.25, 0.1, 0.25, 1) is CSS's "ease". Build it once and keep the func: the returned function does no allocation.

func Ease

func Ease(t, dur float64) float64

Ease is EaseOutCubic(Progress(t, 0, dur)): the workhorse for entrances.

func EaseInCubic

func EaseInCubic(p float64) float64

EaseInCubic accelerates from rest, the mirror of EaseOutCubic: for exits.

func EaseInOutCubic

func EaseInOutCubic(p float64) float64

EaseInOutCubic accelerates then decelerates; transitions use it.

func EaseInOutExpo

func EaseInOutExpo(p float64) float64

EaseInOutExpo is slow at both ends with a fast middle.

func EaseInOutQuad

func EaseInOutQuad(p float64) float64

EaseInOutQuad accelerates then decelerates, more gently than EaseInOutCubic; p is clamped to 0..1.

func EaseInQuad

func EaseInQuad(p float64) float64

EaseInQuad accelerates from rest; p is clamped to 0..1.

func EaseOutBack

func EaseOutBack(p float64) float64

EaseOutBack overshoots slightly before settling, for "pop" entrances.

func EaseOutBounce

func EaseOutBounce(p float64) float64

EaseOutBounce lands on 1 and bounces back up a few times, like a dropped ball.

func EaseOutCubic

func EaseOutCubic(p float64) float64

EaseOutCubic decelerates; p is clamped to 0..1.

func EaseOutElastic

func EaseOutElastic(p float64) float64

EaseOutElastic overshoots and rings like a spring before it settles on 1.

func EaseOutExpo

func EaseOutExpo(p float64) float64

EaseOutExpo decelerates sharply: most of the travel happens at the start.

func EaseOutQuad

func EaseOutQuad(p float64) float64

EaseOutQuad decelerates, more gently than EaseOutCubic; p is clamped to 0..1.

func Hash01

func Hash01(x, y, seed int) float64

Hash01 is a deterministic pseudo-random number in [0,1) for a cell. Use it instead of math/rand so every frame replays exactly.

func IllustrativeTag

func IllustrativeTag(c Ctx, p *Pixels, x, y float64)

IllustrativeTag labels a chart with made-up data to replace before the talk; (x, y) is its top-right corner.

func Label

func Label(c Ctx, p *Pixels, s string, x, y float64, col RGB, align Align) (float64, float64)

Label draws small body-font text at (x, y) and returns its size.

func Lerp

func Lerp(a, b, p float64) float64

Lerp is a+(b-a)*p; unlike Mix, p is not clamped.

func LerpInt

func LerpInt(a, b int, p float64) int

LerpInt is Lerp rounded to the nearest integer.

func LineLabel

func LineLabel(c Ctx, p *Pixels, f *Font, s string, cx, cy float64, col RGB, alpha float64)

LineLabel draws small text on a background plate centered on (cx, cy), so it reads over a line.

func Main

func Main(d Deck)

Main runs a deck from the command line: live in the terminal by default, or the command given (present, list, review, handout, snapshot, sheet, pdf, video). It exits on a bad command line or a failed command; `--help` lists them all.

func PageNumber

func PageNumber(c Ctx, p *Pixels, x, y float64, align Align, col RGB) (float64, float64)

PageNumber draws the slide's position, like "12 / 40", in small body text at (x, y) and returns its size. It draws nothing when c.Count is zero, so an overlay can use it on a Ctx the engine didn't position. Meant for Theme.Overlay.

func Panel

func Panel(c Ctx, p *Pixels, x, y, w, h float64, label string, fill, edge, text RGB, alpha float64)

Panel draws a rounded box with a centered, auto-fitted label. A zero edge draws no outline; alpha fades the whole panel, label included.

Panel is also the pattern for components: take (c Ctx, p *Pixels, ...) in pixel coordinates, size from c.Unit and c.SmallText, take colors and fonts from c.Theme, reveal with c.Reached and c.Since, and return the size drawn if callers lay out around it. Option-heavy components are structs with a Draw method.

func PlaceholderBox

func PlaceholderBox(c Ctx, p *Pixels, x, y, w, h float64, what string)

PlaceholderBox is a dashed, labeled frame marking where real material still needs to go.

func Progress

func Progress(t, start, dur float64) float64

Progress maps t onto 0..1 over [start, start+dur], clamped.

func ProgressBar

func ProgressBar(c Ctx, p *Pixels, r Rect, fill, track RGB)

ProgressBar fills a share of the track r equal to the slides shown so far, (c.Index+1)/c.Count, so the last slide is full. It counts slides, not build steps: Ctx doesn't carry the other slides' step counts. With c.Count zero only the track is drawn. Meant for Theme.Overlay.

func Pulse

func Pulse(t, period float64) float64

Pulse oscillates smoothly between 0 and 1 with the given period.

func ShineBand

func ShineBand(t, dur, strength float64) func(float64) float64

ShineBand returns a Text.Shine func: a bright band sweeping left to right once over dur seconds from t=0.

func Sparkline

func Sparkline(c Ctx, p *Pixels, r Rect, values []float64, col RGB, prog float64)

Sparkline draws values as a tiny line chart with no axes inside r, scaled to fit them, in col. prog (0..1) is how much of the line to draw, left to right, so Ease(c.Since(step), 0.8) animates it; a dot marks the end of what is drawn. NaN values break the line, and a constant series is a flat line.

func SpeechBubble

func SpeechBubble(p *Pixels, x, y, w, h, tailX, tailY float64, fill, edge RGB, alpha float64)

SpeechBubble draws a rounded bubble with a tail toward (tailX, tailY).

func Spring

func Spring(from, to, t, freq, damping float64) float64

Spring returns the position at time t of a Harmonica spring that starts at from and is pulled toward to. freq sets speed (try 2-8); damping < 1 bounces, 1 is critically damped. The result depends only on t, which counts in whole 1/60 s steps; it stops at settling or after 10 seconds.

func StockFigletFontNames

func StockFigletFontNames() []string

StockFigletFontNames returns the bundled FIGlet font names in alphabetical order, without .flf suffixes. The returned slice belongs to the caller.

Example
package main

import (
	"fmt"

	"github.com/austinlparker/decker"
)

func main() {
	for _, name := range decker.StockFigletFontNames() {
		fmt.Println(name)
	}
}
Output:
Decker Geometric
Decker Sign
Decker Slab
Decker Square
Decker Stencil
Spleen 12x24
Spleen 5x8
Spleen 6x12
Spleen 6x12 Shadow
Spleen 8x16

Types

type Align

type Align int

Align positions text relative to the x coordinate passed to Draw.

const (
	// Left aligns text's left edge to x.
	Left Align = iota
	// Center aligns text's center to x.
	Center
	// Right aligns text's right edge to x.
	Right
)

type ArrowHead

type ArrowHead int

ArrowHead is the mark at one end of a Connector.

const (
	HeadNone  ArrowHead = iota // a bare end
	HeadArrow                  // a filled triangle whose tip touches the rect's edge
	HeadDot                    // a disc resting against the rect's edge
)

The marks a Connector's ends can carry.

type BarChart

type BarChart struct {
	Labels     []string    // one per category
	Values     []float64   // one series; ignored if Series is set
	Series     [][]float64 // grouped bars: one slice of values per series
	Names      []string    // series names for the legend, shown for two or more series
	Max        float64     // the axis top; 0 picks one that rounds up from the data
	Horizontal bool        // bars run left to right, with labels down the left side
	ShowValues bool        // write each value at the end of its bar
	Step       int         // the build step it grows in on
	Stagger    float64     // seconds between bars starting; 0 is 0.08, negative is none
	Format     func(float64) string
}

BarChart is a bar chart that grows from its baseline: bars rise (or extend, if Horizontal) one after another, then value labels appear on their ends. Set Values for one series, or Series for grouped bars; Names then label a legend.

The value axis has a few gridlines at round numbers. Negative values hang below a zero line, and NaN or infinite values leave a gap. Category labels are wrapped, then shrunk, to fit their slot.

func (BarChart) Draw

func (b BarChart) Draw(c Ctx, p *Pixels, r Rect) (w, h float64)

Draw renders the chart into r and returns the size it drew, which is r's.

type Block

type Block struct {
	Font  *FigletFont
	Scale float64 // pixel width of one font cell, at least 0.5; it is twice as tall

	Color  RGB  // solid parts (█ ▀ ▄ ▌ ▐ ░ …)
	To     *RGB // if set, solid parts get a left-to-right gradient Color → To
	Shadow RGB  // everything else (box-drawing edges); zero = Color dimmed toward the canvas background
	Drop   RGB  // if set, a drop shadow of the solid parts, offset down-right
	Align  Align
	Gap    int // blank font rows between lines

	// Glow draws a soft light behind the solid parts.
	Glow float64

	FX BlockEffect
}

Block draws text in a block font onto the pixel canvas, scaled to the screen: each font cell becomes a Scale×(2·Scale) pixel box, so block letters are as big on stage at 682 columns as at 240. FitBlock picks a font and scale.

func (Block) Cells

func (b Block) Cells(s string) (w, h int)

Cells returns the block's size in font cells for s: what BlockCell.W and BlockCell.H will be, and the width effects like BlockSlide travel.

func (Block) Draw

func (b Block) Draw(p *Pixels, s string, x, y float64) (w, h float64)

Draw renders s (which may contain "\n") with its top edge at pixel y and x as the left edge, center or right edge per Align. It returns the size in pixels.

func (Block) Size

func (b Block) Size(s string) (w, h float64)

Size returns the block's size in pixels for s.

type BlockCell

type BlockCell struct {
	Char        int     // index of the source character (across all lines)
	Col, Row    int     // position within the whole block, in font cells
	W, H        int     // block size in font cells
	U           float64 // Col / (W-1): 0 at the left edge, 1 at the right
	Rune        rune    // the font's character for this cell
	Solid       bool    // a filled block character, as opposed to a box-drawing edge
	Line, Line0 int     // which line, and the Char index where it starts
}

BlockCell describes one non-blank cell of rendered block text, for effects.

type BlockEffect

type BlockEffect func(BlockCell) BlockFX

BlockEffect animates the cells of a Block (Block.FX); Draw calls it for each non-blank cell. It follows GlyphEffect's contract: a pure function of t (seconds since start) and the cell, where the zero BlockFX hides the cell and BlockFX{Alpha: 1} leaves it alone. Combine with BlockChain.

func BlockBeam

func BlockBeam(t, dur float64) BlockEffect

BlockBeam sweeps a bright band across once in dur seconds; layer it on settled text.

func BlockChain

func BlockChain(fxs ...BlockEffect) BlockEffect

BlockChain runs effects together: offsets add, alpha multiplies, the last rune or color set wins, brightness takes the max.

func BlockDecrypt

func BlockDecrypt(t, dur float64, noise RGB) BlockEffect

BlockDecrypt flickers cells through shade characters in the noise color (faint reads best), then locks them in with a bright flash, left to right over dur.

func BlockFade

func BlockFade(t, dur float64) BlockEffect

BlockFade fades the whole block in over dur seconds.

func BlockGlitch

func BlockGlitch(t, amount float64) BlockEffect

BlockGlitch jumps rows sideways and flickers cells; amount is 0..1.

func BlockRain

func BlockRain(t float64) BlockEffect

BlockRain drops each column in from above with a bounce, staggered left to right.

func BlockSlide

func BlockSlide(t float64, width int) BlockEffect

BlockSlide glides alternate rows in from opposite sides; width is the block's width in cells (Block.Cells).

func BlockType

func BlockType(t, cps float64) BlockEffect

BlockType shows characters one at a time at cps characters per second.

type BlockFX

type BlockFX struct {
	DX, DY float64 // offset in font cells (fractions allowed)
	Alpha  float64 // 0 hides the cell, 1 is fully visible; Draw skips cells at 0.02 or below
	Rune   rune    // 0 keeps the real character
	Bright float64 // 0..1 pushes the color toward white
	Color  *RGB    // replaces the color entirely
}

BlockFX is an effect's instruction for one cell of block text.

type Code

type Code struct {
	Source string
	Lang   string // a chroma language name, alias or file extension; unknown or empty draws plain text

	LineNumbers bool
	Title       string // a filename tab above the code

	// Focus has one range per step, starting at FirstStep: at step
	// FirstStep+i the lines in Focus[i] are drawn at full strength and the rest
	// dimmed behind a highlight bar that glides from the previous range. A step
	// past the last range keeps the last; an empty range (the zero LineRange)
	// returns every line to normal. Before FirstStep, or with no Focus, every
	// line is normal.
	Focus     []LineRange
	FirstStep int

	// Diff reads Source as a unified diff: the first character of each line is
	// its marker. "+" and "-" lines get the added and removed backgrounds and
	// keep their marker in a gutter, " " marks context, and any other first
	// character is part of a context line. What follows the marker is lexed as
	// Lang.
	Diff bool

	// Size is the Mono size in pixels; 0 picks the largest that fits, but never
	// below c.SmallText(Mono) unless Overflow is CodeShrink.
	Size int

	// Overflow is what the block does with source that doesn't fit its rect
	// at the smallest readable size: clip it (the default), shrink below
	// that size, or scroll to follow Focus. Code.Measure says beforehand
	// whether it fits.
	Overflow CodeOverflow

	// Excerpt shows only these lines of Source, numbered and focused as in
	// the whole source, so a long file can be shown a part at a time with
	// its real line numbers. The zero LineRange shows every line.
	Excerpt LineRange
}

Code draws source code on a rounded plate with syntax highlighting, and can pick out a few lines at each build step.

Code{Source: src, Lang: "go", Title: "main.go", LineNumbers: true,
	Focus: []LineRange{{1, 3}, {5, 9}}, FirstStep: 1}.Draw(c, p, c.Rect(0.05, 0.2, 0.6, 0.7))

The source is lexed once per (Source, Lang) and cached. Code assumes the theme's Mono font is monospaced.

func (Code) Draw

func (k Code) Draw(c Ctx, p *Pixels, r Rect) (w, h float64)

Draw renders the block inside r, anchored at its top-left, and returns the size of the plate. The plate hugs the code, and is never larger than r. Under review, lines or columns that don't fit are a "code-clipped" error.

func (Code) Measure added in v0.6.0

func (k Code) Measure(c Ctx, r Rect) CodeLayout

Measure returns how the block fits r, as Draw would draw it there.

type CodeLayout added in v0.6.0

type CodeLayout struct {
	Size int     // the Mono size in pixels: Code.Size, or the size fitted to the rect
	W, H float64 // the plate, which is never larger than the rect

	// NeedW and NeedH are the plate that would show every line and column
	// at Size.
	NeedW, NeedH float64

	// Head is the plate's height above the first line: padding, plus the
	// filename tab when there is a Title.
	Head float64

	Lines, Shown    int // lines in the source (or Excerpt); lines in view
	Cols, ShownCols int // columns of the widest line, gutter included; columns drawn
}

CodeLayout is how a Code block fits a rect: the size its text is set at, the plate it draws and how much of the source shows. Code.Measure returns it without drawing, so a slide can pick a bigger rect, a smaller excerpt or a split across builds before anything is lost.

func (CodeLayout) Fits added in v0.6.0

func (l CodeLayout) Fits() bool

Fits reports whether every line and column of the source shows.

type CodeOverflow added in v0.6.0

type CodeOverflow int

CodeOverflow is what a Code block does with source that doesn't fit its rect at the smallest readable size.

const (
	// CodeClip sets the code at the smallest readable size and clips what
	// still doesn't fit; a review reports it as "code-clipped".
	CodeClip CodeOverflow = iota
	// CodeShrink keeps shrinking until every line and column fits, below the
	// readable size if it must; a review reports text that small as
	// "text-small".
	CodeShrink
	// CodeScroll keeps the readable size and shows the lines that fit,
	// scrolled to keep the current Focus range in view and gliding with its
	// highlight, so a long file can be walked through over builds. A review
	// reports a focus range taller than the view, or no Focus at all, as
	// "code-clipped".
	CodeScroll
)

type Composite

type Composite struct {
	Alpha  float64 // 0 hides the element, 1 draws it as is
	DX, DY float64 // offset in pixels
	Scale  float64 // size about the pivot; 0 means 1

	// PivotX and PivotY place the point Scale grows from, as fractions of
	// the bounds' width and height from their center: 0, 0 scales about the
	// center and -0.5, -0.5 about the top-left corner.
	PivotX, PivotY float64

	Trim Trim // the part of the bounds left out, after moving and scaling
}

Composite draws a group of things as one: onto a layer of its own, then back onto the canvas faded, moved, scaled and clipped as a whole. The zero Composite is invisible (Alpha 0); start from Identity and set what you want, or take one from the element animations (FadeIn, FlyIn, ...).

A Composite is a plain value with no pointers, so building one every frame allocates nothing, and effects compose with Composite.Then. Positions are relative to the bounds given to Draw, and Trim follows the element as it moves and scales.

func AppearAt

func AppearAt(c Ctx, enter, exit int, in, out func(t float64) Composite) Composite

AppearAt turns "built in at step enter, out at step exit" into the Composite for this frame. in and out make the entrance and exit from the seconds since their step began (the closure sees Ctx), such as

func(t float64) Composite { return FlyIn(c, t, 0.5, DirLeft, 0.1) }

The element is hidden before enter. A nil in shows it at once, and enter < 0 has no entrance; exit < 0 never exits, and a nil out cuts it at exit. Slides entered backwards or past a step see Settled times, so the element is already in or already out.

func Combine

func Combine(fxs ...Composite) Composite

Combine runs composites together as Then does, starting from Identity.

func Dim

func Dim(t, dur, level float64) Composite

Dim fades an element toward the background, over dur seconds, down to the opacity level (try 0.35), and holds it there. To undim it, stop calling Dim at the step it should return.

func FadeIn

func FadeIn(t, dur float64) Composite

FadeIn fades an element in over dur seconds.

func FadeOut

func FadeOut(t, dur float64) Composite

FadeOut fades an element out over dur seconds.

func FlyIn

func FlyIn(c Ctx, t, dur float64, from Direction, dist float64) Composite

FlyIn slides an element in over dur seconds from dist of the screen away in direction from (DirLeft starts it left of its place; DirDefault below it), fading in over the first half. dist 1 starts it just off the screen.

func FlyOut

func FlyOut(c Ctx, t, dur float64, to Direction, dist float64) Composite

FlyOut slides an element away over dur seconds, dist of the screen toward direction to, fading out over the second half.

func Grow

func Grow(t, dur, peak float64) Composite

Grow swells an element about its center to peak times its size (try 1.1) and back over dur seconds, to draw the eye. It is Identity outside 0..dur.

func Identity

func Identity() Composite

Identity returns the Composite that changes nothing: it draws straight onto the canvas, with no layer.

func Pop

func Pop(t, dur float64) Composite

Pop grows an element from nothing about its center over dur seconds, overshooting its size a little with EaseOutBack before settling.

func Shake

func Shake(t, dur, amp float64) Composite

Shake jolts an element by up to amp pixels for dur seconds, dying away, 30 times a second. Offsets are whole pixels, so the element stays sharp. It is Identity outside 0..dur.

func WipeIn

func WipeIn(t, dur float64, from Direction) Composite

WipeIn reveals an element over dur seconds from the edge in direction from across to the opposite one: DirLeft uncovers it left to right, DirDefault bottom to top. It is a clip, so nothing moves.

func WipeOut

func WipeOut(t, dur float64, to Direction) Composite

WipeOut hides an element over dur seconds into the edge in direction to, the reverse of WipeIn: DirLeft leaves the left edge showing last.

func ZoomIn

func ZoomIn(t, dur, from float64) Composite

ZoomIn grows an element about its center from the given scale (try 0.6) to 1 over dur seconds, fading in over the first half.

func ZoomOut

func ZoomOut(t, dur, to float64) Composite

ZoomOut shrinks an element about its center from 1 to the given scale (try 0.6) over dur seconds, fading out over the second half.

func (Composite) Clipped

func (k Composite) Clipped(bounds, clip Rect) Composite

Clipped returns k showing only the part of bounds inside clip, in bounds' own coordinates (before k moves it). It sets Trim, replacing any there.

func (Composite) Draw

func (k Composite) Draw(p *Pixels, bounds Rect, draw func(p *Pixels))

Draw calls draw to paint the element, which lives inside bounds (in canvas pixels), and puts the result on p as k says. Like a morphing element, draw may stray up to a tenth of the canvas height outside bounds (a glow, say) and no further; what it draws beyond that is lost.

An identity k calls draw on p itself: no layer, no cost. Fading or trimming alone draws on a pooled copy of the region and mixes it back. Moving or scaling draws the element twice, onto black and onto white, to learn how much of each pixel it covers (the canvas has no alpha), then resamples that: bilinear when moving or growing, averaged over the pixels each one covers when shrinking. So draw must be pure and may run twice, and an element moved off whole pixels is slightly soft. A review sees what draw reports where k shows it, and only once.

The layers are pooled and only the region around bounds is cleared and composited, so a steady-state frame does not allocate. They are canvas sized, because draw paints in canvas coordinates.

func (Composite) Then

func (k Composite) Then(o Composite) Composite

Then returns k followed by o as one Composite: alphas and scales multiply, offsets add, trims keep the larger cut per side, and o's pivot wins if it sets one. A zero Scale counts as 1; a zero Alpha stays 0.

type Connector

type Connector struct {
	From, To Rect
	// FromSide and ToSide force the side the path leaves and arrives at. If
	// the sides picked don't face each other, an elbow or a line can run
	// through the rects.
	FromSide, ToSide Side
	Route            Route
	// Head marks the To end and Tail the From end.
	Head, Tail ArrowHead
	Width      float64 // stroke width; 0 uses a width that scales with the canvas
	Color      RGB     // zero uses the theme's Muted
	Dashed     bool
	Label      string // on a plate at the path's midpoint
	// Prog is how much of the path is drawn, 0..1, by length from the From end;
	// the label fades in at the end. 0 draws nothing, so a connector that is
	// always there wants 1. Animate it with Ease(c.Since(step), dur).
	Prog float64
}

Connector joins one Rect to another with a line, an elbow or a curve, optionally capped with arrowheads and labeled. It leaves From and arrives at To, at the middle of the sides that face each other; set FromSide or ToSide to pick a side instead. The arrowhead's tip sits exactly on the target's edge.

Connector{From: a, To: b, Route: RouteElbow, Head: HeadArrow, Prog: Ease(c.Since(1), 0.6)}.Draw(c, p)

func (Connector) Draw

func (k Connector) Draw(c Ctx, p *Pixels)

Draw renders the connector on p.

type Ctx

type Ctx struct {
	W, H  int     // drawable area in cells
	T     float64 // seconds since this slide appeared
	Step  int     // current build step, 0-based
	StepT float64 // seconds since the current step began

	// Index is this slide's 0-based position in the deck and Count the number
	// of slides, so an overlay can draw "12 / 40" or a progress bar. Section
	// is the slide's resolved Slide.Section: its own, or the nearest one
	// before it, empty before the first. All three are zero in a Ctx built by
	// hand and not passed through the engine (Deck.Render and Deck.Draw fill
	// them in from the slide index).
	Index, Count int
	Section      string

	// Theme is the deck's theme; never nil inside View.
	Theme *Theme
	// contains filtered or unexported fields
}

Ctx is everything a slide needs to draw a frame. Its layout methods take fractions of the pixel canvas (W wide, 2H tall), so a slide looks the same at any size: c.X(0.5) is the center, c.Size(0.1) a font a tenth of the screen tall.

func (Ctx) Fits added in v0.6.0

func (c Ctx) Fits(what string, r Rect, w, h float64) bool

Fits reports whether a w×h block fits in r, give or take half a pixel. When it doesn't and the frame is under review, it records an "overflow" error naming what, with both sizes: custom drawing reports what it can't fit the way stock components do.

Call it on the layout a slide draws, not on one it only tries: a slide that falls back to a smaller layout compares sizes itself, then checks the one it keeps.

lay := layoutWaterfall(spans, r)
if lay.W > r.W || lay.H > r.H {
	lay = layoutWaterfall(spans[:8], r) // the tail, rather than clipping
}
c.Fits("waterfall", r, lay.W, lay.H)

func (Ctx) Frame

func (c Ctx) Frame() Rect

Frame returns the whole canvas as a Rect.

func (Ctx) PH

func (c Ctx) PH() float64

PH returns the pixel canvas height, twice the height in cells.

func (Ctx) PW

func (c Ctx) PW() float64

PW returns the pixel canvas width.

func (Ctx) Reached

func (c Ctx) Reached(step int) bool

Reached reports whether the slide is at or past step.

func (Ctx) Rect

func (c Ctx) Rect(fx, fy, fw, fh float64) Rect

Rect returns the box at (fx, fy) of size fw×fh, all fractions of the canvas like X and Y.

func (Ctx) Since

func (c Ctx) Since(step int) float64

Since returns seconds since step began: negative before it, Settled after it. Animate a step's element with Ease(Since(2), 0.5).

func (Ctx) Size

func (c Ctx) Size(f float64) int

Size returns a font size in pixels, f of the canvas height, at least 6.

func (Ctx) SmallText

func (c Ctx) SmallText(f *Font) int

SmallText is the smallest readable size for f on this screen, as drawn.

func (Ctx) Unit

func (c Ctx) Unit(f float64) float64

Unit scales a stroke, gap or radius with the canvas: f of its height. Same as Y; use it when the number isn't a position.

func (Ctx) X

func (c Ctx) X(f float64) float64

X converts a fraction of the canvas width to a pixel x coordinate.

func (Ctx) Y

func (c Ctx) Y(f float64) float64

Y converts a fraction of the canvas height to a pixel y coordinate.

type CycleDiagram

type CycleDiagram struct {
	Labels []string
	Lap    float64 // seconds per loop; must be > 0
	Step0  int     // step at which the first station appears
	Ring   RGB     // the ring's track; zero uses the theme's Faint
}

CycleDiagram is a ring of numbered stations with a legend, one station per step. Once the ring closes, a comet circles it and lights each station as it passes.

func (CycleDiagram) Draw

func (d CycleDiagram) Draw(c Ctx, p *Pixels, top float64) (looping bool, theta float64, active int, legendX float64)

Draw renders the diagram below y=top. It returns whether the comet is circling, its angle (0 = top, clockwise), the active station (-1 if none), and the legend's x.

type Deck

type Deck struct {
	// Name identifies the deck on this machine: it names the presenter socket
	// and the binary --dev rebuilds into, and the command line's help shows
	// it as the program's name. Keep it short.
	Name string

	// Theme is required, and so are its Display, Body and Mono fonts.
	Theme *Theme

	// Slides in running order. Must not be empty.
	Slides []Slide

	// Notes are the speaker notes, in Markdown, for the presenter view, the
	// n key and the handout. Each "## " heading starts the next slide's
	// section and is that slide's Title as written, so the file reads as the
	// talk's outline and a slide added or moved without its notes fails
	// the deck's check rather than shifting every note after it. Text
	// before the first heading is about the talk as a whole. Sections may
	// stop before the last slide; the slides after have no notes. Keep
	// them in a file beside the talk and embed it:
	//
	//	//go:embed notes.md
	//	var notes string
	//
	// A fenced block whose info string is "post" is a post for a live
	// thread (Publisher), left out of the notes the speaker sees:
	// media=final (the default) attaches the slide's last build,
	// media=builds an image of each build (builds=2-4,6 picks them),
	// media=clip a looping video of it building, and media=none nothing.
	// An "alt" block right after a post describes its media. Posts before
	// the first section open the thread, and attach nothing.
	Notes string

	// Publisher posts the talk as a thread while it's given, once the
	// presenter view's posting dialog (P) starts it: a slide's posts go
	// out when the speaker moves on from it. Nil leaves the dialog only
	// its rehearsal.
	Publisher Publisher
}

Deck is a talk: its slides in running order, its theme, and a name. Pass it to Main from the talk's main function.

func (*Deck) Canvas added in v0.7.0

func (d *Deck) Canvas(i int, c Ctx) *Pixels

Canvas renders slide i's pixels as Render does, without the character layer or turning them into cells: what video and post images show, for tests (decktest.Golden). Like Render it sets c's position from i. The Pixels are the caller's.

func (*Deck) Check added in v0.7.0

func (d *Deck) Check() error

Check reports what makes d unfit to run, as Main refuses it: a missing Name, Theme or theme font, no slides, or Notes whose sections don't follow the slides' titles in order.

func (*Deck) Draw

func (d *Deck) Draw(i int, c Ctx) error

Draw renders slide i into cells without encoding them: the live deck's per-frame work, for tests and benchmarks. Like Render it sets c's position from i, and a panic shows in the frame as its error. Draw also returns that panic, as an error naming the slide, the part that panicked ("View", "a placed element" or "Theme.Overlay") and the panic value, which it wraps if it is an error; it returns nil when nothing panicked.

func (*Deck) Posts added in v0.7.0

func (d *Deck) Posts(dir string) ([]Post, error)

Posts are every post the deck's notes make, in order, with their media rendered into dir as the presenter view's posting dialog renders it. Clips need ffmpeg. decktest.Posts checks them before the talk.

func (*Deck) Render

func (d *Deck) Render(i int, c Ctx) string

Render draws slide i (0-based) as a styled string of exactly c.W×c.H cells, as the snapshot command prints it. A nil c.Theme uses the deck's, and c.Index, c.Count and c.Section are set from i and the deck; a panic renders as its error.

func (*Deck) Review added in v0.6.0

func (d *Deck) Review() []Issue

Review renders every build of every slide, settled, at 240×67, 320×90 and 682×171 cells, and returns what is wrong with them, in slide, build and size order. Stock components report what they cannot fit (a Code block's hidden lines, a Table's dropped rows), text reports running off the canvas, being too small to read or running into other text, a panic is an issue rather than a crash, and a slide's own drawing reports through Ctx.Fits. Codes a slide lists in Slide.Allow are left out. The frames are the ones the deck shows: Review only listens while they are drawn.

func (*Deck) Section

func (d *Deck) Section(i int) string

Section returns the section slide i (0-based) belongs to: its own Slide.Section, else the nearest one before it, else "".

func (*Deck) Steps

func (d *Deck) Steps(i int) int

Steps returns how many build steps slide i (0-based) has.

type Delivery added in v0.7.0

type Delivery struct {
	// Root and Parent are the refs of the thread's first post and of the
	// post this replies to; both empty for the first.
	Root, Parent string

	// Key names the post: the same on every try of it, after a restart
	// too, and no other post's, in this thread or another. First is when
	// it was first tried. The thread file keeps both before that try goes
	// out, so a Thread can derive what it sends from them, as bsky derives
	// a post's record key, and find the post again on a retry.
	Key   string
	First time.Time

	// Retry is set when an earlier try of the post may have gone out: its
	// answer never came, or the presenter view stopped before recording it.
	Retry bool
}

Delivery is where a post goes in its thread, and which post it is.

type Direction

type Direction int

Direction is a side of the screen or of an element: where an element or a transition's new slide comes from, which edge a wipe starts at, the axis a split opens along. DirUp is the top edge and DirDown the bottom.

const (
	// DirDefault is the default side of whatever takes it: each
	// transition's own (see Transition.From), and from below for an element
	// animation, as in PowerPoint.
	DirDefault Direction = iota
	DirLeft
	DirRight
	DirUp
	DirDown
)

The directions.

type DonutChart

type DonutChart struct {
	Labels    []string
	Values    []float64
	Thickness float64 // the ring's width as a fraction of its radius, 0 is 0.32
	Center    string  // big text in the hole
	Step      int     // the build step it sweeps in on
}

DonutChart is a ring split into segments by value, sweeping in clockwise from 12 o'clock, with a legend beside it giving each label and its share. Negative, NaN and infinite values count as zero. Segments are drawn with round ends and a small gap, so they read as separate even when thin.

func (DonutChart) Draw

func (d DonutChart) Draw(c Ctx, p *Pixels, r Rect) (w, h float64)

Draw renders the chart into r and returns the size it drew: the ring and its legend, centered in r.

type FigletFont

type FigletFont struct {
	Name string // display name; file loaders use the file name without .flf
	// Height is the row count the font file declares. Rendered lines trim
	// blank padding rows, so use Rows for the height of drawn text.
	Height int
	// contains filtered or unexported fields
}

FigletFont is a parsed FIGlet (.flf) font, classic terminal-character type drawn by Block. Stock fonts' files and provenance are in fonts/figlet.

func FitBlock

func FitBlock(s string, maxW, maxH float64, maxLines, gap int, fonts ...*FigletFont) (*FigletFont, []string, float64)

FitBlock picks a font and scale at which s, wrapped to at most maxLines lines with gap blank rows between them, is as big as possible in a maxW×maxH pixel box. Fonts missing a character of s are skipped. Fonts are in order of preference: a later one only wins if its letters come out 15%+ taller, and a font that fits always beats one that doesn't. Scales snap to half pixels (whole from 4 up) for nearly crisp edges, and go no smaller than 0.5: when nothing fits even then, FitBlock returns the wrap that overflows least.

func LoadFigletFont

func LoadFigletFont(fsys fs.FS, path string) *FigletFont

LoadFigletFont loads a FIGlet (.flf) font from fsys, named after its file. Fonts of block (█ ▀ ▄ ░) and box-drawing (═ ║ ╗) characters draw best. It panics if the font is missing or broken. fsys may be an embed.FS or an os.DirFS. Load once, outside frame functions. For bytes and error handling, use ParseFigletFont.

Example
package main

import (
	"embed"
	"fmt"

	"github.com/austinlparker/decker"
)

//go:embed fonts/figlet/Decker*.flf
var exampleFigletFonts embed.FS

func main() {
	font := decker.LoadFigletFont(exampleFigletFonts, "fonts/figlet/Decker Square.flf")
	fmt.Println(font.Name, font.Has("Custom fonts!"))
}
Output:
Decker Square true

func ParseFigletFont

func ParseFigletFont(name string, data []byte) (*FigletFont, error)

ParseFigletFont parses a FIGlet (.flf) font from data, using name for FigletFont.Name. It returns an error for missing or malformed ASCII glyph data. Only ASCII input glyphs are loaded; selected Unicode blocks, shades and box-drawing strokes are painted by Block. ASCII-art strokes such as / and _ are not.

Example
package main

import (
	"embed"
	"fmt"

	"github.com/austinlparker/decker"
)

//go:embed fonts/figlet/Decker*.flf
var exampleFigletFonts embed.FS

func main() {
	data, err := exampleFigletFonts.ReadFile("fonts/figlet/Decker Slab.flf")
	if err != nil {
		panic(err)
	}
	font, err := decker.ParseFigletFont("My heading font", data)
	if err != nil {
		panic(err)
	}
	fmt.Println(font.Name, font.Has("Hello, Go!"))
}
Output:
My heading font true

func StockFigletFont

func StockFigletFont(name string) *FigletFont

StockFigletFont loads a bundled FIGlet font by name, without the .flf suffix. StockFigletFontNames lists the available names. Load each font once, outside frame functions, and pass it to Block.Font or FitBlock. It panics for an unknown name. Bundled fonts retain BSD or OFL terms; see fonts/figlet.

Example
package main

import (
	"fmt"

	"github.com/austinlparker/decker"
)

func main() {
	font := decker.StockFigletFont("Decker Sign")
	fmt.Println(font.Name, font.Has("Hello, Go!"))
}
Output:
Decker Sign true

func (*FigletFont) DropQuotes

func (f *FigletFont) DropQuotes(s string) string

DropQuotes removes quote marks the font has no glyph for, which beats falling back to a smaller font. FitBlock does this itself; call it before wrapping or measuring a string FitBlock fitted.

func (*FigletFont) Has

func (f *FigletFont) Has(s string) bool

Has reports whether the font has a visible glyph for each non-space rune of s.

func (*FigletFont) Rows

func (f *FigletFont) Rows() int

Rows returns the height in cells of one rendered line, measured on a sample with ascenders, descenders and capitals so it is the same for every line.

func (*FigletFont) Width

func (f *FigletFont) Width(s string) int

Width returns the width in cells of s rendered on one line.

func (*FigletFont) Wrap

func (f *FigletFont) Wrap(s string, maxW int) []string

Wrap breaks s into lines no wider than maxW cells, at spaces, keeping "\n". Like Font.Wrap it balances the lines: each paragraph uses the narrowest width needing no more lines than maxW does.

type Font

type Font struct {

	// Small, if set, replaces this font below SmallBelow pixels: medium weights
	// get spindly at low resolution, so point it at a bolder cut.
	Small      *Font
	SmallBelow int
	// contains filtered or unexported fields
}

Font draws raster type at any pixel size, caching rasterized glyphs per size. The pixel layer is twice as tall as the terminal in cells.

func LoadFont

func LoadFont(fsys fs.FS, path string) *Font

LoadFont loads a TrueType or OpenType font from fsys. It panics if the font is missing or broken: fonts are built in, so that is a deck bug.

func ParseFont

func ParseFont(data []byte) (*Font, error)

ParseFont parses a TrueType or OpenType font.

func StockFont

func StockFont(name string) *Font

StockFont loads a bundled typeface (SIL OFL; see fonts/): "SpaceGrotesk-Bold", "SpaceGrotesk-Medium" or "JetBrainsMono-ExtraBold". Load each once: a Font caches glyphs.

func (*Font) Ascent

func (f *Font) Ascent(size int) float64

Ascent is the distance from the top of a line to its baseline.

func (*Font) CapHeight

func (f *Font) CapHeight(size int) float64

CapHeight is the height of capital letters.

func (*Font) Drawn

func (f *Font) Drawn(size int) int

Drawn returns the size text requested at size is actually drawn at.

func (*Font) Ink

func (f *Font) Ink(s string, size int) (top, bottom float64)

Ink returns how far s's glyphs reach above (top, negative) and below (bottom, positive) the baseline at size, ignoring spaces. Both are 0 if s has no visible glyphs.

func (*Font) Measure

func (f *Font) Measure(s string, size int) float64

Measure returns the width of a single line of text.

func (*Font) Wrap

func (f *Font) Wrap(s string, size int, maxW float64) []string

Wrap breaks s at spaces into lines no wider than maxW, keeping "\n" breaks. Lines are balanced: it uses the narrowest width needing no more lines than maxW does, so "What Your MCP / Server Does" beats "What Your MCP Server / Does".

type GlyphEffect

type GlyphEffect func(i int) GlyphFX

GlyphEffect animates the glyphs of a Text (Text.FX); i counts glyphs across all lines, spaces and line breaks included.

Constructors take t, the seconds since the effect starts. Effects must be pure functions of t and i (no clock, no math/rand; use Hash01) so frames replay exactly. The zero GlyphFX hides the glyph: return it before the effect reaches i, and GlyphFX{Alpha: 1} for glyphs left alone.

func Chain

func Chain(fxs ...GlyphEffect) GlyphEffect

Chain runs effects together: offsets add, alphas multiply, and the last stand-in rune wins.

func Decode

func Decode(t, dur float64, n int) GlyphEffect

Decode cycles letters through random characters before locking them in, left to right over dur seconds; n is the number of glyphs.

func DropIn

func DropIn(t, stagger float64, size int) GlyphEffect

DropIn drops letters from above with a bounce, stagger seconds apart.

func FadeUp

func FadeUp(t, dur float64, size int) GlyphEffect

FadeUp fades the block in over dur seconds while sliding up 0.4×size.

func Jitter

func Jitter(t, amp float64) GlyphEffect

Jitter shakes letters by up to amp pixels.

func RiseIn

func RiseIn(t, stagger float64, size int) GlyphEffect

RiseIn springs letters up from below, fading in; size sets how far they travel.

func TypeOn

func TypeOn(t, cps float64) GlyphEffect

TypeOn shows letters one at a time at cps characters per second.

func Wave

func Wave(t, amp float64) GlyphEffect

Wave bobs letters by amp pixels in a travelling wave. It never settles.

type GlyphFX

type GlyphFX struct {
	DX, DY float64 // offset in pixels
	Alpha  float64 // 0 hides the glyph, 1 leaves it untouched
	Rune   rune    // 0 = the real character; ignored for spaces
}

GlyphFX is an effect's instruction for one glyph of Text.

type Images

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

Images is a set of raster images (PNG or JPEG), usually embedded in the binary. Each is decoded on first use, and each drawn size is cached.

func NewImages

func NewImages(fsys fs.FS, dir string) *Images

NewImages returns the images in directory dir of fsys.

func (*Images) Draw

func (ims *Images) Draw(p *Pixels, name string, x, y, w, h, alpha float64) (dx, dy, dw, dh float64, ok bool)

Draw paints the image called name into the box (x, y, w, h), aspect kept, centered, at opacity alpha. Source pixels are averaged per canvas pixel and the resampled copy is cached per size. A transparent image lets the canvas show through in proportion to each pixel's own alpha. It returns the drawn rectangle, or ok=false (drawing nothing) if the image is missing.

func (*Images) DrawCover

func (ims *Images) DrawCover(p *Pixels, name string, x, y, w, h, alpha float64) (dx, dy, dw, dh float64, ok bool)

DrawCover paints the image called name so it fills the box (x, y, w, h) completely: the image is scaled until both sides cover the box, keeping its aspect, and the overflow is cropped evenly from both ends. Opacity, caching and transparency work as in Draw. It returns the filled rectangle, which is the box rounded to whole pixels, or ok=false (drawing nothing) if the image is missing.

func (*Images) Has

func (ims *Images) Has(name string) bool

Has reports whether the image called name exists and decodes.

type Issue added in v0.6.0

type Issue struct {
	Slide, Step int    // 0-based, like Ctx.Index and Ctx.Step
	Title       string // the slide's title
	W, H        int    // the frame's size in cells

	Severity Severity
	Code     string
	Rect     Rect // where on the canvas, in pixels; zero for the whole frame
	Msg      string
}

Issue is a problem a review found in one frame: a build of a slide, settled, at one size. Code is a stable name for the kind of problem, such as "code-clipped", "text-small" or "overflow"; Msg says what and by how much, naming the element that has it.

func (Issue) String added in v0.6.0

func (i Issue) String() string

String formats the issue as one line, with the 1-based slide and build numbers the command line uses: "11.2 Collector config 320x90 error code-clipped: Code "otel.yaml": 5 of 10 lines visible".

type LineChart

type LineChart struct {
	Labels []string    // x-axis categories
	Series [][]float64 // one slice of values per line
	Names  []string    // series names for the legend
	Max    float64     // the axis top; 0 fits the data
	Min    float64     // the axis bottom; 0 fits the data, down to zero if its smallest value is at most half its largest
	Step   int         // the build step it draws on at
	Points bool        // mark every value with a dot
	Format func(float64) string
}

LineChart draws one or more series as lines over shared category labels. The lines draw on from left to right as they ease in; Points marks each value. With more than one series a legend names them, using Names. NaN and infinite values break a line.

func (LineChart) Draw

func (l LineChart) Draw(c Ctx, p *Pixels, r Rect) (w, h float64)

Draw renders the chart into r and returns the size it drew, which is r's.

type LineRange

type LineRange struct{ From, To int }

LineRange is a span of lines in a Code block, 1-based and inclusive. A range with To < From, such as the zero value, selects nothing.

type Media added in v0.7.0

type Media struct {
	Path  string // a PNG, or an MP4 if Video
	Video bool   // a clip of the slide building, to play muted and looping, like a GIF
	Alt   string
	W, H  int // in pixels
}

Media is an image or a video of a slide that a Post attaches, rendered from the slide's pixel canvas as the video command draws it.

type PixelArt

type PixelArt struct {
	Rows   []string
	Colors map[rune]RGB
}

PixelArt is one frame of a pixel-art character: each letter in Rows picks a color from Colors; '.' (or any letter without a color) is transparent. Rows must be ASCII. For cell-based art in the character layer, see Sprite.

func (PixelArt) Size

func (a PixelArt) Size() (w, h int)

Size returns the art's size in pixels at scale 1.

type Pixels

type Pixels struct {
	W, H int
	Pix  []RGB
	BG   RGB // the color the canvas was cleared to
	// contains filtered or unexported fields
}

Pixels is a true-color framebuffer. The scene shows it as half blocks: each terminal cell is two stacked pixels, so 240×60 cells is 240×120 pixels. Coordinates are floats, edges are antialiased, and drawing methods blend at opacity a in 0..1.

func NewPixels

func NewPixels(w, h int, bg RGB) *Pixels

NewPixels returns a w×h framebuffer filled with bg.

func (*Pixels) Add

func (p *Pixels) Add(x, y int, c RGB, a float64)

Add brightens pixel (x, y) by c×a, clamped: additive light for glows.

func (*Pixels) Arc

func (p *Pixels) Arc(cx, cy, r, thick, a0, a1 float64, c RGB, alpha float64)

Arc strokes a ring of radius r around (cx, cy) from angle a0 to a1 (radians, 0 = 12 o'clock, clockwise) with round caps; a0=0, a1=2π is a full ring.

func (*Pixels) Art

func (p *Pixels) Art(a PixelArt, x, y, scale, alpha float64, flip bool)

Art draws a at (x, y), scaled by scale (fractions are fine) at opacity alpha, mirrored if flip.

func (*Pixels) At

func (p *Pixels) At(x, y int) RGB

At returns the pixel at (x, y), or black out of bounds.

func (*Pixels) Bezier

func (p *Pixels) Bezier(x0, y0, cx0, cy0, cx1, cy1, x1, y1, width float64, c RGB, a float64, prog float64)

Bezier strokes the cubic curve from (x0, y0) to (x1, y1) with control points (cx0, cy0) and (cx1, cy1), with round caps. prog (0..1) draws it on from the start to that fraction of its length: arc length, to within the accuracy of the flattened curve, so the pen moves at an even speed.

func (*Pixels) Blend

func (p *Pixels) Blend(x, y int, c RGB, a float64)

Blend paints c over pixel (x, y) with opacity a.

func (*Pixels) Box

func (p *Pixels) Box(x0, y0, x1, y1 float64) (int, int, int, int)

Box returns inclusive pixel bounds covering [x0,x1]×[y0,y1], clipped to the canvas.

func (*Pixels) DashedLine

func (p *Pixels) DashedLine(xa, ya, xb, yb, width, dash, gap float64, c RGB, a float64)

DashedLine strokes a line of dashes dash long with gap between them, with round caps, starting with a dash at (xa, ya). It is the dashing PlaceholderBox uses, so the two phase identically.

func (*Pixels) Disc

func (p *Pixels) Disc(cx, cy, r float64, c RGB, a float64)

Disc fills an antialiased circle.

func (*Pixels) Ellipse

func (p *Pixels) Ellipse(cx, cy, rx, ry, stroke float64, c RGB, a float64)

Ellipse fills an antialiased ellipse centered on (cx, cy) with radii rx and ry, or only an outline of thickness stroke, drawn inward from the edge, if stroke > 0. With rx == ry it is Disc, or a ring.

func (*Pixels) Fill

func (p *Pixels) Fill(c RGB)

Fill paints every pixel with c; BG is unchanged.

func (*Pixels) Glow

func (p *Pixels) Glow(cx, cy, r float64, c RGB, a float64)

Glow adds soft light around (cx, cy), fading to zero at radius r.

func (*Pixels) Line

func (p *Pixels) Line(xa, ya, xb, yb, width float64, c RGB, a float64)

Line strokes an antialiased line with round caps.

func (*Pixels) LinearGradient

func (p *Pixels) LinearGradient(x, y, w, h, radius, gx0, gy0, gx1, gy1 float64, from, to RGB, a float64)

LinearGradient fills the rounded rectangle at (x, y), w by h (radius 0 for square corners), with a color that runs from `from` at (gx0, gy0) to `to` at (gx1, gy1) and is constant beyond them.

func (*Pixels) Polygon

func (p *Pixels) Polygon(pts []float64, c RGB, a float64)

Polygon fills the closed shape through pts, x0, y0, x1, y1 and so on, with antialiased edges. The last point joins back to the first. Concave shapes work, and the fill rule is even-odd: a region is inside when a ray from it crosses an odd number of edges, so where the outline crosses itself (a pentagram) the overlap is a hole. Fewer than three points draw nothing.

func (*Pixels) Polyline

func (p *Pixels) Polyline(pts []float64, width float64, c RGB, a float64)

Polyline strokes the path through pts, x0, y0, x1, y1 and so on, with round joins and caps. The whole path is covered once, so at a < 1 the joints show no dark or bright dots where segments overlap. A single point draws a dot.

func (*Pixels) RadialGradient

func (p *Pixels) RadialGradient(x, y, w, h, radius, gx, gy, gr float64, inner, outer RGB, a float64)

RadialGradient fills the rounded rectangle at (x, y), w by h (radius 0 for square corners), with a color that runs from inner at (gx, gy) to outer at distance gr from it and is constant beyond.

func (*Pixels) Rect

func (p *Pixels) Rect(x, y, w, h float64, c RGB, a float64)

Rect fills a rectangle; fractional edges are antialiased by coverage.

func (*Pixels) RoundRect

func (p *Pixels) RoundRect(x, y, w, h, radius, stroke float64, c RGB, a float64)

RoundRect fills a rounded rectangle, or only an outline of thickness stroke if > 0.

func (*Pixels) Set

func (p *Pixels) Set(x, y int, c RGB)

Set overwrites a pixel.

func (*Pixels) VGradient

func (p *Pixels) VGradient(y0, y1 int, top, bottom RGB)

VGradient fills rows y0..y1 with a vertical gradient from top to bottom.

type Post added in v0.7.0

type Post struct {
	// Slide is the slide the post is about, from 0; -1 for a post before
	// the first slide's section, which opens the thread.
	Slide int

	Text string

	// Media is what the post attaches: images, or one video.
	Media []Media
	// contains filtered or unexported fields
}

Post is one post of a live thread, from a post block in Deck.Notes.

type Process

type Process struct {
	Steps     []string
	FirstStep int
}

Process is a row of chevrons, one per step of a procedure, revealed one at a time: step i appears at step FirstStep+i, sliding in from the left. The newest is filled in Accent and earlier ones are outlined in Muted. Text is fitted inside each shape, all at one size.

func (Process) Draw

func (pr Process) Draw(c Ctx, p *Pixels, r Rect) (w, h float64)

Draw renders the row at the top of r, built out to the current step, and returns its size once built: the width of r and a chevron's height.

type Publisher added in v0.7.0

type Publisher interface {
	// Check holds every post the talk can make to the service's limits,
	// without signing in or posting anything: decktest.Posts runs it, and
	// so does a rehearsal, to show what posting for real would stop on. It
	// may look things up, as bsky resolves @mentions, but keeps nothing for
	// later calls.
	Check(ctx context.Context, posts []Post) error

	// SignIn signs in as account, the one the speaker typed into the
	// posting dialog, picking up a session kept from an earlier run if it
	// can, and returns the Session that posts as it. When the speaker must
	// approve it in a browser, SignIn calls visit with the page's address,
	// which the presenter view shows and opens, and waits until they do or
	// ctx ends.
	SignIn(ctx context.Context, account string, visit func(url string)) (Session, error)
}

Publisher posts a talk as a thread while it is given: when the speaker moves on from a slide, the post blocks in its Notes section go out as replies, in order. The presenter view's posting dialog (P) signs in and starts posting. Deck.Publisher sets one; the bsky package has one for Bluesky.

type RGB

type RGB struct{ R, G, B float32 }

RGB is a color with 0..255 float channels, used for fast blending.

func Hex

func Hex(s string) RGB

Hex parses a CSS-style hex color such as "#FFB000" or "#fb0"; unparseable input is black.

func Mix

func Mix(a, b RGB, p float64) RGB

Mix blends two colors: p=0 is a, p=1 is b.

func (RGB) Color

func (c RGB) Color() color.Color

Color returns c as an opaque color.Color, for Lip Gloss.

func (RGB) Scale

func (c RGB) Scale(k float64) RGB

Scale multiplies brightness, clamped to 255.

type Rect

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

Rect is a box on the pixel canvas: X, Y is its top-left corner and W, H its size, all in pixels. Layout is cutting rects out of rects: start from Ctx.Frame or Ctx.Rect, then Inset, Cut, Rows, Cols, Grid or Anchor, and hand the pieces to whatever draws there. Every method returns new rects; widths and heights never go below zero.

head, body := c.Frame().Inset(c.Unit(0.04), c.Unit(0.03)).CutTop(c.Y(0.18))
left, right := body.CutLeft(body.W * 0.4)

func LerpRect

func LerpRect(a, b Rect, p float64) Rect

LerpRect moves a toward b: p=0 is a, p=1 is b.

func NewRect

func NewRect(x, y, w, h float64) Rect

NewRect returns the w×h box with its top-left corner at (x, y), in pixels.

func (Rect) Anchor

func (r Rect) Anchor(w, h, ax, ay float64) Rect

Anchor returns a w×h box inside r, positioned by ax and ay: 0 against r's left or top edge, 0.5 centered, 1 against its right or bottom edge. A box bigger than r overhangs it by the same rule.

func (Rect) Bottom

func (r Rect) Bottom() float64

Bottom returns the y of r's bottom edge.

func (Rect) Center

func (r Rect) Center() (x, y float64)

Center returns the point in the middle of r.

func (Rect) Cols

func (r Rect) Cols(gap float64, weights ...float64) []Rect

Cols splits r left to right into one column per weight, as Rows does.

func (Rect) CutBottom

func (r Rect) CutBottom(h float64) (bottom, rest Rect)

CutBottom splits r into a strip h tall along its bottom and the rest above.

func (Rect) CutLeft

func (r Rect) CutLeft(w float64) (left, rest Rect)

CutLeft splits r into a strip w wide along its left and the rest beside it.

func (Rect) CutRight

func (r Rect) CutRight(w float64) (right, rest Rect)

CutRight splits r into a strip w wide along its right and the rest beside it.

func (Rect) CutTop

func (r Rect) CutTop(h float64) (top, rest Rect)

CutTop splits r into a strip h tall along its top and the rest below it.

func (Rect) Grid

func (r Rect) Grid(cols, rows int, gap float64) []Rect

Grid splits r into cols×rows equal cells with gap pixels between them, in reading order: the cell at column i, row j is at index j*cols+i.

func (Rect) Inset

func (r Rect) Inset(dx, dy float64) Rect

Inset shrinks r by dx on the left and right and dy on the top and bottom; negative values grow it.

func (Rect) Right

func (r Rect) Right() float64

Right returns the x of r's right edge.

func (Rect) Rows

func (r Rect) Rows(gap float64, weights ...float64) []Rect

Rows splits r top to bottom into one row per weight, sized in proportion to the weights, with gap pixels between them. Rows(gap, 1, 1, 1) is three equal rows.

func (Rect) Sub

func (r Rect) Sub(fx, fy, fw, fh float64) Rect

Sub returns the box at (fx, fy) of size fw×fh, all fractions of r.

type Rich

type Rich struct {
	Font    *Font   // required: the base face, and what sets the baseline
	Size    int     // pixels
	Color   RGB     // fill for spans without a Color
	Align   Align   // how x is interpreted
	Leading float64 // line height as a multiple of Size (default 1.1)
	MaxW    float64 // if set, wraps lines to this width

	// Glow is a soft glow strength, 0 (none) to ~1.5, in each span's color.
	Glow float64

	// FX animates glyphs. Indexes count every rune of every span as Text counts
	// them: spaces included, plus one per line break.
	FX GlyphEffect
}

Rich draws a block of raster type whose spans differ in font, color and decoration; like Text it is a value to fill in and Draw. Sizes below 4 pixels are drawn at 4.

A line breaks at "\n" inside a span and, with MaxW set, wraps at spaces across span boundaries (a word may start in one span and end in another). Every glyph on a line sits on the base font's baseline, so a Mono span shares its line with Body text without stepping up or down.

func (Rich) Baseline

func (r Rich) Baseline() float64

Baseline is the distance from the top of a line to its baseline, as Draw lays it out.

func (Rich) Draw

func (r Rich) Draw(p *Pixels, spans []Span, x, y float64) (w, h float64)

Draw renders spans with their top edge at y and returns the block's size.

func (Rich) DrawMid

func (r Rich) DrawMid(p *Pixels, spans []Span, x, cy float64) (w, h float64)

DrawMid draws spans as one line with its ink centered vertically on cy (x as for Draw), as Text.DrawMid does.

func (Rich) Fit

func (r Rich) Fit(spans []Span, w, h float64) Rich

Fit returns r with MaxW set to w and Size set to the largest size, at most r.Size, at which spans wrapped to w fit w×h as Measure measures them, as Text.Fit does for plain text:

r.Fit(spans, w, h).Draw(p, spans, x, y)

Like Text.Fit it only shrinks, and stops at 6px wrapped to w when nothing fits. Results are remembered, so a View can call it every frame; a repeat allocates nothing.

func (Rich) Measure

func (r Rich) Measure(spans []Span) (w, h float64)

Measure returns the size of the block Draw would draw for spans.

type Route

type Route int

Route is the shape of a Connector's path.

const (
	RouteStraight Route = iota // one straight line between the two anchors
	RouteElbow                 // axis-aligned segments with one or two bends
	RouteCurved                // a cubic Bezier leaving and arriving square to the sides
)

The routes a Connector can take.

type Scale added in v0.6.0

type Scale struct {
	Min, Max float64 // the data range shown; NiceScale rounds it out to whole steps
	Step     float64 // the distance between ticks
	From, To float64 // the pixel positions of Min and Max; To < From runs upward, for a y axis
}

Scale maps data values onto a span of pixels: the axis of a visual drawn by hand, like a trace waterfall's milliseconds. It draws nothing. At places a value, Ticks lists round values to mark and Label writes them:

ms := NiceScale(0, trace.Dur, 6, plot.X, plot.Right())
for _, v := range ms.Ticks() {
	x := ms.At(v)
	p.Rect(x, plot.Bottom(), 1, c.Unit(0.01), c.Theme.Faint, 1)
	Text{Font: c.Theme.Body, Size: c.SmallText(c.Theme.Body), Color: c.Theme.Muted, Align: Center}.
		Draw(p, ms.Label(v)+"ms", x, plot.Bottom()+c.Unit(0.015))
}

Its ticks and labels are the charts' gridlines and axis labels, so a hand-drawn axis agrees with a BarChart beside it. A Scale is a plain value: build one by hand, or adjust what NiceScale returns.

func NiceScale added in v0.6.0

func NiceScale(lo, hi float64, maxTicks int, from, to float64) Scale

NiceScale returns a Scale covering lo..hi with about maxTicks ticks at round numbers (1, 2 or 5 times a power of ten), its ends moved out to whole steps, mapped onto the pixels from..to. A range of one value is widened around it. To keep the data's own ends, say a trace that ran 347ms, set Min and Max back afterwards: the ticks stay round.

func (Scale) At added in v0.6.0

func (s Scale) At(v float64) float64

At returns the pixel position of v: From at Min, To at Max, in proportion between and beyond them. A scale with no range puts every value at From.

func (Scale) Label added in v0.6.0

func (s Scale) Label(v float64) string

Label writes a tick value as a chart's axis does: trailing zeros trimmed, large values shortened (12.5k, 3M).

func (Scale) Ticks added in v0.6.0

func (s Scale) Ticks() []float64

Ticks returns the tick values, the multiples of Step from Min to Max, as a chart places its gridlines.

type Scene

type Scene struct {
	W, H int
	Px   *Pixels
	// contains filtered or unexported fields
}

Scene is a fixed-size grid of terminal cells that a slide draws into, layered in drawing order. Off-screen drawing is clipped, so animating in from the edges is safe. Two layers:

  • Px, a pixel framebuffer twice as tall as the scene, for big type, shapes, glows and pixel art.
  • Character cells on top (Put, PutCenter, Overlay, Text, Cell, Fill, Sprite), for small native-terminal text. A cell replaces the two pixels beneath it; with no background of its own it takes their average color.

func NewScene

func NewScene(w, h int, t *Theme) *Scene

NewScene returns an empty w×h-cell scene on the theme's background, for drawing off-screen; a slide draws on the scene View is given.

func (*Scene) Cell

func (s *Scene) Cell(x, y int, ch string, st uv.Style)

Cell sets a single cell; out-of-bounds is ignored.

func (*Scene) Fill

func (s *Scene) Fill(x, y, w, h int, bg color.Color)

Fill paints a rectangle with a background color.

func (*Scene) Overlay

func (s *Scene) Overlay(x, y int, block string)

Overlay is Put with unstyled spaces transparent.

func (*Scene) Place

func (s *Scene) Place(key string, r Rect, draw func(p *Pixels, r Rect))

Place adds an element to the scene: draw paints it into r after View returns, above View's own drawing, in the order placed. draw should size itself from r and stay inside it, give or take a glow, and must be frame-pure like View.

key names the element across slides. When a slide enters with TransitionMorph, an element whose key the slide before it also placed glides from its old rect to its new one, cross-fading between the old draw and the new; elements on only one side fade out or in. An empty key never matches. Keys should be unique within a slide; repeats pair up in order.

func (*Scene) Put

func (s *Scene) Put(x, y int, block string)

Put draws a styled, possibly multi-line block at (x, y). Every cell is opaque, so spaces overwrite what was beneath.

func (*Scene) PutCenter

func (s *Scene) PutCenter(dx, dy int, block string)

PutCenter draws block centered in the scene, shifted by (dx, dy).

func (*Scene) Release

func (s *Scene) Release()

Release returns s to the pool without rendering. Don't use s afterwards or after Render, which releases it.

func (*Scene) Render

func (s *Scene) Render() string

Render returns the scene as exactly H lines of W cells, each with an explicit background, and releases it. Use it on scenes made with NewScene, never on the one a slide's View is given; placed elements are drawn first, but Theme.Overlay is not applied.

func (*Scene) Sprite

func (s *Scene) Sprite(x, y int, sp Sprite)

Sprite draws sp with its top-left corner at (x, y).

func (*Scene) Text

func (s *Scene) Text(x, y int, text string, fg color.Color, attrs ...uint8)

Text draws plain text in one color with transparent spaces; attrs are uv.Attr* bits.

type Session added in v0.7.0

type Session interface {
	// Account names the account in a way that doesn't change, as a handle
	// can: the thread file records it, and a thread posted as one account
	// isn't carried on as another.
	Account() string

	// Open readies posts to go out, as starting to post does: it checks
	// them as Publisher.Check does, and what only the account can answer,
	// such as whether it may upload that many clips today. The Thread it
	// returns posts them, and holds whatever Open worked out for them.
	Open(ctx context.Context, posts []Post) (Thread, error)
}

Session is a Publisher signed in to an account.

type Severity added in v0.6.0

type Severity int

Severity ranks an Issue: how much of what a slide meant to show is missing or worse than intended.

const (
	// SeverityWarning is a slide that shows everything, but worse than
	// intended: text too small to read from the back, two things on top of
	// each other.
	SeverityWarning Severity = iota
	// SeverityError is content lost: lines clipped, rows dropped, text off
	// the canvas, a panic.
	SeverityError
)

func (Severity) String added in v0.6.0

func (s Severity) String() string

String returns "warning" or "error".

type Side

type Side int

Side names a side of a Rect.

const (
	SideAuto Side = iota
	SideLeft
	SideRight
	SideTop
	SideBottom
)

The sides of a Rect. SideAuto, the zero value, lets the Connector choose.

type Slide

type Slide struct {
	// Title is shown in the footer, the window title and the list command.
	Title string

	// Steps is the number of build states: Ctx.Step runs from 0 to Steps-1.
	// For Steps: 2, one "next" reveals step 1 and the next advances to the
	// following slide. Zero means 1.
	Steps int

	// Sources are the works this slide cites. The handout lists them under the
	// slide, and gathers every source once at the end.
	Sources []Source

	// Hold is how many seconds each build step stays on screen in a video.
	// Zero uses the video command's --hold.
	Hold float64

	// Transition is how this slide enters, and for how long
	// (TransitionWipe.Over(0.6)); zero uses DefaultTransition. The ones that
	// move or sweep take a side (TransitionPush.From(DirUp)). Going back to
	// it plays it again, backwards: from the opposite side.
	Transition Transition

	// Section names the chapter this slide belongs to. A slide with no
	// Section inherits the nearest one before it, so only the first slide of
	// a chapter needs to set it. Ctx.Section is the resolved name, and the list
	// command and the presenter view show it.
	Section string

	// Allow lists review issue codes this slide means to have, which
	// Deck.Review then leaves out: "text-offcanvas" for a headline that
	// bleeds off the edge on purpose, say.
	Allow []string

	// HideChrome hides the dev-mode footer on this slide. Theme.Overlay still
	// draws, and presenter notes and previews remain available.
	HideChrome bool

	// View draws one frame onto sc, a Ctx.W x Ctx.H scene already filled
	// with the theme's background; after it returns, the engine draws the
	// elements it placed (Scene.Place), then Theme.Overlay. View must not
	// Render or Release sc. A nil View is a blank
	// slide, and a panic is caught and drawn on screen.
	View func(c Ctx, sc *Scene)
}

Slide is one slide in the deck. View draws a frame and is called once per frame (--fps) with a fresh Ctx, so anything computed from Ctx.T or Ctx.StepT animates. View must be a pure function of Ctx: no time.Now or math/rand (use Hash01 for noise), so snapshots and golden tests can replay any frame.

type Source added in v0.6.0

type Source struct {
	// Label is what is cited, as a reader should see it: "OpenTelemetry
	// Logs Data Model, v1.40".
	Label string

	// URL is where to find it; optional. The handout links Label to it.
	URL string
}

Source is one work a slide cites, in Slide.Sources.

type Span

type Span struct {
	Text      string
	Font      *Font // nil inherits Rich.Font
	Color     *RGB  // nil inherits Rich.Color
	Underline bool  // a rule under the glyphs, in the span's color
	Strike    bool  // a rule through the glyphs, in the span's color
	Mark      *RGB  // a highlighter-pen plate behind the glyphs; also how inline code gets its plate
}

Span is a run of text with its own style inside a Rich block. Its zero style inherits everything from the Rich.

func ParseSpans

func ParseSpans(s string, t *Theme) []Span

ParseSpans turns lightly marked-up text into spans styled from theme t, for Rich. The markup is small on purpose:

*bold*        the theme's Display face, since a theme has no bold cut
_muted_       t.Muted
`code`        t.Mono on a plate; nothing inside is markup
{accent:word} a color tag: text, muted, faint, accent, accent2, warn,
              good, or "#rrggbb" / "#rgb"
{u:word}      underline, in the span's color
{s:word}      strikethrough
{mark:word}   a highlighter pen: Accent plate, Background ink
\*            a backslash makes the next marker character literal; the
              markers are * _ ` { } and \ itself

Markers toggle wherever they appear, so "*bo*ld" is bold inside a word; write snake\_case with a backslash. Tags nest ({accent:a {u:b}}), and the innermost style wins. Inside code, only \` is an escape. A "{" that does not start a known tag is literal along with the "}" that matches it, so braces nest inside a tag ({accent:map{k}}); a stray "}" is literal too. A marker never closed styles to the end. Text is otherwise kept as written, including "\n".

type Sprite

type Sprite struct {
	Art     []string
	Paint   []string
	Colors  map[rune]color.Color
	Default color.Color
	Bold    bool
}

Sprite is multi-line ASCII art with per-character colors; spaces are transparent. Each character of Paint (optional, same shape as Art) selects a color from Colors for the matching Art character; others use Default. For pixel art, see PixelArt.

bee := Sprite{
    Art:    []string{`(o)##>`},
    Paint:  []string{`kkkyyk`},
    Colors: map[rune]color.Color{'k': Hex("#222222").Color(), 'y': theme.Accent.Color()},
}

func (Sprite) Size

func (sp Sprite) Size() (w, h int)

Size returns the sprite's width and height in cells.

type Stat

type Stat struct {
	Value    float64
	Prefix   string  // before the number: "$"
	Suffix   string  // after it: "%", " ms"
	Decimals int     // digits after the point
	Label    string  // what the number is, under it
	Step     int     // the build step it counts up on
	Duration float64 // seconds to count; 0 is 1.2
}

Stat is a big number that counts up from zero to Value when its step begins, with a small Muted label under it. The number is drawn in Display and the theme's Accent at the largest size that fits, with every digit in a cell as wide as the widest, and right-aligned in the width of the final value, so nothing shifts while it counts.

func (Stat) Draw

func (s Stat) Draw(c Ctx, p *Pixels, r Rect) (w, h float64)

Draw renders the stat centered in r and returns the size it drew.

type SyntaxColors

type SyntaxColors struct {
	Keyword RGB // control flow and declarations: if, func, return
	String  RGB // string and character literals
	Comment RGB // comments
	Number  RGB // numeric literals and named constants
	Name    RGB // functions, builtins and decorators
	Type    RGB // type, class and namespace names
	Punct   RGB // operators and punctuation

	Added   RGB // background of a diff's "+" lines
	Removed RGB // background of a diff's "-" lines
}

SyntaxColors is the palette Code colors tokens with, set on Theme.Syntax. Any field left zero falls back to the color derived from the theme's own palette, so a theme can override just the roles it cares about.

type Table

type Table struct {
	Header []string   // optional column titles, in the Display font and the accent color
	Rows   [][]string // body cells, in the Body font

	Weights []float64 // relative column widths; missing entries are 1
	Align   []Align   // per column; missing entries are Left

	Zebra bool // tint every other body row
	Rules bool // thin lines between body rows

	// FirstStep is the step at which the table starts to appear; Reveal says
	// how the rest follows.
	FirstStep int
	Reveal    TableReveal

	// Highlight is the 1-based body row to emphasize with an accent bar while
	// the other rows dim; 0 emphasizes none. It takes effect at once, so to
	// animate a highlight over steps use Walk.
	Highlight int
	// Walk makes the highlight follow the build: step FirstStep+i emphasizes
	// row i+1, gliding down from the row before it, and the last row stays
	// emphasized once the steps run out. Walk overrides Highlight.
	Walk bool
}

Table draws a grid of text: an optional header, rows of cells, columns sized by weight. Every cell is set at one text size, the largest that fits the rect, so the numbers and words on a slide read as one piece.

Rows and cells may be ragged: a missing cell is empty, and the column count is that of the longest row. A cell that cannot fit even at the smallest readable size is ellipsized, never drawn outside its column, and rows that would run past the rect are left out.

func (Table) Draw

func (t Table) Draw(c Ctx, p *Pixels, r Rect) (w, h float64)

Draw renders the table inside r and returns the width and height it occupies, which is all of r's width and as much height as its rows need. The table is laid out the same every frame, so rows that haven't appeared yet still hold their place.

type TableReveal

type TableReveal int

TableReveal is how a Table builds up over steps.

const (
	// TableRevealAll shows the whole table at Table.FirstStep.
	TableRevealAll TableReveal = iota
	// TableRevealRows shows the header, then row i at step FirstStep+i.
	TableRevealRows
	// TableRevealCols shows column j, header cell included, at step
	// FirstStep+j.
	TableRevealCols
)

type Text

type Text struct {
	Font    *Font   // required
	Size    int     // pixels
	Color   RGB     // fill color
	To      *RGB    // if set, a left-to-right gradient from Color to To across the block's width
	Align   Align   // how x is interpreted
	Leading float64 // line height as a multiple of Size (default 1.1)

	Glow      float64 // soft glow strength, 0 (none) to ~1.5
	GlowColor *RGB    // defaults to Color

	// MaxW, if set, wraps lines to this width.
	MaxW float64

	// Shine returns extra brightness (0..1) at horizontal position u, 0 at the
	// block's left edge to 1 at its right; see ShineBand.
	Shine func(u float64) float64

	FX GlyphEffect
}

Text draws a block of raster type with a Font; sizes below 4 pixels are drawn at 4.

func (Text) Baseline

func (t Text) Baseline() float64

Baseline is the distance from the top of a line to its baseline, as Draw lays it out.

func (Text) Draw

func (t Text) Draw(p *Pixels, s string, x, y float64) (w, h float64)

Draw renders s (which may contain "\n") with its top edge at y and returns the block's size. With FX, glyph indexes count every rune of every line, spaces included, plus one per line break.

func (Text) DrawMid

func (t Text) DrawMid(p *Pixels, s string, x, cy float64) (w, h float64)

DrawMid draws one line of s with its ink centered vertically on cy (x as for Draw). Centering the line box instead leaves lowercase text low and descenders poking into borders.

func (Text) Fit added in v0.6.0

func (t Text) Fit(s string, w, h float64) Text

Fit returns t with MaxW set to w and Size set to the largest size, at most t.Size, at which s wrapped to w fits w×h as Measure measures it, so

t.Fit(s, w, h).Draw(p, s, x, y)

draws s as big as the box allows. Set Size to the largest you want: Fit only shrinks. It stops at 6px, wrapped to w, when nothing fits, since running taller beats running off the side of the screen, and a Size below 6, zero included, comes back as 6. Results are remembered, so a View can call it every frame; a repeat allocates nothing.

func (Text) Measure added in v0.6.0

func (t Text) Measure(s string) (w, h float64)

Measure returns the size Draw would return for s, without drawing it: the widest line's width, and the height of its lines once wrapped to MaxW. Lay a block out with it before drawing, to size a plate behind it or center it in a box. It allocates nothing, so a View can call it every frame.

type Theme

type Theme struct {
	Background RGB // painted behind every slide, so the deck looks the same in any terminal
	Text       RGB // default text
	Muted      RGB // secondary text: notes, captions
	Faint      RGB // decoration only: rules, inactive dots
	Accent     RGB // primary highlight: the wipe transition, progress, numbers
	Accent2    RGB // secondary highlight
	Warn       RGB // errors, placeholders
	Good       RGB // positive status, such as the presenter's "on pace"
	Panel      RGB // inset plates and the help box, a shade off Background

	// Series are the colors of chart series, in order, for BarChart, LineChart
	// and DonutChart; see Theme.SeriesColor. Empty uses Accent, Accent2, Good,
	// Warn and Muted.
	Series []RGB

	Display *Font // headlines and numbers
	Body    *Font // supporting lines, labels, chips
	Mono    *Font // code

	// Syntax colors [Code] with; nil derives a palette from the colors above.
	Syntax *SyntaxColors

	// Overlay, if set, draws after every slide into the full canvas p (a TV
	// station's bug), with the slide's Ctx. Slides should leave its corner
	// clear.
	Overlay func(c Ctx, p *Pixels)
}

Theme is the deck's palette and typefaces. The engine paints its chrome (background, footer, help, notes, presenter view, wipe) with the colors; stock components use the fonts. Display, Body and Mono are required.

func (*Theme) SeriesColor

func (t *Theme) SeriesColor(i int) RGB

SeriesColor returns the color of chart series i, cycling through Theme.Series; when that is empty it cycles Accent, Accent2, Good, Warn and Muted. A negative i counts from the end of the cycle.

type Thread added in v0.7.0

type Thread interface {
	// Upload sends p's media ahead of Post, while its slide is up, so the
	// post goes out quickly when the speaker moves on. It runs alongside
	// Post and other Uploads. Post works whatever became of it: it waits
	// for an upload still running, uploads what one didn't, and sends
	// nothing twice.
	Upload(ctx context.Context, p Post) error

	// Post publishes p where d says, and returns a reference to it, which
	// later posts get as their Parent (and Root, if it's the first). A try
	// d marks as a Retry may have gone out already: Post finds it if it
	// did, rather than posting it twice.
	Post(ctx context.Context, p Post, d Delivery) (ref string, err error)
}

Thread posts the posts a Session opened it with.

type Timeline

type Timeline struct {
	Items     []TimelineItem
	FirstStep int
	Vertical  bool // items run down the rect with text beside the line, not across it
}

Timeline is a line with a dot per milestone, built one item per step: item i appears at step FirstStep+i, the line drawing on to its dot, the dot popping and the text fading up. The newest item is in Accent and earlier ones in Muted, as BulletList dims its lines. Text is fitted to its slot, all labels at one size and all details at another.

func (Timeline) Draw

func (t Timeline) Draw(c Ctx, p *Pixels, r Rect) (w, h float64)

Draw renders the timeline in r, built out to the current step, and returns the size of the whole timeline once built: it fills the length of r along the line and takes as much across it as its text needs.

type TimelineItem

type TimelineItem struct {
	Label  string // the milestone's name, in the display font
	Detail string // optional supporting text beneath or beside it
}

TimelineItem is one milestone on a Timeline.

type Transition

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

Transition is how a slide enters: one of the kinds below, taking its default time or the one Over gives it, and the side From gives the ones that have a direction. Transitions are comparable values; the zero Transition is TransitionDefault.

Transition: decker.TransitionMorph.Over(1.2)
Transition: decker.TransitionPush.From(decker.DirDown)

func (Transition) Duration

func (t Transition) Duration() float64

Duration returns how long t takes, in seconds.

func (Transition) From

func (t Transition) From(d Direction) Transition

From returns t starting from side d. Push, Cover and Uncover bring the new slide in from d (Uncover sends the old one out the opposite way), Wipe sweeps from d, and Split opens along d's axis; the other transitions ignore it. DirDefault restores each one's own side (Push, Cover and Uncover from the right, Wipe from the left, Split horizontal), and going back plays t from the opposite side.

func (Transition) Over

func (t Transition) Over(secs float64) Transition

Over returns t taking secs seconds; zero or less restores its default.

type Trim

type Trim struct{ Left, Top, Right, Bottom float64 }

Trim hides the outer fractions of an element's bounds, from each side in: {Right: 0.25} shows the left three quarters. The zero Trim shows it all, and one whose opposite sides add up to 1 or more shows nothing, glow included.

Directories

Path Synopsis
bsky module
Package decktest is the test suite every deck wants: every slide renders at every step, size and moment without panicking, and draws the same frame every time; every build shows all it means to (nothing clipped, dropped or off the canvas); the posts in its notes pass its publisher's checks; reference images catch unintended changes to how it looks; and a per-slide benchmark measures frame time.
Package decktest is the test suite every deck wants: every slide renders at every step, size and moment without panicking, and draws the same frame every time; every build shows all it means to (nothing clipped, dropped or off the canvas); the posts in its notes pass its publisher's checks; reference images catch unintended changes to how it looks; and a per-slide benchmark measures frame time.
examples
hello command
recipes command
Command recipes is a deck of worked recipes for the visuals that carry a talk's explanation, starting with a trace waterfall.
Command recipes is a deck of worked recipes for the visuals that carry a talk's explanation, starting with a trace waterfall.
starter command
Command starter is a talk to copy: a theme, a slide template, three slides, their notes and the tests that keep them right.
Command starter is a talk to copy: a theme, a slide template, three slides, their notes and the tests that keep them right.
internal
visual
Package visual checks rendered frames against reference images kept in the repository, and text against reference files.
Package visual checks rendered frames against reference images kept in the repository, and text against reference files.

Jump to

Keyboard shortcuts

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