1 r'''Convert LaTeX or TeX source to PDF or DVI, and escape strings for LaTeX.
2
3 **The python-tex project is obsolete!** Please have a look at Texcaller_.
4
5 .. _Texcaller: http://www.profv.de/texcaller/
6
7 Python-tex is a convenient interface
8 to the TeX command line tools
9 that handles all kinds of errors without much fuzz.
10
11 Temporary files are always cleaned up.
12 The TeX interpreter is automatically re-run as often as necessary,
13 and an exception is thrown
14 in case the output fails to stabilize soon enough.
15 The TeX interpreter is always run in batch mode,
16 so it won't ever get in your way by stopping your application
17 when there are issues with your TeX source.
18 Instead, an exception is thrown
19 that contains all information of the TeX log.
20
21 This enables you to debug TeX related issues
22 directly within your application
23 or within an interactive Python interpreter session.
24
25 Example:
26
27 >>> from tex import latex2pdf
28 >>> document = ur"""
29 ... \documentclass{article}
30 ... \begin{document}
31 ... Hello, World!
32 ... \end{document}
33 ... """
34 >>> pdf = latex2pdf(document)
35
36 >>> type(pdf)
37 <type 'str'>
38 >>> print "PDF size: %.1f KB" % (len(pdf) / 1024.0)
39 PDF size: 5.6 KB
40 >>> pdf[:5]
41 '%PDF-'
42 >>> pdf[-6:]
43 '%%EOF\n'
44 '''
45
46 __version__ = '1.8'
47 __author__ = 'Volker Grabsch'
48 __author_email__ = 'vog@notjusthosting.com'
49 __url__ = 'http://www.profv.de/python-tex/'
50 __classifiers__ = '''
51 Development Status :: 5 - Production/Stable
52 Development Status :: 6 - Mature
53 Development Status :: 7 - Inactive
54 Intended Audience :: Developers
55 License :: OSI Approved :: MIT License
56 Operating System :: OS Independent
57 Programming Language :: Python
58 Topic :: Documentation
59 Topic :: Office/Business
60 Topic :: Printing
61 Topic :: Software Development :: Libraries :: Python Modules
62 Topic :: Text Processing :: Markup :: LaTeX
63 '''
64 __license__ = '''
65 Permission is hereby granted, free of charge, to any person obtaining
66 a copy of this software and associated documentation files (the
67 "Software"), to deal in the Software without restriction, including
68 without limitation the rights to use, copy, modify, merge, publish,
69 distribute, sublicense, and/or sell copies of the Software, and to
70 permit persons to whom the Software is furnished to do so, subject
71 to the following conditions:
72
73 The above copyright notice and this permission notice shall be
74 included in all copies or substantial portions of the Software.
75
76 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
77 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
78 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
79 IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
80 CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
81 TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
82 SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
83 '''
84
85 import os
86 import os.path
87 import random
88 import shutil
89 import string
90 import subprocess
91 import tempfile
92
94 '''Read the content of a file and close it properly.'''
95 f = file(filename, 'rb')
96 content = f.read()
97 f.close()
98 return content
99
101 '''Write into a file and close it properly.'''
102 f = file(filename, 'wb')
103 f.write(content)
104 f.close()
105
106 -def convert(tex_source, input_format, output_format, max_runs=5):
107 '''Convert LaTeX or TeX source to PDF or DVI.'''
108
109 assert isinstance(tex_source, unicode)
110 try:
111 (tex_cmd, output_suffix) = {
112 ('tex', 'dvi'): ('tex', '.dvi'),
113 ('latex', 'dvi'): ('latex', '.dvi'),
114 ('tex', 'pdf'): ('pdftex', '.pdf'),
115 ('latex', 'pdf'): ('pdflatex', '.pdf'),
116 }[(input_format, output_format)]
117 except KeyError:
118 raise ValueError('Unable to handle conversion: %s -> %s'
119 % (input_format, output_format))
120 if max_runs < 2:
121 raise ValueError('max_runs must be at least 2.')
122
123 tex_dir = tempfile.mkdtemp(suffix='', prefix='tex-temp-')
124 try:
125
126 tex_filename = os.path.join(tex_dir, 'texput.tex')
127 _file_write(tex_filename, tex_source.encode('UTF-8'))
128
129 aux_old = None
130 for i in xrange(max_runs):
131 tex_process = subprocess.Popen(
132 [tex_cmd,
133 '-interaction=batchmode',
134 '-halt-on-error',
135 '-no-shell-escape',
136 tex_filename,
137 ],
138 stdin=file(os.devnull, 'r'),
139 stdout=file(os.devnull, 'w'),
140 stderr=subprocess.STDOUT,
141 close_fds=True,
142 shell=False,
143 cwd=tex_dir,
144 env={'PATH': os.getenv('PATH')},
145 )
146 tex_process.wait()
147 if tex_process.returncode != 0:
148 log = _file_read(os.path.join(tex_dir, 'texput.log'))
149 raise ValueError(log)
150 aux = _file_read(os.path.join(tex_dir, 'texput.aux'))
151 if aux == aux_old:
152
153 try:
154 return _file_read(os.path.join(tex_dir, 'texput' + output_suffix))
155 except:
156 raise ValueError('No output file was produced.')
157 aux_old = aux
158
159
160
161
162 raise ValueError("%s didn't stabilize after %i runs"
163 % ('texput.aux', max_runs))
164 finally:
165
166 shutil.rmtree(tex_dir)
167
169 '''Convert TeX source to DVI.'''
170 return convert(tex_source, 'tex', 'dvi', **kwargs)
171
173 '''Convert LaTeX source to DVI.'''
174 return convert(tex_source, 'latex', 'dvi', **kwargs)
175
177 '''Convert TeX source to PDF.'''
178 return convert(tex_source, 'tex', 'pdf', **kwargs)
179
181 '''Convert LaTeX source to PDF.'''
182 return convert(tex_source, 'latex', 'pdf', **kwargs)
183
184 _latex_special_chars = {
185 u'$': u'\\$',
186 u'%': u'\\%',
187 u'&': u'\\&',
188 u'#': u'\\#',
189 u'_': u'\\_',
190 u'{': u'\\{',
191 u'}': u'\\}',
192 u'[': u'{[}',
193 u']': u'{]}',
194 u'"': u"{''}",
195 u'\\': u'\\textbackslash{}',
196 u'~': u'\\textasciitilde{}',
197 u'<': u'\\textless{}',
198 u'>': u'\\textgreater{}',
199 u'^': u'\\textasciicircum{}',
200 u'`': u'{}`',
201 u'\n': u'\\\\',
202 }
203
205 r'''Escape a unicode string for LaTeX.
206
207 :Warning:
208 The source string must not contain empty lines such as:
209 - u'\n...' -- empty first line
210 - u'...\n\n...' -- empty line in between
211 - u'...\n' -- empty last line
212
213 :Parameters:
214 - `s`: unicode object to escape for LaTeX
215
216 >>> s = u'\\"{}_&%a$b#\nc[]"~<>^`\\'
217 >>> escape_latex(s)
218 u"\\textbackslash{}{''}\\{\\}\\_\\&\\%a\\$b\\#\\\\c{[}{]}{''}\\textasciitilde{}\\textless{}\\textgreater{}\\textasciicircum{}{}`\\textbackslash{}"
219 >>> print s
220 \"{}_&%a$b#
221 c[]"~<>^`\
222 >>> print escape_latex(s)
223 \textbackslash{}{''}\{\}\_\&\%a\$b\#\\c{[}{]}{''}\textasciitilde{}\textless{}\textgreater{}\textasciicircum{}{}`\textbackslash{}
224 '''
225 return u''.join(_latex_special_chars.get(c, c) for c in s)
226
228 '''Run all doc tests of this module.'''
229 import doctest, tex
230 return doctest.testmod(tex)
231
232 if __name__ == '__main__':
233 _test()
234