|
|
||
|---|---|---|
| .github/workflows | ||
| assets/images | ||
| Sources/LaTeXSwiftUI | ||
| Tests/LaTeXSwiftUITests | ||
| .gitignore | ||
| LICENSE.md | ||
| Package.resolved | ||
| Package.swift | ||
| README.md | ||
LaTeXSwiftUI
✨ Beautifully rendered LaTeX equations in SwiftUI — powered by MathJax.
🆕 What's New in v2
- ♿ Accessibility — VoiceOver support via the Speech Rule Engine (SRE)
- 🌐 Script Scaling — CJK and non-Latin script support with
.script() - 📐 Line Spacing — Automatic normalization on iOS 18+ / macOS 15+
- 📊 Arrays & Tables —
array,matrix,cases, and more with borders and rules - 🔲 Redacted Placeholder — New
redactedOriginalasync rendering style - 📏 Dynamic Type — Equations scale with system text size settings
- 🖼️ Render to Image — Render LaTeX to
UIImage/NSImagewithout a SwiftUI view - 🧩 Generic Environments —
\begin{align},\begin{cases}, etc. work standalone
📖 Contents
ℹ️ About
LaTeXSwiftUI is a package that exposes a LaTeX view capable of parsing and rendering TeX and LaTeX equations containing math-mode macros. It uses the MathJaxSwift package to render equations with MathJax, so the view's capabilities are influenced by MathJax's supported features.
The view renders math-mode equations (inline and block), \text{} within equations, numbered block equations, any \begin{...}...\end{...} environment (including align, gather, cases, array, and more), and provides VoiceOver accessibility via the Speech Rule Engine. It scales equations for non-Latin scripts and normalizes line spacing on iOS 18+ / macOS 15+. You can also render equations directly to UIImage/NSImage without a SwiftUI view. Equations automatically scale with Dynamic Type accessibility settings. It does not render full LaTeX documents or text-mode macros.
Requires Swift 6.0. Supports iOS 15+, macOS 12+, and visionOS 1+. All rendering is performed off the main thread.
📦 Installation
Add the dependency to your package manifest file.
.package(url: "https://github.com/colinc86/LaTeXSwiftUI", from: "2.0.0")
🚀 Quick Start
import LaTeXSwiftUI
struct MyView: View {
var body: some View {
LaTeX("Hello, $\\LaTeX$!")
}
}
That's it! The LaTeX view's body is built from Text views, so standard SwiftUI modifiers work out of the box.
LaTeX("Hello, $\\LaTeX$!")
.fontDesign(.serif)
.foregroundColor(.blue)
⌨️ Usage
🔤 Fonts
The view measures the current font's x-height to correctly size rendered equations. It converts SwiftUI's Font to UIFont/NSFont internally, which currently works with SwiftUI's preferred fonts (largeTitle, title, headline, caption, etc.) and platform font types passed directly.
// SwiftUI preferred fonts
LaTeX("Hello, $\\LaTeX$!")
.font(.title)
// UIFont/NSFont passed directly
LaTeX("Hello, $\\LaTeX$!")
.font(UIFont.systemFont(ofSize: 30))
LaTeX("Hello, $\\LaTeX$!")
.font(UIFont(name: "Avenir", size: 25)!)
⚠️ Custom SwiftUI fonts (
.custom(name:size:),.system(size:)) andUIFont/NSFontwrapped inFont()will not size equations correctly. Use preferred fonts or pass platform font types directly.
🔧 Parsing & Input
Parsing Mode
The view can search for top-level equations delimited by the following terminators, or render the entire input as math.
| Terminators |
|---|
$...$ |
$$...$$ |
\(...\) |
\[...\] |
\begin{equation}...\end{equation} |
\begin{equation*}...\end{equation*} |
\begin{...}...\end{...} |
Generic Environments
Any \begin{name}...\end{name} environment is automatically recognized as a block equation — including align, gather, cases, array, matrix, pmatrix, and more. There's no need to wrap them in $$...$$.
LaTeX("The function is \\begin{cases} x & \\text{if } x \\geq 0 \\\\ -x & \\text{if } x < 0 \\end{cases}")
// Only parse equations (default)
LaTeX("Euler's identity is $e^{i\\pi}+1=0$.")
.parsingMode(.onlyEquations)
// Parse the entire input
LaTeX("\\text{Euler's identity is } e^{i\\pi}+1=0\\text{.}")
.parsingMode(.all)
Unencode HTML
Input may contain HTML entities such as < which LaTeX won't parse. Use the unencoded modifier to decode them.
LaTeX("$x^2<1$")
.errorMode(.error)
// Replace "<" with "<"
LaTeX("$x^2<1$")
.unencoded()
String Formatting
The view renders the following markdown syntax by default.
| Syntax | Description |
|---|---|
*...* |
Italic |
**...** |
Bold |
***...*** |
Bold & Italic |
~~...~~ |
Strikethrough |
`...` |
Monospaced |
[...](...) |
Links |
The reserved LaTeX characters &, %, $, #, _, {, }, ~, ^, and \ are also unescaped when preceded by a backslash. Use ignoreStringFormatting() to disable both markdown rendering and escape replacement.
LaTeX(input)
.ignoreStringFormatting()
Use processEscapes() to allow \$ for literal dollar signs and \\ for literal backslashes within your input.
🎨 Visual & Layout
Image Rendering Mode
Equations can match the surrounding text style or display the original MathJax-rendered colors.
// Match surrounding text (default)
LaTeX("Hello, $\\color{red}\\LaTeX$!")
.imageRenderingMode(.template)
// Display original rendered colors
LaTeX("Hello, ${\\color{red} \\LaTeX}$!")
.imageRenderingMode(.original)
Error Mode
Control how the view handles rendering errors.
When
renderedmode is used, MathJax loads thenoerrorsandnoundefinedpackages. In the other modes, errors are either displayed or replaced with the original text.
LaTeX("$\\asdf$")
.errorMode(.original) // Show original text
LaTeX("$\\asdf$")
.errorMode(.error) // Show error message
LaTeX("$\\asdf$")
.errorMode(.rendered) // Show rendered image if available
Block Rendering Mode
Block equations can be rendered centered on their own line (blockViews, the default), forced inline (alwaysInline), or as text with newlines (blockText). Block equations are placed in horizontal scroll views when they exceed the view width.
LaTeX("The quadratic formula is $$x=\\frac{-b\\pm\\sqrt{b^2-4ac}}{2a}$$ and it has zeros at the roots of $f(x)=ax^2+bx+c$.")
.blockMode(.blockViews)
Divider()
LaTeX("The quadratic formula is $$x=\\frac{-b\\pm\\sqrt{b^2-4ac}}{2a}$$ and it has zeros at the roots of $f(x)=ax^2+bx+c$.")
.blockMode(.alwaysInline)
Divider()
LaTeX("The quadratic formula is $$x=\\frac{-b\\pm\\sqrt{b^2-4ac}}{2a}$$ and it has zeros at the roots of $f(x)=ax^2+bx+c$.")
.blockMode(.blockText)
Numbered Block Equations
The view supports simple numbering of block equations when using blockViews mode.
| Modifier | Description |
|---|---|
.equationNumberMode(_:) |
Position: .left, .right, or .none (default) |
.equationNumberStart(_:) |
Starting number (default: 1) |
.equationNumberOffset(_:) |
Left or right offset in points |
.formatEquationNumber(_:) |
Custom formatting closure (Int) -> String |
LaTeX("$$E = mc^2$$")
.equationNumberMode(.right)
.equationNumberOffset(10)
.padding([.bottom])
LaTeX("$$E = mc^2$$ $$E = mc^2$$")
.equationNumberMode(.right)
.equationNumberOffset(10)
.equationNumberStart(2)
.formatEquationNumber { n in
return "~[\(n)]~"
}
Rendering Style
All rendering (MathJax conversion and SVG rasterization) is performed off the main thread. Choose a rendering style to control loading behavior.
| Style | Async | Description |
|---|---|---|
empty |
Yes | The view remains empty until rendering completes. |
original |
Yes | The view displays the input text until rendering completes. |
redactedOriginal |
Yes | The view displays a redacted placeholder until rendering completes. 🆕 |
progress |
Yes | The view displays a progress indicator until rendering completes. |
wait |
No | (default) The view blocks until rendering completes. |
When using an asynchronous style, use renderingAnimation to animate the transition.
LaTeX(input)
.renderingStyle(.original)
.renderingAnimation(.easeIn)
Note: The
LaTeXview automatically re-renders when its input string changes, so you can bind it to@Statevariables without needing.id():@State var text: String = "" var body: some View { VStack { TextField("LaTeX", text: $text) LaTeX("$\(text)$") } }
Script Mode
🆕 When displaying equations inline with non-Latin scripts such as Korean, Japanese, or Chinese, equations may appear undersized or misaligned. The script modifier adjusts equation scaling to match the surrounding text.
// Korean
LaTeX("방정식 $x^2 + y^2 = z^2$ 은 잘 알려져 있습니다.")
.script(.cjk)
// Japanese
LaTeX("方程式 $E = mc^2$ は有名です。")
.script(.cjk)
// Custom scale factor
LaTeX("Scaled equation: $\\int_0^1 x^2 dx$")
.script(.custom(1.3))
| Script | Description |
|---|---|
.latin |
(default) Uses the font's x-height. Suitable for Latin, Cyrillic, and similar scripts. |
.cjk |
Uses the font's cap-height. Suitable for Korean, Japanese, and Chinese. |
.custom(CGFloat) |
Multiplies the font's x-height by the given factor. |
Line Spacing
🆕 On iOS 18+ / macOS 15+, the view automatically normalizes line spacing when inline equations cause uneven line gaps. This uses a custom TextRenderer and requires no configuration.
Arrays & Tables
🆕 The view renders LaTeX array and table environments including array, matrix, pmatrix, vmatrix, and cases. Horizontal rules (\hline) and vertical column borders are supported.
LaTeX("$$\\begin{pmatrix} a & b \\\\ c & d \\end{pmatrix}$$")
LaTeX("$$\\begin{cases} x & \\text{if } x \\geq 0 \\\\ -x & \\text{if } x < 0 \\end{cases}$$")
♿ Accessibility
🆕 Rendered equations are images that need accessibility labels for VoiceOver. By default, LaTeXSwiftUI uses MathJax's Speech Rule Engine (SRE) to generate natural language descriptions automatically.
// Default (.sre) — VoiceOver reads "x squared plus y squared equals z squared"
LaTeX("$x^2 + y^2 = z^2$")
// Use the raw TeX input as the label
LaTeX("$x^2 + y^2 = z^2$")
.imageAccessibility(.input)
// No accessibility label
LaTeX("$x^2 + y^2 = z^2$")
.imageAccessibility(.none)
// Custom label
LaTeX("$E = mc^2$")
.imageAccessibility(.custom("Einstein's mass-energy equivalence"))
| Mode | Description |
|---|---|
.sre |
(default) Uses the Speech Rule Engine to generate natural language. Falls back to raw TeX on failure. |
.input |
Uses the raw TeX input as the accessibility label. |
.none |
No accessibility label (default SwiftUI behavior). |
.custom(String) |
Uses a custom string as the accessibility label. |
🖼️ Rendering to Images
🆕 You can render LaTeX equations directly to UIImage (iOS/visionOS) or NSImage (macOS) without using the LaTeX SwiftUI view. This is useful for UIKit integration, image export, or custom rendering pipelines.
// Render all equations to images
let images = LaTeX.renderToImages("$x^2 + y^2 = z^2$")
// With custom options
let images = LaTeX.renderToImages(
"Euler's identity: $e^{i\\pi}+1=0$ and $\\int_0^1 x\\,dx$",
displayScale: 3.0,
processEscapes: true
)
// Each equation produces one image
for image in images {
imageView.image = image
}
⚡ Performance & Caching
All rendering is performed off the main thread. The package caches both SVG data from MathJax and the rasterized images. You can control the caches directly.
// Clear the SVG data cache
LaTeX.dataCache.removeAllObjects()
// Clear the rendered image cache
LaTeX.imageCache.removeAllObjects()
Preloading
SVGs and images are rendered on demand, but you can preload them to minimize lag when the view appears. Call preload last in the modifier chain.
VStack {
ForEach(expressions, id: \.self) { expression in
LaTeX(expression)
.font(.caption2)
.foregroundColor(.green)
.unencoded()
.errorMode(.error)
.processEscapes()
.preload()
}
}