Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
executable file 187 lines (159 sloc) 5.53 KB
#!/usr/bin/env python3
Read LaTeX code from stdin and render a SVG using LaTeX + dvisvgm.
__version__ = '0.1.0'
__author__ = 'Tino Wagner'
__email__ = ''
__license__ = 'MIT'
__copyright__ = '(c) 2017, Tino Wagner'
import os
import sys
import subprocess
import shlex
import re
from tempfile import TemporaryDirectory
from ctypes.util import find_library
default_template = r"""
\documentclass[{{ fontsize }}pt,preview]{standalone}
{{ preamble }}
{{ code }}
default_preamble = r"""
latex_cmd = 'latex -interaction nonstopmode -halt-on-error'
dvisvgm_cmd = 'dvisvgm --no-fonts'
default_params = {
'fontsize': 12, # pt
'template': default_template,
'preamble': default_preamble,
'latex_cmd': latex_cmd,
'dvisvgm_cmd': dvisvgm_cmd,
'libgs': None,
if not hasattr(os.environ, 'LIBGS') and not find_library('gs'):
if sys.platform == 'darwin':
# Fallback to homebrew Ghostscript on macOS
homebrew_libgs = '/usr/local/opt/ghostscript/lib/libgs.dylib'
if os.path.exists(homebrew_libgs):
default_params['libgs'] = homebrew_libgs
if not default_params['libgs']:
print('Warning: libgs not found')
def latex2svg(code, params=default_params, working_directory=None):
"""Convert LaTeX to SVG using dvisvgm.
code : str
LaTeX code to render.
params : dict
Conversion parameters.
working_directory : str or None
Working directory for external commands and place for temporary files.
Dictionary of SVG output and output information:
* `svg`: SVG data
* `width`: image width in *em*
* `height`: image height in *em*
* `depth`: baseline position in *em*
if working_directory is None:
with TemporaryDirectory() as tmpdir:
return latex2svg(code, params, working_directory=tmpdir)
fontsize = params['fontsize']
document = (params['template']
.replace('{{ preamble }}', params['preamble'])
.replace('{{ fontsize }}', str(fontsize))
.replace('{{ code }}', code))
with open(os.path.join(working_directory, 'code.tex'), 'w') as f:
# Run LaTeX and create DVI file
ret =['latex_cmd']+' code.tex'),
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
# LaTeX prints errors on stdout instead of stderr (stderr is empty),
# so print stdout instead
if ret.returncode: print(ret.stdout.decode('utf-8'))
except FileNotFoundError:
raise RuntimeError('latex not found')
# Add LIBGS to environment if supplied
env = os.environ.copy()
if params['libgs']:
env['LIBGS'] = params['libgs']
# Convert DVI to SVG
ret =['dvisvgm_cmd']+' code.dvi'),
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
cwd=working_directory, env=env)
if ret.returncode: print(ret.stderr.decode('utf-8'))
except FileNotFoundError:
raise RuntimeError('dvisvgm not found')
with open(os.path.join(working_directory, 'code.svg'), 'r') as f:
svg =
# Parse dvisvgm output for size and alignment
def get_size(output):
regex = r'\b([0-9.]+)pt x ([0-9.]+)pt'
match =, output)
if match:
return (float( / fontsize,
float( / fontsize)
return None, None
def get_measure(output, name):
regex = r'\b%s=([0-9.e-]+)pt' % name
match =, output)
if match:
return float( / fontsize
return None
output = ret.stderr.decode('utf-8')
width, height = get_size(output)
depth = get_measure(output, 'depth')
return {'svg': svg, 'depth': depth, 'width': width, 'height': height}
def main():
"""Simple command line interface to latex2svg.
- Read from `stdin`.
- Write SVG to `stdout`.
- Write metadata as JSON to `stderr`.
- On error: write error messages to `stdout` and return with error code.
import json
import argparse
parser = argparse.ArgumentParser(description="""
Render LaTeX code from stdin as SVG to stdout. Writes metadata (baseline
position, width, height in em units) as JSON to stderr.
help="LaTeX preamble code to read from file")
args = parser.parse_args()
preamble = default_preamble
if args.preamble is not None:
with open(args.preamble) as f:
preamble =
latex =
params = default_params.copy()
params['preamble'] = preamble
out = latex2svg(latex, params)
meta = {key: out[key] for key in out if key != 'svg'}
except subprocess.CalledProcessError as exc:
if __name__ == '__main__':