##// END OF EJS Templates
Fix argument type in docsting
klonuo -
Show More
@@ -1,205 +1,205 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
13 13 from IPython.utils.process import find_cmd, FindCmdError
14 14 from traitlets.config import get_config
15 15 from traitlets.config.configurable import SingletonConfigurable
16 16 from traitlets import List, Bool, Unicode
17 17 from IPython.utils.py3compat import cast_unicode, cast_unicode_py2 as u, PY3
18 18
19 19 try: # Py3
20 20 from base64 import encodebytes
21 21 except ImportError: # Py2
22 22 from base64 import encodestring as encodebytes
23 23
24 24
25 25 class LaTeXTool(SingletonConfigurable):
26 26 """An object to store configuration of the LaTeX tool."""
27 27 def _config_default(self):
28 28 return get_config()
29 29
30 30 backends = List(
31 31 Unicode(), ["matplotlib", "dvipng"],
32 32 help="Preferred backend to draw LaTeX math equations. "
33 33 "Backends in the list are checked one by one and the first "
34 34 "usable one is used. Note that `matplotlib` backend "
35 35 "is usable only for inline style equations. To draw "
36 36 "display style equations, `dvipng` backend must be specified. ",
37 37 # It is a List instead of Enum, to make configuration more
38 38 # flexible. For example, to use matplotlib mainly but dvipng
39 39 # for display style, the default ["matplotlib", "dvipng"] can
40 40 # be used. To NOT use dvipng so that other repr such as
41 41 # unicode pretty printing is used, you can use ["matplotlib"].
42 42 ).tag(config=True)
43 43
44 44 use_breqn = Bool(
45 45 True,
46 46 help="Use breqn.sty to automatically break long equations. "
47 47 "This configuration takes effect only for dvipng backend.",
48 48 ).tag(config=True)
49 49
50 50 packages = List(
51 51 ['amsmath', 'amsthm', 'amssymb', 'bm'],
52 52 help="A list of packages to use for dvipng backend. "
53 53 "'breqn' will be automatically appended when use_breqn=True.",
54 54 ).tag(config=True)
55 55
56 56 preamble = Unicode(
57 57 help="Additional preamble to use when generating LaTeX source "
58 58 "for dvipng backend.",
59 59 ).tag(config=True)
60 60
61 61
62 62 def latex_to_png(s, encode=False, backend=None, wrap=False):
63 63 """Render a LaTeX string to PNG.
64 64
65 65 Parameters
66 66 ----------
67 s : text
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
76 76 None is returned when the backend cannot be used.
77 77
78 78 """
79 79 s = cast_unicode(s)
80 80 allowed_backends = LaTeXTool.instance().backends
81 81 if backend is None:
82 82 backend = allowed_backends[0]
83 83 if backend not in allowed_backends:
84 84 return None
85 85 if backend == 'matplotlib':
86 86 f = latex_to_png_mpl
87 87 elif backend == 'dvipng':
88 88 f = latex_to_png_dvipng
89 89 else:
90 90 raise ValueError('No such backend {0}'.format(backend))
91 91 bin_data = f(s, wrap)
92 92 if encode and bin_data:
93 93 bin_data = encodebytes(bin_data)
94 94 return bin_data
95 95
96 96
97 97 def latex_to_png_mpl(s, wrap):
98 98 try:
99 99 from matplotlib import mathtext
100 100 from pyparsing import ParseFatalException
101 101 except ImportError:
102 102 return None
103 103
104 104 # mpl mathtext doesn't support display math, force inline
105 105 s = s.replace('$$', '$')
106 106 if wrap:
107 107 s = u'${0}$'.format(s)
108 108
109 109 try:
110 110 mt = mathtext.MathTextParser('bitmap')
111 111 f = BytesIO()
112 112 mt.to_png(f, s, fontsize=12)
113 113 return f.getvalue()
114 114 except (ValueError, RuntimeError, ParseFatalException):
115 115 return None
116 116
117 117
118 118 def latex_to_png_dvipng(s, wrap):
119 119 try:
120 120 find_cmd('latex')
121 121 find_cmd('dvipng')
122 122 except FindCmdError:
123 123 return None
124 124 try:
125 125 workdir = tempfile.mkdtemp()
126 126 tmpfile = os.path.join(workdir, "tmp.tex")
127 127 dvifile = os.path.join(workdir, "tmp.dvi")
128 128 outfile = os.path.join(workdir, "tmp.png")
129 129
130 130 with open(tmpfile, "w", encoding='utf8') as f:
131 131 f.writelines(genelatex(s, wrap))
132 132
133 133 with open(os.devnull, 'wb') as devnull:
134 134 subprocess.check_call(
135 135 ["latex", "-halt-on-error", "-interaction", "batchmode", tmpfile],
136 136 cwd=workdir, stdout=devnull, stderr=devnull)
137 137
138 138 subprocess.check_call(
139 139 ["dvipng", "-T", "tight", "-x", "1500", "-z", "9",
140 140 "-bg", "transparent", "-o", outfile, dvifile], cwd=workdir,
141 141 stdout=devnull, stderr=devnull)
142 142
143 143 with open(outfile, "rb") as f:
144 144 return f.read()
145 145 except subprocess.CalledProcessError:
146 146 return None
147 147 finally:
148 148 shutil.rmtree(workdir)
149 149
150 150
151 151 def kpsewhich(filename):
152 152 """Invoke kpsewhich command with an argument `filename`."""
153 153 try:
154 154 find_cmd("kpsewhich")
155 155 proc = subprocess.Popen(
156 156 ["kpsewhich", filename],
157 157 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
158 158 (stdout, stderr) = proc.communicate()
159 159 return stdout.strip().decode('utf8', 'replace')
160 160 except FindCmdError:
161 161 pass
162 162
163 163
164 164 def genelatex(body, wrap):
165 165 """Generate LaTeX document for dvipng backend."""
166 166 lt = LaTeXTool.instance()
167 167 breqn = wrap and lt.use_breqn and kpsewhich("breqn.sty")
168 168 yield u(r'\documentclass{article}')
169 169 packages = lt.packages
170 170 if breqn:
171 171 packages = packages + ['breqn']
172 172 for pack in packages:
173 173 yield u(r'\usepackage{{{0}}}'.format(pack))
174 174 yield u(r'\pagestyle{empty}')
175 175 if lt.preamble:
176 176 yield lt.preamble
177 177 yield u(r'\begin{document}')
178 178 if breqn:
179 179 yield u(r'\begin{dmath*}')
180 180 yield body
181 181 yield u(r'\end{dmath*}')
182 182 elif wrap:
183 183 yield u'$${0}$$'.format(body)
184 184 else:
185 185 yield body
186 186 yield u'\end{document}'
187 187
188 188
189 189 _data_uri_template_png = u"""<img src="data:image/png;base64,%s" alt=%s />"""
190 190
191 191 def latex_to_html(s, alt='image'):
192 192 """Render LaTeX to HTML with embedded PNG data using data URIs.
193 193
194 194 Parameters
195 195 ----------
196 196 s : str
197 197 The raw string containing valid inline LateX.
198 198 alt : str
199 199 The alt text to use for the HTML.
200 200 """
201 201 base64_data = latex_to_png(s, encode=True).decode('ascii')
202 202 if base64_data:
203 203 return _data_uri_template_png % (base64_data, alt)
204 204
205 205
@@ -1,128 +1,128 b''
1 1 """Token-related utilities"""
2 2
3 3 # Copyright (c) IPython Development Team.
4 4 # Distributed under the terms of the Modified BSD License.
5 5
6 6 from __future__ import absolute_import, print_function
7 7
8 8 from collections import namedtuple
9 9 from io import StringIO
10 10 from keyword import iskeyword
11 11
12 12 from . import tokenize2
13 13 from .py3compat import cast_unicode_py2
14 14
15 15 Token = namedtuple('Token', ['token', 'text', 'start', 'end', 'line'])
16 16
17 17 def generate_tokens(readline):
18 18 """wrap generate_tokens to catch EOF errors"""
19 19 try:
20 20 for token in tokenize2.generate_tokens(readline):
21 21 yield token
22 22 except tokenize2.TokenError:
23 23 # catch EOF error
24 24 return
25 25
26 26 def line_at_cursor(cell, cursor_pos=0):
27 27 """Return the line in a cell at a given cursor position
28 28
29 29 Used for calling line-based APIs that don't support multi-line input, yet.
30 30
31 31 Parameters
32 32 ----------
33 33
34 cell: text
34 cell: str
35 35 multiline block of text
36 36 cursor_pos: integer
37 37 the cursor position
38 38
39 39 Returns
40 40 -------
41 41
42 42 (line, offset): (text, integer)
43 43 The line with the current cursor, and the character offset of the start of the line.
44 44 """
45 45 offset = 0
46 46 lines = cell.splitlines(True)
47 47 for line in lines:
48 48 next_offset = offset + len(line)
49 49 if next_offset >= cursor_pos:
50 50 break
51 51 offset = next_offset
52 52 else:
53 53 line = ""
54 54 return (line, offset)
55 55
56 56 def token_at_cursor(cell, cursor_pos=0):
57 57 """Get the token at a given cursor
58 58
59 59 Used for introspection.
60 60
61 61 Function calls are prioritized, so the token for the callable will be returned
62 62 if the cursor is anywhere inside the call.
63 63
64 64 Parameters
65 65 ----------
66 66
67 67 cell : unicode
68 68 A block of Python code
69 69 cursor_pos : int
70 70 The location of the cursor in the block where the token should be found
71 71 """
72 72 cell = cast_unicode_py2(cell)
73 73 names = []
74 74 tokens = []
75 75 call_names = []
76 76
77 77 offsets = {1: 0} # lines start at 1
78 78 for tup in generate_tokens(StringIO(cell).readline):
79 79
80 80 tok = Token(*tup)
81 81
82 82 # token, text, start, end, line = tup
83 83 start_line, start_col = tok.start
84 84 end_line, end_col = tok.end
85 85 if end_line + 1 not in offsets:
86 86 # keep track of offsets for each line
87 87 lines = tok.line.splitlines(True)
88 88 for lineno, line in zip(range(start_line + 1, end_line + 2), lines):
89 89 if lineno not in offsets:
90 90 offsets[lineno] = offsets[lineno-1] + len(line)
91 91
92 92 offset = offsets[start_line]
93 93 # allow '|foo' to find 'foo' at the beginning of a line
94 94 boundary = cursor_pos + 1 if start_col == 0 else cursor_pos
95 95 if offset + start_col >= boundary:
96 96 # current token starts after the cursor,
97 97 # don't consume it
98 98 break
99 99
100 100 if tok.token == tokenize2.NAME and not iskeyword(tok.text):
101 101 if names and tokens and tokens[-1].token == tokenize2.OP and tokens[-1].text == '.':
102 102 names[-1] = "%s.%s" % (names[-1], tok.text)
103 103 else:
104 104 names.append(tok.text)
105 105 elif tok.token == tokenize2.OP:
106 106 if tok.text == '=' and names:
107 107 # don't inspect the lhs of an assignment
108 108 names.pop(-1)
109 109 if tok.text == '(' and names:
110 110 # if we are inside a function call, inspect the function
111 111 call_names.append(names[-1])
112 112 elif tok.text == ')' and call_names:
113 113 call_names.pop(-1)
114 114
115 115 tokens.append(tok)
116 116
117 117 if offsets[end_line] + end_col > cursor_pos:
118 118 # we found the cursor, stop reading
119 119 break
120 120
121 121 if call_names:
122 122 return call_names[-1]
123 123 elif names:
124 124 return names[-1]
125 125 else:
126 126 return ''
127 127
128 128
General Comments 0
You need to be logged in to leave comments. Login now