##// END OF EJS Templates
Use batchmode in latex_to_png_dvipng...
Thomas Kluyver -
Show More
@@ -1,251 +1,251 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Tools for handling LaTeX.
2 """Tools for handling LaTeX.
3
3
4 Authors:
4 Authors:
5
5
6 * Brian Granger
6 * Brian Granger
7 """
7 """
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2010 IPython Development Team.
9 # Copyright (C) 2010 IPython Development Team.
10 #
10 #
11 # Distributed under the terms of the Modified BSD License.
11 # Distributed under the terms of the Modified BSD License.
12 #
12 #
13 # The full license is in the file COPYING.txt, distributed with this software.
13 # The full license is in the file COPYING.txt, distributed with this software.
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15
15
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17 # Imports
17 # Imports
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19
19
20 from io import BytesIO
20 from io import BytesIO
21 from base64 import encodestring
21 from base64 import encodestring
22 import os
22 import os
23 import tempfile
23 import tempfile
24 import shutil
24 import shutil
25 import subprocess
25 import subprocess
26
26
27 from IPython.utils.process import find_cmd, FindCmdError
27 from IPython.utils.process import find_cmd, FindCmdError
28 from IPython.config.configurable import SingletonConfigurable
28 from IPython.config.configurable import SingletonConfigurable
29 from IPython.utils.traitlets import List, CBool, CUnicode
29 from IPython.utils.traitlets import List, CBool, CUnicode
30 from IPython.utils.py3compat import bytes_to_str
30 from IPython.utils.py3compat import bytes_to_str
31
31
32 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
33 # Tools
33 # Tools
34 #-----------------------------------------------------------------------------
34 #-----------------------------------------------------------------------------
35
35
36
36
37 class LaTeXTool(SingletonConfigurable):
37 class LaTeXTool(SingletonConfigurable):
38 """An object to store configuration of the LaTeX tool."""
38 """An object to store configuration of the LaTeX tool."""
39
39
40 backends = List(
40 backends = List(
41 CUnicode, ["matplotlib", "dvipng"],
41 CUnicode, ["matplotlib", "dvipng"],
42 help="Preferred backend to draw LaTeX math equations. "
42 help="Preferred backend to draw LaTeX math equations. "
43 "Backends in the list are checked one by one and the first "
43 "Backends in the list are checked one by one and the first "
44 "usable one is used. Note that `matplotlib` backend "
44 "usable one is used. Note that `matplotlib` backend "
45 "is usable only for inline style equations. To draw "
45 "is usable only for inline style equations. To draw "
46 "display style equations, `dvipng` backend must be specified. ",
46 "display style equations, `dvipng` backend must be specified. ",
47 # It is a List instead of Enum, to make configuration more
47 # It is a List instead of Enum, to make configuration more
48 # flexible. For example, to use matplotlib mainly but dvipng
48 # flexible. For example, to use matplotlib mainly but dvipng
49 # for display style, the default ["matplotlib", "dvipng"] can
49 # for display style, the default ["matplotlib", "dvipng"] can
50 # be used. To NOT use dvipng so that other repr such as
50 # be used. To NOT use dvipng so that other repr such as
51 # unicode pretty printing is used, you can use ["matplotlib"].
51 # unicode pretty printing is used, you can use ["matplotlib"].
52 config=True)
52 config=True)
53
53
54 use_breqn = CBool(
54 use_breqn = CBool(
55 True,
55 True,
56 help="Use breqn.sty to automatically break long equations. "
56 help="Use breqn.sty to automatically break long equations. "
57 "This configuration takes effect only for dvipng backend.",
57 "This configuration takes effect only for dvipng backend.",
58 config=True)
58 config=True)
59
59
60 packages = List(
60 packages = List(
61 ['amsmath', 'amsthm', 'amssymb', 'bm'],
61 ['amsmath', 'amsthm', 'amssymb', 'bm'],
62 help="A list of packages to use for dvipng backend. "
62 help="A list of packages to use for dvipng backend. "
63 "'breqn' will be automatically appended when use_breqn=True.",
63 "'breqn' will be automatically appended when use_breqn=True.",
64 config=True)
64 config=True)
65
65
66 preamble = CUnicode(
66 preamble = CUnicode(
67 help="Additional preamble to use when generating LaTeX source "
67 help="Additional preamble to use when generating LaTeX source "
68 "for dvipng backend.",
68 "for dvipng backend.",
69 config=True)
69 config=True)
70
70
71
71
72 def latex_to_png(s, encode=False, backend=None, wrap=False):
72 def latex_to_png(s, encode=False, backend=None, wrap=False):
73 """Render a LaTeX string to PNG.
73 """Render a LaTeX string to PNG.
74
74
75 Parameters
75 Parameters
76 ----------
76 ----------
77 s : str
77 s : str
78 The raw string containing valid inline LaTeX.
78 The raw string containing valid inline LaTeX.
79 encode : bool, optional
79 encode : bool, optional
80 Should the PNG data bebase64 encoded to make it JSON'able.
80 Should the PNG data bebase64 encoded to make it JSON'able.
81 backend : {matplotlib, dvipng}
81 backend : {matplotlib, dvipng}
82 Backend for producing PNG data.
82 Backend for producing PNG data.
83 wrap : bool
83 wrap : bool
84 If true, Automatically wrap `s` as a LaTeX equation.
84 If true, Automatically wrap `s` as a LaTeX equation.
85
85
86 None is returned when the backend cannot be used.
86 None is returned when the backend cannot be used.
87
87
88 """
88 """
89 allowed_backends = LaTeXTool.instance().backends
89 allowed_backends = LaTeXTool.instance().backends
90 if backend is None:
90 if backend is None:
91 backend = allowed_backends[0]
91 backend = allowed_backends[0]
92 if backend not in allowed_backends:
92 if backend not in allowed_backends:
93 return None
93 return None
94 if backend == 'matplotlib':
94 if backend == 'matplotlib':
95 f = latex_to_png_mpl
95 f = latex_to_png_mpl
96 elif backend == 'dvipng':
96 elif backend == 'dvipng':
97 f = latex_to_png_dvipng
97 f = latex_to_png_dvipng
98 else:
98 else:
99 raise ValueError('No such backend {0}'.format(backend))
99 raise ValueError('No such backend {0}'.format(backend))
100 bin_data = f(s, wrap)
100 bin_data = f(s, wrap)
101 if encode and bin_data:
101 if encode and bin_data:
102 bin_data = encodestring(bin_data)
102 bin_data = encodestring(bin_data)
103 return bin_data
103 return bin_data
104
104
105
105
106 def latex_to_png_mpl(s, wrap):
106 def latex_to_png_mpl(s, wrap):
107 try:
107 try:
108 from matplotlib import mathtext
108 from matplotlib import mathtext
109 except ImportError:
109 except ImportError:
110 return None
110 return None
111
111
112 if wrap:
112 if wrap:
113 s = '${0}$'.format(s)
113 s = '${0}$'.format(s)
114 mt = mathtext.MathTextParser('bitmap')
114 mt = mathtext.MathTextParser('bitmap')
115 f = BytesIO()
115 f = BytesIO()
116 mt.to_png(f, s, fontsize=12)
116 mt.to_png(f, s, fontsize=12)
117 return f.getvalue()
117 return f.getvalue()
118
118
119
119
120 def latex_to_png_dvipng(s, wrap):
120 def latex_to_png_dvipng(s, wrap):
121 try:
121 try:
122 find_cmd('latex')
122 find_cmd('latex')
123 find_cmd('dvipng')
123 find_cmd('dvipng')
124 except FindCmdError:
124 except FindCmdError:
125 return None
125 return None
126 try:
126 try:
127 workdir = tempfile.mkdtemp()
127 workdir = tempfile.mkdtemp()
128 tmpfile = os.path.join(workdir, "tmp.tex")
128 tmpfile = os.path.join(workdir, "tmp.tex")
129 dvifile = os.path.join(workdir, "tmp.dvi")
129 dvifile = os.path.join(workdir, "tmp.dvi")
130 outfile = os.path.join(workdir, "tmp.png")
130 outfile = os.path.join(workdir, "tmp.png")
131
131
132 with open(tmpfile, "w") as f:
132 with open(tmpfile, "w") as f:
133 f.writelines(genelatex(s, wrap))
133 f.writelines(genelatex(s, wrap))
134
134
135 with open(os.devnull, 'w') as devnull:
135 with open(os.devnull, 'w') as devnull:
136 subprocess.check_call(
136 subprocess.check_call(
137 ["latex", "-halt-on-error", tmpfile], cwd=workdir,
137 ["latex", "-halt-on-error", "-interaction", "batchmode", tmpfile],
138 stdout=devnull, stderr=devnull)
138 cwd=workdir, stdout=devnull, stderr=devnull)
139
139
140 subprocess.check_call(
140 subprocess.check_call(
141 ["dvipng", "-T", "tight", "-x", "1500", "-z", "9",
141 ["dvipng", "-T", "tight", "-x", "1500", "-z", "9",
142 "-bg", "transparent", "-o", outfile, dvifile], cwd=workdir,
142 "-bg", "transparent", "-o", outfile, dvifile], cwd=workdir,
143 stdout=devnull, stderr=devnull)
143 stdout=devnull, stderr=devnull)
144
144
145 with open(outfile, "rb") as f:
145 with open(outfile, "rb") as f:
146 return f.read()
146 return f.read()
147 finally:
147 finally:
148 shutil.rmtree(workdir)
148 shutil.rmtree(workdir)
149
149
150
150
151 def kpsewhich(filename):
151 def kpsewhich(filename):
152 """Invoke kpsewhich command with an argument `filename`."""
152 """Invoke kpsewhich command with an argument `filename`."""
153 try:
153 try:
154 find_cmd("kpsewhich")
154 find_cmd("kpsewhich")
155 proc = subprocess.Popen(
155 proc = subprocess.Popen(
156 ["kpsewhich", filename],
156 ["kpsewhich", filename],
157 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
157 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
158 (stdout, stderr) = proc.communicate()
158 (stdout, stderr) = proc.communicate()
159 return stdout.strip()
159 return stdout.strip()
160 except FindCmdError:
160 except FindCmdError:
161 pass
161 pass
162
162
163
163
164 def genelatex(body, wrap):
164 def genelatex(body, wrap):
165 """Generate LaTeX document for dvipng backend."""
165 """Generate LaTeX document for dvipng backend."""
166 lt = LaTeXTool.instance()
166 lt = LaTeXTool.instance()
167 breqn = wrap and lt.use_breqn and kpsewhich("breqn.sty")
167 breqn = wrap and lt.use_breqn and kpsewhich("breqn.sty")
168 yield r'\documentclass{article}'
168 yield r'\documentclass{article}'
169 packages = lt.packages
169 packages = lt.packages
170 if breqn:
170 if breqn:
171 packages = packages + ['breqn']
171 packages = packages + ['breqn']
172 for pack in packages:
172 for pack in packages:
173 yield r'\usepackage{{{0}}}'.format(pack)
173 yield r'\usepackage{{{0}}}'.format(pack)
174 yield r'\pagestyle{empty}'
174 yield r'\pagestyle{empty}'
175 if lt.preamble:
175 if lt.preamble:
176 yield lt.preamble
176 yield lt.preamble
177 yield r'\begin{document}'
177 yield r'\begin{document}'
178 if breqn:
178 if breqn:
179 yield r'\begin{dmath*}'
179 yield r'\begin{dmath*}'
180 yield body
180 yield body
181 yield r'\end{dmath*}'
181 yield r'\end{dmath*}'
182 elif wrap:
182 elif wrap:
183 yield '$${0}$$'.format(body)
183 yield '$${0}$$'.format(body)
184 else:
184 else:
185 yield body
185 yield body
186 yield r'\end{document}'
186 yield r'\end{document}'
187
187
188
188
189 _data_uri_template_png = """<img src="data:image/png;base64,%s" alt=%s />"""
189 _data_uri_template_png = """<img src="data:image/png;base64,%s" alt=%s />"""
190
190
191 def latex_to_html(s, alt='image'):
191 def latex_to_html(s, alt='image'):
192 """Render LaTeX to HTML with embedded PNG data using data URIs.
192 """Render LaTeX to HTML with embedded PNG data using data URIs.
193
193
194 Parameters
194 Parameters
195 ----------
195 ----------
196 s : str
196 s : str
197 The raw string containing valid inline LateX.
197 The raw string containing valid inline LateX.
198 alt : str
198 alt : str
199 The alt text to use for the HTML.
199 The alt text to use for the HTML.
200 """
200 """
201 base64_data = bytes_to_str(latex_to_png(s, encode=True), 'ascii')
201 base64_data = bytes_to_str(latex_to_png(s, encode=True), 'ascii')
202 if base64_data:
202 if base64_data:
203 return _data_uri_template_png % (base64_data, alt)
203 return _data_uri_template_png % (base64_data, alt)
204
204
205
205
206 # From matplotlib, thanks to mdboom. Once this is in matplotlib releases, we
206 # From matplotlib, thanks to mdboom. Once this is in matplotlib releases, we
207 # will remove.
207 # will remove.
208 def math_to_image(s, filename_or_obj, prop=None, dpi=None, format=None):
208 def math_to_image(s, filename_or_obj, prop=None, dpi=None, format=None):
209 """
209 """
210 Given a math expression, renders it in a closely-clipped bounding
210 Given a math expression, renders it in a closely-clipped bounding
211 box to an image file.
211 box to an image file.
212
212
213 *s*
213 *s*
214 A math expression. The math portion should be enclosed in
214 A math expression. The math portion should be enclosed in
215 dollar signs.
215 dollar signs.
216
216
217 *filename_or_obj*
217 *filename_or_obj*
218 A filepath or writable file-like object to write the image data
218 A filepath or writable file-like object to write the image data
219 to.
219 to.
220
220
221 *prop*
221 *prop*
222 If provided, a FontProperties() object describing the size and
222 If provided, a FontProperties() object describing the size and
223 style of the text.
223 style of the text.
224
224
225 *dpi*
225 *dpi*
226 Override the output dpi, otherwise use the default associated
226 Override the output dpi, otherwise use the default associated
227 with the output format.
227 with the output format.
228
228
229 *format*
229 *format*
230 The output format, eg. 'svg', 'pdf', 'ps' or 'png'. If not
230 The output format, eg. 'svg', 'pdf', 'ps' or 'png'. If not
231 provided, will be deduced from the filename.
231 provided, will be deduced from the filename.
232 """
232 """
233 from matplotlib import figure
233 from matplotlib import figure
234 # backend_agg supports all of the core output formats
234 # backend_agg supports all of the core output formats
235 from matplotlib.backends import backend_agg
235 from matplotlib.backends import backend_agg
236 from matplotlib.font_manager import FontProperties
236 from matplotlib.font_manager import FontProperties
237 from matplotlib.mathtext import MathTextParser
237 from matplotlib.mathtext import MathTextParser
238
238
239 if prop is None:
239 if prop is None:
240 prop = FontProperties()
240 prop = FontProperties()
241
241
242 parser = MathTextParser('path')
242 parser = MathTextParser('path')
243 width, height, depth, _, _ = parser.parse(s, dpi=72, prop=prop)
243 width, height, depth, _, _ = parser.parse(s, dpi=72, prop=prop)
244
244
245 fig = figure.Figure(figsize=(width / 72.0, height / 72.0))
245 fig = figure.Figure(figsize=(width / 72.0, height / 72.0))
246 fig.text(0, depth/height, s, fontproperties=prop)
246 fig.text(0, depth/height, s, fontproperties=prop)
247 backend_agg.FigureCanvasAgg(fig)
247 backend_agg.FigureCanvasAgg(fig)
248 fig.savefig(filename_or_obj, dpi=dpi, format=format)
248 fig.savefig(filename_or_obj, dpi=dpi, format=format)
249
249
250 return depth
250 return depth
251
251
General Comments 0
You need to be logged in to leave comments. Login now