memory-safe core · Rust → WASM

The terminal core,
rewritten in Rust.

A from-scratch VT100/xterm engine compiled to WebAssembly, with Canvas2D and WebGL renderers built in. Faster parsing, a smaller bundle, and an untrusted-byte parser that is memory-safe by construction.

1.4–4.4×faster parse vs xterm.js
73 KBgzip, both renderers + all features
0 depsno runtime dependencies
ferroterm — live
WebAssembly demo unavailable here.
Build the module with ./build.sh and serve over HTTP, or open the hosted live demo.
what's inside

A real terminal, not a textarea

Everything a browser terminal needs, built in — no addon zoo to assemble.

VT / ANSI

From-scratch parser

DEC (Williams) state machine: CSI/OSC/DCS, SGR with 256-color and 24-bit true color, scroll regions, alternate screen, DECSET modes, host replies.

Unicode

Grapheme-aware

Astral emoji, wide CJK cells, and cluster merging — combining accents, ZWJ sequences, variation selectors and flags collapse into one cell.

GPU

Two renderers

Canvas2D and an instanced WebGL renderer that re-uploads only changed rows. A one-row edit repaints a fraction of a full frame.

Images

Sixel · iTerm2 · Kitty

Sixel decoded in the core; iTerm2 (OSC 1337) and Kitty graphics (APC) images decoded natively by the browser — so no image codec bloats the WASM.

Links

Hyperlinks & palette

OSC 8 links plus automatic URL detection, and a live dynamic palette (OSC 4/10/11/12) with color-query replies.

Safety

Safe at the boundary

Every untrusted byte is parsed in memory-safe Rust with bounded buffers; the JS layer never interprets an escape sequence itself.

beyond the browser

The same core, on the GPU

The engine also drives a native desktop terminal — winit + wgpu (Metal / Vulkan / DX12), no webview, running the identical Rust parser, grid and key encoding as the web build.

Renderer

Instanced wgpu

A GPU cell renderer over a fontdue glyph atlas: 256-color and true color, wide/CJK cells, bold/italic (real font faces or synthetic), underline and strikethrough.

Images

Inline graphics

Sixel and Kitty pixels drawn directly; iTerm2 and encoded Kitty images (PNG/JPEG/GIF/BMP/WebP) decoded through the image crate and composited over the cells.

Input

Selection & scrollback

Click-drag, shift-click, double-click word and triple-click line selection that spans scrollback and auto-scrolls at the edges; select-all, clipboard copy/paste, OSC 8 links, and a blinking cursor.

measured, not asserted

The numbers

Reproducible head-to-head against xterm.js in the same browser on identical payloads.

446 MB/s
plain-text parse
4.4× xterm.js · 179–488 MB/s across workloads
73 KB
gzip bundle
both renderers + Sixel, images, reflow, search, links
~4200 fps
WebGL full frame
200×50, compute-bound; far past display refresh

Full methodology and the xterm.js comparison table in COMPARISON.md. FPS is software-GL (SwiftShader) here — a lower bound; real GPUs are faster.

drop it in

Wire a PTY in a few lines

An ES module with TypeScript types. Point it at any byte stream.

install
# npm — ships wasm + JS, both renderers
npm install ferroterm

# or build from source
git clone https://github.com/DatanoiseTV/ferroterm
cd ferroterm && ./build.sh
app.js
import { Ferroterm } from 'ferroterm';

const term = await Ferroterm.create(el, {
  cols: 80, rows: 24, renderer: 'webgl',
});

// keystrokes → PTY, PTY bytes → screen
const ws = new WebSocket('wss://host/pty');
ws.binaryType = 'arraybuffer';
term.onData(b => ws.send(b));
ws.onmessage = e => term.write(new Uint8Array(e.data));
or as a custom element
// register the <ferro-term> tag once
import { defineFerroTermElement } from 'ferroterm';
defineFerroTermElement();

<!-- then use it declaratively in HTML -->
<ferro-term cols="80" rows="24" renderer="webgl"></ferro-term>

const el = document.querySelector('ferro-term');
await el.ready;                                // WASM + view are up
el.addEventListener('data', e => ws.send(e.detail));
ws.onmessage = m => el.write(new Uint8Array(m.data));
try it

Demos & examples

Everything runs client-side — no backend.