tikra 1.0.0

TikZ figures to SVG and PNG with no TeX install; math labels by mathra.
Documentation

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 { init, tikzToSvg, render, png } from "@mathlabs/tikra";

await init();                                                    // browsers and bundlers; Node needs nothing
const svg = tikzToSvg(String.raw`\tikz \draw[->] (0,0) -- (2,1) node[right] {$v$};`);
const { warnings } = render(code);                               // what was ignored or approximated
const bytes = png(svg, { dpi: 300 });                            // Uint8Array; { background: false } for transparent

Python

import tikra

svg = tikra.tikz_to_svg(r"\tikz \draw[->] (0,0) -- (2,1) node[right] {$v$};")
open("v.png", "wb").write(tikra.png(svg, dpi=300))     # background=False for transparent
tikra.render(code)["warnings"]                          # 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 = tikra::convert(r"\begin{tikzpicture}\draw[->] (0,0) -- (2,1) node[right] {$v$};\end{tikzpicture}")?;
std::fs::write("v.svg", &out.svg)?;
std::fs::write("v.png", tikra::png(&out.svg, 300.0, true)?)?;
for w in &out.warnings { eprintln!("{w}"); }

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:

[patch.crates-io]
mathra = { path = "../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:

  1. crates.io: cargo login, then cargo publish -p tikra and cargo publish -p tikra-cli. Then, for each crate, Settings → Trusted Publishing: repository matheusmota-dev-br/tikra, workflow crates.yml.
  2. npm: npm/build.sh && cd npm/pkg && npm publish --access public (the @mathlabs scope). Then in the package settings, Trusted Publisher → GitHub Actions: repository matheusmota-dev-br/tikra, workflow npm.yml.
  3. PyPI accepts a trusted publisher before the project exists: Publishing → add a pending publisher, project tikra, repository matheusmota-dev-br/tikra, workflow pypi.yml, environment pypi. The py-v* tag publishes it.
  4. GitHub: Settings → Pages → Source: "GitHub Actions", for the playground (pages.yml), and an environment named pypi.

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.