Skip to main content

vectex

Test PyPI Python License

vectex compiles LaTeX source and returns one portable SVG <g> fragment. It is a library-level reimplementation of the rendering and normalization boundary behind TexText: it does not require Inkscape or access to the destination SVG document.

The returned group is also recognizable as an editable TexText object after a caller inserts it into an Inkscape SVG. Vectex stores both TexText-compatible attributes and a richer, versioned metadata record.

Install

The required runtime is Python 3.11 or newer; installation includes lxml and the command-line dependency typer:

python -m pip install vectex

Install an optional object-model adapter with one of:

python -m pip install 'vectex[svg-py]'
python -m pip install 'vectex[drawsvg]'
python -m pip install 'vectex[all]'

The distribution names and imports are svg.py / import svg and drawsvg / import drawsvg.

Command line

The installed vectex command renders a TeX document body to a portable SVG fragment on standard output:

vectex '$E = mc^2$'

Pass --as-doc to emit a complete, openable SVG document rather than a fragment. Without -o, either form is written to standard output:

vectex '$E = mc^2$' --as-doc > einstein.svg

Use --output (or -o) to write the selected form to a file:

vectex '$E = mc^2$' -o einstein-fragment.svg
vectex '$E = mc^2$' --as-doc -o einstein.svg

For multiline input, read UTF-8 source from a file or standard input:

vectex --input equation.tex --as-doc -o equation.svg
printf '%s\n' '$E = mc^2$' | vectex - --as-doc > einstein.svg

--preamble-file preamble.tex reads a complete preamble from a file and also records its absolute path for later TexText editing. It is an alternative to inline --preamble. For ordinary package loading, repeat --extra-package NAME instead of writing a preamble. Reuse persistent render records with --cache-dir PATH; add --refresh to recompile and replace the selected record.

Use --executable NAME=PATH to override a tool location; repeat it for both the engine and dvisvgm when needed. Run vectex --help for the complete option list; vectex --version reports the installed version.

Minimal use

import vectex

fragment = vectex.render(
    r"mass $m$ and energy $E = mc^2$",
    engine="pdflatex",
)
expression = vectex.render(r"$E = mc^2$")
vector = vectex.render(r"$\bm{n}$", extra_packages=("bm",))

svg_text = fragment.to_svg()
lxml_group = fragment.to_lxml()
document = fragment.to_svg_document()  # complete file-ready SVG
fragment.write_svg_document("label.svg")  # same document, written to disk

print(fragment.width, fragment.height, fragment.view_box)
print(fragment.source, fragment.engine, fragment.metadata)

TeX input is always a literal document body, the same convention TexText uses: $...$ marks inline mathematics, \[...\] marks display mathematics, and everything else is prose. Complete environments such as align* can be used directly; inner environments need their normal TeX context. amsmath is loaded by default, so \text{...} works in math expressions.

The default TeX template uses a zero-border standalone page cropped to each fragment and loads amsmath. A nonempty preamble replaces that complete preamble and must contain \documentclass, so preamble=r"\documentclass{article}" restores full-page geometry. Use extra_packages=("bm",) when only additional \usepackage declarations are needed. preamble and extra_packages are mutually exclusive.

Use either size_pt=7 to express a desired font size or the lower-level scale=0.7; passing both is an error. TeX sizing is resolved against the selected document class (10 pt by default).

Every call uses a fresh temporary directory and runs two stages:

source -> pdflatex/xelatex/lualatex -> PDF -> dvisvgm -> SVG -> lxml -> <g>

Embedding and adapters

to_lxml() returns a fresh element on every call, so appending or editing it cannot mutate the fragment's canonical serialization:

from lxml import etree

document = etree.fromstring('<svg xmlns="http://www.w3.org/2000/svg"/>')
document.append(fragment.to_lxml())

The optional adapters deliberately preserve the complete normalized XML rather than translating arbitrary SVG into a smaller object model:

import svg
import drawsvg

svg_py_group = fragment.to_svg_py()
svg_py_document = svg.SVG(
    width=fragment.width,
    height=fragment.height,
    elements=[svg_py_group],
)

drawing = drawsvg.Drawing(fragment.width, fragment.height)
drawing.append(fragment.to_drawsvg())

TexText editing in Inkscape

TexText detects editable nodes from attributes in its namespace on the selected outer <g>. Vectex emits the current compatibility fields: encoded source, compiler, PDF-to-SVG converter marker, preamble-file path, scale, alignment, version, and transform Jacobian.

Insert the outer group itself into an SVG and select that whole group before opening TexText. Selecting only a nested path or subgroup is intentionally rejected by TexText.

The stored TexText text is the source itself, since both tools treat it as a document body. The same $...$, \[...\], and environment syntax therefore recompiles without translation when the object is edited in TexText.

TexText represents its preamble as a file path, while Vectex accepts preamble content. If re-editing must use the same custom preamble, pass both values:

fragment = vectex.render(
    r"$\operatorname{rank}(A)$",
    preamble="\\documentclass{standalone}\n\\usepackage{amsmath}",
    textext_preamble_file="/absolute/shared/preamble.tex",
)

The path must remain accessible to TexText on the editing machine. The preamble content itself is retained in Vectex metadata, but TexText's compatibility field can carry only its path. Pass textext_compatible=False to omit all TexText attributes.

Executable discovery and configuration

Built-in components use shutil.which to resolve pdflatex, xelatex, lualatex, and dvisvgm. Exact overrides make discovery explicit and testable:

fragment = vectex.render(
    "$x+y$",
    executable_overrides={
        "pdflatex": "/opt/texlive/bin/pdflatex",
        "dvisvgm": "/opt/texlive/bin/dvisvgm",
    },
    timeout=20,
    compiler_args=("--synctex=0",),
    converter_args=("--precision=6",),
)

Argument options are sequences, never shell command strings. Vectex never uses shell=True. Nonzero exits and timeouts raise structured CompilationError or ConversionError instances with argv, return code, stdout, and stderr.

Applications may implement the small Compiler and Converter protocols and pass component objects instead of built-in names.

Batch rendering and disk cache

render_many([a, b, ...]) shares one compiler and one dvisvgm invocation while preserving each expression's crop and measurable baseline. A source may also be a RenderItem carrying any option that shapes its fragment; those left as None take the batch value. Items that share a compilation are grouped and rendered together, so a batch of labels differing only in size still costs one invocation, while an item with its own preamble or engine forms its own group. Fragments are returned in input order, and render() accepts a RenderItem as well. cache_dir, refresh, and unique_ids describe how a call runs rather than what it produces, and stay on the call.

The optional persistent cache is enabled with cache_dir= or VECTEX_CACHE_DIR. Entries are keyed by all output-driving options and by the identity of the installed tools -- built-in components contribute the resolved path and reported version of their executable, so records are not reused across a TeX or dvisvgm upgrade, and a component object may declare its own identity(). Entries are checksummed and written atomically; corrupt entries are treated as misses. refresh=True recompiles and replaces one record, and vectex.clear_cache(directory) removes only Vectex's namespaced records and returns the number removed.

Fragment guarantees

A successful render returns exactly one SVG <g> root with:

  • copied converter definitions and visible elements;
  • a deterministic input-derived ID prefix and rewritten href, xlink:href, and url(#...) references, including inline style attributes;
  • the source viewport represented by an inner matrix transform;
  • normalized width, height, view box, scale, and measurable baseline properties;
  • inheritable default black glyph fills, so fill on an enclosing SVG group recolours a label, while explicitly authored non-black colours are preserved;
  • deterministic repeated serialization of that fragment;
  • a Vectex <metadata> child containing format version, original source, engine, converter, geometry, preamble/options, and adapter-independent data;
  • TexText-recognized edit attributes unless explicitly disabled.

Identical render inputs serialize identically, while changed output-driving inputs receive a different namespace. Use unique_ids=True when embedding the same render more than once in one SVG, or supply an explicit id_prefix. render_many(..., id_prefix="labels") suffixes it by input position.

The outer group is named from that prefix: id_prefix="einstein" gives id="einstein-root", while rewritten definitions use IDs such as einstein-0. The CLI exposes this as --id-prefix einstein.

Security and trust assumptions

The XML parser disables DTD loading, entity resolution, network access, recovery, comments, and processing instructions. Normalization rejects scripts, foreignObject, SVG animation, event handlers, document CSS <style> elements, CSS imports, external hrefs/URLs, duplicate IDs, and unresolved local references. This conservative policy avoids active content and dependencies on destination document CSS.

LaTeX is a powerful program, not a safe sandbox. Vectex passes -no-shell-escape to built-in TeX engines, but a malicious source or trusted extra compiler option can still read files or consume resources according to the compiler's capabilities. Only compile trusted source, and use an OS/container sandbox when processing untrusted input. Executable overrides, preamble content, and extra argv values are trusted application configuration.

Development and packaging

Unit tests use checked-in SVG fixtures and mocked subprocesses; they need no TeX installation:

uv sync --all-extras
uv run ruff format --check .
uv run ruff check .
uv run mypy src
uv run pytest
uv run python -m build

Run optional real-tool tests only when explicitly requested:

VECTEX_RUN_INTEGRATION=1 uv run pytest -m integration

Vectex is distributed under the MIT License.

Scope

Vectex produces static, self-contained SVG fragments; it does not manipulate a destination SVG document. See Rendering for the built-in pipeline, baseline behavior, TexText contract, and trust policy, and Fragments for placement and caller integration.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

vectex-0.1.0.tar.gz (43.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

vectex-0.1.0-py3-none-any.whl (31.8 kB view details)

Uploaded Python 3

File details

Details for the file vectex-0.1.0.tar.gz.

File metadata

  • Download URL: vectex-0.1.0.tar.gz
  • Upload date:
  • Size: 43.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for vectex-0.1.0.tar.gz
Algorithm Hash digest
SHA256 cc6220dc26497882deb09811708b788261e199d7078c1619f95498397bb4fe4b
MD5 c0f8db79403a455872f03f4e204ccdea
BLAKE2b-256 3fbb5bdad3e547113dcf67cbf3c5ca3f8b48a76d36a99d2e3cb3c054c1427c14

See more details on using hashes here.

Provenance

The following attestation bundles were made for vectex-0.1.0.tar.gz:

Publisher: publish.yml on maiani/vectex

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file vectex-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: vectex-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 31.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for vectex-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 eb6f755b769de83f11e744d6c9535f837cef908e8e30e040f4454d38303613da
MD5 5ffdb4c0052922fa45ffedf35a2576c0
BLAKE2b-256 fc6aa2dc684749408f263bd377185d251b3f3472c2f36881cb7f9ebe4d198139

See more details on using hashes here.

Provenance

The following attestation bundles were made for vectex-0.1.0-py3-none-any.whl:

Publisher: publish.yml on maiani/vectex

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page