From c892f26723f82bba0033f8d49c39fc6d74d5a010 2011-01-24 05:29:41 From: Brian Granger Date: 2011-01-24 05:29:41 Subject: [PATCH] More improvements to the display system. * Full PNG handling in the rich frontend. * Sympy printer now can render to latex->png. --- diff --git a/IPython/core/display.py b/IPython/core/display.py index f4195ed..0f78d0f 100644 --- a/IPython/core/display.py +++ b/IPython/core/display.py @@ -48,6 +48,17 @@ def display(obj, include=None, exclude=None): publish('IPython.core.display.display', format_dict) +def display_pretty(obj): + """Display the pretty (default) representation of an object. + + Parameters + ---------- + obj : object + The Python object to display. + """ + display(obj, include=['text/plain']) + + def display_html(obj): """Display the HTML representation of an object. diff --git a/IPython/core/formatters.py b/IPython/core/formatters.py index ff2e15e..1df7eb6 100644 --- a/IPython/core/formatters.py +++ b/IPython/core/formatters.py @@ -15,6 +15,10 @@ Authors: # The full license is in the file COPYING.txt, distributed with this software. #----------------------------------------------------------------------------- +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + # Stdlib imports import abc # We must use StringIO, as cStringIO doesn't handle unicode properly. @@ -259,6 +263,23 @@ class BaseFormatter(Configurable): self.deferred_printers[key] = func return oldfunc + def _in_deferred_types(self, cls): + """ + Check if the given class is specified in the deferred type registry. + + Returns the printer from the registry if it exists, and None if the + class is not in the registry. Successful matches will be moved to the + regular type registry for future use. + """ + mod = getattr(cls, '__module__', None) + name = getattr(cls, '__name__', None) + key = (mod, name) + printer = None + if key in self.deferred_printers: + # Move the printer over to the regular registry. + printer = self.deferred_printers.pop(key) + self.type_printers[cls] = printer + return printer class PlainTextFormatter(BaseFormatter): """The default pretty-printer. diff --git a/IPython/extensions/sympy_printing.py b/IPython/extensions/sympy_printing.py index 0bdafa6..7d8051a 100644 --- a/IPython/extensions/sympy_printing.py +++ b/IPython/extensions/sympy_printing.py @@ -14,7 +14,9 @@ Authors: # Imports #----------------------------------------------------------------------------- -from sympy import pretty +from IPython.lib.latextools import latex_to_png + +from sympy import pretty, latex #----------------------------------------------------------------------------- # Definitions of magic functions for use with IPython @@ -30,6 +32,12 @@ def print_basic_unicode(o, p, cycle): p.text(out) +def print_png(o): + """A funciton to display sympy expression using LaTex -> PNG.""" + s = latex(o) + png = latex_to_png(s, encode=True) + return png + _loaded = False @@ -41,5 +49,9 @@ def load_ipython_extension(ip): plaintext_formatter.for_type_by_name( 'sympy.core.basic', 'Basic', print_basic_unicode ) + png_formatter = ip.display_formatter.formatters['image/png'] + png_formatter.for_type_by_name( + 'sympy.core.basic', 'Basic', print_png + ) _loaded = True diff --git a/IPython/frontend/qt/console/ipython_widget.py b/IPython/frontend/qt/console/ipython_widget.py index 082ba79..7b88990 100644 --- a/IPython/frontend/qt/console/ipython_widget.py +++ b/IPython/frontend/qt/console/ipython_widget.py @@ -210,6 +210,8 @@ class IPythonWidget(FrontendWidget): elif data.has_key('text/plain'): text = data['text/plain'] self._append_plain_text(text) + # This newline seems to be needed for text and html output. + self._append_plain_text(u'\n') def _started_channels(self): """ Reimplemented to make a history request. diff --git a/IPython/frontend/qt/console/rich_ipython_widget.py b/IPython/frontend/qt/console/rich_ipython_widget.py index 8658d44..4a5dcda 100644 --- a/IPython/frontend/qt/console/rich_ipython_widget.py +++ b/IPython/frontend/qt/console/rich_ipython_widget.py @@ -1,6 +1,7 @@ # System library imports import os import re +from base64 import decodestring from PyQt4 import QtCore, QtGui # Local imports @@ -74,6 +75,13 @@ class RichIPythonWidget(IPythonWidget): # TODO: try/except this call. self._append_svg(data['image/svg+xml']) self._append_html(self.output_sep2) + elif data.has_key('image/png'): + self._append_plain_text(self.output_sep) + self._append_html(self._make_out_prompt(prompt_number)) + # TODO: try/except these calls + png = decodestring(data['image/png']) + self._append_png(png) + self._append_html(self.output_sep2) else: # Default back to the plain text representation. return super(RichIPythonWidget, self)._handle_pyout(msg) @@ -91,6 +99,12 @@ class RichIPythonWidget(IPythonWidget): svg = data['image/svg+xml'] # TODO: try/except this call. self._append_svg(svg) + elif data.has_key('image/png'): + # TODO: try/except these calls + # PNG data is base64 encoded as it passes over the network + # in a JSON structure so we decode it. + png = decodestring(data['image/png']) + self._append_png(png) else: # Default back to the plain text representation. return super(RichIPythonWidget, self)._handle_display_data(msg) @@ -135,6 +149,21 @@ class RichIPythonWidget(IPythonWidget): cursor.insertImage(format) cursor.insertBlock() + def _append_png(self, png): + """ Append raw svg data to the widget. + """ + try: + image = QtGui.QImage() + image.loadFromData(png, 'PNG') + except ValueError: + self._append_plain_text('Received invalid plot data.') + else: + format = self._add_image(image) + cursor = self._get_end_cursor() + cursor.insertBlock() + cursor.insertImage(format) + cursor.insertBlock() + def _add_image(self, image): """ Adds the specified QImage to the document and returns a QTextImageFormat that references it. diff --git a/IPython/lib/latextools.py b/IPython/lib/latextools.py new file mode 100644 index 0000000..3d97975 --- /dev/null +++ b/IPython/lib/latextools.py @@ -0,0 +1,62 @@ +# -*- coding: utf-8 -*- +"""Tools for handling LaTeX. + +Authors: + +* Brian Granger +""" +#----------------------------------------------------------------------------- +# Copyright (c) 2010, IPython Development Team. +# +# Distributed under the terms of the Modified BSD License. +# +# The full license is in the file COPYING.txt, distributed with this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +from StringIO import StringIO +from base64 import encodestring + +#----------------------------------------------------------------------------- +# Tools +#----------------------------------------------------------------------------- + + +def latex_to_png(s, encode=True): + """Render a LaTeX string to PNG using matplotlib.mathtext. + + Parameters + ---------- + s : str + The raw string containing valid inline LaTeX. + encode : bool, optional + Should the PNG data bebase64 encoded to make it JSON'able. + """ + from matplotlib import mathtext + + mt = mathtext.MathTextParser('bitmap') + f = StringIO() + mt.to_png(f, s, fontsize=12) + bin_data = f.getvalue() + if encode: + bin_data = encodestring(bin_data) + return bin_data + +_data_uri_template_png = """%s""" + +def latex_to_html(s, alt='image'): + """Render LaTeX to HTML with embedded PNG data using data URIs. + + Parameters + ---------- + s : str + The raw string containing valid inline LateX. + alt : str + The alt text to use for the HTML. + """ + base64_data = latex_to_png(s, encode=True) + return _data_uri_template_png % (base64_data, alt) +