]> git.donarmstrong.com Git - lilypond.git/blob - python/book_latex.py
Issue 1388: Initial work to support opentype font features.
[lilypond.git] / python / book_latex.py
1 # -*- coding: utf-8 -*-
2
3 import re
4 import tempfile
5 import os
6 import sys
7 import subprocess
8 import book_base as BookBase
9 from book_snippets import *
10 import lilylib as ly
11 global _;_=ly._
12
13 progress = ly.progress
14 warning = ly.warning
15 error = ly.error
16 debug = ly.debug_output
17
18 # Recognize special sequences in the input.
19 #
20 #   (?P<name>regex) -- Assign result of REGEX to NAME.
21 #   *? -- Match non-greedily.
22 #   (?!...) -- Match if `...' doesn't match next (without consuming
23 #              the string).
24 #
25 #   (?m) -- Multiline regex: Make ^ and $ match at each line.
26 #   (?s) -- Make the dot match all characters including newline.
27 #   (?x) -- Ignore whitespace in patterns.
28 # Possible keys are:
29 #     'multiline_comment', 'verbatim', 'lilypond_block', 'singleline_comment',
30 #     'lilypond_file', 'include', 'lilypond', 'lilypondversion'
31 Latex_snippet_res = {
32     'include':
33          r'''(?smx)
34           ^[^%\n]*?
35           (?P<match>
36           \\input\s*{
37            (?P<filename>\S+?)
38           })''',
39
40     'lilypond':
41          r'''(?smx)
42           ^[^%\n]*?
43           (?P<match>
44           \\lilypond\s*(
45           \[
46            \s*(?P<options>.*?)\s*
47           \])?\s*{
48            (?P<code>.*?)
49           })''',
50
51     'lilypond_block':
52          r'''(?smx)
53           ^[^%\n]*?
54           (?P<match>
55           \\begin\s*(?P<env>{lilypond}\s*)?(
56           \[
57            \s*(?P<options>.*?)\s*
58           \])?(?(env)|\s*{lilypond})
59            (?P<code>.*?)
60           ^[^%\n]*?
61           \\end\s*{lilypond})''',
62
63     'lilypond_file':
64          r'''(?smx)
65           ^[^%\n]*?
66           (?P<match>
67           \\lilypondfile\s*(
68           \[
69            \s*(?P<options>.*?)\s*
70           \])?\s*\{
71            (?P<filename>\S+?)
72           })''',
73
74     'musicxml_file':
75          r'''(?smx)
76           ^[^%\n]*?
77           (?P<match>
78           \\musicxmlfile\s*(
79           \[
80            \s*(?P<options>.*?)\s*
81           \])?\s*\{
82            (?P<filename>\S+?)
83           })''',
84
85     'singleline_comment':
86          r'''(?mx)
87           ^.*?
88           (?P<match>
89            (?P<code>
90            %.*$\n+))''',
91
92     'verb':
93          r'''(?mx)
94           ^[^%\n]*?
95           (?P<match>
96            (?P<code>
97            \\verb(?P<del>.)
98             .*?
99            (?P=del)))''',
100
101     'verbatim':
102          r'''(?msx)
103           ^[^%\n]*?
104           (?P<match>
105            (?P<code>
106            \\begin\s*{verbatim}
107             .*?
108            \\end\s*{verbatim}))''',
109
110     'lilypondversion':
111          r'''(?smx)
112           (?P<match>
113           \\lilypondversion)[^a-zA-Z]''',
114 }
115
116 Latex_output = {
117     FILTER: r'''\begin{lilypond}[%(options)s]
118 %(code)s
119 \end{lilypond}''',
120
121     OUTPUT: r'''{%%
122 \parindent 0pt
123 \noindent
124 \ifx\preLilyPondExample \undefined
125 \else
126   \expandafter\preLilyPondExample
127 \fi
128 \def\lilypondbook{}%%
129 \input{%(base)s-systems.tex}
130 \ifx\postLilyPondExample \undefined
131 \else
132   \expandafter\postLilyPondExample
133 \fi
134 }''',
135
136     PRINTFILENAME: r'''\texttt{%(filename)s}
137 \linebreak
138 ''',
139
140     QUOTE: r'''\begin{quote}
141 %(str)s
142 \end{quote}''',
143
144     VERBATIM: r'''\noindent
145 \begin{verbatim}%(verb)s\end{verbatim}
146 ''',
147
148     VERSION: r'''%(program_version)s''',
149 }
150
151
152
153
154
155 ###
156 # Retrieve dimensions from LaTeX
157 LATEX_INSPECTION_DOCUMENT = r'''
158 \nonstopmode
159 %(preamble)s
160 \begin{document}
161 \typeout{textwidth=\the\textwidth}
162 \typeout{columnsep=\the\columnsep}
163 \makeatletter\if@twocolumn\typeout{columns=2}\fi\makeatother
164 \end{document}
165 '''
166
167 # Do we need anything else besides `textwidth'?
168 def get_latex_textwidth (source, global_options):
169     # default value
170     textwidth = 550.0
171
172     m = re.search (r'''(?P<preamble>\\begin\s*{document})''', source)
173     if m == None:
174         warning (_ ("cannot find \\begin{document} in LaTeX document"))
175         return textwidth
176
177     preamble = source[:m.start (0)]
178     latex_document = LATEX_INSPECTION_DOCUMENT % {'preamble': preamble}
179
180     (handle, tmpfile) = tempfile.mkstemp('.tex')
181     tmpfileroot = os.path.splitext (tmpfile)[0]
182     tmpfileroot = os.path.split (tmpfileroot)[1]
183     auxfile = tmpfileroot + '.aux'
184     logfile = tmpfileroot + '.log'
185
186     tmp_handle = os.fdopen (handle,'w')
187     tmp_handle.write (latex_document)
188     tmp_handle.close ()
189
190     progress (_ ("Running `%s' on file `%s' to detect default page settings.\n")
191               % (global_options.latex_program, tmpfile))
192     cmd = '%s %s' % (global_options.latex_program, tmpfile)
193     debug ("Executing: %s\n" % cmd)
194     run_env = os.environ.copy()
195     run_env['LC_ALL'] = 'C'
196     run_env['TEXINPUTS'] = '%s:%s' % \
197                            (global_options.input_dir, run_env.get('TEXINPUTS',""))
198
199     ### unknown why this is necessary
200     universal_newlines = True
201     if sys.platform == 'mingw32':
202         universal_newlines = False
203         ### use os.system to avoid weird sleep() problems on
204         ### GUB's python 2.4.2 on mingw
205         # make file to write to
206         output_dir = tempfile.mkdtemp()
207         output_filename = os.path.join(output_dir, 'output.txt')
208         # call command
209         cmd += " > %s" % output_filename
210         oldtexinputs = os.environ.get ('TEXINPUTS')
211         os.environ['TEXINPUTS'] = run_env['TEXINPUTS']
212         returncode = os.system(cmd)
213         if oldtexinputs:
214             os.environ['TEXINPUTS'] = oldtexinputs
215         else:
216             del os.environ['TEXINPUTS']
217         parameter_string = open(output_filename).read()
218         if returncode != 0:
219             warning (_ ("Unable to auto-detect default settings:\n"))
220         # clean up
221         os.remove(output_filename)
222         os.rmdir(output_dir)
223     else:
224         proc = subprocess.Popen (cmd,
225             env=run_env,
226             universal_newlines=universal_newlines,
227             shell=True,
228             stdout=subprocess.PIPE, stderr=subprocess.PIPE)
229         (parameter_string, error_string) = proc.communicate ()
230         if proc.returncode != 0:
231             warning (_ ("Unable to auto-detect default settings:\n%s")
232                     % error_string)
233     os.unlink (tmpfile)
234     if os.path.exists (auxfile):
235         os.unlink (auxfile)
236     if os.path.exists (logfile):
237         parameter_string = file (logfile).read()
238         os.unlink (logfile)
239
240     columns = 0
241     m = re.search ('columns=([0-9.]+)', parameter_string)
242     if m:
243         columns = int (m.group (1))
244
245     columnsep = 0
246     m = re.search ('columnsep=([0-9.]+)pt', parameter_string)
247     if m:
248         columnsep = float (m.group (1))
249
250     m = re.search ('textwidth=([0-9.]+)pt', parameter_string)
251     if m:
252         textwidth = float (m.group (1))
253     else:
254         warning (_ ("cannot detect textwidth from LaTeX"))
255         return textwidth
256
257     debug ('Detected values:')
258     debug ('  columns = %s' % columns)
259     debug ('  columnsep = %s' % columnsep)
260     debug ('  textwidth = %s' % textwidth)
261
262     if m and columns:
263         textwidth = (textwidth - columnsep) / columns
264         debug ('Adjusted value:')
265         debug ('  textwidth = %s' % textwidth)
266
267     return textwidth
268
269
270 def modify_preamble (chunk):
271     str = chunk.replacement_text ()
272     if (re.search (r"\\begin *{document}", str)
273       and not re.search ("{graphic[sx]", str)):
274         str = re.sub (r"\\begin{document}",
275                r"\\usepackage{graphics}" + '\n'
276                + r"\\begin{document}",
277                str)
278         chunk.override_text = str
279
280
281
282
283
284
285 class BookLatexOutputFormat (BookBase.BookOutputFormat):
286     def __init__ (self):
287         BookBase.BookOutputFormat.__init__ (self)
288         self.format = "latex"
289         self.default_extension = ".tex"
290         self.snippet_res = Latex_snippet_res
291         self.output = Latex_output
292         self.handled_extensions = ['.latex', '.lytex', '.tex']
293         self.image_formats = "ps"
294         self.snippet_option_separator = '\s*,\s*'
295
296     def process_options (self, global_options):
297         self.process_options_pdfnotdefault (global_options)
298
299     def get_line_width (self, source):
300         textwidth = get_latex_textwidth (source, self.global_options)
301         return '%.0f\\pt' % textwidth
302
303     def input_fullname (self, input_filename):
304         # Use kpsewhich if available, otherwise fall back to the default:
305         if ly.search_exe_path ('kpsewhich'):
306             trial = os.popen ('kpsewhich ' + input_filename).read()[:-1]
307             if trial:
308                 return trial
309         return BookBase.BookOutputFormat.input_fullname (self, input_filename)
310
311     def process_chunks (self, chunks):
312         for c in chunks:
313             if (c.is_plain () and
314               re.search (r"\\begin *{document}", c.replacement_text())):
315                 modify_preamble (c)
316                 break
317         return chunks
318
319     def snippet_output (self, basename, snippet):
320         str = ''
321         rep = snippet.get_replacements ();
322         rep['base'] = basename.replace ('\\', '/')
323         rep['filename'] = os.path.basename (snippet.filename).replace ('\\', '/')
324         rep['ext'] = snippet.ext
325         if PRINTFILENAME in snippet.option_dict:
326             str += self.output[PRINTFILENAME] % rep
327         if VERBATIM in snippet.option_dict:
328             rep['verb'] = snippet.verb_ly ()
329             str += self.output[VERBATIM] % rep
330
331         str += self.output[OUTPUT] % rep
332
333         ## todo: maintain breaks
334         if 0:
335             breaks = snippet.ly ().count ("\n")
336             str += "".ljust (breaks, "\n").replace ("\n","%\n")
337
338         if QUOTE in snippet.option_dict:
339             str = self.output[QUOTE] % {'str': str}
340         return str
341
342
343
344
345 BookBase.register_format (BookLatexOutputFormat ());