123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205 |
- # -*- coding: utf-8 -*-
- """Tools for handling LaTeX."""
- # Copyright (c) IPython Development Team.
- # Distributed under the terms of the Modified BSD License.
- from io import BytesIO, open
- import os
- import tempfile
- import shutil
- import subprocess
- from IPython.utils.process import find_cmd, FindCmdError
- from traitlets.config import get_config
- from traitlets.config.configurable import SingletonConfigurable
- from traitlets import List, Bool, Unicode
- from IPython.utils.py3compat import cast_unicode, cast_unicode_py2 as u, PY3
- try: # Py3
- from base64 import encodebytes
- except ImportError: # Py2
- from base64 import encodestring as encodebytes
- class LaTeXTool(SingletonConfigurable):
- """An object to store configuration of the LaTeX tool."""
- def _config_default(self):
- return get_config()
-
- backends = List(
- Unicode(), ["matplotlib", "dvipng"],
- help="Preferred backend to draw LaTeX math equations. "
- "Backends in the list are checked one by one and the first "
- "usable one is used. Note that `matplotlib` backend "
- "is usable only for inline style equations. To draw "
- "display style equations, `dvipng` backend must be specified. ",
- # It is a List instead of Enum, to make configuration more
- # flexible. For example, to use matplotlib mainly but dvipng
- # for display style, the default ["matplotlib", "dvipng"] can
- # be used. To NOT use dvipng so that other repr such as
- # unicode pretty printing is used, you can use ["matplotlib"].
- ).tag(config=True)
- use_breqn = Bool(
- True,
- help="Use breqn.sty to automatically break long equations. "
- "This configuration takes effect only for dvipng backend.",
- ).tag(config=True)
- packages = List(
- ['amsmath', 'amsthm', 'amssymb', 'bm'],
- help="A list of packages to use for dvipng backend. "
- "'breqn' will be automatically appended when use_breqn=True.",
- ).tag(config=True)
- preamble = Unicode(
- help="Additional preamble to use when generating LaTeX source "
- "for dvipng backend.",
- ).tag(config=True)
- def latex_to_png(s, encode=False, backend=None, wrap=False):
- """Render a LaTeX string to PNG.
- Parameters
- ----------
- s : str
- The raw string containing valid inline LaTeX.
- encode : bool, optional
- Should the PNG data base64 encoded to make it JSON'able.
- backend : {matplotlib, dvipng}
- Backend for producing PNG data.
- wrap : bool
- If true, Automatically wrap `s` as a LaTeX equation.
- None is returned when the backend cannot be used.
- """
- s = cast_unicode(s)
- allowed_backends = LaTeXTool.instance().backends
- if backend is None:
- backend = allowed_backends[0]
- if backend not in allowed_backends:
- return None
- if backend == 'matplotlib':
- f = latex_to_png_mpl
- elif backend == 'dvipng':
- f = latex_to_png_dvipng
- else:
- raise ValueError('No such backend {0}'.format(backend))
- bin_data = f(s, wrap)
- if encode and bin_data:
- bin_data = encodebytes(bin_data)
- return bin_data
- def latex_to_png_mpl(s, wrap):
- try:
- from matplotlib import mathtext
- from pyparsing import ParseFatalException
- except ImportError:
- return None
- # mpl mathtext doesn't support display math, force inline
- s = s.replace('$$', '$')
- if wrap:
- s = u'${0}$'.format(s)
- try:
- mt = mathtext.MathTextParser('bitmap')
- f = BytesIO()
- mt.to_png(f, s, fontsize=12)
- return f.getvalue()
- except (ValueError, RuntimeError, ParseFatalException):
- return None
- def latex_to_png_dvipng(s, wrap):
- try:
- find_cmd('latex')
- find_cmd('dvipng')
- except FindCmdError:
- return None
- try:
- workdir = tempfile.mkdtemp()
- tmpfile = os.path.join(workdir, "tmp.tex")
- dvifile = os.path.join(workdir, "tmp.dvi")
- outfile = os.path.join(workdir, "tmp.png")
- with open(tmpfile, "w", encoding='utf8') as f:
- f.writelines(genelatex(s, wrap))
- with open(os.devnull, 'wb') as devnull:
- subprocess.check_call(
- ["latex", "-halt-on-error", "-interaction", "batchmode", tmpfile],
- cwd=workdir, stdout=devnull, stderr=devnull)
- subprocess.check_call(
- ["dvipng", "-T", "tight", "-x", "1500", "-z", "9",
- "-bg", "transparent", "-o", outfile, dvifile], cwd=workdir,
- stdout=devnull, stderr=devnull)
- with open(outfile, "rb") as f:
- return f.read()
- except subprocess.CalledProcessError:
- return None
- finally:
- shutil.rmtree(workdir)
- def kpsewhich(filename):
- """Invoke kpsewhich command with an argument `filename`."""
- try:
- find_cmd("kpsewhich")
- proc = subprocess.Popen(
- ["kpsewhich", filename],
- stdout=subprocess.PIPE, stderr=subprocess.PIPE)
- (stdout, stderr) = proc.communicate()
- return stdout.strip().decode('utf8', 'replace')
- except FindCmdError:
- pass
- def genelatex(body, wrap):
- """Generate LaTeX document for dvipng backend."""
- lt = LaTeXTool.instance()
- breqn = wrap and lt.use_breqn and kpsewhich("breqn.sty")
- yield u(r'\documentclass{article}')
- packages = lt.packages
- if breqn:
- packages = packages + ['breqn']
- for pack in packages:
- yield u(r'\usepackage{{{0}}}'.format(pack))
- yield u(r'\pagestyle{empty}')
- if lt.preamble:
- yield lt.preamble
- yield u(r'\begin{document}')
- if breqn:
- yield u(r'\begin{dmath*}')
- yield body
- yield u(r'\end{dmath*}')
- elif wrap:
- yield u'$${0}$$'.format(body)
- else:
- yield body
- yield u'\end{document}'
- _data_uri_template_png = u"""<img src="data:image/png;base64,%s" alt=%s />"""
- def latex_to_html(s, alt='image'):
- """Render LaTeX to HTML with embedded PNG data using data URIs.
- Parameters
- ----------
- s : str
- The raw string containing valid inline LateX.
- alt : str
- The alt text to use for the HTML.
- """
- base64_data = latex_to_png(s, encode=True).decode('ascii')
- if base64_data:
- return _data_uri_template_png % (base64_data, alt)
|