Skip to content

Add math in labels (lookup tier + opt-in Typst tier)#88

Draft
gabo515 wants to merge 1 commit into
Psy-Fer:devfrom
gabo515:feat/typst-math
Draft

Add math in labels (lookup tier + opt-in Typst tier)#88
gabo515 wants to merge 1 commit into
Psy-Fer:devfrom
gabo515:feat/typst-math

Conversation

@gabo515

@gabo515 gabo515 commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Description

Adds math in labels — any label (axis titles, plot title, TextPlot body) may embed $...$ math (LaTeX-ish: $\sigma^2$, $\frac{a}{b}$, $\sqrt{x}$). Two tiers that share $-detection and the symbol table, diverging only on structure:

  • Lookup tier (render::math::to_unicode) — always compiled, zero deps. Lowers math to inline Unicode (Greek, operators, super/subscripts, \fraca/b, \sqrt√(…)). Baseline for every backend and the only tier the terminal can use. All-or-nothing super/subscript fallback (x^(2k) when no Unicode form exists); never emits a stray \ or $.
  • Typst tier (feature math) — links the Typst compiler as a library (no external binary) and typesets the whole label into an SVG fragment / pixmap embedded in SVG/PNG/PDF. Real 2-D math: stacked fractions, radicals, large operators. Math color follows the label color; compile failure falls back to the lookup tier with a one-time warning.

Also lands the typst markup backend (feature typst): emits a CETZ-based .typ document for external typst compile, sharing the $...$→Typst-math translation with the math tier.

math is deliberately excluded from full — the Typst compiler is a ~200-crate tree; ordinary builds and cargo ci-test stay lean. Enable explicitly: --features math,png. Bundles only NewCM Math (~1 MB) + reuses the existing DejaVu Sans, not the 15 MB typst-assets.

Note: Typst math is not LaTeX — a multi-letter run like mc is one identifier, so write $E = m c^2$. (Documented in the reference page.)

Type of change

  • New feature / API addition

Checklist

Library

  • src/render/math.rsto_unicode (lookup tier), to_typst_math, shared $-detection + symbol table; #[cfg(feature="math")] Typst tier (render_label_svg / render_label_pixmap), whole-label compile, no cache
  • src/backend/{svg,raster,terminal}.rs — math routing (typst tier → embed/blit; else lookup tier → normal text). Terminal is lookup-only.
  • src/backend/svg_math.rs — fragment placement + per-label ID namespacing (avoids duplicate-ID SVG with multiple math labels)
  • src/backend/typst.rsTypstBackend markup emitter
  • src/fonts.rs — bundled NewCM Math (gzip); fonts module gated on math too
  • Cargo.tomlmath/typst features; math pulls flate2, excluded from full

Tests

  • render::math — 23 exact-match lookup-tier unit tests (Greek, operators, frac, sqrt, all-or-nothing super/sub, nested chains, braceless command, quadratic-formula full chain)
  • tests/math_lookup.rs — lookup tier through SVG + terminal + markdown
  • tests/math_smoke.rs / math_png.rs / math_pdf.rs — Typst-tier SVG embed (unique IDs, color injection), PNG composite incl. rotated y-labels, PDF via usvg
  • tests/typst_basic.rs — markup backend incl. escaping regression
  • cargo test --features cli,full — all suites pass (lookup-tier path)

CLI

  • No new subcommand/flag — math is transparent via existing --x-label / --y-label / --title; no man-page change
  • scripts/smoke_tests.sh — 7 lookup-tier checks (scatter/line/bar) + 3 Typst-tier checks (gated on --math-bin)
  • scripts/terminal_plots.sh — math-labels entry

Documentation

  • docs/src/reference/math.md — two tiers, supported set, Typst≠LaTeX, embedded SVG examples
  • examples/math.rsdocs/src/assets/math/*.svg (8 committed assets); scripts/gen_docs.sh wired (--features full,math)
  • docs/src/SUMMARY.md — reference link

Housekeeping

  • CHANGELOG.md[Unreleased] → Added

The typst markup backend (zero-dep) and the math feature (Typst-as-library) are bundled here per discussion. Builds + clippy clean across cli,full / math,png,pdf / math alone; doc assets generated against current dev.

Any label may embed `$...$` math (LaTeX-ish syntax). Rendering has two tiers
that share `$`-detection and the command→symbol table (`src/render/math.rs`):

Lookup tier — always compiled, zero dependencies:
- `to_unicode` lowers math to inline Unicode: Greek, operators, super/sub
  (all-or-nothing, falling back to a clean `^(2q)` when a glyph is missing),
  `\frac{a}{b}`→`a/b`, `\sqrt{x}`→`√(…)`. Never emits a stray `\` or `$`.
- Baseline for every backend; the only tier the terminal can use. SVG, PNG,
  PDF, and terminal all route plain `$...$` labels through it.

Typst tier — feature `math`, opt-in:
- Links the Typst compiler as a library (minimal `World`: bundled fonts, no
  package/network access) and typesets the *whole label* (text + math) into
  one SVG fragment (SVG/PDF) or pixmap (PNG), embedded into kuva output.
  Real 2-D math: stacked fractions, radicals, large operators with limits.
- Whole-label rendering means no fontdue text measurement and no per-segment
  layout — `svg_math` just places one fragment; raster blits/rotate-blits one
  pixmap. Math color follows the label color (injected into the Typst source).
- On compile failure (e.g. Typst's `mc` ≠ `m c`) the label degrades to the
  lookup tier with a one-time warning.
- Bundles only New Computer Modern Math (~1 MB, gzipped) + reuses the bundled
  DejaVu Sans for text, instead of the ~15 MB `typst-assets`. Excluded from
  `full` so `cargo ci-test` and example builds stay lean; enable explicitly,
  e.g. `--features math,png`.

Also adds the `typst` markup backend (`TypstBackend`): emits a CETZ-based
`.typ` document for external `typst compile`, sharing the `$...$` → Typst-math
translation with the `math` tier.

Tests: exhaustive exact-match units on the lookup tier (`render::math`);
structural tests on the typst tier (SVG embed, PNG composite incl. rotated
y-labels, PDF via usvg, color injection). Docs: Reference → Math in Labels.
@gabo515

gabo515 commented Jun 11, 2026

Copy link
Copy Markdown
Contributor Author
image

@Psy-Fer

Psy-Fer commented Jun 11, 2026

Copy link
Copy Markdown
Owner

So this is really 3 things in 1

  1. A unicode shorthand, for things like \sqrt and \frac or getting greek letters, that kind of thing.
  2. typst math layer, that links the typst compiler to do math in axis labels and annotations. It currently can't do plot body text (like in the textPlot type)
  3. typst backend, to emit a .typ file to add the plot to get compiled in their own doc pipeline.

my thoughts:

  1. The unicode shorthand is great. This is an excellent addition.
  2. pulling in 195 libraries for around 2% of what those libraries can do, for some axis labels, is a LOT of extra baggage. Not sure that's worth it, even with the feature gate.
  3. to too worth if 2 isn't included. in that case a user would just do #image("fig.svg") which is already what would be done. So this is linked to 2.

A few other issues I found in review. Arrow heads as a path primitive would get dropped through the typst backend path so no arrow heads in network, quiver, or other plots that have arrows. It also drops the Clip primitive (less of a big deal but could make for some surprises)

cetz_version being pinned is good, because it's gone through a lot of breaking changes. But this also highlights why i'm hesitant, given the maintenance for this feature going forward.

Overall, I think the unicode shorthand should absolutely be pulled into kuva asap. That's a great feature and it already works everywhere including text plots based on how it's implemented. I don't think the typst related stuff is quite there yet, mostly in issues with typst itself than your PR of the feature. (there was a reason I hadn't done it yet myself, besides time).

What do you think about what I have said?

Perhaps splitting this PR into 2 parts, the unicode shorthand and the typst math layer and backend, and then we can come back to the typst stuff later.

Cheers,
James

@gabo515

gabo515 commented Jun 11, 2026

Copy link
Copy Markdown
Contributor Author

Hey James,

Thanks for the feedback! Honestly, yea I think I bit off a bit more than I could chew. It felt incomplete with just the lookup but yea the typst stuff is quite heavy for little usability. I am happy to split this into two PRs with just the look up stuff and then everything else typst.

Best,
Gabe

@gabo515

gabo515 commented Jun 11, 2026

Copy link
Copy Markdown
Contributor Author

Agreed on all counts — and the split falls out naturally since the two tiers were designed to be independent. The unicode shorthand is now its own PR: #89. Zero deps, no feature flag, no Cargo.toml changes at all.

I'm parking the typst work on this branch and converting it to draft. Two notes for whenever we come back to it:

  • Good catches on Path and Clip — both real. Primitive::Path is currently a TODO in the typst backend (so arrowheads in quiver/network vanish) and ClipStart/ClipEnd are silently dropped. I'll add both on this branch before it's ever un-drafted.
  • One thought on the coupling between (2) and (3): the typst backend is actually the zero-crate path to real typeset math — the user's own typst compile does the typesetting, and $...$ regions pass through as native Typst math with fonts matching the surrounding document, which #image("fig.svg") can't give you. So if the in-process math feature stays out (totally fair on the maintenance argument — cetz/typst churn is real), the backend might still earn its place on its own once Path/Clip are in. No urgency either way.

Cheers!

@gabo515 gabo515 marked this pull request as draft June 11, 2026 18:45
Psy-Fer added a commit that referenced this pull request Jun 12, 2026
## Summary

This is the **unicode shorthand half** of #88, split out per the review
discussion there — the typst math layer and typst backend stay parked on
the other branch for later.

Any label (title, axis labels, `TextPlot` markdown bodies) may embed
`$...$` math written in LaTeX-ish syntax. Math regions are lowered to
inline Unicode by every backend, including the terminal:

| Input | Output |
|-------|--------|
| `$\sigma^2$` | σ² |
| `$x_i$` | xᵢ |
| `$a \leq b \cdot c$` | a ≤ b · c |
| `$\frac{a+b}{c}$` | (a+b)/c |
| `$\sqrt{x^2+y^2}$` | √(x²+y²) |
| `$\sum_{i=1}^{n} x_i$` | ∑ᵢ₌₁ⁿ xᵢ |

**Zero dependencies, no feature flag, always on.** No `Cargo.toml`
changes at all.

## Design notes

- **Inline only, by design.** Fractions and radicals lower to `a/b` and
`√(…)`, never stacked 2-D layout — the output is plain text that flows
anywhere a label can go (rotated y-axis titles, terminal character
grids, markdown bodies).
- **All-or-nothing super/subscripts.** `x^{2n}` → x²ⁿ, but if any
character in the group lacks a Unicode form (`q`, most capitals), the
whole group falls back to a clean `x^(2q)` — never a half-substituted
mix.
- **Never leaks source.** The lowering never emits a stray `\` or `$`.
Literal dollars are written `\$`; an unclosed `$` is left untouched as
ordinary text.
- **One module, four call sites.** `src/render/math.rs` holds detection
(`contains_math`), segmentation, the command table, and `to_unicode`.
The SVG, raster (PNG/PDF), and terminal `Text` arms lower at draw time;
markdown `TextSpan`s are lowered once after markup parsing, so `**bold**
$\sigma^2$` works in `TextPlot` bodies.

## Supported syntax

Greek letters (incl. `\varepsilon`/`\varphi` variants),
operators/relations/arrows (`\pm \times \cdot \leq \neq \approx \in
\partial \nabla \infty \to \degree` …), `\frac{a}{b}`, `\sqrt{x}` /
`\sqrt[3]{x}` (→ ³√x), and `^`/`_` scripts with `{...}` groups or
braceless `\command` operands (`x^\alpha` → `x^(α)`).

## Testing

- **21 unit tests** in `render::math` — substitution, fallbacks,
escaped/unclosed dollars, nested chains (quadratic formula end-to-end),
no-leak guarantees.
- **`tests/math_lookup.rs`** — per-backend integration: terminal, SVG,
markdown `TextPlot` body, escaped-dollar literal.
- **7 smoke tests** across plot types (scatter/line/bar) and label slots
(title, x/y labels, rotated y-label).
- **`scripts/terminal_plots.sh`** — math labels entry for visual
terminal inspection.
- `cargo ci-test` (99 suites), `cargo ci-clippy`, and the full smoke
suite (190 checks) all pass.

## Docs

- New reference page *Reference → Math in Labels* with the substitution
table, supported-syntax list, CLI usage, and generated example SVGs
(`examples/math.rs`, committed under `docs/src/assets/math/`).
- Crate-level docs section in `lib.rs`; CHANGELOG entry.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants