terme
v0.1.0
Published
Ultra-smooth WebGL terminal renderer with MSDF font rendering
Maintainers
Readme
Terme - WebGL Terminal Renderer
Ultra-smooth, high-performance terminal renderer using WebGL 2 and MSDF font rendering. Capable of handling millions of lines via virtual scrolling with sustained 120+ FPS.
What is Terme?
Terme is a WebGL-based terminal renderer optimized for extreme performance. Unlike traditional DOM-based terminals, Terme renders text using GPU-accelerated instancing and MSDF fonts, achieving 120+ FPS even with millions of lines.
Use Cases:
- Terminal emulators (xterm.js alternative)
- Log viewers with millions of lines
- Browser-based code editors
- REPL/Shell interfaces
- Real-time data monitoring dashboards
Not a full terminal emulator: Terme focuses on rendering. For complete terminal functionality with ANSI support, PTY handling, etc., you'll need to implement or integrate those layers.
Quick Start
See USAGE.md for detailed documentation.
Installation:
npm install termeVanilla TypeScript:
import { Terminal } from 'terme';
const terminal = await Terminal.create(
canvas,
'/fonts/cascadia-code.png',
'/fonts/cascadia-code.json'
);
terminal.setContent(['Hello, World!']);
terminal.onInput((text) => console.log('Input:', text));React:
import { TerminalCanvas } from 'terme/react';
<TerminalCanvas
fontAtlasUrl="/fonts/cascadia-code.png"
metricsUrl="/fonts/cascadia-code.json"
config={{ fontSize: 14 }}
onTerminalReady={(terminal) => {
terminal.setContent(['Hello from React!']);
}}
/>Font Setup:
# Copy bundled fonts to your public directory
cp -r node_modules/terme/dist/fonts public/fontsFeatures
- WebGL 2 Rendering: Efficient GPU-accelerated text rendering using MSDF (Multi-channel Signed Distance Field) fonts
- Virtual Scrolling: Handles millions of lines with O(1) access and minimal memory overhead
- High Performance: Target 120+ FPS with zero allocations in hot paths
- Instanced Rendering: Single draw call per frame for optimal GPU utilization
- Text Input: Keyboard input with caret positioning
- Responsive: Automatic DPI handling and resize support
Prerequisites
Install msdfgen (required for font atlas generation):
# macOS brew install msdfgen # Arch Linux sudo pacman -S msdfgen # From source (Linux/Windows) git clone https://github.com/Chlumsky/msdfgen.git cd msdfgen cmake -S . -B build cmake --build build sudo cmake --install buildNode.js: v18+ recommended
Installation
As a Package
npm install termeFor React integration:
npm install terme react react-domFor Development
# Clone repository
git clone https://github.com/victorqueiroz/Terme.git
cd Terme
# Install dependencies
npm install
# Generate font atlas (MUST run before dev server)
npm run generate:font
# Start development server
npm run devUsage
Running the Demo
The demo application generates 1 million lines of sample content to showcase performance:
npm run devOpen http://localhost:5173 in your browser.
Controls:
- Scroll: Mouse wheel or trackpad
- Navigate: Arrow keys move the caret
- Input: Type any key (logged to console)
Using as a Component
import { TerminalCanvas } from 'terme/react';
import type { Terminal } from 'terme';
function MyApp() {
const handleTerminalReady = (terminal: Terminal) => {
// Set initial content
terminal.writeLine('Hello, World!');
terminal.writeLine('This is a terminal renderer');
terminal.writeLine('Powered by WebGL and MSDF');
// Setup callbacks
terminal.onInput((text) => console.log('Input:', text));
terminal.onScroll(() => console.log('Scrolled'));
};
return (
<TerminalCanvas
fontAtlasUrl="/fonts/cascadia-code.png"
metricsUrl="/fonts/cascadia-code.json"
config={{
fontSize: 14,
lineHeight: 1.5,
backgroundColor: [0, 0, 0, 1],
overscan: 10,
}}
onTerminalReady={handleTerminalReady}
/>
);
}Vanilla TypeScript API
import { Terminal } from 'terme';
// Create terminal (automatically initializes and attaches to canvas)
const terminal = await Terminal.create(
canvas,
'/fonts/cascadia-code.png',
'/fonts/cascadia-code.json',
{
fontSize: 14,
lineHeight: 1.5,
}
);
// Write content
terminal.writeLine('Hello, World!');
terminal.writeLine('This is line 2');
// Control scrolling
terminal.scrollTo(100);
// Setup callbacks
terminal.onInput((text) => console.log('Input:', text));
terminal.onScroll(() => console.log('Scrolled'));
// Cleanup when done
terminal.dispose();Architecture
src/
├── core/
│ ├── Terminal.ts # Main orchestrator
│ ├── font/ # Font atlas loading, metrics
│ ├── text/ # Text buffer, layout engine
│ ├── scroll/ # Virtual scrolling, viewport
│ ├── input/ # Caret, keyboard handling
│ └── renderer/ # WebGL pipeline
├── ui/react/ # React wrapper
└── demo/ # Demo applicationKey Technical Decisions
- Virtual Scrolling: Paged array (1024 lines/page) for O(1) access
- Rendering: Instanced rendering with single quad geometry
- Font Pipeline: msdfgen → Node.js packing → PNG atlas + JSON metrics
- Performance: Reuse typed arrays, zero allocations in hot paths
- React Integration: Terminal owns RAF loop, React is thin wrapper
Font Generation
The font generation script calls msdfgen for each character and packs them into a texture atlas:
npm run generate:fontThis creates:
public/fonts/cascadia-code.png- MSDF texture atlaspublic/fonts/cascadia-code.json- Glyph metrics
Customization:
Edit scripts/generate-font-atlas.mjs to:
- Change font file path
- Add/remove characters
- Adjust glyph size or MSDF range
- Modify atlas packing algorithm
Performance
Optimizations
- ✅ Zero allocations in scroll/layout hot paths
- ✅ Reuse typed arrays and object pools
- ✅ Single texture bind per frame
- ✅ Single draw call via instancing
- ✅ Dirty flag prevents unnecessary renders
- ✅ Overscan reduces layout thrashing
Benchmarks
- 1M lines: Smooth 120 FPS scrolling
- Memory: ~200MB for 1M lines @ 100 chars/line
- Init time: <100ms for font loading + WebGL setup
- Draw call: 1 per frame for all visible glyphs
Troubleshooting
msdfgen not found
Error: msdfgen not found in PATHInstall msdfgen (see Prerequisites above).
Font atlas missing
Error: Failed to load font metricsRun npm run generate:font before starting the dev server.
Text looks blurry
Ensure LINEAR filtering is used (already configured). MSDF textures must NOT use sRGB colorspace.
Low FPS
- Check GPU utilization (should be low, <10%)
- Verify single draw call in Chrome DevTools > Rendering > Frame Rendering Stats
- Reduce overscan if layout is bottleneck
Tech Stack
- React 18.2+ - UI framework
- TypeScript 5.2+ - Type safety
- Vite 5.0+ - Build tool
- WebGL 2 - GPU rendering
- twgl.js 5.5+ - WebGL utilities
- gl-matrix 3.4+ - Matrix math
- msdfgen - Font atlas generation
License
MIT
Credits
- MSDF font rendering: Based on Chlumsky/msdfgen
- Cascadia Code font: microsoft/cascadia-code
