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