summaryrefslogtreecommitdiff
path: root/macros/latex/contrib/hybrid-latex
diff options
context:
space:
mode:
authorNorbert Preining <norbert@preining.info>2019-09-02 13:46:59 +0900
committerNorbert Preining <norbert@preining.info>2019-09-02 13:46:59 +0900
commite0c6872cf40896c7be36b11dcc744620f10adf1d (patch)
tree60335e10d2f4354b0674ec22d7b53f0f8abee672 /macros/latex/contrib/hybrid-latex
Initial commit
Diffstat (limited to 'macros/latex/contrib/hybrid-latex')
-rw-r--r--macros/latex/contrib/hybrid-latex/LICENSE.txt21
-rw-r--r--macros/latex/contrib/hybrid-latex/README.md47
-rw-r--r--macros/latex/contrib/hybrid-latex/doc/hybrid.pdfbin0 -> 240299 bytes
-rw-r--r--macros/latex/contrib/hybrid-latex/doc/hybrid.pytex36
-rw-r--r--macros/latex/contrib/hybrid-latex/doc/hybrid.sty77
-rw-r--r--macros/latex/contrib/hybrid-latex/doc/hybrid.tex1089
-rw-r--r--macros/latex/contrib/hybrid-latex/examples/example-01.pdfbin0 -> 129381 bytes
-rw-r--r--macros/latex/contrib/hybrid-latex/examples/example-01.pytex123
-rw-r--r--macros/latex/contrib/hybrid-latex/examples/example-01.tex267
-rw-r--r--macros/latex/contrib/hybrid-latex/examples/example-02.pdfbin0 -> 98382 bytes
-rw-r--r--macros/latex/contrib/hybrid-latex/examples/example-02.pytex20
-rw-r--r--macros/latex/contrib/hybrid-latex/examples/example-02.tex53
-rw-r--r--macros/latex/contrib/hybrid-latex/examples/example-03.pdfbin0 -> 93824 bytes
-rw-r--r--macros/latex/contrib/hybrid-latex/examples/example-03.pytex18
-rw-r--r--macros/latex/contrib/hybrid-latex/examples/example-03.tex73
-rw-r--r--macros/latex/contrib/hybrid-latex/examples/example-04-fig.pdfbin0 -> 175485 bytes
-rw-r--r--macros/latex/contrib/hybrid-latex/examples/example-04.pdfbin0 -> 281409 bytes
-rw-r--r--macros/latex/contrib/hybrid-latex/examples/example-04.pytex1
-rw-r--r--macros/latex/contrib/hybrid-latex/examples/example-04.tex119
-rw-r--r--macros/latex/contrib/hybrid-latex/examples/example-05.pdfbin0 -> 83216 bytes
-rw-r--r--macros/latex/contrib/hybrid-latex/examples/example-05.pytex12
-rw-r--r--macros/latex/contrib/hybrid-latex/examples/example-05.tex51
-rw-r--r--macros/latex/contrib/hybrid-latex/examples/example-06.pdfbin0 -> 106438 bytes
-rw-r--r--macros/latex/contrib/hybrid-latex/examples/example-06.pytex9
-rw-r--r--macros/latex/contrib/hybrid-latex/examples/example-06.tex89
-rw-r--r--macros/latex/contrib/hybrid-latex/examples/example-07.pdfbin0 -> 57402 bytes
-rw-r--r--macros/latex/contrib/hybrid-latex/examples/example-07.pytex9
-rw-r--r--macros/latex/contrib/hybrid-latex/examples/example-07.tex45
-rw-r--r--macros/latex/contrib/hybrid-latex/examples/example-08.pdfbin0 -> 113548 bytes
-rw-r--r--macros/latex/contrib/hybrid-latex/examples/example-08.pytex78
-rw-r--r--macros/latex/contrib/hybrid-latex/examples/example-08.tex215
-rw-r--r--macros/latex/contrib/hybrid-latex/examples/example-09.pdfbin0 -> 240360 bytes
-rw-r--r--macros/latex/contrib/hybrid-latex/examples/example-09.pytex6
-rw-r--r--macros/latex/contrib/hybrid-latex/examples/example-09.tex142
-rw-r--r--macros/latex/contrib/hybrid-latex/examples/examples.sty31
-rw-r--r--macros/latex/contrib/hybrid-latex/examples/summary.pdfbin0 -> 288391 bytes
-rw-r--r--macros/latex/contrib/hybrid-latex/examples/summary.tex148
-rw-r--r--macros/latex/contrib/hybrid-latex/examples/version.txt17
-rw-r--r--macros/latex/contrib/hybrid-latex/latex/pylatex.cls17
-rw-r--r--macros/latex/contrib/hybrid-latex/latex/pylatex.sty127
-rw-r--r--macros/latex/contrib/hybrid-latex/latex/pymacros.sty24
-rwxr-xr-xmacros/latex/contrib/hybrid-latex/python/merge-tex.py115
-rwxr-xr-xmacros/latex/contrib/hybrid-latex/python/pypostproc.py239
-rwxr-xr-xmacros/latex/contrib/hybrid-latex/python/pypreproc.py414
-rwxr-xr-xmacros/latex/contrib/hybrid-latex/shell/pylatex.sh83
45 files changed, 3815 insertions, 0 deletions
diff --git a/macros/latex/contrib/hybrid-latex/LICENSE.txt b/macros/latex/contrib/hybrid-latex/LICENSE.txt
new file mode 100644
index 0000000000..219b952199
--- /dev/null
+++ b/macros/latex/contrib/hybrid-latex/LICENSE.txt
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2018 Leo Brewin
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/macros/latex/contrib/hybrid-latex/README.md b/macros/latex/contrib/hybrid-latex/README.md
new file mode 100644
index 0000000000..6a6181db8e
--- /dev/null
+++ b/macros/latex/contrib/hybrid-latex/README.md
@@ -0,0 +1,47 @@
+# Hybrid LaTeX - adding active Python code to LaTeX
+
+## Overview
+
+This is a collection of Python and Bash shell scripts that allows LaTeX documents to contain active Python code. The active code blocks are processed ahead of LaTeX and their results are accessible within the same LaTeX document. Thus a calculation that might otherwise be done using in two documents, one using Python to do the maths, and a separate LaTeX document to record the results can be consolidated into a single source.
+
+There are other collections that achieve the same result, see [PyhtonTeX][1] and [SageTeX][2] .
+
+The main differences between this package and PyhtonTeX and SageTeX are:
+
+ * The tools are written in Python and Bash rather than LaTeX, thus allowing easy extension to other languages,
+ * The results of the active code blocks are saved to a single file that can be easily shared with colleagues, journals or included in other LaTeX documents.
+
+This version of the package is specific to embedded Python code. Extensions for Mathematica, Maple, Matlab and Cadabra can be found on the [github repository][5].
+
+## Installation
+
+There are a handful of files to install (by hand) for each language. These include Bash shell scripts, Python scripts and LaTeX style files. Each file can be copied to wherever their respective program expects to find them. For example, the Bash and Python scripts could be sudo copied to `/usr/local/bin` (for access by all users) or to `~/bin` (for your personal access). The LaTeX style files can be copied to wherever they will be visible to LaTeX (see [this discussion][3] on tex.stackexchange for useful suggestions). If you place the files in non-standard locations you may need to adjust your `PATH` and `TEXINPUT` environment variables accordingly. If all else fails, you can copy all of the Bash, Python and LaTeX files into the same directory as your LaTeX document (though this is a very bad solution).
+
+More details on the installation can be found in the main documentation, `doc/hybrid.pdf`.
+
+## Documentation
+
+The main documentation can be found in `doc/hybrid.pdf`. See also the examples in `examples/`.
+
+## Version
+
+v0.1 (2018/08/22)
+
+ * Initial relase.
+
+## Author
+
+>Leo Brewin,
+>School of Mathematics,
+>Monash University,
+>Leo.Brewin@monash.edu
+
+## License
+
+All files in this collection are distributed under the [MIT][4] license. See the file LICENSE.txt for the full details.
+
+ [1]: https://github.com/gpoore/pythontex
+ [2]: https://github.com/sagemath/sagetex
+ [3]: https://tex.stackexchange.com/questions/1137/where-do-i-place-my-own-sty-or-cls-files-to-make-them-available-to-all-my-te
+ [4]: https://opensource.org/licenses/MIT
+ [5]: https://github.com/leo-brewin/hybrid-latex
diff --git a/macros/latex/contrib/hybrid-latex/doc/hybrid.pdf b/macros/latex/contrib/hybrid-latex/doc/hybrid.pdf
new file mode 100644
index 0000000000..aa4d14d230
--- /dev/null
+++ b/macros/latex/contrib/hybrid-latex/doc/hybrid.pdf
Binary files differ
diff --git a/macros/latex/contrib/hybrid-latex/doc/hybrid.pytex b/macros/latex/contrib/hybrid-latex/doc/hybrid.pytex
new file mode 100644
index 0000000000..ad2d228b6a
--- /dev/null
+++ b/macros/latex/contrib/hybrid-latex/doc/hybrid.pytex
@@ -0,0 +1,36 @@
+\pytag{ans.01}{%
+e^{- x}%
+}
+\pytag{ans.02}{%
+1 - x + \frac{x^{2}}{2} - \frac{x^{3}}{6} + \mathcal{O}\left(x^{4}\right)%
+}
+\pytag{ans.03}{%
+1 - x + \frac{x^{2}}{2} - \frac{x^{3}}{6} + \frac{x^{4}}{24} - \frac{x^{5}}{120} + \frac{x^{6}}{720} - \frac{x^{7}}{5040} + \mathcal{O}\left(x^{8}\right)%
+}
+\pytag{Pascal}{%
+\m{1}\cr%
+\m{1}\m{1}\cr%
+\m{1}\m{2}\m{1}\cr%
+\m{1}\m{3}\m{3}\m{1}\cr%
+\m{1}\m{4}\m{6}\m{4}\m{1}\cr%
+\m{1}\m{5}\m{10}\m{10}\m{5}\m{1}\cr%
+\m{1}\m{6}\m{15}\m{20}\m{15}\m{6}\m{1}\cr%
+}
+\pytag{fun}{%
+\frac{1}{x + 1}%
+}
+\pytag{ans.101}{%
+1 - x + x^{2} - x^{3} + x^{4} - x^{5} + \mathcal{O}\left(x^{6}\right)%
+}
+\pytag{ans.102}{%
+1 - x + x^{2} - x^{3} + x^{4} - x^{5} + x^{6} - x^{7} + x^{8} + \mathcal{O}\left(x^{9}\right)%
+}
+\pytag{ans.103}{%
+1 - x + x^{2} - x^{3} + x^{4} - x^{5} + x^{6} - x^{7} + x^{8} - x^{9} + x^{10} - x^{11} + \mathcal{O}\left(x^{12}\right)%
+}
+\pytag{ans.104}{%
+1 - x + x^{2} - x^{3} + x^{4} - x^{5} + x^{6} - x^{7} + x^{8} - x^{9} + x^{10} - x^{11} + x^{12} - x^{13} + x^{14} + \mathcal{O}\left(x^{15}\right)%
+}
+\pytag{ans.105}{%
+1 - x + x^{2} - x^{3} + x^{4} - x^{5} + x^{6} - x^{7} + x^{8} - x^{9} + x^{10} - x^{11} + x^{12} - x^{13} + x^{14} - x^{15} + x^{16} - x^{17} + x^{18} - x^{19} + x^{20} - x^{21} + x^{22} - x^{23} + x^{24} - x^{25} + x^{26} - x^{27} + x^{28} - x^{29} + \mathcal{O}\left(x^{30}\right)%
+}
diff --git a/macros/latex/contrib/hybrid-latex/doc/hybrid.sty b/macros/latex/contrib/hybrid-latex/doc/hybrid.sty
new file mode 100644
index 0000000000..d0c6efedeb
--- /dev/null
+++ b/macros/latex/contrib/hybrid-latex/doc/hybrid.sty
@@ -0,0 +1,77 @@
+\usepackage{pylatex}
+\usepackage{amsmath}
+\usepackage{amssymb}
+\usepackage{listings}
+\usepackage{xcolor}
+\usepackage{graphicx}
+\usepackage{geometry}
+\usepackage[toc]{multitoc}
+\usepackage{hyperref}
+\usepackage{breqn}
+\usepackage{longtable}
+
+\renewcommand*{\multicolumntoc}{2}
+\setlength{\columnseprule}{0pt}
+\setlength{\columnsep}{25pt}
+
+\hypersetup{colorlinks=true,filecolor=magenta}
+% \geometry{papersize={210mm,297mm},hmargin=2cm,tmargin=1.0cm,bmargin=1.5cm}
+% \geometry{papersize={210mm,297mm},hmargin=2cm,tmargin=1.0cm,bmargin=2.0cm}
+\geometry{papersize={210mm,297mm},hmargin=2cm,tmargin=1.5cm,bmargin=2.0cm}
+% \geometry{papersize={210mm,297mm},hmargin=2.5cm,tmargin=1.5cm,bmargin=2.5cm}
+
+\definecolor{grey95}{rgb}{0.95,0.95,0.95}
+
+\parindent=0pt
+\parskip=8pt plus 2pt minus 1pt
+
+\def\tts{\tt\small}
+
+\def\MYn{\vrule height 0pt depth 5pt width 0pt}
+\def\MYN{\vrule height 15pt depth 0pt width 0pt}
+
+\def\MyPar#1#2{\parbox[t]{#1\textwidth}{\sloppy \MYN#2\MYn}}
+
+\newbox\SlashPy
+\setbox\SlashPy=\hbox{\tts\verb|\Py|}
+
+\newbox\SlashPyStar
+\setbox\SlashPyStar=\hbox{\tts\verb|\Py*|}
+
+\def\PythonTeX{{\tts PythonTeX}}
+\def\SymPyTeX{{\tts SymPyTeX}}
+\def\SageTeX{{\tts SageTeX}}
+
+\def\pyLaTeX{{\tts pyLaTeX}}
+\def\matLaTeX{{\tts matLaTeX}}
+\def\mmaLaTeX{{\tts mmaLaTeX}}
+\def\mplLaTeX{{\tts mplLaTeX}}
+\def\cdbLaTeX{{\tts cdbLaTeX}}
+
+\def\bgcolour#1{\lstset{backgroundcolor=\color{#1}}}
+
+\def\pythonstyle{\lstset{%
+language=python,
+gobble=2,
+basicstyle={\tts},basewidth={0.50em},
+numbers=none,numberstyle=\tiny,numbersep=10pt,
+aboveskip=0pt,belowskip=0pt,
+frame=none,framesep=0pt,framerule=0pt,
+}}
+
+\def\latexstyle{\lstset{%
+language={[LaTeX]TeX},
+gobble=2,
+basicstyle={\tts},basewidth={0.50em},
+numbers=none,numberstyle=\tiny,numbersep=10pt,
+aboveskip=0pt,belowskip=0pt,
+frame=none,framesep=0pt,framerule=0pt,
+texcsstyle=*\bfseries\color{blue},
+stringstyle=\color{green},
+commentstyle=\color{blue}\slshape,
+}}
+
+\def\lstlatex#1{\def\latexmore{\lstset{#1}}}
+\lstlatex{}
+
+\lstnewenvironment{latex}{\begingroup\latexstyle\latexmore}{\endgroup}
diff --git a/macros/latex/contrib/hybrid-latex/doc/hybrid.tex b/macros/latex/contrib/hybrid-latex/doc/hybrid.tex
new file mode 100644
index 0000000000..c226321e90
--- /dev/null
+++ b/macros/latex/contrib/hybrid-latex/doc/hybrid.tex
@@ -0,0 +1,1089 @@
+\documentclass[12pt]{article}
+\usepackage{hybrid}
+
+\begin{document}
+
+\lstset{%
+basicstyle={\tts},basewidth={0.50em},
+numbers=none,numberstyle=\tiny,numbersep=10pt,
+aboveskip=10pt,belowskip=0pt,
+frame=single,framesep=2pt,framerule=0pt}
+
+\bgcolour{grey95}
+
+\begin{center}
+ {\Large\bf A hybrid LaTeX collection.}\\[20pt]
+ {Leo Brewin}\\[5pt]
+ {School of Mathematics, Monash University}\\[5pt]
+ {\tts Leo.Brewin@monash.edu}\\[5pt]
+ {August 2018}\\[10pt]
+ {Version 0.1}\\[10pt]
+ {\href{https://github.com/leo-brewin/hybrid-latex}{https://github.com/leo-brewin/hybrid-latex}}\\[20pt]
+ {\bf\small Abstract}\\[10pt]
+ {\small\MyPar{0.80}%
+ {This collection of LaTeX packages and Python scripts provides simple
+ tools to allow active Python to be embedded in a LaTeX document.
+ It does this by defining
+ a number of simple macros and Python scripts that build a bridge between
+ the output generated by the active code and the LaTeX code. Each language
+ is defined independently of the other languages. The scripts and macros
+ for one language are almost identical to those for the other languages in
+ the collection differing only in the names of the macros and some minor
+ language specific tweaks.
+ This makes it a simple matter to extend the collection to other languages.}}
+\end{center}
+
+\vskip 1cm
+
+\parindent=0pt
+\parskip=0pt plus 0pt minus 0pt
+
+\tableofcontents
+
+\vfill
+
+\clearpage
+
+\parindent=0pt
+\parskip=8pt plus 2pt minus 1pt
+
+\section{Introduction}
+
+Programs like Mathematica, Maple, Python and so on do a wonderful job of symbolic,
+numeric and graphical computations. On the other hand, LaTeX produces exceptional
+typeset documents but is less adept at symbolic and other forms of computation. It
+is a case of horses for courses. A common approach is to use one or more codes (in
+Python, Maple etc.) to do the computations and a separate LaTeX code to collect and
+document the results. It seems reasonable then to attempt to consolidate these
+separate codes into a single hybrid LaTeX document. That is the main purpose of
+this collection -- to provide tools that support active code within a LaTeX
+document.
+
+This document describes the CTAN version of this package. An extended version
+of the pacakge that allows inclusion of Python, Mathematica, Maple, Matlab and
+Cadabra code can be found on the
+{\href{https://github.com/leo-brewin/hybrid-latex}{GitHub repository}}.
+
+\section{The pyLaTeX package}
+
+There are other packages that do the same job, such as
+\href{https://github.com/gpoore/pythontex}{\PythonTeX} and
+\href{https://github.com/sagemath/sagetex}{\SageTeX}. In each case the journey from
+source to pdf is similar; the Python code is mined from the LaTeX source, passed
+over to Python and the output feed back into the LaTeX source. The packages differ
+in both the tools used to mine the Python code and the manner in which the output
+is made visible to the LaTeX source.
+
+In both \PythonTeX\ and \SageTeX, the Python code is mined using a first pass of
+LaTeX whereas \pyLaTeX\ uses a Python script to do the same job. There are two main
+reasons for adopting Python as the code miner. First, it is much easier to extend
+\pyLaTeX\ to incorporate other foreign languages (such as Mathematica, Matlab,
+Cadabra etc.). Doing so in \PythonTeX\ and \SageTeX\ requires careful tweaking of
+the underlying LaTeX class files -- such adventures requires a solid understanding
+of the LaTeX programming language and is not for the faint of heart. The second
+reason is that LaTeX is not the ideal tool to mine Python (or any other code) from
+a LaTeX document. There are much better text processing tools (such as Python) that
+can do the same job while being much easier (for the developer) to read, debug and
+extend.
+
+There are also some important differences in the way the end user accesses the
+Python output for use in the LaTeX source. In \PythonTeX\ and \SageTeX\ the Python
+output is saved to a collection of files (roughly one file per Python code block)
+while \pyLaTeX\ creates a single file. In all cases the contents of these files are
+made available to the LaTeX source by way of special LaTeX macros. The \PythonTeX\
+and \SageTeX\ output files are tightly coupled to the original LaTeX source. This
+means that their contents can not be easily shared with other LaTeX sources
+(without manual intervention). In contrast, the \pyLaTeX\ output is a single file
+that contains a collection of LaTeX macros, one for each piece of (tagged) Python
+output, that can be read by any other LaTeX source. Thus a user could split a
+larger LaTeX source into a collection of smaller sources (e.g., chapters of a book)
+with the Python output included as needed. An example of this sharing of Python
+output can be found in the examples directory. The file {\tts summary.tex} does no
+symbolic calculations of its own but instead pulls in selected parts of the Python
+output from other examples in that directory.
+
+Another important difference between \pyLaTeX\ and \PythonTeX\ and \SageTeX\ is
+that \pyLaTeX\ uses tags to nominate which pieces of the Python output should be
+made available to the LaTeX source. Both \PythonTeX\ and \SageTeX\ allow all Python
+output to accessed in the LaTeX source. The problem with that method is that it is
+not possible to record the history of a single Python expression. For example, if
+the Python code computes the expression {\tts foo} five times then only the fifth
+instance will be seen by LaTeX.
+
+\section{Requirements}
+
+\subsection{Latex}
+The \pyLaTeX\ package draws on a number of latex packages all of which should be
+available in any recent LaTeX distribution. The examples described in this document
+were run using the {\tts TeXLive-2018} distribution for macOS.
+
+The \pyLaTeX\ package provides both a normal style file, {\tts pylatex.sty} and a
+class file {\tts pylatex.cls}. The preamble to the style file contains the
+following declarations.
+
+\vskip 12pt
+
+\begin{latex}
+ \usepackage{comment}
+ \usepackage{listings}
+ \usepackage{keyval}
+ \usepackage{etoolbox}
+ \usepackage{xcolor}
+ \usepackage{pymacros}
+\end{latex}
+
+The {\tts pymacros} package is a key part of the \pyLaTeX\ collection as it defines
+the core macros for \pyLaTeX.
+
+The class file first includes the style file then executes the following statements
+
+\vskip 12pt
+
+\begin{latex}
+ \usepackage[papersize={297mm,210mm},
+ hmargin=2cm,tmargin=1.0cm,bmargin=1.5cm]{geometry}
+ \usepackage{amsmath}
+ \usepackage{amssymb}
+ \usepackage{hyperref}
+ \usepackage{breqn}
+\end{latex}
+
+The extra packages included in the class file are not essential but have proven
+useful in many cases and thus are included as a convenient default. Note that the
+options to the geometry package sets the output to A4 landscape format. This can be
+changed, either by editing the class file or by including a line like the following
+somewhere in the preamble of the users document.
+
+\vskip 6pt
+
+\begin{latex}
+ \geometry{margin=2.0cm,paperheight=25cm}
+\end{latex}
+
+\subsection{Python}
+There are no particular requirements on the Python environment in order to use the
+\pyLaTeX\ package though the examples listed below do require {\tts sympy} version
+1.1.1 or later. The simple measure is that if your embedded Python code can be run
+in your normal Python environment then that same code should work equally well
+using \pyLaTeX. All of the examples described in this document were run under macOS
+using the default Apple Python environment (Python 2.7) as well as under the
+Anaconda 5.2 (Python3.6) environment.
+
+\section{Setup}
+
+There is no setup tool as you only have a few files to install. The following files
+can be found inside the {\tts Python} directory of the hybrid collection.
+
+\begin{tabular}{rl}
+{\tts shell/pylatex.sh}:&Copied to anywhere that is visible on your path.\\[5pt]
+{\tts python/pypostproc.py}:&Copied to anywhere that is visible on your path.\\
+{\tts python/pypreproc.py}:&Ditto\\
+{\tts python/merge-tex.py}:&Ditto\\[5pt]
+{\tts latex/pylatex.cls}:&Copied to anywhere that is visible to LaTeX.\\
+{\tts latex/pylatex.sty}:&Ditto\\
+{\tts latex/pymacros.sty}:&Ditto
+\end{tabular}
+
+For example, the shell and Python scripts could be copied to {\tts
+\$HOME/local/sh/} while the LaTeX files could be copied to {\tts
+\$HOME/tex/inputs/}. Provided those directories are on the appropriate paths (i.e.,
+PATH and TEXINPUTS), that should be all that needs doing.
+
+Note that each of the Python scripts uses the following shebang (i.e., the first
+line in the file)
+
+\begin{lstlisting}
+#!/usr/local/bin/python3
+\end{lstlisting}
+
+You may need to change this to match the path to your version of {\tts python3}.
+
+\section{Compiling}
+To compile the file {\tts foo.tex} use
+
+\begin{lstlisting}
+ pylatex.sh -i foo
+\end{lstlisting}
+
+The {\tts pylatex.sh} script will call Pyhton and LaTeX as required to produce the
+pdf output. Along the way a number of intermediate files will be created (most of
+which will be deleted by default). Using the above command will produce not only
+the pdf but also {\tts foo.pytex}. This file contains a list of macros, one for
+each \pyLaTeX\ tag in {\tts foo.tex}, that are executed when the \pyLaTeX\ macros
+such as {\tts\verb|\py{bah}|} are called (in this case {\tts bah} is a tag
+associated with part of the Python code in the {\tts foo.tex} source). This same
+file can also be included in other LaTeX files using
+{\tts\verb|\input{foo.pytex}|}. This makes the Python output from {\tts foo.tex}
+available to other LaTeX files.
+
+Executing {\tts pylatex.sh} with the {\tts -h} flag will produce the following
+usage information.
+
+\begin{lstlisting}
+ usage : pylatex.sh -i file [-I<path to pymacros.sty>]
+ [-P<path to python>] [-s] [-k] [-x] [-h]
+ options : -i file : source file (without .tex extension)
+ -I file : full path to pymacros.sty file
+ -P path : where to find the Python binary
+ -s : silent, don't open the pdf file
+ -k : keep all temporary files
+ -x : don't call latex
+ -h : this help message
+ example : pylatex.sh -i file -P/usr/local/bin/python
+\end{lstlisting}
+
+The intermediate files produced by the {\tts -k} option are (excluding the
+standard LaTeX files)
+
+\bgroup
+\def\MYn{\vrule height 0pt depth 5pt width 0pt}
+\def\MYN{\vrule height 0pt depth 0pt width 0pt}
+\begin{tabular}{rl}
+ {\tts foo.py:}&\MyPar{0.75}%
+ {A copy of all the Python code stripped bare of (most) tags.}\\
+ {\tts foo_.py:}&\MyPar{0.75}%
+ {Similar to {\tts foo.py} but with additional Python code to catch Python
+ output for the \pyLaTeX\ tags.}\\
+ {\tts foo.pyidx:}&\MyPar{0.75}%
+ {An index of all \pyLaTeX\ tags.}\\
+ {\tts foo.pytex:}&\MyPar{0.75}%
+ {A set of macros, one for each tag, expanding to the Python output.}\\
+ {\tts foo.pytxt:}&\MyPar{0.75}%
+ {The raw Python output from {\tts foo_.py}.}
+\end{tabular}
+\egroup
+
+The {\tts -k} option is useful in cases where there is a bug in the users Python
+code. This option will force {\tts pylatex.sh} to retain all intermediate files,
+in particular the mined Python code {\tts foo.py}. This file can then be debugged
+and in turn the original LaTeX source can be corrected.
+
+The {\tts -I} option can be used in cases where the output from {\tts foo.tex} is
+intended for use in other LaTeX sources that do not use the \pyLaTeX\ style file
+{\tts pylatex.sty}. This could be useful for sharing results with colleagues who
+have not installed the \pyLaTeX\ package or when submitting a paper to a journal.
+The nett effect of the {\tts -I} option is to prepend the {\tts foo.pytex} file
+with the LaTeX definitions for all of the \pyLaTeX\ macros. Another approach would
+be to not use the {\tts -I} but share both the {\tts foo.pytex} and {\tts
+pymacros.sty} file with colleagues or journals.
+
+The only files that must be kept are {\tts foo.tex} (obviously) and {\tts
+foo.pytex}. This pair of files alone is sufficient for successful execution of
+LaTeX. The file {\tts foo.pytex} only needs to be recreated (using {\tts
+pylatex.sh}) when changes have been made to the Python code in {\tts foo.tex}.
+
+It is important to note that the Python code mined from the LaTeX source will be
+written to a single file. Thus it is important that all parts of this file
+interact harmoniously. If there is any possibility that one part of the (combined)
+code may adversely effect other parts then some action must be taken. One solution
+is to use language features of Python to avoid the clash, such as placing
+conflicting code in separate functions. Another solution is to move the
+conflicting code to separate LaTeX documents, run {\tts pylatex.sh} over each
+document, then include the results in the original LaTeX source using
+{\tts\verb|\input|} on the corresponding {\tts .pytex} files.
+
+\subsection{Caution}
+
+Be very careful -- each of the above named temporary files will overwrite any
+existing files of the same name. An easy road to disaster lies this way. Suppose
+you have an existing Python file named {\tts foo.py} and suppose you wish to catch
+its output for later use in some other LaTeX document. You might start by creating
+a wrapper file, {\tts foo.tex}, such as the following
+
+\vspace{10pt}
+
+\PySetup{action=verbatim}
+
+% note: the * in the 1st column is to ensure the python preprocessor does not respond
+% to any of the \begin{...} or \end{...} lines. The \end{document} line is the
+% real problem as it terminates the preprocessing.
+
+\bgroup
+\bgcolour{white}
+\begin{latex}
+* \documentclass[12pt]{pylatex}
+* \begin{document}
+* \begin{python}
+* \Input{foo.py}
+* \end{python}
+* \end{document}
+\end{latex}
+\egroup
+
+\PySetup{action=show}
+
+The {\tts pylatex.sh} will expand the {\tts\verb|\Input{foo.py}|} command to
+include the contents of {\tts foo.py}. No damage done -- so far. But now {\tts
+pylatex.sh} will read the newly merged file and will create its own version of
+{\tts foo.py} without \emph{any} of the \pyLaTeX\ tags. Thus any subsequent
+processing of {\tts foo.tex} will \emph{not} capture any Python output -- because
+all of the Python tags have been deleted.
+
+\subsection{The pypreproc.py and pypostproc.py scripts}
+
+The job of the {\tts pypreproc.py} script is to harvest the Python code from the
+LaTeX source. For a source named {\tt foo.tex} the script could be run using
+
+\begin{lstlisting}
+ pypreproc.py -i foo
+\end{lstlisting}
+
+This will produce {\tts foo.py, foo_.py} and {\tts foo.pyidx}. The file {\tts
+foo.py} is an almost\footnote{It may retain a few comments.} exact copy of the
+active Python code while {\tts foo_.py} is a version of {\tts foo.py} containing
+extra Python commands to catch the nominated Python output. This file is passed to
+Python to create the {\tts foo.pytxt} file which in turn is passed to {\tts
+pypostproc.py} using
+
+\begin{lstlisting}
+ pypostproc.py -i foo
+\end{lstlisting}
+
+which should create the {\tts foo.pytex} file.
+
+The pre and post processor scripts, {\tts pypreproc.py} and {\tts pypreproc.py},
+would rarely be used on their own. Though if something fails in the execution of
+the {\tts pylatex.sh} script it might prove useful to run these scripts on their
+own. Bug reports are always welcome (working on the time proven axiom that no code
+is ever bug free).
+
+\subsection{The merge-tex.py script}
+
+A common practice when writing LaTeX documents is to use a driver file to pull in
+other smaller LaTeX documents (e.g., a driver for a book could pull in the
+chapters as separate documents). Each of the chapters could contain some active
+Python code -- so how can such code be made visible to the Python preprocessor
+{\tts pypreproc.py}? That is the job of the {\tts merge-tex.py} script. It will
+read each file and copy the contents to a single merged file (named {\tts
+.merged.tex}). This file is then read by {\tts pypreproc.py}. Not all included
+files will contain active Python code so {\tts merge-tex.py} only looks for lines
+like {\tts\verb|\Input{foo.tex}|}. Other files included using {\tts\verb|\input|}
+or {\tts\verb|\include|} are ignored by {\tts merge-tex.py}. Thus the {\tts
+.merged.tex} need not be a faithful copy of the original source -- but that is not
+a problem since {\tts .merged.tex} exists solely for {\tts pypreproc.py} to
+harvest the active code after which it is no longer used.
+
+The {\tts merge-tex.py} script is language agnostic -- all it does is read its
+input and respond to any {\tts \verb|\Input|} lines. If the input file is {\tts
+foo.tex} then a merged file {\tts bah.tex} can be created using
+
+\begin{lstlisting}
+ merge-tex.py -i foo.tex -o bah.tex
+\end{lstlisting}
+
+Note that the {\tts .tex} file extensions \emph{are} required (unlike {\tts
+pypreproc.py} and {\tts pypostproc.py}).
+
+Note also that {\tts merge-tex.py} will follow nested files up to 10 deep.
+
+\section{Usage}
+
+Using \pyLaTeX\ entails the addition of user defined tags, in the form of Python
+comments, within the LaTeX source. These tags serve to connect the Python output
+to the LaTeX source. A typical example is shown in the following code.
+
+\vspace{10pt}
+
+\PySetup{action=verbatim}
+
+% Note: The * in the 1st column is to ensure the python preprocessor does not respond
+% to any of the \begin{...} or \end{...} lines. The \end{document} line is the
+% real problem as it terminates the preprocessing.
+% The * is hidden from LaTeX by way of the listinings option gobble=2.
+
+\bgroup
+\bgcolour{white}
+\latexstyle
+\begin{latex}
+* \documentclass[12pt]{pylatex}
+* \begin{document}
+* \begin{python}
+\end{latex}
+\pythonstyle
+\begin{python}
+* from sympy import *
+* x = Symbol('x')
+* ans = exp(-x) # py (ans.01,ans)
+* taylor = ans.series(x, 0, 4) # py (ans.02,taylor)
+* taylor = ans.series(x, 0, 8) # py (ans.03,taylor)
+\end{python}
+\latexstyle
+\begin{latex}
+* \end{python}
+* \begin{align*}
+* &\py*{ans.01}\\
+* &\py*{ans.02}\\
+* &\py*{ans.03}
+* \end{align*}
+* \end{document}
+\end{latex}
+\egroup
+
+\PySetup{action=show}
+
+This will lead to a pdf file that contains not just the Python output but also,
+for reference, a copy of the Python source (though this can be suppressed, see the
+section \hyperlink{sec:custom}{below} on customisation).
+
+\vspace{5pt}
+
+\begin{python}
+ from sympy import *
+ x = Symbol('x')
+ ans = exp(-x) # py (ans.01,ans)
+ taylor = ans.series(x, 0, 4) # py (ans.02,taylor)
+ taylor = ans.series(x, 0, 8) # py (ans.03,taylor)
+\end{python}
+\vspace{-10pt}
+\begin{align*}
+ &\py*{ans.01}\\
+ &\py*{ans.02}\\
+ &\py*{ans.03}
+\end{align*}
+
+The key elements to note are the \pyLaTeX\ tags (e.g., {\tts py(ans.01,ans)}) and
+the Python environment block. The tags are always part of a Python comment and are
+always of the form {\tts py(foo,bah)} where {\tts bah} is a Python symbol (or any
+printable entity) and {\tts foo} is the tag (later used in the LaTeX source). The
+\pyLaTeX\ tag can be any string of characters from the set {\tts a-zA-Z0-9.\_}. In
+the above example there are three tags, {\tts ans.01, ans.02} and {\tts ans.03}.
+The dot postfix is not essential but it provides a simple way to record a sequence
+of Python outputs. These tags are used later in the body of the LaTeX code and are
+accessed in the above example using the \pyLaTeX\ macro {\tts\verb|\py|}. This
+macro takes just one argument namely the corresponding \pyLaTeX\ tag. The macro
+{\tts\verb|\py{foo}|} will expand to the Python output for the tag {\tts foo}.
+Other macros are provided such as {\tts\verb|\py*{foo}|} which will expand to
+{\tts foo := } followed by the Python output. The full set of macros are described
+in the section \hyperlink{sec:pylatexmacros}{LaTeX macros}.
+
+The \pyLaTeX\ tags can be placed anywhere within the Python block. In the above
+example the tags were placed on the same line as the Python code. This is just for
+aesthetic effect -- they could equally well have been located on following lines.
+The \pyLaTeX\ tags should be unique within the LaTeX file. If the same tag name is
+used more than once the output associated with that tag will be from the last
+instance of that tag.
+
+Note that the {\tts pypreproc.py} script will interpret {\tts py(foo)} as a
+shorthand for {\tts py(foo,foo)}.
+
+\subsection{Undefined tags}
+
+If any of the \pyLaTeX\ macros are called for a tag that was \emph{not} defined
+then \hbox{(??)} will be printed. Note that this may cause a LaTeX syntax error
+when the \hbox{(??)} appears in an unexpected context (e.g., in cases where the
+output of {\tts\verb|\py|} was expected to expand to one or more
+{\tts\verb|\\|}'s).
+
+\subsection{Tagged blocks}
+
+There is a second form of tag, known as a tagged block, in which a block of Python
+code is enclosed in a matching {\tts\verb|# pyBeg(foo)|},
+{\tts\verb|# pyEnd(foo)|} pair. An example of a tagged block can be seen in the
+following code. The tag is given the name {\tts Pascal} and can be accessed in the
+LaTeX code using any of the \pyLaTeX\ macros, in this case {\tts\verb|\py|}. The
+intention of tagged blocks is that they can be used to capture any Python output
+generated within the tagged block (except the output that is caught by simple tags
+such as shown in the previous example).
+
+Here is a short example\footnote{This example is very \emph{inefficient} as it
+uses recursive calls for \emph{every} element in the table.} that demonstrates the
+use of a single tagged block.
+
+\vspace{10pt}
+
+\PySetup{action=verbatim}
+
+% note: the * in the 1st column is to ensure the python preprocessor does not respond
+% to any of the \begin{...} or \end{...} lines. The \end{document} line is the
+% real problem as it terminates the preprocessing.
+
+\bgroup
+\bgcolour{white}
+\latexstyle
+\begin{latex}
+* \documentclass[12pt]{pylatex}
+* \begin{document}
+*
+* \begin{python}
+\end{latex}
+\pythonstyle
+\begin{python}
+* def build(n):
+* if n==0:
+* return [1]
+* else:
+* P = build(n-1)
+* return [1] + [P[i]+P[i+1] for i in range(n-1)] + [1]
+*
+* def display(n):
+* for i in range(n):
+* ans = ""
+* for j in range(0,len(build(i))):
+* ans = ans + "\m{" + str(build(i)[j]) + "}"
+* print (ans+"\cr")
+*
+* # pyBeg (Pascal)
+* display(7)
+* # pyEnd (Pascal)
+\end{python}
+\latexstyle
+\begin{latex}
+* \end{python}
+*
+* \def\m#1{\hbox to 1cm{\hfill #1\hfill}}
+* \halign{\hbox to \textwidth{\hfill#\hfill}\cr\py{Pascal}}
+*
+* \end{document}
+\end{latex}
+\egroup
+
+\PySetup{action=hide}
+
+\begin{python}
+ def build(n):
+ if n==0:
+ return [1]
+ else:
+ P = build(n-1)
+ return [1] + [P[i]+P[i+1] for i in range(n-1)] + [1]
+
+ def display(n):
+ for i in range(n):
+ ans = ""
+ for j in range(0,len(build(i))):
+ ans = ans + "\m{" + str(build(i)[j]) + "}"
+ print (ans+"\cr")
+
+ # pyBeg (Pascal)
+ display(7)
+ # pyEnd (Pascal)
+\end{python}
+
+\clearpage
+
+\PySetup{action=show}
+
+The output of the above code (excluding the echoed Python code) is Pascal's triangle (as expected).
+
+\def\m#1{\hbox to 1cm{\hfill #1\hfill}}
+\halign{\hbox to \textwidth{\hfill#\hfill}\cr\py{Pascal}}
+
+All Python code must be contained within the Python environment block, i.e.,
+within a {\tts\verb|\begin{python}|} {\tts\verb|...|} {\tts\verb|\end{python}|}
+pair. There is no facility to allow \emph{inline} execution of Python code (though
+this is allowed in other packages, notably \PythonTeX). The reason for taking this
+approach was to force a clear separation between native LaTex code and active
+Python blocks. This makes it easier to locate the Python code. If an inline
+calculation is needed then it is a simple matter of including the Python code in a
+Python block and using a tag to catch and display the result.
+
+Tagged blocks can be nested (though it is not clear what use this might serve) and
+they can contain simple tags. The simple tags will always take priority over
+tagged blocks in capturing Python output. Tagged blocks should respect the usual
+rules of begin-end blocks -- they can nested but they must not overlap. The Python
+pre and post processors (within the {\tts pylatex.sh} script) will report errors
+when tagged blocks are not properly structured (i.e., overlapping, unmatched {\tts
+pyBeg/pyEnd} items etc.).
+
+\subsection{LaTeX macros}
+\hypertarget{sec:pylatexmacros}{}
+
+The \pyLaTeX\ package provides a number commands for typesetting tagged expressions.
+
+\newbox\Slashpya
+\setbox\Slashpya=\hbox{\tts\verb|\py{foo}|}
+
+\newbox\Slashpyb
+\setbox\Slashpyb=\hbox{\tts\verb|\py*{foo}|}
+
+\newbox\SlashPya
+\setbox\SlashPya=\hbox{\tts\verb|\Py{foo}|}
+
+\newbox\SlashPyb
+\setbox\SlashPyb=\hbox{\tts\verb|\Py*[bah]{foo}|}
+
+\newbox\Slashhfill
+\setbox\Slashhfill=\hbox{\tts\verb|[\hfill]|}
+
+\newbox\SlashPySetup
+\setbox\SlashPySetup=\hbox{\tts\verb|\PySetup{key=value}|}
+
+\newbox\SlashDmatha
+\setbox\SlashDmatha=\hbox{\tts\verb|\Dmath[bah]{foo}|}
+
+\newbox\SlashDmathb
+\setbox\SlashDmathb=\hbox{\tts\verb|\Dmath*[bah]{foo}|}
+
+\newbox\SlashttTag
+\setbox\SlashttTag=\hbox{\tts\verb|\ttTag{foo}|}
+
+\newbox\SlashInput
+\setbox\SlashInput=\hbox{\tts\verb|\Input{foo.tex}|}
+
+\begin{longtable}{rl}
+ {\copy\Slashpya}&\MyPar{0.70}{%
+ Display the value of {\tts foo} without any additional decorations.}\\
+ {\copy\Slashpyb}&\MyPar{0.70}{%
+ Similar to \copy\Slashpya\ but adds {\tts foo$\colonEq$}
+ immediately to the left of {\tts foo}.}\\
+ {\copy\SlashPya}&\MyPar{0.70}{%
+ Display the value of {\tts foo} and set the equation tag to be ({\tts
+ foo}). Since this macro creates its own equation tag it should not be used
+ in any maths environment that also creates an equation tag (e.g., {\tts
+ align, equation} etc.) It can not be used in any of the {\tts breqn}
+ environments ({\tts breqn} will report a package error).}\\
+ {\copy\SlashPyb}&\MyPar{0.70}{%
+ Use this version of \copy\SlashPya\ in any of the (starred) {\tts breqn}
+ environments. The optional argument {\tts [bah]} specifies the spacing
+ between the end of {\tts foo} and the right hand edge of the tag. The
+ default is \copy\Slashhfill\ which forces the tag to the far right.}\\
+ {\copy\SlashDmatha}&\MyPar{0.70}{%
+ This is a shortcut for the {\tts dmath} environment. The optional and
+ required arguments of \copy\SlashDmatha\ are passed onto the {\tts dmath}
+ environment. See page (\pageref{ex:Dmath}) for an example.}\\
+ {\copy\SlashDmathb}&\MyPar{0.70}{%
+ This is similar to \copy\SlashDmatha\ but with the one small change that it
+ applies to the {\tts dmath*} environment.}\\
+ {\copy\SlashttTag}&\MyPar{0.70}{%
+ This is a simple macro that typesets a tag name such as {\tts foo} in a
+ small typewriter font enclosed in matching parenthesis, e.g., ({\tts
+ foo}).}\\
+ {\copy\SlashPySetup}&\MyPar{0.70}{%
+ This command takes one argument in the form of a key=value pair. Its
+ purpose is to set global options for the following Python blocks. The
+ current list of options is limited to one key with three values. These are
+ described in the following section.}\\
+\end{longtable}
+
+\subsection{Custom formats}
+\hypertarget{sec:custom}{}
+
+The \pyLaTeX\ package allows for some minor customisation of the formatting of the Python code. The macro
+{\tts\verb|\PySetup|} can be used to set the preferred options using key-value pairs. There is only
+one key as described in the following table.
+
+\begin{tabular}{rl}
+ {\tts action=show}&%
+ \MyPar{0.70}{Make the Python code visible and active (i.e., cancel the
+ {\tts hide} and {\tts verbatim} actions).}\\
+ {\tts action=hide}&%
+ \MyPar{0.70}{Hide the python code but still allow the Python code to be
+ active.}\\
+ {\tts action=verbatim}&%
+ \MyPar{0.70}{This will make the Python code inactive (i.e., it will not
+ be passed to Python).}\\[5pt]
+\end{tabular}
+
+Multiple calls to {\tts\verb|\PySetup|} can be made and apply to all subsequent
+Python environments or until changed by a later call to {\tts\verb|\PySetup|}. The
+default value is {\tts action=show}.
+
+The \pyLaTeX\ package uses the {\tts listings} package which allows further
+formatting changes to be made. For example, to add line numbers and a shaded
+background to the Python code use
+
+\begin{lstlisting}
+ \lstset{numbers=left,backgroundcolor=\color{lightgrey}}
+\end{lstlisting}
+
+Many other options are available, see the {\tts listings} documentation for full
+details.
+
+\subsection{Python indentation}
+
+Python has very strict (but simple) indentation rules that must be observed not
+only within a single Python block but across all Python blocks. Why? Because all
+of the Python blocks will be collected into a separate single Python code and that
+code must observe the Python indentation rules.
+
+\subsection{Error reporting}
+
+None of the packages in this collection treat errors from the active code with any
+degree of aplomb -- at best they report that an error occurred, at worst they
+silently ignore the error leaving {\tts pdflatex} to pick up the pieces. This is
+far from ideal. If in doubt, run the main script (e.g., {\tts pylatex.sh}) using
+the {\tts -k -x} command line options then inspect the file {\tts foo.pytxt} for
+any errors. If there are errors then you can debug the extracted code {\tts
+foo.py} later returning the corrected code back to the LaTeX source {\tts foo.tex}.
+
+\subsection{Matplotlib and macOS}
+
+There is a well known issue when using Matplotlib on macOS where some Matplotlib
+codes may fail. The
+\href{https://matplotlib.org/faq/osx_framework.html}{Matplotlib documentation}
+provides a number of suggestions including to use {\tts pythonw} rather than {\tts
+python}. For such cases use the {\tts -P} flag to set the path to the {\tts
+pythonw} command when invoking the {\tts pylatex.sh}. This has been tried and
+tested under the Anaconda Python3 environment for macOS.
+
+\section{Examples}
+
+The {\tts examples} directory contains a small collection of complete \pyLaTeX\
+codes that showcase the various features of the \pyLaTeX\ package. The Python
+source contains inline comments that result in long lines of text that do not
+comfortably fit on an A4 page in portrait mode. Thus most of the examples have
+been formatted in A4 landscape mode (this is the default setting for the \pyLaTeX\
+class). Consequently it is not practical to include those examples in this file.
+The following table provides a very brief summary of each example (with hyperlinks
+to the corresponding pdf's in {\tts ../python/examples/}).
+
+\newbox\exa
+\setbox\exa=\hbox{\href{../python/examples/example-01.pdf}{Example 1}}
+
+\newbox\exb
+\setbox\exb=\hbox{\href{../python/examples/example-02.pdf}{Example 2}}
+
+\newbox\exc
+\setbox\exc=\hbox{\href{../python/examples/example-03.pdf}{Example 3}}
+
+\newbox\exd
+\setbox\exd=\hbox{\href{../python/examples/example-04.pdf}{Example 4}}
+
+\newbox\exe
+\setbox\exe=\hbox{\href{../python/examples/example-05.pdf}{Example 5}}
+
+\newbox\exf
+\setbox\exf=\hbox{\href{../python/examples/example-06.pdf}{Example 6}}
+
+\newbox\exg
+\setbox\exg=\hbox{\href{../python/examples/example-07.pdf}{Example 7}}
+
+\newbox\exh
+\setbox\exh=\hbox{\href{../python/examples/example-08.pdf}{Example 8}}
+
+\newbox\exi
+\setbox\exi=\hbox{\href{../python/examples/example-09.pdf}{Example 9}}
+
+\newbox\exz
+\setbox\exz=\hbox{\href{../python/examples/summary.pdf}{Summary}}
+
+\begin{longtable}{rl}
+ \box\exa:&\MyPar{0.80}%
+ {This is a collection of basic mathematical computations. It contains
+ examples of simple tags and tagged blocks.}\\
+ \box\exb:&\MyPar{0.80}%
+ {This is the first of two examples drawn from the \PythonTeX\ website
+ \href{https://github.com/gpoore/pythontex/}{(here)}. It shows a nice example
+ of using a tagged block.}\\
+ \box\exc:&\MyPar{0.80}%
+ {This is the second example taken from the \PythonTeX\ website
+ \href{https://github.com/gpoore/pythontex/}{(here)}. It shows a step-by-step
+ computation of a simple triple integral.}\\
+ \box\exd:&\MyPar{0.80}%
+ {This example shows how {\tts Matplotlib} and friends can be used to
+ construct a simple xy-plot.}\\
+ \box\exe:&\MyPar{0.80}%
+ {This example shows how problems with the placement of equation tags can be
+ resolved by careful choice of the optional argument to
+ {\copy\SlashPyStar}.}\\
+ \box\exf:&\MyPar{0.80}%
+ {This is a simple example that uses Python to compute the first few
+ iterations of a Newton-Raphson method.}\\
+ \box\exg:&\MyPar{0.80}%
+ {This example contains a single tag-block. It captures basic system data.}\\
+ \box\exh:&\MyPar{0.80}%
+ {This example shows how to completely hide the Python code as well a trick
+ for hiding just the tags.}\\
+ \box\exi:&\MyPar{0.80}%
+ {This example is a proof of concept example. It shows one approach for
+ passing LaTeX information to the Python code. The code is not elegant but it
+ does the job.}\\
+ \box\exz:&\MyPar{0.80}%
+ {There is no Python code in this example. Instead, the results from the
+ above examples are included by inputting the corresponding {\tts .pytex}
+ file.}
+\end{longtable}
+
+\section{Splitting long lines using breqn}
+
+On some occasions the output from a computation may be too long to fit comfortably
+on one line. In such cases some form of line splitting is required. A less than
+ideal approach is to manually edit the output by inserting carefully chosen line
+breaks. Not only is this approach tedious and inelegant it also requires the line
+breaks to be reset whenever the document format is changed (e.g., from A4 to
+letter size) or when some of the computations are changed (e.g., showing twice as
+many terms in a series expansion).
+
+A better approach is to use the {\tts breqn} package which uses sophisticated
+algorithms to find good line breaks with minimal input from the user. In many
+cases {\tts breqn} will produce an acceptable output. But it is not without its
+own limitations\footnote{The {\tts breqn} documentation does note that it is a
+work in progress.} as some of the following examples will demonstrate. The fact is
+that there are no algorithms that will guarantee prefect line splitting as there
+will always be some subjective element in what constitutes a good line break. The
+following series of examples are intended as guide of what to expect when using
+{\tts breqn} and also to demonstrate some possible remedies.
+
+Here is a simple example based on a truncated Taylor series expansion of $1/(1+x)$
+around $x=0$.
+\bgroup
+\lstset{gobble=2,numbers=left}
+\begin{python}
+ from sympy import *
+ x = Symbol('x')
+ ans = 1/(1+x) # py (fun,ans)
+ taylor = ans.series(x, 0, 6) # py (ans.101,taylor)
+ taylor = ans.series(x, 0, 9) # py (ans.102,taylor)
+ taylor = ans.series(x, 0, 12) # py (ans.103,taylor)
+ taylor = ans.series(x, 0, 15) # py (ans.104,taylor)
+ taylor = ans.series(x, 0, 30) # py (ans.105,taylor)
+\end{python}
+\egroup
+For an expansion with only a handful of terms the usual {\tts align} environment
+produces an acceptable output as demonstrated in the following code and output.
+
+\vskip 12pt
+
+\bgroup
+\bgcolour{white}
+\begin{latex}
+ \begin{align*}
+ \py{fun} &= \py {ans.101}\tag{\ttTag{ans.101}}\\
+ &= \py {ans.102}\tag{\ttTag{ans.102}}\\
+ &= \py {ans.103}\tag{\ttTag{ans.103}}
+ \end{align*}
+\end{latex}
+\egroup
+\begin{align*}
+ \py{fun} &= \py {ans.101}\tag{\ttTag{ans.101}}\\
+ &= \py {ans.102}\tag{\ttTag{ans.102}}\\
+ &= \py {ans.103}\tag{\ttTag{ans.103}}
+\end{align*}
+In this and the following examples the equation tags have been explicitly matched
+to the corresponding {\tts sympy} expressions. This makes it somewhat easier to
+follow the progress of a computation. The normal equation numbering (e.g., (1),
+(2) etc.) can be recovered by deleting the calls to {\tts\verb!\tag{...}!}. The
+{\tts\verb|\ttTag|} macro, defined in the \pyLaTeX\ package, does as its name
+suggests -- typesets the tag in the {\tts\verb|\tt|} font.
+
+The above works well for short expressions but for longer expressions it is much
+better to use the {\tts breqn} package. The key points to note in the following
+example are the use of the {\tts dmath} environment and the manner in which the
+equation tags have been set.
+
+\vskip 12pt
+
+\bgroup
+\bgcolour{white}
+\begin{latex}
+ \usepackage{breqn}
+ ...
+ \begin{dmath}[number={\ttTag{ans.105}}]
+ \py {fun} = \py {ans.105}
+ \end{dmath}
+\end{latex}
+\egroup
+The corresponding output is
+\Dmath[number={\ttTag{ans.105}}]{\py{fun} = \py{ans.105}}
+
+The {\tts dmath} environment is one of a number environments provided by the {\tts
+breqn} package. Its job is to chose sensible line breaks that produce a pleasant
+output (though what constitutes \emph{pleasant} may be up for debate). Note that
+all of the {\tts breqn} environments do \emph{not} support the {\tts\verb!\tag!}
+macro for setting equation tags. The only way to do so is through the
+{\tts\verb!number={...}!} optional argument as shown in the above example. To
+suppress the equation label use the {\tts dmath*} environment.
+
+\bgroup
+\advance\leftskip1cm
+\advance\rightskip1cm
+\hypertarget{landing}{{\bf Note:}}
+This sentence may seem out of place but its purpose is to demonstrate a claim made
+later is this documentation that, with a suitably modified label macro, the {\tts
+hyperref} package can be used to jump to the correct page when clicking on a
+hyperlink for an equation. For example, clicking on this link \eqref{eq:target}
+should take us to page \pageref{eq:target}. But now, back to the main discussion.
+\par
+\egroup
+
+The {\tts breqn} package also provides a {\tts dgroup} environment. This can be
+used to group a sequence of {\tts dmath} environments to align the output on a
+targeted symbol (usually the first equals sign). Here is a short example
+
+\vskip 12pt
+
+\bgroup
+\bgcolour{white}
+\lstlatex{numbers=left}
+\begin{latex}
+ \begin{dgroup}
+ \begin{dmath}[number={\ttTag{ans.103}}] \py {fun} = \py {ans.103} \end{dmath}
+ \begin{dmath}[number={\ttTag{ans.104}}] {} = \py {ans.104} \end{dmath}
+ \begin{dmath}[number={\ttTag{ans.105}}] {} = \py {ans.105} \end{dmath}
+ \end{dgroup}
+\end{latex}
+\egroup
+
+and here is the corresponding output
+\begin{dgroup}
+ \begin{dmath}[number={\ttTag{ans.103}}] \py {fun} = \py {ans.103} \end{dmath}
+ \begin{dmath}[number={\ttTag{ans.104}}] {} = \py {ans.104} \end{dmath}
+ \begin{dmath}[number={\ttTag{ans.105}}] {} = \py {ans.105} \end{dmath}
+\end{dgroup}
+
+There are a few points to be made here. First is the less then ideal placement of
+tags. Second is the somewhat ungainly syntax. Both of these points will be
+discussed in more detail shortly. Next, notice the {\tts\verb!{}!} preceding the
+equal signs in lines 3 and 4. This supplies an empty left hand side for each
+equation and is essential to ensure proper alignment (on the equals sign) across
+all three equations. Unlike other maths environments, {\tts breqn} does not allow
+you to specify which symbol to use as the alignment target -- that choice is made
+deep within the inner workings of {\tts dmath} and friends. It will choose symbols
+including {\tts\verb!=-+:()!}. If you wish to tell {\tts dmath} to not use a
+particular symbol you can wrap that symbol inside {\tts\verb!\hiderel!} as in the
+following example.
+
+\vskip 12pt
+
+\bgroup
+\bgcolour{white}
+\begin{latex}
+ \begin{dgroup*}
+ \begin{dmath*} f(x) \hiderel{=} \py {fun} = \py {ans.101} \end{dmath*}
+ \begin{dmath*} {} = \py {ans.102} \end{dmath*}
+ \end{dgroup*}
+\end{latex}
+\egroup
+\begin{dgroup*}
+ \begin{dmath*} f(x) \hiderel{=} \py {fun} = \Py*{ans.101} \end{dmath*}
+ \begin{dmath*} {} = \Py*{ans.102} \end{dmath*}
+\end{dgroup*}
+
+Consider now the earlier example in which the equation tags were not well placed
+and where the syntax was described as ungainly. The syntax can be tidied slightly
+using one of the features of the {\tts breqn} package -- it allows the {\tts
+dmath} and {\tts dmath*} environments to be wrapped in user defined macros such as
+
+\vskip 12pt
+
+\bgroup
+\bgcolour{white}
+\begin{latex}
+ \newcommand*{\Dmath}[2][]{\begin{dmath}[#1]#2\end{dmath}}
+\end{latex}
+\egroup
+This defines {\tts\verb!\Dmath!} as a macro that takes one required argument and
+one optional argument (both of which are passed on to {\tts dmath}). It is not a
+huge saving but even so it does save a few keystrokes. Here is one of the previous
+examples rewritten using {\tts\verb!\Dmath!}.
+
+\vskip 12pt
+
+\bgroup
+\bgcolour{white}
+\begin{latex}
+ \begin{dgroup}
+ \Dmath[number={\ttTag{ans.103}}]{ \py {fun} = \py {ans.103} }
+ \Dmath[number={\ttTag{ans.104}}]{ {} = \py {ans.104} }
+ \Dmath[number={\ttTag{ans.105}}]{ {} = \py {ans.105} }
+ \end{dgroup}
+\end{latex}
+\egroup
+The \pyLaTeX\ package defines {\tts\verb|\Dmath|} for the {\tts dmath} environment
+as well as {\tts\verb|\Dmath*|} as a shortcut for the {\tts dmath*} environment.
+
+The use of {\tts\verb|\Dmath*|} does save some keystrokes but it does not fix the
+problem where the text is obscured by the tag. There seems to be no simple way to
+tell {\tts breqn} to avoid such problems while retaining {\tts breqn}'s method of
+placing the tags. The {\tts\verb|\Py*|} macro avoids this problem by presenting
+{\tts dmath*} with a concatenated string built from the text (e.g., the output of
+{\tts\verb|\py{foo}|}), some flexible white space (usually {\tts\verb|\hfill|})
+and the tag (e.g., {\tts foo}). This string is processed by {\tts dmath*} and line
+splits are inserted at its discretion. This will guarantee that the all of the
+text and the tag will be displayed. The user defined whitespace can always be
+chosen so that the tag does not overlap the text. This is a bare minimum of what
+could be deemed an acceptable output -- non overlap of the text and the tag. The
+output might not be optimal but at least it is readable. Here is the same example
+as above but this time using {\tts\verb|\Py*|} to display the text and the tags.
+
+\vskip 12pt
+
+\pglabel{ex:Dmath}
+\bgroup
+\bgcolour{white}
+\begin{latex}
+ \begin{dgroup*}
+ \Dmath*{ \py {fun} = \Py* {ans.103} }
+ \Dmath*{ {} = \Py*[\hskip 2cm] {ans.104} }
+ \Dmath*{ {} = \Py*[\hskip 3cm] {ans.105} }
+ \end{dgroup*}
+\end{latex}
+\egroup
+\begin{dgroup*}
+ \Dmath*{ \py {fun} = \Py* {ans.103} }
+ \Dmath*{ {} = \Py*[\hskip 2cm] {ans.104} }
+ \Dmath*{ {} = \Py*[\hskip 3cm] {ans.105} }
+\end{dgroup*}
+The whitespace between the text and the tag is passed through the optional
+argument of {\tts\verb|\Py*|} with the default set as {\tts\verb|\hfill|}. The
+inner workings of {\tts\verb|\Py*|} sets the tag using {\tts\verb|\llap|} and thus
+the whitespace optional argument measures the space between the right hand edge of
+the tag and left hand end of the equation text. Note also that this example uses
+the starred version of {\tts dgroup} and {\tts\verb|\Dmath*|}. This is to ensure
+that {\tts breqn} plays no part in forming the tag (as that is handled by
+{\tts\verb|\Py*|}).
+
+The \pyLaTeX\ package defines both {\tts\verb|\Py|} and {\tts\verb|\Py*|}. The
+{\tts\verb|\Py|} macro is intended for use only in the starred maths environments
+(e.g., {\tts align*, equation*} etc.). Like {\tts\verb|\Py*|}, it also sets the
+equation tag to match the expression but it does so using standard methods
+(internally it calls {\tts\verb|\tag|}). This last point (that {\tts\verb|\Py*|}
+uses {\tts\verb|\tag|}) also means that it can not be used in any of the {\tts
+breqn} environments (doing so will cause LaTeX to issue a {\tts breqn} package
+error).
+
+Both {\tts\verb|\Py|} and {\tts\verb|\Py*|} support the usual method of labelling
+and referencing equations using {\tts\verb|\label{foo}|} and
+{\tts\verb|\ref{foo}|}. However, there are good reasons to use a modified version
+of the {\tts\verb|\label|} macro (as described below) to improve the accuracy of
+the label-reference pairs when using hyperlinks (i.e., so that the reference
+points to the correct page where the label was issued).
+
+\subsection{Hyperref, hyperlinks and breqn}
+
+The {\tts breqn} package does support the {\tts hyperref} package though as
+discussed
+\href{https://tex.stackexchange.com/questions/193380/hyperref-pageref-links-point-t
+o-first-page}{(here)} the hyperlinks to an equation may not work as expected
+(clicking on a link may take you to the wrong page). The suggested fix is to
+define equation labels via a modified label macro such as the following
+
+\vskip 12pt
+
+\bgroup
+\bgcolour{white}
+\begin{latex}
+ \newcommand{\pglabel}[1]{\phantomsection\label{#1}}
+\end{latex}
+\egroup
+An example of its use might be
+
+\vskip 12pt
+
+\bgroup
+\bgcolour{white}
+\begin{latex}
+ \begin{dmath}[number={\ttTag{ans.105}}]
+ \py{fun} = \py{ans.105} \pglabel{eq:target}
+ \end{dmath}
+\end{latex}
+\egroup
+\begin{dmath}[number={\ttTag{ans.105}}]
+ \py{fun} = \py{ans.105} \pglabel{eq:target}
+\end{dmath}
+Then clicking on the link defined by {\tts\verb!\ref{eq:target}!} will bring the
+reader to this page. An example of such a link was seen earlier in this document
+(click \hyperlink{landing}{here} to jump back to that page).
+
+\section{Licence}
+
+This software is licensed under the MIT License (MIT). Please see the {\tts
+LICENCE.txt} file for more information.
+
+\section{Version history}
+\leftline{\bf v0.1 (2018/08/22)}% 22 Aug 2018
+\begin{itemize}
+\item Initial public release.
+\end{itemize}
+
+\end{document}
diff --git a/macros/latex/contrib/hybrid-latex/examples/example-01.pdf b/macros/latex/contrib/hybrid-latex/examples/example-01.pdf
new file mode 100644
index 0000000000..6284929a7e
--- /dev/null
+++ b/macros/latex/contrib/hybrid-latex/examples/example-01.pdf
Binary files differ
diff --git a/macros/latex/contrib/hybrid-latex/examples/example-01.pytex b/macros/latex/contrib/hybrid-latex/examples/example-01.pytex
new file mode 100644
index 0000000000..cd366cd8ba
--- /dev/null
+++ b/macros/latex/contrib/hybrid-latex/examples/example-01.pytex
@@ -0,0 +1,123 @@
+\pytag{ans.101}{%
+a^{3} + 3 a^{2} b + 3 a b^{2} + b^{3}%
+}
+\pytag{ans.102}{%
+- x \left(- a + x\right) \left(x + 1\right)%
+}
+\pytag{ans.103}{%
+\left [ -2, \quad 2\right ]%
+}
+\pytag{ans.104}{%
+\left \{ a : \frac{1}{5}, \quad b : - \frac{13}{5}, \quad c : \frac{17}{5}\right \}%
+}
+\pytag{ans.105}{%
+3.1415926535897932384626433832795028841971693993751%
+}
+\pytag{ans.106}{%
+- \frac{1}{4 \left(x + 5\right)} + \frac{1}{4 \left(x + 1\right)}%
+}
+\pytag{ans.107}{%
+\frac{1}{\left(x + 1\right) \left(x + 5\right)}%
+}
+\pytag{rhs.108}{%
+\tanh{\left (\log{\left (x \right )} \right )}%
+}
+\pytag{rhs.109}{%
+i \tan{\left (x \right )}%
+}
+\pytag{rhs.110}{%
+0%
+}
+\pytag{lhs.108}{%
+\tanh{\left (\log{\left (x \right )} \right )}%
+}
+\pytag{lhs.109}{%
+\tanh{\left (i x \right )}%
+}
+\pytag{lhs.110}{%
+- 4 \sinh^{3}{\left (x \right )} - 3 \sinh{\left (x \right )} + \sinh{\left (3 x \right )}%
+}
+\pytag{ans.201}{%
+\left[\begin{matrix}2 & 3\\5 & 4\end{matrix}\right]%
+}
+\pytag{ans.202}{%
+\left[\begin{matrix}-1\\7\end{matrix}\right]%
+}
+\pytag{ans.203}{%
+\left[\begin{matrix}-5 & 3\\5 & 5\end{matrix}\right]%
+}
+\pytag{ans.204}{%
+\lambda^{2} - 6 \lambda - 7%
+}
+\pytag{ans.205}{%
+\left[\begin{matrix}3\\7\end{matrix}\right]%
+}
+\pytag{ans.206}{%
+\left ( \frac{9}{7}, \quad \frac{1}{7}\right )%
+}
+\pytag{ans.301}{%
+4%
+}
+\pytag{ans.302}{%
+\infty%
+}
+\pytag{ans.303}{%
+2 x%
+}
+\pytag{ans.304}{%
+\frac{4}{3}%
+}
+\pytag{ans.305}{%
+e^{a}%
+}
+\pytag{ans.401}{%
+\frac{1}{2} + \frac{3 \left(x - 1\right)^{2}}{16} - \frac{\left(x - 1\right)^{3}}{8} + \frac{5 \left(x - 1\right)^{4}}{64} - \frac{3 \left(x - 1\right)^{5}}{64} - \frac{x}{4} + O\left(\left(x - 1\right)^{6}; x\rightarrow 1\right)%
+}
+\pytag{ans.402}{%
+1 + x + \frac{x^{2}}{2} + \frac{x^{3}}{6} + \frac{x^{4}}{24} + \frac{x^{5}}{120} + O\left(x^{6}\right)%
+}
+\pytag{ans.403}{%
+\frac{3121579929551692678469635660835626209661709}{1920815367859463099600511526151929560192000}%
+}
+\pytag{ans.404}{%
+\frac{\pi^{4}}{90}%
+}
+\pytag{ans.501}{%
+x \cos{\left (x \right )} + \sin{\left (x \right )}%
+}
+\pytag{ans.502}{%
+\frac{\sqrt{2} \pi}{8} + \frac{\sqrt{2}}{2}%
+}
+\pytag{ans.503}{%
+- a + b + \sin{\left (a \right )} \cos{\left (a \right )} - \sin{\left (b \right )} \cos{\left (b \right )}%
+}
+\pytag{lhs.504}{%
+\int_{0}^{\infty} 2 e^{- x^{2}}\, dx%
+}
+\pytag{ans.504}{%
+\sqrt{\pi}%
+}
+\pytag{lhs.505}{%
+\int_{0}^{1}\int_{0}^{x} \left(x^{2} + y^{2}\right)\, dy\, dx%
+}
+\pytag{ans.505}{%
+\frac{1}{3}%
+}
+\pytag{ans.601}{%
+C_{1} e^{- x} + a \sin{\left (x \right )} - a \cos{\left (x \right )}%
+}
+\pytag{ans.602}{%
+a \sin{\left (x \right )} - a \cos{\left (x \right )} + a e^{- x}%
+}
+\pytag{ans.603}{%
+C_{1} \sin{\left (x \right )} + C_{2} \cos{\left (x \right )}%
+}
+\pytag{ans.604}{%
+\sin{\left (x \right )}%
+}
+\pytag{ans.605}{%
+C_{1} e^{- 6 x} + C_{2} e^{x}%
+}
+\pytag{ans.606}{%
+3 e^{x} + 2 e^{- 6 x}%
+}
diff --git a/macros/latex/contrib/hybrid-latex/examples/example-01.tex b/macros/latex/contrib/hybrid-latex/examples/example-01.tex
new file mode 100644
index 0000000000..08919ec095
--- /dev/null
+++ b/macros/latex/contrib/hybrid-latex/examples/example-01.tex
@@ -0,0 +1,267 @@
+\documentclass[12pt]{pylatex}
+\usepackage{examples}
+
+\begin{document}
+
+\section*{Elementary maths}
+
+\vspace{-5pt}
+
+This is a collection of basic mathematical computations using {\tt\small sympy}. The main purpose is to demonstrate the use of {\tt\small\verb|\py|} and {\tt\small\verb|\py*|}. Note that {\tt\small sympy 1.1.1} appears unable to simplify $\tanh(\log(x))$ (compare {\tt\small rhs.108} shown below against {\tt\small ans.108} shown in the \href{../../mathematica/examples/example-01.pdf}{Mathematica} examples). Note also the separate computaions for the left and right hand sides of results 108, 109 and 110.
+
+\vspace{-10pt}
+
+\begin{minipage}[t]{0.65\textwidth}
+\begin{python}
+ from sympy import *
+ x, y, z, a, b, c = symbols('x y z a b c')
+ ans = expand((a+b)**3) # py (ans.101,ans)
+ ans = factor(-2*x+2*x+a*x-x**2+a*x**2-x**3) # py (ans.102,ans)
+ ans = solve(x**2-4, x) # py (ans.103,ans)
+ ans = solve([2*a-b - 3, a+b+c - 1,-b+c - 6],[a,b,c]) # py (ans.104,ans)
+ ans = N(pi,50) # py (ans.105,ans)
+ ans = apart(1/((1 + x)*(5 + x))) # py (ans.106,ans)
+ ans = together((1/(1 + x) - 1/(5 + x))/4) # py (ans.107,ans)
+ ans = simplify(tanh(log(x))) # py (rhs.108,ans)
+ ans = simplify(tanh(I*x)) # py (rhs.109,ans)
+ ans = simplify(sinh(3*x) - 3*sinh(x) - 4*(sinh(x))**3) # py (rhs.110,ans)
+ ans = tanh(log(x)) # py (lhs.108,ans)
+ ans = tanh(UnevaluatedExpr(I*x)) # py (lhs.109,ans)
+ ans = sinh(3*x) - 3*sinh(x) - 4*(sinh(x))**3 # py (lhs.110,ans)
+\end{python}
+\end{minipage}
+\hskip 1cm
+\begin{minipage}[t]{0.35\textwidth}
+\begin{latex}
+ \begin{align*}
+ &\py*{ans.101}\\
+ &\py*{ans.102}\\
+ &\py*{ans.103}\\
+ &\py*{ans.104}\\
+ &\py*{ans.105}\\
+ &\py*{ans.106}\\
+ &\py*{ans.107}\\
+ \py{lhs.108} &= \Py{rhs.108}\\
+ \py{lhs.109} &= \Py{rhs.109}\\
+ \py{lhs.110} &= \Py{rhs.110}
+ \end{align*}
+\end{latex}
+\end{minipage}
+
+\vspace{-10pt}
+
+\begin{align*}
+ &\py*{ans.101}\\
+ &\py*{ans.102}\\
+ &\py*{ans.103}\\
+ &\py*{ans.104}\\
+ &\py*{ans.105}\\
+ &\py*{ans.106}\\
+ &\py*{ans.107}\\
+ \py{lhs.108} &= \Py{rhs.108}\\
+ \py{lhs.109} &= \Py{rhs.109}\\
+ \py{lhs.110} &= \Py{rhs.110}
+\end{align*}
+
+\clearpage
+
+\section*{Linear Algebra}
+
+\vspace{-10pt}
+
+\begin{minipage}[t]{0.65\textwidth}
+\begin{python}
+ from sympy import linsolve
+ lamda = Symbol('lamda')
+ mat = Matrix([[2,3], [5,4]]) # py (ans.201,mat)
+ eig1 = mat.eigenvects()[0][0] # 1st eigenvalue
+ eig2 = mat.eigenvects()[1][0] # 2nd eigenvalue
+ v1 = mat.eigenvects()[0][2][0] # 1st eigenvector
+ v2 = mat.eigenvects()[1][2][0] # 2nd eigenvector
+ eig = simplify(Matrix([eig1,eig2])) # py (ans.202,eig)
+ vec = simplify(5*Matrix([]).col_insert(0,v1)
+ .col_insert(1,v2)) # py (ans.203,vec)
+ det = expand((mat - lamda * eye(2)).det()) # py (ans.204,det)
+ rhs = Matrix([[3],[7]]) # py (ans.205,rhs)
+ ans = list(linsolve((mat,rhs),x,y))[0] # py (ans.206,ans)
+\end{python}
+\end{minipage}
+\hskip 1cm
+\begin{minipage}[t]{0.35\textwidth}
+\begin{latex}
+ \begin{align*}
+ &\py*{ans.201}\\
+ &\py*{ans.202}\\
+ &\py*{ans.203}\\
+ &\py*{ans.204}\\
+ &\py*{ans.205}\\
+ &\py*{ans.206}
+ \end{align*}
+\end{latex}
+\end{minipage}
+
+\begin{align*}
+ &\py*{ans.201}\\
+ &\py*{ans.202}\\
+ &\py*{ans.203}\\
+ &\py*{ans.204}\\
+ &\py*{ans.205}\\
+ &\py*{ans.206}
+\end{align*}
+
+\clearpage
+
+\section*{Limits}
+
+\vspace{-10pt}
+
+\begin{minipage}[t]{0.65\textwidth}
+\begin{python}
+ n, dx = symbols('n dx')
+ ans = limit(sin(4*x)/x,x,0) # py (ans.301,ans)
+ ans = limit(2**x/x,x,oo) # py (ans.302,ans)
+ ans = limit(((x+dx)**2 - x**2)/dx, dx,0) # py (ans.303,ans)
+ ans = limit((4*n + 1)/(3*n - 1),n,oo) # py (ans.304,ans)
+ ans = limit((1+(a/n))**n,n,oo) # py (ans.305,ans)
+\end{python}
+\end{minipage}
+\hskip 1cm
+\begin{minipage}[t]{0.35\textwidth}
+\begin{latex}
+ \begin{align*}
+ &\py*{ans.301}\\
+ &\py*{ans.302}\\
+ &\py*{ans.303}\\
+ &\py*{ans.304}\\
+ &\py*{ans.305}
+ \end{align*}
+\end{latex}
+\end{minipage}
+
+\begin{align*}
+ &\py*{ans.301}\\
+ &\py*{ans.302}\\
+ &\py*{ans.303}\\
+ &\py*{ans.304}\\
+ &\py*{ans.305}
+\end{align*}
+
+\section*{Series}
+
+\vspace{-10pt}
+
+\begin{minipage}[t]{0.65\textwidth}
+\begin{python}
+ ans = series((1 + x)**(-2), x, 1, 6) # py (ans.401,ans)
+ ans = series(exp(x), x, 0, 6) # py (ans.402,ans)
+ ans = Sum(1/n**2, (n,1,50)).doit() # py (ans.403,ans)
+ ans = Sum(1/n**4, (n,1,oo)).doit() # py (ans.404,ans)
+\end{python}
+\end{minipage}
+\hskip 1cm
+\begin{minipage}[t]{0.35\textwidth}
+\begin{latex}
+ \begin{align*}
+ &\py*{ans.401}\\
+ &\py*{ans.402}\\
+ &\py*{ans.403}\\
+ &\py*{ans.404}
+ \end{align*}
+\end{latex}
+\end{minipage}
+
+\begin{align*}
+ &\py*{ans.401}\\
+ &\py*{ans.402}\\
+ &\py*{ans.403}\\
+ &\py*{ans.404}
+\end{align*}
+
+\clearpage
+
+\section*{Calculus}
+
+This example shows how {\tt\small\verb|\Py|} can be used to set the equation tag on the far right hand side.
+
+\begin{minipage}[t]{0.65\textwidth}
+\begin{python}
+ ans = diff(x*sin(x),x) # py (ans.501,ans)
+ ans = diff(x*sin(x),x).subs(x,pi/4) # py (ans.502,ans)
+ ans = integrate(2*sin(x)**2, (x,a,b)) # py (ans.503,ans)
+ ans = Integral(2*exp(-x**2), (x,0,oo)) # py (lhs.504,ans)
+ ans = ans.doit() # py (ans.504,ans)
+ ans = Integral(Integral(x**2 + y**2, (y,0,x)), (x,0,1)) # py (lhs.505,ans)
+ ans = ans.doit() # py (ans.505,ans)
+\end{python}
+\end{minipage}
+\hskip 1cm
+\begin{minipage}[t]{0.35\textwidth}
+\begin{latex}
+ \begin{align*}
+ &\py*{ans.501}\\
+ &\py*{ans.502}\\
+ &\py*{ans.503}\\
+ \py{lhs.504}&=\Py{ans.504}\\
+ \py{lhs.505}&=\Py{ans.505}
+ \end{align*}
+\end{latex}
+\end{minipage}
+
+\begin{align*}
+ &\py*{ans.501}\\
+ &\py*{ans.502}\\
+ &\py*{ans.503}\\
+ \py{lhs.504}&=\Py{ans.504}\\
+ \py{lhs.505}&=\Py{ans.505}
+\end{align*}
+
+\clearpage
+
+\section*{Differential equations}
+
+\vspace{-10pt}
+
+\begin{minipage}[t]{0.70\textwidth}
+\begin{python}
+ y = Function('y')
+ C1, C2 = symbols('C1 C2')
+
+ ode = Eq(y(x).diff(x) + y(x), 2*a*sin(x))
+ sol = expand(dsolve(ode,y(x)).rhs) # py (ans.601,sol)
+ cst = solve([sol.subs(x,0)],dict=True)
+ sol = sol.subs(cst[0]) # py (ans.602,sol)
+
+ ode = Eq(y(x).diff(x,2) + y(x), 0)
+ sol = expand(dsolve(ode,y(x)).rhs) # py (ans.603,sol)
+ cst = solve([sol.subs(x,0),sol.diff(x).subs(x,0)-1],dict=True)
+ sol = sol.subs(cst[0]) # py (ans.604,sol)
+
+ ode = Eq(y(x).diff(x,2) + 5*y(x).diff(x) - 6*y(x), 0)
+ sol = expand(dsolve(ode,y(x)).rhs) # py (ans.605,sol)
+ sol = sol.subs({C1:2,C2:3}) # py (ans.606,sol)
+\end{python}
+\end{minipage}
+\hskip 1cm
+\begin{minipage}[t]{0.30\textwidth}
+\begin{latex}
+ \begin{align*}
+ &\py*{ans.601}\\
+ &\py*{ans.602}\\
+ &\py*{ans.603}\\
+ &\py*{ans.604}\\
+ &\py*{ans.605}\\
+ &\py*{ans.606}
+ \end{align*}
+\end{latex}
+\end{minipage}
+
+\begin{align*}
+ &\py*{ans.601}\\
+ &\py*{ans.602}\\
+ &\py*{ans.603}\\
+ &\py*{ans.604}\\
+ &\py*{ans.605}\\
+ &\py*{ans.606}
+\end{align*}
+
+\end{document}
diff --git a/macros/latex/contrib/hybrid-latex/examples/example-02.pdf b/macros/latex/contrib/hybrid-latex/examples/example-02.pdf
new file mode 100644
index 0000000000..49678e745f
--- /dev/null
+++ b/macros/latex/contrib/hybrid-latex/examples/example-02.pdf
Binary files differ
diff --git a/macros/latex/contrib/hybrid-latex/examples/example-02.pytex b/macros/latex/contrib/hybrid-latex/examples/example-02.pytex
new file mode 100644
index 0000000000..c48f72720e
--- /dev/null
+++ b/macros/latex/contrib/hybrid-latex/examples/example-02.pytex
@@ -0,0 +1,20 @@
+\pytag{CalculusTable}{%
+\frac{d}{d x} \sin{\left (x \right )}&=\cos{\left (x \right )}\quad & \quad%
+\int \sin{\left (x \right )}\, dx&=- \cos{\left (x \right )}\\%
+\frac{d}{d x} \cos{\left (x \right )}&=- \sin{\left (x \right )}\quad & \quad%
+\int \cos{\left (x \right )}\, dx&=\sin{\left (x \right )}\\%
+\frac{d}{d x} \tan{\left (x \right )}&=\tan^{2}{\left (x \right )} + 1\quad & \quad%
+\int \tan{\left (x \right )}\, dx&=- \log{\left (\cos{\left (x \right )} \right )}\\%
+\frac{d}{d x} \operatorname{asin}{\left (x \right )}&=\frac{1}{\sqrt{- x^{2} + 1}}\quad & \quad%
+\int \operatorname{asin}{\left (x \right )}\, dx&=x \operatorname{asin}{\left (x \right )} + \sqrt{- x^{2} + 1}\\[5pt]%
+\frac{d}{d x} \operatorname{acos}{\left (x \right )}&=- \frac{1}{\sqrt{- x^{2} + 1}}\quad & \quad%
+\int \operatorname{acos}{\left (x \right )}\, dx&=x \operatorname{acos}{\left (x \right )} - \sqrt{- x^{2} + 1}\\[5pt]%
+\frac{d}{d x} \operatorname{atan}{\left (x \right )}&=\frac{1}{x^{2} + 1}\quad & \quad%
+\int \operatorname{atan}{\left (x \right )}\, dx&=x \operatorname{atan}{\left (x \right )} - \frac{\log{\left (x^{2} + 1 \right )}}{2}\\[5pt]%
+\frac{d}{d x} \sinh{\left (x \right )}&=\cosh{\left (x \right )}\quad & \quad%
+\int \sinh{\left (x \right )}\, dx&=\cosh{\left (x \right )}\\%
+\frac{d}{d x} \cosh{\left (x \right )}&=\sinh{\left (x \right )}\quad & \quad%
+\int \cosh{\left (x \right )}\, dx&=\sinh{\left (x \right )}\\%
+\frac{d}{d x} \tanh{\left (x \right )}&=- \tanh^{2}{\left (x \right )} + 1\quad & \quad%
+\int \tanh{\left (x \right )}\, dx&=x - \log{\left (\tanh{\left (x \right )} + 1 \right )} %
+}
diff --git a/macros/latex/contrib/hybrid-latex/examples/example-02.tex b/macros/latex/contrib/hybrid-latex/examples/example-02.tex
new file mode 100644
index 0000000000..6bf01d1052
--- /dev/null
+++ b/macros/latex/contrib/hybrid-latex/examples/example-02.tex
@@ -0,0 +1,53 @@
+% based on example 7 in pythontex_gallery
+% https://github.com/gpoore/pythontex/
+
+\documentclass[12pt]{pylatex}
+\usepackage{examples}
+
+\begin{document}
+
+\section*{A table of derivatives and anti-derivatives}
+
+This example is based upon a nice example in the Pythontex gallery, see
+\ \url{https://github.com/gpoore/pythontex/}.
+It uses a tagged block to capture the {\tt\small Sympy} output for later use
+in the body of the LaTeX table.
+
+\lstset{numbers=left}
+
+\begin{minipage}[t]{0.72\textwidth}
+\begin{python}
+ from sympy import *
+
+ var('x')
+
+ # Create a list of functions to include in the table
+ funcs = [['sin(x)',r'\\'], ['cos(x)',r'\\'], ['tan(x)',r'\\'],
+ ['asin(x)',r'\\[5pt]'], ['acos(x)',r'\\[5pt]'], ['atan(x)',r'\\[5pt]'],
+ ['sinh(x)',r'\\'], ['cosh(x)',r'\\'], ['tanh(x)',r' ']]
+
+ # pyBeg (CalculusTable)
+ for func, eol in funcs:
+ myddx = 'Derivative(' + func + ', x)'
+ myint = 'Integral(' + func + ', x)'
+ print(latex(eval(myddx)) + '&=' + latex(eval(myddx + '.doit()')) + r'\quad & \quad')
+ print(latex(eval(myint)) + '&=' + latex(eval(myint + '.doit()')) + eol)
+ # pyEnd (CalculusTable)
+\end{python}
+\end{minipage}
+\hskip 1cm
+\begin{minipage}[t]{0.28\textwidth}
+\begin{latex}
+ \begin{align*}
+ \py {CalculusTable}
+ \end{align*}
+\end{latex}
+\end{minipage}
+
+\clearpage
+
+\begin{align*}
+ \py {CalculusTable}
+\end{align*}
+
+\end{document}
diff --git a/macros/latex/contrib/hybrid-latex/examples/example-03.pdf b/macros/latex/contrib/hybrid-latex/examples/example-03.pdf
new file mode 100644
index 0000000000..58e502141c
--- /dev/null
+++ b/macros/latex/contrib/hybrid-latex/examples/example-03.pdf
Binary files differ
diff --git a/macros/latex/contrib/hybrid-latex/examples/example-03.pytex b/macros/latex/contrib/hybrid-latex/examples/example-03.pytex
new file mode 100644
index 0000000000..1633cdcb18
--- /dev/null
+++ b/macros/latex/contrib/hybrid-latex/examples/example-03.pytex
@@ -0,0 +1,18 @@
+\pytag{lhs.01}{%
+\int_{0}^{4}\int_{0}^{3}\int_{0}^{2} f(x,y,z)\, dx\, dy\, dz%
+}
+\pytag{rhs.01}{%
+\int_{0}^{4}\int_{0}^{3}\int_{0}^{2} \left(x y + y \sin{\left (z \right )} + \cos{\left (x + y \right )}\right)\, dx\, dy\, dz%
+}
+\pytag{rhs.02}{%
+\int_{0}^{4}\int_{0}^{3} \left(2 y \sin{\left (z \right )} + 2 y - \sin{\left (y \right )} + \sin{\left (y + 2 \right )}\right)\, dy\, dz%
+}
+\pytag{rhs.03}{%
+\int_{0}^{4} \left(9 \sin{\left (z \right )} + \cos{\left (3 \right )} + \cos{\left (2 \right )} - \cos{\left (5 \right )} + 8\right)\, dz%
+}
+\pytag{rhs.04}{%
+4 \cos{\left (3 \right )} + 4 \cos{\left (2 \right )} - 4 \cos{\left (5 \right )} - 9 \cos{\left (4 \right )} + 41%
+}
+\pytag{rhs.05}{%
+40.1235865133293%
+}
diff --git a/macros/latex/contrib/hybrid-latex/examples/example-03.tex b/macros/latex/contrib/hybrid-latex/examples/example-03.tex
new file mode 100644
index 0000000000..abb9863d09
--- /dev/null
+++ b/macros/latex/contrib/hybrid-latex/examples/example-03.tex
@@ -0,0 +1,73 @@
+% based on example 8 in pythontex_gallery
+% https://github.com/gpoore/pythontex/
+
+\documentclass[12pt]{pylatex}
+\usepackage{examples}
+
+\begin{document}
+
+\section*{Step-by-step integration}
+
+This is another nice example drawn from the Pythontex gallery, see
+\ \url{https://github.com/gpoore/pythontex}.
+
+It shows the step-by-step computations of a simple triple integral.
+
+\begin{python}
+ from sympy import *
+
+ x, y, z = symbols('x,y,z')
+ f = Symbol('f(x,y,z)')
+
+ # Define limits of integration
+ x_max = 2; y_max = 3; z_max = 4;
+ x_min = 0; y_min = 0; z_min = 0;
+
+ lhs = Integral(f, (x, x_min, x_max),
+ (y, y_min, y_max),
+ (z, z_min, z_max)) # py(lhs.01,lhs)
+
+ f = x*y + y*sin(z) + cos(x+y)
+
+ rhs = Integral(f, (x, x_min, x_max),
+ (y, y_min, y_max),
+ (z, z_min, z_max)) # py(rhs.01,rhs)
+ rhs = Integral(Integral(f, (x, x_min, x_max)).doit(),
+ (y, y_min, y_max),
+ (z, z_min, z_max)) # py(rhs.02,rhs)
+ rhs = Integral(Integral(f, (x, x_min, x_max),
+ (y, y_min, y_max)).doit(),
+ (z, z_min, z_max)) # py(rhs.03,rhs)
+ rhs = Integral(f, (x, x_min, x_max),
+ (y, y_min, y_max),
+ (z, z_min, z_max)).doit() # py(rhs.04,rhs)
+
+ # And now, a numerical approximation
+ rhs = N(rhs) # py(rhs.05,rhs)
+
+\end{python}
+
+\begin{minipage}[t]{0.65\textwidth}
+\begin{align*}
+ \py{lhs.01} &= \py{rhs.01}\\
+ &= \py{rhs.02}\\
+ &= \py{rhs.03}\\
+ &= \py{rhs.04}\\[8pt]
+ &\approx \py{rhs.05}
+\end{align*}
+\end{minipage}
+\hskip 1cm
+\lower16pt\hbox{%
+\begin{minipage}[t]{0.35\textwidth}
+\begin{latex}
+ \begin{align*}
+ \py{lhs.01} &= \py{rhs.01}\\
+ &= \py{rhs.02}\\
+ &= \py{rhs.03}\\
+ &= \py{rhs.04}\\[8pt]
+ &\approx \py{rhs.05}
+ \end{align*}
+\end{latex}
+\end{minipage}}
+
+\end{document}
diff --git a/macros/latex/contrib/hybrid-latex/examples/example-04-fig.pdf b/macros/latex/contrib/hybrid-latex/examples/example-04-fig.pdf
new file mode 100644
index 0000000000..4cea66ebff
--- /dev/null
+++ b/macros/latex/contrib/hybrid-latex/examples/example-04-fig.pdf
Binary files differ
diff --git a/macros/latex/contrib/hybrid-latex/examples/example-04.pdf b/macros/latex/contrib/hybrid-latex/examples/example-04.pdf
new file mode 100644
index 0000000000..efb57d535c
--- /dev/null
+++ b/macros/latex/contrib/hybrid-latex/examples/example-04.pdf
Binary files differ
diff --git a/macros/latex/contrib/hybrid-latex/examples/example-04.pytex b/macros/latex/contrib/hybrid-latex/examples/example-04.pytex
new file mode 100644
index 0000000000..ecee7c0809
--- /dev/null
+++ b/macros/latex/contrib/hybrid-latex/examples/example-04.pytex
@@ -0,0 +1 @@
+% no Python output \ No newline at end of file
diff --git a/macros/latex/contrib/hybrid-latex/examples/example-04.tex b/macros/latex/contrib/hybrid-latex/examples/example-04.tex
new file mode 100644
index 0000000000..71e437b9fd
--- /dev/null
+++ b/macros/latex/contrib/hybrid-latex/examples/example-04.tex
@@ -0,0 +1,119 @@
+\documentclass[12pt]{pylatex}
+\usepackage{examples}
+\usepackage{caption}
+\usepackage{pgfplots}
+
+\begin{document}
+
+\section*{Plotting Bessel functions}
+
+\vspace{-5pt}
+
+This simple example uses {\tt\small numpy, scipy} and {\tt\small Matplotlib} to produce a plot of the first six Bessel functions. Two plots are shown, one created by {\tt\small Matplotlib} and a second created by LaTeX using the plotting package {\tt\small pgfplots} and the data exported from {\tt\small Matplotlib}.
+
+If you are using macOS, you may need to use the {\tt\small -Ppythonw} option when running {\tt\small pylatex.sh}. This is a known problem with macOS and {\tt\small Matplotlib}, see\ \url{https://matplotlib.org/faq/osx_framework.html}.
+
+\vspace{-10pt}
+
+\begin{minipage}[t]{0.60\textwidth}
+\begin{python}
+ import numpy as np
+ import scipy.special as sp
+ import matplotlib.pyplot as plt
+
+ plt.matplotlib.rc('text', usetex = True)
+ plt.matplotlib.rc('grid', linestyle = 'dotted')
+ plt.matplotlib.rc('figure', figsize = (6.4,4.8)) # (width,height) inches
+
+ x = np.linspace(0, 15, 500)
+
+ for v in range(0, 6):
+ plt.plot(x, sp.jv(v, x))
+
+ plt.xlim((0, 15))
+ plt.ylim((-0.5, 1.1))
+ plt.legend(('${J}_0(x)$', '${J}_1(x)$', '${J}_2(x)$',
+ '${J}_3(x)$', '${J}_4(x)$', '${J}_5(x)$'), loc = 0)
+ plt.xlabel('$x$')
+ plt.ylabel('${J}_n(x)$')
+ plt.grid(True)
+ plt.tight_layout(0.5)
+
+ plt.savefig('example-04-fig.pdf')
+
+ # save the data for later use by pgfplots
+ np.savetxt('example-04.txt',list(zip(x,sp.jv(0,x),sp.jv(1,x),sp.jv(2,x),
+ sp.jv(3,x),sp.jv(4,x),sp.jv(5,x))),
+ fmt="% .10e")
+\end{python}
+\end{minipage}
+\hskip 0.25cm
+\begin{minipage}[t]{0.35\textwidth}
+\begin{latex}
+ \begin{minipage}{\textwidth}
+ \centering
+ \IfFileExists{example-04-fig.pdf}%
+ {\includegraphics[width=6.4in]
+ {example-04-fig.pdf}}{Failed to create pdf plot.}
+ \captionof{figure}{The first six Bessel functions.}
+ \end{minipage}
+\end{latex}
+\end{minipage}
+
+\begin{minipage}{\textwidth}
+ \centering
+ \IfFileExists{example-04-fig.pdf}%
+ {\includegraphics[width=6.4in]
+ {example-04-fig.pdf}}{Failed to create pdf plot.}
+ \captionof{figure}{The first six Bessel functions.}
+\end{minipage}
+
+\clearpage
+
+\pgfplotsset{compat=newest}
+\pgfplotsset{width=0.45\textwidth,height=0.34\textwidth}
+
+\subsection*{Using pgfplots}
+
+\begin{minipage}[t]{\textwidth}
+ \centering
+ \begin{tikzpicture}
+ \begin{axis}
+ [xmin= 0.0, xmax=15.0,
+ ymin=-0.45, ymax=1.05,
+ xlabel=$x$, ylabel=$J_n(x)$,
+ grid=major, grid style={dashed,gray!30},
+ legend entries = {$J_0$, $J_1$, $J_2$, $J_3$, $J_4$, $J_5$}]
+ \addplot[blue] table [x index=0, y index=1]{example-04.txt};
+ \addplot[red] table [x index=0, y index=2]{example-04.txt};
+ \addplot[green] table [x index=0, y index=3]{example-04.txt};
+ \addplot[teal] table [x index=0, y index=4]{example-04.txt};
+ \addplot[orange] table [x index=0, y index=5]{example-04.txt};
+ \addplot[purple] table [x index=0, y index=6]{example-04.txt};
+ \end{axis}
+ \end{tikzpicture}
+ \captionof{figure}{The first six Bessel functions.}
+\end{minipage}
+
+\vfill
+
+\begin{latex}
+ \begin{tikzpicture} % requires \usepackage{pgfplots}
+ \begin{axis}
+ [xmin= 0.0, xmax=15.0,
+ ymin=-0.45, ymax=1.05,
+ xlabel=$x$, ylabel=$J_n(x)$,
+ grid=major, grid style={dashed,gray!30},
+ legend entries = {$J_0$, $J_1$, $J_2$, $J_3$, $J_4$, $J_5$}]
+ \addplot[blue] table [x index=0, y index=1]{example-04.txt};
+ \addplot[red] table [x index=0, y index=2]{example-04.txt};
+ \addplot[green] table [x index=0, y index=3]{example-04.txt};
+ \addplot[teal] table [x index=0, y index=4]{example-04.txt};
+ \addplot[orange] table [x index=0, y index=5]{example-04.txt};
+ \addplot[purple] table [x index=0, y index=6]{example-04.txt};
+ \end{axis}
+ \end{tikzpicture}
+ \captionof{figure}{The first six Bessel functions.} % requires \usepackage{caption}
+\end{latex}
+
+\end{document}
diff --git a/macros/latex/contrib/hybrid-latex/examples/example-05.pdf b/macros/latex/contrib/hybrid-latex/examples/example-05.pdf
new file mode 100644
index 0000000000..c5836ce869
--- /dev/null
+++ b/macros/latex/contrib/hybrid-latex/examples/example-05.pdf
Binary files differ
diff --git a/macros/latex/contrib/hybrid-latex/examples/example-05.pytex b/macros/latex/contrib/hybrid-latex/examples/example-05.pytex
new file mode 100644
index 0000000000..50d0cef9d6
--- /dev/null
+++ b/macros/latex/contrib/hybrid-latex/examples/example-05.pytex
@@ -0,0 +1,12 @@
+\pytag{ans.511}{%
+\frac{1}{x + 1}%
+}
+\pytag{ans.512}{%
+1 - x + x^{2} - x^{3} + x^{4} - x^{5} + x^{6} - x^{7} + x^{8} - x^{9} + O\left(x^{10}\right)%
+}
+\pytag{ans.513}{%
+1 - x + x^{2} - x^{3} + x^{4} - x^{5} + x^{6} - x^{7} + x^{8} - x^{9} + x^{10} - x^{11} + x^{12} - x^{13} + x^{14} - x^{15} + x^{16} - x^{17} + x^{18} - x^{19} + O\left(x^{20}\right)%
+}
+\pytag{ans.514}{%
+1 - x + x^{2} - x^{3} + x^{4} - x^{5} + x^{6} - x^{7} + x^{8} - x^{9} + x^{10} - x^{11} + x^{12} - x^{13} + x^{14} - x^{15} + x^{16} - x^{17} + x^{18} - x^{19} + x^{20} - x^{21} + x^{22} + O\left(x^{23}\right)%
+}
diff --git a/macros/latex/contrib/hybrid-latex/examples/example-05.tex b/macros/latex/contrib/hybrid-latex/examples/example-05.tex
new file mode 100644
index 0000000000..dfcb060f52
--- /dev/null
+++ b/macros/latex/contrib/hybrid-latex/examples/example-05.tex
@@ -0,0 +1,51 @@
+\documentclass[12pt]{pylatex}
+\usepackage{examples}
+
+\begin{document}
+
+\section*{Displaying long expressions}
+
+This example uses a simple (though contrived) example of a Taylor series expansion of $1/(1+x)$ to demonstrate the problems that can arise when displaying very long expressions.
+
+% \vspace{12pt}
+
+\begin{minipage}[t]{0.50\textwidth}
+\begin{python}
+ from sympy import *
+ x = Symbol('x')
+ ans = 1/(1+x) # py (ans.511,ans)
+ taylor = ans.series(x, 0, 10) # py (ans.512,taylor)
+ taylor = ans.series(x, 0, 20) # py (ans.513,taylor)
+ taylor = ans.series(x, 0, 23) # py (ans.514,taylor)
+\end{python}
+\end{minipage}
+\hskip 0.5cm
+\begin{minipage}[t]{0.50\textwidth}
+\begin{latex}
+ \begin{dgroup*}[spread={5pt}]
+ \begin{dmath*} f(x) = \Py*{ans.511} \end{dmath*}
+ \begin{dmath*} {}= \Py*{ans.512} \end{dmath*}
+ \begin{dmath*} {}= \Py*{ans.513} \end{dmath*}
+ \begin{dmath*} {}= \Py*{ans.514} \end{dmath*}
+ \begin{dmath*} {}= \Py*[\hskip 2cm]{ans.514} \end{dmath*}
+ \end{dgroup*}
+\end{latex}
+\end{minipage}
+
+\vspace{18pt}
+
+The first four lines of the following output were set using {\tt\small\verb|\Py*|}
+while the final line used {\tt\small\verb|\Py*[\hskip=2cm]|}. The last pair of lines displays
+the output for the same tag {\tt\small ans.514} and clearly the formatting of the second
+last line is not ideal as the text has overlapped the tag. This was corrected in the final
+line by using the optional argument {\tt\small\verb|[\hskip=2cm]|} in the call to {\tt\small\verb|\Py*|}.
+
+\begin{dgroup*}[spread={5pt}]
+ \begin{dmath*} f(x) = \Py*{ans.511} \end{dmath*}
+ \begin{dmath*} {}= \Py*{ans.512} \end{dmath*}
+ \begin{dmath*} {}= \Py*{ans.513} \end{dmath*}
+ \begin{dmath*} {}= \Py*{ans.514} \end{dmath*}
+ \begin{dmath*} {}= \Py*[\hskip 2cm]{ans.514} \end{dmath*}
+\end{dgroup*}
+
+\end{document}
diff --git a/macros/latex/contrib/hybrid-latex/examples/example-06.pdf b/macros/latex/contrib/hybrid-latex/examples/example-06.pdf
new file mode 100644
index 0000000000..8c94c5ebcd
--- /dev/null
+++ b/macros/latex/contrib/hybrid-latex/examples/example-06.pdf
Binary files differ
diff --git a/macros/latex/contrib/hybrid-latex/examples/example-06.pytex b/macros/latex/contrib/hybrid-latex/examples/example-06.pytex
new file mode 100644
index 0000000000..66a9c8eaa6
--- /dev/null
+++ b/macros/latex/contrib/hybrid-latex/examples/example-06.pytex
@@ -0,0 +1,9 @@
+\pytag{table}{%
+\RuleA 0 & 0.5000000000000000000000000 & -1.0653065971e-1 &\\%
+\RuleA 1 & 0.5663110031972181530416492 & -1.3045098060e-3 & -0.11495\\%
+\RuleA 2 & 0.5671431650348622127865121 & -1.9648047172e-7 & -0.11546\\%
+\RuleA 3 & 0.5671432904097810286995766 & -4.4574262753e-15 & -0.11546\\%
+\RuleA 4 & 0.5671432904097838729999687 & -2.2941072910e-30 & -0.11546\\%
+\RuleA 5 & 0.5671432904097838729999687 & -6.0767705445e-61 & -0.11546\\%
+\RuleA 6 & 0.5671432904097838729999687 & -4.2637434326e-122 & -0.11546\\%
+}
diff --git a/macros/latex/contrib/hybrid-latex/examples/example-06.tex b/macros/latex/contrib/hybrid-latex/examples/example-06.tex
new file mode 100644
index 0000000000..27b5d2cf3b
--- /dev/null
+++ b/macros/latex/contrib/hybrid-latex/examples/example-06.tex
@@ -0,0 +1,89 @@
+\documentclass[12pt]{pylatex}
+\usepackage{examples}
+
+\begin{document}
+
+\section*{Quadratic convergence of Newton-Raphson iterations}
+
+This is a simple example that uses Python and {\tt\small sympy} to demonstrate the quadratic converegnce of Newton-Raphson iterations to the exact root of a non-linear equation.
+
+\vspace{10pt}
+
+\begin{python}
+ from sympy import *
+
+ x = Symbol('x')
+
+ f = Lambda (x,x-exp(-x))
+ df = Lambda (x,diff(f(x),x))
+ Step = Lambda (x,x-f(x)/df(x))
+
+ Digits = 200 # use 200 decimal digits for all numerical computations
+
+ x_new = Float('0.5',Digits)
+ f_new = N (f(x_new),Digits)
+
+ # pyBeg (table)
+
+ print ('\RuleA {:2d} & {: .25f} & {: .10e} &\\\\'.format(0,x_new,f_new))
+
+ for n in range (1,7):
+ x_old = x_new
+ x_new = N (Step(x_new),Digits)
+ f_old = N (f(x_old),Digits)
+ f_new = N (f(x_new),Digits)
+ ratio = N (f_new / f_old**2,Digits)
+ print ('\RuleA {:2d} & {: .25f} & {: .10e} & {: .5f}\\\\'.format(n,x_new,f_new,ratio))
+
+ # pyEnd (table)
+\end{python}
+
+\clearpage
+
+Note the clear quadratic convergence in the iterations -- the last column settles to approximately $-0.11546$ independent of the number of iterations. This behaviour would not be seen using normal floating point computations as they are normally limited to no more than 18 decimal digits. This computation used 200 decimal digits.
+
+\def\RuleA{\vrule depth0pt width0pt height14pt}
+\def\RuleB{\vrule depth8pt width0pt height14pt}
+\def\RuleC{\vrule depth10pt width0pt height16pt}
+
+\setlength{\tabcolsep}{0.025\textwidth}%
+
+\begin{center}
+ \begin{tabular}{cccc}%
+ \noalign{\hrule height 1pt}
+ \multicolumn{4}{c}{\RuleC\rmfamily\bfseries%
+ Newton-Raphson iterations \quad%
+ $x_{n+1} = x_n - f_n/f'_n\ ,\quad f(x) = x-e^{-x}$}\\
+ \noalign{\hrule height 1pt}
+ \RuleB$ n$&$ x_n$&$ \epsilon_{n} = x_{n} - e^{-x_{n}}$&$\epsilon_{n}/\epsilon_{n-1}^2$\\
+ \noalign{\hrule height 0.5pt}
+ \py{table}
+ \noalign{\hrule height 1pt}
+ \end{tabular}
+\end{center}
+
+\vspace{20pt}
+
+\begin{latex}
+ \def\RuleA{\vrule depth0pt width0pt height14pt}
+ \def\RuleB{\vrule depth8pt width0pt height14pt}
+ \def\RuleC{\vrule depth10pt width0pt height16pt}
+
+ \setlength{\tabcolsep}{0.025\textwidth}%
+
+ \begin{center}
+ \begin{tabular}{cccc}%
+ \noalign{\hrule height 1pt}
+ \multicolumn{4}{c}{\RuleC\rmfamily\bfseries%
+ Newton-Raphson iterations \quad%
+ $x_{n+1} = x_n - f_n/f'_n\ ,\quad f(x) = x-e^{-x}$}\\
+ \noalign{\hrule height 1pt}
+ \RuleB$ n$&$ x_n$&$ \epsilon_{n} = x_{n} - e^{-x_{n}}$&$\epsilon_{n}/\epsilon_{n-1}^2$\\
+ \noalign{\hrule height 0.5pt}
+ \py{table}
+ \noalign{\hrule height 1pt}
+ \end{tabular}
+ \end{center}
+\end{latex}
+
+\end{document}
diff --git a/macros/latex/contrib/hybrid-latex/examples/example-07.pdf b/macros/latex/contrib/hybrid-latex/examples/example-07.pdf
new file mode 100644
index 0000000000..6d4fad78c0
--- /dev/null
+++ b/macros/latex/contrib/hybrid-latex/examples/example-07.pdf
Binary files differ
diff --git a/macros/latex/contrib/hybrid-latex/examples/example-07.pytex b/macros/latex/contrib/hybrid-latex/examples/example-07.pytex
new file mode 100644
index 0000000000..fc0464a831
--- /dev/null
+++ b/macros/latex/contrib/hybrid-latex/examples/example-07.pytex
@@ -0,0 +1,9 @@
+\pytag{info}{%
+date : &Mon 27 Aug 2018 10:37:23\\%
+python : &2.7.15\\%
+system : &Darwin\\%
+release : &17.7.0\\%
+machine : &x86_64\\%
+processor : &i386\\%
+platform : &Darwin-17.7.0-x86_64-i386-64bit%
+}
diff --git a/macros/latex/contrib/hybrid-latex/examples/example-07.tex b/macros/latex/contrib/hybrid-latex/examples/example-07.tex
new file mode 100644
index 0000000000..8c26db4ef8
--- /dev/null
+++ b/macros/latex/contrib/hybrid-latex/examples/example-07.tex
@@ -0,0 +1,45 @@
+\documentclass[12pt]{pylatex}
+\usepackage{examples}
+
+\begin{document}
+
+\section*{Using tagged blocks}
+
+The following Python code block contains a matched {\tt\small\verb|pyBeg/pyEnd|} pair, with the tag name {\tt\small info}, to capture the output from the formatted Python {\tt\small print} statements.
+
+\begin{minipage}[t]{0.75\textwidth}
+\begin{python}
+ import platform, datetime
+ # pyBeg(info)
+ print("date : &"+'{:%a %d %b %Y %H:%M:%S}'.format(datetime.datetime.now())+"\\\\")
+ print("python : &"+str(platform.python_version())+"\\\\")
+ print("system : &"+str(platform.system())+"\\\\")
+ print("release : &"+str(platform.release())+"\\\\")
+ print("machine : &"+str(platform.machine())+"\\\\")
+ print("processor : &"+str(platform.processor())+"\\\\")
+ print("platform : &"+str(platform.platform()))
+ # pyEnd(info)
+\end{python}
+\end{minipage}
+\hskip 1cm
+\begin{minipage}[t]{0.25\textwidth}
+\begin{latex}
+ \bgroup\tt
+ \begin{tabular}{rl}
+ \py{info}
+ \end{tabular}
+ \egroup
+\end{latex}
+\end{minipage}
+
+\vspace{12pt}
+
+Here is the output caught from the above block.
+
+\bgroup\tt
+\begin{tabular}{rl}
+ \py{info}
+\end{tabular}
+\egroup
+
+\end{document}
diff --git a/macros/latex/contrib/hybrid-latex/examples/example-08.pdf b/macros/latex/contrib/hybrid-latex/examples/example-08.pdf
new file mode 100644
index 0000000000..2b6c902233
--- /dev/null
+++ b/macros/latex/contrib/hybrid-latex/examples/example-08.pdf
Binary files differ
diff --git a/macros/latex/contrib/hybrid-latex/examples/example-08.pytex b/macros/latex/contrib/hybrid-latex/examples/example-08.pytex
new file mode 100644
index 0000000000..c3ac7a8a6b
--- /dev/null
+++ b/macros/latex/contrib/hybrid-latex/examples/example-08.pytex
@@ -0,0 +1,78 @@
+\pytag{ans.101}{%
+- \frac{1}{6}%
+}
+\pytag{ans.102}{%
+1 + \frac{x^{2}}{2} + \frac{5 x^{4}}{24} + O\left(x^{6}\right)%
+}
+\pytag{ans.103}{%
+- \frac{4 x \sin{\left (x^{2} \right )} \cos{\left (x^{2} \right )}}{x + 1} - \frac{\cos^{2}{\left (x^{2} \right )}}{\left(x + 1\right)^{2}}%
+}
+\pytag{ans.104}{%
+x^{2} \sin{\left (x \right )} + 2 x \cos{\left (x \right )} - 2 \sin{\left (x \right )}%
+}
+\pytag{ans.105}{%
+-2 + \frac{\pi^{2}}{4}%
+}
+\pytag{ans.106}{%
+9 f{\left (x \right )} + \frac{d^{2}}{d x^{2}} f{\left (x \right )} = 1%
+}
+\pytag{ans.107}{%
+f{\left (x \right )} = C_{1} \sin{\left (3 x \right )} + C_{2} \cos{\left (3 x \right )} + \frac{1}{9}%
+}
+\pytag{ans.108}{%
+9 f{\left (x \right )} + \frac{d^{2}}{d x^{2}} f{\left (x \right )} = 1%
+}
+\pytag{ans.109}{%
+f{\left (x \right )} = C_{1} \sin{\left (3 x \right )} + C_{2} \cos{\left (3 x \right )} + \frac{1}{9}%
+}
+\pytag{ans.110}{%
+\int_{0}^{2} \cos{\left (x \right )}\, dx%
+}
+\pytag{ans.111}{%
+\sin{\left (2 \right )}%
+}
+\pytag{ans.201}{%
+- \frac{1}{6}%
+}
+\pytag{ans.202}{%
+1 + \frac{x^{2}}{2} + \frac{5 x^{4}}{24} + O\left(x^{6}\right)%
+}
+\pytag{ans.203}{%
+- \frac{4 x \sin{\left (x^{2} \right )} \cos{\left (x^{2} \right )}}{x + 1} - \frac{\cos^{2}{\left (x^{2} \right )}}{\left(x + 1\right)^{2}}%
+}
+\pytag{ans.204}{%
+x^{2} \sin{\left (x \right )} + 2 x \cos{\left (x \right )} - 2 \sin{\left (x \right )}%
+}
+\pytag{ans.205}{%
+-2 + \frac{\pi^{2}}{4}%
+}
+\pytag{ans.206}{%
+9 f{\left (x \right )} + \frac{d^{2}}{d x^{2}} f{\left (x \right )} = 1%
+}
+\pytag{ans.207}{%
+f{\left (x \right )} = C_{1} \sin{\left (3 x \right )} + C_{2} \cos{\left (3 x \right )} + \frac{1}{9}%
+}
+\pytag{ans.208}{%
+9 f{\left (x \right )} + \frac{d^{2}}{d x^{2}} f{\left (x \right )} = 1%
+}
+\pytag{ans.209}{%
+f{\left (x \right )} = C_{1} \sin{\left (3 x \right )} + C_{2} \cos{\left (3 x \right )} + \frac{1}{9}%
+}
+\pytag{ans.210}{%
+\int_{0}^{2} \cos{\left (x \right )}\, dx%
+}
+\pytag{ans.211}{%
+\sin{\left (2 \right )}%
+}
+\pytag{ans.301}{%
+- 2 \sin{\left (x \right )} \cos{\left (x \right )}%
+}
+\pytag{ans.302}{%
+- e^{- x} \sin{\left (x \right )} - e^{- x} \cos{\left (x \right )}%
+}
+\pytag{ans.401}{%
+- 2 \sin{\left (x \right )} \cos{\left (x \right )}%
+}
+\pytag{ans.402}{%
+- e^{- x} \sin{\left (x \right )} - e^{- x} \cos{\left (x \right )}%
+}
diff --git a/macros/latex/contrib/hybrid-latex/examples/example-08.tex b/macros/latex/contrib/hybrid-latex/examples/example-08.tex
new file mode 100644
index 0000000000..86043fcc60
--- /dev/null
+++ b/macros/latex/contrib/hybrid-latex/examples/example-08.tex
@@ -0,0 +1,215 @@
+% based on
+% https://github.com/sympy/sympy/wiki/Quick-examples
+
+\documentclass[12pt]{pylatex}
+\usepackage{examples}
+
+\begin{document}
+
+\section*{Visible Python code}
+
+This is the first of two-part example. In this first part the Python code and tags are made visible using {\tt\small\verb|\PySetup{action=show}|} (this is the default). In the second part the Python code will be hidden using {\tt\small\verb|\PySetup{action=hide}|}. In both cases the Python output is identical (apart from the different tag names).
+
+\vspace{12pt}
+
+\PySetup{action=show}
+
+\begin{python}
+ from sympy import *
+
+ x, y = symbols('x y')
+
+ ans = limit((sin(x)-x)/x**3, x, 0) # py(ans.101,ans)
+ ans = (1/cos(x)).series(x, 0, 6) # py(ans.102,ans)
+ ans = diff(cos(x**2)**2 / (1+x), x) # py(ans.103,ans)
+ ans = integrate(x**2 * cos(x), x) # py(ans.104,ans)
+ ans = integrate(x**2 * cos(x), (x, 0, pi/2)) # py(ans.105,ans)
+
+ f = Function('f')
+
+ eqn = Eq(Derivative(f(x),x,x) + 9*f(x), 1) # py(ans.106,eqn)
+ sol = dsolve(eqn, f(x)) # py(ans.107,sol)
+
+ eqn = Eq(f(x).diff(x, x) + 9*f(x), 1) # py(ans.108,eqn)
+ sol = dsolve(eqn, f(x)) # py(ans.109,sol)
+
+ my_int = Integral(cos(x),(x,0,2)) # py(ans.110,my_int)
+ my_ans = my_int.doit() # py(ans.111,my_ans)
+\end{python}
+
+\clearpage
+
+\section*{Visible Python code output}
+
+Here is the output from the above Python code. This line of text is here for no other reason than to fill out the line so that we have two lines of text before the Python output. This makes it easier to compare the vertical spacing between this and the next example.
+%
+\begin{align*}
+ &\py*{ans.101}\\
+ &\py*{ans.102}\\
+ &\py*{ans.103}\\
+ &\py*{ans.104}\\
+ &\py*{ans.105}\\
+ &\py*{ans.106}\\
+ &\py*{ans.107}\\
+ &\py*{ans.108}\\
+ &\py*{ans.109}\\
+ &\py*{ans.110}\\
+ &\py*{ans.111}
+\end{align*}
+
+\clearpage
+
+\section*{Hidden Python code and output}
+
+\PySetup{action=hide}
+
+In this example the Python code is hidden and consumes no vertical space on the page. Compare the gap between this pair of lines and the following output text against that seen on the previous page.
+%
+\begin{python}
+ from sympy import *
+
+ x, y = symbols('x y')
+
+ ans = limit((sin(x)-x)/x**3, x, 0) # py(ans.201,ans)
+ ans = (1/cos(x)).series(x, 0, 6) # py(ans.202,ans)
+ ans = diff(cos(x**2)**2 / (1+x), x) # py(ans.203,ans)
+ ans = integrate(x**2 * cos(x), x) # py(ans.204,ans)
+ ans = integrate(x**2 * cos(x), (x, 0, pi/2)) # py(ans.205,ans)
+
+ f = Function('f')
+
+ eqn = Eq(Derivative(f(x),x,x) + 9*f(x), 1) # py(ans.206,eqn)
+ sol = dsolve(eqn, f(x)) # py(ans.207,sol)
+
+ eqn = Eq(f(x).diff(x, x) + 9*f(x), 1) # py(ans.208,eqn)
+ sol = dsolve(eqn, f(x)) # py(ans.209,sol)
+
+ my_int = Integral(cos(x),(x,0,2)) # py(ans.210,my_int)
+ my_ans = my_int.doit() # py(ans.211,my_ans)
+\end{python}
+%
+\begin{align*}
+ &\py*{ans.201}\\
+ &\py*{ans.202}\\
+ &\py*{ans.203}\\
+ &\py*{ans.204}\\
+ &\py*{ans.205}\\
+ &\py*{ans.206}\\
+ &\py*{ans.207}\\
+ &\py*{ans.208}\\
+ &\py*{ans.209}\\
+ &\py*{ans.210}\\
+ &\py*{ans.211}
+\end{align*}
+
+\clearpage
+
+\section*{Visible Python markup}
+
+There is nothing special in this particular example. Its purpose is to provide a constart with the following example where the tags have been hidden from view.
+
+\vspace{5pt}
+
+\PySetup{action=show}
+
+\begin{python}
+ from sympy import *
+
+ x = Symbol('x')
+
+ ans = diff(cos(x)**2, x) # py(ans.301,ans)
+ ans = integrate(2*sin(x)*exp(-x), x) # py(ans.302,ans)
+\end{python}
+
+\begin{align*}
+ &\py*{ans.301}\\
+ &\py*{ans.302}
+\end{align*}
+
+\section*{Hidden Python markup}
+This example creates the impression that there are no tags in the following block. But looks can be deceptive, the truth is revealed on the following page.
+
+\vspace{5pt}
+
+\PySetup{action=hide}
+
+\begin{python}
+ from sympy import *
+
+ x = Symbol('x')
+
+ ans = diff(cos(x)**2, x) # py(ans.401,ans)
+ ans = integrate(2*sin(x)*exp(-x), x) # py(ans.402,ans)
+\end{python}
+
+\PySetup{action=verbatim}
+
+\begin{python}
+ from sympy import *
+
+ x = Symbol('x')
+
+ ans = diff(cos(x)**2, x)
+ ans = integrate(sin(x)*exp(-x), x)
+\end{python}
+
+\begin{align*}
+ &\py*{ans.401}\\
+ &\py*{ans.402}
+\end{align*}
+
+\clearpage
+
+\section*{How to hide the tags}
+The easiest way to hide the tags from view is to include two blocks, one containing the tags but hidden from view using {\tt\small\verb|\PySetup{action=hide}|}, the other block is a copy of the first but with all tags stripped out. That second block is made visible using {\tt\small\verb|\PySetup{action=verbatim}|}. This is a bit cumbersome and prone to errors (the second block must be a faithful copy of the first block). Better solutions could be devised but they all appear to require significant changes to the current preprocessors -- so much so that the this cloning trick was deemed a reasonable compromise.
+
+Here is the LaTeX code from the previous example.
+
+\vspace{-5pt}
+
+\begin{minipage}[t]{0.65\textwidth}
+\bgcolour{white}
+\latexstyle
+\begin{latex}
+* \PySetup{action=hide}
+*
+* \begin{python}
+\end{latex}
+\pythonstyle
+\begin{python}
+* from sympy import *
+*
+* x = Symbol('x')
+*
+* ans = diff(cos(x)**2, x) # py(ans.401,ans)
+* ans = integrate(2*sin(x)*exp(-x), x) # py(ans.402,ans)
+\end{python}
+\latexstyle
+\begin{latex}
+* \end{python}
+*
+* \PySetup{action=verbatim}
+*
+* \begin{python}
+\end{latex}
+\pythonstyle
+\begin{python}
+* from sympy import *
+*
+* x = Symbol('x')
+*
+* ans = diff(cos(x)**2, x)
+* ans = integrate(sin(x)*exp(-x), x)
+\end{python}
+\latexstyle
+\begin{latex}
+* \end{python}
+*
+* \begin{align*}
+* &\py*{ans.401}\\
+* &\py*{ans.402}
+* \end{align*}
+\end{latex}
+\end{minipage}
+
+\end{document}
diff --git a/macros/latex/contrib/hybrid-latex/examples/example-09.pdf b/macros/latex/contrib/hybrid-latex/examples/example-09.pdf
new file mode 100644
index 0000000000..99c3e7c4b1
--- /dev/null
+++ b/macros/latex/contrib/hybrid-latex/examples/example-09.pdf
Binary files differ
diff --git a/macros/latex/contrib/hybrid-latex/examples/example-09.pytex b/macros/latex/contrib/hybrid-latex/examples/example-09.pytex
new file mode 100644
index 0000000000..2caa3d6a73
--- /dev/null
+++ b/macros/latex/contrib/hybrid-latex/examples/example-09.pytex
@@ -0,0 +1,6 @@
+\pytag{width}{%
+17.0%
+}
+\pytag{height}{%
+13.5%
+}
diff --git a/macros/latex/contrib/hybrid-latex/examples/example-09.tex b/macros/latex/contrib/hybrid-latex/examples/example-09.tex
new file mode 100644
index 0000000000..eb56b3a66c
--- /dev/null
+++ b/macros/latex/contrib/hybrid-latex/examples/example-09.tex
@@ -0,0 +1,142 @@
+\documentclass[12pt]{pylatex}
+\usepackage{examples}
+\usepackage{caption}
+\usepackage{graphicx}
+
+\makeatletter
+\def\cm#1{\strip@pt\dimexpr #1*65536/\number\dimexpr1cm}
+\def\inches#1{\strip@pt\dimexpr #1*65536/\number\dimexpr1in}
+
+\begin{document}
+
+\section*{Passing LaTeX data to Python}
+
+There are occassions where the active Python code may require information from the LaTeX source. Since Python has no direct access to LaTeX some other means must be provided to build this bridge. The idea presented here follows a standard LaTeX pattern -- run LaTeX twice, once before Python, leaving breadcrumbs for Python to pickup during its run, then a final LaTeX run to complete the job.
+
+The examaple shown here is a simple proof of concept. It shows how the dimensions of plot can be specified in the LaTeX source and then accessed later by Python.
+
+To compile this example, use
+
+\begin{lstlisting}
+ pdflatex example-09
+ pylatex.sh -i example-09
+\end{lstlisting}
+
+The idea is to use LaTeX to create a Python dictionary saved as a {\tt\small .json} file. The dictionary will contain just two entries, one for the height and one for the width of the plot. Here are the relevant lines of LaTeX.
+
+\vspace{10pt}
+
+\begin{latex}
+ \newdimen\mywidth\mywidth=17cm % target width
+ \newdimen\myheight\myheight=13.5cm % target height
+
+ \newwrite\breadcrumbs
+ \immediate\openout\breadcrumbs=\jobname.json % create Json file
+ \immediate\write\breadcrumbs{\writebgroup} % {
+ \immediate\write\breadcrumbs{"height":\inches{\myheight},} % "height":5.31496,
+ \immediate\write\breadcrumbs{"width":\inches{\mywidth}} % "width":6.69292
+ \immediate\write\breadcrumbs{\writeegroup} % }
+ \immediate\closeout\breadcrumbs % close the file
+\end{latex}
+
+% https://tex.stackexchange.com/questions/47452/writing-and-to-a-file-with-latex
+% macros to write { and }
+\begingroup
+\catcode`<=1 \catcode`>=2 \catcode`{=12 \catcode`}=12
+\gdef\writebgroup<{> \gdef\writeegroup<}>
+\endgroup
+% or:
+% \edef\writebgroup{\iftrue\string{\else}\fi}
+% \edef\writeegroup{\iffalse{\else\string}\fi}
+
+% set the width and height of the figure
+\newdimen\mywidth\mywidth=17cm
+\newdimen\myheight\myheight=13.5cm
+
+% save width and height in a Python-Json dictionary
+\newwrite\breadcrumbs
+\immediate\openout\breadcrumbs=\jobname.json
+\immediate\write\breadcrumbs{\writebgroup}
+\immediate\write\breadcrumbs{"height":\inches{\myheight},}
+\immediate\write\breadcrumbs{"width":\inches{\mywidth}}
+\immediate\write\breadcrumbs{\writeegroup}
+\immediate\closeout\breadcrumbs
+
+The target dimensions are: (width, height) = (\cm{\mywidth} cm, \cm{\myheight} cm).
+
+\lstset{numbers=left}
+
+\vspace{5pt}
+
+The following Python code does the job of reading the dictionary and setting the values of
+{\tt\small height} and {\tt\small width}.
+
+\vspace{5pt}
+
+\begin{python}
+ import io, os, json
+
+ # read the dictionary
+ try:
+ with io.open(os.getcwd() + '/' + 'example-09.json') as inp_file:
+ inp = json.load(inp_file)
+ except:
+ inp = { "width":"6.4",
+ "height":"4.8" }
+
+ # set figure dimensions in inches (yikes)
+ width = inp['width']
+ height = inp['height']
+
+\end{python}
+
+\vspace{5pt}
+
+This Python code does the job of plotting the Bessel functions (it is based on the code from example-04).
+
+\vspace{5pt}
+
+\begin{python}
+ # plot the Bessel functions
+ import numpy as np
+ import scipy.special as sp
+ import matplotlib.pyplot as plt
+
+ plt.matplotlib.rc('text', usetex = True)
+ plt.matplotlib.rc('grid', linestyle = 'dotted')
+ plt.matplotlib.rc('figure', figsize = (width,height))
+
+ x = np.linspace(0, 15, 500)
+
+ for v in range(0, 6):
+ plt.plot(x, sp.jv(v, x))
+
+ plt.xlim((0, 15))
+ plt.ylim((-0.5, 1.1))
+ plt.legend(('${J}_0(x)$', '${J}_1(x)$', '${J}_2(x)$',
+ '${J}_3(x)$', '${J}_4(x)$', '${J}_5(x)$'), loc = 0)
+ plt.xlabel('$x$')
+ plt.ylabel('${J}_n(x)$')
+ plt.grid(True)
+
+ plt.tight_layout(0.5)
+
+ plt.savefig('example-09-fig.pdf')
+
+ width_cm = round (width * 2.54, 2) # py (width,width_cm)
+ height_cm = round (height * 2.54, 2) # py (height,height_cm)
+\end{python}
+
+The following figure should have dimensions: (width, height) = (\py{width} cm, \py{height} cm).
+
+\vspace{10pt}
+
+\begin{minipage}{\textwidth}
+ \centering
+ \IfFileExists{example-09-fig.pdf}%
+ {\fbox{\includegraphics
+ {example-09-fig.pdf}}}{Failed to create pdf plot.}
+ \captionof{figure}{The first six Bessel functions.}
+\end{minipage}
+
+\end{document}
diff --git a/macros/latex/contrib/hybrid-latex/examples/examples.sty b/macros/latex/contrib/hybrid-latex/examples/examples.sty
new file mode 100644
index 0000000000..4276eaa31f
--- /dev/null
+++ b/macros/latex/contrib/hybrid-latex/examples/examples.sty
@@ -0,0 +1,31 @@
+\hypersetup{colorlinks=true,filecolor=magenta}
+
+\parindent=0pt
+
+\lstset{gobble=2}
+
+\def\bgcolour#1{\lstset{backgroundcolor=\color{#1}}}
+
+\def\pythonstyle{\lstset{%
+language=python,
+gobble=2,
+basicstyle={\small\tt},basewidth={0.50em},
+numbers=none,numberstyle=\tiny,numbersep=10pt,
+aboveskip=0pt,belowskip=0pt,
+frame=none,framesep=0pt,framerule=0pt,
+}}
+
+\def\latexstyle{\lstset{%
+language={[LaTeX]TeX},
+gobble=2,
+basicstyle={\small\tt},basewidth={0.50em},
+numbers=none,numberstyle=\tiny,numbersep=10pt,
+aboveskip=0pt,belowskip=0pt,
+backgroundcolor=\color{white},
+frame=none,framesep=0pt,framerule=0pt,
+texcsstyle=*\bfseries\color{blue},
+stringstyle=\color{green},
+commentstyle=\color{blue}\slshape,
+}}
+
+\lstnewenvironment{latex}{\begingroup\latexstyle}{\endgroup}
diff --git a/macros/latex/contrib/hybrid-latex/examples/summary.pdf b/macros/latex/contrib/hybrid-latex/examples/summary.pdf
new file mode 100644
index 0000000000..c02673dab8
--- /dev/null
+++ b/macros/latex/contrib/hybrid-latex/examples/summary.pdf
Binary files differ
diff --git a/macros/latex/contrib/hybrid-latex/examples/summary.tex b/macros/latex/contrib/hybrid-latex/examples/summary.tex
new file mode 100644
index 0000000000..d05a66dc64
--- /dev/null
+++ b/macros/latex/contrib/hybrid-latex/examples/summary.tex
@@ -0,0 +1,148 @@
+\documentclass[12pt]{article}
+\usepackage{pylatex}
+\usepackage{geometry}
+\usepackage{pgf}
+\usepackage{amsmath}
+\usepackage{listings}
+\usepackage{hyperref}
+\usepackage{breqn}
+\usepackage{caption}
+\usepackage{examples}
+
+\geometry{papersize={210mm,297mm},hmargin=2cm,tmargin=1.0cm,bmargin=1.5cm}
+
+\def\pyLaTeX{{\tt\small pyLaTeX}}
+
+\begin{document}
+
+\section*{Using output from other sources}
+
+This document performs no computations (i.e., it has no active code blocks) but instead uses selected parts of the output created by other documents. Thus this document can be compiled using {\tt\small pdflatex summary}. The basic structure of this document is as follows.
+
+\begin{minipage}[t]{0.75\textwidth}
+\begin{latex}
+ \documentclass[12pt]{article}
+ \usepackage{pylatex} % so that we can use \py{foo}
+ \usepackage{amsmath}
+ ... % other packages such as geometry, hyperref, breqn etc.
+ \begin{document}
+ ...
+ \input{example-01.pytex} % all Python output from example-01.tex
+ ...
+ \begin{align*}
+ &\py*{ans.301}\\ % the Python output
+ &\py*{ans.302}
+ \end{align*}
+ ...
+ \input{example-02.pytex} % all Python output from example-02.tex
+ ...
+ \begin{align*}
+ &\py*{ans.401}\\ % the Python output
+ &\py*{ans.402}
+ \end{align*}
+ ...
+ \end{document}
+\end{latex}
+\end{minipage}
+
+\vspace{10pt}
+
+Note that care must be taken to avoid name clashes across tags from different sources. If two or more sources define tags with the same name (e.g., {\tt\small foo.pytex} and {\tt\small bah.pytex} both define {\tt\small\verb|\pytag{ans.101}|}) then the last definition will be used.
+
+\section*{Example 1}
+
+\input{example-01.pytex}
+
+\begin{align*}
+ &\py*{ans.102}\\
+ &\py*{ans.302}\\
+ &\py*{ans.303}\\
+ &\py*{ans.305}\\
+ &\py*{ans.401}\\
+ &\py*{ans.402}\\
+ &\py*{ans.403}\\
+ &\py*{ans.404}
+\end{align*}
+
+\section*{Example 2}
+
+\input{example-02.pytex}
+
+\begin{align*}
+ \py {CalculusTable}
+\end{align*}
+
+\section*{Example 3}
+
+\input{example-03.pytex}
+
+\begin{align*}
+ \py{lhs.01} &= \py{rhs.01}\\
+ &= \py{rhs.02}\\
+ &= \py{rhs.03}\\
+ &= \py{rhs.04}\\
+ &\approx \py{rhs.05}
+\end{align*}
+
+\clearpage
+
+\section*{Example 4}
+
+\begin{minipage}{\textwidth}
+ \centering
+ \IfFileExists{example-04-fig.pdf}%
+ % {\scalebox{0.75}{\input{example-04-fig.pdf}}}{Failed to create plot.}
+ {\includegraphics[width=6.4in]{example-04-fig.pdf}}{Failed to create plot.}
+ \captionof{figure}{The first six Bessel functions.}
+\end{minipage}
+
+\section*{Example 5}
+
+\input{example-05.pytex}
+
+\begin{dgroup*}[spread={5pt}]
+ \begin{dmath*} f(x) = \Py*{ans.511} \end{dmath*}
+ \begin{dmath*} {}= \Py*{ans.512} \end{dmath*}
+ \begin{dmath*} {}= \Py*{ans.513} \end{dmath*}
+ \begin{dmath*} {}= \Py*{ans.514} \end{dmath*}
+ \begin{dmath*} {}= \Py*{ans.514} \end{dmath*}% LCB: do we need extar space for the tag?
+\end{dgroup*}
+
+\section*{Example 6}
+
+\input{example-06.pytex}
+
+\def\eps{\epsilon}
+\def\RuleA{\vrule depth0pt width0pt height14pt}
+\def\RuleB{\vrule depth8pt width0pt height14pt}
+\def\RuleC{\vrule depth10pt width0pt height16pt}
+
+\setlength{\tabcolsep}{0.025\textwidth}%
+
+\vspace{20pt}
+
+\begin{center}
+\begin{tabular}{cccc}%
+ \noalign{\hrule height 1pt}
+ \multicolumn{4}{c}{\RuleC\rmfamily\bfseries%
+ Newton-Raphson iterations \quad%
+ $x_{n+1} = x_n - f_n/f'_n\ ,\quad f(x) = x-e^{-x}$}\\
+ \noalign{\hrule height 1pt}
+ \RuleB$ n$&$ x_n$&$ \eps_{n} = x_{n} - e^{-x_{n}}$&$\eps_{n}/\eps_{n-1}^2$\\
+ \noalign{\hrule height 0.5pt}
+ \py{table}
+ \noalign{\hrule height 1pt}
+\end{tabular}
+\end{center}
+
+\section*{Example 7}
+
+\input{example-07.pytex}
+
+\bgroup\tt
+\begin{tabular}{rl}
+ \py{info}
+\end{tabular}
+\egroup
+
+\end{document}
diff --git a/macros/latex/contrib/hybrid-latex/examples/version.txt b/macros/latex/contrib/hybrid-latex/examples/version.txt
new file mode 100644
index 0000000000..cea04032c6
--- /dev/null
+++ b/macros/latex/contrib/hybrid-latex/examples/version.txt
@@ -0,0 +1,17 @@
+macOS 10.13.3 High Sierra
+
+python: 3.6.5 (Anaconda3)
+Matplotlib: 2.2.2
+numpy: 1.14.3
+sympy: 1.1.1
+scipy: 1.1.0
+
+------------------------------
+python: 2.7.10 (Apple)
+Matplotlib: 1.3.1
+numpy: 1.8.0rc1
+sympy: 1.1.1
+scipy: 0.13.0b1
+
+platform: 1.0.7
+datetime: unknown
diff --git a/macros/latex/contrib/hybrid-latex/latex/pylatex.cls b/macros/latex/contrib/hybrid-latex/latex/pylatex.cls
new file mode 100644
index 0000000000..18f1c36c9d
--- /dev/null
+++ b/macros/latex/contrib/hybrid-latex/latex/pylatex.cls
@@ -0,0 +1,17 @@
+\NeedsTeXFormat{LaTeX2e}
+\LoadClassWithOptions{article}
+%-------------------------------------------------------------------------------
+\input{pylatex.sty}
+\usepackage[papersize={297mm,210mm},hmargin=2cm,tmargin=1.0cm,bmargin=1.5cm]{geometry}
+\usepackage{amsmath}
+\usepackage{amssymb}
+\usepackage{hyperref}
+\usepackage{breqn}
+%-------------------------------------------------------------------------------
+\thispagestyle{empty}
+%-------------------------------------------------------------------------------
+\parindent=0pt
+\parskip=8pt plus 2pt minus 1pt
+%-------------------------------------------------------------------------------
+\InputIfFileExists{./\jobname.sty}{}{}% over ride defaults, e.g., papersize
+%-------------------------------------------------------------------------------
diff --git a/macros/latex/contrib/hybrid-latex/latex/pylatex.sty b/macros/latex/contrib/hybrid-latex/latex/pylatex.sty
new file mode 100644
index 0000000000..be299df48b
--- /dev/null
+++ b/macros/latex/contrib/hybrid-latex/latex/pylatex.sty
@@ -0,0 +1,127 @@
+\def\fileversion{v.0.1}
+\def\filedate{2018/06/04}% 4 Jun 2018
+\ProvidesPackage{pylatex}[\filedate\space\fileversion\space Python-Latex (LCB)]
+%-------------------------------------------------------------------------------
+\usepackage{comment}
+\usepackage{listings}
+\usepackage{keyval}
+\usepackage{etoolbox}
+\usepackage{xcolor}
+%-------------------------------------------------------------------------------
+\usepackage{pymacros}
+%-------------------------------------------------------------------------------
+\def\pyverb#1{{\color{blue}\texttt{\detokenize{#1}}}}% for inline Python verbatim, fails with # in arg.
+%-------------------------------------------------------------------------------
+\def\ProcessCutFile{}% part of the comment.sty package
+%-------------------------------------------------------------------------------
+% define and process key/value pairs in PySetup
+%
+\define@key{mypy}{action}{\def\py@action{#1}}% hide, show, verbatim
+%
+\def\@SetLstShow{\def\@PyEnv{\let\python\undefined\let\python@\undefined\lstnewenvironment{python}{\begingroup\@pystyle}{\endgroup}}}%
+\def\@SetLstVerbatim{\@SetLstShow}%
+\def\@SetLstHide{\def\@PyEnv{\let\endpython\undefined\specialcomment{python}{\begingroup}{\endgroup}}}%
+%
+\providecommand{\PySetup}[1]{%
+%
+\setkeys{mypy}{#1}% Set new keys
+%
+\ifdefstring{\py@action}{show}{\@SetLstShow}{\relax}
+\ifdefstring{\py@action}{hide}{\@SetLstHide}{\relax}
+\ifdefstring{\py@action}{verbatim}{\@SetLstVerbatim}{\relax}
+%
+\@PyEnv}
+%
+\PySetup{action=show}% defaults/initial setup
+%
+%-------------------------------------------------------------------------------
+% silently read the pytex file (if it exists)
+\AfterEndPreamble{\IfFileExists{./\jobname.pytex}{\input{./\jobname.pytex}}{\relax}}
+%===============================================================================
+% Listings style for Python
+%===============================================================================
+\lstset{%
+language=python,
+showtabs=true,
+showstringspaces=false,
+tab=,
+tabsize=2,
+basicstyle=\ttfamily\small,
+basewidth={0.5em}}
+%
+\def\@pystyle{
+\colorlet{commentcolour}{green!50!black}
+\colorlet{stringcolour}{red!60!black}
+\colorlet{keywordcolour}{magenta!90!black}
+\colorlet{exceptioncolour}{yellow!50!red}
+\colorlet{numpycolour}{blue!60!green}
+\colorlet{reservedcolour}{blue}
+\colorlet{magiccolour}{violet}
+\colorlet{booleancolour}{magenta}
+\colorlet{builtincolour}{violet}
+\lstset{%
+language=python,
+stringstyle=\color{stringcolour},
+keywordstyle=\color{keywordcolour}\bfseries,
+commentstyle=\color{commentcolour}\slshape,
+alsoletter={1234567890},
+morekeywords={\%, \}, \{, \&, \|},
+%
+% Reserved words
+emph={assert, break, continue, del, elif, else, except,%
+ exec, finally, for, global, if, lambda, pass,%
+ print, raise, return, try, while, yield,
+ yield from, as, with},%
+emphstyle=\color{blue}\bfseries,
+emphstyle=\color{reservedcolour}\bfseries,
+%
+% Builtins
+emph={[2]abs, all, any, apply, basestring, bin,%
+ bool, buffer, bytearray, bytes, callable, chr, classmethod,%
+ cmp, coerce, compile, complex, delattr, dict, dir, divmod,%
+ enumerate, eval, execfile, exit, file, filter, float,%
+ frozenset, getattr, globals, hasattr, hash, hex, id,%
+ input, int, intern, isinstance, issubclass, iter, len,%
+ list, locals, long, map, max, min, next, object,%
+ oct, open, ord, pow, property, range, raw_input, reduce,%
+ reload, repr, reversed, round, set, setattr, slice,%
+ sorted, staticmethod, str, sum, super, tuple, type,%
+ unichr, unicode, vars, xrange, zip},
+emphstyle=[2]\color{builtincolour},
+%
+% Boolean constants
+emph={[3]True, False, None},
+emphstyle=[3]\color{booleancolour},
+%
+% Exceptions
+emph={[4]Exception,NameError,FloatingPointError,IOError,EOFError,IndexError,RuntimeError,%
+ SyntaxError,TypeError,ValueError,OverflowError,ZeroDivisionError},
+emphstyle=[4]\color{exceptioncolour}\bfseries,
+%
+% numpy functions
+emph={[5]ode,fsolve,sqrt,exp,sin,cos,arctan,arctan2,arccos,pi,array,norm,solve,dot,
+ arange,isscalar,max,sum,flatten,shape,reshape,find,any,all,abs,plot,linspace,legend,
+ quad,polyval,polyfit,hstack,concatenate,vstack,column_stack,empty,zeros,ones,rand,vander,
+ grid,pcolor,eig,eigs,eigvals,svd,qr,tan,det,logspace,roll,min,mean,cumsum,cumprod,diff,
+ vectorize,lstsq,cla,eye,xlabel,ylabel,squeeze},
+emphstyle=[5]\color{numpycolour},
+%
+% python magic
+emph={[6]__init__,__add__,__mul__,__div__,__sub__,__call__,__getitem__,__setitem__,__eq__,__ne__,
+ __nonzero__,__rmul__,__radd__,__repr__,__str__,__get__,__truediv__,__pow__,__name__,__future__,__all__},
+emphstyle=[6]\color{magiccolour},
+%
+% Comments
+morecomment=[s]{"""}{"""},
+}}
+%-------------------------------------------------------------------------------
+% set listings defaults for Python
+%-------------------------------------------------------------------------------
+\definecolor{grey95}{rgb}{0.95,0.95,0.95}
+\lstset{%
+language=python,
+numberstyle=\tiny,numbersep=15pt,
+aboveskip=10pt,belowskip=0pt,
+frame=single,framesep=5pt,framerule=0pt,
+xleftmargin=5pt,xrightmargin=5pt,
+backgroundcolor=\color{grey95}}
diff --git a/macros/latex/contrib/hybrid-latex/latex/pymacros.sty b/macros/latex/contrib/hybrid-latex/latex/pymacros.sty
new file mode 100644
index 0000000000..5d63bb8b32
--- /dev/null
+++ b/macros/latex/contrib/hybrid-latex/latex/pymacros.sty
@@ -0,0 +1,24 @@
+\font\@displayfont=pzdr at 10pt
+% \providecommand*{\@None}{\hbox{\@displayfont\char'067}}% 067 = light cross, 070 = bold cross
+\providecommand*{\@None}{\hbox{(??)}}
+% ---------------------------------------------------------------------
+\providecommand*{\colonEq}{\mathrel{\vcenter{\baselineskip0.5ex\lineskiplimit0pt\hbox{.}\hbox{.}}}=}% centred colon
+\providecommand*{\pytag}[2]{\expandafter\def\csname py@#1\endcsname{#2}}
+% ---------------------------------------------------------------------
+\providecommand*{\py}{\@ifstar\@py\@@py}
+\providecommand*{\@py}[1]{\ttTag{#1}\colonEq\py{#1}}% places tag immediately to the left
+\providecommand*{\@@py}[1]{\@ifundefined{py@#1}{\@None}{\csname py@#1\endcsname}}% shows just the value, nothing else
+% ---------------------------------------------------------------------
+\providecommand*{\Py}{\@ifstar\@Py\@@Py}
+\providecommand*{\@Py}[2][\hfill]{\py{#2}\def\@currentlabel{#2}#1\llap{(\ttTag{#2})}}% for use in dmath* etc.
+\providecommand*{\@@Py}[1]{\py{#1}\tag{\ttTag{#1}}}% for use in align* etc.
+% ---------------------------------------------------------------------
+\providecommand*{\Dmath}{\@ifstar\@Dmath\@@Dmath}
+\providecommand*{\@Dmath}[2][]{\begin{dmath*}[#1]#2\end{dmath*}}%
+\providecommand*{\@@Dmath}[2][]{\begin{dmath}[#1]#2\end{dmath}}%
+% ---------------------------------------------------------------------
+\providecommand*{\ttTag}[1]{\hbox{\small\tt\detokenize{#1}}}
+\providecommand*{\pglabel}[1]{\phantomsection\label{#1}}% fixes a problem with hyperref and label
+% see https://tex.stackexchange.com/questions/193380/hyperref-pageref-links-point-to-first-page
+% ---------------------------------------------------------------------
+\let\Input=\input
diff --git a/macros/latex/contrib/hybrid-latex/python/merge-tex.py b/macros/latex/contrib/hybrid-latex/python/merge-tex.py
new file mode 100755
index 0000000000..de5fd157c2
--- /dev/null
+++ b/macros/latex/contrib/hybrid-latex/python/merge-tex.py
@@ -0,0 +1,115 @@
+#!/usr/local/bin/python3
+
+import re
+import sys
+import os.path
+
+max_recurse_depth = 10 # limit depth of nested \Input statements
+
+re_comment = re.compile (r"^\s*%")
+re_file_name = re.compile (r"^\s*\\Input\{([_a-zA-Z0-9./-]+)\}") # use \Input to avoid confusion with \input
+
+def make_str (num,digits):
+ return '{number:0{width}d}'.format(number=num,width=digits)
+
+def include_files (txt, file_name, recurse_depth):
+
+ def grep (this_line, regex, the_group):
+ result = regex.search (this_line)
+ if result:
+ found = True
+ group = result.group (the_group)
+ else:
+ found = False
+ group = "<none>"
+ return group, found
+
+ def not_comment (the_line):
+ return not re_comment.search (the_line)
+
+ def read_include_name (the_line):
+ file_name, found = grep (the_line,re_file_name,1)
+ return file_name, found
+
+ recurse_depth = recurse_depth + 1
+
+ if recurse_depth > max_recurse_depth:
+ print ("> merge-tex.py: Recursion limit for \\Input{...} reached (max = "+str(max_recurse_depth)+"), exit")
+ sys.exit(1)
+
+ with open (file_name,'r') as src:
+
+ for the_line in src:
+
+ if not_comment (the_line):
+ inc_file, found = read_include_name (the_line)
+ if found:
+ if not os.path.isfile (inc_file):
+ print ("> merge-tex.py: Include file " + inc_file + " not found, exit.\n")
+ sys.exit(1)
+ if markup:
+ txt.write (comment + " beg" + make_str(recurse_depth,2) + ": " + inc_file + "\n")
+ recurse_depth = include_files (txt, inc_file, recurse_depth)
+ txt.write (comment + " end" + make_str(recurse_depth,2) + ": " + inc_file + "\n")
+ else:
+ recurse_depth = include_files (txt, inc_file, recurse_depth)
+ else:
+ txt.write (the_line)
+ else:
+ txt.write (the_line)
+
+ recurse_depth = recurse_depth - 1
+
+ return recurse_depth
+
+# -----------------------------------------------------------------------------
+# the main code
+
+comment = "%%" # standard comment marker for LaTeX source
+
+import argparse
+
+parser = argparse.ArgumentParser(description="Merge a sequence of LaTeX files into a single LaTeX file")
+parser.add_argument("-i", dest="input", metavar="foo.tex", help="source file", required=True)
+parser.add_argument("-o", dest="output", metavar="bah.tex", help="output file", required=True)
+parser.add_argument("-S", dest="silent", default="False", action='store_true', help="suppress markup lines for each \\Input{foo.tex}", required=False)
+
+src_file_name = parser.parse_args().input
+out_file_name = parser.parse_args().output
+
+silent = parser.parse_args().silent
+
+if silent == "False":
+ markup = True
+else:
+ markup = False
+
+if src_file_name == out_file_name:
+ print ("> merge-tex.py: Indentical files names for input and output, exit.")
+ sys.exit (1)
+
+if not os.path.isfile (src_file_name):
+ print ("> merge-tex.py: Source file " + src_file_name + " not found, exit.")
+ sys.exit (1)
+
+with open (out_file_name,"w") as txt:
+
+ recurse_depth = 0
+
+ if markup:
+
+ txt.write (comment + " --------------------------------------------------------------\n")
+ txt.write (comment + " Do not edit this file, it was created by merge-tex.py using:\n")
+ txt.write (comment + " merge-tex.py -i " + src_file_name + " -o " + out_file_name + "\n")
+ txt.write (comment + " --------------------------------------------------------------\n")
+
+ txt.write (comment + " beg" + make_str(recurse_depth,2) + ": ./" + src_file_name + "\n")
+ recurse_depth = include_files (txt, "./" + src_file_name, recurse_depth)
+ txt.write (comment + " end" + make_str(recurse_depth,2) + ": ./" + src_file_name + "\n")
+
+ else:
+ recurse_depth = include_files (txt, "./" + src_file_name, recurse_depth)
+
+if not recurse_depth == 0:
+ print("> merge-tex.py: error during merger")
+ print("> recursion depth should be zero, actual value: "+str(recurse_depth))
diff --git a/macros/latex/contrib/hybrid-latex/python/pypostproc.py b/macros/latex/contrib/hybrid-latex/python/pypostproc.py
new file mode 100755
index 0000000000..11b4ae3382
--- /dev/null
+++ b/macros/latex/contrib/hybrid-latex/python/pypostproc.py
@@ -0,0 +1,239 @@
+#!/usr/local/bin/python3
+
+import re
+import sys
+import os.path
+
+import enum
+
+class Type (enum.Enum):
+ BegTag = 1
+ EndTag = 2
+ PlainText = 3
+ Unknown = 4
+
+def empty_line (this_line):
+ if len(this_line) == 0:
+ return True
+ else:
+ return re.search ("(^ *$)",this_line)
+
+def get_index (this_line):
+ result = re.search ("tag([0-9]+)",this_line)
+ if result:
+ return int(result.group (1))
+ else:
+ print ("> Error reading tag index on: " + this_line)
+ sys.exit (1)
+
+def get_text (this_line):
+ result = re.search ("tag([0-9]+)=(.*)",this_line)
+ if result:
+ return result.group (2)
+ else:
+ print ("> Error reading tag text on: " + this_line)
+ sys.exit (1)
+
+def has_tag (this_line):
+ return re.search (r"(tag[0-9]+=)",this_line)
+
+def is_beg_tag (this_line):
+ return re.search (r"(^beg_tag[0-9]+$)",this_line)
+
+def is_end_tag (this_line):
+ return re.search (r"(^end_tag[0-9]+$)",this_line)
+
+# -------------------------------------------------------------------------
+# stack operations
+
+stack = []
+stack_index = 0
+max_stack_index = 5
+for i in range (0,max_stack_index): # create an empty stack
+ stack.append(0) # using zero forces any non-tagged output to be stored in tag_output[0]
+
+def read_stack (index):
+ return stack [index]
+
+def push_stack (index, stack_index):
+ if stack_index < max_stack_index:
+ stack_index = stack_index + 1
+ stack [stack_index] = index
+ return stack_index
+ else:
+ print ("> Depth of nested pyBeg/pyEnd pairs exceeded, max = "+str(max_stack_index)+", exit\n")
+ sys.exit (1)
+
+def pop_stack (index, stack_index):
+ if stack_index > 0:
+ if index == read_stack (stack_index):
+ stack [stack_index] = 0
+ return stack_index - 1
+ else:
+ print ("> Error with pyBeg/pyEnd pairs for tag index: "+str(index)+", exit\n")
+ sys.exit (1)
+ else:
+ print ("> Error with pyBeg/pyEnd pairs, check for missing pyBeg or pyEnd, exit\n")
+ sys.exit (1)
+
+def parse (this_line):
+ if is_beg_tag (this_line):
+ return Type.BegTag
+ elif is_end_tag (this_line):
+ return Type.EndTag
+ else:
+ return Type.PlainText
+
+def append_text (this_line, index):
+ tag_output[index].append(this_line.rstrip("\n"))
+
+def tex_macro (tex, index):
+ the_lines = tag_output [index]
+ tex.write ("\pytag{"+tag_name[index]+"}{%\n")
+ for i in range (0,len(the_lines)):
+ if not empty_line (the_lines[i]):
+ tex.write (the_lines[i]+"%\n")
+ tex.write("}\n")
+
+# -----------------------------------------------------------------------------
+# the main code
+
+import argparse
+
+parser = argparse.ArgumentParser(description="Post-process Python output")
+parser.add_argument("-i", dest="input", metavar="source", help="LaTeX-Python source file (without .tex file extension)", required=True)
+parser.add_argument("-I", dest="sty", metavar="include", help="Full path to LaTeX-Python pymacros.sty file")
+
+the_file_name = parser.parse_args().input
+sty_file_name = parser.parse_args().sty
+
+# ----------------------------------------------------------------------------
+# include the Python macros in the .cdbtex file?
+
+if sty_file_name:
+ include_macros_header = True
+ if not os.path.isfile (sty_file_name):
+ print ("> could not find "+sty_file_name)
+ print ("> will not include Python macros")
+ include_macros_header = False
+else:
+ include_macros_header = False
+
+# ----------------------------------------------------------------------------
+# file names
+
+idx_file_name = the_file_name + ".pyidx"
+tex_file_name = the_file_name + ".pytex"
+src_file_name = the_file_name + ".pytxt"
+
+# ----------------------------------------------------------------------------
+# any tag index/name pairs to read?
+
+if not os.path.isfile (idx_file_name):
+ with open(tex_file_name,"w") as tex:
+ tex.write ("% no Python output")
+ sys.exit (0)
+
+# ----------------------------------------------------------------------------
+# read tag index/name pairs
+
+tag_name = [""] # dummy entry at index = 0
+tag_done = [False] # ditto
+tag_found = [False] # ditto
+tag_output = [[]] # ditto
+num_tag = 0
+tag_index = 0
+
+with open(idx_file_name, "r") as idx:
+ for this_line in idx:
+ if has_tag (this_line): # skip any non-tag text (e.g., comments)
+ num_tag = num_tag + 1 # the tag index
+ tag_name.append (get_text (this_line)) # the tag name
+ tag_done.append (False)
+ tag_found.append (False)
+ tag_output.append ([])
+
+# note: num_tag = number of tags declared in the Python/LaTeX source
+# these tags are stored in locations 1,2,3 ... num_tag in the various arrays
+# tag_output[0] will contain all non-tagged Python output.
+
+# ----------------------------------------------------------------------------
+# read Python output and create LaTeX macros for each tag
+
+if num_tag == 0:
+ with open(tex_file_name,"w") as tex:
+ tex.write ("% no Python output")
+else:
+
+ if not os.path.isfile (src_file_name):
+ with open(tex_file_name,"w") as tex:
+ tex.write ("% no Python output")
+ print ("> post-process: Source file " + src_file_name + " not found, exit.")
+ print ("> possible error during execution of Python.")
+ sys.exit (1)
+
+ # -------------------------------------------------------------------------
+ # read Python output
+
+ with open(src_file_name,"r") as src:
+ for this_line in src:
+
+ line_type = parse (this_line)
+ if line_type == Type.BegTag:
+
+ tag_index = get_index (this_line)
+ tag_done [tag_index] = False
+ tag_found [tag_index] = True
+ stack_index = push_stack (tag_index,stack_index)
+
+ elif line_type == Type.EndTag:
+
+ tag_index = get_index (this_line)
+ tag_done [tag_index] = True
+ tag_found [tag_index] = True
+ stack_index = pop_stack (tag_index,stack_index)
+ tag_index = read_stack (stack_index)
+
+ elif line_type == Type.PlainText:
+
+ append_text (this_line,tag_index)
+
+ else:
+ pass # should never get here
+
+ # -----------------------------------------------------------------------
+ # create the latex macros, one for each tag
+
+ with open(tex_file_name,"w") as tex:
+
+ if include_macros_header:
+ tex.write(r'% ====================================================================='+'\n')
+ tex.write(r'% Define Python macros so that this file may be used by other LaTeX sources.'+'\n')
+ tex.write(r'% To include this file in some other LaTeX source be sure to add the following'+'\n')
+ tex.write(r'% lines in the LaTeX preamble.'+'\n')
+ tex.write(r'% \input{foo.mattex}% change foo to match the name of this file.'+'\n')
+ tex.write(r'% ---------------------------------------------------------------------'+'\n')
+ tex.write(r'\makeatletter'+'\n')
+ with open(sty_file_name,"r") as sty:
+ for this_line in sty:
+ tex.write (this_line)
+ tex.write(r'\makeatother'+'\n')
+ tex.write(r'% ====================================================================='+'\n')
+
+ for i in range (1,num_tag+1):
+
+ tex_macro (tex, i)
+
+ # -------------------------------------------------------------------------
+ # report tags that didn't have matching Python output
+
+ for i in range (1,num_tag+1):
+ if not tag_found [i]:
+ print ("> post-process: Failed to find output for tag: "+tag_name[i])
+
+ # -------------------------------------------------------------------------
+ # report problems with un-matched beg/end pairs
+
+ for i in range (1,num_tag+1):
+ if not tag_done [i]:
+ print ("> post-process: Something is missing for tag: "+tag_name[i])
diff --git a/macros/latex/contrib/hybrid-latex/python/pypreproc.py b/macros/latex/contrib/hybrid-latex/python/pypreproc.py
new file mode 100755
index 0000000000..613153013a
--- /dev/null
+++ b/macros/latex/contrib/hybrid-latex/python/pypreproc.py
@@ -0,0 +1,414 @@
+#!/usr/local/bin/python3
+
+import re
+import sys
+import os.path
+
+re_beg_python_environ = re.compile (r'^\s*\\begin{python}')
+re_end_python_environ = re.compile (r'^\s*\\end{python}')
+
+re_beg_python_verbatim = re.compile (r'^\s*\\PySetup{(.*?)action=verbatim')
+re_end_python_verbatim = re.compile (r'^\s*\\PySetup{(.*?)action=(show|hide)')
+
+re_beg_latex_document = re.compile (r'^\s*\\begin{document}')
+re_end_latex_document = re.compile (r'^\s*\\end{document}')
+
+re_indent = re.compile (r'(^\s*)')
+re_empty_line = re.compile (r'(^\s*$)')
+re_latex_comment = re.compile (r'(^\s*%)')
+re_python_comment = re.compile (r'(^\s*#)')
+re_python_markup = re.compile (r'(#\s*(py\s*\(|pyBeg\s*\(|pyEnd))')
+re_hidden_markup = re.compile (r'(^\s*#.*#\s*(py\s*\(|pyBeg\s*\(|pyEnd))')
+re_capture = re.compile (r'(#\s*((pyBeg|pyEnd)\s*\(\s*([a-zA-Z0-9_.]+)\)))')
+re_beg_capture = re.compile (r'(#\s*(pyBeg\s*\(\s*([a-zA-Z0-9_.]+)\)))')
+re_end_capture = re.compile (r'(#\s*(pyEnd\s*(\(\s*([a-zA-Z0-9_.]+)\))?))')
+re_py_tag = re.compile (r'(#\s*(py\s*\(\s*([a-zA-Z0-9_.]+)\s*(,\s*([a-zA-Z0-9_]+)\s*)?\)))')
+ # Allow two forms of tag, py(foo,bah) and py(bah).
+ # In both cases bah must be a valid python expression.
+
+def make_str (num,digits):
+ return '{number:0{width}d}'.format(number=num,width=digits)
+
+def grep (this_line, regex, the_group):
+ result = regex.search (this_line)
+ if result:
+ found = True
+ the_beg = result.start (the_group)
+ the_end = result.end (the_group)
+ else:
+ found = False
+ the_beg = -1
+ the_end = -1
+ return the_beg, the_end, found
+
+def not_latex_comment (this_line):
+ return not re_latex_comment.search (this_line)
+
+def not_python_comment (this_line):
+ return not re_python_comment.search (this_line)
+
+def is_beg_latex_document (this_line):
+ return re_beg_latex_document.search (this_line)
+
+def is_end_latex_document (this_line):
+ return re_end_latex_document.search (this_line)
+
+def is_beg_python_environ (this_line):
+ return re_beg_python_environ.search (this_line)
+
+def is_end_python_environ (this_line):
+ return re_end_python_environ.search (this_line)
+
+def is_beg_python_verbatim (this_line):
+ return re_beg_python_verbatim.search (this_line)
+
+def is_end_python_verbatim (this_line):
+ return re_end_python_verbatim.search (this_line)
+
+def has_beg_python_capture (this_line):
+ return re_beg_capture.search (this_line)
+
+def has_end_python_capture (this_line):
+ return re_end_capture.search (this_line)
+
+def has_python_tag (this_line):
+ return re_py_tag.search (this_line)
+
+def has_python_markup (this_line):
+ return re_python_markup.search (this_line)
+
+def not_hidden_markup (this_line):
+ return not re_hidden_markup.search (this_line)
+
+def filter_python_markup (this_line):
+ if len(this_line) == 0:
+ return ""
+ else:
+ if has_python_markup (this_line):
+ the_beg,the_end,found = grep (this_line,re_python_markup,1)
+ if the_beg > 0 :
+ return this_line[0:the_beg-1].rstrip(" ")
+ else:
+ return this_line.rstrip("\n")
+ else:
+ return this_line.rstrip("\n")
+
+# -----------------------------------------------------------------------------
+# 1st pass: copy Python source from source.tex to source.py file
+# leave in-line comments in place, these will be removed in pass2
+
+def pass1 (src_file_name, out_file_name, the_file_name):
+
+ in_latex_document = False
+ in_python_environ = False
+ in_python_verbatim = False
+
+ # --------------------------------------------------------------------------
+ # find the first non-empty, non-comment line in the Python blocks
+ # record the indent of that line
+
+ indent_num = 0 # number of leading spaces of first non-trivial Python line
+
+ with open(src_file_name, "r") as src:
+ for this_line in src:
+ if not_latex_comment (this_line):
+ if in_latex_document:
+ if is_end_latex_document (this_line):
+ in_latex_document = False
+ break
+ else:
+ if is_beg_python_verbatim (this_line):
+ in_python_verbatim = True
+ elif is_end_python_verbatim (this_line):
+ in_python_verbatim = False
+ if in_python_environ:
+ if is_end_python_environ (this_line):
+ in_python_environ = False
+ else:
+ if not in_python_verbatim:
+ if len(this_line) > 1:
+ if not_python_comment (this_line):
+ the_beg,the_end,found = grep (this_line,re_indent,1)
+ if not found:
+ print ("> read error in preproc/pass1")
+ print ("> line: "&this_line)
+ sys.exit (1)
+ else:
+ indent_num = the_end - the_beg # number of leading spaces
+ break
+ else:
+ if is_beg_python_environ (this_line):
+ in_python_environ = True
+ else:
+ if is_beg_latex_document (this_line):
+ in_latex_document = True
+
+ # --------------------------------------------------------------------------
+ # collect the Python begin/end blocks and write to a single file
+
+ in_latex_document = False
+ in_python_environ = False
+ in_python_verbatim = False
+
+ with open (src_file_name,"r") as src:
+ with open (out_file_name,"w") as out:
+
+ out.write ("# ----------------------------------------------\n")
+ out.write ("# auto-generated from " + src_file_name + "\n")
+ out.write ("# ----------------------------------------------\n")
+
+ out.write ("def Print (obj):\n")
+ out.write (" try:\n")
+ out.write (" print(latex(obj))\n")
+ out.write (" except:\n")
+ out.write (" print(obj)\n")
+ out.write ("# ----------------------------------------------\n\n")
+
+ for this_line in src:
+
+ if not_latex_comment (this_line):
+ if in_latex_document:
+ if is_end_latex_document (this_line):
+ in_latex_document = False
+ break
+ else:
+ if is_beg_python_verbatim (this_line):
+ in_python_verbatim = True
+ elif is_end_python_verbatim (this_line):
+ in_python_verbatim = False
+
+ if in_python_environ:
+ if is_end_python_environ (this_line):
+ in_python_environ = False
+ else:
+ if not in_python_verbatim:
+ if len(this_line) > 0:
+ the_beg,the_end,found = grep (this_line,re_indent,1)
+ start = min(indent_num,the_end-the_beg)
+ out.write (this_line[start:len(this_line)-1]+"\n") # use 0: to retain indent
+ else:
+ out.write("\n")
+
+ else:
+ if is_beg_python_environ (this_line):
+ in_python_environ = True
+
+ else:
+ if is_beg_latex_document (this_line):
+ in_latex_document = True
+
+# -----------------------------------------------------------------------------
+# 2nd pass: use the source.py (from pass1) file to build the following files
+
+# source_.py : annotated file, contains extra lines to generate output for later capture
+# source.pyidx : a list of tags, one line per tag, written as: tag index = tag name
+
+def pass2 (src_file_name, out_file_name, idx_file_name):
+
+ # -----------------------------------------------------------
+ # read functions, extract tag and expressions names
+
+ def get_tag (this_line):
+ the_beg,the_end,found = grep (this_line,re_py_tag,3)
+ if found:
+ return this_line[the_beg:the_end].strip(" ")
+ else:
+ return this_line.strip(" ")
+
+ def get_exp (this_line):
+ the_beg,the_end,found = grep (this_line,re_py_tag,5)
+ if found and (the_beg > 0) and (the_end > 0):
+ return this_line[the_beg:the_end].strip(" ") # returns foo given py (foo,bah)
+ else:
+ return get_tag (this_line) # returns bah given py (bah)
+
+ def get_src (this_line):
+ the_beg,the_end,found = grep (this_line,re_py_tag,1)
+ if found:
+ if the_beg == 0:
+ return ""
+ else:
+ return this_line [0:the_beg-1].rstrip(" ")
+ else:
+ return this_line.rstrip(" ")
+
+ def get_capture (this_line):
+ the_beg,the_end,found = grep (this_line,re_capture,4)
+ if found:
+ return this_line[the_beg:the_end].strip(" ")
+ else:
+ return this_line.strip(" ")
+
+ def beg_capture_src (this_line):
+ the_beg,the_end,found = grep (this_line,re_beg_capture,1)
+ if found:
+ if the_beg > 0:
+ return this_line [0:the_beg-1].rstrip(" ")
+ else:
+ return ""
+ else:
+ return this_line.rstrip(" ")
+
+ def end_capture_src (this_line):
+ the_beg,the_end,found = grep (this_line,re_end_capture,1)
+ if found:
+ if the_beg > 0:
+ return this_line [0:the_beg-1].rstrip(" ")
+ else:
+ return ""
+ else:
+ return this_line.rstrip(" ")
+
+ # ----------------------------------------------------------
+ # output functions, writes to index file, temp py file etc.
+
+ def beg_tag (num):
+ return 'print("beg_tag'+make_str(num,4)+'")'
+
+ def end_tag (num):
+ return 'print("end_tag'+make_str(num,4)+'")'
+
+ def beg_capture (num):
+ return beg_tag (num)
+
+ def end_capture (num):
+ return end_tag (num)
+
+ def wrt_latex (this_line):
+ this_text = get_exp (this_line)
+ return "Print("+this_text+")"
+
+ # -------------------------------------------------------------------------
+ # stack operations
+
+ stack = []
+ tag_name = []
+ stack_index = 0
+ max_stack_index = 5
+ for i in range (0,max_stack_index+1): # create an empty stack
+ stack.append(-1)
+ tag_name.append("")
+
+ def read_stack (index):
+ return stack [index]
+
+ def push_stack (index, this_line, stack_index):
+ if stack_index < max_stack_index:
+ stack_index = stack_index + 1
+ stack [stack_index] = index
+ tag_name [stack_index] = get_capture (this_line)
+ return stack_index
+ else:
+ print ("> Depth of nested pyBeg/pyEnd pairs exceeded, max = "+str(max_stack_index)+", exit")
+ sys.exit (1)
+
+ def pop_stack (index, this_line, stack_index):
+ if stack_index > 0:
+ tmp_name = get_capture (this_line)
+ if tmp_name == tag_name [stack_index]:
+ stack [stack_index] = -1
+ return stack_index - 1
+ else:
+ print ("> Error in pyBeg/pyEnd pair for tag: "+str(tag_name[index])+", exit")
+ sys.exit (1)
+ else:
+ print ("> Error with pyBeg/pyEnd pairs, check for missing pyBeg or pyEnd, exit")
+ sys.exit (1)
+
+ # create a temporary copy of the Python source
+
+ from shutil import copyfile
+ from uuid import uuid4
+
+ tmp_file_name = "/tmp/"+str(uuid4())
+ copyfile (src_file_name,tmp_file_name)
+
+ # create an annotated _.py and .pyidx files using the temporary file as source
+
+ with open (out_file_name,"w") as out:
+ with open (idx_file_name,"w") as idx:
+ with open (tmp_file_name,"r") as src:
+
+ tag_index = 0
+
+ for this_line in src:
+
+ if has_python_markup (this_line) and not_hidden_markup (this_line):
+
+ if has_beg_python_capture (this_line):
+
+ tag_index = tag_index + 1
+
+ out.write ( beg_capture_src (this_line) + "\n")
+ out.write ( beg_capture (tag_index) + "\n" )
+
+ idx.write ("tag"+make_str(tag_index,4)+"=")
+ idx.write ( get_capture (this_line) + "\n")
+
+ stack_index = push_stack (tag_index,this_line,stack_index)
+
+ elif has_end_python_capture (this_line):
+
+ out.write ( end_capture_src (this_line) + "\n" )
+ out.write ( end_capture (read_stack (stack_index)) + "\n")
+
+ stack_index = pop_stack (read_stack (stack_index),this_line,stack_index)
+
+ elif has_python_tag (this_line):
+
+ tag_index = tag_index + 1
+
+ out.write (get_src (this_line) + "\n")
+ out.write (beg_tag (tag_index) + "\n")
+ out.write (wrt_latex (this_line) + "\n")
+ out.write (end_tag (tag_index) + "\n")
+
+ idx.write ("tag"+make_str(tag_index,4) + "=")
+ idx.write ( get_tag (this_line) + "\n")
+
+ else:
+
+ out.write (filter_python_markup (this_line)+"\n")
+
+ else:
+
+ out.write (filter_python_markup (this_line)+"\n")
+
+ # copy the temporary file back to the source with in-line comments removed
+ # note: this clean copy is just for reference, it's never used
+
+ with open (tmp_file_name,"r") as tmp:
+ with open (src_file_name,"w") as src:
+ for this_line in tmp:
+ src.write (filter_python_markup(this_line)+"\n") # note: could choose to retain in-line comments
+ # just drop the filter_python_markup
+
+# -----------------------------------------------------------------------------
+# the main code
+
+import argparse
+
+parser = argparse.ArgumentParser(description="Pre-process LaTeX-Python source")
+parser.add_argument("-i", dest="input", metavar="input", help="LaTeX-Python source file (without file extension)", required=True)
+parser.add_argument("-m", dest="name", metavar="name", help="Merged LaTeX-Python source file (without file extension)")
+
+src_file_name = parser.parse_args().input
+mrg_file_name = parser.parse_args().name
+
+if not mrg_file_name:
+
+ if not os.path.isfile (src_file_name + ".tex"):
+ print ("> pre-process: Source file " + src_file_name + ".tex" + " not found, exit.")
+ sys.exit (1)
+
+ pass1 (src_file_name + ".tex", src_file_name + ".py", src_file_name)
+ pass2 (src_file_name + ".py", src_file_name + "_.py", src_file_name + ".pyidx")
+
+else:
+
+ if not os.path.isfile (mrg_file_name + ".tex"):
+ print ("> pre-process: Source file " + mrg_file_name + ".tex" + " not found, exit.")
+ sys.exit (1)
+
+ pass1 (mrg_file_name + ".tex", src_file_name + ".py", src_file_name)
+ pass2 (src_file_name + ".py", src_file_name + "_.py", src_file_name + ".pyidx")
diff --git a/macros/latex/contrib/hybrid-latex/shell/pylatex.sh b/macros/latex/contrib/hybrid-latex/shell/pylatex.sh
new file mode 100755
index 0000000000..4348002054
--- /dev/null
+++ b/macros/latex/contrib/hybrid-latex/shell/pylatex.sh
@@ -0,0 +1,83 @@
+#!/bin/bash
+
+file="<none>"
+silent="no"
+keep="no"
+skiplatex="no"
+Python="python"
+sty=""
+
+# -----------------------------------------------------------------------------------------
+# Parse the command-line options
+
+OPTIND=1
+
+while getopts 'i:I:P:skxh' option
+do
+ case "$option" in
+ "i") file="$OPTARG" ;;
+ "I") sty="-I$OPTARG" ;;
+ "P") Python="$OPTARG" ;;
+ "s") silent="yes" ;;
+ "k") keep="yes" ;;
+ "x") skiplatex="yes" ;;
+ "h") echo "usage : pylatex.sh -i file [-P<path to python>] [-I<path to pymacros.sty>] [-s] [-k] [-x] [-h]"
+ echo "options : -i file : source file (without .tex extension)"
+ echo " -I file : full path to pymacros.sty file"
+ echo " -P path : where to find the Python binary"
+ echo " -s : silent, don't open the pdf file"
+ echo " -k : keep all temporary files"
+ echo " -x : don't call latex"
+ echo " -h : this help message"
+ echo "example : pylatex.sh -i file -P/usr/bin/python"
+ exit ;;
+ ?) echo "pylatex.sh : Unknown option specified."
+ exit ;;
+ esac
+done
+
+if [[ ! -e $file.tex ]]; then
+ echo "> file ""$file.tex"" not found, exit"
+ exit 1;
+fi
+
+if [[ $file = "<none>" ]]; then
+ echo "> no source file given, use pylatex.sh -i file"
+ exit 1;
+fi;
+
+file=$(basename -s ".tex" "$file")
+num=$(egrep -c -e'^\s*\\Input\{' "$file".tex)
+name=$file
+
+if ! [[ $num = 0 ]]; then
+ merge-tex.py -i $file.tex -o .merged.tex
+ name=".merged"
+fi
+
+touch $file.pytxt
+
+pypreproc.py -i $file -m $name || exit 1
+
+$Python $file"_.py" > $file.pytxt || exit 3
+
+pypostproc.py -i $file $sty || exit 5
+
+if [[ $skiplatex = "no" ]]; then
+ pdflatex -halt-on-error -interaction=batchmode -synctex=1 $file || exit 7
+ echo " " # for some silly reason pdfsync forgets a trailing \n
+else
+ silent="yes"
+fi
+
+if [[ $silent = "no" ]]; then
+ open $file.pdf # macOS
+ # xdg-open $file.pdf # Linux
+ # evince $file.pdf # Linux
+fi
+
+if [[ $keep = "no" ]]; then
+ rm -rf comment.cut
+ rm -rf .merged.tex .tmp.txt
+ rm -rf $file.log $file.out $file.py $file"_.py" $file.pyidx $file.pytxt
+fi