omacom/ttfx

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

Rust

322

347 commits

updated Sep 28, 2026

See the code

See what people are saying

SourceMessageScoreDate

Ttfx: Add an x86-64 assembly engine: 9.8x faster than Rust, 322x than Python

This is complete slop. It doesn't matter how highly optimised it is, it is slop. And DHH doesn't even know what he is doing. In this PR he now removed portability (and adding it back after he did the first pass of assembly and someone told him the mistake) The end result in assembly isn't even…

0

Sep 28, 2026

Ttfx: Add an x86-64 assembly engine: 9.8x faster than Rust, 322x than Python

3

Sep 28, 2026

README

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 under a millisecond.

That difference is the whole reason this exists, and it compounds: once running, ttfx renders every effect hundreds of times faster than Python TTE. Time to render each whole animation at 200×50 cells (46 lines of 190 characters), pacing disabled so this measures throughput rather than sleep():

EffectFramesPython TTEttfxFaster
beams7547,193 ms8.2 ms876×
binarypath2,00319,489 ms57.3 ms340×
blackhole1,81411,714 ms39.8 ms294×
bouncyballs9,1698,665 ms19.7 ms439×
bubbles12,20712,991 ms25.6 ms506×
burn3,1788,276 ms11.1 ms746×
colorshift5286,614 ms13.7 ms482×
crumble1,9588,343 ms34.3 ms243×
decrypt5,50610,134 ms11.7 ms868×
errorcorrect5,2527,232 ms11.5 ms626×
expand3143,651 ms11.9 ms306×
fireworks1,50316,502 ms42.4 ms389×
highlight1291,418 ms2.5 ms559×
laseretch14,49018,051 ms26.4 ms683×
matrix ¹2,315———
middleout2452,549 ms7.1 ms360×
orbittingvolley1,1691,967 ms10.6 ms186×
overflow3132,723 ms9.9 ms276×
pour7,2526,783 ms12.0 ms567×
print10,0658,020 ms5.8 ms1,387×
rain4,8535,166 ms13.4 ms384×
randomsequence2071,223 ms2.8 ms443×
rings1,58013,003 ms73.8 ms176×
scattered4384,055 ms15.1 ms269×
slice4002,703 ms5.8 ms464×
slide3752,643 ms8.3 ms317×
smoke6454,016 ms5.8 ms694×
spotlights8329,848 ms27.7 ms355×
spray7183,364 ms19.4 ms174×
swarm5,24216,675 ms69.8 ms239×
sweep2201,663 ms3.3 ms508×
synthgrid6872,602 ms5.2 ms497×
thunderstorm ¹1,083———
unstable5525,014 ms22.4 ms223×
vhstape7276,872 ms17.8 ms387×
waves63510,656 ms11.7 ms914×
wipe1381,346 ms2.4 ms552×

ttfx is 423× faster than Python TTE (geometric mean over the 35 effects that run to completion; median 439×, range 174×–1,387×). Starting up to draw a single character takes 0.8 ms against 66 ms.

¹ matrix and thunderstorm run for a fixed wall-clock duration, so Python and ttfx finish at the same moment; what ttfx buys there is a far higher frame rate inside that window.

Measured on an AMD Ryzen 9 9955HX with ttfx 0.5.0 against TerminalTextEffects 0.15.0 on CPython 3.14.7, both pinned to two cores, output to /dev/null: ttfx best of five runs, Python best of two. Reproduce it with tools/fx/speed.py --python (see tools/fx/speed.py --help for installing Python TTE).

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.

SuiteChecksWhat it proves
tools/parity/run_suite.sh354every effect's frame stream, byte for byte, across configs and seeds
tools/parity/tty_compare.sh41the full terminal byte stream — canvas prep, cursor moves, teardown
tools/tests/cli_corpus.sh19exit codes and stdout/stderr routing
tools/tests/*_behavior.pyptywhat only a real terminal shows: resize restarts, signal teardown
cargo testgoldens + traceseasing/geometry/gradient values and engine state machines

./bin/test runs the lot, which is all CI does.

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

./bin/test runs every suite. It needs python3, and the parity half needs a copy of upstream, which it clones at the pinned commit on first run:

./tools/parity/fetch_reference.sh   # what bin/test calls; safe to run by hand

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.

animation
ascii-art
cli
rust
terminal

Significant stargazers

Tommy Falkowski

57 followers · starred Aug 2026

Moinul Moin

231 followers · starred Aug 2026

Igor Shubovych

101 followers · starred Aug 2026

omacom/ttfx

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

Rust

322

347 commits

updated Sep 28, 2026

See the code

See what people are saying

SourceMessageScoreDate

Ttfx: Add an x86-64 assembly engine: 9.8x faster than Rust, 322x than Python

This is complete slop. It doesn't matter how highly optimised it is, it is slop. And DHH doesn't even know what he is doing. In this PR he now removed portability (and adding it back after he did the first pass of assembly and someone told him the mistake) The end result in assembly isn't even…

0

Sep 28, 2026

Ttfx: Add an x86-64 assembly engine: 9.8x faster than Rust, 322x than Python

3

Sep 28, 2026

README

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 under a millisecond.

That difference is the whole reason this exists, and it compounds: once running, ttfx renders every effect hundreds of times faster than Python TTE. Time to render each whole animation at 200×50 cells (46 lines of 190 characters), pacing disabled so this measures throughput rather than sleep():

EffectFramesPython TTEttfxFaster
beams7547,193 ms8.2 ms876×
binarypath2,00319,489 ms57.3 ms340×
blackhole1,81411,714 ms39.8 ms294×
bouncyballs9,1698,665 ms19.7 ms439×
bubbles12,20712,991 ms25.6 ms506×
burn3,1788,276 ms11.1 ms746×
colorshift5286,614 ms13.7 ms482×
crumble1,9588,343 ms34.3 ms243×
decrypt5,50610,134 ms11.7 ms868×
errorcorrect5,2527,232 ms11.5 ms626×
expand3143,651 ms11.9 ms306×
fireworks1,50316,502 ms42.4 ms389×
highlight1291,418 ms2.5 ms559×
laseretch14,49018,051 ms26.4 ms683×
matrix ¹2,315———
middleout2452,549 ms7.1 ms360×
orbittingvolley1,1691,967 ms10.6 ms186×
overflow3132,723 ms9.9 ms276×
pour7,2526,783 ms12.0 ms567×
print10,0658,020 ms5.8 ms1,387×
rain4,8535,166 ms13.4 ms384×
randomsequence2071,223 ms2.8 ms443×
rings1,58013,003 ms73.8 ms176×
scattered4384,055 ms15.1 ms269×
slice4002,703 ms5.8 ms464×
slide3752,643 ms8.3 ms317×
smoke6454,016 ms5.8 ms694×
spotlights8329,848 ms27.7 ms355×
spray7183,364 ms19.4 ms174×
swarm5,24216,675 ms69.8 ms239×
sweep2201,663 ms3.3 ms508×
synthgrid6872,602 ms5.2 ms497×
thunderstorm ¹1,083———
unstable5525,014 ms22.4 ms223×
vhstape7276,872 ms17.8 ms387×
waves63510,656 ms11.7 ms914×
wipe1381,346 ms2.4 ms552×

ttfx is 423× faster than Python TTE (geometric mean over the 35 effects that run to completion; median 439×, range 174×–1,387×). Starting up to draw a single character takes 0.8 ms against 66 ms.

¹ matrix and thunderstorm run for a fixed wall-clock duration, so Python and ttfx finish at the same moment; what ttfx buys there is a far higher frame rate inside that window.

Measured on an AMD Ryzen 9 9955HX with ttfx 0.5.0 against TerminalTextEffects 0.15.0 on CPython 3.14.7, both pinned to two cores, output to /dev/null: ttfx best of five runs, Python best of two. Reproduce it with tools/fx/speed.py --python (see tools/fx/speed.py --help for installing Python TTE).

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.

SuiteChecksWhat it proves
tools/parity/run_suite.sh354every effect's frame stream, byte for byte, across configs and seeds
tools/parity/tty_compare.sh41the full terminal byte stream — canvas prep, cursor moves, teardown
tools/tests/cli_corpus.sh19exit codes and stdout/stderr routing
tools/tests/*_behavior.pyptywhat only a real terminal shows: resize restarts, signal teardown
cargo testgoldens + traceseasing/geometry/gradient values and engine state machines

./bin/test runs the lot, which is all CI does.

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

./bin/test runs every suite. It needs python3, and the parity half needs a copy of upstream, which it clones at the pinned commit on first run:

./tools/parity/fetch_reference.sh   # what bin/test calls; safe to run by hand

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.

animation
ascii-art
cli
rust
terminal

Significant stargazers

Tommy Falkowski

57 followers · starred Aug 2026

Moinul Moin

231 followers · starred Aug 2026

Igor Shubovych

101 followers · starred Aug 2026

Languages

Rust

93.8%

Python

4.6%

Shell

1.6%