14

I've created my own environment mytikz, which basically looks like this (full MWE):

\documentclass[a4paper]{article}

\usepackage{tikz}
\usetikzlibrary{external}

\newenvironment{mytikz}{%begin code
    \begin{figure}[htp]
    \centering
    \begin{tikzpicture}%
}%
{%end code
    \end{tikzpicture}
    \caption{}
    \end{figure}%
}

\tikzexternalize

\begin{document}

\begin{mytikz}
    \draw(0,0) circle (1cm);
\end{mytikz}

\end{document}

The problem is that I'd like to use TikZ's external library so I can speed up the building time of my document. This isn't possible, since the environment's end code won't expand in time for TikZ to find the \end{tikzpicture}it's looking for. Therefore I get the following error:

! File ended while scanning use of \tikzexternal@laTeX@collect@until@end@tikzpicture.

Is there a way I can make the end code expand earlier, so TikZ may find it? I've tried to use the environ package, but this reduces the usability of the newenvironment commands (my actual new environment is a bit more complicated, using \newenvironmentx of the xargs package).

1 Answer 1

18

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}
6
  • 3
    wow, Andrew. You're my TeX-hero.. Where'd you learn all of this? (or should I open a new question on that too?). I'm checking this out now. Will probably accept. Many many thanks!!!
    – romeovs
    Apr 12, 2011 at 16:01
  • 1
    @romeovs: It's a combination of trial and error together with the fact that once I get interested in a question then it's hard for me to let it go until it's finally answered. Apr 12, 2011 at 17:26
  • well, consider this one answered!
    – romeovs
    Apr 12, 2011 at 17:32
  • @AndrewStacey great answer! One question: what is the advantage of using xargs instead of the following code? \NewEnviron{mytikz}[1][]{\begin{figure}[htp] \centering \begin{tikzpicture}[#1] \BODY \end{tikzpicture} \end{figure}} See also this question. Apr 23, 2014 at 17:41
  • @MarcoVarisco The original question said that xargs was used, so I answered using it to show how to preserve the facility of xargs with environ. Apr 24, 2014 at 7:04

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .