Look up
Frames and terminals
The charset and color ladders, what detection reads, the overrides, and a live explorer.
How a chart meets a terminal: the charset and color ladders, what detection reads, and the overrides. The argument is Degradation is the contract. This file is the mechanics.
The charset ladder#
Octants — 2×4 blocks, Unicode 16
Sextants — 2×3 blocks, Unicode 13
Braille — 2×4 dots
Quadrants — 2×2 blocks, the UTF-8 default
Half blocks — 1×2
ASCII — 1×1, the guaranteed fallback
A charset is the glyph tier the subpixel surface encodes through. Glyph tables are data, not code.
| charset | subpixels per cell | standing |
|---|---|---|
Octants | 2×4 solid blocks | Unicode 16; densest ink, explicit opt-in |
Sextants | 2×3 solid blocks | Unicode 13; explicit opt-in |
Braille | 2×4 dots | dense opt-in; dots, not blocks |
Quadrants | 2×2 solid blocks | the conservative UTF-8 default |
HalfBlocks | 1×2 | the lowest Unicode rung |
Ascii | 1×1 | the guaranteed fallback |
The dense tiers are opt-in. No environment variable can prove the configured
font covers them. A terminal name is not a font. Frame::detect picks
quadrants in any UTF-8 environment, and ASCII otherwise. Octants, sextants,
and braille are choices you make for fonts you know.
The gallery's charsets example renders one curve at every rung:
cargo run --example charsets.
The color ladder#
TrueColor — 24-bit RGB
a heatmap and a line
── a line
10.0 ┤████████████████████████████████████████████████ █ 8
│████████████████████████████████████████████████ █
7.5 ┤████████████████████████████████████████████████ █ 6
│████████████████████████████████████████████████ ▓
│████████████████████████████████████████████████ ▓ 4
5.0 ┤▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ ▒
│▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ ▒ 2
2.5 ┤▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ ░
│▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ ░ 0
0.0 ┤▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ ░
└┬────────────┬─────────────┬────────────┬────────────┬
0 10 20 30 40Ansi256 — the xterm 256-color palette
a heatmap and a line
── a line
10.0 ┤████████████████████████████████████████████████ █ 8
│████████████████████████████████████████████████ █
7.5 ┤████████████████████████████████████████████████ █ 6
│████████████████████████████████████████████████ ▓
│████████████████████████████████████████████████ ▓ 4
5.0 ┤▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ ▒
│▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ ▒ 2
2.5 ┤▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ ░
│▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ ░ 0
0.0 ┤▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ ░
└┬────────────┬─────────────┬────────────┬────────────┬
0 10 20 30 40Ansi16 — the sixteen named colors, picked in OKLab
a heatmap and a line
── a line
10.0 ┤████████████████████████████████████████████████ █ 8
│████████████████████████████████████████████████ █
7.5 ┤████████████████████████████████████████████████ █ 6
│████████████████████████████████████████████████ ▓
│████████████████████████████████████████████████ ▓ 4
5.0 ┤▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ ▒
│▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ ▒ 2
2.5 ┤▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ ░
│▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ ░ 0
0.0 ┤▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ ░
└┬────────────┬─────────────┬────────────┬────────────┬
0 10 20 30 40Plain — no escape byte at all
a heatmap and a line
── a line
10.0 ┤░░░░░░░░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒░░░░░░░░░░░░░ █ 8
│░░░░░░░▒░░░░░░░░▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▒▒▒▒▒▒░░░░░░░░░░░ █
7.5 ┤░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▒░░░░░░░░░ █ 6
│░░░░▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓███████████▓▓▓▓▒▒▒░░░░░░░░░ ▓
│░░░░▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓█████████████▓▓▓▒▒▒░░░░░░░░░ ▓ 4
5.0 ┤░░░░▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓███████████▓▓▓▓▒▒▒░░░░░░░░░ ▒
│░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▒░░░░░░░░░ ▒ 2
2.5 ┤░░░░░░░▒░░░░░░░░▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▒▒▒▒▒▒░░░░░░░░░░░ ░
│░░░░░░░░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒░░░░░░░░░░░░░ ░ 0
0.0 ┤░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒▒▒▒░░░░░░░░░░░░░░░ ░
└┬────────────┬─────────────┬────────────┬────────────┬
0 10 20 30 40Loading the engine…
Four tiers, quantized honestly downhill: TrueColor, Ansi256, Ansi16,
Plain. The sixteen-color pick is made in OKLab, by lightness and hue, the
two things sixteen colors can carry. A teal drops to green, never to the
grey nearer in RGB. Heatmap half-blocks carry independent upper and lower
colors. Plain output keeps an averaged shade on the charset's own ramp
(░▒▓█, or .:#@ in ASCII). In colorless output, color_by categories
cycle portable marker shapes (•, +, x, *, o), so groups never
vanish in a pipe.
What detection reads#
Frame::detect sniffs. It never writes to the terminal. The rules, in
order:
- Charset.
MALEVICH_CHARSETwins if set to a known name.TERM=dumborunknownmeans ASCII. Then the locale, POSIX precedence:LC_ALL,LC_CTYPE,LANG. A locale withoututfmeans ASCII. Otherwise quadrants. - Color.
NO_COLOR(any value) means plain. Output that is not a terminal means plain, unlessCLICOLOR_FORCEorFORCE_COLORis set and not0.TERM=dumborunknownmeans plain.COLORTERM=truecoloror24bit, or aTERMending in-direct, means truecolor. ATERMstarting withscreencaps at 256, since the multiplexer re-encodes what passes through it. ATERMcontaining256colormeans 256. Otherwise 16. - Size. The terminal's reported size. The plot takes a third of the
height. No terminal:
COLUMNSandLINESwhen a shell exports them, else 80×16. - Theme.
COLORFGBGdistinguishes dark from light backgrounds.
A pipe is clean plain text by default. Detection sees a non-terminal and drops color, and the charset never emits anything a file cannot hold.
Overrides#
MALEVICH_CHARSET—ascii,half,quad,sextants,octants,braille, orauto.MALEVICH_GRAPHICS—kitty,sixel,iterm2, ornone: the pixel protocol, outranking the sniff and skipping the probe (featurepixel).NO_COLOR— force plain output (no-color.org).CLICOLOR_FORCEorFORCE_COLOR— keep color when piping.COLUMNS/LINES— size a render that has no terminal to measure.- An explicit
Frame— set in code. It consults nothing.
Text discipline#
- CJK labels are measured in display cells and stay aligned.
- Combining marks are deliberately dropped at the cell grid.
- Control characters are dropped at the cell grid. A title, label, or category carrying escape bytes can never smuggle them into any encoder's output. The only escapes in ANSI output are the encoder's own SGR sequences. A regression test pins this.
NaNis always a visible gap, never interpolated away.
Small frames#
Loading the engine…
When the frame shrinks, furniture sheds before data: legend, then titles,
then tick density. The data region is the last thing standing, and
TERM=dumb at any width still gets a correct chart.