texlive[51252] Master/texmf-dist: lyluatex (28may19)

commits+karl at tug.org commits+karl at tug.org
Tue May 28 23:25:42 CEST 2019


Revision: 51252
          http://tug.org/svn/texlive?view=revision&revision=51252
Author:   karl
Date:     2019-05-28 23:25:42 +0200 (Tue, 28 May 2019)
Log Message:
-----------
lyluatex (28may19)

Modified Paths:
--------------
    trunk/Master/texmf-dist/doc/support/lyluatex/LICENSE
    trunk/Master/texmf-dist/doc/support/lyluatex/README.md
    trunk/Master/texmf-dist/doc/support/lyluatex/lyluatex.pdf
    trunk/Master/texmf-dist/doc/support/lyluatex/lyluatex.tex
    trunk/Master/texmf-dist/doc/support/lyluatex/lyluatexbase.cls
    trunk/Master/texmf-dist/doc/support/lyluatex/lyluatexmanual.cls
    trunk/Master/texmf-dist/scripts/lyluatex/lyluatex.lua
    trunk/Master/texmf-dist/tex/luatex/lyluatex/lyluatex.sty

Added Paths:
-----------
    trunk/Master/texmf-dist/scripts/lyluatex/lyluatex-lib.lua
    trunk/Master/texmf-dist/scripts/lyluatex/lyluatex-options.lua

Modified: trunk/Master/texmf-dist/doc/support/lyluatex/LICENSE
===================================================================
--- trunk/Master/texmf-dist/doc/support/lyluatex/LICENSE	2019-05-28 21:25:28 UTC (rev 51251)
+++ trunk/Master/texmf-dist/doc/support/lyluatex/LICENSE	2019-05-28 21:25:42 UTC (rev 51252)
@@ -1,6 +1,6 @@
 The MIT License (MIT)
 
-Copyright (c) 2015--2018 jperon and others
+Copyright (c) 2015--2019 jperon and others
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal

Modified: trunk/Master/texmf-dist/doc/support/lyluatex/README.md
===================================================================
--- trunk/Master/texmf-dist/doc/support/lyluatex/README.md	2019-05-28 21:25:28 UTC (rev 51251)
+++ trunk/Master/texmf-dist/doc/support/lyluatex/README.md	2019-05-28 21:25:42 UTC (rev 51252)
@@ -84,3 +84,14 @@
 # Credits
 
 Cf. [Contributors.md](Contributors.md)
+
+# Contributing
+
+If you want improvements or encounter an error, do not hesitate to
+to report the [issue](https://github.com/jperon/lyluatex/issues).
+If you have programming skills, you may also propose your changes
+via a [pull request](https://github.com/jperon/lyluatex/pulls).
+
+This extension is and will remain free; if you find it useful and
+wish to encourage its development by a
+[donation](https://www.paypal.me/abjperon), many thanks!

Modified: trunk/Master/texmf-dist/doc/support/lyluatex/lyluatex.pdf
===================================================================
(Binary files differ)

Modified: trunk/Master/texmf-dist/doc/support/lyluatex/lyluatex.tex
===================================================================
--- trunk/Master/texmf-dist/doc/support/lyluatex/lyluatex.tex	2019-05-28 21:25:28 UTC (rev 51251)
+++ trunk/Master/texmf-dist/doc/support/lyluatex/lyluatex.tex	2019-05-28 21:25:42 UTC (rev 51252)
@@ -40,37 +40,37 @@
 \DefineVerbatimEnvironment{Highlighting}{Verbatim}{commandchars=\\\{\}}
 % Add ',fontsize=\small' for more characters per line
 \newenvironment{Shaded}{}{}
-\newcommand{\KeywordTok}[1]{\textcolor[rgb]{0.00,0.44,0.13}{\textbf{#1}}}
-\newcommand{\DataTypeTok}[1]{\textcolor[rgb]{0.56,0.13,0.00}{#1}}
-\newcommand{\DecValTok}[1]{\textcolor[rgb]{0.25,0.63,0.44}{#1}}
+\newcommand{\AlertTok}[1]{\textcolor[rgb]{1.00,0.00,0.00}{\textbf{#1}}}
+\newcommand{\AnnotationTok}[1]{\textcolor[rgb]{0.38,0.63,0.69}{\textbf{\textit{#1}}}}
+\newcommand{\AttributeTok}[1]{\textcolor[rgb]{0.49,0.56,0.16}{#1}}
 \newcommand{\BaseNTok}[1]{\textcolor[rgb]{0.25,0.63,0.44}{#1}}
-\newcommand{\FloatTok}[1]{\textcolor[rgb]{0.25,0.63,0.44}{#1}}
-\newcommand{\ConstantTok}[1]{\textcolor[rgb]{0.53,0.00,0.00}{#1}}
+\newcommand{\BuiltInTok}[1]{#1}
 \newcommand{\CharTok}[1]{\textcolor[rgb]{0.25,0.44,0.63}{#1}}
-\newcommand{\SpecialCharTok}[1]{\textcolor[rgb]{0.25,0.44,0.63}{#1}}
-\newcommand{\StringTok}[1]{\textcolor[rgb]{0.25,0.44,0.63}{#1}}
-\newcommand{\VerbatimStringTok}[1]{\textcolor[rgb]{0.25,0.44,0.63}{#1}}
-\newcommand{\SpecialStringTok}[1]{\textcolor[rgb]{0.73,0.40,0.53}{#1}}
-\newcommand{\ImportTok}[1]{#1}
 \newcommand{\CommentTok}[1]{\textcolor[rgb]{0.38,0.63,0.69}{\textit{#1}}}
+\newcommand{\CommentVarTok}[1]{\textcolor[rgb]{0.38,0.63,0.69}{\textbf{\textit{#1}}}}
+\newcommand{\ConstantTok}[1]{\textcolor[rgb]{0.53,0.00,0.00}{#1}}
+\newcommand{\ControlFlowTok}[1]{\textcolor[rgb]{0.00,0.44,0.13}{\textbf{#1}}}
+\newcommand{\DataTypeTok}[1]{\textcolor[rgb]{0.56,0.13,0.00}{#1}}
+\newcommand{\DecValTok}[1]{\textcolor[rgb]{0.25,0.63,0.44}{#1}}
 \newcommand{\DocumentationTok}[1]{\textcolor[rgb]{0.73,0.13,0.13}{\textit{#1}}}
-\newcommand{\AnnotationTok}[1]{\textcolor[rgb]{0.38,0.63,0.69}{\textbf{\textit{#1}}}}
-\newcommand{\CommentVarTok}[1]{\textcolor[rgb]{0.38,0.63,0.69}{\textbf{\textit{#1}}}}
-\newcommand{\OtherTok}[1]{\textcolor[rgb]{0.00,0.44,0.13}{#1}}
+\newcommand{\ErrorTok}[1]{\textcolor[rgb]{1.00,0.00,0.00}{\textbf{#1}}}
+\newcommand{\ExtensionTok}[1]{#1}
+\newcommand{\FloatTok}[1]{\textcolor[rgb]{0.25,0.63,0.44}{#1}}
 \newcommand{\FunctionTok}[1]{\textcolor[rgb]{0.02,0.16,0.49}{#1}}
-\newcommand{\VariableTok}[1]{\textcolor[rgb]{0.10,0.09,0.49}{#1}}
-\newcommand{\ControlFlowTok}[1]{\textcolor[rgb]{0.00,0.44,0.13}{\textbf{#1}}}
+\newcommand{\ImportTok}[1]{#1}
+\newcommand{\InformationTok}[1]{\textcolor[rgb]{0.38,0.63,0.69}{\textbf{\textit{#1}}}}
+\newcommand{\KeywordTok}[1]{\textcolor[rgb]{0.00,0.44,0.13}{\textbf{#1}}}
+\newcommand{\NormalTok}[1]{#1}
 \newcommand{\OperatorTok}[1]{\textcolor[rgb]{0.40,0.40,0.40}{#1}}
-\newcommand{\BuiltInTok}[1]{#1}
-\newcommand{\ExtensionTok}[1]{#1}
+\newcommand{\OtherTok}[1]{\textcolor[rgb]{0.00,0.44,0.13}{#1}}
 \newcommand{\PreprocessorTok}[1]{\textcolor[rgb]{0.74,0.48,0.00}{#1}}
-\newcommand{\AttributeTok}[1]{\textcolor[rgb]{0.49,0.56,0.16}{#1}}
 \newcommand{\RegionMarkerTok}[1]{#1}
-\newcommand{\InformationTok}[1]{\textcolor[rgb]{0.38,0.63,0.69}{\textbf{\textit{#1}}}}
+\newcommand{\SpecialCharTok}[1]{\textcolor[rgb]{0.25,0.44,0.63}{#1}}
+\newcommand{\SpecialStringTok}[1]{\textcolor[rgb]{0.73,0.40,0.53}{#1}}
+\newcommand{\StringTok}[1]{\textcolor[rgb]{0.25,0.44,0.63}{#1}}
+\newcommand{\VariableTok}[1]{\textcolor[rgb]{0.10,0.09,0.49}{#1}}
+\newcommand{\VerbatimStringTok}[1]{\textcolor[rgb]{0.25,0.44,0.63}{#1}}
 \newcommand{\WarningTok}[1]{\textcolor[rgb]{0.38,0.63,0.69}{\textbf{\textit{#1}}}}
-\newcommand{\AlertTok}[1]{\textcolor[rgb]{1.00,0.00,0.00}{\textbf{#1}}}
-\newcommand{\ErrorTok}[1]{\textcolor[rgb]{1.00,0.00,0.00}{\textbf{#1}}}
-\newcommand{\NormalTok}[1]{#1}
 \setlength{\emergencystretch}{3em}  % prevent overfull lines
 \providecommand{\tightlist}{%
   \setlength{\itemsep}{0pt}\setlength{\parskip}{0pt}}
@@ -93,9 +93,9 @@
 
 \title{\lyluatex}
 \providecommand{\subtitle}[1]{}
-\subtitle{1.0b}
+\subtitle{1.0f}
 \author{Fr. Jacques Peron \and Urs Liska \and Br. Samuel Springuel}
-\date{}
+\date{\lyluatexmanualdate}
 
 \begin{document}
 \maketitle
@@ -107,8 +107,8 @@
 \hypertarget{introduction}{%
 \section{Introduction}\label{introduction}}
 
-\lyluatex~is a \LaTeX~package that manages the inclusion of musical
-scores in \LaTeX~documents. It uses the GNU LilyPond\footnote{\url{http://lilypond.org}}
+\lyluatex~is a comprehensive \LuaLaTeX~package to manage the inclusion
+of musical scores in text documents. It uses the GNU LilyPond\footnote{\url{http://lilypond.org}}
 score writer to produce beautiful music elements in beautifully typeset
 text documents. \lyluatex~supports a wide range of use cases and lends
 itself equally well to authoring musicological texts with music examples
@@ -119,10 +119,10 @@
 \lyluatex~is inspired by and provides a fully compatible drop-in
 replacement to
 \href{http://lilypond.org/doc/v2.18/Documentation/usage/invoking-lilypond_002dbook.html}{lilypond-book},
-a \LaTeX~document preprocessor shipping with LilyPond. However, thanks
-to the use of \LuaLaTeX~it can overcome substantial limitations of the
-scripted solution, and it actually is a \emph{superset} of
-\texttt{lilypond-book}, providing numerous additional features.
+a \LaTeX~document preprocessor shipping with LilyPond, which it actually
+is a \emph{superset} of. However, thanks to the use of \LuaLaTeX~it can
+overcome substantial usability limitations of the scripted solution and
+provide numerous additional features.
 
 \lyluatex's main features include:
 
@@ -141,80 +141,90 @@
   Comprehensive configuration through global and per-score options
 \end{itemize}
 
-\hypertarget{installation}{%
-\subsection{Installation}\label{installation}}
+What \lyluatex~does \emph{not} try to do is managing the handling of
+floating environments, counters and lists of music examples. The
+\emph{ly}\LuaTeX\textsc{mp} package\footnote{\url{https://github.com/uliska/lyluatexmp}}
+is currently under construction and practical testing and will
+eventually be released to become a suitable wrapper for using
+\lyluatex~to create numbered music examples.
 
-\hypertarget{for-a-single-document}{%
-\subsubsection{For a single document}\label{for-a-single-document}}
+\hypertarget{installation-and-requirements}{%
+\subsection{Installation and
+Requirements}\label{installation-and-requirements}}
 
-Copy \texttt{lyluatex.sty} and \texttt{lyluatex.lua} into the folder
-containing the document you wish to typeset.
+\hypertarget{prerequisites}{%
+\subsubsection{Prerequisites}\label{prerequisites}}
 
-\hypertarget{for-all-documents-compiled-with-your-latex-distribution}{%
-\subsubsection{For all documents compiled with your LaTeX
-distribution}\label{for-all-documents-compiled-with-your-latex-distribution}}
+As the name \lyluatex~implies this package can only be used with the
+\LuaLaTeX~engine. For more information on this please refer to
+\protect\hyperlink{usage}{Usage} below.
 
-\hypertarget{texlive-version}{%
-\paragraph{TeXLive version}\label{texlive-version}}
+Musical scores are created in real-time (instead of incorporating
+pre-built \emph{image} files) using the GNU LilyPond\footnote{\url{http://lilypond.org}}
+score writer, so of course this has to be installed too.
+\lyluatex~should work with any versions of LilyPond but it has been
+developed against the stable and development versions that were current
+at the time of this writing: 2.18.2 and 2.19.83.
 
-Just run this command~:
+\hypertarget{texlive-and-miktex}{%
+\subsubsection{TeXLive and MiKTeX}\label{texlive-and-miktex}}
 
+\lyluatex~is included in both the TeXLive and MiKTeX
+\LaTeX~distributions and can be installed through their package
+management systems. In TeXLive it is included in the
+\texttt{texlive-music} collection and -- of course -- in
+\texttt{texlive-full}. If neither of these collections is installed
+\lyluatex~can be added to a TeXLive installation by running
+
 \begin{verbatim}
 tlmgr install lyluatex
 \end{verbatim}
 
+from the command line.
+
+\textbf{TODO:} Document handling with MiKTeX.
+
 \hypertarget{latest-version}{%
-\paragraph{Latest version}\label{latest-version}}
+\subsubsection{Latest version}\label{latest-version}}
 
+The \lyluatex~versions shipped with the \LaTeX~distributions may be
+significantly outdated so you may want to install and use the latest
+version from the Github repository\footnote{\url{https://github.com/jperon/lyluatex}}
+instead.
+
 Copy \texttt{lyluatex.sty} and \texttt{lyluatex.lua} from this
-repository into your \texttt{TEXMF} tree, or clone this repostory into
-your \texttt{TEXMF} tree using Git, then run \texttt{mktexlsr}. Note
-that in this case your local copy will shadow the version possibly
-installed in your \TeX~distribution.
+repository into your \texttt{\$TEXMFHOME} tree, or clone this repostory
+into your \texttt{\$TEXMFHOME} tree using Git. In many cases this will
+be \texttt{\$HOME/texmf}, and \lyluatex~should be located below
+\texttt{\$TEXMFHOME/tex/luatex}. It is important that this is the
+\texttt{tex/luatex} subtree rather than \texttt{tex/latex}: if
+\lyluatex~should \emph{also} be present in the \LaTeX~distribution
+\LuaLaTeX~would otherwise find that version first and use that instead
+of your local clone.
 
-\hypertarget{usage}{%
-\section{Usage}\label{usage}}
+\lyMargin{Note:}
 
-\lyluatex~is loaded with the command
-\texttt{\textbackslash{}usepackage\{lyluatex\}} which also accepts a
-number of \texttt{key=value} options. Their general use is described in
-the \protect\hyperlink{option-handling}{Option Handling} section below.
+It may be useful to clone the Git repository not into the
+\texttt{\$TEXMFHOME} tree directly but to some arbitrary location and
+link to that. Please note that \LuaLaTeX~will only follow such symbolic
+links if there is at least one \emph{real} subdirectory in each
+directory. So if there is a directory \texttt{\$TEXMFHOME/tex/luatex}
+containing \emph{only} symbolic links it is necessary to create a dummy
+subdirectory in it.
 
-By default \lyluatex~invokes LilyPond simply as \texttt{lilypond}. If
-LilyPond is installed in another location or a specific version of
-LilyPond should be used the invocation is controlled with the
-\option{program} option, see \protect\hyperlink{program}{The LilyPond
-Executable}.
+\hypertarget{for-a-single-document}{%
+\subsubsection{For a single document}\label{for-a-single-document}}
 
-\lyIssue{Note:} \lyluatex~can only be used with \LuaLaTeX, and compiling
-with any other \LaTeX~engine will fail.
+Copy \texttt{lyluatex.sty} and \texttt{lyluatex.lua} into the folder
+containing the document you wish to typeset.
 
-\lyIssue{Note:} In order to avoid unexpected behaviour it is strongly
-suggested that documents are generally compiled from their actual
-directory, i.e.~without referring to it through a path.
+\hypertarget{usage}{%
+\section{Usage}\label{usage}}
 
-\lyIssue{NOTE:} \lyluatex~requires that \LuaLaTeX~is started with the
-\texttt{-\/-shell-escape} command line option to enable the execution of
-arbitrary shell commands, which is necessary to let LilyPond compile the
-inserted scores on-the-fly and to perform some auxiliary shell
-operations. However, this opens a significant security hole, and only
-fully trusted input files should be compiled. You may mitigate (but not
-totally remove) this security hole by adding \texttt{lilypond} and
-\texttt{gs} to \texttt{shell\_escape\_commands}, and using
-\texttt{-\/-shell-restricted} instead of \texttt{-\/-shell-escape}: look
-at the documentation of your \TeX~distribution. For example, on Debian
-Linux with TeXLive:
+\hypertarget{the-big-picture-and-caveats}{%
+\subsection{The Big Picture and
+caveats}\label{the-big-picture-and-caveats}}
 
-\begin{Shaded}
-\begin{Highlighting}[]
-\ExtensionTok{%}\NormalTok{ export shell_escape_commands=}\VariableTok{$(}\ExtensionTok{kpsewhich}\NormalTok{ -expand-var }\StringTok{'$shell_escape_commands'}\VariableTok{)}\NormalTok{,lilypond,gs}
-\ExtensionTok{%}\NormalTok{ lualatex --shell-restricted DOCUMENT.tex}
-\end{Highlighting}
-\end{Shaded}
-
-\hypertarget{basic-operation}{%
-\subsection{Basic Operation}\label{basic-operation}}
-
 Once \lyluatex~is loaded it provides commands and environments to
 include musical scores and score fragments which are produced using the
 GNU LilyPond score writer. They are encoded in LilyPond input language,
@@ -224,7 +234,11 @@
 mechanism --, and it will match the score's layout to that of the text
 document. \lyluatex~will produce PDF image files which are automatically
 included within the current paragraph, in their own paragraphs or as
-full pages.
+full pages. The behaviour of \lyluatex~and the appearance of the
+resulting scores are highly configurable through package, global, and
+per-score options which are globally described in the
+\protect\hyperlink{option-handling}{Option Handling} section below and
+in detail throughout the rest of this manual.
 
 \lyluatex~aims at being an upwards-compatible drop-in replacement for
 the \highlight{lilypond-book} preprocessor shipping with
@@ -257,6 +271,67 @@
 \highlight{lilypond-book}, you should load it with options as follows:
 \cmd{usepackage[nofragment, insert=systems]\{lyluatex\}}.
 
+\lyIssue{Note:}
+
+By default \lyluatex~invokes LilyPond simply as \texttt{lilypond}. If
+LilyPond is installed in another location or a specific version of
+LilyPond should be used the invocation is controlled with the
+\option{program} option, see \protect\hyperlink{program}{The LilyPond
+Executable}.
+
+\lyIssue{Note:} \lyluatex~can only be used with \LuaLaTeX, and compiling
+with any other \LaTeX~engine will fail.
+
+\lyIssue{Note:} In order to avoid unexpected behaviour it is strongly
+suggested that documents are generally compiled from their actual
+directory, i.e.~without referring to it through a path. This is because
+in many places during the compilation process relative paths are
+calculated from this starting point. Building \emph{out-of-tree} isn't
+supported, though it should be possible with the following workarounds:
+
+\begin{enumerate}
+\def\labelenumi{\arabic{enumi}.}
+\tightlist
+\item
+  \texttt{includepaths=\{..\}}: (for example, if you build from a
+  subdirectory of main directory) tell lyluatex to search the parent
+  directory;
+\item
+  if the book contain figures, enter only the name of the files, then
+  set two paths, e.g.:
+  \texttt{\textbackslash{}graphicspath\{\{images/\}\{../images/\}\}}, so
+  images will be found either way.
+\end{enumerate}
+
+\lyIssue{NOTE:} \lyluatex~requires that \LuaLaTeX~is started with the
+\texttt{-\/-shell-escape} command line option to enable the execution of
+arbitrary shell commands, which is necessary to let LilyPond compile the
+inserted scores on-the-fly and to perform some auxiliary shell
+operations. However, this opens a significant security hole, and only
+fully trusted input files should be compiled. You may mitigate (but not
+totally remove) this security hole by adding \texttt{lilypond} and
+\texttt{gs} to \texttt{shell\_escape\_commands}, and using
+\texttt{-\/-shell-restricted} instead of \texttt{-\/-shell-escape}: look
+at the documentation of your \TeX~distribution. For example, on Debian
+Linux with TeXLive:
+
+\begin{Shaded}
+\begin{Highlighting}[]
+\ExtensionTok{%}\NormalTok{ export shell_escape_commands=}\VariableTok{$(}\ExtensionTok{kpsewhich}\NormalTok{ -expand-var }\StringTok{'$shell_escape_commands'}\VariableTok{)}\NormalTok{,lilypond,gs}
+\ExtensionTok{%}\NormalTok{ lualatex --shell-restricted DOCUMENT.tex}
+\end{Highlighting}
+\end{Shaded}
+
+\hypertarget{basic-operation}{%
+\subsection{Basic Operation}\label{basic-operation}}
+
+\lyluatex~is loaded with the command
+\texttt{\textbackslash{}usepackage\{lyluatex\}} which also accepts a
+number of \texttt{key=value} options. Their general use is described in
+the \protect\hyperlink{option-handling}{Option Handling} section below.
+If LilyPond can be invoked through \texttt{lilypond} on the given system
+using the package without any options already provides a usable system.
+
 \lyMargin{lilypond\index{lilypond}}
 
 The basic mode of inserting scores into text documents is the
@@ -311,6 +386,15 @@
 and all options will be used to determine if the score has already been
 compiled earlier, so unnecessary recompilations are avoided.
 
+\lyIssue{Note:} Despite its familiar appearance, this environment is
+very special, using a mechanism specific to \LuaLaTeX. One consequence
+is that you necessarily need a newline after
+\texttt{\textbackslash{}begin\{lilypond\}}, and before
+\texttt{\textbackslash{}end\{lilypond\}}; another is that you have to be
+careful when you want to wrap this environment in a custom one: see
+\protect\hyperlink{wrapping-commands}{Wrapping \lyluatex~commands} and
+the examples at the end of the manual.
+
 \lyCmd{lilypond}
 
 Very short fragments of LilyPond code can be entered inline using the
@@ -479,10 +563,28 @@
 files representing one system each. By default the systems are separated
 by a paragraph and a variable skip depending on the staffsize.
 
+\lyIssue{Note:}
+
+\option{insert=systems} implies the use of
+\texttt{lilypond-book-preamble.ly}. It is worth pointing out that the
+score will \emph{not} have any notion of pages anymore - resulting in
+the staff-staff spacing to be minimal/natural. Usually LilyPond will
+space out the inter-staff (not -system!) space when the page is not
+filled, but with \option{insert=systems} this will not happen. While
+this behavior is usually desirable when including score examples in
+text, it may result in suboptimal output for multi-page scores, when
+there's the typical issues of how many systems will fit on a page.
+
+Also notice that by default pages will be ragged-bottom, and LilyPond
+will not make any efforts to optimize page breaks.
+\cmd{betweenLilyPondSystem} can be used when the space between systems
+seems too tight, for example using something like
+\texttt{\textbackslash{}vfill}.
+
 \lyCmd{betweenLilyPondSystem}
 
-However, if a macro \cmd{betweenLilyPondSystem} is defined it will be
-expanded between each system. This macro is documented in
+If a macro \cmd{betweenLilyPondSystem} is defined it will be expanded
+between each system. This macro is documented in
 \href{http://lilypond.org/doc/v2.18/Documentation/usage/latex}{LilyPond
 documentation}. It must accept one argument, which will be the number of
 systems already printed in the score (`1' after the first system). With
@@ -540,6 +642,15 @@
 setting of these two options means that LilyPond does \emph{not} print
 page numbers while \LaTeX~continues to print headers and footers.
 
+The same may be achieved only for first page with
+\option{print-first-page-number}.
+
+\lyOption{first-page-number}{false}
+
+Normally, \lyluatex~should automatically determine the first page number
+of the score to match its place in the document. Should you like to
+force it to another value, you may do it thanks to this option.
+
 \hypertarget{inline}{%
 \subsubsection{Inline}\label{inline}}
 
@@ -685,6 +796,13 @@
 \option{indent=0pt}. Please also see the section about
 \protect\hyperlink{indent}{Dynamic Indentation}.
 
+\lyOption{system-count}{}
+
+Forces LilyPond to produce a fixed number of systems. This may be useful
+when LilyPond breaks a score that can manually be squeezed to one system
+less, but it is also possible to spread out a score to more systems than
+LilyPond would consider necessary.
+
 \lyOption{quote}{false}
 
 This option, which is there for compatibility with
@@ -768,7 +886,9 @@
 }
 \end{lilypond}
 
-\lyOption{max-protrusion}{\cmd{maxdimen}}\lyOption{max-left-protrusion}{default}\lyOption{max-right-protrusion}{default}
+\lyOption{max-protrusion}{\cmd{maxdimen}}
+\lyOption{max-left-protrusion}{default}
+\lyOption{max-right-protrusion}{default}
 
 These options set the protrusion limit. If either of the \texttt{-left-}
 or \texttt{-right-} options is unset then the value will be taken from
@@ -1116,7 +1236,9 @@
 each other. Therefore \lyluatex~by default passes the text document's
 three font families to their directy LilyPond counterparts.
 
-\lyOption{rmfamily}{}\lyOption{sffamily}{}\lyOption{ttfamily}{}
+\lyOption{rmfamily}{}
+\lyOption{sffamily}{}
+\lyOption{ttfamily}{}
 
 The roman, sans, and mono fonts can also be specified explicitly to be
 passed into the LilyPond document independently from the text document's
@@ -1359,10 +1481,10 @@
 
 \lyOption{includepaths}{./}
 
-With the \option{includepaths} option a comma-separated list of search
-paths can be specified. These paths will be used by \lyluatex~to locate
-external files, and relative paths are searched for in the following
-order:
+With the \option{includepaths} option a comma-separated list (enclosed
+in curly brackets) of search paths can be specified. These paths will be
+used by \lyluatex~to locate external files, and relative paths are
+searched for in the following order:
 
 \begin{itemize}
 \tightlist
@@ -1381,6 +1503,14 @@
 code. Paths starting with the tilde will implicitly be expanded to
 absolute paths in that process.
 
+\begin{Shaded}
+\begin{Highlighting}[]
+\FunctionTok{\textbackslash{}lysetoption}\NormalTok{\{includepaths\}\{}\FunctionTok{\textbackslash{}string}\NormalTok{~/lilypond-lib\}}
+
+\FunctionTok{\textbackslash{}lilypondfile}\NormalTok{[includepaths=\{}\FunctionTok{\textbackslash{}string}\NormalTok{~/lilypond-lib,/home/johndoe/project-lib\}]}
+\end{Highlighting}
+\end{Shaded}
+
 \hypertarget{program}{%
 \subsubsection{LilyPond Executable}\label{program}}
 
@@ -1454,6 +1584,36 @@
 \texttt{\textless{}documentname\textgreater{}.list} files and only
 remove scores that are not referenced by \emph{any} list file.
 
+\hypertarget{writing-headers-to-include-file}{%
+\subsubsection{Writing headers to include
+file}\label{writing-headers-to-include-file}}
+
+\lyOption{write-headers}{false}
+
+When using \texttt{\textbackslash{}lilypondfile} it is possible to write
+a copy of the LilyPond headers defining the layout and appearance of the
+score to an include file. When working on the score in an external
+editor this makes it possible to include this file to see the score in
+the layout it will have in the final \LaTeX\\
+document. Using this option together with non-filebased scores makes no
+sense, therefore it is ignored while a warning is issued.
+
+\emph{NOTE}: Of course this will produce conflicts if a LilyPond file is
+used in multiple \LaTeX~documents.
+
+If set to a \emph{path} the LilyPond headers defining the layout and
+appearance of the score will be exported to a file
+\texttt{\textless{}path\textgreater{}/\textless{}input-file-basename\textgreater{}-lyluatex-headers.ily}.
+The target directory will be created if necessary.
+
+If set to a \emph{filename} (i.e.~a path with a file extension) the
+headers will be written to this specific file. This is useful because in
+most cases the headers will be consistent throughout a \LaTeX~document,
+so it should be unncecessary to copy them for all input files. A typical
+use case might be to specify one header file as a package option while
+overriding the option for specific scores that require different headers
+(e.g.~in combination with a different \texttt{staffsize})
+
 \hypertarget{pdf-optimization}{%
 \subsubsection{PDF optimization}\label{pdf-optimization}}
 
@@ -1472,7 +1632,10 @@
 basic problem is when LilyPond can't be started at all. \lyluatex~will
 correctly determine and report an error if \LuaLaTeX~has been started
 without the \option{--shell-escape} option or if the \option{program}
-option doesn't point to a valid LilyPond executable.
+option doesn't point to a valid LilyPond executable. However, if the
+\option{showfailed} option is also set then only a \emph{warning} is
+issued while instead of a score an information box is created in the
+document, informing about the problem.
 
 Two other situations that are correctly recognized are when LilyPond
 \emph{reports} a compilation failure but still produces a (potentially
@@ -1501,6 +1664,30 @@
 warning is issued and a box with an informative text is typeset into the
 resulting document.
 
+\hypertarget{forcing-re-compilation}{%
+\subsubsection{Forcing (Re-)Compilation}\label{forcing-re-compilation}}
+
+\lyOption{force-compilation}{false}
+
+In some cases \lyluatex's heuristics to determine the need for
+recompilation may fail, especially when not all relevant code is
+included through LilyPond's \cmd{include} command, in which cases
+\lyluatex~may consider the content unchanged. In such cases the
+\option{force-compilation} option skips the checks and unconditionally
+recompiles the score, which may be a better solution than to
+(selectively) delete the scores from the \option{tmpdir} directory.
+
+\hypertarget{bug-workaround}{%
+\subsubsection{Bug workaround}\label{bug-workaround}}
+
+\lyOption{fix\_badly\_cropped\_staffgroup\_brackets}{false}
+
+This option is a dirty workaround for a
+\href{https://lists.gnu.org/archive/html/lilypond-user/2018-11/msg00039.html}{known
+bug} of LilyPond. It's disabled by default; should you enable it
+globally, you may cancel it locally with
+\option{nofix\_badly\_cropped\_staffgroup\_brackets}.
+
 \hypertarget{musicxml-options}{%
 \subsection{MusicXML options}\label{musicxml-options}}
 
@@ -1509,7 +1696,9 @@
 This option does the same for \texttt{\textbackslash{}musicxmlfile} as
 \option{program} for \texttt{\textbackslash{}lilypondfile}.
 
-\lyOption{language}{}\lyOption{absolute, lxml, verbose}{false}\lyOption{
+\lyOption{language}{}
+\lyOption{absolute, lxml, verbose}{false}
+\lyOption{
 no-articulation-directions, no-beaming, no-page-layout, no-rest-positions
 }{true}
 
@@ -1574,7 +1763,8 @@
 be found in \protect\hyperlink{insert-raw-pdf}{Wrapping Raw PDF
 Filenames}.
 
-\printindex\addcontentsline{toc}{section}{Index}
+\printindex
+\addcontentsline{toc}{section}{Index}
 
 \hypertarget{examples}{%
 \section{Examples}\label{examples}}
@@ -1584,31 +1774,22 @@
 
 \addcontentsline{toc}{subsection}{Insert Systems}
 \hypertarget{insert-systems}{}
-
 \section*{Insert System-by-System}
-
 By default scores defined by the \option{lilypond} environment or the \cmd{lilypondfile} command are inserted as a sequence of systems.
-
 \lyluatex\ determines the vertical space between the systems as a flexible length calculated from the \emph{staff size} of the score (as opposed to from the font size) to produce an regular-looking vertical spacing:
-
 \begin{lilypond}[]
 {
   \repeat unfold 30 { c' d' e' d' }
 }
 \end{lilypond}
-
 The following score has a significantly smaller staff size, and consequently the inter-system space is reduced:
-
 \begin{lilypond}[staffsize=12]
 {
   \repeat unfold 36 { c' d' e' d' }
 }
 \end{lilypond}
-
 \subsection*{Before and After the Score}
-
 \cmd{preLilyPondExample} and \cmd{postLilyPondExample} allow some code to be printed before and after the score. This may for example be used to wrap the resulting score in an environment. In the following example rules are printed:
-
 \def\preLilyPondExample{%
 \par\bigskip
 \noindent Before the score:
@@ -1617,30 +1798,24 @@
 \par\bigskip
 \hrule\par\medskip\noindent After the score
 \par\bigskip}
-
 \begin{verbatim}
   \newcommand{\preLilyPondExample}{%
   \par\bigskip
   \noindent Before the score:
   \par\medskip\hrule\par\medskip}
-
   \newcommand{\postLilyPondExample}{%
   \par\bigskip
   \hrule\par\medskip\noindent After the score
   \par\bigskip}
 \end{verbatim}
-
 \begin{lilypond}[]
 {
   \repeat unfold 30 { c' d' e' d' }
 }
 \end{lilypond}
-
 \subsection*{Configuring the Inter-System Content}
-
 \let\preLilyPondExample\undefined
 \let\postLilyPondExample\undefined
-
 Using \cmd{betweenLilyPondSystem} it is possible to define a macro that is
 expanded between each system pair. It is given the index of the previous system
 as an argument to work with. The following example simply prints that index
@@ -1647,13 +1822,11 @@
 between the systems, but with some programming more complicated and useful
 things could be done, for example printing a rule after every third system or
 conditionally insert a page break.
-
 \def\betweenLilyPondSystem#1{%
 \begin{center}
 System #1
 \end{center}
 }
-
 \begin{verbatim}
   \newcommand{\betweenLilyPondSystem}[1]{%
   \begin{center}
@@ -1661,24 +1834,18 @@
   \end{center}
   }
 \end{verbatim}
-
 \bigskip
-
 \begin{lilypond}[]
 {
   \repeat unfold 30 { c' d' e' d' }
 }
 \end{lilypond}
-
 \let\betweenLilyPondSystem\undefined
 
 
-
 \addcontentsline{toc}{subsection}{Insert Inline}
 \hypertarget{insert-inline}{}
-
 \section*{Insert Scores Inline}
-
 With the \option{insert=inline} option it is simple to insert arbitrary
 notational fragments in the \lilypond{ e'8 d'16 e' } continuous text of a
 document. By default the staffsize is scaled to be 2/3 of the staffsize a
@@ -1685,17 +1852,14 @@
 regular score would have at this point. This means if the \option{staffsize}
 option is modified globally or locally then the staffsize of the inline score is
 changed too.
-
 In order to make the size of inline scores independent from the regular
 staffsize the option \option{inline-staffsize} can be used the same way as
 \option{staffsize}. \lilypond[inline-staffsize=8]{ e'8 d'16 e' } has the inline
 staffsize manually set to \texttt{8}.
-
 \paragraph{Alignment and padding} By default inline scores are vertically
 centered to a line 1/2em above the text's baseline. \lilypond[valign=top]{ e'8
 d'16 e' } but the score can also be aligned \lilypond[valign=bottom]{ e'8 d'16
 e' } to the top or the baseline of the text.
-
 Unfortunately this can only consider the borders of the \emph{image} and not
 those of the \emph{score} or the staff lines. To alleviate this situation a
 specific vertical offset can be given with \option{voffset=-3pt} (or any other
@@ -1702,12 +1866,10 @@
 \TeX\ lengths). This offset is calculated after the alignment.
 \lilypond[valign=bottom,voffset=-6pt]{ e'8 d'16 e' } is inserted with
 \option{valign=bottom,voffset=-4pt}.
-
 Horizontally inline scores are padded by \option{hpadding=0.75ex} -- except if
 they happen to appear at the beginning or end of a line, as can be seen in the
 last score in the previous paragraph. \lilypond[hpadding=2em]{ e'8 d'16 e' }
 Increasing the \option{hpadding} will ensure more space around the score.
-
 \paragraph{Bare Inline scores} \option{insert=bare-inline} will remove all the
 staff elements (staff symbol, time signature, clef) by implicitly applying
 \option{nostaff}, which is most useful for including notational symbols like
@@ -1723,77 +1885,58 @@
 precompiled PDF images.
 
 
-
 \addcontentsline{toc}{subsection}{Choosing Systems}
 \hypertarget{print-only}{}
-
 \def\postLilyPondExample{\par\bigskip\hrule\par\bigskip}
-
 \section*{Print only Selected Systems or Pages}
-
-The \texttt{print-only} option allows to limit the printed systems or pages from
-a score. A typical use case is to print a score interspersed with comments.  The
-advantage of this approach is that the score is compiled only once while the
-individual systems are simply reused by \LaTeX.
-
+The \texttt{print-only} and \texttt{do-not-print} options allow to limit
+the printed systems or pages from a score. A typical use case is to print
+a score interspersed with comments.  The advantage of this approach is that
+the score is compiled only once while the individual systems are simply
+reused by \LaTeX.
 Throughout this document we'll demonstrate the different options to
 select systems from the following score:
-
 \lilypondfile[verbatim]{eight-systems.ly}
-
 The simplest selection is a single system: \texttt{print-only=4}
-
 \lilypondfile[print-only=4]{eight-systems.ly}
-
 Ranges are also possible: \texttt{print-only=3-5}, with the special form of
 \texttt{print-only=6-} which prints from the given system throughout the end of
 the score. Negative ranges can be given with \texttt{print-only=7-5}
-
 \lilypondfile[print-only=3-5]{eight-systems.ly}
-
 \lilypondfile[print-only=6-]{eight-systems.ly}
-
 \lilypondfile[print-only=7-5]{eight-systems.ly}
-
 With a comma-separated list an arbitrary sequence of systems can be specified.
 The list has to be enclosed in curly brackets: \texttt{print-only={4,1,2}}
-
 \lilypondfile[print-only={4,1,2}]{eight-systems.ly}
-
 Each element of the list can include any of the forms described above:\\
 \texttt{print-only={3,5-7,4,7-}}
-
 \lilypondfile[print-only={3,5-7,4,7-}]{eight-systems.ly}
-
+\texttt{do-not-print} does the opposite: it prevents the list of systems from
+being printed. It might be used alone, or in combination with
+\texttt{print-only}:\\
+\texttt{print-only=3-,do-not-print=6}
+\lilypondfile[print-only=3-,do-not-print=6]{eight-systems.ly}
 The functionality is identical with fullpage scores where the selection applies
 to \emph{pages} instead. This can for example be used when the “score” file
 contains a number of individual pieces (e.g. songs for a song book), and
 individual selections are to be printed.
-
-Systems have some specific behaviour with regard to \emph{indent}, but this is demonstrated in its own file \texttt{dynamic-indent.tex}.
-
+Systems have some specific behaviour with regard to \emph{indent},
+but this is demonstrated in its own file \texttt{dynamic-indent.tex}.
 \let\postLilyPondExample\undefined
 
 
-
 \addcontentsline{toc}{subsection}{Dynamic Indent Handling}
 \hypertarget{dynamic-indent}{}
-
 \def\postLilyPondExample{\par\bigskip\hrule\par\bigskip}
-
 \section*{Dynamic Indent}
-
 This document demonstrates the use of \texttt{indent} and \texttt{autoindent},
 partially in combination with \texttt{print-only}.
-
 \texttt{indent=1cm} indents the first line, but if the resulting score contains
 only one system this indent is suppressed (issuing a warning on the console):
-
 \begin{lilypond}[indent=1cm]
   \set Staff.instrumentName = "Violin"
   \repeat unfold 12 { c' d' e' d' }
 \end{lilypond}
-
 \begin{lilypond}[indent=1cm]
   {
     \set Staff.instrumentName = "Violin"
@@ -1800,38 +1943,30 @@
     c' d' e' d'
   }
 \end{lilypond}
-
 If the output of a score which contains more than one system is limited to the
 first system using \texttt{print-only=1} then the indent is removed but the
 score is recompiled to ensure a full-length system. The following score shows
 the two-system score from above (with \texttt{indent=1cm}), limited to its first
 system:
-
 \begin{lilypond}[indent=1cm,print-only=1]
   \set Staff.instrumentName = "Violin"
   \repeat unfold 12 { c' d' e' d' }
 \end{lilypond}
-
 Note that this behaviour also applies when \texttt{print-only} causes the first
 system to be printed at another position, e.g. with \texttt{print-only={3,1,2}}.
 In this case the indent of the first system is suppressed in order to avoid a
 “hole”. Of course this is a corner case, but might be useful when a score
 consists of separate entities (examples, exercises) per system.
-
 \begin{lilypond}[indent=1cm,print-only={3,1,2},max-protrusion=0.5cm]
   \repeat unfold 25 { c' d' e' d' }
 \end{lilypond}
-
 If a protrusion limit has been set with \texttt{max-protrusion=0.5cm} and the
 score exceeds that limit in spite of \texttt{indent=1cm} then the whole score
 will appropriately be narrowed:
-
 \begin{lilypond}[indent=1cm,max-protrusion=0.5cm]
   \set Staff.instrumentName = "Violin I. and II."
   \repeat unfold 11 { c' d' e' d' }
 \end{lilypond}
-
-
 This doesn't really look good because the indentation of the second system
 wouldn't have been necessary since only the first system exceeds the protrusion
 limit. The solution to this situation is the option \texttt{autoindent} which
@@ -1838,41 +1973,33 @@
 handles the indentation \emph{automatically} and set the indent to a value that
 will make the \emph{first} system fit into the protrusion limit and leave the
 remaining systems unchanged:
-
 \begin{lilypond}[autoindent=true,max-protrusion=0.5cm]
   \set Staff.instrumentName = "Violin I. and II."
   \repeat unfold 11 { c' d' e' d' }
 \end{lilypond}
-
-
 However, if the protrusion limit is not only exceeded by the \emph{first} system
 (which should be the typical case due to the instrument name) \texttt{lyluatex}
 will deal with the situation by narrowing the \emph{whole} score by the
 appropriate amount and adjusting the indent of the first system so all systems
 will just fit into the protrusion limit:
-
 \begin{lilypond}[autoindent=true,max-protrusion=0.5cm]
   \set Staff.instrumentName = "Violin I. and II."
   \set Staff.shortInstrumentName = "Violin I/II"
   \repeat unfold 11 { c' d' e' d' }
 \end{lilypond}
-
 There is one special case to be mentioned. As described above the indent is
 deactivated if the first system of a score is printed at a later position.
 However, if this score will exceed the left protrusion limit \texttt{autoindent}
 will be automatically activated to avoid having the \emph{whole} score narrowed:
-
 \begin{lilypond}[indent=1cm,print-only={3,1,2},max-protrusion=0.5cm]
   \set Staff.instrumentName = "Violin"
   \repeat unfold 25 { c' d' e' d' }
 \end{lilypond}
-
 \paragraph{Right protrusion}
 The dynamic handling of (automatic) indent also works correctly when there is
 protrusion handling to the right. The following score has the ties manually
 shaped to exceed the staff symbol by 10, and 7 staff spaces, and
 \texttt{max-protrusion=1cm} .
-
 \begin{lilypond}[nofragment,max-protrusion=1cm,]
 {
   \set Staff.instrumentName = "Violin 1 & 2"
@@ -1884,57 +2011,42 @@
   c'
 }
 \end{lilypond}
-
-
 \paragraph{Performance considerations}
 The handling of indent suppression may require up to four compilations of the
 score, but these are handled automatically, and the resulting intermediate
 stages of the score are cached just like the scores actually used in the
 document.
-
 The \texttt{autoindent} option is active by default but will be deactivated if
 \texttt{indent} is set explicitly. It has to be noted that this option will add
 more LilyPond compilations and therefore compilation time. But it will only
 apply and be executed if the score exceeds the protrusion limit, so it can only
 occur in circumstances where multiple LilyPond runs are expected anyway.
-
 \let\postLilyPondExample\undefined
 
 
-
 \addcontentsline{toc}{subsection}{Font Handling}
 \hypertarget{fonts}{}
-
 \defaultfontfeatures{Ligatures=TeX,Numbers=OldStyle,Scale=MatchLowercase}
 \setmainfont{Linux Libertine O}
 \setsansfont[BoldFont={Linux Biolinum O Bold}]{Linux Biolinum O}
 \setmonofont{Inconsolata}
-
 \section*{Font Handling}
-
 To demonstrate the font handling features of \lyluatex\ we will repeatedly
 include the following score from an external file.  It includes roman (lyrics,
 instrument name), sans (rehearsal mark), and mono (tempo) text, first using
 LilyPond's built-in default fonts.
-
 \lilypondfile[verbatim]{fonts}
-
 \bigskip
 The current document uses \option{fontspec} to set roman font to \emph{Linux
 Libertine O}, sans font to \emph{Linux Biolinum O}, and mono font to
 \emph{Inconsolata}. So if you compile this document yourself and don't have
 these fonts installed you will receive unexpected results.
-
 \subsection*{Passing Document Fonts to Score}
-
 With \option{pass-fonts} the currently active font families for roman, sans, and
 mono fonts are passed to LilyPond in order to achieve the most coherent
 appearance between text and music.
-
 \bigskip
-
 \lilypondfile[pass-fonts]{fonts}
-
 \bigskip
 Note that LilyPond loads fonts differently than \LaTeX\ and can only make use of
 fonts installed as system fonts, fonts that are only installed through a \LaTeX\
@@ -1942,14 +2054,11 @@
 not installed system-wide (e.\,g. the default fonts) LilyPond will use rather
 ugly fallback fonts. This can't be demonstrated here but the section about
 explicitly setting font families will include an example.
-
 The inherent problem of fallback fonts, especially with \LaTeX's default
 settings, is the reason \option{pass-fonts} is inactive by default. But the
 general recommendation is to set \option{pass-fonts} as package option if the
 text document uses fonts that are available to LilyPond.
-
 \bigskip
-
 \sffamily \option{current-font-as-main} will use the font that is
 \emph{currently} used for typesetting as LilyPond's main (roman) font. This can
 make sure that the score's main font (and roman is usually the font used most in
@@ -1960,12 +2069,9 @@
 inconsistency not between the score and the surrounding text but between
 different scores in a document. For all these reasons the option is by default
 set to \texttt{false}.
-
 \bigskip
 \lilypondfile[pass-fonts,current-font-as-main]{fonts}
-
 \subsection*{Setting Score Fonts Explicitly}
-
 With \option{rmfamily}, \option{sffamily}, and \option{ttfamily} specific
 families can be set to arbitrary fonts, independently from the text document.
 For the following score \option{ttfamily=\{TeXGyre Adventor\}} is
@@ -1973,31 +2079,24 @@
 installed if you want to successfully compile this document.} Note that this
 implicitly sets \option{pass-fonts=true}, and \emph{Linux Libertine O} and
 \emph{Linux Biolinum O} are used from the text document.
-
 \bigskip
 \lilypondfile[ttfamily={TeXGyre Adventor}]{fonts}
-
 \highlight{NOTE:} when \option{rmfamily} is set explicitly
 \option{current-font-as-main} is forced to \texttt{false} to ensure that the
 roman font is actually used. The next score sets \option{rmfamily=\{TeXGyre
 Adventor\}} and \option{current-font-as-main}, and despite the current font still being \cmd{sffamily}
 \emph{Adventor} is used as the score's main font:
-
 \bigskip
 \lilypondfile[current-font-as-main,rmfamily={TeXGyre Adventor}]{fonts}
-
 \subsection*{LilyPond's Font Fallback}
-
 If unavailable fonts are set in a LilyPond document they will \emph{silently} be
 replaced with fallback fonts that tend to cause ugly results. This will be shown
 by setting \option{rmfamily=FantasyFontOne}, \option{sffamily=FantasyFontTwo},
 and \option{tfamily=FantasyFontThree}:
-
 \bigskip
 \lilypondfile[rmfamily=FantasyFontOne,%
 sffamily=FantasyFontTwo,%
 ttfamily=FantasyFontThree]{fonts}
-
 This can happen in several contexts: apart from compiling the document on a
 different computer where the used fonts are missing it is most likely to occur
 with the \option{pass-fonts} option, when the text document uses internal
@@ -2005,27 +2104,28 @@
 family is specified explicitly with an option and the other families are passed
 from the text document.
 
+\defaultfontfeatures{Ligatures=TeX,Numbers=OldStyle,Scale=MatchLowercase}
+\setmainfont{Linux Libertine O}
+\setsansfont[BoldFont={Linux Biolinum O Bold}]{Linux Biolinum O}
+\setmonofont{Inconsolata}
 
-
 \addcontentsline{toc}{subsection}{Wrapping Commands}
 \hypertarget{wrappingcommands}{}
-
+\VerbatimFootnotes
 \section*{Wrapping Commands}
-
+\subsection*{Command within commands}
 \cmd{lily} can be wrapped within another command as usual:
-
 \begin{verbatim}
 \newcommand\mylily[2][1]{\lily[inline-staffsize=10, #1]{#2}}
-
 This is \mylily[voffset=10pt]{a' b' c''} an example.
 \end{verbatim}
-
 \newcommand\mylily[2][1]{\lily[inline-staffsize=10, #1]{#2}}
-
-This is \mylily[voffset=10pt]{a' b' c''} an example.
-
+This is \mylily[voffset=10pt]{a' b' c''} an example.\par
+\subsection*{Environment within environments}
+\emph{It isn't possible to wrap \highlight{ly} environment within a command.}\par
 It's possible to wrap \highlight{ly} within and environment, but there are
-several drawbacks:
+several drawbacks\footnote{%
+Those drawbacks are:
 \begin{itemize}
   \item this custom environment cannot have optional parameters. To be more
   precise, if it has only optional parameters, it will be necessary to add \verb`[]`
@@ -2038,34 +2138,32 @@
     \item or use the \TeX\ primitives \verb`\ly \endly` (not only for \highlight{ly},
     but also for other environments).
   \end{itemize}
-\end{itemize}
-
+\end{itemize}%
+}.
+To avoid those drawbacks, \lyluatex\ defines a special command, \verb`\lynewenvironment`,
+that behaves as you'd expect from \verb`\newenvironment`.
 \begin{verbatim}
-\newenvironment{myly}{%
+\lynewenvironment{myly}{%
   This is \emph{my} lilypond environment.
-  \begin{ly}[]%
+  \begin{ly}%
 }{%
   \end{ly}
 }
-
 \begin{myly}
   a b c
 \end{myly}
 \end{verbatim}
-
 \newenvironment{myly}{%
   This is \emph{my} lilypond environment.
-  \begin{ly}[]%
+  \begin{ly}%
 }{%
   \end{ly}
 }
-
 \begin{myly}
   a b c
 \end{myly}
-
 \begin{verbatim}
-\newenvironment{lyfigure}[2][]{%
+\lynewenvironment{lyfigure}[2][]{%
 \edef\mycaption{#2}
 \begin{figure}
 \begin{center}
@@ -2076,14 +2174,12 @@
 \end{center}
 \end{figure}
 }
-
-\begin{lyfigure}{caption}
+\begin{lyfigure}{This is a caption}
 a' b' c
 d' e' f
 \end{lyfigure}
 \end{verbatim}
-
-\newenvironment{lyfigure}[2][]{%
+\lynewenvironment{lyfigure}[2][]{%
 \edef\mycaption{#2}
 \begin{figure}
 \begin{center}
@@ -2094,14 +2190,12 @@
 \end{center}
 \end{figure}
 }
-
-\begin{lyfigure}{caption}
+\begin{lyfigure}{This is a caption}
 a' b' c
 d' e' f
 \end{lyfigure}
-
 \begin{verbatim}
-\newenvironment{lyotherfigure}[1][]{%
+\lynewenvironment{lyotherfigure}[1][]{%
 \edef\option{#1}
 \figure
 \center
@@ -2112,14 +2206,12 @@
 \endcenter
 \endfigure
 }
-
-\begin{lyotherfigure}[]
+\begin{lyotherfigure}
 d' e' f
 a' b' c
 \end{lyotherfigure}
 \end{verbatim}
-
-\newenvironment{lyotherfigure}[1][]{%
+\lynewenvironment{lyotherfigure}[1][]{%
 \edef\option{#1}
 \figure
 \center
@@ -2130,19 +2222,108 @@
 \endcenter
 \endfigure
 }
-
-\begin{lyotherfigure}[]
+\begin{lyotherfigure}
 d' e' f
 a' b' c
 \end{lyotherfigure}
+\begin{verbatim}
+\begin{lyotherfigure}[This time with a caption]
+d' e' f
+a' b' c
+\end{lyotherfigure}
+\end{verbatim}
+\begin{lyotherfigure}[This time with a caption]
+d' e' f
+a' b' c
+\end{lyotherfigure}
+\textbf{Important note:} \verb`\lynewenvironment` is intended to insert \LaTeX\ code before
+and after the scores; due to the special behavior of \verb`ly` environment, it isn't possible
+to insert \emph{LilyPond} code that way. So this won't work:
+\begin{verbatim}
+  \lynewenvironment{myly}{%
+    \begin{ly}
+      a b c
+  }{%
+    \end{ly}
+  }
+\end{verbatim}
+To do such a thing, \lyluatex\ defines a command and four options:
+\begin{itemize}
+  \item \verb`\lysavefrag` lets one save a LilyPond fragment to be re-used afterward;
+  \item \verb`include_header`, \verb`include_footer`, \verb`include_before_body` and \verb`include_after_body` options
+        let one insert such fragments at designed places within inserted score.
+\end{itemize}
+So this works:
+\begin{verbatim}
+\begin{lysavefrag}{head}
+a b c
+\end{lysavefrag}
+\begin{lysavefrag}{foot}
+g a' b
+\end{lysavefrag}
+\begin{lysavefrag}{mymark}
+\mark \default
+\end{lysavefrag}
+\begin{lysavefrag}{mymark}
+\mark \default
+\end{lysavefrag}
+begin{ly}[
+  include_before_body={head,mymark,head},
+  include_after_body=foot,
+]
+d e f
+\end{ly}
+\end{verbatim}
+It's also possible to use \verb`\lynewenvironment` to wrap such a command:
+\begin{verbatim}
+\begin{lysavefrag}{head}
+a b c
+\end{lysavefrag}
+\begin{lysavefrag}{foot}
+g a' b
+\end{lysavefrag}
+\begin{lysavefrag}{mymark}
+\mark \default
+\end{lysavefrag}
+\lynewenvironment{yourly}[1][]{%
+  {\centering test \par}
+  \begin{ly}[
+    include_before_body={head,mymark,head},
+    include_after_body=foot,
+  ]
+}{
+  \end{ly}
+}
+\begin{yourly}
+d e f
+\end{yourly}
+\end{verbatim}
+\begin{lysavefrag}{head}
+a b c
+\end{lysavefrag}
+\begin{lysavefrag}{foot}
+g a' b
+\end{lysavefrag}
+\begin{lysavefrag}{mymark}
+\mark \default
+\end{lysavefrag}
+\lynewenvironment{yourly}[1][]{%
+  {\centering test \par}
+  \begin{ly}[
+    include_before_body={head,mymark,head},
+    include_after_body=foot,
+  ]
+}{
+  \end{ly}
+}
+\begin{yourly}
+d e f
+\end{yourly}
 
 
-
 \addcontentsline{toc}{subsection}{Wrapping Raw PDF Filenames}
 \hypertarget{insert-raw-pdf}{}
-
 \section*{Wrapping Raw PDF Filenames}
-
 With the \option{raw-pdf} option it is possible to create wrapping commands that
 circumvent \lyluatex's layout considerations by working with the raw PDF
 filename of the generated score. This is especially useful for developing
@@ -2149,16 +2330,13 @@
 packages or personal class and style files. For this scores generated with
 \option{raw-pdf} define a command \cmd{lyscore} that can be used in the wrapping
 commands or environments.
-
 All examples in this document could also be realized using “default” \lyluatex\
 without \option{raw-pdf}, but they are intended to show how this low-level
 access can be used to retrieve the information from the generated score in order
 to build custom versions of commands that don't have to adhere to \lyluatex's
 pre-built strategies of including the score in the document
-
 The easiest way to use a “raw” score is to simply access \cmd{lyscore} in a
 command and pass it to an \cmd{includegraphics} macro:
-
 \begin{verbatim}
   \newcommand\lilyinline[2][]{%
   \lily[raw-pdf,%
@@ -2170,7 +2348,6 @@
   \includegraphics{\lyscore{}}%
   }
 \end{verbatim}
-
 \newcommand\lilyinline[2][]{%
 \lily[raw-pdf,insert=bare-inline,inline-staffsize=8,hpadding=0.25ex,#1]{
   \omit Stem
@@ -2177,12 +2354,9 @@
   #2}%
 \includegraphics{\lyscore{}}%
 }
-
 This basically is a way to provide pre-configured commands. In this case
 \lilyinline{ c'8 d' c' d'} it is used to pre-configure an inline
 type, entered as \verb+\lilyinline{ c'8 d' c' d'}+.
-
-
 \bigskip \cmd{lyscore} takes one mandatory argument which can be empty -- as in
 the example above --, receive a number, one of the keywords \texttt{nsystems}
 and \texttt{hoffset}, or any of the score's options. If passed a number it will
@@ -2189,7 +2363,6 @@
 return the filename of the N-th system. With \texttt{nsystems} the number of
 systems in the generated score will be returned, while \texttt{hoffset}
 generates the code that shifts the score to the left to accommodate protrusion.
-
 The following example takes an optional argument with options that are passed to
 \lyluatex, and one mandatory argument which expects the system to be used. It
 prints the given system centered in a figure and uses the file name as the
@@ -2196,7 +2369,6 @@
 caption and makes use of the score's \texttt{label}. Figure \ref{centered} shows
 the centering of a short fragment, figure \ref{fifth} the selection of the fifth
 system from a larger score.
-
 \begin{verbatim}
   \newenvironment{centeredlilypondsystem}[2][]{%
   \def\usesystem{#2}
@@ -2211,16 +2383,13 @@
   \end{center}
   \end{figure}
   }
-
   \begin{centeredlilypondsystem}[label=centered]{1}
   c'1 d' e'
   \end{centeredlilypondsystem}
-
   \begin{centeredlilypondsystem}[label=fifth]{5}
   \repeat unfold 8 { c'1 \break }
   \end{centeredlilypondsystem}
 \end{verbatim}
-
 \newenvironment{centeredlilypondsystem}[2][]{%
 \def\usesystem{#2}
 \begin{figure}
@@ -2234,22 +2403,17 @@
 \end{center}
 \end{figure}
 }
-
 \begin{centeredlilypondsystem}[label=centered]{1}
 c'1 d' e'
 \end{centeredlilypondsystem}
-
 \begin{centeredlilypondsystem}[label=fifth]{5}
 \repeat unfold 8 { c'1 \break }
 \end{centeredlilypondsystem}
-
-
 Finally there's an example showing how to iterate over the systems of a score
 using \cmd{foreach} from the \option{pgffor} package. It iterates over all the
 systems in the given score, prints them using the protrusion adjustment seen
 before, and if the system is the third it prints this information, otherwise
 just a line break:
-
 \begin{verbatim}
 \newcommand\myforlily[2][]{%
 \lily[insert=systems,raw-pdf,#1]{#2}%
@@ -2258,12 +2422,10 @@
     \ifthenelse{\equal{\n}{3}}{\par Third system\par}{\\}
     }%
 }
-
 \myforlily[staffsize=24]{
 \set Staff.instrumentName = "Vl. "
 \repeat unfold 4 { c'1 \break } }
 \end{verbatim}
-
 \newcommand\myforlily[2][]{%
 \lily[insert=systems,raw-pdf,#1]{#2}%
 \foreach \n in {1,...,\lyscore{nsystems}}%
@@ -2271,11 +2433,9 @@
     \ifthenelse{\equal{\n}{3}}{\par\bigskip Third system\par\bigskip}{\\}
     }%
 }
-
 \myforlily[staffsize=24]{
 \set Staff.instrumentName = "Vl. "
 \repeat unfold 4 { c'1 \break } }
 
 
-
 \end{document}

Modified: trunk/Master/texmf-dist/doc/support/lyluatex/lyluatexbase.cls
===================================================================
--- trunk/Master/texmf-dist/doc/support/lyluatex/lyluatexbase.cls	2019-05-28 21:25:28 UTC (rev 51251)
+++ trunk/Master/texmf-dist/doc/support/lyluatex/lyluatexbase.cls	2019-05-28 21:25:42 UTC (rev 51252)
@@ -1,11 +1,11 @@
 %Lyluatex LaTeX class.
 %
-% Copyright (C) 2015-2018 jperon and others (see CONTRIBUTORS.md)
+% Copyright (C) 2015-2019 jperon and others (see CONTRIBUTORS.md)
 % License: MIT
 % This file is part of lyluatex.
 
 \NeedsTeXFormat{LaTeX2e}
-\ProvidesClass{lyluatexbase}[2018/03/12 v1.0b]  %%LYLUATEX_DATE LYLUATEX_VERSION
+\ProvidesClass{lyluatexbase}[2019/05/27 v1.0f]  %%LYLUATEX_DATE LYLUATEX_VERSION
 
 \LoadClass[DIV=11]{scrartcl}
 \RequirePackage{lyluatex}
@@ -14,6 +14,7 @@
 \RequirePackage{listings}
 \RequirePackage{minted}
 \RequirePackage{pgffor}
+\RequirePackage{fancyvrb}
 \RequirePackage[colorlinks=true]{hyperref}
 \lysetoption{includepaths}{./, ly/}
 

Modified: trunk/Master/texmf-dist/doc/support/lyluatex/lyluatexmanual.cls
===================================================================
--- trunk/Master/texmf-dist/doc/support/lyluatex/lyluatexmanual.cls	2019-05-28 21:25:28 UTC (rev 51251)
+++ trunk/Master/texmf-dist/doc/support/lyluatex/lyluatexmanual.cls	2019-05-28 21:25:42 UTC (rev 51252)
@@ -1,11 +1,11 @@
 %Lyluatex LaTeX class for the manual.
 %
-% Copyright (C) 2015-2018 jperon and others (see CONTRIBUTORS.md)
+% Copyright (C) 2015-2019 jperon and others (see CONTRIBUTORS.md)
 % License: MIT
 % This file is part of lyluatex.
 
 \NeedsTeXFormat{LaTeX2e}
-\ProvidesClass{lyluatexmanual}[2018/03/12 v1.0b]  %%LYLUATEX_DATE LYLUATEX_VERSION
+\ProvidesClass{lyluatexmanual}[2019/05/27 v1.0f]  %%LYLUATEX_DATE LYLUATEX_VERSION
 
 \LoadClass{lyluatexbase}
 
@@ -28,3 +28,14 @@
     \addcontentsline{toc}{subsection}{#2}
     \hypertarget{#1}{}
 }
+
+
+\newcommand{\lyluatexmanualdate}{
+	\directlua{
+		local PC = string.char(37)
+		local date_fmt = PC..'Y-'..PC..'m-'..PC..'d'
+		local p = io.popen('git log -n1 --date=short --format='..PC..'"ad"', 'r')
+		tex.sprint(p and p:read('*a') or os.date(date_fmt))
+		if p then p:close() end
+	}
+}
\ No newline at end of file

Added: trunk/Master/texmf-dist/scripts/lyluatex/lyluatex-lib.lua
===================================================================
--- trunk/Master/texmf-dist/scripts/lyluatex/lyluatex-lib.lua	                        (rev 0)
+++ trunk/Master/texmf-dist/scripts/lyluatex/lyluatex-lib.lua	2019-05-28 21:25:42 UTC (rev 51252)
@@ -0,0 +1,163 @@
+-- luacheck: ignore ly log self luatexbase internalversion font fonts tex token kpse status
+local err, warn, info, log = luatexbase.provides_module({
+    name               = "lyluatex-lib",
+    version            = '1.0f',  --LYLUATEX_VERSION
+    date               = "2019/05/27",  --LYLUATEX_DATE
+    description        = "Module lyluatex-lib.",
+    author             = "The Gregorio Project  − (see Contributors.md)",
+    copyright          = "2015-2019 - jperon and others",
+    license            = "MIT",
+})
+
+local lib = {}
+lib.TEX_UNITS = {'bp', 'cc', 'cm', 'dd', 'in', 'mm', 'pc', 'pt', 'sp', 'em',
+'ex'}
+
+-------------------------
+-- General tool functions
+
+function lib.basename(str)
+--[[
+  Given the full path to a file, return only the file name (without path).
+  If there is no slash, return the full name.
+--]]
+    return str:gsub(".*/(.*)", "%1") or str
+end
+
+
+function lib.contains(table_var, value)
+--[[
+  Returns the key if the given table contains the given value, or nil.
+  A value of 'false' (string) is considered equal to false (Boolean).
+--]]
+    for k, v in pairs(table_var) do
+        if v == value then return k
+        elseif v == 'false' and value == false then return k
+        end
+    end
+end
+
+
+function lib.contains_key(table_var, key)
+-- Returs true if the given key is present in the table, nil otherwise.
+    for k in pairs(table_var) do
+        if k == key then return true end
+    end
+end
+
+
+function lib.convert_unit(value)
+--[[
+  Convert a LaTeX unit, if possible.
+  TODO: Understand what this *really* does, what is accepted and returned.
+--]]
+    if not value then return 0
+    elseif value == '' then return false
+    elseif value:match('\\') then
+        local n, u = value:match('^%d*%.?%d*'), value:match('%a+')
+        if n == '' then n = 1 end
+        return tonumber(n) * tex.dimen[u] / tex.sp("1pt")
+    else return ('%f'):format(tonumber(value) or tex.sp(value) / tex.sp("1pt"))
+    end
+end
+
+
+function lib.dirname(str)
+--[[
+  Given the full path to a file, return only the path (without file name),
+  including the last slash. If there is no slash, return an empty string.
+--]]
+    return str:gsub("(.*/).*", "%1") or ''
+end
+
+
+local fontdata = fonts.hashes.identifiers
+function lib.fontinfo(id)
+--[[
+  Return a LuaTeX font object based on the given ID
+--]]
+    return fontdata[id] or font.fonts[id]
+end
+
+
+function lib.max(a, b)
+    a, b = tonumber(a), tonumber(b)
+    if a > b then return a else return b end
+end
+
+
+function lib.min(a, b)
+    a, b = tonumber(a), tonumber(b)
+    if a < b then return a else return b end
+end
+
+
+function lib.mkdirs(str)
+    local path
+    if str:sub(1, 1) == '/' then path = '' else path = '.' end
+    for dir in str:gmatch('([^%/]+)') do
+        path = path .. '/' .. dir
+        lfs.mkdir(path)
+    end
+end
+
+
+function lib.orderedpairs(t)
+    local key
+    local i = 0
+    local orderedIndex = {}
+    for k in pairs(t) do table.insert(orderedIndex, k) end
+    table.sort(orderedIndex)
+    return function ()
+            i = i + 1
+            key = orderedIndex[i]
+            if key then return key, t[key] end
+        end
+end
+
+
+function lib.readlinematching(s, f)
+    if f then
+        local result = ''
+        while result and not result:find(s) do result = f:read() end
+        f:close()
+        return result
+    end
+end
+
+
+function lib.splitext(str, ext)
+--[[
+    If 'ext' is supplied return str stripped of the given extension,
+    otherwise return the base and extension (if any)
+--]]
+    return ext and (str:match('(.*)%.'..ext..'$') or str)
+        or (str:match('(.*)%.(%w*)$') or str)
+end
+
+
+------------------------------------
+-- Engine, version, TeX distribution
+
+local tex_engine = {}
+setmetatable(tex_engine, tex_engine)
+
+function tex_engine:__call()
+--[[
+    Defines the properties extracted from the first line of jobname.log.
+--]]
+    local f = io.open(tex.jobname..'.log')
+    if not f then return end
+    self.engine, self.engine_version, self.dist, self.dist_version, self.format, self.format_version =
+        f:read():match(
+            'This is ([^,]*), Version ([^%(]*) %(([^%)]*) ([^%)]*)%)%s+%(format=([^%)]*) ([^)]*)%)'
+        )
+    f:close()
+    return self
+end
+
+function tex_engine:__index(k) return rawget(self(), k) end
+
+
+lib.tex_engine = tex_engine
+return lib


Property changes on: trunk/Master/texmf-dist/scripts/lyluatex/lyluatex-lib.lua
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/texmf-dist/scripts/lyluatex/lyluatex-options.lua
===================================================================
--- trunk/Master/texmf-dist/scripts/lyluatex/lyluatex-options.lua	                        (rev 0)
+++ trunk/Master/texmf-dist/scripts/lyluatex/lyluatex-options.lua	2019-05-28 21:25:42 UTC (rev 51252)
@@ -0,0 +1,266 @@
+-- luacheck: ignore ly warn info log self luatexbase internalversion font fonts tex token kpse status
+local err, warn, info, log = luatexbase.provides_module({
+    name               = "lyluatex-options",
+    version            = '1.0f',  --LYLUATEX_VERSION
+    date               = "2019/05/27",  --LYLUATEX_DATE
+    description        = "Module lyluatex-options.",
+    author             = "The Gregorio Project  − (see Contributors.md)",
+    copyright          = "2015-2019 - jperon and others",
+    license            = "MIT",
+})
+
+--[[
+    This module provides functionality to handle package options and make them
+    configurable in a fine-grained fashion as
+    - package options
+    - local options (for individual instances of commands/environments)
+    - changed “from here on” within a document.
+
+-- ]]
+
+local lib = require(kpse.find_file("lyluatex-lib.lua") or "lyluatex-lib.lua")
+local optlib = {}  -- namespace for the returned table
+local Opts = {options = {}}  -- Options class
+Opts.__index = function (self, k) return self.options[k] or rawget(Opts, k) end
+setmetatable(Opts, Opts)
+
+
+function Opts:new(opt_prefix, declarations)
+--[[
+    Declare package options along with their default and
+    accepted values. To *some* extent also provide type checking.
+    - opt_prefix: the prefix/name by which the lyluatex-options module is
+        referenced in the parent LaTeX document (preamble or package).
+        This is required to write the code calling optlib.set_option into
+        the option declaration.
+    - declarations: a definition table stored in the calling module (see below)
+    Each entry in the 'declarations' table represents one package option, with each
+    value being an array (table with integer indexes instead of keys). For
+    details please refer to the manual.
+--]]
+    local o = setmetatable(
+        {
+            declarations = declarations,
+            options = {}
+        },
+        self
+    )
+    local exopt = ''
+    for k, v in pairs(declarations) do
+        o.options[k] = v[1] or ''
+        tex.sprint(string.format([[
+\DeclareOptionX{%s}{\directlua{
+    %s:set_option('%s', '\luatexluaescapestring{#1}')
+}}%%
+]],
+            k, opt_prefix, k
+        ))
+        exopt = exopt..k..'='..(v[1] or '')..','
+    end
+    tex.sprint([[\ExecuteOptionsX{]]..exopt..[[}%%]], [[\ProcessOptionsX]])
+    return o
+end
+
+function Opts:check_local_options(opts, ignore_declarations)
+--[[
+    Parse the given options (options passed to a command/environment),
+    sanitize them against the global package options and return a table
+    with the local options that should then supersede the global options.
+    If ignore_declaration is given any non-false value the sanitization
+    step is skipped (i.e. local options are only parsed and duplicates
+    rejected).
+--]]
+    local options = {}
+    local next_opt = opts:gmatch('([^,]+)')  -- iterator over options
+    for opt in next_opt do
+        local k, v = opt:match('([^=]+)=?(.*)')
+        if k then
+            if v and v:sub(1, 1) == '{' then  -- handle keys with {multiple, values}
+                while select(2, v:gsub('{', '')) ~= select(2, v:gsub('}', '')) do v = v..','..next_opt() end
+                v = v:sub(2, -2)  -- remove { }
+            end
+            if not ignore_declarations then
+                k, v = self:sanitize_option(k:gsub('^%s', ''), v:gsub('^%s', ''))
+            end
+            if k then
+                if options[k] then err('Option %s is set two times for the same score.', k)
+                else options[k] = v
+                end
+            end
+        end
+    end
+    return options
+end
+
+function Opts:is_neg(k)
+--[[
+    Type check for a 'negative' option. This is an existing option
+    name prefixed with 'no' (e.g. 'noalign')
+--]]
+    local _, i = k:find('^no')
+    return i and lib.contains_key(self.declarations, k:sub(i + 1))
+end
+
+function Opts:sanitize_option(k, v)
+--[[
+    Check and (if necessary) adjust the value of a given option.
+    Reject undefined options
+    Check 'negative' options
+    Handle boolean options (empty strings or 'false'), set them to real booleans
+--]]
+    local declarations = self.declarations
+    if k == '' or k == 'noarg' then return end
+    if not lib.contains_key(declarations, k) then err('Unknown option: '..k) end
+    -- aliases
+    if declarations[k] and declarations[k][2] == optlib.is_alias then
+        if declarations[k][1] == v then return
+        else k = declarations[k] end
+    end
+    -- boolean
+    if v == 'false' then v = false end
+    -- negation (for example, noindent is the negation of indent)
+    if self:is_neg(k) then
+        if v ~= nil and v ~= 'default' then
+            k = k:gsub('^no(.*)', '%1')
+            v = not v
+        else return
+        end
+    end
+    return k, v
+end
+
+function Opts:set_option(k, v)
+--[[
+    Set an option for the given prefix to be in effect from this point on.
+    Raises an error if the option is not declared or does not meet the
+    declared expectations. (TODO: The latter has to be integrated by extracting
+    optlib.validate_option from optlib.validate_options and call it in
+    sanitize_option).
+--]]
+    k, v = self:sanitize_option(k, v)
+    if k then
+        self.options[k] = v
+        self:validate_option(k)
+    end
+end
+
+function Opts:validate_option(key, options_obj)
+--[[
+    Validate an (already sanitized) option against its expected values.
+    With options_obj a local options table can be provided,
+    otherwise the global options stored in OPTIONS are checked.
+--]]
+    local package_opts = self.declarations
+    local options = options_obj or self.options
+    local unexpected
+    if options[key] == 'default' then
+        -- Replace 'default' with an actual value
+        options[key] = package_opts[key][1]
+        unexpected = options[key] == nil
+    end
+    if not lib.contains(package_opts[key], options[key]) and package_opts[key][2] then
+        -- option value is not in the array of accepted values
+        if type(package_opts[key][2]) == 'function' then package_opts[key][2](key, options[key])
+        else unexpected = true
+        end
+    end
+    if unexpected then
+        err([[
+    Unexpected value "%s" for option %s:
+    authorized values are "%s"
+    ]],
+            options[key], key, table.concat(package_opts[key], ', ')
+        )
+    end
+end
+
+function Opts:validate_options(options_obj)
+--[[
+    Validate the given set of options against the option declaration
+    table for the given prefix.
+    With options_obj a local options table can be provided,
+    otherwise the global options stored in OPTIONS are checked.
+--]]
+    for k, _ in lib.orderedpairs(self.declarations) do
+        self:validate_option(k, options_obj)
+    end
+end
+
+
+function optlib.is_alias()
+--[[
+    This function doesn't do anything, but if an option is defined
+    as an alias, its second parameter will be this function, so the
+    test declarations[k][2] == optlib.is_alias in Opts:sanitize_options
+    will return true.
+--]]
+end
+
+
+function optlib.is_dim(k, v)
+--[[
+    Type checking for options that accept a LaTeX dimension.
+    This can be
+    - a number (integer or float)
+    - a number with unit
+    - a (multiplied) TeX length
+    (see error message in code for examples)
+--]]
+    if v == '' or v == false or tonumber(v) then return true end
+    local n, sl, u = v:match('^%d*%.?%d*'), v:match('\\'), v:match('%a+')
+    -- a value of number - backslash - length is a dimension
+    -- invalid input will be prevented in by the LaTeX parser already
+    if n and sl and u then return true end
+    if n and lib.contains(lib.TEX_UNITS, u) then return true end
+    err([[
+Unexpected value "%s" for dimension %s:
+should be either a number (for example "12"),
+a number with unit, without space ("12pt"),
+or a (multiplied) TeX length (".8\linewidth")
+]],
+        v, k
+    )
+end
+
+
+function optlib.is_neg(k, _)
+--[[
+    Type check for a 'negative' option. At this stage,
+    we only check that it begins with 'no'.
+--]]
+    return k:find('^no')
+end
+
+
+function optlib.is_num(_, v)
+--[[
+    Type check for number options
+--]]
+    return v == '' or tonumber(v)
+end
+
+
+function optlib.is_str(_, v)
+--[[
+    Type check for string options
+--]]
+    return type(v) == 'string'
+end
+
+
+function optlib.merge_options(base_opt, super_opt)
+--[[
+    Merge two tables.
+    Create a new table as a copy of base_opt, then merge with
+    super_opt. Entries in super_opt supersede (i.e. overwrite)
+    colliding entries in base_opt.
+--]]
+    local result = {}
+    for k, v in pairs(base_opt) do result[k] = v end
+    for k, v in pairs(super_opt) do result[k] = v end
+    return result
+end
+
+
+optlib.Opts = Opts
+return optlib


Property changes on: trunk/Master/texmf-dist/scripts/lyluatex/lyluatex-options.lua
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Modified: trunk/Master/texmf-dist/scripts/lyluatex/lyluatex.lua
===================================================================
--- trunk/Master/texmf-dist/scripts/lyluatex/lyluatex.lua	2019-05-28 21:25:28 UTC (rev 51251)
+++ trunk/Master/texmf-dist/scripts/lyluatex/lyluatex.lua	2019-05-28 21:25:42 UTC (rev 51252)
@@ -1,24 +1,29 @@
--- luacheck: ignore ly log self luatexbase internalversion font fonts tex token kpse status
+-- luacheck: ignore ly log self luatexbase internalversion font fonts tex token kpse status ly_opts
 local err, warn, info, log = luatexbase.provides_module({
     name               = "lyluatex",
-    version            = '1.0b',  --LYLUATEX_VERSION
-    date               = "2018/03/12",  --LYLUATEX_DATE
+    version            = '1.0f',  --LYLUATEX_VERSION
+    date               = "2019/05/27",  --LYLUATEX_DATE
     description        = "Module lyluatex.",
     author             = "The Gregorio Project  − (see Contributors.md)",
-    copyright          = "2015-2018 - jperon and others",
+    copyright          = "2015-2019 - jperon and others",
     license            = "MIT",
 })
 
+local lib = require(kpse.find_file("lyluatex-lib.lua") or "lyluatex-lib.lua")
+local ly_opts = ly_opts  -- global ly_opts has been defined before in lyluatex.sty
+
 local md5 = require 'md5'
 local lfs = require 'lfs'
 
 local latex = {}
-local ly = {}
-local obj = {}
-local Score = {}
+local ly = {
+    err = err,
+    varwidth_available = kpse.find_file('varwidth.sty')
+}
+local Score = ly_opts.options
+Score.__index = Score
 
 local FILELIST
-local OPTIONS = {}
 local DIM_OPTIONS = {
     'extra-bottom-margin',
     'extra-top-margin',
@@ -38,6 +43,8 @@
 local HASHIGNORE = {
     'autoindent',
     'cleantmp',
+    'do-not-print',
+    'force-compilation',
     'hpadding',
     'max-left-protrusion',
     'max-right-protrusion',
@@ -56,7 +63,6 @@
     'verbose',
 }
 local TEXINFO_OPTIONS = {'doctitle', 'nogettext', 'texidoc'}
-local TEX_UNITS = {'bp', 'cc', 'cm', 'dd', 'in', 'mm', 'pc', 'pt', 'sp', 'em', 'ex'}
 local LY_HEAD = [[
 %%File header
 \version "<<<version>>>"
@@ -81,13 +87,15 @@
 }
 \layout{
     <<<staffprops>>>
+    <<<fixbadlycroppedstaffgroupbrackets>>>
 }
+<<<header>>>
 
 %%Follows original score
 ]]
 
 
---[[ ========================== Helper functions ========================== ]]
+--[[ ========================== Helper functions ========================== --]]
 -- dirty fix as info doesn't work as expected
 local oldinfo = info
 function info(...)
@@ -100,37 +108,6 @@
 end
 
 
-local function contains(table_var, value)
-    for _, v in pairs(table_var) do
-        if v == value then return true
-        elseif v == 'false' and value == false then return true
-        end
-    end
-end
-
-
-local function contains_key(table_var, key)
-    for k in pairs(table_var) do
-        if k == key then return true end
-    end
-end
-
-
-local function convert_unit(value)
-    if not value then return 0
-    elseif value == '' then return false
-    elseif value:match('\\') then
-        local n, u = value:match('^%d*%.?%d*'), value:match('%a+')
-        if n == '' then n = 1 end
-        return tonumber(n) * tex.dimen[u] / tex.sp("1pt")
-    else return ('%f'):format(tonumber(value) or tex.sp(value) / tex.sp("1pt"))
-    end
-end
-
-
-local function dirname(str) return str:gsub("(.*/)(.*)", "%1") or '' end
-
-
 local function extract_includepaths(includepaths)
     includepaths = includepaths:explode(',')
     local cfd = Score.currfiledir:gsub('^$', './')
@@ -137,18 +114,27 @@
     table.insert(includepaths, 1, cfd)
     for i, path in ipairs(includepaths) do
         -- delete initial space (in case someone puts a space after the comma)
-        includepaths[i] = path:gsub('^ ', ''):gsub('^~', os.getenv("HOME"))
+        includepaths[i] = path:gsub('^ ', ''):gsub('^~', os.getenv("HOME")):gsub('^%.%.', './..')
     end
     return includepaths
 end
 
 
-local fontdata = fonts.hashes.identifiers
-local function fontinfo(id) return fontdata[id] or font.fonts[id] end
+local function font_default_staffsize()
+    return lib.fontinfo(font.current()).size/39321.6
+end
 
 
-local function font_default_staffsize()
-    return fontinfo(font.current()).size/39321.6
+local function includes_parse(list)
+    local includes = ''
+    if list then
+        list = list:explode(',')
+        for _, included_file in ipairs(list) do
+            warn(included_file)
+            includes = includes .. '\\include "'..included_file..'.ly"\n'
+        end
+    end
+    return includes
 end
 
 
@@ -165,70 +151,12 @@
 end
 
 
-local function max(a, b)
-    a, b = tonumber(a), tonumber(b)
-    if a > b then return a else return b end
-end
-
-
-local function min(a, b)
-    a, b = tonumber(a), tonumber(b)
-    if a < b then return a else return b end
-end
-
-
-local function mkdirs(str)
-    local path = '.'
-    for dir in str:gmatch('([^%/]+)') do
-        path = path .. '/' .. dir
-        lfs.mkdir(path)
-    end
-end
-
-
-local function orderedpairs(t)
-    local key
-    local i = 0
-    local orderedIndex = {}
-    for k in pairs(t) do table.insert(orderedIndex, k) end
-    table.sort(orderedIndex)
-    return function ()
-            i = i + 1
-            key = orderedIndex[i]
-            if key then return key, t[key] end
-        end
-end
-
-
-local function process_options(k, v)
-    if k == '' or k == 'noarg' then return end
-    if not contains_key(OPTIONS, k) then err('Unknown option: '..k) end
-    -- aliases
-    if OPTIONS[k] and OPTIONS[k][2] == ly.is_alias then
-        if OPTIONS[k][1] == v then return
-        else k = OPTIONS[k][1]
-        end
-    end
-    -- boolean
-    if v == 'false' then v = false end
-    -- negation (for example, noindent is the negation of indent)
-    if ly.is_neg(k) then
-        if v ~= nil and v ~= 'default' then
-            k = k:gsub('^no(.*)', '%1')
-            v = not v
-        else return
-        end
-    end
-    return k, v
-end
-
-
 local function range_parse(range, nsystems)
     local num = tonumber(range)
     if num then return {num} end
     -- if nsystems is set, we have insert=systems
     if nsystems ~= 0 and range:sub(-1) == '-' then range = range..nsystems end
-    if not range:match('^%d+%s*-%s*%d*$') then
+    if not (range == '' or range:match('^%d+%s*-%s*%d*$')) then
         warn([[
 Invalid value '%s' for item
 in list of page ranges. Possible entries:
@@ -252,16 +180,6 @@
 end
 
 
-local function readlinematching(s, f)
-    if f then
-        local result = ''
-        while result and not result:find(s) do result = f:read() end
-        f:close()
-        return result
-    end
-end
-
-
 local function set_lyscore(score)
     ly.score = score
     ly.score.nsystems = ly.score:count_systems()
@@ -277,23 +195,34 @@
 end
 
 
-local function splitext(str, ext) return str:match('(.*)%.'..ext..'$') or str end
+--[[ ================ Bounding box calculations =========================== --]]
 
-
---[[ ================ Bounding box calculations =========================== ]]
-
 local bbox = {}
 function bbox.get(filename, line_width)
     return bbox.read(filename) or bbox.parse(filename, line_width)
 end
 
+function bbox.calc(x_1, x_2, y_1, y_2, line_width)
+    local bb = {
+        ['protrusion'] = -lib.convert_unit(("%fbp"):format(x_1)),
+        ['r_protrusion'] = lib.convert_unit(("%fbp"):format(x_2)) - line_width,
+        ['width'] = lib.convert_unit(("%fbp"):format(x_2))
+    }
+    --FIX #192: height is only calculated if really needed, to prevent errors with huge scores.
+    function bb.__index(_, k)
+        if k == 'height' then return lib.convert_unit(("%fbp"):format(y_2)) - lib.convert_unit(("%fbp"):format(y_1)) end
+    end
+    setmetatable(bb, bb)
+    return bb
+end
+
 function bbox.parse(filename, line_width)
     -- get BoundingBox from EPS file
-    local bbline = readlinematching('^%%%%BoundingBox', io.open(filename..'.eps', 'r'))
+    local bbline = lib.readlinematching('^%%%%BoundingBox', io.open(filename..'.eps', 'r'))
     if not bbline then return end
     local x_1, y_1, x_2, y_2 = bbline:match('(%--%d+)%s(%--%d+)%s(%--%d+)%s(%--%d+)')
     -- try to get HiResBoundingBox from PDF (if 'gs' works)
-    bbline = readlinematching(
+    bbline = lib.readlinematching(
         '^%%%%HiResBoundingBox',
         io.popen('gs -sDEVICE=bbox -q -dBATCH -dNOPAUSE '..filename..'.pdf 2>&1', 'r')
     )
@@ -308,20 +237,24 @@
         x_1, y_1, x_2, y_2 = pbb() + x_1, pbb() + y_1, pbb() + x_1, pbb() + y_1
     else warn([[gs couldn't be launched; there could be rounding errors.]])
     end
-    local bb = {
-        ['protrusion'] = -convert_unit(("%fbp"):format(x_1)),
-        ['r_protrusion'] = convert_unit(("%fbp"):format(x_2)) - line_width,
-        ['width'] = convert_unit(("%fbp"):format(x_2)),
-        ['height'] = convert_unit(("%fbp"):format(y_2)) - convert_unit(("%fbp"):format(y_1))
-    }
-    obj.serialize(bb, filename..'.bbox')
-    return bb
+    local f = io.open(filename .. '.bbox', 'w')
+    f:write(
+        string.format("return %s, %s, %s, %s, %s", x_1, y_1, x_2, y_2, line_width)
+    )
+    f:close()
+    return bbox.calc(x_1, x_2, y_1, y_2, line_width)
 end
 
-function bbox.read(f) return obj.deserialize(f..'.bbox') end
+function bbox.read(f)
+    f = f .. '.bbox'
+    if lfs.isfile(f) then
+        local x_1, y_1, x_2, y_2, line_width = dofile(f)
+        return bbox.calc(x_1, x_2, y_1, y_2, line_width)
+    end
+end
 
 
---[[ =============== Functions that output LaTeX code ===================== ]]
+--[[ =============== Functions that output LaTeX code ===================== --]]
 
 function latex.filename(printfilename, insert, input_file)
     if printfilename and input_file then
@@ -347,8 +280,8 @@
 function latex.includeinline(pdfname, height, valign, hpadding, voffset)
     local v_base
     if valign == 'bottom' then v_base = 0
-    elseif valign == 'top' then v_base = convert_unit('1em') - height
-    else v_base = (convert_unit('1em') - height) / 2
+    elseif valign == 'top' then v_base = lib.convert_unit('1em') - height
+    else v_base = (lib.convert_unit('1em') - height) / 2
     end
     tex.sprint(
         string.format(
@@ -407,8 +340,8 @@
             '.*%%%s*begin verbatim', ''):gsub(
             '%%%s*end verbatim.*', '')
         --[[ We unfortunately need an external file,
-             as verbatim environments are quite special. ]]
-        local fname = ly.get_option('tmpdir')..'/verb.tex'
+             as verbatim environments are quite special. --]]
+        local fname = ly_opts.tmpdir..'/verb.tex'
         local f = io.open(fname, 'w')
         f:write(
             ly.verbenv[1]..'\n'..
@@ -422,36 +355,12 @@
 end
 
 
---[[ =============== Functions that operate on objects ==================== ]]
+--[[ =============================== Classes =============================== --]]
 
-function obj.deserialize(f) if lfs.isfile(f) then return dofile(f) end end
-
-function obj.serialize(o, f)
-    f = io.open(f, 'w')
-    if not f then return end
-    f:write('local o = '..obj.str(o)..'\nreturn o')
-    f:close()
-end
-
-function obj.str(o)
-    if type(o) == "number" then return o
-    elseif type(o) == "string" then return string.format("%q", o)
-    elseif type(o) == "table" then
-        local r = '{\n'
-        for k, v in pairs(o) do r = r.."  ["..obj.str(k).."] = "..obj.str(v)..",\n" end
-        return r.."}\n"
-    else error("cannot convert " .. type(o).." to string")
-    end
-end
-
-
---[[ =============================== Classes =============================== ]]
-
 -- Score class
 function Score:new(ly_code, options, input_file)
     local o = options or {}
     setmetatable(o, self)
-    self.__index = self
     o.output_names = {}
     o.input_file = input_file
     o.ly_code = ly_code
@@ -475,6 +384,10 @@
 
 function Score:calc_properties()
     self:calc_staff_properties()
+    -- add includes to lilypond code
+    self.ly_code = includes_parse(self.include_before_body)
+        .. self.ly_code
+        .. includes_parse(self.include_after_body)
     -- fragment and relative
     if self.relative and not self.fragment then
         -- local option takes precedence over global option
@@ -506,7 +419,7 @@
     end
     -- dimensions that can be given by LaTeX
     for _, dimension in pairs(DIM_OPTIONS) do
-        self[dimension] = convert_unit(self[dimension])
+        self[dimension] = lib.convert_unit(self[dimension])
     end
     self['max-left-protrusion'] = self['max-left-protrusion'] or self['max-protrusion']
     self['max-right-protrusion'] = self['max-right-protrusion'] or self['max-protrusion']
@@ -533,21 +446,30 @@
 
 function Score:calc_range()
     local nsystems = self:count_systems(true)
-    if self['print-only'] == '' then
-        if nsystems == 0 then self['print-only'] = '1-'
-        else self['print-only'] = '1-'..nsystems
+    local printonly, donotprint = self['print-only'], self['do-not-print']
+    if printonly == '' then printonly = '1-' end
+    local result = tonumber(printonly) and {tonumber(printonly)} or {}
+    if not result[1] then
+        for _, r in pairs(printonly:explode(',')) do
+            local range = range_parse(r:gsub('^%s', ''):gsub('%s$', ''), nsystems)
+            if range then
+                for _, v in pairs(range) do table.insert(result, v) end
+            end
         end
     end
-    local result = {}
-    if tonumber(self['print-only']) then result = {self['print-only']}
-    else
-        for _, r in pairs(self['print-only']:explode(',')) do
+    local rm_result = tonumber(donotprint) and {tonumber(donotprint)} or {}
+    if not rm_result[1] then
+        for _, r in pairs(donotprint:explode(',')) do
             local range = range_parse(r:gsub('^%s', ''):gsub('%s$', ''), nsystems)
             if range then
-                for _, v in pairs(range) do table.insert(result, v) end
+                for _, v in pairs(range) do table.insert(rm_result, v) end
             end
         end
     end
+    for _, v in pairs(rm_result) do
+        local k = lib.contains(result, v)
+        if k then table.remove(result, k) end
+    end
     return result
 end
 
@@ -651,9 +573,9 @@
         if lp.shorten > 0 then
             if not self.indent or self.indent == 0 then
                 self.indent = lp.overflow_left
-                lp.shorten = max(lp.shorten - lp.overflow_left, 0)
+                lp.shorten = lib.max(lp.shorten - lp.overflow_left, 0)
             else
-                self.indent = max(self.indent - lp.overflow_left, 0)
+                self.indent = lib.max(self.indent - lp.overflow_left, 0)
             end
             lp.changed_indent = true
         end
@@ -700,26 +622,7 @@
 end
 
 function Score:check_properties()
-    local unexpected = false
-    for k, _ in orderedpairs(OPTIONS) do
-        if self[k] == 'default' then
-            self[k] = OPTIONS[k][1] or nil
-            unexpected = not self[k]
-        end
-        if not contains(OPTIONS[k], self[k]) and OPTIONS[k][2] then
-            if type(OPTIONS[k][2]) == 'function' then OPTIONS[k][2](k, self[k])
-            else unexpected = true
-            end
-        end
-        if unexpected then
-            err([[
-Unexpected value "%s" for option %s:
-authorized values are "%s"
-]],
-                self[k], k, table.concat(OPTIONS[k], ', ')
-            )
-        end
-    end
+    ly_opts:validate_options(self)
     for _, k in pairs(TEXINFO_OPTIONS) do
         if self[k] then info([[Option %s is specific to Texinfo: ignoring it.]], k) end
     end
@@ -750,10 +653,10 @@
     -- line_props lp
     local lp = {}
     -- Determine offset due to left protrusion
-    lp.overflow_left = max(bb.protrusion - math.floor(self['max-left-protrusion']), 0)
+    lp.overflow_left = lib.max(bb.protrusion - math.floor(self['max-left-protrusion']), 0)
     self.protrusion_left = lp.overflow_left - bb.protrusion
     -- Determine further line properties
-    lp.stave_extent = lp.overflow_left + min(self['line-width'], bb.width)
+    lp.stave_extent = lp.overflow_left + lib.min(self['line-width'], bb.width)
     lp.available = self.original_lw + self['max-right-protrusion']
     lp.total_extent = lp.stave_extent + bb.r_protrusion
     -- Check if stafflines protrude into the right margin after offsetting
@@ -760,10 +663,10 @@
     -- Note: we can't *reliably* determine this with ragged one-system scores,
     -- possibly resulting in unnecessarily short lines when right protrusion is
     -- present
-    lp.stave_overflow_right = max(lp.stave_extent - self.original_lw, 0)
+    lp.stave_overflow_right = lib.max(lp.stave_extent - self.original_lw, 0)
     -- Check if image as a whole protrudes over max-right-protrusion
-    lp.overflow_right = max(lp.total_extent - lp.available, 0)
-    lp.shorten = max(lp.stave_overflow_right, lp.overflow_right)
+    lp.overflow_right = lib.max(lp.total_extent - lp.available, 0)
+    lp.shorten = lib.max(lp.stave_overflow_right, lp.overflow_right)
     lp.changed_indent = false
     self:check_indent(lp, bb)
     if lp.shorten > 0 or lp.changed_indent then
@@ -789,6 +692,7 @@
 
 function Score:content()
     local n = ''
+    local ly_code = self.ly_code
     if self.relative then
         self.fragment = 'true'  -- in case it would serve later
         if self.relative < 0 then
@@ -796,9 +700,9 @@
         elseif self.relative > 0 then
             for _ = 1, self.relative do n = n.."'" end
         end
-        return string.format([[\relative c%s {%s}]], n, self.ly_code)
-    elseif self.fragment then return [[{]]..self.ly_code..[[}]]
-    else return self.ly_code
+        return string.format([[\relative c%s {%s}]], n, ly_code)
+    elseif self.fragment then return [[{]]..ly_code..[[}]]
+    else return ly_code
     end
 end
 
@@ -832,9 +736,13 @@
         including referenced files (if they can be opened.
         Other files (from LilyPond's include path) are considered
         irrelevant for the purpose of a hashsum.) --]]
+
+    -- Replace percent signs with another character that doesn't
+    -- meddle with Lua's gsub escape character.
+    ly_code = ly_code:gsub('%%', '#')
     local f
-    local includepaths = self.includepaths
-    if self.input_file then includepaths = self.includepaths..','..dirname(self.input_file) end
+    local includepaths = self.includepaths..','..self.tmpdir
+    if self.input_file then includepaths = self.includepaths..','..lib.dirname(self.input_file) end
     for iline in ly_code:gmatch('\\include%s*"[^"]*"') do
         f = io.open(locate(iline:match('\\include%s*"([^"]*)"'), includepaths, '.ly') or '')
         if f then
@@ -845,46 +753,75 @@
     return ly_code
 end
 
+function Score:footer()
+    return includes_parse(self.include_footer)
+end
+
 function Score:header()
     local header = LY_HEAD
     for element in LY_HEAD:gmatch('<<<(%w+)>>>') do
         header = header:gsub('<<<'..element..'>>>', self['ly_'..element](self) or '')
     end
+    local wh_dest = self['write-headers']
+    if wh_dest then
+        if self.input_file then
+            local _, ext = lib.splitext(wh_dest)
+            local header_file = ext and wh_dest
+                or wh_dest..'/'..lib.splitext(lib.basename(self.input_file), 'ly').."-lyluatex-headers.ily"
+            lib.mkdirs(lib.dirname(header_file))
+            local f = io.open(header_file, 'w')
+            f:write(header
+                :gsub([[%\include "lilypond%-book%-preamble.ly"]], '')
+                :gsub([[%#%(define inside%-lyluatex %#t%)]], '')
+                :gsub('\n+', '\n')
+            )
+            f:close()
+        else
+            warn([[Ignoring 'write-headers' for non-file score.]])
+        end
+    end
     return header
 end
 
 function Score:is_compiled()
+    if self['force-compilation'] then return false end
     return lfs.isfile(self.output..'.pdf') or self:count_systems(true) ~= 0
 end
 
 function Score:is_odd_page() return tex.count['c at page'] % 2 == 1 end
 
-function Score:lilypond_cmd(ly_code)
+function Score:lilypond_cmd()
     local input, mode = '-s -', 'w'
     if self.debug then
         local f = io.open(self.output..'.ly', 'w')
-        f:write(ly_code)
+        f:write(self.complete_ly_code)
         f:close()
         input = self.output..".ly 2>&1"
         mode = 'r'
     end
-    local cmd = self.program.." "..
+    local cmd = '"'..self.program..'" '..
         "-dno-point-and-click "..
         "-djob-count=2 "..
         "-dno-delete-intermediate-files "
+    if self['optimize-pdf'] and self:lilypond_has_TeXGS() then cmd = cmd.."-O TeX-GS " end
     if self.input_file then
-        cmd = cmd..'-I "'..dirname(self.input_file):gsub('%./', lfs.currentdir()..'/')..'" '
+        cmd = cmd..'-I "'..lib.dirname(self.input_file):gsub('^%./', lfs.currentdir()..'/')..'" '
     end
     for _, dir in ipairs(extract_includepaths(self.includepaths)) do
         cmd = cmd..'-I "'..dir:gsub('^%./', lfs.currentdir()..'/')..'" '
     end
     cmd = cmd..'-o "'..self.output..'" '..input
+    if lib.tex_engine.dist == 'MiKTeX' then cmd = '"'..cmd..'"' end
     debug("Command:\n"..cmd)
     return cmd, mode
 end
 
+function Score:lilypond_has_TeXGS()
+    return lib.readlinematching('TeX%-GS', io.popen('"'..self.program..'" --help', 'r'))
+end
+
 function Score:lilypond_version(number)
-    local result = readlinematching('GNU LilyPond', io.popen(self.program..' --version', 'r'))
+    local result = lib.readlinematching('GNU LilyPond', io.popen('"'..self.program..'" --version', 'r'))
     if result then
         if number then return result:match('%d+%.%d+%.?%d*')
         else
@@ -893,18 +830,22 @@
                 self.output, self.program
             )
             debug(result)
+            return true
         end
-    else
-        err([[
-LilyPond could not be started.
-Please check that LuaLaTeX is started with the
---shell-escape option, and that 'program'
-points to a valid LilyPond executable.
-]]
-        )
     end
 end
 
+function Score:ly_fixbadlycroppedstaffgroupbrackets()
+    return self.fix_badly_cropped_staffgroup_brackets and [[\context {
+        \Score
+        \override SystemStartBracket.after-line-breaking =
+        #(lambda (grob)
+            (let ((Y-off (ly:grob-property grob 'Y-extent)))
+                (ly:grob-set-property! grob 'Y-extent
+                  (cons (- (car Y-off) 1.7) (+ (cdr Y-off) 1.7)))))
+    }]]
+end
+
 function Score:ly_fonts()
     if self['pass-fonts'] then
         return string.format([[
@@ -921,6 +862,10 @@
     end
 end
 
+function Score:ly_header()
+    return includes_parse(self.include_header)
+end
+
 function Score:ly_indent()
     if not (self.indent == false and self.insert == 'fullpage') then
         return [[indent = ]]..(self.indent or 0)..[[\pt]]
@@ -936,43 +881,45 @@
 function Score:ly_staffsize() return self.staffsize end
 
 function Score:ly_margins()
+    local horizontal_margins =
+        self.twoside and string.format([[
+            inner-margin = %s\pt]], self:tex_margin_inner())
+        or string.format([[
+            left-margin = %s\pt]], self:tex_margin_left())
+
     local tex_top = self['extra-top-margin'] + self:tex_margin_top()
     local tex_bottom = self['extra-bottom-margin'] + self:tex_margin_bottom()
     if self.fullpagealign == 'crop' then
         return string.format([[
-top-margin = %s\pt
-bottom-margin = %s\pt
-inner-margin = %s\pt
-left-margin = %s\pt
-]],
-            tex_top, tex_bottom, self:tex_margin_inner(), self:tex_margin_left()
+    top-margin = %s\pt
+    bottom-margin = %s\pt
+    %s]],
+            tex_top, tex_bottom, horizontal_margins
         )
     elseif self.fullpagealign == 'staffline' then
         local top_distance = 4 * tex_top / self.staffsize + 2
         local bottom_distance = 4 * tex_bottom / self.staffsize + 2
         return string.format([[
-top-margin = 0\pt
-bottom-margin = 0\pt
-inner-margin = %s\pt
-left-margin = %s\pt
-top-system-spacing =
+    top-margin = 0\pt
+    bottom-margin = 0\pt
+    %s
+    top-system-spacing =
     #'((basic-distance . %s)
         (minimum-distance . %s)
         (padding . 0)
         (stretchability . 0))
-top-markup-spacing =
+    top-markup-spacing =
     #'((basic-distance . %s)
         (minimum-distance . %s)
         (padding . 0)
         (stretchability . 0))
-last-bottom-spacing =
+    last-bottom-spacing =
     #'((basic-distance . %s)
         (minimum-distance . %s)
         (padding . 0)
         (stretchability . 0))
 ]],
-            self:tex_margin_inner(),
-            self:tex_margin_left(),
+            horizontal_margins,
             top_distance,
             top_distance,
             top_distance,
@@ -992,19 +939,33 @@
 end
 
 function Score:ly_paper()
+    local system_count =
+        self['system-count'] == 0 and ''
+        or 'system-count = '..self['system-count']..'\n    '
+
     local papersize = '#(set-paper-size "'..(self.papersize or 'lyluatexfmt')..'")'
     if self.insert == 'fullpage' then
-        local ppn = 'f'
-        if self['print-page-number'] then ppn = 't' end
+        local first_page_number = self['first-page-number'] or tex.count['c at page']
+        local pfpn = self['print-first-page-number'] and 't' or 'f'
+        local ppn = self['print-page-number'] and 't' or 'f'
         return string.format([[
-%s
-print-page-number = ##%s
-print-first-page-number = ##t
-first-page-number = %s
+%s%s
+    print-page-number = ##%s
+    print-first-page-number = ##%s
+    first-page-number = %s
 %s]],
-            papersize, ppn, tex.count['c at page'], self:ly_margins()
+          system_count, papersize, ppn, pfpn,
+          first_page_number, self:ly_margins()
 	    )
-    elseif self.papersize then return papersize
+    else
+        if self.papersize then
+            papersize = papersize..[[
+]]
+        else
+            papersize = ''
+        end
+
+        return string.format([[%s%s]], papersize, system_count)
     end
 end
 
@@ -1019,7 +980,7 @@
 end
 
 function Score:ly_raggedright()
-    if not self['ragged-right'] == 'default' then
+    if self['ragged-right'] ~= 'default' then
         if self['ragged-right'] then return 'ragged-right = ##t'
         else return 'ragged-right = ##f'
         end
@@ -1041,6 +1002,20 @@
 
 function Score:optimize_pdf()
     if not self['optimize-pdf'] then return end
+    if self:lilypond_has_TeXGS() and not ly.final_optimization_message then
+        ly.final_optimization_message = true
+        luatexbase.add_to_callback(
+            'stop_run',
+            function()
+                info(
+                    [[Optimization enabled: remember to run
+                    'gs -q -dBATCH -dNOPAUSE -sDEVICE=pdfwrite -sOutputFile=%s %s'.]],
+                    tex.jobname..'-final.pdf', tex.jobname..'.pdf'
+                )
+            end,
+            'lyluatex optimize-pdf'
+        )
+    end
     local pdf2ps, ps2pdf, path
     for file in lfs.dir(self.tmpdir) do
         path = self.tmpdir..'/'..file
@@ -1069,8 +1044,8 @@
 
 function Score:output_filename()
     local properties = ''
-    for k, _ in orderedpairs(OPTIONS) do
-        if (not contains(HASHIGNORE, k)) and self[k] and type(self[k]) ~= 'function' then
+    for k, _ in lib.orderedpairs(ly_opts.declarations) do
+        if (not lib.contains(HASHIGNORE, k)) and self[k] and type(self[k]) ~= 'function' then
             properties = properties..'\n'..k..'\t'..self[k]
         end
     end
@@ -1086,12 +1061,34 @@
 function Score:process()
     self:check_properties()
     self:calc_properties()
-    -- with bbox.read check_ptrotrusion will only execute with
+    if not self:lilypond_version() then
+        local warning = [[
+LilyPond could not be started.
+Please check that LuaLaTeX is started with the
+--shell-escape option, and that 'program'
+points to a valid LilyPond executable.
+]]
+        if self.showfailed then
+            warn(warning)
+            tex.sprint(string.format([[
+\begin{quote}
+\minibox[frame]{LilyPond could not be started.}
+\end{quote}
+
+]]))
+            return
+        else
+            err(warning)
+        end
+    end
+    -- with bbox.read check_protrusion will only execute with
     -- a prior compilation, otherwise it will be ignored
     local do_compile = not self:check_protrusion(bbox.read)
-    if do_compile then
+    if self['force-compilation'] or do_compile then
         repeat
-            self:run_lilypond(self:header()..self:content())
+            self.complete_ly_code = self:header()..self:content()..self:footer()
+            self:run_lilypond()
+            self['force-compilation'] = false
             if self:is_compiled() then table.insert(self.output_names, self.output)
             else
                 self:clean_failed_compilation()
@@ -1102,36 +1099,47 @@
     else table.insert(self.output_names, self.output)
     end
     set_lyscore(self)
+    if self:count_systems() == 0 then
+        warn([[
+The score doesn't contain any music:
+this will probably cause bad output.]]
+        )
+    end
     if not self['raw-pdf'] then self:write_latex(do_compile) end
     self:write_to_filelist()
     if not self.debug then self:delete_intermediate_files() end
 end
 
-function Score:run_lilypond(ly_code)
+function Score:run_lily_proc(p)
+        if self.debug then
+          local f = io.open(self.output..".log", 'w')
+          f:write(p:read('*a'))
+          f:close()
+        else p:write(self.complete_ly_code)
+        end
+        return p:close()
+    end
+
+function Score:run_lilypond()
     if self:is_compiled() then return end
-    mkdirs(dirname(self.output))
-    self:lilypond_version()
-    local p = io.popen(self:lilypond_cmd(ly_code))
-    if self.debug then
-        local f = io.open(self.output..".log", 'w')
-        f:write(p:read('*a'))
-        f:close()
-    else p:write(ly_code)
+    lib.mkdirs(lib.dirname(self.output))
+    if not self:run_lily_proc(io.popen(self:lilypond_cmd(self.complete_ly_code))) and not self.debug then
+        self.debug = true
+        self.lilypond_error = not self:run_lily_proc(io.popen(self:lilypond_cmd(self.complete_ly_code)))
     end
-    self.lilypond_error = not p:close()
 end
 
 function Score:tex_margin_bottom()
     self._tex_margin_bottom = self._tex_margin_bottom or
-        convert_unit(tex.dimen.paperheight..'sp')
+        lib.convert_unit(tex.dimen.paperheight..'sp')
         - self:tex_margin_top()
-        - convert_unit(tex.dimen.textheight..'sp')
+        - lib.convert_unit(tex.dimen.textheight..'sp')
     return self._tex_margin_bottom
 end
 
 function Score:tex_margin_inner()
     self._tex_margin_inner = self._tex_margin_inner or
-        convert_unit((
+        lib.convert_unit((
           tex.sp('1in') + tex.dimen.oddsidemargin + tex.dimen.hoffset
         )..'sp')
     return self._tex_margin_inner
@@ -1139,19 +1147,19 @@
 
 function Score:tex_margin_outer()
     self._tex_margin_outer = self._tex_margin_outer or
-        convert_unit((tex.dimen.paperwidth - tex.dimen.textwidth)..'sp')
+        lib.convert_unit((tex.dimen.paperwidth - tex.dimen.textwidth)..'sp')
         - self:tex_margin_inner()
     return self._tex_margin_outer
 end
 
 function Score:tex_margin_left()
-    if self:is_odd_page() then return self:tex_margin_inner()
+    if self:is_odd_page() or not self.twopage then return self:tex_margin_inner()
     else return self:tex_margin_outer()
     end
 end
 
 function Score:tex_margin_right()
-    if self:is_odd_page() then return self:tex_margin_outer()
+    if self:is_odd_page() or not self.twopage then return self:tex_margin_outer()
     else return self:tex_margin_inner()
     end
 end
@@ -1158,7 +1166,7 @@
 
 function Score:tex_margin_top()
     self._tex_margin_top = self._tex_margin_top or
-        convert_unit((
+        lib.convert_unit((
             tex.sp('1in') + tex.dimen.voffset + tex.dimen.topmargin
             + tex.dimen.headheight + tex.dimen.headsep
         )..'sp')
@@ -1186,9 +1194,12 @@
 This will probably cause bad output.]]
             )
         end
-        latex.includeinline(
-            self.output, self:bbox(1).height, self.valign, self.hpadding, self.voffset
-        )
+        local bb = self:bbox(1)
+        if bb then
+            latex.includeinline(
+                self.output, bb.height, self.valign, self.hpadding, self.voffset
+            )
+        end
     end
 end
 
@@ -1202,9 +1213,9 @@
 end
 
 
---[[ ========================== Public functions ========================== ]]
+--[[ ========================== Public functions ========================== --]]
 
-ly.score_content = {}
+
 function ly.buffenv_begin()
 
     function ly.buffenv(line)
@@ -1259,47 +1270,18 @@
 end
 
 
-function ly.declare_package_options(options)
-    OPTIONS = options
-    local exopt = ''
-    for k, v in pairs(options) do
-        tex.sprint(string.format([[
-\DeclareOptionX{%s}{\directlua{
-  ly.set_property('%s', '\luatexluaescapestring{#1}')
-}}%%
-]],
-            k, k
-        ))
-        exopt = exopt..k..'='..(v[1] or '')..','
-    end
-    tex.sprint([[\ExecuteOptionsX{]]..exopt..[[}%%]], [[\ProcessOptionsX]])
-    mkdirs(options.tmpdir[1])
-    FILELIST = options.tmpdir[1]..'/'..splitext(status.log_name, 'log')..'.list'
+function ly.make_list_file()
+    local tmpdir = ly_opts.tmpdir
+    lib.mkdirs(tmpdir)
+    FILELIST = tmpdir..'/'..lib.splitext(status.log_name, 'log')..'.list'
     os.remove(FILELIST)
 end
 
-
-function ly.env_begin(opts)
-    ly.state = 'env'
-    ly.env_no_args = opts == 'noarg'
-    if ly.env_no_args then tex.sprint(40, [[\ly at compilely]])
-    else tex.sprint(40, [[\ly at bufferenv]])
-    end
-end
-
-
-function ly.env_end()
-    if ly.env_no_args then tex.sprint(40, [[\endly at compilely]])
-    else tex.sprint(40, [[\endly at bufferenv]])
-    end
-end
-
-
 function ly.file(input_file, options)
     --[[ Here, we only take in account global option includepaths,
-    as it really doesn't mean anything as a local option. ]]
+    as it really doesn't mean anything as a local option. --]]
     local file = locate(input_file, Score.includepaths, '.ly')
-    options = ly.set_local_options(options)
+    options = ly_opts:check_local_options(options)
     if not file then err("File %s doesn't exist.", input_file) end
     local i = io.open(file, 'r')
     ly.score = Score:new(i:read('*a'), options, file)
@@ -1309,9 +1291,9 @@
 
 function ly.file_musicxml(input_file, options)
     --[[ Here, we only take in account global option includepaths,
-    as it really doesn't mean anything as a local option. ]]
+    as it really doesn't mean anything as a local option. --]]
     local file = locate(input_file, Score.includepaths, '.xml')
-    options = ly.set_local_options(options)
+    options = ly_opts:check_local_options(options)
     if not file then err("File %s doesn't exist.", input_file) end
     local xmlopts = ''
     for _, opt in pairs(MXML_OPTIONS) do
@@ -1321,16 +1303,17 @@
                     xmlopts = xmlopts..' '..options[opt]
                 end
             end
-        elseif ly.get_option(opt) then xmlopts = xmlopts..' --'..opt
+        elseif ly_opts[opt] then xmlopts = xmlopts..' --'..opt
         end
     end
-    local i = io.popen(ly.get_option('xml2ly')..' --out=-'..xmlopts..' "'..file..'"', 'r')
+    local i = io.popen(ly_opts.xml2ly..' --out=-'..xmlopts..' "'..file..'"', 'r')
     if not i then
         err([[
-LilyPond could not be started.
+%s could not be started.
 Please check that LuaLaTeX is started with the
 --shell-escape option.
-]]
+]],
+            ly_opts.xml2ly
         )
     end
     ly.score = Score:new(i:read('*a'), options, file)
@@ -1339,7 +1322,7 @@
 
 
 function ly.fragment(ly_code, options)
-    options = ly.set_local_options(options)
+    options = ly_opts:check_local_options(options)
     if type(ly_code) == 'string' then
         ly_code = ly_code:gsub('\\par ', '\n'):gsub('\\([^%s]*) %-([^%s])', '\\%1-%2')
     else ly_code = table.concat(ly_code, '\n')
@@ -1349,43 +1332,10 @@
 
 
 function ly.get_font_family(font_id)
-    return fontinfo(font_id).shared.rawdata.metadata['familyname']
+    return lib.fontinfo(font_id).shared.rawdata.metadata['familyname']
 end
 
 
-function ly.get_option(opt) return Score[opt] end
-
-
-function ly.is_alias() end
-
-
-function ly.is_dim(k, v)
-    if v == '' or v == false or tonumber(v) then return true end
-    local n, sl, u = v:match('^%d*%.?%d*'), v:match('\\'), v:match('%a+')
-    -- a value of number - backslash - length is a dimension
-    -- invalid input will be prevented in by the LaTeX parser already
-    if n and sl and u then return true end
-    if n and contains(TEX_UNITS, u) then return true end
-    err([[
-Unexpected value "%s" for dimension %s:
-should be either a number (for example "12"),
-a number with unit, without space ("12pt"),
-or a (multiplied) TeX length (".8\linewidth")
-]],
-        v, k
-    )
-end
-
-
-function ly.is_neg(k, _)
-    local _, i = k:find('^no')
-    return i and contains_key(OPTIONS, k:sub(i + 1))
-end
-
-
-function ly.is_num(_, v) return v == '' or tonumber(v) end
-
-
 function ly.newpage_if_fullpage()
     if ly.score.insert == 'fullpage' then tex.sprint([[\newpage]]) end
 end
@@ -1408,32 +1358,12 @@
   if ly.score.ttfamily == '' then ly.score.ttfamily = ly.get_font_family(tt) end
 end
 
-function ly.set_local_options(opts)
-    local options = {}
-    local next_opt = opts:gmatch('([^,]+)')  -- iterator over options
-    for opt in next_opt do
-        local k, v = opt:match('([^=]+)=?(.*)')
-        if k then
-            if v and v:sub(1, 1) == '{' then  -- handle keys with {multiple, values}
-                while v:sub(-1) ~= '}' do v = v..','..next_opt() end
-                v = v:sub(2, -2)  -- remove { }
-            end
-            k, v = process_options(k:gsub('^%s', ''), v:gsub('^%s', ''))
-            if k then
-                if options[k] then err('Option %s is set two times for the same score.', k)
-                else options[k] = v
-                end
-            end
-        end
-    end
-    return options
-end
 
-
-function ly.set_property(k, v)
-    k, v = process_options(k, v)
-    if k then Score[k] = v end
+function ly.write_to_file(file, content)
+    local f = io.open(Score.tmpdir..'/'..file, 'w')
+    if not f then err('Unable to write to file %s', file) end
+    f:write(content)
+    f:close()
 end
 
-
 return ly

Modified: trunk/Master/texmf-dist/tex/luatex/lyluatex/lyluatex.sty
===================================================================
--- trunk/Master/texmf-dist/tex/luatex/lyluatex/lyluatex.sty	2019-05-28 21:25:28 UTC (rev 51251)
+++ trunk/Master/texmf-dist/tex/luatex/lyluatex/lyluatex.sty	2019-05-28 21:25:42 UTC (rev 51252)
@@ -1,11 +1,11 @@
 %Lyluatex LaTeX style.
 %
-% Copyright (C) 2015-2018 jperon and others (see CONTRIBUTORS.md)
+% Copyright (C) 2015-2019 jperon and others (see CONTRIBUTORS.md)
 % License: MIT
 % This file is part of lyluatex.
 
 \NeedsTeXFormat{LaTeX2e}%
-\ProvidesPackage{lyluatex}[2018/03/12 v1.0b]  %%LYLUATEX_DATE LYLUATEX_VERSION
+\ProvidesPackage{lyluatex}[2019/05/27 v1.0f]  %%LYLUATEX_DATE LYLUATEX_VERSION
 
 % Dependencies
 \RequirePackage{luatexbase}
@@ -16,6 +16,7 @@
 \RequirePackage{environ}
 \RequirePackage{currfile}
 \RequirePackage{pdfpages}
+\IfFileExists{varwidth.sty}{\RequirePackage{varwidth}}{}
 
 \RequirePackage{metalogo}
 \newcommand{\lyluatex}{\textit{ly}\LuaTeX}
@@ -23,37 +24,62 @@
 \edef\ly at false{false}\def\ly at istwosided{\if at twoside\else\ly at false\fi}
 \savecatcodetable 40
 
+% Copied from ifnextok.sty.
+% We use \providecommand instead of \newcommand and \def in order
+% to avoid overriding ifnextok if it is already loaded.
+\providecommand{\IfNextToken}[3]{%
+  \let\nextok at match= #1%
+  \def\nextok at if{#2}\def\nextok at else{#3}%
+  \futurelet\@let at token\nextok at decide%
+}
+\providecommand\nextok at decide{%
+  \ifx\@let at token\nextok at match
+    \expandafter\nextok at if
+  \else
+    \expandafter\nextok at else
+  \fi%
+}
+
 % Options
 \catcode`-=11
 \directlua{
-  ly = require(kpse.find_file("lyluatex.lua") or "lyluatex.lua")
-  ly.declare_package_options({
+  local _opt = require(
+    kpse.find_file("lyluatex-options.lua") or "lyluatex-options.lua"
+  )
+  ly_opts = _opt.Opts:new('ly_opts', {
     ['addversion'] = {'false', 'true', ''},
     ['autoindent'] = {'true', 'false', ''},
     ['cleantmp'] = {'false', 'true', ''},
     ['currfiledir'] = {},
     ['debug'] = {'false', 'true', ''},
-    ['extra-bottom-margin'] = {'0', ly.is_dim},
-    ['extra-top-margin'] = {'0', ly.is_dim},
+    ['extra-bottom-margin'] = {'0', _opt.is_dim},
+    ['extra-top-margin'] = {'0', _opt.is_dim},
+    ['fix_badly_cropped_staffgroup_brackets'] = {'false', 'true', ''},
+      ['nofix_badly_cropped_staffgroup_brackets'] = {'default', _opt.is_neg},
+    ['force-compilation'] = {'false', 'true', ''},
     ['fragment'] = {'', 'false', 'true'},
-        ['nofragment'] = {'default', ly.is_neg},
+        ['nofragment'] = {'default', _opt.is_neg},
     ['fullpagealign'] = {'crop', 'staffline'},
     ['fullpagestyle'] = {''},
-    ['gutter'] = {'.4in', ly.is_dim},
-        ['exampleindent'] = {'gutter', ly.is_alias},
-        ['leftgutter'] = {'', ly.is_dim}, ['rightgutter'] = {'', ly.is_dim},
-    ['hpadding'] = {'0.75ex', ly.is_dim},
+    ['gutter'] = {'.4in', _opt.is_dim},
+        ['exampleindent'] = {'gutter', _opt.is_alias},
+        ['leftgutter'] = {'', _opt.is_dim}, ['rightgutter'] = {'', _opt.is_dim},
+    ['hpadding'] = {'0.75ex', _opt.is_dim},
+    ['include_after_body'] = {'false'},
+    ['include_before_body'] = {'false'},
+    ['include_footer'] = {'false'},
+    ['include_header'] = {'false'},
     ['includepaths'] = {'./'},
-    ['indent'] = {'', ly.is_dim},
-        ['noindent'] = {'default', ly.is_neg},
+    ['indent'] = {'', _opt.is_dim},
+        ['noindent'] = {'default', _opt.is_neg},
     ['insert'] = {'', 'systems', 'fullpage', 'inline', 'bare-inline'},
     ['intertext'] = {''},
     ['label'] = {'false'}, ['labelprefix'] = {'ly_'},
-    ['line-width'] = {[[\linewidth]], ly.is_dim},
+    ['line-width'] = {[[\linewidth]], _opt.is_dim},
     ['ly-version'] = {'2.18.2'},
-    ['max-protrusion'] = {[[\maxdimen]], ly.is_dim},
-        ['max-left-protrusion'] = {'', ly.is_dim},
-        ['max-right-protrusion'] = {'', ly.is_dim},
+    ['max-protrusion'] = {[[\maxdimen]], _opt.is_dim},
+        ['max-left-protrusion'] = {'', _opt.is_dim},
+        ['max-right-protrusion'] = {'', _opt.is_dim},
     ['noclef'] = {'false', 'true', ''},
     ['nostaff'] = {'false', 'true', ''},
     ['nostaffsymbol'] = {'false', 'true', ''},
@@ -61,32 +87,37 @@
     ['notiming'] = {'false', 'true', ''},
     ['notimesig'] = {'false', 'true', ''},
     ['optimize-pdf'] = {'false', 'true', ''},
-    ['paperwidth'] = {[[\paperwidth]], ly.is_dim},
-    ['paperheight'] = {[[\paperheight]], ly.is_dim},
+    ['paperwidth'] = {[[\paperwidth]], _opt.is_dim},
+    ['paperheight'] = {[[\paperheight]], _opt.is_dim},
     ['papersize'] = {'false'},
     ['pass-fonts'] = {'false', 'true', ''},
         ['current-font'] = {}, ['current-font-as-main'] = {'false', 'true', ''},
         ['rmfamily'] = {}, ['sffamily'] = {}, ['ttfamily'] = {},
     ['print-page-number'] = {'false', 'true', ''},
+        ['first-page-number'] = {'false', ''},
+        ['print-first-page-number'] = {'true', 'false', ''},
     ['print-only'] = {''},
+        ['do-not-print'] = {''},
     ['printfilename'] = {'false', 'true', ''},
     ['program'] = {'lilypond'},
-    ['protrusion'] = {'', ly.is_dim},
-        ['noprotrusion'] = {'default', ly.is_neg},
+    ['protrusion'] = {'', _opt.is_dim},
+        ['noprotrusion'] = {'default', _opt.is_neg},
     ['raw-pdf'] = {'false', 'true', ''},
     ['quote'] = {'false', 'true', ''},
     ['ragged-right'] = {'default', 'true', 'false', ''},
-        ['noragged-right'] = {'default', ly.is_neg},
-    ['relative'] = {'false', ly.is_num},
-        ['norelative'] = {'default', ly.is_neg},
+        ['noragged-right'] = {'default', _opt.is_neg},
+    ['relative'] = {'false', _opt.is_num},
+        ['norelative'] = {'default', _opt.is_neg},
     ['showfailed'] = {'false', 'true' ,''},
-    ['staffsize'] = {'0', ly.is_dim},
-        ['inline-staffsize'] = {'0', ly.is_dim},
+    ['staffsize'] = {'0', _opt.is_dim},
+        ['inline-staffsize'] = {'0', _opt.is_dim},
+    ['system-count'] = {'0', _opt.is_dim},
     ['tmpdir'] = {'tmp-ly'},
     ['twoside'] = {'\ly at istwosided', 'false', 'true', ''},
     ['verbatim'] = {'false', 'true', ''},
-    ['voffset'] = {'0pt', ly.is_dim},
+    ['voffset'] = {'0pt', _opt.is_dim},
     ['valign'] = {'center', 'top', 'bottom'},
+    ['write-headers'] = {'false'},
     % MusicXML options
     ['absolute'] = {'false', 'true', ''},
     ['language'] = {'false'},
@@ -100,7 +131,9 @@
   })
 }
 \directlua{
-  if ly.get_option('cleantmp') then
+  ly = require(kpse.find_file("lyluatex.lua") or "lyluatex.lua")
+  ly.make_list_file()
+  if ly_opts.cleantmp then
     luatexbase.add_to_callback('stop_run', ly.clean_tmp_dir, 'lyluatex cleantmp')
     luatexbase.add_to_callback('stop_run', ly.conclusion_text, 'lyluatex conclusion')
   end
@@ -125,7 +158,7 @@
 }
 
 % Command to change options during the document
-\newcommand{\lysetoption}[2]{\directlua{ly.set_property([[#1]], [[#2]])}}
+\newcommand{\lysetoption}[2]{\directlua{ly_opts:set_option([[#1]], [[#2]])}}
 
 % How the filename of a score will look like (if printed)
 \newcommand{\lyFilename}[1]{\noindent #1\par\bigskip}
@@ -141,7 +174,7 @@
 % *current* font for optional use.
 \newcommand{\ly at currentfonts}{%
   \begingroup%
-    \directlua{ly.set_property('current-font', ly.get_font_family(font.current()))}%
+    \directlua{ly_opts:set_option('current-font', ly.get_font_family(font.current()))}%
     \rmfamily \edef\rmfamilyid{\fontid\font}%
     \sffamily \edef\sffamilyid{\fontid\font}%
     \ttfamily \edef\ttfamilyid{\fontid\font}%
@@ -156,8 +189,8 @@
 \newcommand*{\ly at compilescore}[1]{%
   \ly at setunits%
   \directlua{
-    ly.set_property('currfiledir', [[\currfiledir]])
-    ly.set_property('twoside', '\ly at istwosided')
+    ly_opts:set_option('currfiledir', [[\currfiledir]])
+    ly_opts:set_option('twoside', '\ly at istwosided')
     #1
     ly.newpage_if_fullpage()
   }%
@@ -187,10 +220,31 @@
 \newcommand\lyscorebegin{\directlua{ly.buffenv_begin()}}
 \newcommand\lyscoreend{\directlua{ly.buffenv_end()}}
 \newenvironment{ly at bufferenv}{%
+  \directlua{
+    ly.insert_inline = string.match([[\options]], 'insert.*inline')
+    if ly.insert_inline then
+      if ly.varwidth_available then
+        tex.print([[
+          \string\begin{varwidth}{\string\linewidth}
+        ]])
+      else
+        ly.insert_inline = false
+        ly.err(
+          [[You have required 'insert=inline' with lilypond environment,
+          but package 'varwidth' wasn't found; either install it, or disable
+          this option.]]
+        )
+      end
+    end
+  }
   \lyscorebegin%
 }{%
   \lyscoreend%
   \ly at compilescore{ly.fragment(ly.score_content, [[\options]])}%
+  \hspace{0pt}\\
+  \directlua{
+    if ly.insert_inline then tex.print([[\string\end{varwidth}]]) end
+  }%
 }
 
 \NewEnviron{ly at compilely}{%
@@ -208,15 +262,39 @@
   }}%
 }
 
+% Environments to record custom headers and footers to be included in fragments
+\newenvironment{lysavefrag}[1]{%
+  \edef\filename{#1}
+  \lyscorebegin%
+}{%
+  \lyscoreend%
+  \directlua{ly.write_to_file('\filename'..'.ly', table.concat(ly.score_content,'\string\n'))}%
+}
+
+% Commands to transform or define lilypond environments so that it isn't necessary to add empty [].
+\def\lyenv#1{%
+  \expandafter\let\csname ly at env@#1\expandafter\endcsname\csname #1\endcsname%
+  \expandafter\let\csname ly at env@end#1\expandafter\endcsname\csname end#1\endcsname%
+  \expandafter\def\csname #1\endcsname{\IfNextToken[{\csname ly at env@#1\endcsname}{\csname ly at env@#1\endcsname[]}}%
+}
+\long\def\lynewenvironment#1{\@ifnextchar[{\ly at newenv@a{#1}}{\ly at newenv@a{#1}[0]}}
+\long\def\ly at newenv@a#1[#2]{\@ifnextchar[{\ly at newenv@b{#1}{#2}}{\ly at newenv@b{#1}{#2}[]}}
+\long\def\ly at newenv@b#1#2[#3]#4#5{%
+  \newenvironment{#1}[#2][#3]{#4}{#5}
+  \lyenv{#1}
+}
+
 % Parametrized command and environment for included LilyPond fragment
-\newenvironment{ly}[1][noarg]{%
+\lynewenvironment{ly}[1][noarg]{%
   \edef\options{#1}%
-  \directlua{ly.env_begin([[#1]])}%
+  \directlua{ly.state = 'env'}%
+  \ly at bufferenv%
 }{%
-  \directlua{ly.env_end()}%
+  \endly at bufferenv%
 }
 
-\newcommand*{\lily}[2][]{%
+
+\newcommand{\lily}[2][]{%
   \edef\options{#1}%
   \let\ly at oldrepeat\repeat\def\repeat{}% Fix #51
   \directlua{ly.state = 'cmd'}%



More information about the tex-live-commits mailing list