# geometry.sty and article.cls. Give me a hint, and I'll
# fix it.)
+#
+# TODO: magnification support should also work for texinfo -> html: eg. add as option to dvips.
+#
+
# This is was the idea for handling of comments:
# Multiline comments, @ignore .. @end ignore is scanned for
# in read_doc_file, and the chunks are marked as 'ignore', so
# The the rest of the rexeces are searched for. They don't have to test
# if they are on a commented out line.
+
+
import os
import stat
import string
-import re
+
+
import getopt
import sys
import __main__
import operator
+# Handle bug in Python 1.6-2.1
+#
+# there are recursion limits for some patterns in Python 1.6 til 2.1.
+# fix this by importing the 1.5.2 implementation pre instead. Fix by Mats.
+
+if float (sys.version[0:3]) < 2.2:
+ try:
+ import pre
+ re = pre
+ del pre
+ except ImportError:
+ import re
+else:
+ import re
+
+# Attempt to fix problems with limited stack size set by Python!
+# Sets unlimited stack size. Note that the resource module only
+# is available on UNIX.
+try:
+ import resource
+ resource.setrlimit (resource.RLIMIT_STACK, (-1, -1))
+except:
+ pass
+
+
program_version = '@TOPLEVEL_VERSION@'
if program_version == '@' + 'TOPLEVEL_VERSION' + '@':
- program_version = '1.4pre'
+ program_version = '1.4.12'
+
+# if set, LILYPONDPREFIX must take prevalence
+# if datadir is not set, we're doing a build and LILYPONDPREFIX
+datadir = '@datadir@'
+
+if os.environ.has_key ('LILYPONDPREFIX') :
+ datadir = os.environ['LILYPONDPREFIX']
+else:
+ datadir = '@datadir@'
+
+while datadir[-1] == os.sep:
+ datadir= datadir[:-1]
-#
# Try to cater for bad installations of LilyPond, that have
# broken TeX setup. Just hope this doesn't hurt good TeX
# setups. Maybe we should check if kpsewhich can find
# feta16.{afm,mf,tex,tfm}, and only set env upon failure.
-#
-datadir = '@datadir@'
environment = {
'MFINPUTS' : datadir + '/mf:',
'TEXINPUTS': datadir + '/tex:' + datadir + '/ps:.:',
'GS_LIB' : datadir + '/ps',
}
+# tex needs lots of memory, more than it gets by default on Debian
+non_path_environment = {
+ 'extra_mem_top' : '1000000',
+ 'extra_mem_bottom' : '1000000',
+ 'pool_size' : '250000',
+}
+
def setup_environment ():
for key in environment.keys ():
val = environment[key]
val = val + os.pathsep + os.environ[key]
os.environ[key] = val
-
+ for key in non_path_environment.keys ():
+ val = non_path_environment[key]
+ print '%s=%s' % (key,val)
+ os.environ[key] = val
include_path = [os.getcwd()]
# g_ is for global (?)
-
+g_extra_opts = ''
g_here_dir = os.getcwd ()
g_dep_prefix = ''
g_outdir = ''
g_read_lys = 0
g_do_pictures = 1
g_num_cols = 1
+
format = ''
g_run_lilypond = 1
no_match = 'a\ba'
self.m_geo_x_marginparsep = None
self.__body = None
def set_geo_option(self, name, value):
+
+ if type(value) == type([]):
+ value = map(conv_dimen_to_float, value)
+ else:
+ value = conv_dimen_to_float(value)
+
if name == 'body' or name == 'text':
- if type(value) == type(""):
- self.m_geo_textwidth = value
- else:
+ if type(value) == type([]):
self.m_geo_textwidth = value[0]
+ else:
+ self.m_geo_textwidth = value
self.__body = 1
elif name == 'portrait':
self.m_geo_landscape = 0
self.m_geo_x_marginparsep = value
self.m_geo_includemp = 1
elif name == 'scale':
- if type(value) == type(""):
- self.m_geo_width = self.get_paperwidth() * float(value)
+ if type(value) == type([]):
+ self.m_geo_width = self.get_paperwidth() * value[0]
else:
- self.m_geo_width = self.get_paperwidth() * float(value[0])
+ self.m_geo_width = self.get_paperwidth() * value
elif name == 'hscale':
- self.m_geo_width = self.get_paperwidth() * float(value)
+ self.m_geo_width = self.get_paperwidth() * value
elif name == 'left' or name == 'lmargin':
self.m_geo_lmargin = value
elif name == 'right' or name == 'rmargin':
if value[2] not in ('*', ''):
self.m_geo_rmargin = value[2]
elif name == 'hmargin':
- if type(value) == type(""):
- self.m_geo_lmargin = value
- self.m_geo_rmargin = value
- else:
+ if type(value) == type([]):
self.m_geo_lmargin = value[0]
self.m_geo_rmargin = value[1]
- elif name == 'margin':#ugh there is a bug about this option in
- # the geometry documentation
- if type(value) == type(""):
+ else:
self.m_geo_lmargin = value
self.m_geo_rmargin = value
- else:
+ elif name == 'margin':#ugh there is a bug about this option in
+ # the geometry documentation
+ if type(value) == type([]):
self.m_geo_lmargin = value[0]
self.m_geo_rmargin = value[0]
- elif name == 'total':
- if type(value) == type(""):
- self.m_geo_width = value
else:
+ self.m_geo_lmargin = value
+ self.m_geo_rmargin = value
+ elif name == 'total':
+ if type(value) == type([]):
self.m_geo_width = value[0]
+ else:
+ self.m_geo_width = value
elif name == 'width' or name == 'totalwidth':
self.m_geo_width = value
elif name == 'paper' or name == 'papername':
elif name[-5:] == 'paper':
self.m_papersize = name
else:
- self._set_dimen('m_geo_'+name, value)
+ pass
+
def __setattr__(self, name, value):
if type(value) == type("") and \
dimension_conversion_dict.has_key (value[-2:]):
dimension_conversion_dict ={
'mm': mm2pt,
+ 'cm': lambda x: mm2pt(10*x),
'in': in2pt,
'em': em2pt,
'ex': ex2pt,
'pt': pt2pt
}
+# Convert numeric values, with or without specific dimension, to floats.
+# Keep other strings
+def conv_dimen_to_float(value):
+ if type(value) == type(""):
+ m = re.match ("([0-9.]+)(cm|in|pt|mm|em|ex)",value)
+ if m:
+ unit = m.group (2)
+ num = string.atof(m.group (1))
+ conv = dimension_conversion_dict[m.group(2)]
+
+ value = conv(num)
+
+ elif re.match ("^[0-9.]+$",value):
+ value = float(value)
+
+ return value
+
# latex linewidths:
# indices are no. of columns, papersize, fontsize
('EXT', 'f', 'format', 'set format. EXT is one of texi and latex.'),
('DIM', '', 'default-music-fontsize', 'default fontsize for music. DIM is assumed to be in points'),
('DIM', '', 'default-lilypond-fontsize', 'deprecated, use --default-music-fontsize'),
+ ('OPT', '', 'extra-options' , 'Pass OPT quoted to the lilypond command line'),
('DIM', '', 'force-music-fontsize', 'force fontsize for all inline lilypond. DIM is assumed be to in points'),
('DIM', '', 'force-lilypond-fontsize', 'deprecated, use --force-music-fontsize'),
('DIR', 'I', 'include', 'include path'),
\verb+%s+:''',
'output-lilypond': r"""\begin[%s]{lilypond}
%s
-\end{lilypond}""",
- 'output-verbatim': "\\begin{verbatim}%s\\end{verbatim}",
+\end{lilypond}
+""",
+ 'output-verbatim': r'''\begin{verbatim}%s\end{verbatim}%%
+''',
'output-default-post': "\\def\postLilypondExample{}\n",
'output-default-pre': "\\def\preLilypondExample{}\n",
'usepackage-graphics': '\\usepackage{graphics}\n',
'output-eps': '\\noindent\\parbox{\\lilypondepswidth{%(fn)s.eps}}{\includegraphics{%(fn)s.eps}}',
- 'output-tex': '\\preLilypondExample \\input %(fn)s.tex \\postLilypondExample\n',
+ 'output-noinline': r'''
+%% generated: %(fn)s.eps
+''',
+ 'output-tex': '{\\preLilypondExample \\input %(fn)s.tex \\postLilypondExample\n}',
'pagebreak': r'\pagebreak',
},
+
'texi' : {'output-lilypond': """@lilypond[%s]
%s
@end lilypond
'output-lilypond-fragment': """@lilypond[%s]
\context Staff\context Voice{ %s }
@end lilypond """,
+ 'output-noinline': r'''
+@c generated: %(fn)s.png
+''',
'pagebreak': None,
'output-verbatim': r"""@example
%s
return get_output ('output-verbatim') % body
+#warning: this uses extended regular expressions. Tread with care.
+
+# legenda (?P name parameter
+# *? match non-greedily.
+
re_dict = {
'latex': {'input': r'(?m)^[^%\n]*?(?P<match>\\mbinput{?([^}\t \n}]*))',
'include': r'(?m)^[^%\n]*?(?P<match>\\mbinclude{(?P<filename>[^}]+)})',
- 'option-sep' : ', *',
+ 'option-sep' : ',\s*',
'header': r"\\documentclass\s*(\[.*?\])?",
'geometry': r"^(?m)[^%\n]*?\\usepackage\s*(\[(?P<options>.*)\])?\s*{geometry}",
'preamble-end': r'(?P<code>\\begin{document})',
'verbatim': r"(?s)(?P<code>\\begin{verbatim}.*?\\end{verbatim})",
'verb': r"(?P<code>\\verb(?P<del>.).*?(?P=del))",
- 'lilypond-file': r'(?m)^[^%\n]*?(?P<match>\\lilypondfile(\[(?P<options>.*?)\])?\{(?P<filename>.+)})',
- 'lilypond' : r'(?m)^[^%\n]*?(?P<match>\\lilypond(\[(?P<options>.*?)\])?{(?P<code>.*?)})',
- 'lilypond-block': r"(?sm)^[^%\n]*?(?P<match>\\begin(\[(?P<options>.*?)\])?{lilypond}(?P<code>.*?)\\end{lilypond})",
+ 'lilypond-file': r'(?m)^[^%\n]*?(?P<match>\\lilypondfile\s*(\[(?P<options>.*?)\])?\s*\{(?P<filename>.+)})',
+ 'lilypond' : r'(?m)^[^%\n]*?(?P<match>\\lilypond\s*(\[(?P<options>.*?)\])?\s*{(?P<code>.*?)})',
+ 'lilypond-block': r"(?sm)^[^%\n]*?(?P<match>\\begin\s*(\[(?P<options>.*?)\])?\s*{lilypond}(?P<code>.*?)\\end{lilypond})",
'def-post-re': r"\\def\\postLilypondExample",
'def-pre-re': r"\\def\\preLilypondExample",
'usepackage-graphics': r"\usepackage{graphics}",
},
- # why do we have distinction between @mbinclude and @include?
+ # why do we have distinction between @mbinclude and @include?
+
+
'texi': {
'include': '(?m)^[^%\n]*?(?P<match>@mbinclude[ \n\t]+(?P<filename>[^\t \n]*))',
'input': no_match,
'landscape': no_match,
'verbatim': r"""(?s)(?P<code>@example\s.*?@end example\s)""",
'verb': r"""(?P<code>@code{.*?})""",
- 'lilypond-file': '(?m)^(?!@c)(?P<match>@lilypondfile(\[(?P<options>.*?)\])?{(?P<filename>[^}]+)})',
- 'lilypond' : '(?m)^(?!@c)(?P<match>@lilypond(\[(?P<options>.*?)\])?{(?P<code>.*?)})',
- 'lilypond-block': r"""(?m)^(?!@c)(?P<match>(?s)(?P<match>@lilypond(\[(?P<options>.*?)\])?\s(?P<code>.*?)@end lilypond\s))""",
- 'option-sep' : ', *',
+ 'lilypond-file': '(?m)^(?P<match>@lilypondfile(\[(?P<options>[^]]*)\])?{(?P<filename>[^}]+)})',
+ 'lilypond' : '(?m)^(?P<match>@lilypond(\[(?P<options>[^]]*)\])?{(?P<code>.*?)})',
+ 'lilypond-block': r"""(?ms)^(?P<match>@lilypond(\[(?P<options>[^]]*)\])?\s(?P<code>.*?)@end lilypond)\s""",
+ 'option-sep' : ',\s*',
'intertext': r',?\s*intertext=\".*?\"',
'multiline-comment': r"(?sm)^\s*(?!@c\s+)(?P<code>@ignore\s.*?@end ignore)\s",
'singleline-comment': r"(?m)^.*?(?P<match>(?P<code>@c.*$\n+))",
olddict = re_dict[r]
newdict = {}
for k in olddict.keys ():
- newdict[k] = re.compile (olddict[k])
+ try:
+ newdict[k] = re.compile (olddict[k])
+ except:
+ print 'invalid regexp: %s' % olddict[k]
+
+ # we'd like to catch and reraise a more detailed error, but
+ # alas, the exceptions changed across the 1.5/2.1 boundary.
+ raise "Invalid re"
re_dict[r] = newdict
str = fd.read ()
s = re.search('%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', str)
if s:
- return (int(s.group(3))-int(s.group(1)),
- int(s.group(4))-int(s.group(2)))
+
+ gs = map (lambda x: string.atoi (x), s.groups ())
+ return (int (gs[2] - gs[0] + 0.5),
+ int (gs[3] - gs[1] + 0.5))
else:
return (0,0)
Add stuff to BODY using OPTS as options."""
music_size = default_music_fontsize
latex_size = default_text_fontsize
+ indent = ''
+ linewidth = ''
for o in opts:
if g_force_lilypond_fontsize:
music_size = g_force_lilypond_fontsize
m = re.match ('latexfontsize=([0-9]+)pt', o)
if m:
latex_size = string.atoi (m.group (1))
+
+ m = re.match ('indent=([-.0-9]+)(cm|in|mm|pt)', o)
+ if m:
+ f = float (m.group (1))
+ indent = 'indent = %f\\%s' % (f, m.group (2))
+
+ m = re.match ('linewidth=([-.0-9]+)(cm|in|mm|pt)', o)
+ if m:
+ f = float (m.group (1))
+ linewidth = 'linewidth = %f\\%s' % (f, m.group (2))
if re.search ('\\\\score', body):
is_fragment = 0
if is_fragment and not 'multiline' in opts:
opts.append('singleline')
+
if 'singleline' in opts:
- l = -1.0;
- else:
- l = __main__.paperguru.get_linewidth()
+ linewidth = 'linewidth = -1.0'
+ elif not linewidth:
+ l = __main__.paperguru.get_linewidth ()
+ linewidth = 'linewidth = %f\pt' % l
+
+ if 'noindent' in opts:
+ indent = 'indent = 0.0\mm'
for o in opts:
m= re.search ('relative(.*)', o)
body = '\\relative %s { %s }' %(pitch, body)
if is_fragment:
- body = r"""\score {
+ body = r'''\score {
\notes { %s }
\paper { }
-}""" % body
+}''' % body
opts = uniq (opts)
optstring = string.join (opts, ' ')
optstring = re.sub ('\n', ' ', optstring)
- body = r"""
+ body = r'''
%% Generated automatically by: lilypond-book.py
%% options are %s
\include "paper%d.ly"
-\paper { linewidth = %f \pt }
-""" % (optstring, music_size, l) + body
+\paper {
+ %s
+ %s
+}
+''' % (optstring, music_size, linewidth, indent) + body
# ughUGH not original options
return body
idx = idx + 1
continue
m = get_re ('header').match(chunks[idx][1])
- if m.group (1):
+ if m <> None and m.group (1):
options = re.split (',[\n \t]*', m.group(1)[1:-1])
else:
options = []
m = re.match("(\d\d)pt", o)
if m:
paperguru.m_fontsize = int(m.group(1))
-
break
+
while chunks[idx][0] != 'preamble-end':
if chunks[idx] == 'ignore':
idx = idx + 1
m = re.search ('intertext="(.*?)"', o)
if m:
newbody = newbody + m.group (1) + "\n\n"
- if format == 'latex':
+
+ if 'noinline' in opts:
+ s = 'output-noinline'
+ elif format == 'latex':
if 'eps' in opts:
s = 'output-eps'
else:
return newchunks
-def find_eps_dims (match):
- "Fill in dimensions of EPS files."
-
- fn =match.group (1)
- dims = bounding_box_dimensions (fn)
- if g_outdir:
- fn = os.path.join(g_outdir, fn)
-
- return '%ipt' % dims[0]
-
def system (cmd):
sys.stderr.write ("invoking `%s'\n" % cmd)
error ('Error command exited with value %d\n' % st)
return st
+
+def get_bbox (filename):
+ system ('gs -sDEVICE=bbox -q -sOutputFile=- -dNOPAUSE %s -c quit > %s.bbox 2>&1 ' % (filename, filename))
+
+ box = open (filename + '.bbox').read()
+ m = re.match ('^%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', box)
+ gr = []
+ if m:
+ gr = map (string.atoi, m.groups ())
+
+ return gr
+
+def make_pixmap (name):
+ bbox = get_bbox (name + '.eps')
+ margin = 0
+ fo = open (name + '.trans.eps' , 'w')
+ fo.write ('%d %d translate\n' % (-bbox[0]+margin, -bbox[1]+margin))
+ fo.close ()
+
+ res = 90
+
+ x = (2* margin + bbox[2] - bbox[0]) * res / 72.
+ y = (2* margin + bbox[3] - bbox[1]) * res / 72.
+
+ cmd = r"""gs -g%dx%d -sDEVICE=pnggray -dTextAlphaBits=4 -dGraphicsAlphaBits=4 -q -sOutputFile=- -r%d -dNOPAUSE %s %s -c quit > %s"""
+
+ cmd = cmd % (x, y, res, name + '.trans.eps', name + '.eps',name + '.png')
+ try:
+ status = system (cmd)
+ except:
+ os.unlink (name + '.png')
+ error ("Removing output file")
+
def compile_all_files (chunks):
global foutn
eps = []
if g_outdir:
lilyopts = lilyopts + '--dep-prefix=' + g_outdir + '/'
texfiles = string.join (tex, ' ')
- system ('lilypond --header=texidoc %s %s' % (lilyopts, texfiles))
+ system ('lilypond --header=texidoc %s %s %s' % (lilyopts, g_extra_opts, texfiles))
#
# Ugh, fixing up dependencies for .tex generation
for e in eps:
system(r"tex '\nonstopmode \input %s'" % e)
system(r"dvips -E -o %s %s" % (e + '.eps', e))
+
for g in png:
- cmd = r"""gs -sDEVICE=pgm -dTextAlphaBits=4 -dGraphicsAlphaBits=4 -q -sOutputFile=- -r90 -dNOPAUSE %s -c quit | pnmcrop | pnmtopng > %s"""
- cmd = cmd % (g + '.eps', g + '.png')
- try:
- status = system (cmd)
- except:
- os.unlink (g + '.png')
- error ("Removing output file")
+ make_pixmap (g)
os.chdir (d)
n.append (c)
return n
+
+## what's this? Docme --hwn
+##
def fix_epswidth (chunks):
newchunks = []
for c in chunks:
- if c[0] == 'lilypond' and 'eps' in c[2]:
- body = re.sub (r"""\\lilypondepswidth{(.*?)}""", find_eps_dims, c[1])
- newchunks.append(('lilypond', body, c[2], c[3], c[4]))
- else:
+ if c[0] <> 'lilypond' or 'eps' not in c[2]:
newchunks.append (c)
+ continue
+
+ mag = 1.0
+ for o in c[2]:
+ m = re.match ('magnification=([0-9.]+)', o)
+ if m:
+ mag = string.atof (m.group (1))
+
+ def replace_eps_dim (match, lmag = mag):
+ filename = match.group (1)
+ dims = bounding_box_dimensions (filename)
+
+ return '%fpt' % (dims[0] *lmag)
+
+ body = re.sub (r"""\\lilypondepswidth{(.*?)}""", replace_eps_dim, c[1])
+ newchunks.append(('lilypond', body, c[2], c[3], c[4]))
+
return newchunks
elif o == '--default-lilypond-fontsize':
print "--default-lilypond-fontsize is deprecated, use --default-music-fontsize"
default_music_fontsize = string.atoi (a)
+ elif o == '--extra-options':
+ g_extra_opts = a
elif o == '--force-music-fontsize':
g_force_lilypond_fontsize = string.atoi(a)
elif o == '--force-lilypond-fontsize':