Skip to main content
MarkdownView provides native support for rendering LaTeX math expressions in your markdown content using the SwiftMath library. Math content is rendered as high-quality images with caching for optimal performance.

Overview

The library supports both inline and display math expressions using standard LaTeX syntax:
  • Inline math: $...$ for math within text
  • Display math: $$...$$ for block-level equations
Math rendering is performed by the MathRenderer class, which integrates with SwiftMath’s MTMathImage to convert LaTeX expressions into images.

SwiftMath Integration

MarkdownView uses SwiftMath as its LaTeX rendering engine. The integration happens in MathRenderer.swift:101-106:
let mathImage = MTMathImage(
    latex: processedLatex,
    fontSize: fontSize,
    textColor: resolvedTextColor,
    labelMode: .text
)
let (error, image) = mathImage.asImage()
SwiftMath converts LaTeX expressions into native iOS/macOS/visionOS images using CoreText and CoreGraphics.

LaTeX Preprocessing

Before rendering, LaTeX expressions are preprocessed to ensure compatibility with SwiftMath. The following transformations are automatically applied:
Original CommandReplacementReason
\\dots\\ldotsStandard ellipsis
\\implies\\RightarrowImplication arrow
\\begin{align}\\begin{aligned}Alignment environment
\\begin{cases}\\left\\{\\begin{matrix}Cases environment
\\dfrac\\fracDisplay fraction
\\boxed{...}content onlyRemoves boxing
These transformations occur in MathRenderer.swift:65-77 to handle common LaTeX patterns that may not be fully supported by SwiftMath.
If you encounter rendering issues with specific LaTeX commands, check if they’re supported by SwiftMath or require preprocessing.

Render Cache

Math expressions are expensive to render, so MarkdownView implements an intelligent caching system. Each rendered image is cached based on:
  • LaTeX content
  • Font size
  • Text color (resolved RGBA values)
The cache is implemented in MathRenderer.swift:59-63:
private static let renderCache: NSCache<CacheKey, PlatformImage> = {
    let cache = NSCache<CacheKey, PlatformImage>()
    cache.countLimit = 256
    return cache
}()
The cache stores up to 256 rendered math expressions. Cache keys account for color appearance (light/dark mode) to ensure correct rendering in different themes.

Color and Appearance

Math images automatically adapt to the current appearance mode:
  • On iOS/visionOS: Images use .alwaysTemplate rendering mode with .label tint color
  • On macOS: Images are marked as template images for automatic color adaptation
This ensures math expressions match your theme’s text color in both light and dark modes.
// iOS/visionOS (MathRenderer.swift:115)
let result = image.withRenderingMode(.alwaysTemplate).withTintColor(.label)

// macOS (MathRenderer.swift:117-118)
image.isTemplate = true
let result = image

Fallback Behavior

When LaTeX rendering fails (invalid syntax, unsupported commands, etc.), MarkdownView displays the original LaTeX source as inline code with a monospace font. This ensures content remains readable even when rendering fails. From InlineNode+Render.swift:198-208:
// Fallback: render failed, show original LaTeX as inline code
return NSAttributedString(
    string: latexContent,
    attributes: [
        .font: theme.fonts.codeInline,
        .foregroundColor: theme.colors.code,
        .backgroundColor: theme.colors.codeBackground.withAlphaComponent(0.05),
    ]
)

Inline Rendering Details

Math expressions are rendered as LTXAttachment objects that integrate seamlessly with text layout. The rendering process:
  1. Parse markdown to identify math expressions
  2. Render to image on background queue (for raw markdown) or main queue (for preprocessed content)
  3. Create attachment with the rendered image
  4. Draw inline using custom LTXLineDrawingAction callback
The math image is vertically scaled to fit within the line’s ascent if necessary, ensuring it doesn’t disrupt line spacing (InlineNode+Render.swift:149-152).
Math rendering that involves UI context (color resolution) must occur on the main thread. The library handles this automatically when you call setMarkdown(string:) or completeMathRendering().

Performance Considerations

  • Async rendering: When using setMarkdown(string:), math rendering happens on a background queue with UI-dependent steps completed on the main thread
  • Incremental parsing: Unchanged math expressions are reused when only part of the document changes
  • Memory limits: The cache is limited to 256 expressions to prevent unbounded memory growth

Font Size

The default font size for math rendering is 16pt, matching typical body text. Math rendering uses the theme’s body font size to ensure consistent appearance:
public static func renderToImage(
    latex: String,
    fontSize: CGFloat = 16,
    textColor: PlatformColor = .black
) -> PlatformImage?
Customize font sizes through your MarkdownTheme configuration. Math expressions will scale proportionally with your theme’s body font size.

Build docs developers (and LLMs) love