The difficulty is that the externalisation library looks for \end{tikzpicture}
to determine the end of the picture that it is "externalising". It does this without expansion so the \end{tikzpicture}
has to appear at the same time as the \begin{tikzpicture}
. That's why:
\newenvironment{mytikz}{\begin{tikzpicture}}{\end{tikzpicture}}
doesn't work. When one types
\begin{mytikz}
\node {hello world};
\end{mytikz}
then the \begin{mytikz}
gets expanded all the way to \begin{tikzpicture}
(and beyond) at which point TeX starts gobbling stuff. It meets the \end{mytikz}
without expanding it and so never realises that it is the \end{tikzpicture}
that it was looking for.
The environ package gets around this by converting the environment to a command. So when TeX see \begin{mytikz}
, before it expands that (fully, that is, it has to expand it just enough to know that it was defined using the environ
package), it goes looking for the \end{mytikz}
. Then it slurps in the contents in to an internal(ish) macro and dumps the corresponding code in to the stream. That means that when TeX finally sees the \begin{tikzpicture}
, then the correct \end{tikzpicture}
has also been inserted into the stream at the correct place.
So we want to keep this feature, but at the same time add in the argument handling features of the xargs
package. The trick here is to realise that the argument handling part does not actually have to be connected to the environment - it just has to look like it to the user. (This is quite common in TeX/LaTeX commands. Many appear to take an argument but in fact do nothing of the sort.) If we declare an environment using \NewEnviron
then whatever appears after the \begin{myenv}
gets put in to a special macro called \BODY
. That is, if we have:
\NewEnviron{myenv}{something first \BODY\ something last}
then
\begin{myenv}
in the middle
\end{myenv}
gets replaced by something first in the middle something last
in the stream (almost ..., and I'm not worrying about \par
here). The point is that this also works for
\begin{myenv}[some][optional]{or}{mandatory}[arguments]
in the middle
\end{myenv}
in that this produces something first [some][optional]{or}{mandatory}[arguments] in the middle something last
. So if something first
happens to be a macro, it will "see" the [some][optional]{or}{mandatory}[arguments]
and think that they were intended for it.
The "almost" here is that actually what is put in the stream is something first \BODY\ something last
so we need to expand \BODY
first to get at the arguments.
Putting all of this together, we get:
\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{external}
\tikzexternalize
\usepackage{environ}
\usepackage{xargs}
\newcommandx{\startmytikz}[1][1=]{%
\begin{figure}[htp]
\centering
\begin{tikzpicture}[#1]}
\NewEnviron{mytikz}{\expandafter\startmytikz\BODY
\end{tikzpicture}
\end{figure}}
\begin{document}
\tikzset{external/force remake=true}
\begin{mytikz}[every path/.style={red}]
\draw(0,0) circle (1cm);
\node {hello world};
\end{mytikz}
\end{document}
Of course, one can wrap all of this in to a single definition if one wishes:
\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{external}
\tikzexternalize
\usepackage{environ}
\usepackage{xargs}
\newcommandx{\NewEnvironx}[5][2,3]{%
\expandafter\newcommandx\csname start#1\endcsname[#2][#3]{#4}%
\NewEnviron{#1}{\csname start#1\expandafter\endcsname\BODY #5}}
\NewEnvironx{mytikz}[1][1=]{%
\begin{figure}[htp]
\centering
\begin{tikzpicture}[#1]}
{\end{tikzpicture}
\end{figure}}
\begin{document}
\tikzset{external/force remake=true}
\begin{mytikz}[every path/.style={red}]
\draw(0,0) circle (1cm);
\node {hello world};
\end{mytikz}
\end{document}