A simple mathematical LaTeX renderer for Python. Output as PNG (Pillow) or SVG (vector text with New Computer Modern).
Installation
pip install fondi
Requires Python 3.8+, Pillow, and NumPy.
Quick start
import fondi
mt = fondi.MathText(r"\frac{1}{2}", fontSize=50, color=(255, 255, 255, 255))
mt.to_pil().save("formula.png") # raster
mt.save_svg("formula.svg") # small SVG + external fonts (default)
MathText arguments
| Argument | Type | Description |
|---|---|---|
text |
str |
Fondi math string (LaTeX-like, not full LaTeX). |
fontSize |
int |
Nominal size in pixels (same scale as the PNG renderer). |
color |
(R, G, B, A) |
Each channel 0–255. |
Output methods
| Method | Returns | Use for |
|---|---|---|
to_pil() |
PIL.Image |
Matplotlib, GUIs, notebooks, textures. |
to_svg(...) |
str |
SVG XML; you control where files are written. |
save_svg(path, ...) |
Path |
Write SVG to disk; optional font copy. |
scene() |
Scene |
Inspect layout primitives (advanced). |
image |
PIL.Image |
Same as to_pil() (backward compatible). |
SVG export and fonts
Text in SVG uses New CM10 (NewCM10-Regular.otf / NewCM10-Italic.otf from the package). Integrals and braces are still embedded PNGs inside the SVG (v1).
You choose how those OTF files are shipped:
| Mode | SVG size | Font files | Best for |
|---|---|---|---|
| Embedded | Large (~1 MB+) | Inside the SVG | Single file, email, simple hosting |
| External + copy | Small (few KB) | Copied next to the SVG | First export in a project |
| External, no copy | Small | You provide them | Many SVGs sharing one fonts/ folder |
1. Embedded fonts (self-contained)
Every to_svg() call includes both full OTF files as base64 in <defs>. No separate font files; viewers need nothing installed.
svg = mt.to_svg() # default
svg = mt.to_svg(embed_fonts=True)
with open("formula.svg", "w") as f:
f.write(svg)
mt.save_svg("formula.svg", embed_fonts=True)
2. External fonts (small SVG)
The SVG references OTF files by URL, e.g. fonts/NewCM10-Regular.otf. You must keep those paths valid relative to where the SVG is opened (browser, Inkscape, etc.).
to_svg() — string only
svg = mt.to_svg(embed_fonts=False, font_url_prefix="fonts/")
# Writes no font files. You place the OTFs yourself.
| Parameter | Default | Meaning |
|---|---|---|
embed_fonts |
True |
False → external @font-face URLs. |
font_url_prefix |
"" |
Directory prefix in the URL, e.g. "fonts/" or "" if OTFs sit beside the SVG. |
Copy fonts once from the package:
from fondi.backends import copy_font_files
copy_font_files("shared/fonts/")
# Creates shared/fonts/NewCM10-Regular.otf and NewCM10-Italic.otf
Then export many formulas pointing at the same folder:
for name, tex in formulas.items():
m = fondi.MathText(tex, 24, (0, 0, 0, 255))
with open(f"out/{name}.svg", "w") as f:
f.write(m.to_svg(embed_fonts=False, font_url_prefix="../shared/fonts/"))
save_svg() — write file (+ optional font copy)
mt.save_svg("out/formula.svg")
Defaults: embed_fonts=False, font_subdir="fonts", copy_fonts=True.
| Parameter | Default | Meaning |
|---|---|---|
embed_fonts |
False |
True → one large self-contained SVG. |
font_subdir |
"fonts" |
Subfolder under the SVG’s parent for OTF files and URL prefix ("fonts/"). Use None for OTFs in the same directory as the SVG. |
copy_fonts |
True |
Copy bundled OTFs into font_subdir. False → only write the SVG (fonts must already exist there). |
Copy fonts once per output tree:
mt.save_svg("out/formula.svg", embed_fonts=False, font_subdir="fonts")
out/
formula.svg
fonts/
NewCM10-Regular.otf
NewCM10-Italic.otf
SVG only (fonts already present):
from fondi.backends import copy_font_files
copy_font_files("out/fonts/") # once
for i, tex in enumerate(items):
fondi.MathText(tex, 20, (0, 0, 0, 255)).save_svg(
f"out/label_{i}.svg",
embed_fonts=False,
font_subdir="fonts",
copy_fonts=False,
)
OTFs beside the SVG (no subfolder):
mt.save_svg("out/formula.svg", embed_fonts=False, font_subdir=None)
# Expects out/NewCM10-Regular.otf next to out/formula.svg
# URLs in SVG: NewCM10-Regular.otf (no prefix)
Choosing a mode (cheat sheet)
# One file, simplest sharing
mt.save_svg("x.svg", embed_fonts=True)
# Project with assets/fonts/ reused everywhere
copy_font_files("assets/fonts/")
mt.save_svg("plots/a.svg", embed_fonts=False, font_subdir="fonts", copy_fonts=False)
# Ensure font_subdir resolves from plots/a.svg → often use font_url_prefix like "../assets/fonts/"
# via to_svg(), or place fonts relative to each SVG accordingly
For nested paths, prefer to_svg(embed_fonts=False, font_url_prefix="...") with a prefix that matches your real directory layout.
Low-level API
from fondi.backends import render_svg, save_svg_bundle, copy_font_files
scene = mt.scene()
svg = render_svg(scene, embed_fonts=False, font_url_prefix="fonts/")
save_svg_bundle(scene, "out.svg", embed_fonts=False, font_subdir="fonts", copy_fonts=False)
PNG export
Raster output bakes the font into pixels. No font files are needed at view time.
img = mt.to_pil()
img.save("formula.png")
mt.image.save("formula.png") # equivalent
Architecture
Layouts position math and emit a backend-neutral scene graph (TextRun, Line, RasterSymbol, …). Renderers turn the same scene into PNG or SVG — new macros are implemented once.
| Layer | Role |
|---|---|
layout/*, plain/plain.py |
Geometry + scene primitives only |
metrics.py |
Font measurement (single source) |
scene.py |
Data types |
backends/pillow.py, backends/svg.py |
PNG / SVG output |
Adding a macro (write once)
- Add
FooLayout(Layout)undersrc/fondi/layout/. - Register
MACROS["\\foo"] = FooLayout. - Position children with
boundingBox,setCenter, etc. - Implement
collect_scene(offset)returningTextRun,Line,RasterSymbol, … — no Pillow or SVG code in the layout. - Run
tests/test.py(PNG) andtests/test_svg.py(SVG smoke).
Features
- Parentheses
- Subscript / superscript
- Delimiters (
(),[], cases{) as vector paths; integrals still PNG (v1) - Fractions
- Cases
- Greek letters
- Square root
- Integrals
Tests
cd tests && python test.py # regenerate PNG golden images
cd tests && python test_svg.py # SVG smoke (embedded + external fonts)
Metadata
Release files for fondi 0.2.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| fondi-0.2.2.tar.gz | 10.5 MB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| fondi-0.2.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 21.1 MB
Release files / fondi-0.2.2.tar.gz
| Download URL | fondi-0.2.2.tar.gz |
|---|---|
| Size | 10.5 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
71e50fd0eb968ac9f393693c43cd62f60356bcd47255e1b2b7200197160a7763
|
|
BLAKE2b-256 checksum How to use checksums |
bfcf7cbb129004dda49f641085c4beed71fa52366274d9a8b305c1a6998ed537
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Release files / fondi-0.2.2-py3-none-any.whl
| Download URL | fondi-0.2.2-py3-none-any.whl |
|---|---|
| Size | 10.5 MB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
3623d007ced80eac2f33462afcd6ba6c4f115d5efe61c56836682c7f257783c5
|
|
BLAKE2b-256 checksum How to use checksums |
1106dd66eb3d1684b11ca45a0f17fde69855e697f3fd3e9925c9591193eecd79
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Security for fondi 0.2.2
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.