Created
August 17, 2012 03:34
-
-
Save rschroll/3375702 to your computer and use it in GitHub Desktop.
Sympy wrapper for Reinteract
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Copyright 2009, 2010, 2012 Jorn Baayen | |
# Copyright 2012 Robert Schroll | |
# | |
# Released under the BSD license. | |
# | |
# Based on code proposed for inclusion with Reinteract. This version uses | |
# matplotlib's TexManager to display the LaTeX instead of Lasem, but works | |
# similarly otherwise. | |
import gtk | |
import pango | |
import cairo | |
import os | |
import tempfile | |
import subprocess | |
import sympy | |
from matplotlib import rcParams | |
from matplotlib.texmanager import TexManager | |
# sympy's latex() function emits code that uses amsmath | |
rcParams['text.latex.preamble'].append(r'\usepackage{amsmath}') | |
texmanager = TexManager() | |
try: | |
import poppler | |
except ImportError: | |
poppler = None | |
else: | |
try: | |
subprocess.call(['epstopdf --help'], shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE) | |
except OSError: | |
poppler = None | |
from reinteract.custom_result import CustomResult, ResultWidget | |
class SympyRenderer(ResultWidget): | |
__gsignals__ = {} | |
def __init__(self, result, expr): | |
# Gtk widget setup. | |
ResultWidget.__init__(self) | |
self.add_events(gtk.gdk.BUTTON_PRESS_MASK | gtk.gdk.BUTTON_RELEASE_MASK) | |
self.cached_contents = None | |
self.dpi = 96 | |
self.texstyle = {} | |
self.result = result | |
self.expr = expr | |
def sync_dpi(self, dpi): | |
self.dpi = dpi | |
def sync_style(self, style): | |
# We will try to match the style of the parent TextView. | |
# (Though we don't do the font family yet.) | |
text_color = style.text[gtk.STATE_NORMAL] | |
bg_color = style.base[gtk.STATE_NORMAL] | |
font_desc = style.font_desc | |
self.texstyle = {'size': font_desc.get_size() / pango.SCALE, | |
'font': font_desc.get_family(), | |
'color': (text_color.red / 65535.0, text_color.green / 65535.0, text_color.blue / 65535.0), | |
'bgcolor': (bg_color.red / 65535.0, bg_color.green / 65535.0, bg_color.blue / 65535.0) | |
} | |
self.queue_resize() | |
self.cached_contents = None | |
def make_contents(self): | |
png = texmanager.make_png(self.result, | |
fontsize=self.texstyle['size'], | |
dpi=self.dpi) | |
self.cached_contents = cairo.ImageSurface.create_from_png(png) | |
def do_expose_event(self, event): | |
cr = self.window.cairo_create() | |
if not self.cached_contents: | |
self.make_contents() | |
cr.set_source_rgb(*self.texstyle['bgcolor']) | |
cr.paint() | |
cr.set_source_rgb(*self.texstyle['color']) | |
cr.mask_surface(self.cached_contents, | |
SympyResult.margin_left, | |
SympyResult.margin_top) | |
def do_size_allocate(self, allocation): | |
if allocation.width != self.allocation.width or allocation.height != self.allocation.height: | |
self.cached_contents = None | |
gtk.DrawingArea.do_size_allocate(self, allocation) | |
def do_unrealize(self): | |
gtk.DrawingArea.do_unrealize(self) | |
self.cached_contents = None | |
def do_button_press_event(self, event): | |
if event.button == 3: | |
self._show_menu(event) | |
return True | |
def do_button_release_event(self, event): | |
return True | |
def do_size_request(self, requisition): | |
if not self.cached_contents: | |
self.make_contents() | |
requisition.width, requisition.height = \ | |
(self.cached_contents.get_width(), self.cached_contents.get_height()) | |
requisition.height += SympyResult.margin_top + SympyResult.margin_bottom | |
requisition.width += SympyResult.margin_left + SympyResult.margin_right | |
def _copy_to_clipboard(self, value): | |
for selection in (gtk.gdk.SELECTION_CLIPBOARD, gtk.gdk.SELECTION_PRIMARY): | |
clipboard = self.get_clipboard(selection) | |
clipboard.set_text(value) | |
def _show_menu(self, event): | |
# Create a menu offering to copy the result to the clipboard in a | |
# variety of formats. | |
menu = gtk.Menu() | |
menu.attach_to_widget(self, None) | |
for label, value in (("Copy as LaTeX", self.result), | |
("Copy as Python", sympy.python(self.expr)), | |
("Copy as string", str(self.expr))): | |
menu_item = gtk.MenuItem(label=label) | |
menu_item.connect('activate', | |
lambda menu, value=value: self._copy_to_clipboard(value)) | |
menu_item.show() | |
menu.add(menu_item) | |
def on_selection_done(menu): | |
menu.destroy() | |
menu.connect('selection-done', on_selection_done) | |
menu.popup(None, None, None, event.button, event.time) | |
class SympyResult(CustomResult): | |
margin_top = 3 | |
margin_left = 0 | |
margin_right = 3 | |
margin_bottom = 3 | |
def __init__(self, expr): | |
self.expr = expr | |
try: | |
self.result = sympy.latex(expr, mode="equation*") | |
except Exception, e: | |
self.result = "Couldn't create LaTeX (" + str(type(e)) + ")\n" + str(e) | |
def create_widget(self): | |
return SympyRenderer(self.result, self.expr) | |
def print_result_png(self, context, render): | |
print_dpi = 300. | |
cdpi = context.get_dpi_x() | |
sf = cdpi/print_dpi | |
png = texmanager.make_png(self.result, | |
fontsize=12, | |
dpi=print_dpi) | |
im = cairo.ImageSurface.create_from_png(png) | |
if render: | |
cr = context.get_cairo_context() | |
cr.scale(sf, sf) | |
cr.set_source_rgb(0,0,0) | |
cr.mask_surface(im, self.margin_left/sf, self.margin_top/sf) | |
return im.get_height() * sf + self.margin_top + self.margin_bottom | |
def print_result_ps(self, context, render): | |
ps_dpi = 72.27 | |
cdpi = context.get_dpi_x() | |
sf = cdpi/ps_dpi | |
bbox = texmanager.get_ps_bbox(self.result, fontsize=12) | |
if render: | |
fn = texmanager.make_ps(self.result, fontsize=12) | |
fd, pdffn = tempfile.mkstemp() | |
os.close(fd) | |
subprocess.call(['epstopdf ' + fn + ' --outfile=' + pdffn], shell=True) | |
page = poppler.document_new_from_file('file://' + pdffn, None).get_page(0) | |
os.unlink(pdffn) | |
cr = context.get_cairo_context() | |
cr.translate(self.margin_left, self.margin_top) | |
cr.scale(sf, sf) | |
page.render(cr) | |
return (bbox[3] - bbox[1]) * sf + self.margin_top + self.margin_bottom | |
def print_result(self, context, render): | |
if poppler is not None: | |
return self.print_result_ps(context, render) | |
return self.print_result_png(context, render) | |
_wrapped_sympy_classes = (sympy.Basic, sympy.Matrix) | |
try: | |
import sympy.galgebra.GA | |
_wrapped_sympy_classes += (sympy.galgebra.GA.MV,) | |
except: | |
pass | |
def __reinteract_wrap__(obj): | |
if isinstance(obj, _wrapped_sympy_classes): | |
return SympyResult(obj) | |
elif isinstance(obj, (list, tuple, dict)): | |
if len(obj) == 0: | |
return None | |
for item in obj: | |
if not isinstance(item, _wrapped_sympy_classes): | |
return None | |
return SympyResult(obj) | |
else: | |
return None |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment