shot

package module
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Oct 17, 2026 License: MIT Imports: 24 Imported by: 0

README

shot

Go Reference

tuios.dev/shot renders a terminal screen to a picture. It takes a grid of cells and writes PNG, SVG, HTML, ANSI or plain text, with an optional window frame around it.

  • PNG is drawn in Go, with no browser, no cgo and no external tool.
  • SVG uses real <text> and <rect> elements, so design tools and browsers open it, and the text stays selectable.
  • Box drawing, block elements, braille and powerline glyphs are drawn as shapes, so borders join up in any font.
  • A glyph the font does not have comes from a fallback face. A glyph that no face has draws as a dotted box, so a gap in the picture is never silent.

The mosaic package does the opposite: it draws a picture as cells of block glyphs, the way chafa's symbol mode does.

tuios uses shot for tuios screenshot and the screenshot keys.

Install

go get tuios.dev/shot

The go command reads the import path from tuios.dev. Go 1.25 or later can fetch it directly. With the default module proxy, any Go version can fetch it.

Render an ANSI string to PNG and SVG

shot does not parse escape sequences. A terminal emulator does that, and FromScreen reads its screen. This example uses tuios.dev/vt.

package main

import (
	"log"
	"os"

	"tuios.dev/shot"
	"tuios.dev/vt"
)

func main() {
	// A terminal emulator reads what a program printed.
	term := vt.NewEmulator(46, 6)
	defer term.Close()
	term.Write([]byte("\x1b[35m◆\x1b[0m \x1b[1mmain\x1b[0m \x1b[2m▸\x1b[0m ~/dev/tuios\r\n" +
		"$ go test ./shot/...\r\n" +
		"\x1b[32mok\x1b[0m  tuios.dev/shot         0.012s\r\n" +
		"\x1b[32mok\x1b[0m  tuios.dev/shot/mosaic  0.004s\r\n" +
		"\x1b[44;30m ▤ 2 packages \x1b[0m \x1b[33m╭─╮\x1b[0m \x1b[2m⣿⣶⣤⣀\x1b[0m\r\n$ "))

	// The palette turns the basic and indexed colours into RGB.
	p := shot.XTermPalette()
	p.FG, p.BG = shot.RGB(0xcd, 0xd6, 0xf4), shot.RGB(0x1e, 0x1e, 0x2e)
	p.ANSI[2], p.ANSI[3] = shot.RGB(0xa6, 0xe3, 0xa1), shot.RGB(0xf9, 0xe2, 0xaf)
	p.ANSI[4], p.ANSI[5] = shot.RGB(0x89, 0xb4, 0xfa), shot.RGB(0xcb, 0xa6, 0xf7)

	// FromScreen reads the screen into a grid of resolved cells.
	grid := shot.FromScreen(term, p)

	// BuildFrame dresses the grid as a window on a gradient.
	frame := shot.BuildFrame(shot.FrameSpec{
		Frame: "window", Title: "tuios", Padding: 24, Radius: 10, Shadow: true, Scale: 2,
	}, shot.FrameInputs{Palette: p})

	for _, format := range []shot.Format{shot.FormatPNG, shot.FormatSVG} {
		out, err := shot.Render(format, grid, frame)
		if err != nil {
			log.Fatal(err)
		}
		if err := os.WriteFile("screen."+format.Ext(), out, 0o644); err != nil {
			log.Fatal(err)
		}
	}
}

Build a grid cell by cell

A program that keeps its own screen model fills a grid directly. Each cell holds its text, its colours as RGB and its attributes.

g := shot.NewGrid(20, 1, shot.RGB(0xe5, 0xe5, 0xe5), shot.RGB(0, 0, 0))
for x, r := range "hello, world" {
	g.Cells[0][x].Cluster = string(r)
}
g.Cells[0][0].Bold = true
png, err := shot.Render(shot.FormatPNG, g, nil) // a nil frame is the bare grid

MakeCell resolves one cell of an ultraviolet screen, with its style and link, through a palette.

Fonts

PNG looks for each glyph in this order, and the first face that has the glyph draws it:

  1. Frame.Font, and Frame.BoldFont for a bold cell. Without a font, Go Mono.
  2. Each face in Frame.Fallback, in order.
  3. Go Mono, which shot embeds.

A glyph in no face draws as a dotted box. MissingGlyphs lists those glyphs before you render, so you can find a font for each one and add it:

font, _ := os.ReadFile("GeistMonoNerdFontMono-Regular.otf")
frame.Font = shot.Font{Data: font}
missing, _ := shot.MissingGlyphs(grid, frame) // ◆ ▸ ▤ for Geist Mono
// Find a font that has missing[0], for example with fc-match on Linux:
//   fc-match -f '%{file}' 'GeistMono Nerd Font Mono:charset=25c6'
noto, _ := os.ReadFile("/usr/share/fonts/noto/NotoSansMono-Regular.ttf")
frame.Fallback = append(frame.Fallback, shot.Font{Data: noto})

shot parses a fallback face only when a glyph needs it, and looks up each glyph once per render. A glyph wider than its cell is centred on the cell. tuios fills the fallback list from fontconfig.

SVG and HTML name Frame.FontFamily and let the viewer pick the fonts. With Frame.EmbedFont, they also carry Frame.Font as an @font-face.

Images

A grid can carry pictures, such as the ones a program shows with the kitty graphics protocol or with sixel. Put each one in Grid.Images:

g.Images = append(g.Images, shot.Image{
	Pixels: pic,                       // any image.Image
	Col: 2, Row: 1, Cols: 30, Rows: 8, // the box of cells it fills
	Source: image.Rect(0, 0, 240, 160), // the part of pic in the box
})
  • The image fills its box. Source is the part of Pixels that goes into the box. An empty Source is the whole image.
  • A Source that is larger than Pixels draws nothing past the edge of the image. Use this to keep an image at its own size in a larger box.
  • The part of a box outside the grid is cut off.
  • Below draws the image under the text, as kitty does for a negative z-index. Otherwise the image goes over the text.
  • PNG draws images at the frame's cell size. SVG and HTML embed each image as a PNG data URI. They encode only the part of the image on the grid.
  • ANSI and text output have no images.

shot does not decode a terminal's image data. The caller decodes it and bounds its size.

The API

  • Grid, Cell and NewGrid are the screen. FromScreen and MakeCell make one from an emulator.
  • Palette, XTermPalette and XTerm256 turn basic and indexed colours into RGB.
  • Render writes one Format. RenderPNG, RenderSVG, RenderHTML, RenderANSI and RenderText write one format each. Raster returns the PNG pixels before they are encoded, and Downscale shrinks them.
  • Frame is the dressing: the window bar, the gradient, the padding, the shadow and the fonts. BuildFrame makes one from a FrameSpec in config spellings, such as "window" and "#1e1e2e..#313244".
  • Font, MissingGlyphs and Frame.Fallback choose the faces.
  • Image and Grid.Images put pictures on the grid.

Stability

The module is at v0, so the API can change between minor versions. Its tags have the folder name as a prefix, such as shot/v0.1.0.

  • Settled: Grid, Cell, Palette, Render and the five formats, and FromScreen. These change only for a strong reason.
  • Can still change: the fields of Frame and FrameSpec, and the mosaic package.

The pictures can change between versions. Do not compare PNG bytes in a test. Compare the grid, or the text and ANSI output.

Report a problem in the tuios issue tracker.

Licence

MIT, as tuios. The Go Mono and Go Mono Bold fonts are embedded in the module. They are by Bigelow & Holmes for the Go project, under the licence in fonts/README.

Documentation

Overview

Package shot renders a grid of terminal cells to PNG, SVG, HTML, ANSI or plain text, with an optional frame around it.

A Grid holds resolved cells: each one has its text, its colours as RGB and its attributes. Build one cell by cell with NewGrid, or read a terminal emulator's screen with FromScreen. Render then turns the grid into the bytes of one format. A render is one call from grid to bytes, with no goroutines and no timers.

PNG draws text with Frame.Font, then each face of Frame.Fallback, then the Go Mono it embeds. Box drawing, block elements, braille and powerline glyphs are drawn as shapes, so they join up whatever the font. A glyph no face has draws as a dotted box. MissingGlyphs lists those glyphs, so a caller with a font database can add fallback faces for them.

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	XTermFg = RGB(0xe5, 0xe5, 0xe5)
	XTermBg = RGB(0x00, 0x00, 0x00)
)

XTermFg and XTermBg are the documented no-theme defaults: the xterm reference palette's own.

View Source
var Formats = []string{"png", "svg", "ansi", "html", "txt"}

Formats lists every backend, default first.

Functions

func Downscale

func Downscale(img *image.RGBA, maxW, maxH int) *image.RGBA

Downscale shrinks img to fit maxW by maxH, keeping its proportions. An image already inside the box comes back untouched.

This is a box filter over integer source rectangles: the preview is a picture of text being looked at from across a panel, and the difference between this and a Lanczos kernel there is invisible, while the difference in what it costs is the whole reason the preview arrives at all.

func EncodePNG

func EncodePNG(img *image.RGBA) ([]byte, error)

EncodePNG serialises a raster. BestSpeed because the picture is a screenshot on its way to a file or a terminal, and the seconds a smaller file would save are not seconds anybody spends.

func HasPrivateUse

func HasPrivateUse(g *Grid) bool

HasPrivateUse reports whether the grid holds a glyph from a private use area.

That is what a Nerd Font icon is: a codepoint no standard font carries, put there by the font the terminal draws with. A capture full of them rendered in the embedded fallback face is a capture full of tofu boxes, and this is how the caller knows to say which setting fixes it rather than leaving the user to guess that the empty boxes are about a font at all.

func Hex

func Hex(c Color) string

Hex formats c as #rrggbb. Alpha is dropped: every place this string goes (SVG, HTML, SGR) carries opacity out of band or not at all.

func MissingGlyphs

func MissingGlyphs(g *Grid, f *Frame) ([]rune, error)

MissingGlyphs returns the runes of g that no face of f can draw, in the order they first appear and without repeats: not the font, not a fallback, not the built-in Go Mono. Box drawing, block elements, braille and powerline glyphs are drawn as shapes and are never missing.

A caller with a font database uses it to fill Frame.Fallback: ask the database for a font that covers each missing rune, add the answer, and call MissingGlyphs again to skip the runes that font also covers.

Example

Find the glyphs that no face of a frame has, before the render draws them as boxes. A program with a font database adds a fallback face for each.

package main

import (
	"fmt"

	"tuios.dev/shot"
)

func main() {
	g := shot.NewGrid(8, 1, shot.RGB(0xe5, 0xe5, 0xe5), shot.RGB(0, 0, 0))
	g.Cells[0][0].Cluster = "あ"
	g.Cells[0][0].Width = 2
	g.Cells[0][1].Width = 0
	frame := &shot.Frame{}
	missing, err := shot.MissingGlyphs(g, frame)
	if err != nil {
		panic(err)
	}
	for _, r := range missing {
		// Ask the font database for a face that has r, then:
		//   frame.Fallback = append(frame.Fallback, shot.Font{Data: data})
		fmt.Printf("%U\n", r)
	}
}

func Raster

func Raster(g *Grid, f *Frame) (*image.RGBA, error)

Raster draws the capture and stops there, handing back the pixels.

It is split out of RenderPNG because the preview and the file want the same pixels at different sizes: the file wants them encoded whole, the panel wants them shrunk to the box it will draw them in. Rendering twice cost a full second on a full-screen capture and produced two pictures that had to be kept in step; rendering once and encoding twice cannot drift.

func Render

func Render(format Format, g *Grid, f *Frame) ([]byte, error)

Render renders the grid in the given format. The frame applies to the pixel formats and HTML. ANSI and text have no frame. A nil frame renders the bare grid.

Example

Build a grid cell by cell and render it to PNG. A nil frame renders the bare grid.

package main

import (
	"os"

	"tuios.dev/shot"
)

func main() {
	g := shot.NewGrid(20, 1, shot.RGB(0xe5, 0xe5, 0xe5), shot.RGB(0, 0, 0))
	for x, r := range []rune("hello, world") {
		g.Cells[0][x].Cluster = string(r)
	}
	g.Cells[0][0].Bold = true
	png, err := shot.Render(shot.FormatPNG, g, nil)
	if err != nil {
		panic(err)
	}
	if err := os.WriteFile("hello.png", png, 0o644); err != nil {
		panic(err)
	}
}

func RenderANSI

func RenderANSI(g *Grid) []byte

RenderANSI emits the grid as truecolor SGR text, one line per row, reset at every style change and at each line end so a partial paste cannot bleed style into the host.

func RenderHTML

func RenderHTML(g *Grid, f *Frame) []byte

RenderHTML renders the grid and frame to a standalone HTML document.

func RenderPNG

func RenderPNG(g *Grid, f *Frame) ([]byte, error)

RenderPNG renders the grid and frame to PNG bytes.

func RenderSVG

func RenderSVG(g *Grid, f *Frame) []byte

RenderSVG renders the grid and frame to an SVG document.

func RenderText

func RenderText(g *Grid) []byte

RenderText emits the grid as plain text, styles dropped, lines right-trimmed.

Types

type Cell

type Cell struct {
	// Cluster is the grapheme cluster, empty for a blank cell and for the
	// continuation column of a wide cluster.
	Cluster string
	// Width is the cluster's cell width: 1 or 2. 0 marks the continuation
	// column of a wide cluster, which no backend draws.
	Width uint8

	FG, BG Color
	// BGDefault marks a cell whose background is the grid default, so
	// backends can skip its rect and let the card ground show through.
	BGDefault bool

	Bold, Faint, Italic, Strike bool
	Underline                   Underline
	// Link is the OSC 8 target, kept so SVG and HTML can emit real anchors.
	Link string
}

Cell is one resolved grid cell. Colors are concrete RGB: theme lookup, xterm fallback, reverse video, and the cursor are all folded in before a Cell exists, so every backend renders the same truth without knowing where it came from.

func MakeCell

func MakeCell(content string, width int, style uv.Style, link uv.Link, p *Palette) Cell

MakeCell resolves one emulator cell into a grid cell: colors made concrete, reverse video folded into them, conceal blanked.

func (Cell) SameStyle

func (c Cell) SameStyle(o Cell) bool

SameStyle reports whether two cells can share a text run.

type Color

type Color = color.RGBA

Color is a resolved sRGB color. Alpha is kept because a frame background can be transparent; cell colors are always opaque by the time they land here.

func ParseHex

func ParseHex(s string) (Color, bool)

ParseHex parses #rrggbb or rrggbb. It reports false for anything else.

func RGB

func RGB(r, g, b uint8) Color

RGB builds an opaque Color.

func XTerm256

func XTerm256(i int) Color

XTerm256 is the standard 256-color mapping: xterm's basic 16, then the 6x6x6 cube, then the grayscale ramp. An index outside 0-255 gives the zero Color.

Indexes 16-255 come from the x/ansi table, whose cube and ramp are xterm's. Its first 16 entries follow the darker VGA scheme instead, so those come from xtermBasic.

type ControlsStyle

type ControlsStyle int

ControlsStyle picks the window control marks in the title bar.

const (
	// ControlsMacOS draws the traffic lights, and is what "auto" resolves to.
	//
	// Accent-tinted dots at the traffic lights' size, spacing and position do
	// not read as a quieter variant of the arrangement everyone knows; they
	// read as that arrangement with the colours broken. Red, amber and green are what a window control
	// is, everywhere, so they are not the theme's to tint. See the rule in the
	// corrective spec's section 4: chrome that carries a learned meaning keeps
	// its learned colours.
	ControlsMacOS ControlsStyle = iota
	// ControlsGlyphs draws the user's own glyph-set window marks.
	ControlsGlyphs
	// ControlsNone leaves the bar empty of controls.
	ControlsNone
)

type Font

type Font struct {
	// Data is the whole font file. The renderer reads it and does not keep it
	// past the render.
	Data []byte
	// Index selects a face when Data is a collection (.ttc, .otc). Zero is the
	// first face, and the only one of a plain font file.
	Index int
}

Font is one face of a font file: TrueType, OpenType, or a collection of either. The PNG backend rasterizes with it. SVG and HTML embed it only when Frame.EmbedFont allows that.

type Format

type Format string

Format is an output backend.

const (
	FormatPNG  Format = "png"
	FormatSVG  Format = "svg"
	FormatANSI Format = "ansi"
	FormatHTML Format = "html"
	FormatText Format = "txt"
)

func ParseFormat

func ParseFormat(s string) (Format, bool)

ParseFormat resolves a format name, accepting the extension spellings.

func (Format) Ext

func (f Format) Ext() string

Ext is the file extension for a format, without the dot.

func (Format) MediaType

func (f Format) MediaType() string

MediaType is the MIME type of a format's output.

type Frame

type Frame struct {
	Mode    FrameMode
	Padding int // px around the card, at scale 1
	Radius  int // card corner radius, px at scale 1
	Shadow  bool

	// Transparent drops the wash entirely: PNG alpha, SVG nothing.
	Transparent bool
	// WashStart and WashEnd are the diagonal gradient stops. Equal values
	// give a solid fill.
	WashStart, WashEnd Color

	Controls ControlsStyle
	// Title is the window display name for the title bar.
	Title string
	// Accent tints the dots and the title, derived from the theme.
	Accent Color
	// CloseGlyph, MinimizeGlyph, MaximizeGlyph are the glyph-set marks for
	// ControlsGlyphs.
	CloseGlyph, MinimizeGlyph, MaximizeGlyph string

	// FontFamily is the CSS font stack for SVG and HTML.
	FontFamily string
	// Font is the font PNG rasterizes with, and the one SVG and HTML inline
	// as an @font-face when EmbedFont is set. No Data means reference-only
	// SVG and HTML and the embedded Go Mono for PNG.
	Font Font
	// BoldFont is the bold cut of Font. No Data double-strikes bold cells.
	BoldFont Font
	// Fallback lists the faces PNG looks in, in order, for a glyph Font does
	// not have. Go Mono comes after them. MissingGlyphs says which runes
	// still need a face.
	Fallback []Font
	// EmbedFont allows SVG and HTML to inline Font as an @font-face. Only
	// a font the user named themselves earns that: embedding turns a kilobyte
	// of SVG into megabytes, and a font tuios found by asking the terminal was
	// found for the raster, not for the export.
	EmbedFont bool
	// Scale multiplies the PNG raster size, 1 to 4.
	Scale int

	// CellAspect is one cell of the terminal being pictured, width over
	// height. Zero leaves the raster on the font's own cell, which is what a
	// render with no terminal to ask has to do.
	CellAspect float64
}

Frame is the fully resolved dressing around a grid. Every value is concrete by the time it lands here: config parsing, theme derivation, and the auto keywords are the caller's job, so the renderer has no defaults of its own to disagree with the settings panel about.

func BuildFrame

func BuildFrame(spec FrameSpec, in FrameInputs) *Frame

BuildFrame resolves a spec against its inputs into a concrete Frame.

Example

Dress a grid as a window, with the gradient and the controls in the palette's colours.

package main

import (
	"fmt"

	"tuios.dev/shot"
)

func main() {
	p := shot.XTermPalette()
	g := shot.NewGrid(40, 10, p.FG, p.BG)
	frame := shot.BuildFrame(shot.FrameSpec{
		Frame:   "window",
		Title:   "build",
		Padding: 24,
		Radius:  10,
		Shadow:  true,
		Scale:   2,
	}, shot.FrameInputs{Palette: p})
	svg, err := shot.Render(shot.FormatSVG, g, frame)
	if err != nil {
		panic(err)
	}
	fmt.Println(len(svg) > 0)
}

type FrameInputs

type FrameInputs struct {
	Palette *Palette
	// Accents seed the auto wash, best first. Empty falls back to the
	// palette's blue and magenta.
	Accents []Color
	// Close, Minimize, Maximize are the glyph-set marks for controls glyphs.
	Close, Minimize, Maximize string
	// CellAspect is one cell of the terminal being pictured, width over
	// height. Zero when nothing asked the terminal, which is every render off a
	// daemon or the CLI, and then the font's own cell stands.
	CellAspect float64
}

FrameInputs is what the environment contributes: the resolved palette, accent candidates for the wash and controls, and the glyph-set window marks.

type FrameMode

type FrameMode int

FrameMode says how much dressing goes around the grid.

const (
	// FrameNone renders the bare grid on its own background.
	FrameNone FrameMode = iota
	// FramePlain renders the padded card without a title bar.
	FramePlain
	// FrameWindow adds the title bar and window controls.
	FrameWindow
)

type FrameSpec

type FrameSpec struct {
	Frame      string // window, plain, none
	Background string // auto, none, hex, hex..hex
	Padding    int
	Radius     int
	Shadow     bool
	Controls   string // auto, macos, glyphs, none
	Title      string
	FontFamily string
	// Font is the font to rasterize with. No Data means Go Mono.
	Font Font
	// BoldFont is the bold cut of Font, when the resolver found one. Without
	// it a bold cell is double-struck, which thickens a glyph without making
	// it the bold letterform the terminal drew.
	BoldFont Font
	// Fallback is the list of faces for glyphs Font does not have.
	Fallback []Font
	// EmbedFont says the font may be written into SVG and HTML output as an
	// @font-face. It is separate from Font because the two questions are
	// different: PNG has to rasterize with a real file, and a file found for
	// it by asking the terminal is not a file the user asked to have three
	// megabytes of copied into every SVG they export.
	EmbedFont bool
	Scale     int
}

FrameSpec is the raw configuration of a frame, before theme derivation. Strings use the config spellings so config and CLI hand them over as-is.

type Grid

type Grid struct {
	Cols, Rows int
	// Cells is Rows rows of Cols cells each.
	Cells [][]Cell
	// FG and BG are the resolved default colors, used for the card ground
	// and for any styling derived from the content.
	FG, BG Color
	// Images are the pictures on the screen, drawn in order: a later image
	// in the same layer goes over an earlier one. See Image.
	Images []Image
}

Grid is a captured screen region, fully resolved.

func FromScreen

func FromScreen(s Screen, p *Palette) *Grid

FromScreen resolves every cell of s into a new grid. p supplies the colours for the default, basic and indexed colours. A nil p is XTermPalette.

func NewGrid

func NewGrid(cols, rows int, fg, bg Color) *Grid

NewGrid allocates a blank grid with every cell on the default colors.

func (*Grid) ReverseCursor

func (g *Grid) ReverseCursor(x, y int)

ReverseCursor folds the cursor into the grid by swapping the cell's colors, the way a block cursor reads.

type Image added in v0.2.0

type Image struct {
	// Pixels is the decoded picture.
	Pixels image.Image
	// Source is the rectangle of Pixels that fills the box. An empty Source
	// is all of Pixels. A Source that reaches past the edges of Pixels draws
	// nothing there, which is how a picture smaller than its box keeps its
	// own size: kitty draws an image at its own pixel size from the top-left
	// corner of its first cell.
	Source image.Rectangle
	// Col and Row are the cell at the top-left corner of the box. They can
	// be outside the grid.
	Col, Row int
	// Cols and Rows are the size of the box in cells.
	Cols, Rows int
	// Below draws the picture under the text and over the cell backgrounds,
	// which is what kitty does with a negative z-index. Otherwise the picture
	// goes over the text.
	Below bool
}

Image is a picture drawn on the grid, such as one a program shows with the kitty graphics protocol or with sixel.

The picture fills a box of cells. Source is the part of Pixels that goes into the box, scaled to fit it. Part of the box outside the grid is cut off, so a picture that scrolled half off the screen draws only the half that is on it.

PNG, SVG and HTML draw images. ANSI and text output have no way to show a picture, so they leave images out.

type Palette

type Palette struct {
	// FG and BG are the default foreground and background.
	FG, BG Color
	// ANSI is the basic 16.
	ANSI [16]Color
	// Indexed overrides 256-color lookups (an emulator's OSC 4 state);
	// nil falls through to ANSI and the xterm cube.
	Indexed func(i int) (Color, bool)
}

Palette resolves preserved color kinds to concrete RGB.

func XTermPalette

func XTermPalette() *Palette

XTermPalette is the guess a render falls back to when no theme is set.

func (*Palette) Resolve

func (p *Palette) Resolve(c color.Color, def Color) Color

Resolve turns any color the emulator can hold into concrete RGB. def is what nil (no color set) means for this position.

type Screen

type Screen interface {
	Width() int
	Height() int
	// CellAt returns the cell at column x, row y, or nil for a blank cell.
	CellAt(x, y int) *uv.Cell
}

Screen is a terminal screen that FromScreen can read. The emulators of tuios.dev/vt satisfy it.

type Underline

type Underline uint8

Underline styles, matching the SGR 4:n space the emulator preserves.

const (
	UnderlineNone Underline = iota
	UnderlineSingle
	UnderlineDouble
	UnderlineCurly
	UnderlineDotted
	UnderlineDashed
)

Directories

Path Synopsis
Package mosaic draws a picture with text: each terminal cell becomes one block glyph with a foreground and a background colour, the way chafa's symbol mode does.
Package mosaic draws a picture with text: each terminal cell becomes one block glyph with a foreground and a background colour, the way chafa's symbol mode does.

Jump to

Keyboard shortcuts

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