Spaces:
Sleeping
Sleeping
# book_latex.py | |
# -*- coding: utf-8 -*- | |
# | |
# This file is part of LilyPond, the GNU music typesetter. | |
# | |
# Copyright (C) 2010 Reinhold Kainhofer <[email protected]>, | |
# 2020--2020 Han-Wen Nienhuys <[email protected]> | |
# | |
# LilyPond is free software: you can redistribute it and/or modify | |
# it under the terms of the GNU General Public License as published by | |
# the Free Software Foundation, either version 3 of the License, or | |
# (at your option) any later version. | |
# | |
# LilyPond is distributed in the hope that it will be useful, | |
# but WITHOUT ANY WARRANTY; without even the implied warranty of | |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
# GNU General Public License for more details. | |
# | |
# You should have received a copy of the GNU General Public License | |
# along with LilyPond. If not, see <http://www.gnu.org/licenses/>. | |
import os | |
import re | |
import subprocess | |
import sys | |
import tempfile | |
import book_base | |
import book_snippets | |
import lilylib as ly | |
progress = ly.progress | |
warning = ly.warning | |
error = ly.error | |
debug = ly.debug_output | |
# Recognize special sequences in the input. | |
# | |
# (?P<name>regex) -- Assign result of REGEX to NAME. | |
# *? -- Match non-greedily. | |
# (?!...) -- Match if `...' doesn't match next (without consuming | |
# the string). | |
# | |
# (?m) -- Multiline regex: Make ^ and $ match at each line. | |
# (?s) -- Make the dot match all characters including newline. | |
# (?x) -- Ignore whitespace in patterns. | |
# See book_base.BookOutputFormat for possible keys | |
Latex_snippet_res = { | |
'include': | |
r'''(?smx) | |
^[^%\n]*? | |
(?P<match> | |
\\input\s*{ | |
(?P<filename>\S+?) | |
})''', | |
'lilypond': | |
r'''(?smx) | |
^[^%\n]*? | |
(?P<match> | |
\\lilypond\s*( | |
\[ | |
\s*(?P<options>.*?)\s* | |
\])?\s*{ | |
(?P<code>.*?) | |
})''', | |
'lilypond_block': | |
r'''(?smx) | |
^[^%\n]*? | |
(?P<match> | |
\\begin\s*(?P<env>{lilypond}\s*)?( | |
\[ | |
\s*(?P<options>.*?)\s* | |
\])?(?(env)|\s*{lilypond}) | |
(?P<code>.*?) | |
^[^%\n]*? | |
\\end\s*{lilypond})''', | |
'lilypond_file': | |
r'''(?smx) | |
^[^%\n]*? | |
(?P<match> | |
\\lilypondfile\s*( | |
\[ | |
\s*(?P<options>.*?)\s* | |
\])?\s*\{ | |
(?P<filename>\S+?) | |
})''', | |
'musicxml_file': | |
r'''(?smx) | |
^[^%\n]*? | |
(?P<match> | |
\\musicxmlfile\s*( | |
\[ | |
\s*(?P<options>.*?)\s* | |
\])?\s*\{ | |
(?P<filename>\S+?) | |
})''', | |
'singleline_comment': | |
r'''(?mx) | |
^.*? | |
(?P<match> | |
(?P<code> | |
%.*$\n+))''', | |
'verb': | |
r'''(?mx) | |
^[^%\n]*? | |
(?P<match> | |
(?P<code> | |
\\verb(?P<del>.) | |
.*? | |
(?P=del)))''', | |
'verbatim': | |
r'''(?msx) | |
^[^%\n]*? | |
(?P<match> | |
(?P<code> | |
\\begin\s*{verbatim} | |
.*? | |
\\end\s*{verbatim}))''', | |
'lilypondversion': | |
r'''(?smx) | |
(?P<match> | |
\\lilypondversion)[^a-zA-Z]''', | |
} | |
Latex_output = { | |
book_snippets.FILTER: r'''\begin{lilypond}[%(options)s] | |
%(code)s | |
\end{lilypond}''', | |
book_snippets.OUTPUT: r'''{%% | |
\parindent 0pt | |
\noindent | |
\ifx\preLilyPondExample \undefined | |
\else | |
\expandafter\preLilyPondExample | |
\fi | |
\def\lilypondbook{}%% | |
\input{%(base)s-systems.tex} | |
\ifx\postLilyPondExample \undefined | |
\else | |
\expandafter\postLilyPondExample | |
\fi | |
}''', | |
book_snippets.PRINTFILENAME: r'''\texttt{%(filename)s} | |
\linebreak | |
''', | |
book_snippets.QUOTE: r'''\begin{quote} | |
%(str)s | |
\end{quote}''', | |
book_snippets.VERBATIM: r'''\noindent | |
\begin{verbatim}%(verb)s\end{verbatim} | |
''', | |
book_snippets.VERSION: r'''%(program_version)s''', | |
} | |
### | |
# Retrieve dimensions from LaTeX | |
LATEX_INSPECTION_DOCUMENT = r''' | |
\nonstopmode | |
%(preamble)s | |
\begin{document} | |
\typeout{textwidth=\the\textwidth} | |
\typeout{columnsep=\the\columnsep} | |
\makeatletter\if@twocolumn\typeout{columns=2}\fi\makeatother | |
\end{document} | |
''' | |
# Do we need anything else besides `textwidth'? | |
def get_latex_textwidth(source, global_options): | |
# default value | |
textwidth = 550.0 | |
m = re.search(r'''(?P<preamble>\\begin\s*{document})''', source) | |
if m is None: | |
warning(_("cannot find \\begin{document} in LaTeX document")) | |
return textwidth | |
preamble = source[:m.start(0)] | |
latex_document = LATEX_INSPECTION_DOCUMENT % {'preamble': preamble} | |
(handle, tmpfile) = tempfile.mkstemp('.tex') | |
tmpfileroot = os.path.splitext(tmpfile)[0] | |
tmpfileroot = os.path.split(tmpfileroot)[1] | |
auxfile = tmpfileroot + '.aux' | |
logfile = tmpfileroot + '.log' | |
tmp_handle = os.fdopen(handle, 'w') | |
tmp_handle.write(latex_document) | |
tmp_handle.close() | |
progress(_("Running `%s' on file `%s' to detect default page settings.\n") | |
% (global_options.latex_program, tmpfile)) | |
cmd = '%s %s' % (global_options.latex_program, tmpfile) | |
debug("Executing: %s\n" % cmd) | |
run_env = os.environ.copy() | |
run_env['LC_ALL'] = 'C' | |
run_env['TEXINPUTS'] = '%s:%s' % \ | |
(global_options.input_dir, run_env.get('TEXINPUTS', "")) | |
# unknown why this is necessary | |
universal_newlines = True | |
if sys.platform == 'mingw32': | |
universal_newlines = False | |
# use os.system to avoid weird sleep() problems on | |
# GUB's python 2.4.2 on mingw | |
# make file to write to | |
output_dir = tempfile.mkdtemp() | |
output_filename = os.path.join(output_dir, 'output.txt') | |
# call command | |
cmd += " > %s" % output_filename | |
oldtexinputs = os.environ.get('TEXINPUTS') | |
os.environ['TEXINPUTS'] = run_env['TEXINPUTS'] | |
returncode = os.system(cmd) | |
if oldtexinputs: | |
os.environ['TEXINPUTS'] = oldtexinputs | |
else: | |
del os.environ['TEXINPUTS'] | |
parameter_string = open(output_filename, encoding="utf8").read() | |
if returncode != 0: | |
warning(_("Unable to auto-detect default settings:\n")) | |
# clean up | |
os.remove(output_filename) | |
os.rmdir(output_dir) | |
else: | |
proc = subprocess.Popen(cmd, | |
env=run_env, | |
universal_newlines=universal_newlines, | |
shell=True, | |
stdout=subprocess.PIPE, stderr=subprocess.PIPE) | |
(parameter_string, error_string) = proc.communicate() | |
if proc.returncode != 0: | |
warning(_("Unable to auto-detect default settings:\n%s") | |
% error_string) | |
os.unlink(tmpfile) | |
if os.path.exists(auxfile): | |
os.unlink(auxfile) | |
if os.path.exists(logfile): | |
parameter_string = open(logfile, encoding="utf8").read() | |
os.unlink(logfile) | |
columns = 0 | |
m = re.search('columns=([0-9.]+)', parameter_string) | |
if m: | |
columns = int(m.group(1)) | |
columnsep = 0 | |
m = re.search('columnsep=([0-9.]+)pt', parameter_string) | |
if m: | |
columnsep = float(m.group(1)) | |
m = re.search('textwidth=([0-9.]+)pt', parameter_string) | |
if m: | |
textwidth = float(m.group(1)) | |
else: | |
warning(_("cannot detect textwidth from LaTeX")) | |
return textwidth | |
debug('Detected values:') | |
debug(' columns = %s' % columns) | |
debug(' columnsep = %s' % columnsep) | |
debug(' textwidth = %s' % textwidth) | |
if m and columns: | |
textwidth = (textwidth - columnsep) / columns | |
debug('Adjusted value:') | |
debug(' textwidth = %s' % textwidth) | |
return textwidth | |
def modify_preamble(chunk): | |
s = chunk.replacement_text() | |
if (re.search(r"\\begin *{document}", s) | |
and not re.search("{graphic[sx]", s)): | |
s = re.sub(r"\\begin{document}", | |
r"\\usepackage{graphics}" + '\n' | |
+ r"\\begin{document}", | |
s) | |
chunk.override_text = s | |
class BookLatexOutputFormat (book_base.BookOutputFormat): | |
def __init__(self): | |
book_base.BookOutputFormat.__init__(self) | |
self.format = "latex" | |
self.default_extension = ".tex" | |
self.snippet_res = Latex_snippet_res | |
self.output = Latex_output | |
self.handled_extensions = ['.latex', '.lytex', '.tex'] | |
self.image_formats = "ps" | |
self.snippet_option_separator = r'\s*,\s*' | |
def process_options(self, global_options): | |
self.process_options_pdfnotdefault(global_options) | |
def get_line_width(self, source): | |
textwidth = get_latex_textwidth(source, self.global_options) | |
return '%.0f\\pt' % textwidth | |
def input_fullname(self, input_filename): | |
# Use kpsewhich if available, otherwise fall back to the default: | |
try: | |
trial = subprocess.run(['kpsewhich', input_filename], | |
check=True, stdout=subprocess.PIPE).stdout | |
if trial: | |
return trial | |
except subprocess.CalledProcessError: | |
pass | |
return book_base.BookOutputFormat.input_fullname(self, input_filename) | |
def process_chunks(self, chunks): | |
for c in chunks: | |
if (c.is_plain() and | |
re.search(r"\\begin *{document}", c.replacement_text())): | |
modify_preamble(c) | |
break | |
return chunks | |
def snippet_output(self, basename, snippet): | |
s = '' | |
rep = snippet.get_replacements() | |
rep['base'] = basename.replace('\\', '/') | |
rep['filename'] = os.path.basename(snippet.filename).replace('\\', '/') | |
rep['ext'] = snippet.ext | |
if book_snippets.PRINTFILENAME in snippet.option_dict: | |
s += self.output[book_snippets.PRINTFILENAME] % rep | |
if book_snippets.VERBATIM in snippet.option_dict: | |
rep['verb'] = snippet.verb_ly() | |
s += self.output[book_snippets.VERBATIM] % rep | |
s += self.output[book_snippets.OUTPUT] % rep | |
# todo: maintain breaks | |
if 0: | |
breaks = snippet.ly().count("\n") | |
s += "".ljust(breaks, "\n").replace("\n", "%\n") | |
if book_snippets.QUOTE in snippet.option_dict: | |
s = self.output[book_snippets.QUOTE] % {'str': s} | |
return s | |
book_base.register_format(BookLatexOutputFormat()) | |