root/latexformulamacro/0.9/formula.py

Revision 678, 9.9 kB (checked in by vgough, 3 years ago)

LatexFormulaMacro:

Apply changes from Ken McIvor?
fixes #65

Line 
1 """
2 Convert a latex formula into an image.
3 by Valient Gough <vgough@pobox.com>, David Douard <david.douard@gmail.com>
4
5 Changes:
6     2006-01-16 (David Douard):
7         * make this macro work with Trac 0.9
8         * make the generated images be saved in $PROJECT/htdocs/formulas
9         * make default image format be 'png'
10         * replaced every Tab by spaces
11         * make tmp dir creation recursive
12     2005-10-21:  Ken McIvor <mcivor@iit.edu>:
13         * Updated to support trac 0.9b2.
14         * Improved the error messages for missing configuration elements.
15     2005-10-03:
16         * make image format selectable via 'image_format' configuration option
17           (defaults to 'jpg')
18         * allow paths to executables to be specified in configuration by
19           setting 'latex_path', 'dvips_path', 'convert_path' to point to
20           executable. Based on code by Reed Cartwright.
21     2005-10-01:
22         * add #display and #fleqn options to add html formatting around image
23           (Christian Marquardt).
24     2005-09-21:
25         * add #center and #indent options to add html formatting around image.
26     2005-08-02:
27         * remove hard-coded paths, read from configuration. Fixes #26
28     2005-07-27:
29         * figured out how to get rid of the annoying internal error after latex
30         was run.  Redirected latex output to /dev/null..
31         * found out that {{{#!figure ... }}} runs wiki macro, and doesn't have
32         the problem of not being able to use paranthesis.  So this is the
33         default usage now.  Can still use [[formula(...)]] for simple formula.
34         * add "nomode" command, which can be used to turn off automatic
35           enclosure of commands in display-math mode ("$$ ... $$")
36     2005-07-26: first release
37
38 Installation:
39     1. Copy into wiki-macros directory.
40     2. Edit conf/trac.ini and add a [latex] group with three values:
41         [latex]
42         # temp_dir points to directory where temporary files are created
43         temp_dir = /var/tmp/trac
44         # Set to 1 for fleqn style equations (default is centered)
45         fleqn = 0
46         # Indentation width for fleqn style equations
47         fleqn_width = '5%'
48
49 Usage:
50
51 {{{
52 #!formula
53 [latex code]
54 }}}
55
56 or, additional keywords can be specified before the latex code:
57 {{{
58 #!formula
59 #density=100
60 [latex code]
61 }}}
62
63 Optional keywords (must be specified before the latex code):
64     #density=100
65         Density defaults to 100.  Larger values produces larger images.
66     #nomode
67         Disable the default display mode setting.  Use this if you want to
68         include things outside of tex's display mode.
69     #display
70         Create a displayed equation (either centered or fleqn style,
71         depending on the fleqn variable in the config file.
72     #center
73         Center the equation on the page.
74     #fleqn
75         fleqn style equation; indentation is controlled by fleqn_witdh in
76         conf/trac.ini.
77     #indent [=class name]
78         places image link in a paragraph <p>...</p>
79         If class name is specified, then it is used to specify a CSS class for
80         the paragraph.
81
82 Notes:
83     A matrix macro is included in the tex code.  This allows you to do things
84     like:
85       \mat{1&2\\3&4}  to get a 2x2 matrix.  The "\\" separates rows, and "&"
86       separates columns.  Any size up to around 25? will work..
87
88 Images are automatically named based on a sha1 hash of the formula, the
89 density, and the script version.  This way the image doesn't have to be
90 regenerated every time it is used, and if anything is changed then a new image
91 is created.
92
93 Note that temporary files can build up in the tmpdir, and every time a formula
94 is modified, a new image will be created in the imagePath directory.  These can
95 be considered as cached files.  You can safely let the tmp file cleaner process
96 remove old files from these directories.
97
98 PS.  This is my first python program, so it is probably pretty ugly by python
99 standards (whatever those may be).  Feedback is welcome, but complaints about
100 ugliness will be redirected to /dev/null.
101 """
102
103 # if the output version string changes, then images will be regenerated
104 outputVersion = "0.1"
105
106
107 import re
108 import string
109 import os
110 import sha
111
112
113 def render(hdf, env, texData, density, fleqnMode, mathMode):
114     # gets paths from configuration
115     tmpdir = env.config.get('latex', 'temp_dir')
116
117     cfg = env.config
118     fleqnIndent = cfg.get('latex', 'fleqn_indent', '5%')
119     latexPath = cfg.get('latex', 'latex_path', 'latex')
120     dvipsPath = cfg.get('latex', 'dvips_path', 'dvips')
121     convertPath = cfg.get('latex', 'convert_path', 'convert')
122     texMag = cfg.get('latex', 'text_mag', 1000)
123     imageFormat = cfg.get('latex', 'image_format', 'png')
124
125     imagePath = os.path.normpath(os.path.join(env.get_htdocs_dir(), "formulas"))
126     if not os.path.exists(imagePath):
127         try:
128             os.mkdir(imagePath)
129         except:
130             return "<b>Error: unable to create image directory</b><br>"       
131
132     def make_cfg_error(element):
133         msg = """\
134 <div class="system-message">
135     <strong>Error: the <code>formula</code> macro requires the
136         setting <code>%s</code> in the configuration section
137         <code>latex</code>
138     </strong>
139 </div>
140 """
141         return msg % element
142
143     if not tmpdir:
144         return make_cfg_error('temp_dir')
145     if not imagePath:
146         return make_cfg_error('image_path')
147
148     path = tmpdir
149     # create temporary directory if necessary
150     def mkd(path):
151         if not os.path.exists(path):
152             d, t, = os.path.split(path)
153             if not os.path.exists(d):
154                 mkd(d)
155            
156             os.mkdir(path)
157                  
158     try:
159         if not os.path.exists(path):
160             mkd(path)
161     except:
162         return "Unable to create temporary directory " + path
163    
164     # generate final image name.  Use a hash of the parameters which affect
165     # the image, so we don't have to recreate it unless they change.
166     hash = sha.new(texData)
167     # include some options in the hash, as they affect the output image
168     hash.update( "%d %d" % (density, int(texMag)) )
169     hash.update( outputVersion )
170     name = hash.hexdigest()
171     imageFile = "%s/%s.%s" % (imagePath, name, imageFormat)
172
173     log = "<br>"
174     if not os.path.exists(imageFile):
175         # latex writes out lots of stuff to the current directory, so we have
176         # to run it from there.
177         cwd = os.getcwd()
178         os.chdir(path)
179
180         texFile = name + ".tex"
181         makeTexFile(texFile, texData, mathMode, texMag)
182
183         # the output from latex on stdout seems to cause problems, so sent it
184         # to /dev/null
185         cmd = "%s %s > /dev/null" % (latexPath, texFile)
186         log += execprog( cmd )
187         os.chdir(cwd)
188
189         # use dvips to convert to eps
190         dviFile = "%s/%s.dvi" % (path, name)
191         epsFile = "%s/%s.eps" % (path, name)
192         cmd = "%s -q -D 600 -E -n 1 -p 1 -o %s %s" % (dvipsPath, epsFile, dviFile)
193         log += execprog( cmd )
194
195         # and finally, ImageMagick to convert from eps to [imageFormat] type
196         cmd = "%s -antialias -density %ix%i %s %s" % (convertPath, density, density, epsFile, imageFile)
197         log += execprog( cmd )
198
199     if fleqnMode:
200         margin = " margin-left: %s" % fleqnIndent
201     else:
202         margin = ""
203        
204     html = "<img src='%s' border='0' style='vertical-align: middle;%s' alt='formula' />" % (env.href.chrome('site','formulas/%s.%s'%(name, imageFormat)), margin)
205     return html
206
207 def execprog(cmd):
208     os.system( cmd )
209     return cmd + "<br>"
210
211 def makeTexFile(texFile, texData, mathMode, texMag):
212     tex = "\\batchmode\n"
213     tex += "\\documentclass{article}\n"
214     tex += "\\usepackage{amsmath}\n"
215     tex += "\\usepackage{amssymb}\n"
216     tex += "\\usepackage{epsfig}\n"
217     tex += "\\pagestyle{empty}\n"
218     tex += "\\mag=%s\n" % texMag
219     # matrix macro
220     tex += "\\newcommand{\\mat}[2][rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr]{\n"
221     tex += \\left[\\begin{array}{#1}\n"
222     tex += "  #2\\\\\n"
223     tex += \\end{array}\n"
224     tex += \\right]}\n"
225     # start the document
226     tex += "\\begin{document}\n"
227     if mathMode:
228         tex += "$$\n"
229     tex += "%s\n" % texData
230     if mathMode:
231         tex += "$$\n"
232     tex += "\\pagebreak\n"
233     tex += "\\end{document}\n"
234        
235     FILE = open(texFile, "w")
236     FILE.write( tex )
237     FILE.close()
238
239 # arguments start with "#" on the beginning of a line
240 def execute(hdf, text, env):
241     cfg = env.config
242
243     # TODO: unescape all html escape codes
244     text = text.replace("&amp;", "&")
245        
246     # defaults
247     density = 100
248     mathMode = 1    # default to using display-math mode for LaTeX processing
249     displayMode = 0 # default to generating inline formula
250     fleqnMode   = cfg.get('latex', 'fleqn')
251     centerImage = 0
252     indentImage = 0
253     indentClass = ""
254
255     # find some number of arguments, followed by the formula
256     command = re.compile('^\s*#([^=]+)=?(.*)')
257     formula = ""
258     errors = ""
259     for line in text.split("\n"):
260         m = command.match(line)
261         if m:
262             if m.group(1) == "density":
263                 density = int(m.group(2))
264             elif m.group(1) == "nomode":
265                 mathMode = 0
266             elif m.group(1) == "center":
267                 centerImage = 1
268                 fleqnMode   = 0
269             elif m.group(1) == "indent":
270                 indentImage = 1
271                 indentClass = m.group(2)
272             elif m.group(1) == "display":
273                 displayMode = 1
274             elif m.group(1) == "fleqn":
275                 displayMode = 1
276                 fleqnMode   = 1
277             else:
278                 errors = '<br>Unknown <i>formula</i> command "%s"<br>' % m.group(1)
279         else:
280             formula += line + "\n"
281
282     # Set display and fleqn defaults
283     if displayMode:
284         if fleqnMode:
285             centerImage = 0
286         else:
287             centerImage = 1
288
289     # Render formula
290     format = '%s'
291     if centerImage:
292         format = '<center>%s</center>' % format
293     if indentImage:
294         if indentClass:
295             format = '<p class="%s">%s</p>' % (indentClass, format)
296         else:
297             format = '<p>%s</p>' % format
298    
299     result = errors + render(hdf, env, formula, density, fleqnMode, mathMode)
300     return format % result
Note: See TracBrowser for help on using the browser.