Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

35 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

termTeX

Beautiful LaTeX math, right in your terminal. 🧮

termtex renders LaTeX expressions and whole Markdown documents into the terminal window, typeset the way MathJax typesets them and drawn with the kitty graphics protocol. Inline math ($ ... $) and display math ($$ ... $$) sit among your prose, in real Computer Modern.

It is a single Go binary with no dependencies of any kind — no Node, no librsvg, no fonts to install, no cgo. 5 MB, and it draws the pixels itself.

A Markdown document rendered in the terminal: prose with inline math, and centered display equations

A Markdown file rendered in kitty. The math is drawn at the terminal's own pixel grid — no external renderer, nothing resampled.


🚀 Installation

go install github.com/junosuarez/termtex@latest

Or from source:

git clone https://github.com/junosuarez/termtex.git
cd termtex && go build -o termtex .

Requirements: Go 1.21+ to build, and that's the whole list — no cgo, no system libraries, no fonts to install. 🎉

To see the math rather than the fallback text you want a terminal that speaks the kitty graphics protocol: kitty, Ghostty, WezTerm, or Konsole. Anywhere else, -f text still works.


✨ Features

  • 📄 Mixed documents: plain text, inline equations ($V(S_t)$) and centered display equations ($$\sum_{i=1}^n x_i$$), interleaved.
  • 🔤 Real Computer Modern: the actual TeX outlines, taken from MathJax's own font data.
  • 🖼️ Drawn at your terminal's pixel grid: kitty, Ghostty, WezTerm and friends, rasterized in-process so nothing is resampled and nothing is blurry.
  • 🪶 No dependencies at all: pure Go, no cgo, one file to copy.
  • 💾 SVG and PNG export: -o paper.svg, -o hero.png, publication quality.
  • 🔡 Unicode fallback: no graphics terminal, no problem — it notices and writes x² + y².
  • 🔬 Typeset the way MathJax typesets it: the layout is a port of MathJax's own algorithms, and both the layout and the SVG it writes are checked glyph-for-glyph — position and outline — against MathJax over tens of thousands of generated expressions. Nothing at run time calls MathJax: termtex renders on its own, and MathJax is only what that rendering is held to. See DEVELOPMENT.md for the harness.
  • The math you actually write:
    • Fractions: \frac{a}{b}
    • Square & N-th Roots: \sqrt{x}, \sqrt[n]{x}
    • Subscripts & Superscripts: x_i, e^{-x^2}, \sum_{i=1}^n
    • Big Operators: \sum, \prod, \int, \iint, \iiint, \oint, \lim, \max, \min
    • Delimiters & Braces: \left( ... \right), \left[ ... \right], \left\{ ... \right\}, \left\langle ... \right\rangle
    • Environments: \begin{pmatrix}, \begin{bmatrix}, \begin{cases}
    • Greek Letters & Accents: \alpha, \beta, \Delta, \hat{x}, \vec{v}, \bar{z}

📸 Output Examples

Definite Integral & Square Root

\int_0^\infty e^{-x^2} dx = \frac{\sqrt{\pi}}{2}

Definite Integral

Piecewise Functions

\begin{cases} x & \text{if } x > 0 \\ -x & \text{otherwise} \end{cases}

Piecewise Cases

Matrix Layout

\begin{pmatrix} a & b \\ c & d \end{pmatrix}

Matrix



🧑‍💻 Use in scripts

termtex reads a formula from an argument, a file, or standard input, and writes to your terminal — or to a file, if you name one.

termtex "\frac{1}{x^2+1}"              # one expression
termtex notes.md                       # a whole markdown file
cat notes.md | termtex                 # …or from a pipe
termtex "Where \$V(S_t)\$ is the value."  # prose with inline math

🎛️ Flags

Flag Default What it does
-f, --format auto kitty, svg, png, text, or auto
-o, --output Write to a file; .svg gives SVG, anything else a PNG
-s, --size 32 Font size in pixels
-c, --color #cdd6f4 Foreground color
-bg, --background transparent Background color
-p, --padding 16 Padding around the formula
-d, --display true Display style — big operators, tall fractions
--inline row Inline math height: row fits the text line, natural is full size
--demo A showcase of what it can draw
-h, --help This list

Anything that fails — a formula it cannot parse, a file it cannot write — exits non-zero with the reason on stderr, so set -e does what you expect. ✅

🍳 Recipes

Render every formula in a directory to PNGs:

for f in equations/*.tex; do
  termtex -o "out/$(basename "$f" .tex).png" -s 64 "$(cat "$f")"
done

Preview math in a git hook, a pager, or a build log:

git show HEAD:paper.md | termtex

No graphics terminal? It notices. -f auto (the default) checks, and writes a Unicode approximation instead — so a script that pipes through less, or runs in CI, still says something useful:

$ TERM=dumb termtex "x^2 + y^2"
x² + y²

Match your color scheme:

termtex -c "#f38ba8" -bg "#1e1e2e" -s 48 "e^{i\pi} + 1 = 0"

Generate an SVG for the web:

termtex -o hero.svg -s 96 -c "#8b5cf6" "\nabla \times \mathbf{B} = \mu_0 \mathbf{J}"

📦 Use as a library

go get github.com/junosuarez/termtex

Everything below is the whole API — there is not much of it. 🧩

Draw into a terminal

import (
    "os"

    "github.com/junosuarez/termtex/pkg/render"
    "github.com/junosuarez/termtex/pkg/tex"
)

opts := tex.DefaultRenderOptions()
opts.DisplayMode = true

render.RenderToTerminal(os.Stdout, `\int_0^\infty e^{-x^2}dx`, opts)

RenderInlineToTerminal does the same on the line you are already on, and steps the cursor past the image so text carries on after it. SupportsGraphics reports whether the terminal will show it at all.

Render a whole document

import "github.com/junosuarez/termtex/pkg/doc"

doc.RenderDocument(os.Stdout, "Given $x^2$, we have:\n\n$$e^{i\\pi} + 1 = 0$$\n", opts)

Text passes through untouched; $…$ and $$…$$ become math.

Get an SVG

svg, err := tex.RenderTeXToSVG(`\frac{a}{b}`, opts)

Get an image

import "github.com/junosuarez/termtex/pkg/raster"

img, err := raster.RenderTeX(`\sqrt{x}`, opts, 2) // 2 device pixels per unit
// img is an *image.RGBA — encode it, composite it, do as you like

Draw many formulas efficiently

A Renderer keeps the glyphs it has rasterized and the buffers it drew into, so a second formula costs a fraction of the first:

r := raster.NewRenderer()
for _, formula := range formulas {
    node, err := tex.Parse(formula)
    if err != nil {
        continue
    }
    page := tex.BuildPage(node, opts)   // lay out, and find the canvas it needs
    img := r.Image(page, opts, 2)       // draw it
    encode(img)                          // …before the next call reuses the buffer
}

Two things to know: the image a Renderer hands back is only good until the next call on that renderer — copy it if it must outlive that — and a Renderer is not safe to share between goroutines. Give each one its own; they are cheap.

Options

type RenderOptions struct {
    FgColor     string  // "#cdd6f4", or "currentColor"
    BgColor     string  // "transparent" or a color
    FontSize    float64 // font size in pixels
    Padding     float64 // around the ink
    DisplayMode bool    // display style rather than inline
}

⚡ Performance

Pure Go, no cgo, no dependencies — the binary is everything it needs.

Binary 5.4 MiB (4.2 MiB stripped), including 1.1 MiB of Computer Modern glyph outlines
Peak memory, one formula 6.2 MiB resident
Peak memory, a whole document 6.9 MiB resident
One formula to the terminal 4.9 ms end to end
A Markdown document (3 formulas) 5.5 ms end to end

Most of that is not termtex. Breaking one invocation down:

Process spawn — what any binary costs 2.14 ms
Go runtime start 1.25 ms
termtex: tables, flags 0.99 ms
termtex: parse, lay out, draw, emit 0.49 ms
4.86 ms

So the program's own share of a cold invocation is about 1.5 ms, and the work of turning TeX into pixels is 0.49 ms of it. The lookup tables are placed in the binary rather than built at start-up, so init costs 0.16 ms and 82 KB.

In process, where the start-up is paid once, over 3,000 generated expressions (median):

Parse 1.7 µs
Lay out 4.1 µs
Parse and write SVG 14.4 µs
Rasterize (64k pixels, warm glyph cache) 99 µs

Measured on an Apple M1 Max, macOS 26.5, go 1.26; wall-clock figures are the median of 60 runs. go test ./pkg/raster/ -bench . measures the drawing loops directly.

Compared with the usual pipeline

The ordinary way to get TeX into a terminal is node + MathJax to make an SVG, rsvg-convert to make a PNG, and kitty +kitten icat to display it. Measured the same way, on the same machine, for the same formula:

node + MathJax + rsvg + icat termtex
One formula to the screen 175 ms 9.8 ms
Peak memory 73 MB (node) + 16 MB (rsvg) 6 MB
Installed size ~148 MB (node, mathjax-full, librsvg) 5.4 MB, one file
Processes per formula 3 1

Nearly all of that gap is start-up rather than typesetting. Inside node:

require MathJax 67.8 ms
build the TeX and SVG pipelines 6.0 ms
first render 9.8 ms
every render after that 0.63 ms

So MathJax lays a formula out in well under a millisecond; it is the loading that costs, and a command-line tool pays it every time. A long-running service that keeps node warm would not — and that is the fair comparison for termtex's in-process figures: 0.63 ms for MathJax to produce the SVG, against 14 µs for termtex to produce the same geometry and ~110 µs to produce pixels.

Ten formulas in one termtex process take 14.6 ms all told, because the start-up is paid once and the glyphs are already rasterized.

One place the naive pipeline wins: it sends a PNG to the terminal, about 29 KB base64 for the formula above, where termtex sends raw RGBA — 250 KB. That is deliberate (encoding a PNG costs more than drawing the formula did) and it does not matter locally, but it would over a slow ssh link.

None of this says termtex is better at typesetting than MathJax. MathJax is the reference these numbers are checked against, and it supports things termtex does not; where termtex does support something, the geometry is identical by construction. What the numbers say is that a terminal renderer does not need to start a JavaScript runtime three times a second.


🙏 Attribution & Credits

This project's TeX vector glyph data and typesetting lineage are deeply indebted to MathJax and The MathJax Consortium.

  • Vector glyph paths and TeX Computer Modern font path definitions are derived from mathjax-full.
  • Huge gratitude to the MathJax team and contributors for their pioneering work in open-source mathematical web typesetting!

🔬 Development

termtex is kept in exact agreement with MathJax by a comparison harness that lives in pkg/parity, pkg/fuzz, cmd/ and scripts/. It compares both stages of the pipeline: the box tree the layout builds, and the SVG the writer produces from it. If you are changing how anything is placed or drawn, start with DEVELOPMENT.md, and docs/adr for the decisions behind how it draws.


📄 License

Licensed under the Apache License, Version 2.0. See the LICENSE file for full details.

About

renders LaTeX math expressions and mixed Markdown documents in your terminal with Kitty Graphics Protocol

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages