Ticket #4176: latex.py

File latex.py, 8.2 kB (added by yvesf, 4 months ago)

some clean-ups

Line 
1# -*- coding: utf-8 -*-
2## This file is part of Gajim.
3##
4## Gajim is free software; you can redistribute it and/or modify
5## it under the terms of the GNU General Public License as published
6## by the Free Software Foundation; version 3 only.
7##
8## Gajim is distributed in the hope that it will be useful,
9## but WITHOUT ANY WARRANTY; without even the implied warranty of
10## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11## GNU General Public License for more details.
12##
13## You should have received a copy of the GNU General Public License
14## along with Gajim.  If not, see <http://www.gnu.org/licenses/>.
15##
16
17'''
18LaTeX Plugin.
19
20based on Acronym Plugin by Mateusz Biliński
21:author: Yves Fischer <yvesf@xapek.org>
22:since: 17th June 2008
23:copyright: Copyright (2008) Yves Fischer
24:license: GPL
25'''
26
27
28from threading import Thread
29from random import random
30import sys,gtk,gobject
31
32gtk.gdk.threads_init() ##for gtk.gdk.thread_[enter|leave]()
33
34
35#following constants are taken from Pidgin Latex Plugin
36def latex_template(code):
37        return """\\documentclass[12pt]{article}
38\\usepackage[dvips]{graphicx}
39\\usepackage{amsmath}
40\\usepackage{amssymb}
41\\pagestyle{empty}
42\\begin{document}
43\\begin{gather*}
44%s
45\\end{gather*}
46\\end{document}""" % (code)
47
48
49#used in pidgin-latex, necessary in gajim?
50"""
51FILTER_AND="&amp;"
52FILTER_LT= "&lt;"
53FILTER_GT="&gt;"
54FILTER_BR="<br>"
55"""
56
57"""
58Yes, this is simply a copy/paste of KopeteTex blacklist.
59But too bad in LaTeX and system security to verify all
60of this
61"""
62BLACKLIST=[
63        "\def",
64        "\\let",
65        "\\futurelet",
66        "\\newcommand",
67        "\\renewcomment",
68        "\\else",
69        "\\fi",
70        "\\write",
71        "\\input",
72        "\\include",
73        "\\chardef",
74        "\\catcode",
75        "\\makeatletter",
76        "\\noexpand",
77        "\\toksdef",
78        "\\every",
79        "\\errhelp",
80        "\\errorstopmode",
81        "\\scrollmode",
82        "\\nonstopmode",
83        "\\batchmode",
84        "\\read",
85        "\\csname",
86        "\\newhelp",
87        "\\relax",
88        "\\afterground",
89        "\\afterassignment",
90        "\\expandafter",
91        "\\noexpand",
92        "\\special",
93        "\\command",
94        "\\loop",
95        "\\repeat",
96        "\\toks",
97        "\\output",
98        "\\line",
99        "\\mathcode",
100        "\\name",
101        "\\item",
102        "\\section",
103        "\\mbox",
104        "\\DeclareRobustCommand"
105]
106
107
108
109
110"""
111
112"""
113class LatexRenderer(Thread):
114        def __init__(self, iter_start, iter_end, buffer, widget):
115                Thread.__init__(self)
116
117                self.code = iter_start.get_text(iter_end)
118                self.mark_name = "LatexRendererMark%d" % (int(random() * 100000))
119                self.mark = buffer.create_mark(self.mark_name, iter_start, True)
120
121                self.buffer = buffer
122                self.widget = widget
123
124                #delete code and show message "processing"
125                self.buffer.delete(iter_start, iter_end)
126                #iter_start.forward_char()
127                self.buffer.insert(iter_start, "Processing LaTeX")
128
129                self.start() #start background processing
130
131        def run(self):
132                gtk.gdk.threads_enter() #its nearly non-sense using this in a full thread
133                #should be fine-grained later
134                try:
135                        if self.check_code():
136                                self.show_image()
137                        else:
138                                self.show_error("There are bad commands!")
139                except:
140                        pass
141                finally:
142                        gtk.gdk.threads_leave()
143                        self.buffer.delete_mark(self.mark)
144
145        def show_error(self, message):
146                iter_mark = self.buffer.get_iter_at_mark(self.mark)
147                iter_end = iter_mark.copy().forward_search("Processing LaTeX", gtk.TEXT_SEARCH_TEXT_ONLY)[1]
148                self.buffer.delete(iter_mark, iter_end)
149
150
151                pixbuf = self.widget.render_icon(gtk.STOCK_STOP, gtk.ICON_SIZE_BUTTON)
152                self.buffer.insert_pixbuf(iter_end, pixbuf)
153                self.buffer.insert(iter_end, message)
154
155        def show_image(self):
156                from os import system
157                fn = "/tmp/gajim_latex-" + str(int(random() * 100000))
158
159                f = open(fn+".tex", 'w')
160                f.write(latex_template(self.code.split("$$")[1]))
161                f.close()
162
163                system("latex --interaction=nonstopmode -output-directory=/tmp -output-format=dvi %s.tex" % fn)
164
165                system("dvipng -T tight -x 1200 -z 9 -bg transparent -o %s.png %s.dvi" % (fn, fn))
166
167                try:
168                        pixbuf = gtk.gdk.pixbuf_new_from_file(fn + ".png")
169                        iter_mark = self.buffer.get_iter_at_mark(self.mark)
170                        iter_end = iter_mark.copy().forward_search("Processing LaTeX", gtk.TEXT_SEARCH_TEXT_ONLY)[1]
171                        self.buffer.delete(iter_mark, iter_end)
172
173                        self.buffer.insert_pixbuf(iter_end, pixbuf)
174                except gobject.GError:
175                        self.show_error("Cant open %s.png, something around invoking latex goes wrong" % fn)
176                finally:
177                        system("rm -vf %s.png %s.log %s.aux %s.tex" % (fn, fn, fn, fn))
178
179        def check_code(self):
180                for bad_cmd in BLACKLIST:
181                        if self.code.find(bad_cmd) != -1:
182                                print "Found bad command %s" % bad_cmd
183                                return False
184                return True
185
186
187
188class LatexPluginConfiguration(gtk.Window):
189        def __init__(self):
190                gtk.Window.__init__(self)
191                self.set_title("Latex Plugin Configuration")
192               
193                self.pane = gtk.VBox()
194                self.add(self.pane)
195
196                self.btn_test = gtk.Button("Test Latex Configuration")
197                self.btn_test.connect("clicked", self.start_test)
198                self.pane.pack_start(self.btn_test,expand=False)
199
200                self.lbl_messages = gtk.Label("Results:\n")
201                self.lbl_messages.set_line_wrap(True)
202                self.pane.pack_start(self.lbl_messages)
203
204        def log(self, message):
205                self.lbl_messages.set_text(self.lbl_messages.get_text() + "\n" + message)
206
207       
208        """
209        performs very simple checks (check if executable is in PATH)
210        """
211        def start_test(self,widget):
212                log = self.log
213                from os import system
214                log("Start Test")
215                log("Test Latex Binary")
216                ret = system("latex -version")
217                if ret != 0:
218                        log("No LaTeX binary found in PATH")
219                else:
220                        log("OK")
221                log("Test dvipng")
222                ret = system("dvipng --version")
223                if ret != 0:
224                        log("No dvipng binary found in PATH")
225                else:
226                        log("OK")
227
228        def run(self,parent):
229                self.present()
230                self.show_all()
231
232
233from plugins import GajimPlugin
234from plugins.helpers import log, log_calls
235
236
237class LatexPlugin(GajimPlugin):
238        name = u'Latex Plugin'
239        short_name = u'latex'
240        version = u'0.1'
241        description = u'''Invoke Latex to render $$foobar$$ sourrounded Latex equations. Needs latex and dvipng'''
242        authors = [u'Yves Fischer <yvesf@xapek.org>']
243        homepage = u'http://xapek.org'
244
245
246        def init(self):
247                print "init LatexPlugin"
248                self.config_dialog = LatexPluginConfiguration()
249               
250                self.gui_extension_points = {
251                        'chat_control_base' : (self.connect_with_chat_control_base,
252                                                                   self.disconnect_from_chat_control_base)
253                }
254
255        """
256        start rendering if clicked on a link
257        """
258        @log_calls('LatexPlugin')
259        def textview_event_after(self, tag, widget, event, iter):
260                if tag.get_property("name") != "latex" or event.type != gtk.gdk.BUTTON_PRESS:
261                        return
262                dollar_start, iter_start = iter.backward_search("$$", gtk.TEXT_SEARCH_TEXT_ONLY)
263                iter_end, dollar_end = iter.forward_search("$$", gtk.TEXT_SEARCH_TEXT_ONLY)
264                #code = iter_start.get_text(iter_end)
265                #widget.get_buffer().delete(dollar_start, dollar_end)
266                #pixbuf = widget.render_icon(gtk.STOCK_EXECUTE, gtk.ICON_SIZE_BUTTON)
267                #widget.get_buffer().insert_pixbuf(dollar_start, pixbuf)
268                LatexRenderer(dollar_start, dollar_end, widget.get_buffer(), widget)
269
270        """
271        called when conversation text widget changes
272        """
273        @log_calls('AcronymsExpanderPlugin')
274        def textbuffer_live_latex_expander(self, tb):
275                def split_list(list):
276                        newlist = []
277                        for i in range(0,len(list)-1,2):
278                                newlist.append( [ list[i], list[i+1], ] )
279                        return newlist
280
281                assert isinstance(tb,gtk.TextBuffer)
282                start_iter = tb.get_start_iter()
283                points = []
284                tuple_found = start_iter.forward_search("$$", gtk.TEXT_SEARCH_TEXT_ONLY)
285                while tuple_found != None:
286                        points.append(tuple_found)
287                        tuple_found = tuple_found[1].forward_search("$$", gtk.TEXT_SEARCH_TEXT_ONLY)
288
289                for pair in split_list(points):
290                        tb.apply_tag_by_name("latex", pair[0][1], pair[1][0])
291
292        @log_calls('AcronymsExpanderPlugin')
293        def connect_with_chat_control_base(self, chat_control):
294                d = {}
295                tv = chat_control.conv_textview.tv
296                tb = tv.get_buffer()
297
298                self.latex_tag = gtk.TextTag("latex")
299                self.latex_tag.set_property('foreground', "blue")
300                self.latex_tag.set_property('underline', "single")
301                d['tag_id'] = self.latex_tag.connect('event', self.textview_event_after)               
302                tb.get_tag_table().add(self.latex_tag)
303
304                d['h_id'] = tb.connect('changed', self.textbuffer_live_latex_expander)
305                chat_control.latexs_expander_plugin_data = d
306
307                return True
308
309        @log_calls('AcronymsExpanderPlugin')
310        def disconnect_from_chat_control_base(self, chat_control):
311                d = chat_control.latexs_expander_plugin_data
312                #tv = chat_control.msg_textview
313                tv = chat_control.conv_textview.tv
314
315                tv.get_buffer().disconnect(d['h_id'])
316                self.latex_tag.disconnect(d['tag_id'])