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:
latexmakes a DVI file,dvisvgm --no-fontsturns 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 2 s
WriteFormula 2.5 s
FormulaParts 2.5 s
TikzDiagram 2.5 s
EulerJourney 6 sEach 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 2 s
TransformMatchingTex 2.5 s
ChemEq 2.5 sWhich 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 inrender_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}}^2has 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, soa {{+}} bis spaced likea + bwith 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\divas÷) and\qty(x)/\qty[x], whose brackets follow the argument. Units by name (\meter,\per) are unknown commands. TransformMatchingTexmatches 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#1is the second2) before their outline:x^2into2xmoves both glyphs although the2changes size. Formulas without tokens (TeX) give oneinspectwarning and match as without. - Glyphs of different backends never match. A transform between a TeX and a Typst formula
fades every glyph, and
inspectwarns about it: typeset both with the same backend.
- With
ChemEqmakes 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 aTexError::NotInstalledthat 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’sCargo.toml. The engine reads embedded files first, so the editor draws every formula a native render has cached, from both backends. Add abuild.rswithprintln!("cargo:rerun-if-changed=tex-cache");so that new cache files are embedded too (seegallery/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)whentypesetfails: the view then shows the source as text, andinspectlists it as the missing fontfframes-kit formula not typeset. Theeprintln!of a failure is silent in the browser. - With
emphasizeand aWriteentrance, the part and the rest of the formula each stagger their glyphs over the wholeWrite.
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);FormulaView
Section titled “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) -> Selffn 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
TransformMatchingTex
Section titled “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: Fromfromtotoin Manim’s 1 second, smooth.fn shapes_at(&self, p: f32) -> Vec<Shape>: The shapespof the way fromfromtoto.fn matching(&self) -> (usize, usize, usize): Glyph counts: (matched pairs of groups, glyphs fading out, glyphs fading in).
Feature latex · Source
