# mathtex-editor
Structural math editing for Rust and the web, typeset by real TeX.
Type `x^2`, `a/b` or `alpha` and watch it lay out the way TeX would. The document is a tree, so the caret moves through fractions, scripts and matrices the way you think about them, and every edit exports clean LaTeX.
* **Real TeX layout.** Every keystroke is typeset by [mathtex](https://github.com/gabriel-nsiqueira/mathtex), the XeTeX engine in Rust, in the browser or natively.
* **Keyboard first.** Autocorrect for `<=` and `->`, about 190 backslash words, a command palette with live previews, and a Backspace that selects a structure first and offers to swap brackets.
* **Pick your layer.** An explicit headless core, a pluggable keymap, a batteries included session, a wasm runtime and a Leptos component. Take as much or as little as your app needs.
* **Your widgets inside the math.** Host boxes reserve space in a formula for anything you draw, from a text field to a chart.
* **Documents you can trust.** Versioned JSON, validated on load, with seeded fuzz tests over random edits.
## Crates
| `mathtex-editor-core` | the editable tree, caret, selection, LaTeX export and geometry, with no hidden behaviour |
| `mathtex-editor-keymap` | key events and typed text to editor commands |
| `mathtex-editor-session` | core, keymap and typesetter wired together with undo, clipboard and host box tokens |
| `mathtex-editor-js` | the session for the web, rendering SVG |
| `mathtex-editor-leptos` | a `MathEditor` component for Leptos 0.8 |
## Quick start
```rust
let mut session = Session::new(typesetter);
session.key(&KeyInput { key: "x".into(), shift: false, ctrl: false, alt: false, meta: false });
let view = session.view()?;
// Draw view.fragment, then view.render.caret, selection and placeholders on top.
```
Build the `typesetter` from a baked mathtex format as the [mathtex README](https://github.com/gabriel-nsiqueira/mathtex) shows. The session typesets only when the document changed, keeps undo in one step per edit, copies as JSON and LaTeX, and gives pasted host boxes fresh tokens. `UndoStack`, `TokenRegistry` and `RenderCache` also work on their own.
## Full control with the core
The core never typesets, keeps no history, reads no keys and calls nothing back. You send a command and get a value back that says exactly what happened.
```rust
let before = editor.snapshot();
if editor.exec(Command::InsertText("x".into())).changed {
history.push(before);
}
let source = editor.source();
let output = editor.render(&source, &typeset(&source.tex))?;
```
* `exec` returns an `Outcome` with `changed`, `moved`, `exit`, `close`, `entered_host_box` and the `revision`. Retypeset only when the revision moves.
* A `CaretPath` names the route to the caret through each slot, survives serialization and can be set directly with `set_cursor` or `set_selection`.
* `snapshot` and `restore` capture the document with its caret for undo.
* `at_start`, `in_text_slot`, `menu`, `matrix_shape` and `input_context` answer questions without moving anything.
* Geometry is in points with y down from the fragment's top left, the units of the mathtex SVG `viewBox`, and `source` spans are byte ranges of the LaTeX.
## Typing
The keymap turns `a/b` into a fraction, `x^2` into a superscript and `alpha` plus space into α. The longest known word at the end wins, so `xsin` gives x then sin. `<=`, `->` and friends become one symbol, while ordinary input like `x<-1` is left alone. Inside `\text{}` everything types literally.
Read `Editor::input_context()` before each `map_key` or `map_text` call, run the returned commands once in order, and call `reset()` when the caret moves outside the keymap. Conversions ask the core to replace exactly the letters typed, so a stale word can never delete the wrong thing.
## Documents
`Document` is versioned JSON, the unit of storage and of the clipboard.
```json
{"version":1,"root":[{"type":"atom","data":{"latex":"x","class":"ord"}}]}
```
Every document is validated on load, `Document::repair` fixes one built in code and lists each fix, and `to_tex_with` exports LaTeX with your own content in place of host boxes. The LaTeX uses amsmath, and `\mathbb` or `\mathfrak` need amssymb or a Unicode math font.
## On the web
```js
const editor = new WebEditor(formatBytes, (name, kind) => fonts.get(name) ?? null);
editor.key('x', false, false, false, false);
stage.innerHTML = editor.render();
```
The resolver hands back font files as `Uint8Array` or `null`. `key`, `text`, `click`, the clipboard calls and the toolbar calls return an `Update` saying what changed. `render` returns SVG with the caret and selection drawn in and throws the TeX error when a formula does not typeset. The demo in `web/` shows the whole feature set, and `web/hostbox.html` puts live HTML inputs inside a formula.
In Leptos, `MathEditor` takes a shared `WebEditor` and handles keys, IME, pointer, clipboard, undo and the swap menu for you.
## Development
```sh
cargo test --workspace
wasm-pack build crates/mathtex-editor-js --target nodejs --out-dir pkg-node --release
node test/e2e.mjs
```
The e2e reads `format.pkg` and fonts from `MATHTEX_ASSETS`, the sibling mathtex checkout's `web/assets` by default. For the web demo, build with `--target web`, copy the output with `cp -r crates/mathtex-editor-js/pkg/. web/pkg/` and the assets into `web/assets`, then serve `web/`.
## Known gaps
* Up and Down between unrelated lines only moves structurally, through fractions, scripts and matrices.
* The Leptos component compiles for wasm32 but no browser test drives it yet.
Licensed under either Apache-2.0 or MIT.