Skip to content

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 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.

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

FormulaView demo, first frame
FormulaView 2 s
WriteFormula demo, first frame
WriteFormula 2.5 s
FormulaParts demo, first frame
FormulaParts 2.5 s
TikzDiagram demo, first frame
TikzDiagram 2.5 s
EulerJourney demo, first frame
EulerJourney 6 s

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 FormulaViews), delay, run_time, rate_func, match_tokens (false)

Gallery scenes: MathTex, TransformMatchingTex, ChemEq.

MathTex demo, first frame
MathTex 2 s
TransformMatchingTex demo, first frame
TransformMatchingTex 2.5 s
ChemEq demo, first frame
ChemEq 2.5 s

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.
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);

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

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