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