##// END OF EJS Templates
Small fixes for latex/png printing....
Brian Granger -
Show More
@@ -1,57 +1,60 b''
1 """A print function that pretty prints sympy Basic objects.
1 """A print function that pretty prints sympy Basic objects.
2
2
3 Authors:
3 Authors:
4 * Brian Granger
4 * Brian Granger
5 """
5 """
6 #-----------------------------------------------------------------------------
6 #-----------------------------------------------------------------------------
7 # Copyright (C) 2008-2011 The IPython Development Team
7 # Copyright (C) 2008-2011 The IPython Development Team
8 #
8 #
9 # Distributed under the terms of the BSD License. The full license is in
9 # Distributed under the terms of the BSD License. The full license is in
10 # the file COPYING, distributed as part of this software.
10 # the file COPYING, distributed as part of this software.
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16
16
17 from IPython.lib.latextools import latex_to_png
17 from IPython.lib.latextools import latex_to_png
18
18
19 from sympy import pretty, latex
19 from sympy import pretty, latex
20
20
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22 # Definitions of magic functions for use with IPython
22 # Definitions of magic functions for use with IPython
23 #-----------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
24
24
25 def print_basic_unicode(o, p, cycle):
25 def print_basic_unicode(o, p, cycle):
26 """A function to pretty print sympy Basic objects."""
26 """A function to pretty print sympy Basic objects."""
27 if cycle:
27 if cycle:
28 return p.text('Basic(...)')
28 return p.text('Basic(...)')
29 out = pretty(o, use_unicode=True)
29 out = pretty(o, use_unicode=True)
30 if '\n' in out:
30 if '\n' in out:
31 p.text(u'\n')
31 p.text(u'\n')
32 p.text(out)
32 p.text(out)
33
33
34
34
35 def print_png(o):
35 def print_png(o):
36 """A funciton to display sympy expression using LaTex -> PNG."""
36 """A funciton to display sympy expression using LaTex -> PNG."""
37 s = latex(o, mode='inline')
37 s = latex(o, mode='inline')
38 # mathtext does not understand \\operatorname to we remove it so functions
39 # like sin, cos can print. We should possible replace it with mathrm.
40 s = s.replace('\\operatorname','')
38 png = latex_to_png(s, encode=True)
41 png = latex_to_png(s, encode=True)
39 return png
42 return png
40
43
41 _loaded = False
44 _loaded = False
42
45
43
46
44 def load_ipython_extension(ip):
47 def load_ipython_extension(ip):
45 """Load the extension in IPython."""
48 """Load the extension in IPython."""
46 global _loaded
49 global _loaded
47 if not _loaded:
50 if not _loaded:
48 plaintext_formatter = ip.display_formatter.formatters['text/plain']
51 plaintext_formatter = ip.display_formatter.formatters['text/plain']
49 plaintext_formatter.for_type_by_name(
52 plaintext_formatter.for_type_by_name(
50 'sympy.core.basic', 'Basic', print_basic_unicode
53 'sympy.core.basic', 'Basic', print_basic_unicode
51 )
54 )
52 png_formatter = ip.display_formatter.formatters['image/png']
55 png_formatter = ip.display_formatter.formatters['image/png']
53 png_formatter.for_type_by_name(
56 png_formatter.for_type_by_name(
54 'sympy.core.basic', 'Basic', print_png
57 'sympy.core.basic', 'Basic', print_png
55 )
58 )
56 _loaded = True
59 _loaded = True
57
60
@@ -1,269 +1,271 b''
1 # System library imports
1 # System library imports
2 import os
2 import os
3 import re
3 import re
4 from base64 import decodestring
4 from base64 import decodestring
5 from PyQt4 import QtCore, QtGui
5 from PyQt4 import QtCore, QtGui
6
6
7 # Local imports
7 # Local imports
8 from IPython.frontend.qt.svg import save_svg, svg_to_clipboard, svg_to_image
8 from IPython.frontend.qt.svg import save_svg, svg_to_clipboard, svg_to_image
9 from ipython_widget import IPythonWidget
9 from ipython_widget import IPythonWidget
10
10
11
11
12 class RichIPythonWidget(IPythonWidget):
12 class RichIPythonWidget(IPythonWidget):
13 """ An IPythonWidget that supports rich text, including lists, images, and
13 """ An IPythonWidget that supports rich text, including lists, images, and
14 tables. Note that raw performance will be reduced compared to the plain
14 tables. Note that raw performance will be reduced compared to the plain
15 text version.
15 text version.
16 """
16 """
17
17
18 # RichIPythonWidget protected class variables.
18 # RichIPythonWidget protected class variables.
19 _payload_source_plot = 'IPython.zmq.pylab.backend_payload.add_plot_payload'
19 _payload_source_plot = 'IPython.zmq.pylab.backend_payload.add_plot_payload'
20 _svg_text_format_property = 1
20 _svg_text_format_property = 1
21
21
22 #---------------------------------------------------------------------------
22 #---------------------------------------------------------------------------
23 # 'object' interface
23 # 'object' interface
24 #---------------------------------------------------------------------------
24 #---------------------------------------------------------------------------
25
25
26 def __init__(self, *args, **kw):
26 def __init__(self, *args, **kw):
27 """ Create a RichIPythonWidget.
27 """ Create a RichIPythonWidget.
28 """
28 """
29 kw['kind'] = 'rich'
29 kw['kind'] = 'rich'
30 super(RichIPythonWidget, self).__init__(*args, **kw)
30 super(RichIPythonWidget, self).__init__(*args, **kw)
31 # Dictionary for resolving Qt names to images when
31 # Dictionary for resolving Qt names to images when
32 # generating XHTML output
32 # generating XHTML output
33 self._name_to_svg = {}
33 self._name_to_svg = {}
34
34
35 #---------------------------------------------------------------------------
35 #---------------------------------------------------------------------------
36 # 'ConsoleWidget' protected interface
36 # 'ConsoleWidget' protected interface
37 #---------------------------------------------------------------------------
37 #---------------------------------------------------------------------------
38
38
39 def _context_menu_make(self, pos):
39 def _context_menu_make(self, pos):
40 """ Reimplemented to return a custom context menu for images.
40 """ Reimplemented to return a custom context menu for images.
41 """
41 """
42 format = self._control.cursorForPosition(pos).charFormat()
42 format = self._control.cursorForPosition(pos).charFormat()
43 name = format.stringProperty(QtGui.QTextFormat.ImageName)
43 name = format.stringProperty(QtGui.QTextFormat.ImageName)
44 if name.isEmpty():
44 if name.isEmpty():
45 menu = super(RichIPythonWidget, self)._context_menu_make(pos)
45 menu = super(RichIPythonWidget, self)._context_menu_make(pos)
46 else:
46 else:
47 menu = QtGui.QMenu()
47 menu = QtGui.QMenu()
48
48
49 menu.addAction('Copy Image', lambda: self._copy_image(name))
49 menu.addAction('Copy Image', lambda: self._copy_image(name))
50 menu.addAction('Save Image As...', lambda: self._save_image(name))
50 menu.addAction('Save Image As...', lambda: self._save_image(name))
51 menu.addSeparator()
51 menu.addSeparator()
52
52
53 svg = format.stringProperty(self._svg_text_format_property)
53 svg = format.stringProperty(self._svg_text_format_property)
54 if not svg.isEmpty():
54 if not svg.isEmpty():
55 menu.addSeparator()
55 menu.addSeparator()
56 menu.addAction('Copy SVG', lambda: svg_to_clipboard(svg))
56 menu.addAction('Copy SVG', lambda: svg_to_clipboard(svg))
57 menu.addAction('Save SVG As...',
57 menu.addAction('Save SVG As...',
58 lambda: save_svg(svg, self._control))
58 lambda: save_svg(svg, self._control))
59 return menu
59 return menu
60
60
61 #---------------------------------------------------------------------------
61 #---------------------------------------------------------------------------
62 # 'BaseFrontendMixin' abstract interface
62 # 'BaseFrontendMixin' abstract interface
63 #---------------------------------------------------------------------------
63 #---------------------------------------------------------------------------
64
64
65 def _handle_pyout(self, msg):
65 def _handle_pyout(self, msg):
66 """ Overridden to handle rich data types, like SVG.
66 """ Overridden to handle rich data types, like SVG.
67 """
67 """
68 if not self._hidden and self._is_from_this_session(msg):
68 if not self._hidden and self._is_from_this_session(msg):
69 content = msg['content']
69 content = msg['content']
70 prompt_number = content['execution_count']
70 prompt_number = content['execution_count']
71 data = content['data']
71 data = content['data']
72 if data.has_key('image/svg+xml'):
72 if data.has_key('image/svg+xml'):
73 self._append_plain_text(self.output_sep)
73 self._append_plain_text(self.output_sep)
74 self._append_html(self._make_out_prompt(prompt_number))
74 self._append_html(self._make_out_prompt(prompt_number))
75 # TODO: try/except this call.
75 # TODO: try/except this call.
76 self._append_svg(data['image/svg+xml'])
76 self._append_svg(data['image/svg+xml'])
77 self._append_html(self.output_sep2)
77 self._append_html(self.output_sep2)
78 elif data.has_key('image/png'):
78 elif data.has_key('image/png'):
79 self._append_plain_text(self.output_sep)
79 self._append_plain_text(self.output_sep)
80 self._append_html(self._make_out_prompt(prompt_number))
80 self._append_html(self._make_out_prompt(prompt_number))
81 # This helps the output to look nice.
82 self._append_plain_text('\n')
81 # TODO: try/except these calls
83 # TODO: try/except these calls
82 png = decodestring(data['image/png'])
84 png = decodestring(data['image/png'])
83 self._append_png(png)
85 self._append_png(png)
84 self._append_html(self.output_sep2)
86 self._append_html(self.output_sep2)
85 else:
87 else:
86 # Default back to the plain text representation.
88 # Default back to the plain text representation.
87 return super(RichIPythonWidget, self)._handle_pyout(msg)
89 return super(RichIPythonWidget, self)._handle_pyout(msg)
88
90
89 def _handle_display_data(self, msg):
91 def _handle_display_data(self, msg):
90 """ Overridden to handle rich data types, like SVG.
92 """ Overridden to handle rich data types, like SVG.
91 """
93 """
92 if not self._hidden and self._is_from_this_session(msg):
94 if not self._hidden and self._is_from_this_session(msg):
93 source = msg['content']['source']
95 source = msg['content']['source']
94 data = msg['content']['data']
96 data = msg['content']['data']
95 metadata = msg['content']['metadata']
97 metadata = msg['content']['metadata']
96 # Try to use the svg or html representations.
98 # Try to use the svg or html representations.
97 # FIXME: Is this the right ordering of things to try?
99 # FIXME: Is this the right ordering of things to try?
98 if data.has_key('image/svg+xml'):
100 if data.has_key('image/svg+xml'):
99 svg = data['image/svg+xml']
101 svg = data['image/svg+xml']
100 # TODO: try/except this call.
102 # TODO: try/except this call.
101 self._append_svg(svg)
103 self._append_svg(svg)
102 elif data.has_key('image/png'):
104 elif data.has_key('image/png'):
103 # TODO: try/except these calls
105 # TODO: try/except these calls
104 # PNG data is base64 encoded as it passes over the network
106 # PNG data is base64 encoded as it passes over the network
105 # in a JSON structure so we decode it.
107 # in a JSON structure so we decode it.
106 png = decodestring(data['image/png'])
108 png = decodestring(data['image/png'])
107 self._append_png(png)
109 self._append_png(png)
108 else:
110 else:
109 # Default back to the plain text representation.
111 # Default back to the plain text representation.
110 return super(RichIPythonWidget, self)._handle_display_data(msg)
112 return super(RichIPythonWidget, self)._handle_display_data(msg)
111
113
112 #---------------------------------------------------------------------------
114 #---------------------------------------------------------------------------
113 # 'FrontendWidget' protected interface
115 # 'FrontendWidget' protected interface
114 #---------------------------------------------------------------------------
116 #---------------------------------------------------------------------------
115
117
116 def _process_execute_payload(self, item):
118 def _process_execute_payload(self, item):
117 """ Reimplemented to handle matplotlib plot payloads.
119 """ Reimplemented to handle matplotlib plot payloads.
118 """
120 """
119 # TODO: remove this as all plot data is coming back through the
121 # TODO: remove this as all plot data is coming back through the
120 # display_data message type.
122 # display_data message type.
121 if item['source'] == self._payload_source_plot:
123 if item['source'] == self._payload_source_plot:
122 if item['format'] == 'svg':
124 if item['format'] == 'svg':
123 svg = item['data']
125 svg = item['data']
124 self._append_svg(svg)
126 self._append_svg(svg)
125 return True
127 return True
126 else:
128 else:
127 # Add other plot formats here!
129 # Add other plot formats here!
128 return False
130 return False
129 else:
131 else:
130 return super(RichIPythonWidget, self)._process_execute_payload(item)
132 return super(RichIPythonWidget, self)._process_execute_payload(item)
131
133
132 #---------------------------------------------------------------------------
134 #---------------------------------------------------------------------------
133 # 'RichIPythonWidget' protected interface
135 # 'RichIPythonWidget' protected interface
134 #---------------------------------------------------------------------------
136 #---------------------------------------------------------------------------
135
137
136 def _append_svg(self, svg):
138 def _append_svg(self, svg):
137 """ Append raw svg data to the widget.
139 """ Append raw svg data to the widget.
138 """
140 """
139 try:
141 try:
140 image = svg_to_image(svg)
142 image = svg_to_image(svg)
141 except ValueError:
143 except ValueError:
142 self._append_plain_text('Received invalid plot data.')
144 self._append_plain_text('Received invalid plot data.')
143 else:
145 else:
144 format = self._add_image(image)
146 format = self._add_image(image)
145 self._name_to_svg[str(format.name())] = svg
147 self._name_to_svg[str(format.name())] = svg
146 format.setProperty(self._svg_text_format_property, svg)
148 format.setProperty(self._svg_text_format_property, svg)
147 cursor = self._get_end_cursor()
149 cursor = self._get_end_cursor()
148 cursor.insertBlock()
150 cursor.insertBlock()
149 cursor.insertImage(format)
151 cursor.insertImage(format)
150 cursor.insertBlock()
152 cursor.insertBlock()
151
153
152 def _append_png(self, png):
154 def _append_png(self, png):
153 """ Append raw svg data to the widget.
155 """ Append raw svg data to the widget.
154 """
156 """
155 try:
157 try:
156 image = QtGui.QImage()
158 image = QtGui.QImage()
157 image.loadFromData(png, 'PNG')
159 image.loadFromData(png, 'PNG')
158 except ValueError:
160 except ValueError:
159 self._append_plain_text('Received invalid plot data.')
161 self._append_plain_text('Received invalid plot data.')
160 else:
162 else:
161 format = self._add_image(image)
163 format = self._add_image(image)
162 cursor = self._get_end_cursor()
164 cursor = self._get_end_cursor()
163 cursor.insertBlock()
165 cursor.insertBlock()
164 cursor.insertImage(format)
166 cursor.insertImage(format)
165 cursor.insertBlock()
167 cursor.insertBlock()
166
168
167 def _add_image(self, image):
169 def _add_image(self, image):
168 """ Adds the specified QImage to the document and returns a
170 """ Adds the specified QImage to the document and returns a
169 QTextImageFormat that references it.
171 QTextImageFormat that references it.
170 """
172 """
171 document = self._control.document()
173 document = self._control.document()
172 name = QtCore.QString.number(image.cacheKey())
174 name = QtCore.QString.number(image.cacheKey())
173 document.addResource(QtGui.QTextDocument.ImageResource,
175 document.addResource(QtGui.QTextDocument.ImageResource,
174 QtCore.QUrl(name), image)
176 QtCore.QUrl(name), image)
175 format = QtGui.QTextImageFormat()
177 format = QtGui.QTextImageFormat()
176 format.setName(name)
178 format.setName(name)
177 return format
179 return format
178
180
179 def _copy_image(self, name):
181 def _copy_image(self, name):
180 """ Copies the ImageResource with 'name' to the clipboard.
182 """ Copies the ImageResource with 'name' to the clipboard.
181 """
183 """
182 image = self._get_image(name)
184 image = self._get_image(name)
183 QtGui.QApplication.clipboard().setImage(image)
185 QtGui.QApplication.clipboard().setImage(image)
184
186
185 def _get_image(self, name):
187 def _get_image(self, name):
186 """ Returns the QImage stored as the ImageResource with 'name'.
188 """ Returns the QImage stored as the ImageResource with 'name'.
187 """
189 """
188 document = self._control.document()
190 document = self._control.document()
189 variant = document.resource(QtGui.QTextDocument.ImageResource,
191 variant = document.resource(QtGui.QTextDocument.ImageResource,
190 QtCore.QUrl(name))
192 QtCore.QUrl(name))
191 return variant.toPyObject()
193 return variant.toPyObject()
192
194
193 def _save_image(self, name, format='PNG'):
195 def _save_image(self, name, format='PNG'):
194 """ Shows a save dialog for the ImageResource with 'name'.
196 """ Shows a save dialog for the ImageResource with 'name'.
195 """
197 """
196 dialog = QtGui.QFileDialog(self._control, 'Save Image')
198 dialog = QtGui.QFileDialog(self._control, 'Save Image')
197 dialog.setAcceptMode(QtGui.QFileDialog.AcceptSave)
199 dialog.setAcceptMode(QtGui.QFileDialog.AcceptSave)
198 dialog.setDefaultSuffix(format.lower())
200 dialog.setDefaultSuffix(format.lower())
199 dialog.setNameFilter('%s file (*.%s)' % (format, format.lower()))
201 dialog.setNameFilter('%s file (*.%s)' % (format, format.lower()))
200 if dialog.exec_():
202 if dialog.exec_():
201 filename = dialog.selectedFiles()[0]
203 filename = dialog.selectedFiles()[0]
202 image = self._get_image(name)
204 image = self._get_image(name)
203 image.save(filename, format)
205 image.save(filename, format)
204
206
205 def image_tag(self, match, path = None, format = "png"):
207 def image_tag(self, match, path = None, format = "png"):
206 """ Return (X)HTML mark-up for the image-tag given by match.
208 """ Return (X)HTML mark-up for the image-tag given by match.
207
209
208 Parameters
210 Parameters
209 ----------
211 ----------
210 match : re.SRE_Match
212 match : re.SRE_Match
211 A match to an HTML image tag as exported by Qt, with
213 A match to an HTML image tag as exported by Qt, with
212 match.group("Name") containing the matched image ID.
214 match.group("Name") containing the matched image ID.
213
215
214 path : string|None, optional [default None]
216 path : string|None, optional [default None]
215 If not None, specifies a path to which supporting files
217 If not None, specifies a path to which supporting files
216 may be written (e.g., for linked images).
218 may be written (e.g., for linked images).
217 If None, all images are to be included inline.
219 If None, all images are to be included inline.
218
220
219 format : "png"|"svg", optional [default "png"]
221 format : "png"|"svg", optional [default "png"]
220 Format for returned or referenced images.
222 Format for returned or referenced images.
221
223
222 Subclasses supporting image display should override this
224 Subclasses supporting image display should override this
223 method.
225 method.
224 """
226 """
225
227
226 if(format == "png"):
228 if(format == "png"):
227 try:
229 try:
228 image = self._get_image(match.group("name"))
230 image = self._get_image(match.group("name"))
229 except KeyError:
231 except KeyError:
230 return "<b>Couldn't find image %s</b>" % match.group("name")
232 return "<b>Couldn't find image %s</b>" % match.group("name")
231
233
232 if(path is not None):
234 if(path is not None):
233 if not os.path.exists(path):
235 if not os.path.exists(path):
234 os.mkdir(path)
236 os.mkdir(path)
235 relpath = os.path.basename(path)
237 relpath = os.path.basename(path)
236 if(image.save("%s/qt_img%s.png" % (path,match.group("name")),
238 if(image.save("%s/qt_img%s.png" % (path,match.group("name")),
237 "PNG")):
239 "PNG")):
238 return '<img src="%s/qt_img%s.png">' % (relpath,
240 return '<img src="%s/qt_img%s.png">' % (relpath,
239 match.group("name"))
241 match.group("name"))
240 else:
242 else:
241 return "<b>Couldn't save image!</b>"
243 return "<b>Couldn't save image!</b>"
242 else:
244 else:
243 ba = QtCore.QByteArray()
245 ba = QtCore.QByteArray()
244 buffer_ = QtCore.QBuffer(ba)
246 buffer_ = QtCore.QBuffer(ba)
245 buffer_.open(QtCore.QIODevice.WriteOnly)
247 buffer_.open(QtCore.QIODevice.WriteOnly)
246 image.save(buffer_, "PNG")
248 image.save(buffer_, "PNG")
247 buffer_.close()
249 buffer_.close()
248 return '<img src="data:image/png;base64,\n%s\n" />' % (
250 return '<img src="data:image/png;base64,\n%s\n" />' % (
249 re.sub(r'(.{60})',r'\1\n',str(ba.toBase64())))
251 re.sub(r'(.{60})',r'\1\n',str(ba.toBase64())))
250
252
251 elif(format == "svg"):
253 elif(format == "svg"):
252 try:
254 try:
253 svg = str(self._name_to_svg[match.group("name")])
255 svg = str(self._name_to_svg[match.group("name")])
254 except KeyError:
256 except KeyError:
255 return "<b>Couldn't find image %s</b>" % match.group("name")
257 return "<b>Couldn't find image %s</b>" % match.group("name")
256
258
257 # Not currently checking path, because it's tricky to find a
259 # Not currently checking path, because it's tricky to find a
258 # cross-browser way to embed external SVG images (e.g., via
260 # cross-browser way to embed external SVG images (e.g., via
259 # object or embed tags).
261 # object or embed tags).
260
262
261 # Chop stand-alone header from matplotlib SVG
263 # Chop stand-alone header from matplotlib SVG
262 offset = svg.find("<svg")
264 offset = svg.find("<svg")
263 assert(offset > -1)
265 assert(offset > -1)
264
266
265 return svg[offset:]
267 return svg[offset:]
266
268
267 else:
269 else:
268 return '<b>Unrecognized image format</b>'
270 return '<b>Unrecognized image format</b>'
269
271
General Comments 0
You need to be logged in to leave comments. Login now