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