Skip to content

Instantly share code, notes, and snippets.

@rschroll
Created August 17, 2012 03:34
Show Gist options
  • Save rschroll/3375702 to your computer and use it in GitHub Desktop.
Save rschroll/3375702 to your computer and use it in GitHub Desktop.
Sympy wrapper for Reinteract
# 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