Decode CEEFAX-era Level 1 broadcast teletext into a reusable 25×40 cell grid, then render it with ordinary ANSI terminal colours.
let page = ttx42::Page::from_raw(&bytes)?;
let grid = ttx42::decode(&page, &ttx42::DecodeOptions::default());
print!("{}", ttx42::to_ansi(&grid, &ttx42::AnsiOptions::default()));For a complete carousel, Service::parse_tti retains ordered pages,
subpages, Fasttext links and vendor records, and Service::to_tti writes a
canonical CRLF TTI document. Graphical consumers can use present instead of
parsing ANSI; it returns glyph spans plus colour, flash and conceal metadata.
The authoring-side compile_visual_row converts styled cells into genuine
Level 1 control-code rows and reports every display cell it had to sacrifice.
The ttx42 command reads raw 960/1000-byte pages, MRG .tti files and
42-byte packet .t42 recoveries. It reads stdin when no file (or -) is
given:
ttx42 page.tti
ttx42 recovery.t42 --page 100 --subpage 1 --reveal
ttx42 page.bin --separated braille
ttx42 page.bin --narrow
For T42 input containing multiple transmissions, running without --page or
--subpage lists one line per recovered page and subpage, sorted by identity.
The columns are hexadecimal page, hexadecimal subpage and decimal transmission
count. Either selector switches to rendering;
selectors may be combined and their values are hexadecimal.
Raw buffers have no page number, so --page cannot match them. Omit that
selector when rendering a raw file.
When multiple pages or transmissions match, the CLI renders the one with the
most visible non-space decoded cells; ties select the last match in input
order. This also applies to multi-page TTI files. Transmissions are not merged.
Use --format raw|tti|t42 to override automatic format detection.
Separated mosaics default to widely-supported braille. Its bottom dot row is repeated to give the 2×3 teletext mosaic a better terminal aspect ratio. Contiguous legacy sextants and Unicode 16 separated sextants are selectable. Double height and flash remain explicit cell metadata so graphical consumers can render or animate them properly.
Terminal output is 80 columns wide by default. Double-height text uses a
two-row 4×8 octant font derived from the original Mullard SAA5050 character
ROM and generated by
sn8kfonts; use --narrow for a
compact 40-column fallback. The default wide double-height rendering requires
a font with Unicode 16 octants, even when separated mosaics use braille.
The braille default only avoids that requirement for separated mosaics.
Alignment assumes ambiguous-width Unicode characters occupy one terminal column. Arrows, fractions, division, the double bar, the em dash and the solid square are emitted with a padding space in wide mode; terminals configured to display those characters as two columns can shift the rest of the row. Use your terminal's single-column setting for ambiguous characters. Narrow mode also depends on that setting.
Run cargo run --example specimen -- --wide for a small built-in page, or
render a recovery with ttx42 recovery.t42 --page 188 --wide.
Run ./corpus/fetch.sh to download a public recovery sample (about 23 MB),
then cargo test --test corpus -- --ignored to run the corpus smoke test.
The fetch script is tracked; downloaded samples are gitignored. Fetching
requires curl and an internet connection.
The dormant Rust teletext decoder and
its ttxcat CLI by Vilcans are prior art. For browser-based authoring see
edit.tf and the ZXNet teletext editor.
This release targets UK Level 1 pages. Level 1.5/2.5/3.5 enhancements and additional national subsets are intentionally out of scope.
The repository pins Rust 1.98.0, clippy and rustfmt in rust-toolchain.toml.
With rustup, ordinary cargo commands use that version here. Warnings are
errors, including with plain cargo clippy.
Run just check for the complete CI suite: formatting, Clippy across all
targets, tests, doctests, documentation, Rust 1.88 compatibility and release
recipe tests. It requires Python 3.11 or newer. CI and just release use the
same scripts/check.sh; it selects the pinned toolchain explicitly and installs
missing toolchains through rustup. Update the pin deliberately to adopt a
new Rust release and its lint changes.
Maintainers need just, rustup, Python 3.11 or newer, Bash and Git.
From a clean working tree, just release runs the checks, bumps the patch
version, commits and tags it, then pushes the branch and tag. The tag triggers
the release workflow, which validates and publishes to crates.io using the
configured release credentials.
WTFPL with an additional clause; see LICENSE. Font attribution and license terms are in THIRD_PARTY_LICENSES.md.
