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