KaTeX-compatible math rendering engine in pure Swift — no JavaScript, no WebView, no DOM.
SwaTex is a ground-up Swift port of RaTeX, the Rust KaTeX-compatible math renderer, built and maintained by the team behind Phrase — it renders every formula in Phrase's AI notes on iOS and macOS. One Swift core, one display list, native rendering on every Apple platform:
\frac{-b \pm \sqrt{b^2-4ac}}{2a} → iOS · macOS · tvOS · watchOS · visionOS · SVG · PNG
| Light | Dark — adaptive colors built in |
|---|---|
![]() |
![]() |
Generated straight from the library (SwiftUI MathView, dynamic
.mathColor(.primary)) by Tests/SwaTexRenderTests/ScreenshotGenerator.swift —
regenerate with SWATEX_SCREENSHOTS=.github/assets swift test --filter Screenshot.
Every major cross-platform math renderer runs LaTeX through a browser or JavaScript engine — a hidden WebView eating 50–150 MB RAM, startup latency before the first formula, no offline guarantee. SwaTex is the same KaTeX-compatible pipeline as a native Swift library:
- Pure Swift 6 — value types,
Sendablethroughout, typed throws,Synchronization.Mutex. No Objective-C, no C dependencies. - KaTeX-metric-exact layout — the layout engine drives TeX's box-and-glue rules from the bundled KaTeX font metrics, not from platform text APIs, so output matches KaTeX (and RaTeX) glyph-for-glyph.
- Flat display list — layout produces a renderer-agnostic
DisplayList(glyphs, lines, rects, paths) whose JSON wire format is byte-compatible with RaTeX's, enabling cross-engine differential testing. - Native backends — CoreText/CoreGraphics rendering with the bundled KaTeX fonts (no font registration side effects), SwiftUI
MathView, PNG export via ImageIO, and an SVG string backend.
Math — 1007 of 1008 entries in the official KaTeX support table;
the only exception is \includegraphics, which KaTeX itself gates behind
trust. (Four \html* commands render their content but drop DOM-only
attributes — there is no DOM in a native renderer.) Fractions, radicals,
integrals, matrices, environments (pmatrix, align, cases, CD, …),
stretchy delimiters, accents, macros (\def, \newcommand,
\expandafter), and more.
Chemistry — full mhchem support via \ce and \pu (state-machine port verified byte-identical to RaTeX on a 253-case corpus):
\ce{H2SO4 + 2NaOH -> Na2SO4 + 2H2O}Proof trees — bussproofs-style prooftree for inference rules and sequent calculi.
Swift Package Manager. In Package.swift:
dependencies: [
.package(url: "https://github.com/PhraseHQ/SwaTex.git", from: "0.5.0")
]Then add the product you need to your target — SwaTexRender for apps
(SwiftUI/UIKit/AppKit views, PNG; pulls in the core), or SwaTex alone for
the engine + SVG with zero platform dependencies:
.target(
name: "YourApp",
dependencies: [
.product(name: "SwaTexRender", package: "SwaTex")
]
)Or in Xcode: File → Add Package Dependencies… and paste
https://github.com/PhraseHQ/SwaTex.git.
Requires Xcode 16.3+ to build; deploys to iOS 18 / macOS 15 / tvOS 18 / watchOS 11 / visionOS 2.
import SwaTexRender // SwiftUI + UIKit/AppKit + CoreGraphics (Apple platforms)
// SwiftUI
MathView(#"\sum_{n=1}^{\infty} \frac{1}{n^2} = \frac{\pi^2}{6}"#)
.font(size: 24)
// UIKit / AppKit — built for block editors
let view = SwaTexView()
view.latex = #"\frac{a}{b}"#
view.fontSize = 18
view.mathStyle = .text // inline
let baseline = view.baselineFromTop // align with surrounding text runs
// PNG
let png = try ImageRenderer.png(latex: #"\frac{a}{b}"#)SwaTexView is editor-grade by construction: parse+layout is memoized
through a shared formula cache (~100 ns for repeated formulas), property
sets are lazily coalesced (configuring a recycled cell costs one layout),
and on macOS the view renders via updateLayer — it owns its rasterized
bitmap, so scrolling and window redisplays never re-rasterize glyphs;
rasterization happens only when content, backing scale, or orientation
actually change (CI-asserted through real display cycles).
import SwaTex // platform-independent core
// Parse → layout → display list (em units)
let list = try SwaTexEngine.displayList(for: #"x^2 + y^2 = z^2"#)
// SVG string
let svg = SVGRenderer().render(list)LaTeX string
│ Lexer (KaTeX-compatible tokens, catcodes, \verb)
│ MacroExpander (builtin macro table, \def/\newcommand, mhchem \ce/\pu)
│ Parser (KaTeX ParseNode AST; functions + environments registries)
│ Layout engine (TeX box/glue rules from KaTeX font metrics → LayoutBox)
▼ toDisplayList (flat, absolute-coordinate DisplayList in em units)
DisplayList ──► CoreGraphics/CoreText (SwaTexRender) ──► CGImage / PNG / SwiftUI
└─► SVG string (core)
| Target | Contents | Dependencies |
|---|---|---|
SwaTex |
lexer, parser, mhchem, layout engine, SVG backend | none |
SwaTexRender |
CoreText/CG renderer, bundled KaTeX fonts, MathView, SwaTexView |
SwaTex |
LayoutDump |
differential-testing CLI (mirrors RaTeX's layout binary) |
SwaTex |
Measured against the Rust reference engine on identical corpora, same machine, median of 3 runs, with all 2 541 golden formulas bit-exact between the engines. Every number is reproducible with the commands in BENCHMARKS.md:
| Workload | RaTeX (Rust) | SwaTex |
|---|---|---|
| parse → layout → display list | 41.4 µs/formula | 12.5 µs/formula (3.3×) |
| PNG batch, 100 formulas @40px 2× | ~200 µs/formula | 34 µs effective (parallel, 5.9×) |
| repeated formula (editor cache hit) | 41.4 µs | 105 ns (~390×) |
| editor scroll | — | 0 glyph rasterization (updateLayer) |
- Builds with Swift 6.1+ (Xcode 16.3+); consuming apps can use any Swift language mode
- Deploys to iOS 18 / macOS 15 / tvOS 18 / watchOS 11 / visionOS 2
(floor set by
Synchronization.Mutex)
\includegraphicsis not supported (the one missing entry of KaTeX's 1008; KaTeX itself gates it behindtrust). The four\html*commands render their content but drop DOM-only attributes.- Deployment floors are iOS 18 / macOS 15 / tvOS 18 / watchOS 11 /
visionOS 2 (
Synchronization.Mutex). Apps targeting older OS versions cannot adopt SwaTex yet. - Deliberate, documented divergences favor KaTeX over other engines:
size units accept ASCII digits only (KaTeX's JS
\d; Rust/Swift regex\dalso accept Unicode digits), and tokenization steps by Unicode scalar, so combining marks on syntax characters behave like the JS/Rust references rather than Swift's grapheme clusters. Additional KaTeX-correct divergences from RaTeX (each pinned by a regression test): unterminated\verbandx\limits^2are parse errors instead of being silently accepted;\vcenterphysically centers ink on the math axis;\gdef/\xdefsurvive group exit; zero-arg delimited\defmacros validate and consume their parameter text;\middlenested deeper than 8\left…\rightstretch scopes renders at natural (unstretched) size. - Shared with KaTeX/RaTeX upstream:
\begin{alignat}{N}does not boundN; hostile inputs should be length-limited by the host app. Deeply nested input is guarded (recursion count + stack headroom → parse error, never a crash). - Zero-redraw scrolling on macOS requires a layer-backed host hierarchy
(
wantsLayer— the default in modern AppKit apps). - Native Apple platforms only: no React Native / Flutter / Web wrappers.
swift run SwaTexDemo # interactive gallery: formulas, styles, sizes (macOS)swift testThe suite ports RaTeX's Rust tests: lexer/token, symbol/metrics tables, parser spec (KaTeX katex-spec.ts–derived), environments, mhchem, layout primitives, KaTeX SVG geometry, and renderer smoke tests. Cross-engine layout parity is checked with scripts/diff_layout.py, which diffs LayoutDump against RaTeX's layout binary over the 1000+-formula golden corpus.
Capture anything. Turn it into notes you can trust.
Phrase turns recordings, PDFs, images, and videos into source-backed AI notes,
slide decks, study tools, and dashboards — Apple-native, for notes that need proof.
SwaTex is the engine that renders every math formula in Phrase.
See CONTRIBUTING.md — the short version: keep the 2 541 golden fixtures green, pin parser fast paths with differential tests, run the formatter, and bring numbers for performance claims. Security reports: SECURITY.md.
The bundled KaTeX fonts (Sources/SwaTexRender/Resources/Fonts) are from the
KaTeX project, licensed under the SIL Open Font
License 1.1 (see OFL.txt / FONT_NOTICE.txt alongside the fonts).

