1 """
2 New latex formatter using dvipng and tempfile
3
4 Author: JohannesBerg <johannes@sipsolutions.net>
5
6 This parser (and the corresponding macro) was tested with Python 2.3.4 and
7 * Debian Linux with out-of-the-box tetex-bin and dvipng packages installed
8 * Windows XP (not by me)
9
10 In the parser, you can add stuff to the prologue by writing
11 %%end-prologue%%
12 somewhere in the document, before that write stuff like \\usepackage and after it put
13 the actual latex display code.
14 """
15
16 Dependencies = []
17
18 import sha, os, tempfile, shutil, re
19 from MoinMoin.action import AttachFile
20 from MoinMoin.Page import Page
21
22 latex_template = r'''
23 \documentclass[12pt]{article}
24 \pagestyle{empty}
25 \usepackage[utf8]{inputenc}
26 %(prologue)s
27 \begin{document}
28 %(raw)s
29 \end{document}
30 '''
31
32 max_pages = 10
33 MAX_RUN_TIME = 5
34
35 latex = "latex"
36 dvipng = "dvipng"
37
38
39 latex_args = ("--interaction=nonstopmode", "%s.tex")
40
41
42 dvipng_args = ("-bgTransparent", "-Ttight", "--noghostscript", "-l%s" % max_pages, "%s.dvi")
43
44
45
46
47 latex_name_template = "latex_%s_p"
48
49
50 latex_attachment = re.compile((latex_name_template+'%s%s') % (r'[0-9a-fA-F]{40}', r'[0-9]{1,2}', r'\.png'))
51
52 anchor = re.compile(r'^%%anchor:[ ]*([a-zA-Z0-9_-]+)$', re.MULTILINE | re.IGNORECASE)
53
54 end_prologue = '%%end-prologue%%'
55
56 def call_command_in_dir_NT(app, args, targetdir):
57 reslimit = "runlimit.exe"
58 os.environ['openin_any'] = 'p'
59 os.environ['openout_any'] = 'p'
60 os.environ['shell_escape'] = 'f'
61 stdouterr = os.popen('%s %d "%s" %s %s < NUL' % (reslimit, MAX_RUN_TIME, targetdir, app, ' '.join(args)), 'r')
62 output = ''.join(stdouterr.readlines())
63 err = stdouterr.close()
64 if not err is None:
65 return ' error! exitcode was %d, transscript follows:\n\n%s' % (err,output)
66 return None
67
68 def call_command_in_dir_unix(app, args, targetdir):
69
70 (r,w) = os.pipe()
71 pid = os.fork()
72 if pid == -1:
73 return 'could not fork'
74 if pid == 0:
75
76 os.close(r)
77 os.dup2(os.open("/dev/null", os.O_WRONLY), 0)
78 os.dup2(w, 1)
79 os.dup2(w, 2)
80 os.chdir(targetdir)
81 os.environ['openin_any'] = 'p'
82 os.environ['openout_any'] = 'p'
83 os.environ['shell_escape'] = 'f'
84 import resource
85 resource.setrlimit(resource.RLIMIT_CPU,
86 (MAX_RUN_TIME * 1000, MAX_RUN_TIME * 1000))
87
88
89
90
91
92
93 try:
94 os.execvp(app, [app] + list(args))
95 finally:
96 print "failed to exec()",app
97 os._exit(2)
98 else:
99
100 os.close(w)
101 r = os.fdopen(r,"r")
102 output = ''.join(r.readlines())
103 (npid, exi) = os.waitpid(pid, 0)
104 r.close()
105 sig = exi & 0xFF
106 stat = exi >> 8
107 if stat != 0 or sig != 0:
108 return ' error! exitcode was %d (signal %d), transscript follows:\n\n%s' % (stat,sig,output)
109 return None
110
111
112 if os.name == 'nt':
113 call_command_in_dir = call_command_in_dir_NT
114 else:
115 call_command_in_dir = call_command_in_dir_unix
116
117
118 class Parser:
119 extensions = ['.tex']
120 def __init__ (self, raw, request, **kw):
121 self.raw = raw
122 if len(self.raw)>0 and self.raw[0] == '#':
123 self.raw[0] = '%'
124 self.request = request
125 self.exclude = []
126 if not hasattr(request, "latex_cleanup_done"):
127 request.latex_cleanup_done = {}
128
129 def cleanup(self, pagename):
130 attachdir = AttachFile.getAttachDir(self.request, pagename, create=1)
131 for f in os.listdir(attachdir):
132 if not latex_attachment.match(f) is None:
133 os.remove("%s/%s" % (attachdir, f))
134
135 def _internal_format(self, formatter, text):
136 tmp = text.split(end_prologue, 1)
137 if len(tmp) == 2:
138 prologue,tex=tmp
139 else:
140 prologue = ''
141 tex = tmp[0]
142 if callable(getattr(formatter, 'johill_sidecall_emit_latex', None)):
143 return formatter.johill_sidecall_emit_latex(tex)
144 return self.get(formatter, tex, prologue, True)
145
146 def format(self, formatter):
147 self.request.write(self._internal_format(formatter, self.raw))
148
149 def get(self, formatter, inputtex, prologue, para=False):
150 if not self.request.latex_cleanup_done.has_key(self.request.page.page_name):
151 self.request.latex_cleanup_done[self.request.page.page_name] = True
152 self.cleanup(self.request.page.page_name)
153
154 if len(inputtex) == 0: return ''
155
156 if callable(getattr(formatter, 'johill_sidecall_emit_latex', None)):
157 return formatter.johill_sidecall_emit_latex(inputtex)
158
159 extra_preamble = ''
160 preamble_page = self.request.pragma.get('latex_preamble', None)
161 if preamble_page is not None:
162 extra_preamble = Page(self.request, preamble_page).get_raw_body()
163 extra_preamble = re.sub(re.compile('^#'), '%', extra_preamble)
164
165 tex = latex_template % { 'raw': inputtex, 'prologue': extra_preamble + prologue }
166 enctex = tex.encode('utf-8')
167 fn = latex_name_template % sha.new(enctex).hexdigest()
168
169 attachdir = AttachFile.getAttachDir(self.request, formatter.page.page_name, create=1)
170 dst = "%s/%s%%d.png" % (attachdir, fn)
171 if not os.access(dst % 1, os.R_OK):
172 tmpdir = tempfile.mkdtemp()
173 try:
174 data = open("%s/%s.tex" % (tmpdir, fn), "w")
175 data.write(enctex)
176 data.close()
177 args = list(latex_args)
178 args[-1] = args[-1] % fn
179 res = call_command_in_dir(latex, args, tmpdir)
180 if not res is None:
181 return formatter.preformatted(1)+formatter.text('latex'+res)+formatter.preformatted(0)
182 args = list(dvipng_args)
183 args[-1] = args[-1] % fn
184 res = call_command_in_dir(dvipng, args, tmpdir)
185 if not res is None:
186 return formatter.preformatted(1)+formatter.text('dvipng'+res)+formatter.preformatted(0)
187
188 page = 1
189 while os.access("%s/%s%d.png" % (tmpdir, fn, page), os.R_OK):
190 shutil.copyfile ("%s/%s%d.png" % (tmpdir, fn, page), dst % page)
191 page += 1
192
193 finally:
194 for root,dirs,files in os.walk(tmpdir, topdown=False):
195 for name in files:
196 os.remove(os.path.join(root,name))
197 for name in dirs:
198 os.rmdir(os.path.join(root,name))
199 os.rmdir(tmpdir)
200
201 result = ""
202 page = 1
203 loop = False
204 for match in anchor.finditer(inputtex):
205 result += formatter.anchordef(match.group(1))
206 for match in anchor.finditer(prologue):
207 result += formatter.anchordef(match.group(1))
208 while os.access(dst % page, os.R_OK):
209 url = AttachFile.getAttachUrl(formatter.page.page_name, fn+"%d.png" % page, self.request)
210 if loop:
211 result += formatter.linebreak(0)+formatter.linebreak(0)
212 if para:
213 result += formatter.paragraph(1)
214 result += formatter.image(src="%s" % url, alt=inputtex, title=inputtex, align="absmiddle")
215 if para:
216 result += formatter.paragraph(0)
217 page += 1
218 loop = True
219 return result