##// END OF EJS Templates
Use breqn.sty in dvipng backend if possible
Takafumi Arakaki -
Show More
@@ -1,151 +1,151 b''
1 """
1 """
2 A print function that pretty prints sympy Basic objects.
2 A print function that pretty prints sympy Basic objects.
3
3
4 :moduleauthor: Brian Granger
4 :moduleauthor: Brian Granger
5
5
6 Usage
6 Usage
7 =====
7 =====
8
8
9 Once the extension is loaded, Sympy Basic objects are automatically
9 Once the extension is loaded, Sympy Basic objects are automatically
10 pretty-printed.
10 pretty-printed.
11
11
12 """
12 """
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Copyright (C) 2008 The IPython Development Team
14 # Copyright (C) 2008 The IPython Development Team
15 #
15 #
16 # Distributed under the terms of the BSD License. The full license is in
16 # Distributed under the terms of the BSD License. The full license is in
17 # the file COPYING, distributed as part of this software.
17 # the file COPYING, distributed as part of this software.
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19
19
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21 # Imports
21 # Imports
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23
23
24 from IPython.lib.latextools import latex_to_png
24 from IPython.lib.latextools import latex_to_png
25 from IPython.testing import decorators as dec
25 from IPython.testing import decorators as dec
26 # use @dec.skipif_not_sympy to skip tests requiring sympy
26 # use @dec.skipif_not_sympy to skip tests requiring sympy
27
27
28 try:
28 try:
29 from sympy import pretty, latex
29 from sympy import pretty, latex
30 except ImportError:
30 except ImportError:
31 pass
31 pass
32
32
33 #-----------------------------------------------------------------------------
33 #-----------------------------------------------------------------------------
34 # Definitions of special display functions for use with IPython
34 # Definitions of special display functions for use with IPython
35 #-----------------------------------------------------------------------------
35 #-----------------------------------------------------------------------------
36
36
37 def print_basic_unicode(o, p, cycle):
37 def print_basic_unicode(o, p, cycle):
38 """A function to pretty print sympy Basic objects."""
38 """A function to pretty print sympy Basic objects."""
39 if cycle:
39 if cycle:
40 return p.text('Basic(...)')
40 return p.text('Basic(...)')
41 out = pretty(o, use_unicode=True)
41 out = pretty(o, use_unicode=True)
42 if '\n' in out:
42 if '\n' in out:
43 p.text(u'\n')
43 p.text(u'\n')
44 p.text(out)
44 p.text(out)
45
45
46
46
47 def print_png(o):
47 def print_png(o):
48 """
48 """
49 A function to display sympy expression using inline style LaTeX in PNG.
49 A function to display sympy expression using inline style LaTeX in PNG.
50 """
50 """
51 s = latex(o, mode='inline')
51 s = latex(o, mode='inline')
52 # mathtext does not understand certain latex flags, so we try to replace
52 # mathtext does not understand certain latex flags, so we try to replace
53 # them with suitable subs.
53 # them with suitable subs.
54 s = s.replace('\\operatorname','')
54 s = s.replace('\\operatorname','')
55 s = s.replace('\\overline', '\\bar')
55 s = s.replace('\\overline', '\\bar')
56 png = latex_to_png(s)
56 png = latex_to_png(s)
57 return png
57 return png
58
58
59
59
60 def print_display_png(o):
60 def print_display_png(o):
61 """
61 """
62 A function to display sympy expression using display style LaTeX in PNG.
62 A function to display sympy expression using display style LaTeX in PNG.
63 """
63 """
64 s = latex(o, mode='plain')
64 s = latex(o, mode='plain')
65 s = s.strip('$')
65 s = s.strip('$')
66 # As matplotlib does not support display style, dvipng backend is
66 # As matplotlib does not support display style, dvipng backend is
67 # used here.
67 # used here.
68 png = latex_to_png('$$%s$$' % s, backend='dvipng')
68 png = latex_to_png(s, backend='dvipng', wrap=True)
69 return png
69 return png
70
70
71
71
72 def can_print_latex(o):
72 def can_print_latex(o):
73 """
73 """
74 Return True if type o can be printed with LaTeX.
74 Return True if type o can be printed with LaTeX.
75
75
76 If o is a container type, this is True if and only if every element of o
76 If o is a container type, this is True if and only if every element of o
77 can be printed with LaTeX.
77 can be printed with LaTeX.
78 """
78 """
79 import sympy
79 import sympy
80 if isinstance(o, (list, tuple, set, frozenset)):
80 if isinstance(o, (list, tuple, set, frozenset)):
81 return all(can_print_latex(i) for i in o)
81 return all(can_print_latex(i) for i in o)
82 elif isinstance(o, dict):
82 elif isinstance(o, dict):
83 return all((isinstance(i, basestring) or can_print_latex(i)) and can_print_latex(o[i]) for i in o)
83 return all((isinstance(i, basestring) or can_print_latex(i)) and can_print_latex(o[i]) for i in o)
84 elif isinstance(o,(sympy.Basic, sympy.matrices.Matrix, int, long, float)):
84 elif isinstance(o,(sympy.Basic, sympy.matrices.Matrix, int, long, float)):
85 return True
85 return True
86 return False
86 return False
87
87
88 def print_latex(o):
88 def print_latex(o):
89 """A function to generate the latex representation of sympy
89 """A function to generate the latex representation of sympy
90 expressions."""
90 expressions."""
91 if can_print_latex(o):
91 if can_print_latex(o):
92 s = latex(o, mode='plain')
92 s = latex(o, mode='plain')
93 s = s.replace('\\dag','\\dagger')
93 s = s.replace('\\dag','\\dagger')
94 s = s.strip('$')
94 s = s.strip('$')
95 return '$$%s$$' % s
95 return '$$%s$$' % s
96 # Fallback to the string printer
96 # Fallback to the string printer
97 return None
97 return None
98
98
99 _loaded = False
99 _loaded = False
100
100
101 def load_ipython_extension(ip):
101 def load_ipython_extension(ip):
102 """Load the extension in IPython."""
102 """Load the extension in IPython."""
103 import sympy
103 import sympy
104 global _loaded
104 global _loaded
105 if not _loaded:
105 if not _loaded:
106 plaintext_formatter = ip.display_formatter.formatters['text/plain']
106 plaintext_formatter = ip.display_formatter.formatters['text/plain']
107
107
108 for cls in (object, str):
108 for cls in (object, str):
109 plaintext_formatter.for_type(cls, print_basic_unicode)
109 plaintext_formatter.for_type(cls, print_basic_unicode)
110
110
111 printable_containers = [list, tuple]
111 printable_containers = [list, tuple]
112
112
113 # set and frozen set were broken with SymPy's latex() function, but
113 # set and frozen set were broken with SymPy's latex() function, but
114 # was fixed in the 0.7.1-git development version. See
114 # was fixed in the 0.7.1-git development version. See
115 # http://code.google.com/p/sympy/issues/detail?id=3062.
115 # http://code.google.com/p/sympy/issues/detail?id=3062.
116 if sympy.__version__ > '0.7.1':
116 if sympy.__version__ > '0.7.1':
117 printable_containers += [set, frozenset]
117 printable_containers += [set, frozenset]
118 else:
118 else:
119 plaintext_formatter.for_type(cls, print_basic_unicode)
119 plaintext_formatter.for_type(cls, print_basic_unicode)
120
120
121 plaintext_formatter.for_type_by_name(
121 plaintext_formatter.for_type_by_name(
122 'sympy.core.basic', 'Basic', print_basic_unicode
122 'sympy.core.basic', 'Basic', print_basic_unicode
123 )
123 )
124 plaintext_formatter.for_type_by_name(
124 plaintext_formatter.for_type_by_name(
125 'sympy.matrices.matrices', 'Matrix', print_basic_unicode
125 'sympy.matrices.matrices', 'Matrix', print_basic_unicode
126 )
126 )
127
127
128 png_formatter = ip.display_formatter.formatters['image/png']
128 png_formatter = ip.display_formatter.formatters['image/png']
129
129
130 png_formatter.for_type_by_name(
130 png_formatter.for_type_by_name(
131 'sympy.core.basic', 'Basic', print_png
131 'sympy.core.basic', 'Basic', print_png
132 )
132 )
133 png_formatter.for_type_by_name(
133 png_formatter.for_type_by_name(
134 'sympy.matrices.matrices', 'Matrix', print_display_png
134 'sympy.matrices.matrices', 'Matrix', print_display_png
135 )
135 )
136 for cls in [dict, int, long, float] + printable_containers:
136 for cls in [dict, int, long, float] + printable_containers:
137 png_formatter.for_type(cls, print_png)
137 png_formatter.for_type(cls, print_png)
138
138
139 latex_formatter = ip.display_formatter.formatters['text/latex']
139 latex_formatter = ip.display_formatter.formatters['text/latex']
140 latex_formatter.for_type_by_name(
140 latex_formatter.for_type_by_name(
141 'sympy.core.basic', 'Basic', print_latex
141 'sympy.core.basic', 'Basic', print_latex
142 )
142 )
143 latex_formatter.for_type_by_name(
143 latex_formatter.for_type_by_name(
144 'sympy.matrices.matrices', 'Matrix', print_latex
144 'sympy.matrices.matrices', 'Matrix', print_latex
145 )
145 )
146
146
147 for cls in printable_containers:
147 for cls in printable_containers:
148 # Use LaTeX only if every element is printable by latex
148 # Use LaTeX only if every element is printable by latex
149 latex_formatter.for_type(cls, print_latex)
149 latex_formatter.for_type(cls, print_latex)
150
150
151 _loaded = True
151 _loaded = True
@@ -1,181 +1,205 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Tools for handling LaTeX.
2 """Tools for handling LaTeX.
3
3
4 Authors:
4 Authors:
5
5
6 * Brian Granger
6 * Brian Granger
7 """
7 """
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2010 IPython Development Team.
9 # Copyright (C) 2010 IPython Development Team.
10 #
10 #
11 # Distributed under the terms of the Modified BSD License.
11 # Distributed under the terms of the Modified BSD License.
12 #
12 #
13 # The full license is in the file COPYING.txt, distributed with this software.
13 # The full license is in the file COPYING.txt, distributed with this software.
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15
15
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17 # Imports
17 # Imports
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19
19
20 from StringIO import StringIO
20 from StringIO import StringIO
21 from base64 import encodestring
21 from base64 import encodestring
22 import os
22 import os
23 import tempfile
23 import tempfile
24 import shutil
24 import shutil
25 import subprocess
25 import subprocess
26
26
27 from IPython.utils.process import find_cmd, FindCmdError
27 from IPython.utils.process import find_cmd, FindCmdError
28
28
29 #-----------------------------------------------------------------------------
29 #-----------------------------------------------------------------------------
30 # Tools
30 # Tools
31 #-----------------------------------------------------------------------------
31 #-----------------------------------------------------------------------------
32
32
33
33
34 def latex_to_png(s, encode=False, backend='mpl'):
34 def latex_to_png(s, encode=False, backend='mpl', wrap=False):
35 """Render a LaTeX string to PNG.
35 """Render a LaTeX string to PNG.
36
36
37 Parameters
37 Parameters
38 ----------
38 ----------
39 s : str
39 s : str
40 The raw string containing valid inline LaTeX.
40 The raw string containing valid inline LaTeX.
41 encode : bool, optional
41 encode : bool, optional
42 Should the PNG data bebase64 encoded to make it JSON'able.
42 Should the PNG data bebase64 encoded to make it JSON'able.
43 backend : {mpl, dvipng}
43 backend : {mpl, dvipng}
44 Backend for producing PNG data.
44 Backend for producing PNG data.
45 wrap : bool
46 If true, Automatically wrap `s` as a LaTeX equation.
45
47
46 None is returned when the backend cannot be used.
48 None is returned when the backend cannot be used.
47
49
48 """
50 """
49 if backend == 'mpl':
51 if backend == 'mpl':
50 f = latex_to_png_mpl
52 f = latex_to_png_mpl
51 elif backend == 'dvipng':
53 elif backend == 'dvipng':
52 f = latex_to_png_dvipng
54 f = latex_to_png_dvipng
53 else:
55 else:
54 raise ValueError('No such backend {0}'.format(backend))
56 raise ValueError('No such backend {0}'.format(backend))
55 bin_data = f(s)
57 bin_data = f(s, wrap)
56 if encode and bin_data:
58 if encode and bin_data:
57 bin_data = encodestring(bin_data)
59 bin_data = encodestring(bin_data)
58 return bin_data
60 return bin_data
59
61
60
62
61 def latex_to_png_mpl(s):
63 def latex_to_png_mpl(s, wrap):
62 try:
64 try:
63 from matplotlib import mathtext
65 from matplotlib import mathtext
64 except ImportError:
66 except ImportError:
65 return None
67 return None
66
68
69 if wrap:
70 s = '${0}$'.format(s)
67 mt = mathtext.MathTextParser('bitmap')
71 mt = mathtext.MathTextParser('bitmap')
68 f = StringIO()
72 f = StringIO()
69 mt.to_png(f, s, fontsize=12)
73 mt.to_png(f, s, fontsize=12)
70 return f.getvalue()
74 return f.getvalue()
71
75
72
76
73 def latex_to_png_dvipng(s):
77 def latex_to_png_dvipng(s, wrap):
74 try:
78 try:
75 find_cmd('latex')
79 find_cmd('latex')
76 find_cmd('dvipng')
80 find_cmd('dvipng')
77 except FindCmdError:
81 except FindCmdError:
78 return None
82 return None
79 try:
83 try:
80 workdir = tempfile.mkdtemp()
84 workdir = tempfile.mkdtemp()
81 tmpfile = os.path.join(workdir, "tmp.tex")
85 tmpfile = os.path.join(workdir, "tmp.tex")
82 dvifile = os.path.join(workdir, "tmp.dvi")
86 dvifile = os.path.join(workdir, "tmp.dvi")
83 outfile = os.path.join(workdir, "tmp.png")
87 outfile = os.path.join(workdir, "tmp.png")
84
88
85 with open(tmpfile, "w") as f:
89 with open(tmpfile, "w") as f:
86 f.write(_latex_header)
90 f.writelines(genelatex(s, wrap))
87 f.write(s)
88 f.write(_latex_footer)
89
91
90 subprocess.check_call(
92 subprocess.check_call(
91 ["latex", "-halt-on-errror", tmpfile], cwd=workdir,
93 ["latex", "-halt-on-errror", tmpfile], cwd=workdir,
92 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
94 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
93
95
94 subprocess.check_call(
96 subprocess.check_call(
95 ["dvipng", "-T", "tight", "-x", "1500", "-z", "9",
97 ["dvipng", "-T", "tight", "-x", "1500", "-z", "9",
96 "-bg", "transparent", "-o", outfile, dvifile], cwd=workdir,
98 "-bg", "transparent", "-o", outfile, dvifile], cwd=workdir,
97 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
99 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
98
100
99 with open(outfile) as f:
101 with open(outfile) as f:
100 bin_data = f.read()
102 bin_data = f.read()
101 finally:
103 finally:
102 shutil.rmtree(workdir)
104 shutil.rmtree(workdir)
103 return bin_data
105 return bin_data
104
106
105
107
106 _latex_header = r'''
108 def kpsewhich(filename):
107 \documentclass{article}
109 """Invoke kpsewhich command with an argument `filename`."""
108 \usepackage{amsmath}
110 try:
109 \usepackage{amsthm}
111 find_cmd("kpsewhich")
110 \usepackage{amssymb}
112 proc = subprocess.Popen(
111 \usepackage{bm}
113 ["kpsewhich", filename],
112 \pagestyle{empty}
114 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
113 \begin{document}
115 (stdout, stderr) = proc.communicate()
114 '''
116 return stdout.strip()
115
117 except FindCmdError:
116 _latex_footer = r'\end{document}'
118 pass
119
120
121 def genelatex(body, wrap):
122 """Generate LaTeX document for dvipng backend."""
123 breqn = wrap and kpsewhich("breqn.sty")
124 yield r'\documentclass{article}'
125 packages = ['amsmath', 'amsthm', 'amssymb', 'bm']
126 if breqn:
127 packages.append('breqn')
128 for pack in packages:
129 yield r'\usepackage{{{0}}}'.format(pack)
130 yield r'\pagestyle{empty}'
131 yield r'\begin{document}'
132 if breqn:
133 yield r'\begin{dmath*}'
134 yield body
135 yield r'\end{dmath*}'
136 elif wrap:
137 yield '$${0}$$'.format(body)
138 else:
139 yield body
140 yield r'\end{document}'
117
141
118
142
119 _data_uri_template_png = """<img src="data:image/png;base64,%s" alt=%s />"""
143 _data_uri_template_png = """<img src="data:image/png;base64,%s" alt=%s />"""
120
144
121 def latex_to_html(s, alt='image'):
145 def latex_to_html(s, alt='image'):
122 """Render LaTeX to HTML with embedded PNG data using data URIs.
146 """Render LaTeX to HTML with embedded PNG data using data URIs.
123
147
124 Parameters
148 Parameters
125 ----------
149 ----------
126 s : str
150 s : str
127 The raw string containing valid inline LateX.
151 The raw string containing valid inline LateX.
128 alt : str
152 alt : str
129 The alt text to use for the HTML.
153 The alt text to use for the HTML.
130 """
154 """
131 base64_data = latex_to_png(s, encode=True)
155 base64_data = latex_to_png(s, encode=True)
132 if base64_data:
156 if base64_data:
133 return _data_uri_template_png % (base64_data, alt)
157 return _data_uri_template_png % (base64_data, alt)
134
158
135
159
136 # From matplotlib, thanks to mdboom. Once this is in matplotlib releases, we
160 # From matplotlib, thanks to mdboom. Once this is in matplotlib releases, we
137 # will remove.
161 # will remove.
138 def math_to_image(s, filename_or_obj, prop=None, dpi=None, format=None):
162 def math_to_image(s, filename_or_obj, prop=None, dpi=None, format=None):
139 """
163 """
140 Given a math expression, renders it in a closely-clipped bounding
164 Given a math expression, renders it in a closely-clipped bounding
141 box to an image file.
165 box to an image file.
142
166
143 *s*
167 *s*
144 A math expression. The math portion should be enclosed in
168 A math expression. The math portion should be enclosed in
145 dollar signs.
169 dollar signs.
146
170
147 *filename_or_obj*
171 *filename_or_obj*
148 A filepath or writable file-like object to write the image data
172 A filepath or writable file-like object to write the image data
149 to.
173 to.
150
174
151 *prop*
175 *prop*
152 If provided, a FontProperties() object describing the size and
176 If provided, a FontProperties() object describing the size and
153 style of the text.
177 style of the text.
154
178
155 *dpi*
179 *dpi*
156 Override the output dpi, otherwise use the default associated
180 Override the output dpi, otherwise use the default associated
157 with the output format.
181 with the output format.
158
182
159 *format*
183 *format*
160 The output format, eg. 'svg', 'pdf', 'ps' or 'png'. If not
184 The output format, eg. 'svg', 'pdf', 'ps' or 'png'. If not
161 provided, will be deduced from the filename.
185 provided, will be deduced from the filename.
162 """
186 """
163 from matplotlib import figure
187 from matplotlib import figure
164 # backend_agg supports all of the core output formats
188 # backend_agg supports all of the core output formats
165 from matplotlib.backends import backend_agg
189 from matplotlib.backends import backend_agg
166 from matplotlib.font_manager import FontProperties
190 from matplotlib.font_manager import FontProperties
167 from matplotlib.mathtext import MathTextParser
191 from matplotlib.mathtext import MathTextParser
168
192
169 if prop is None:
193 if prop is None:
170 prop = FontProperties()
194 prop = FontProperties()
171
195
172 parser = MathTextParser('path')
196 parser = MathTextParser('path')
173 width, height, depth, _, _ = parser.parse(s, dpi=72, prop=prop)
197 width, height, depth, _, _ = parser.parse(s, dpi=72, prop=prop)
174
198
175 fig = figure.Figure(figsize=(width / 72.0, height / 72.0))
199 fig = figure.Figure(figsize=(width / 72.0, height / 72.0))
176 fig.text(0, depth/height, s, fontproperties=prop)
200 fig.text(0, depth/height, s, fontproperties=prop)
177 backend_agg.FigureCanvasAgg(fig)
201 backend_agg.FigureCanvasAgg(fig)
178 fig.savefig(filename_or_obj, dpi=dpi, format=format)
202 fig.savefig(filename_or_obj, dpi=dpi, format=format)
179
203
180 return depth
204 return depth
181
205
General Comments 0
You need to be logged in to leave comments. Login now