Contents

diyamantina/mathtypeset

A small, dependency-free Swift package that typesets a TeX-math subset into a

Install

.package(url: "https://codeberg.org/MarkdownPdfHQ/MathTypeset.git", from: "0.7.0"),
.target(name: "YourTarget", dependencies: ["MathTypeset"]),

Usage

import MathTypeset

// 1. Parse a formula.
let parsed = try MathParser().parse(#"\frac{-b \pm \sqrt{b^2 - 4ac}}{2a}"#)

// 2. Build metrics from the font's OpenType MATH table.
let table = try TrueTypeMathTableParser(bytes: mathTableBytes, numGlyphs: numGlyphs).parse()
let metrics = MathLayoutMetrics.openType(constants: table.constants, unitsPerEm: unitsPerEm)

// 3. Lay it out. `measureText` is your font measurement (see the contract).
let layout = MathLayout(
    font: .regular,
    color: .black,
    measureText: { run in myAdvanceWidth(of: run.text, at: run.size) },
    metrics: metrics,
)
let box = try layout.layout(parsed.root, size: 11, displayStyle: true)

// 4. Emit. Walk box.elements and draw each in your backend.
for element in box.elements {
    switch element {
    case let .text(run, x, y):
        draw(run.text, at: (x, y), size: run.size, color: run.color)
    case let .rule(x, y, width, height, color):
        fillRect(x: x, y: y, width: width, height: height, color: color)
    }
}

The measurement contract

This is load-bearing. The layout never measures glyphs itself; it calls your measureText closure and trusts the result. For the layout and the MATH-table metrics to agree:

  • measureText(run) returns the **advance width of run.text, set in run.font

at run.size, in typographic points** (the same space run.size is in).

  • MathLayoutMetrics.openType(constants:unitsPerEm:) must be given the

unitsPerEm of the same font you measure against, so the MATH constants (font units) normalize to the same point space your advances use.

  • The resulting MathBox (width, height, depth) and every element

coordinate are in points at the formula's base size. Emit at those coordinates directly; do not rescale.

If you measure with one font but pass another font's unitsPerEm, or measure in a different unit than run.size, advances and metrics will disagree and the output will drift. Measure and feed metrics from one font artifact.

baselineOffset on a MathRun shifts that run vertically from the run's baseline (used for scripts). MathBox.depth is the extent below the baseline, useful for baseline-aligning inline math.

Public surface

  • MathParser().parse(_:) -> MathParser.ParsedFormula (.root: MathNode)
  • MathNode (the AST)
  • MathLayout(font:color:measureText:metrics:).layout(_:size:displayStyle:) -> MathBox
  • MathBox, MathLayoutElement (.text(run:x:y:) / .rule(...))
  • MathRun, MathFontStyle, MathColor
  • MathLayoutMetrics.openType(constants:unitsPerEm:) and .default
  • TrueTypeMathTableParser(bytes:numGlyphs:).parse() -> TrueTypeMathTable

(.constants)

License

MathTypeset is dual licensed as AGPL-3.0 / commercial.

The AGPL is a free, open-source license, but that does not mean the software is free of obligations. It is a copyleft license: any derivative work, including software or a network service that incorporates MathTypeset, must also be released under the AGPL-3.0 with its complete corresponding source. If you are building something that cannot comply with the AGPL terms, a commercial license is available that exempts you from them.

See COMMERCIAL.md for commercial licensing.

Package Metadata

Repository: diyamantina/mathtypeset

Default branch: main

README: README.md