# Formulas

> Feature latex: fframes-kit = { features = ["latex"] }.

Page: https://fframes-kit.pages.dev/docs/text-and-formulas/formulas/

Feature `latex`: `fframes-kit = { features = ["latex"] }`. Formulas are LaTeX, typeset by one
of two backends (`TexBackend`):

- **Typst** (the default for `MathTex`): the kit converts the LaTeX with
  [mitex](https://github.com/mitex-rs/mitex) and typesets it with Typst in process, in New
  Computer Modern. Nothing has to be installed, and a formula takes a few milliseconds.
- **TeX**: the kit runs the TeX installed on the machine, as Manim does: `latex` makes a DVI
  file, `dvisvgm --no-fonts` turns it into paths. Any package your TeX has works: mhchem,
  TikZ, chemfig, circuitikz. `Tex`, `ChemEq`, TikZ pictures and templates with a preamble of
  your own always use it. Installing and checking TeX on Windows, Ubuntu and macOS is in
  [latex-setup.md](https://fframes-kit.pages.dev/docs/start/latex-setup.md).

Gallery scenes: `FormulaView`, `WriteFormula`, `FormulaParts`, `TikzDiagram`, `EulerJourney`.

Gallery clip of `FormulaView`: https://fframes-kit.pages.dev/media/scenes/FormulaView-de6d0d88.mp4

Gallery clip of `WriteFormula`: https://fframes-kit.pages.dev/media/scenes/WriteFormula-10d1fc96.mp4

Gallery clip of `FormulaParts`: https://fframes-kit.pages.dev/media/scenes/FormulaParts-f14358cc.mp4

Gallery clip of `TikzDiagram`: https://fframes-kit.pages.dev/media/scenes/TikzDiagram-e349c626.mp4

Gallery clip of `EulerJourney`: https://fframes-kit.pages.dev/media/scenes/EulerJourney-2e7bd22b.mp4

Each glyph becomes a `<path>`.

| Type | What it is |
| --- | --- |
| `TexEngine::new(cache_dir)` | keeps every SVG in `cache_dir`; finds `latex`, `xelatex`, `lualatex` and `dvisvgm` and runs them with a timeout (120 s, `with_timeout`); `with_backend(TexBackend::Tex)` makes TeX the default for `MathTex` |
| `MathTex::new(tex)` | math mode, as in an `align*` environment (`&` aligns, `\\` breaks a line), `.template(..)`, `.backend(TexBackend::..)`, `.typeset(&engine) -> Result<Formula, TexError>` |
| `Tex::new(text)` | text mode with TeX: `$..$`, whole TikZ pictures, anything a document body holds |
| `ChemEq::new("2H2 + O2 -> 2H2O")` | `\ce{..}` with the chemistry template and TeX, one part per species |
| `TexTemplate` | `compiler` (`TexCompiler::Latex`, `XeLatex`, `LuaLatex`), `preamble`, `body_wrapper` (with `YourTextHere`); `default()` (standalone, amsmath, amssymb), `chemistry()`, `physics()`, `tikz()`, `add_to_preamble` |
| `Formula` | the glyphs in TeX points, `part_bbox(part)`, `size_pt()`, `backend()`; implements `Geometry` |
| `FormulaView` | the component: `of`, `size` (pixels of 10 pt text, 48), `x`, `y`, `anchor` (`Anchor::TopLeft`, `Center`, ...), `color` (replaces black, keeps the colours the source sets), `color_map: &[(Part, Color)]`, `opacity`, `enter`, `exit`, `emphasis`, `emphasize: Option<Part>` (the part `emphasis` applies to), `key` |
| `TransformMatchingTex` | `from`, `to` (two `FormulaView`s), `delay`, `run_time`, `rate_func`, `match_tokens` (false) |

Gallery scenes: `MathTex`, `TransformMatchingTex`, `ChemEq`.

Gallery clip of `MathTex`: https://fframes-kit.pages.dev/media/scenes/MathTex-ad45c652.mp4

Gallery clip of `TransformMatchingTex`: https://fframes-kit.pages.dev/media/scenes/TransformMatchingTex-22eb35a8.mp4

Gallery clip of `ChemEq`: https://fframes-kit.pages.dev/media/scenes/ChemEq-2addcdd9.mp4

Which backend typesets a `MathTex`: its own `.backend(..)` first. Without one, a template
other than `TexTemplate::default()` and `TexTemplate::physics()` as they are (any
`add_to_preamble`, another compiler or wrapper) uses TeX, and the kit's two templates use
the engine's backend, Typst unless `with_backend` says otherwise.

Rules:

- Typeset in the video's constructor or a `OnceLock`, never in `render_frame`. A new formula
  takes a few milliseconds with Typst and about a second with TeX; one from the cache takes a
  file read.
- `{{ }}` marks a part, as in Manim: `{{a}}^2 + {{b}}^2 = {{c}}^2` has the parts 0, 1 and 2
  (`Part::Index`). `\part{name}{..}` names one (`Part::Label("name")`). A part cannot hold an
  `&`. A part that is a single binary operator or relation (`{{+}}`, `{{=}}`) keeps its
  spacing, so `a {{+}} b` is spaced like `a + b` with both backends.
- A command Typst cannot typeset fails with `TexError::NeedsTex`, which names the command
  (`\ce`, `\begin{tikzpicture}`, an unknown macro). The kit never falls back to TeX on its
  own: write `.backend(TexBackend::Tex)` on that formula.
- With `TexTemplate::physics()`, Typst knows `\dv`, `\pdv`, `\vb`, `\vu`, `\abs`, `\norm`,
  `\ev`, `\ket`, `\bra`, `\braket`, `\grad`, `\curl`, `\qty{x}`, `\SI{9.81}{m/s^2}`,
  `\si{kg}` and `\unit{m/s}` with physics' and siunitx' meaning, and draws each with the
  glyphs TeX draws. Two need TeX (`NeedsTex`): physics' `\div` (the divergence; Typst reads
  `\div` as `÷`) and `\qty(x)` / `\qty[x]`, whose brackets follow the argument. Units by
  name (`\meter`, `\per`) are unknown commands.
- `TransformMatchingTex` matches labels first, then parts with the same source, then glyphs
  outside parts with the same outline. Matched glyphs move, the rest fades out and in.
  - With `match_tokens: true`, glyphs outside parts of Typst formulas match by the LaTeX
    token they come from (`2#1` is the second `2`) before their outline: `x^2` into `2x`
    moves both glyphs although the `2` changes size. Formulas without tokens (TeX) give one
    `inspect` warning and match as without.
  - Glyphs of different backends never match. A transform between a TeX and a Typst formula
    fades every glyph, and `inspect` warns about it: typeset both with the same backend.
- `ChemEq` makes each species a part: separate `+` and arrows with spaces.
- Commit the cache directory: it holds the formulas of both backends. Open it at a path
  that does not depend on the working directory,
  `concat!(env!("CARGO_MANIFEST_DIR"), "/tex-cache")`, and machines without TeX (CI, other
  people) render from it. A TeX formula that is neither cached nor typesettable is a
  `TexError::NotInstalled` that names it.
- The web editor runs neither TeX nor Typst and cannot read the cache directory. Embed the
  cache into the build:
  `TexEngine::new(dir).with_embedded(fframes_kit::latex::tex_cache!("tex-cache"))`, with
  the path relative to the crate's `Cargo.toml`. The engine reads embedded files first, so
  the editor draws every formula a native render has cached, from both backends. Add a
  `build.rs` with `println!("cargo:rerun-if-changed=tex-cache");` so that new cache files
  are embedded too (see `gallery/build.rs`). The folder must exist, or Cargo rebuilds the
  crate every time; the first native render creates it.
- A formula changed since the last native render is not in the embedded cache. Draw
  `Formula::unset(source)` when `typeset` fails: the view then shows the source as text, and
  `inspect` lists it as the missing font `fframes-kit formula not typeset`. The `eprintln!`
  of a failure is silent in the browser.
- With `emphasize` and a `Write` entrance, the part and the rest of the formula each stagger
  their glyphs over the whole `Write`.

```rust
use std::sync::OnceLock;

use fframes_kit::prelude::*;

struct Formulas {
    pythagoras: Formula,
    solved: Formula,
}

fn typeset() -> Result<Formulas, TexError> {
    let engine = TexEngine::new(concat!(env!("CARGO_MANIFEST_DIR"), "/tex-cache"));
    Ok(Formulas {
        pythagoras: MathTex::new("{{a}}^2 + {{b}}^2 = {{c}}^2").typeset(&engine)?,
        solved: MathTex::new("{{a}}^2 = {{c}}^2 - {{b}}^2").typeset(&engine)?,
    })
}

// filled in the video's constructor: `FORMULAS.get_or_init(typeset)`
static FORMULAS: OnceLock<Result<Formulas, TexError>> = OnceLock::new();

fn solve<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> {
    let Ok(f) = FORMULAS.get_or_init(typeset) else {
        return Svgr::empty();
    };
    let theme = ui.theme;
    let view = |of| FormulaView {
        size: 120.0,
        x: 960.0,
        y: 540.0,
        anchor: Anchor::Center,
        ..FormulaView::new(of).themed(theme)
    };
    let map = [(Part::Index(0), theme.colors.accent)];
    let from = FormulaView { color_map: &map, ..view(&f.pythagoras) };
    ui.show(TransformMatchingTex { delay: 0.5, ..TransformMatchingTex::new(from, view(&f.solved)) })
}

let reaction = ChemEq::new("CH4 + 2O2 -> CO2 + 2H2O");
assert_eq!(fframes_kit::latex::chem_source(reaction.ce).matches("{{").count(), 4);
```

## API

### FormulaView

Draws a `Formula` (Manim's `MathTex` on screen).

| Field | Type | Default | Description |
| --- | --- | --- | --- |
| `of` | `&Formula` | — | The typeset formula to draw. A formula that was not typeset is drawn as plain `<text>` of its source. |
| `size` | `f32` | — | Height in pixels of 10 pt text (the TeX default size). |
| `x` | `f32` | — | Position of the `anchor` point of the formula in pixels of the canvas. |
| `y` | `f32` | — | Position of the `anchor` point of the formula in pixels of the canvas. |
| `anchor` | `Anchor` | — | The point of the formula's box at `(x, y)`. |
| `color` | `Color` | — | Replaces black, the color of everything the source does not color itself. |
| `color_map` | `&[(Part, Color)]` | — | Colors of single parts, over `color` and the source's colors. |
| `opacity` | `f32` | — | 0 transparent to 1 opaque. |
| `enter` | `Option<EnterAnim>` | — | How it appears; `None` shows it from the start. |
| `exit` | `Option<ExitAnim>` | — | How it leaves at the end of its container; `None` stays. |
| `emphasis` | `Option<Emphasis>` | — | An emphasis animation (`Indicate`, `Wiggle`, ..) played while it is shown. |
| `emphasize` | `Option<Part>` | — | The part `emphasis` applies to; the whole formula when `None`. |
| `key` | `Option<&str>` | — | Names it when two of the same kind share a frame. |

It has no theme defaults: build it with `FormulaView::new(..)`.

Methods:

- `fn new(of: &Formula) -> Self`
- `fn themed(self, t: &Theme) -> Self`: The same view with the theme's colors.
- `fn scale(&self) -> f64`: Pixels per TeX point.
- `fn transform(&self) -> Affine`: From TeX points to the canvas.
- `fn bounds(&self) -> Rect`: The box of the whole formula on the canvas, for a matcher around it.
- `fn part_bbox(&self, part: Part) -> Option<Rect>`: The box of a part on the canvas.
- `fn shapes(&self) -> Vec<Shape>`: One shape per glyph on the canvas, in drawing order.

Feature `latex` · [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/latex/component.rs#L360)

### TransformMatchingTex

One formula into another (Manim's `TransformMatchingTex`). Parts with the same `\part{..}` label match first, then `{{ }}` parts with the same source, then glyphs outside parts with the same outline. Matched glyphs move to their new place (a part with a different number of glyphs morphs as one shape), the rest of `from` fades out and the rest of `to` fades in. Before the start it draws `from`, after the end `to`.

| Field | Type | Default | Description |
| --- | --- | --- | --- |
| `from` | `FormulaView` | — | The formula that is shown before the start and that its matched glyphs leave. |
| `to` | `FormulaView` | — | The formula that is shown after the end and that its matched glyphs arrive at. |
| `delay` | `f32` | — | Seconds after the container's start before the transform begins. |
| `run_time` | `f32` | — | Seconds the transform takes. |
| `rate_func` | `RateFunc` | — | Easing of the transform over its run time. |
| `match_tokens` | `bool` | — | Match glyphs outside parts by their token (text and occurrence, `2#1`), then by outline. Needs Typst formulas; with others it warns and matches as without. |

It has no theme defaults: build it with `TransformMatchingTex::new(..)`.

Methods:

- `fn new(from: FormulaView, to: FormulaView) -> Self`: From `from` to `to` in Manim's 1 second, smooth.
- `fn shapes_at(&self, p: f32) -> Vec<Shape>`: The shapes `p` of the way from `from` to `to`.
- `fn matching(&self) -> (usize, usize, usize)`: Glyph counts: (matched pairs of groups, glyphs fading out, glyphs fading in).

Feature `latex` · [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/latex/matching.rs#L201)
