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 ofrun.text, set inrun.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:) -> MathBoxMathBox,MathLayoutElement(.text(run:x:y:)/.rule(...))MathRun,MathFontStyle,MathColorMathLayoutMetrics.openType(constants:unitsPerEm:)and.defaultTrueTypeMathTableParser(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