##// END OF EJS Templates
Backport PR #9203: Latextools: Make latex_to_png_mpl not fail on errors...
Min RK -
Show More
@@ -1,199 +1,205
1 1 # -*- coding: utf-8 -*-
2 2 """Tools for handling LaTeX."""
3 3
4 4 # Copyright (c) IPython Development Team.
5 5 # Distributed under the terms of the Modified BSD License.
6 6
7 7 from io import BytesIO, open
8 8 import os
9 9 import tempfile
10 10 import shutil
11 11 import subprocess
12 12
13 13 from IPython.utils.process import find_cmd, FindCmdError
14 14 from traitlets.config import get_config
15 15 from traitlets.config.configurable import SingletonConfigurable
16 16 from traitlets import List, Bool, Unicode
17 17 from IPython.utils.py3compat import cast_unicode, cast_unicode_py2 as u, PY3
18 18
19 19 try: # Py3
20 20 from base64 import encodebytes
21 21 except ImportError: # Py2
22 22 from base64 import encodestring as encodebytes
23 23
24 24
25 25 class LaTeXTool(SingletonConfigurable):
26 26 """An object to store configuration of the LaTeX tool."""
27 27 def _config_default(self):
28 28 return get_config()
29 29
30 30 backends = List(
31 31 Unicode(), ["matplotlib", "dvipng"],
32 32 help="Preferred backend to draw LaTeX math equations. "
33 33 "Backends in the list are checked one by one and the first "
34 34 "usable one is used. Note that `matplotlib` backend "
35 35 "is usable only for inline style equations. To draw "
36 36 "display style equations, `dvipng` backend must be specified. ",
37 37 # It is a List instead of Enum, to make configuration more
38 38 # flexible. For example, to use matplotlib mainly but dvipng
39 39 # for display style, the default ["matplotlib", "dvipng"] can
40 40 # be used. To NOT use dvipng so that other repr such as
41 41 # unicode pretty printing is used, you can use ["matplotlib"].
42 42 config=True)
43 43
44 44 use_breqn = Bool(
45 45 True,
46 46 help="Use breqn.sty to automatically break long equations. "
47 47 "This configuration takes effect only for dvipng backend.",
48 48 config=True)
49 49
50 50 packages = List(
51 51 ['amsmath', 'amsthm', 'amssymb', 'bm'],
52 52 help="A list of packages to use for dvipng backend. "
53 53 "'breqn' will be automatically appended when use_breqn=True.",
54 54 config=True)
55 55
56 56 preamble = Unicode(
57 57 help="Additional preamble to use when generating LaTeX source "
58 58 "for dvipng backend.",
59 59 config=True)
60 60
61 61
62 62 def latex_to_png(s, encode=False, backend=None, wrap=False):
63 63 """Render a LaTeX string to PNG.
64 64
65 65 Parameters
66 66 ----------
67 67 s : text
68 68 The raw string containing valid inline LaTeX.
69 69 encode : bool, optional
70 70 Should the PNG data base64 encoded to make it JSON'able.
71 71 backend : {matplotlib, dvipng}
72 72 Backend for producing PNG data.
73 73 wrap : bool
74 74 If true, Automatically wrap `s` as a LaTeX equation.
75 75
76 76 None is returned when the backend cannot be used.
77 77
78 78 """
79 79 s = cast_unicode(s)
80 80 allowed_backends = LaTeXTool.instance().backends
81 81 if backend is None:
82 82 backend = allowed_backends[0]
83 83 if backend not in allowed_backends:
84 84 return None
85 85 if backend == 'matplotlib':
86 86 f = latex_to_png_mpl
87 87 elif backend == 'dvipng':
88 88 f = latex_to_png_dvipng
89 89 else:
90 90 raise ValueError('No such backend {0}'.format(backend))
91 91 bin_data = f(s, wrap)
92 92 if encode and bin_data:
93 93 bin_data = encodebytes(bin_data)
94 94 return bin_data
95 95
96 96
97 97 def latex_to_png_mpl(s, wrap):
98 98 try:
99 99 from matplotlib import mathtext
100 from pyparsing import ParseFatalException
100 101 except ImportError:
101 102 return None
102
103
103 104 # mpl mathtext doesn't support display math, force inline
104 105 s = s.replace('$$', '$')
105 106 if wrap:
106 107 s = u'${0}$'.format(s)
107
108 mt = mathtext.MathTextParser('bitmap')
109 f = BytesIO()
110 mt.to_png(f, s, fontsize=12)
111 return f.getvalue()
108
109 try:
110 mt = mathtext.MathTextParser('bitmap')
111 f = BytesIO()
112 mt.to_png(f, s, fontsize=12)
113 return f.getvalue()
114 except (ValueError, RuntimeError, ParseFatalException):
115 return None
112 116
113 117
114 118 def latex_to_png_dvipng(s, wrap):
115 119 try:
116 120 find_cmd('latex')
117 121 find_cmd('dvipng')
118 122 except FindCmdError:
119 123 return None
120 124 try:
121 125 workdir = tempfile.mkdtemp()
122 126 tmpfile = os.path.join(workdir, "tmp.tex")
123 127 dvifile = os.path.join(workdir, "tmp.dvi")
124 128 outfile = os.path.join(workdir, "tmp.png")
125 129
126 130 with open(tmpfile, "w", encoding='utf8') as f:
127 131 f.writelines(genelatex(s, wrap))
128 132
129 133 with open(os.devnull, 'wb') as devnull:
130 134 subprocess.check_call(
131 135 ["latex", "-halt-on-error", "-interaction", "batchmode", tmpfile],
132 136 cwd=workdir, stdout=devnull, stderr=devnull)
133 137
134 138 subprocess.check_call(
135 139 ["dvipng", "-T", "tight", "-x", "1500", "-z", "9",
136 140 "-bg", "transparent", "-o", outfile, dvifile], cwd=workdir,
137 141 stdout=devnull, stderr=devnull)
138 142
139 143 with open(outfile, "rb") as f:
140 144 return f.read()
145 except subprocess.CalledProcessError:
146 return None
141 147 finally:
142 148 shutil.rmtree(workdir)
143 149
144 150
145 151 def kpsewhich(filename):
146 152 """Invoke kpsewhich command with an argument `filename`."""
147 153 try:
148 154 find_cmd("kpsewhich")
149 155 proc = subprocess.Popen(
150 156 ["kpsewhich", filename],
151 157 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
152 158 (stdout, stderr) = proc.communicate()
153 159 return stdout.strip().decode('utf8', 'replace')
154 160 except FindCmdError:
155 161 pass
156 162
157 163
158 164 def genelatex(body, wrap):
159 165 """Generate LaTeX document for dvipng backend."""
160 166 lt = LaTeXTool.instance()
161 167 breqn = wrap and lt.use_breqn and kpsewhich("breqn.sty")
162 168 yield u(r'\documentclass{article}')
163 169 packages = lt.packages
164 170 if breqn:
165 171 packages = packages + ['breqn']
166 172 for pack in packages:
167 173 yield u(r'\usepackage{{{0}}}'.format(pack))
168 174 yield u(r'\pagestyle{empty}')
169 175 if lt.preamble:
170 176 yield lt.preamble
171 177 yield u(r'\begin{document}')
172 178 if breqn:
173 179 yield u(r'\begin{dmath*}')
174 180 yield body
175 181 yield u(r'\end{dmath*}')
176 182 elif wrap:
177 183 yield u'$${0}$$'.format(body)
178 184 else:
179 185 yield body
180 186 yield u'\end{document}'
181 187
182 188
183 189 _data_uri_template_png = u"""<img src="data:image/png;base64,%s" alt=%s />"""
184 190
185 191 def latex_to_html(s, alt='image'):
186 192 """Render LaTeX to HTML with embedded PNG data using data URIs.
187 193
188 194 Parameters
189 195 ----------
190 196 s : str
191 197 The raw string containing valid inline LateX.
192 198 alt : str
193 199 The alt text to use for the HTML.
194 200 """
195 201 base64_data = latex_to_png(s, encode=True).decode('ascii')
196 202 if base64_data:
197 203 return _data_uri_template_png % (base64_data, alt)
198 204
199 205
General Comments 0
You need to be logged in to leave comments. Login now