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