##// END OF EJS Templates
autoreformat with darker
Matthias Bussonnier -
Show More
@@ -1,222 +1,222 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
81 81 None is returned when the backend cannot be used.
82 82
83 83 """
84 84 s = cast_unicode(s)
85 85 allowed_backends = LaTeXTool.instance().backends
86 86 if backend is None:
87 87 backend = allowed_backends[0]
88 88 if backend not in allowed_backends:
89 89 return None
90 90 if backend == 'matplotlib':
91 91 f = latex_to_png_mpl
92 92 elif backend == 'dvipng':
93 93 f = latex_to_png_dvipng
94 94 if color.startswith('#'):
95 95 # Convert hex RGB color to LaTeX RGB color.
96 96 if len(color) == 7:
97 97 try:
98 98 color = "RGB {}".format(" ".join([str(int(x, 16)) for x in
99 99 textwrap.wrap(color[1:], 2)]))
100 100 except ValueError as e:
101 101 raise ValueError('Invalid color specification {}.'.format(color)) from e
102 102 else:
103 103 raise ValueError('Invalid color specification {}.'.format(color))
104 104 else:
105 105 raise ValueError('No such backend {0}'.format(backend))
106 106 bin_data = f(s, wrap, color, scale)
107 107 if encode and bin_data:
108 108 bin_data = encodebytes(bin_data)
109 109 return bin_data
110 110
111 111
112 112 def latex_to_png_mpl(s, wrap, color='Black', scale=1.0):
113 113 try:
114 114 from matplotlib import mathtext
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 mt = mathtext.MathTextParser('bitmap')
126 126 f = BytesIO()
127 127 dpi = 120*scale
128 128 mt.to_png(f, s, fontsize=12, dpi=dpi, color=color)
129 129 return f.getvalue()
130 130 except (ValueError, RuntimeError, ParseFatalException):
131 131 return None
132 132
133 133
134 134 def latex_to_png_dvipng(s, wrap, color='Black', scale=1.0):
135 135 try:
136 136 find_cmd('latex')
137 137 find_cmd('dvipng')
138 138 except FindCmdError:
139 139 return None
140 140 try:
141 141 workdir = PurePath(tempfile.mkdtemp())
142 142 tmpfile = workdir.joinpath("tmp.tex")
143 143 dvifile = workdir.joinpath("tmp.dvi")
144 144 outfile = workdir.joinpath("tmp.png")
145 145
146 with tmpfile.open("w", encoding='utf8') as f:
146 with tmpfile.open("w", encoding="utf8") as f:
147 147 f.writelines(genelatex(s, wrap))
148 148
149 149 with open(os.devnull, 'wb') as devnull:
150 150 subprocess.check_call(
151 151 ["latex", "-halt-on-error", "-interaction", "batchmode", tmpfile],
152 152 cwd=workdir, stdout=devnull, stderr=devnull)
153 153
154 154 resolution = round(150*scale)
155 155 subprocess.check_call(
156 156 ["dvipng", "-T", "tight", "-D", str(resolution), "-z", "9",
157 157 "-bg", "transparent", "-o", outfile, dvifile, "-fg", color],
158 158 cwd=workdir, stdout=devnull, stderr=devnull)
159 159
160 160 with outfile.open("rb") as f:
161 161 return f.read()
162 162 except subprocess.CalledProcessError:
163 163 return None
164 164 finally:
165 165 shutil.rmtree(workdir)
166 166
167 167
168 168 def kpsewhich(filename):
169 169 """Invoke kpsewhich command with an argument `filename`."""
170 170 try:
171 171 find_cmd("kpsewhich")
172 172 proc = subprocess.Popen(
173 173 ["kpsewhich", filename],
174 174 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
175 175 (stdout, stderr) = proc.communicate()
176 176 return stdout.strip().decode('utf8', 'replace')
177 177 except FindCmdError:
178 178 pass
179 179
180 180
181 181 def genelatex(body, wrap):
182 182 """Generate LaTeX document for dvipng backend."""
183 183 lt = LaTeXTool.instance()
184 184 breqn = wrap and lt.use_breqn and kpsewhich("breqn.sty")
185 185 yield r'\documentclass{article}'
186 186 packages = lt.packages
187 187 if breqn:
188 188 packages = packages + ['breqn']
189 189 for pack in packages:
190 190 yield r'\usepackage{{{0}}}'.format(pack)
191 191 yield r'\pagestyle{empty}'
192 192 if lt.preamble:
193 193 yield lt.preamble
194 194 yield r'\begin{document}'
195 195 if breqn:
196 196 yield r'\begin{dmath*}'
197 197 yield body
198 198 yield r'\end{dmath*}'
199 199 elif wrap:
200 200 yield u'$${0}$$'.format(body)
201 201 else:
202 202 yield body
203 203 yield u'\\end{document}'
204 204
205 205
206 206 _data_uri_template_png = u"""<img src="data:image/png;base64,%s" alt=%s />"""
207 207
208 208 def latex_to_html(s, alt='image'):
209 209 """Render LaTeX to HTML with embedded PNG data using data URIs.
210 210
211 211 Parameters
212 212 ----------
213 213 s : str
214 214 The raw string containing valid inline LateX.
215 215 alt : str
216 216 The alt text to use for the HTML.
217 217 """
218 218 base64_data = latex_to_png(s, encode=True).decode('ascii')
219 219 if base64_data:
220 220 return _data_uri_template_png % (base64_data, alt)
221 221
222 222
General Comments 0
You need to be logged in to leave comments. Login now