##// END OF EJS Templates
Fix for latextools commandline arguments...
Jörgen Stenarson -
Show More
@@ -1,251 +1,252 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 Instance, List, CBool, CUnicode
29 from IPython.utils.traitlets import Instance, 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 subprocess.check_call(
135 with open(os.devnull, 'w') as devnull:
136 ["latex", "-halt-on-errror", tmpfile], cwd=workdir,
136 subprocess.check_call(
137 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
137 ["latex", "-halt-on-error", tmpfile], cwd=workdir,
138
138 stdout=devnull, stderr=devnull)
139 subprocess.check_call(
139
140 ["dvipng", "-T", "tight", "-x", "1500", "-z", "9",
140 subprocess.check_call(
141 "-bg", "transparent", "-o", outfile, dvifile], cwd=workdir,
141 ["dvipng", "-T", "tight", "-x", "1500", "-z", "9",
142 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
142 "-bg", "transparent", "-o", outfile, dvifile], cwd=workdir,
143 stdout=devnull, stderr=devnull)
143
144
144 with open(outfile, "rb") as f:
145 with open(outfile, "rb") as f:
145 bin_data = f.read()
146 bin_data = f.read()
146 finally:
147 finally:
147 shutil.rmtree(workdir)
148 shutil.rmtree(workdir)
148 return bin_data
149 return bin_data
149
150
150
151
151 def kpsewhich(filename):
152 def kpsewhich(filename):
152 """Invoke kpsewhich command with an argument `filename`."""
153 """Invoke kpsewhich command with an argument `filename`."""
153 try:
154 try:
154 find_cmd("kpsewhich")
155 find_cmd("kpsewhich")
155 proc = subprocess.Popen(
156 proc = subprocess.Popen(
156 ["kpsewhich", filename],
157 ["kpsewhich", filename],
157 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
158 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
158 (stdout, stderr) = proc.communicate()
159 (stdout, stderr) = proc.communicate()
159 return stdout.strip()
160 return stdout.strip()
160 except FindCmdError:
161 except FindCmdError:
161 pass
162 pass
162
163
163
164
164 def genelatex(body, wrap):
165 def genelatex(body, wrap):
165 """Generate LaTeX document for dvipng backend."""
166 """Generate LaTeX document for dvipng backend."""
166 lt = LaTeXTool.instance()
167 lt = LaTeXTool.instance()
167 breqn = wrap and lt.use_breqn and kpsewhich("breqn.sty")
168 breqn = wrap and lt.use_breqn and kpsewhich("breqn.sty")
168 yield r'\documentclass{article}'
169 yield r'\documentclass{article}'
169 packages = lt.packages
170 packages = lt.packages
170 if breqn:
171 if breqn:
171 packages = packages + ['breqn']
172 packages = packages + ['breqn']
172 for pack in packages:
173 for pack in packages:
173 yield r'\usepackage{{{0}}}'.format(pack)
174 yield r'\usepackage{{{0}}}'.format(pack)
174 yield r'\pagestyle{empty}'
175 yield r'\pagestyle{empty}'
175 if lt.preamble:
176 if lt.preamble:
176 yield lt.preamble
177 yield lt.preamble
177 yield r'\begin{document}'
178 yield r'\begin{document}'
178 if breqn:
179 if breqn:
179 yield r'\begin{dmath*}'
180 yield r'\begin{dmath*}'
180 yield body
181 yield body
181 yield r'\end{dmath*}'
182 yield r'\end{dmath*}'
182 elif wrap:
183 elif wrap:
183 yield '$${0}$$'.format(body)
184 yield '$${0}$$'.format(body)
184 else:
185 else:
185 yield body
186 yield body
186 yield r'\end{document}'
187 yield r'\end{document}'
187
188
188
189
189 _data_uri_template_png = """<img src="data:image/png;base64,%s" alt=%s />"""
190 _data_uri_template_png = """<img src="data:image/png;base64,%s" alt=%s />"""
190
191
191 def latex_to_html(s, alt='image'):
192 def latex_to_html(s, alt='image'):
192 """Render LaTeX to HTML with embedded PNG data using data URIs.
193 """Render LaTeX to HTML with embedded PNG data using data URIs.
193
194
194 Parameters
195 Parameters
195 ----------
196 ----------
196 s : str
197 s : str
197 The raw string containing valid inline LateX.
198 The raw string containing valid inline LateX.
198 alt : str
199 alt : str
199 The alt text to use for the HTML.
200 The alt text to use for the HTML.
200 """
201 """
201 base64_data = bytes_to_str(latex_to_png(s, encode=True), 'ascii')
202 base64_data = bytes_to_str(latex_to_png(s, encode=True), 'ascii')
202 if base64_data:
203 if base64_data:
203 return _data_uri_template_png % (base64_data, alt)
204 return _data_uri_template_png % (base64_data, alt)
204
205
205
206
206 # From matplotlib, thanks to mdboom. Once this is in matplotlib releases, we
207 # From matplotlib, thanks to mdboom. Once this is in matplotlib releases, we
207 # will remove.
208 # will remove.
208 def math_to_image(s, filename_or_obj, prop=None, dpi=None, format=None):
209 def math_to_image(s, filename_or_obj, prop=None, dpi=None, format=None):
209 """
210 """
210 Given a math expression, renders it in a closely-clipped bounding
211 Given a math expression, renders it in a closely-clipped bounding
211 box to an image file.
212 box to an image file.
212
213
213 *s*
214 *s*
214 A math expression. The math portion should be enclosed in
215 A math expression. The math portion should be enclosed in
215 dollar signs.
216 dollar signs.
216
217
217 *filename_or_obj*
218 *filename_or_obj*
218 A filepath or writable file-like object to write the image data
219 A filepath or writable file-like object to write the image data
219 to.
220 to.
220
221
221 *prop*
222 *prop*
222 If provided, a FontProperties() object describing the size and
223 If provided, a FontProperties() object describing the size and
223 style of the text.
224 style of the text.
224
225
225 *dpi*
226 *dpi*
226 Override the output dpi, otherwise use the default associated
227 Override the output dpi, otherwise use the default associated
227 with the output format.
228 with the output format.
228
229
229 *format*
230 *format*
230 The output format, eg. 'svg', 'pdf', 'ps' or 'png'. If not
231 The output format, eg. 'svg', 'pdf', 'ps' or 'png'. If not
231 provided, will be deduced from the filename.
232 provided, will be deduced from the filename.
232 """
233 """
233 from matplotlib import figure
234 from matplotlib import figure
234 # backend_agg supports all of the core output formats
235 # backend_agg supports all of the core output formats
235 from matplotlib.backends import backend_agg
236 from matplotlib.backends import backend_agg
236 from matplotlib.font_manager import FontProperties
237 from matplotlib.font_manager import FontProperties
237 from matplotlib.mathtext import MathTextParser
238 from matplotlib.mathtext import MathTextParser
238
239
239 if prop is None:
240 if prop is None:
240 prop = FontProperties()
241 prop = FontProperties()
241
242
242 parser = MathTextParser('path')
243 parser = MathTextParser('path')
243 width, height, depth, _, _ = parser.parse(s, dpi=72, prop=prop)
244 width, height, depth, _, _ = parser.parse(s, dpi=72, prop=prop)
244
245
245 fig = figure.Figure(figsize=(width / 72.0, height / 72.0))
246 fig = figure.Figure(figsize=(width / 72.0, height / 72.0))
246 fig.text(0, depth/height, s, fontproperties=prop)
247 fig.text(0, depth/height, s, fontproperties=prop)
247 backend_agg.FigureCanvasAgg(fig)
248 backend_agg.FigureCanvasAgg(fig)
248 fig.savefig(filename_or_obj, dpi=dpi, format=format)
249 fig.savefig(filename_or_obj, dpi=dpi, format=format)
249
250
250 return depth
251 return depth
251
252
General Comments 0
You need to be logged in to leave comments. Login now