Skip to content

Repository files navigation

ttfx

Terminal text effects as a single static binary. Pipe text in, pick an effect:

ls -la | ttfx decrypt
cat banner.txt | ttfx beams
fortune | ttfx --random-effect
git log --oneline -10 | ttfx matrix

the decrypt effect resolving the Omarchy logo

Credit where it's due

This is a port of TerminalTextEffects (TTE) by ChrisBuilds. Every effect, the animation engine, and the command-line interface are their design — this project translates that work to Rust and adds nothing of its own to the art. If you like what you see here, star the original.

TTE is MIT licensed and so is this port; the original copyright is preserved in LICENSE and NOTICE. Please file effect ideas upstream, where they belong.

Why a port

TTE is a Python package. That's the right call for a library, but for a shell toy that lives in your prompt pipeline it means an interpreter, an install step, and ~65 ms of import before the first frame. ttfx is one dependency-free binary that starts in half a millisecond.

That difference is the whole reason this exists. On a fullscreen canvas the heavier effects run out of headroom under Python. Time to render a whole animation, pacing disabled so this measures throughput rather than sleep():

At 200×50 cells frames ttfx Python TTE ttfx fps
slide 375 76 ms 2,203 ms 4,930
beams 732 181 ms 5,564 ms 4,050
rings 1,566 521 ms 10,439 ms 3,004
waves 633 374 ms 8,745 ms 1,693
startup 0.5 ms 64 ms

Across the 35 effects that aren't gated on wall-clock time, the median speedup is 27.5× (range 17.1×–47.4×). The two that are gated — matrix and thunderstorm — spend most of their runtime in a fixed animation duration that no implementation can shorten, so they come in at 1.9× and 1.3×; what ttfx buys there is a far higher frame rate inside that window, not a shorter one.

Reproduce it with python3 tools/tests/bench_full.py, or set TTFX_BENCH_COLS, TTFX_BENCH_LINES and TTFX_BENCH_FILL=1 for the fullscreen numbers above. Both sides run their real user-facing command, best of five.

The effects

All 37, each animating the Omarchy logo. Every frame below came out of the Rust binary — and is byte-identical to what the Python original produces from the same input and seed.

beams
beams
Create beams which travel over the canvas illuminating the characters behind them
binarypath
binarypath
Binary representations of each character move towards the home coordinate of the character
blackhole
blackhole
Characters are consumed by a black hole and explode outwards
bouncyballs
bouncyballs
Characters are bouncy balls falling from the top of the canvas
bubbles
bubbles
Characters are formed into bubbles that float down and pop
burn
burn
Burns vertically in the canvas
colorshift
colorshift
Display a gradient that shifts colors across the terminal
crumble
crumble
Characters lose color and crumble into dust, vacuumed up, and reformed
decrypt
decrypt
Display a movie style decryption effect
errorcorrect
errorcorrect
Some characters start in the wrong position and are corrected in sequence
expand
expand
Expands the text from a single point
fireworks
fireworks
Characters launch and explode like fireworks and fall into place
highlight
highlight
Run a specular highlight across the text
laseretch
laseretch
A laser etches characters onto the terminal
matrix
matrix
Matrix digital rain effect
middleout
middleout
Text expands in a single row or column in the middle of the canvas then out
orbittingvolley
orbittingvolley
Four launchers orbit the canvas firing volleys of characters inward to build the input text from the center out
overflow
overflow
Input text overflows and scrolls the terminal in a random order until eventually appearing ordered
pour
pour
Pours the characters into position from the given direction
print
print
Lines are printed one at a time following a print head. Print head performs line feed, carriage return
rain
rain
Rain characters from the top of the canvas
randomsequence
randomsequence
Prints the input data in a random sequence
rings
rings
Characters are dispersed and form into spinning rings
scattered
scattered
Text is scattered across the canvas and moves into position
slice
slice
Slices the input in half and slides it into place from opposite directions
slide
slide
Slide characters into view from outside the terminal
smoke
smoke
Smoke floods the canvas colorizing any characters it crosses
spotlights
spotlights
Spotlights search the text area, illuminating characters, before converging in the center and expanding
spray
spray
Draws the characters spawning at varying rates from a single point
swarm
swarm
Characters are grouped into swarms and move around the terminal before settling into position
sweep
sweep
Sweep across the canvas to reveal uncolored text, reverse sweep to color the text
synthgrid
synthgrid
Create a grid which fills with characters dissolving into the final text
thunderstorm
thunderstorm
Create a thunderstorm in the terminal
unstable
unstable
Spawn characters jumbled, explode them to the edge of the canvas, then reassemble them in the correct layout
vhstape
vhstape
Lines of characters glitch left and right and lose detail like an old VHS tape
waves
waves
Waves travel across the terminal leaving behind the characters
wipe
wipe
Wipes the text across the terminal to reveal characters

Every effect takes its own options — ttfx <effect> --help. A few of the GIFs above shorten a timed phase so the loop stays watchable (matrix --rain-time 3, thunderstorm --storm-time 3, vhstape --total-glitch-time 250, spotlights --search-duration 80, errorcorrect --error-pairs 0.5); everything else is stock.

Fidelity

This is a parity port, not a reimplementation-in-spirit. Given the same input, config, and random draws, ttfx produces byte-identical frames to the Python original — verified mechanically in CI against a pinned upstream checkout (v0.15.0), not by eyeballing.

Suite Checks What it proves
tools/parity/run_suite.sh 354 every effect's frame stream, byte for byte, across configs and seeds
tools/parity/tty_compare.sh 41 the full terminal byte stream — canvas prep, cursor moves, teardown
tools/tests/cli_corpus.sh 19 exit codes and stdout/stderr routing
cargo test goldens + traces easing/geometry/gradient values and engine state machines

Making that possible meant reproducing upstream's quirks deliberately, not "fixing" them: Python's banker's rounding, gradients built from integer floor division rather than float interpolation, a bezier arc-length approximation that drops its final segment, and looping scenes that report themselves complete on every tick. They're catalogued in plan.md; the places where Python's unordered iteration had to be pinned down are in docs/ordering-inventory.md.

Two deliberate differences. Random number generation is not bit-compatible with CPython — ttfx uses xoshiro256++, so --seed is reproducible within ttfx but won't match Python's Mersenne Twister. (The parity harness swaps a shared PRNG into both sides, which is what makes frame comparison possible at all.) And Python plugin effects aren't supported, since there's no interpreter to load them.

Usage

<producer> | ttfx [terminal options] <effect> [effect options]

ttfx --help                 # all 37 effects and the terminal options
ttfx <effect> --help        # options for one effect
ttfx --random-effect        # surprise me (--include-effects / --exclude-effects to filter)
ttfx --print-completion bash|zsh

Terminal options (canvas size and anchoring, color handling, frame rate, text wrapping) go before the effect name; effect options after it. Option names and defaults match tte, so existing invocations work with the binary name swapped.

Building

cargo build --release
cargo build --release --target x86_64-unknown-linux-musl   # static, ~3.3 MB

Running the parity suites needs python3 and a copy of upstream:

./tools/parity/fetch_reference.sh   # clones TTE at the pinned commit
./tools/parity/run_suite.sh

Upstream is not vendored here — the harness fetches it, because it's their code.

Scope

Linux and macOS. Built for Omarchy originally; nothing targets a specific libc, and CI runs the tests and CLI corpus on both platforms. The byte-exact parity suites stay pinned to Linux/glibc — Apple's libm rounds a few transcendentals a last-ulp differently, which quantization hides in real frames but a bit-exact comparison would surface.

License

MIT — see LICENSE, which carries both this project's copyright and the original TerminalTextEffects copyright, and NOTICE for the attribution in full.

About

Terminal text effects as a single static binary — a parity-exact Rust port of terminaltexteffects

Topics

Resources

Stars

54 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages