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