tikra
tikra converts TikZ and pgfplots figures to SVG and PNG, with no TeX installation, no fonts on the system and no external processes. Math in labels is typeset by mathra. It runs in JavaScript, Python, Rust and the terminal, and every one gives the same SVG, byte for byte. Try it in the browser.
Install and use
| Where | Install | Registry |
|---|---|---|
| JavaScript and TypeScript: Node, bundlers, browsers | npm install @mathlabs/tikra |
npm |
| Python 3.9+: wheels for Linux, macOS and Windows | pip install tikra |
PyPI |
| Terminal | cargo install tikra-cli |
crates.io |
| Rust | cargo add tikra |
crates.io · docs.rs |
JavaScript and TypeScript
Types included. In Node it works right away; in browsers and bundlers, await init() once. More in npm/README.md.
import from "@mathlabs/tikra";
await ; // browsers and bundlers; Node needs nothing
const svg = ;
const = ; // what was ignored or approximated
const bytes = ; // Uint8Array; { background: false } for transparent
Python
=
# background=False for transparent
# dict: svg, warnings
Types included; more in crates/python/README.md.
Terminal
$ tikra figure.tex -o figure.svg
$ tikra figure.tex -o figure.png --dpi 300 # white background
$ tikra figure.tex -o figure.png --transparent
$ cat figure.tex | tikra --warnings # SVG on stdout, warnings listed
Rust
let out = convert?;
write?;
write?;
for w in &out.warnings
The banner above is drawn by tikra itself from docs/banner.tex.
Examples
Each image is an SVG drawn by tikra from the figure in corpus/ it links to, shown on a white card. tests/examples.rs keeps them in sync with the code.
Input
A full standalone document, a bare tikzpicture, an inline \tikz, a lone axis environment, or loose TikZ statements. The preamble is read for \definecolor, \colorlet, \tikzset and \usetikzlibrary; the rest is ignored.
What it draws
See spec/subset.md. In short: \draw, \fill, \filldraw, \path, \node, \coordinate, \clip, \foreach (several variables, ..., count), scope with rotate/shift/scale/x=/y=, \def, \pgfmathsetmacro, declare function, \tikzset and styles, \definecolor, xcolor mixes, positioning (below=of A, right=2cm of B, below left=1cm and 2cm of C), calc ($(A)!0.5!(B)$), --, |-, -|, to[out=,in=], bend left/right, .. controls .., rectangle, circle, ellipse, arc, grid, plot (functions and coordinates, smooth), polar coordinates, nodes (rectangle, circle, ellipse, diamond) with text width, align, minimum size, rounded corners, anchors, label, nodes in the middle of a segment, arrow tips (->, ->>, -Latex, -Stealth, |, o), dashes, line widths, opacity, hatching (pattern=…), the zigzag, snake and random steps decorations, and pgfplots basics (axis, \addplot of an expression or of coordinates, only marks, legend, axis labels, title).
Labels: node text is typeset by mathra, with $…$ and \(…\) for math, \textbf, \\ for line breaks and \small…\Huge. Labels on a path inherit the path's color. Math follows mathra's default locale (English).
Warnings, not errors
What tikra does not understand becomes a warning, and the figure is drawn with the rest: \matrix, \pic, unknown commands and options it cannot read, TikZ escaped twice or for Markdown (undone), labels with broken LaTeX (recovered by mathra's tolerant mode). --warnings lists them; Output::warnings has them in the library.
Limits protect against hostile input: 100,000 statements per figure, 64 levels of nesting, 2,000 samples per plot, coordinates within ±100,000 cm, PNG up to 100 megapixels. Past a limit you get a clear error or a warning, never a hang.
Benchmark
corpus/ holds 25 hand-made figures that cover the features above. Reproduce with:
$ cargo run --release -p tikra --example bench
One run on an Apple M5 (arm64), 15 runs per figure, median per figure:
| Figures | Without warnings | With warnings | Errors |
|---|---|---|---|
| 25 | 25 | 0 | 0 |
| Per figure | Mean | p95 |
|---|---|---|
| SVG | 0.12 ms | 0.26 ms |
| SVG + PNG at 300 dpi | 2.13 ms | 5.88 ms |
tests/corpus.rs checks on every cargo test that all corpus figures convert without warnings.
Development
$ cargo test # every test
$ cargo llvm-cov --workspace --summary-only # coverage (100% of lines)
$ cargo clippy --all-targets -- -D warnings
$ npm/build.sh && npm/test/run.sh # the npm package, packed and installed as users get it
$ site/build.sh # the playground: then open site/index.html
$ scripts/release.sh --dry-run # what a release would publish
What each test file covers and the inputs built to break the interpreter are in docs/tests.md.
tikra depends on the published mathra crate. To work against a local checkout of mathra next to this one, add to .cargo/config.toml (ignored by git), and leave the Cargo.lock changes it causes out of commits:
[]
= { = "../mathra/crates/core" }
Releasing. Each package has its own version: Cargo.toml (crates tikra and tikra-cli), npm/package.json and crates/python/Cargo.toml. Bump the ones to release in a pull request, merge it, then run scripts/release.sh on main: it tags every version not yet on its registry (v*, npm-v*, py-v*) and pushes the tags at once; each tag runs its workflow (crates.yml, npm.yml, pypi.yml), which publishes with trusted publishing, without stored tokens. A rerun of a half-done release skips what is already out.
The first release needs a few steps by hand, because crates.io and npm only let a package that already exists trust a workflow:
- crates.io:
cargo login, thencargo publish -p tikraandcargo publish -p tikra-cli. Then, for each crate, Settings → Trusted Publishing: repositorymatheusmota-dev-br/tikra, workflowcrates.yml. - npm:
npm/build.sh && cd npm/pkg && npm publish --access public(the@mathlabsscope). Then in the package settings, Trusted Publisher → GitHub Actions: repositorymatheusmota-dev-br/tikra, workflownpm.yml. - PyPI accepts a trusted publisher before the project exists: Publishing → add a pending publisher, project
tikra, repositorymatheusmota-dev-br/tikra, workflowpypi.yml, environmentpypi. Thepy-v*tag publishes it. - GitHub: Settings → Pages → Source: "GitHub Actions", for the playground (
pages.yml), and an environment namedpypi.
Every pull request runs the same checks (.github/workflows/ci.yml): fmt, clippy, the Rust, wasm, npm and Python tests.
Licenses
Code: MIT or Apache-2.0. The font comes from mathra (STIX Two Math, SIL OFL 1.1); the Python and npm packages carry its license text.