I'm not sure if it's a well known fact or not, but using the tikz externalization library does not work very well if a LaTeX document is compiled with a jobname that is different from the filename of that document.
For example, if the following MWE is compiled using pdflatex
(note the -jobname output
parameter), it won't work unless the file is named output.tex
(for convenience, let's say it's called main.tex
):
%& -jobname=output
\documentclass{book}
\usepackage{tikz,pgfplots}
\usetikzlibrary{external}
\tikzset{external/only named=true}
\pgfplotsset{compat=newest}
\tikzexternalize
\begin{document}
\begin{figure}[h]
\tikzsetnextfilename{sample_tikz}
\begin{tikzpicture}[scale=1.50]
\begin{axis}\addplot {x^3};\end{axis}
\end{tikzpicture}
\caption{Sample tikz picture.}
\end{figure}
\end{document}
If compiled without the -jobname
parameter, it works just fine, whereas with the parameter, I get an error that looks something like this:
===== 'mode=convert with system call': Invoking 'pdflatex -shell-escape -halt-o
n-error -interaction=batchmode -jobname "sample_tikz" "\def\tikzexternalrealjob
{output}\input{output}"' ========
! Package tikz Error: Sorry, the system call 'pdflatex -shell-escape -halt-on-e
rror -interaction=batchmode -jobname "sample_tikz" "\def\tikzexternalrealjob{ou
tput}\input{output}"' did NOT result in a usable output file 'sample_tikz' (exp
ected one of .pdf:.jpg:.jpeg:.png:).
From the error message, it is also quite obvious what goes wrong. The default externalization system call looks something like this:
pdflatex \tikzexternalcheckshellescape -halt-on-error -interaction=batchmode -jobname "\image" "\texsource"
where the macro \texsource
is \edef
'd as
\string\def\string\tikzexternalrealjob{\tikzexternal@realjob}\string\input{\tikzexternal@realjob}
and \tikzexternal@realjob
is set based on the value of \jobname
used when compiling main.tex
. When main.tex
is compiled with -jobname output
, \texsource
expands into
\def\tikzexternalrealjob{output}\input{output}
which is problematic, because main.tex
isn't called output.tex
.
A possible solution
I've tried to look at the tikz external source code, and experimented with how to approach this problem.
My reasoning has been that I want the \texsource
somehow to expand to
\def\tikzexternalrealjob{output}\input{main}
instead (note that \tikzexternalrealjob
must still be \def
'd to the actual \jobname
, because it's used in the tikz externalization code to decide whether the current job is for the main document or for the externalization of a tikz image).
I was inspired by this post to use the currfile
package (thanks @martin-scharrer) to retrieve the name main
automatically.
What I've come up with so far (which seems to work) is the following:
Change the MWE to:
%& -jobname=output -recorder
\documentclass{book}
\usepackage{tikz,pgfplots}
\usetikzlibrary{external}
\tikzset{external/only named=true}
\pgfplotsset{compat=newest}
\input{externalize-hack.tex} % <---- THIS WAS ADDED
\tikzexternalize
\begin{document}
\begin{figure}[h]
\tikzsetnextfilename{sample_tikz}
\begin{tikzpicture}[scale=1.50]
\begin{axis}\addplot {x^3};\end{axis}
\end{tikzpicture}
\caption{Sample tikz picture.}
\end{figure}
\end{document}
and let the file externalize-hack.tex
be defined as the following:
\usepackage{currfile-abspath}
\getmainfile
\usepackage{xpatch}
\makeatletter
\AtEndDocument{%
\newwrite\maindeffile%
\immediate\openout\maindeffile=\jobname.maindef%
\immediate\write\maindeffile{%
\noexpand\ifcsname mymainfile\noexpand\endcsname\noexpand\else%
\noexpand\gdef\noexpand\mymainfile{\mymainfile}%
\noexpand\fi%
}%
\immediate\closeout\maindeffile%
}
\@input{\jobname.maindef}
\ifcsname mymainfile\endcsname%
\ifx\mymainfile\@empty%
% Make a second attempt at extracting the real file name
% (necessary because currfile needs two runs on MiKTeX..)
\filename@parse{\themainfile}%
\let\mymainfile\filename@base%
\ifx\mymainfile\@empty%
% If still empty, fallback to default behavior
\def\mymainfile{\tikzexternal@realjob}%
\fi%
\fi%
\else%
% First attempt at extracting real file name
\filename@parse{\themainfile}%
\let\mymainfile\filename@base%
\fi%
\xpatchcmd{\tikzexternal@assemble@systemcall}%
{%
\edef\texsource{%
\string\def\string\tikzexternalrealjob{\tikzexternal@realjob}%
\string\input{\tikzexternal@realjob}%
}%
}%
{%
\edef\texsource{%
\string\def\string\tikzexternalrealjob{\tikzexternal@realjob}%
\string\def\string\mymainfile{\mymainfile}%
\string\input{\mymainfile}%
}%
}%
{}
{}
\let\old@tikzexternalize@opt@withname\tikzexternalize@opt@withname%
\def\tikzexternalize@opt@withname[#1]#2{%
% Suppress externalization as long as \mymainfile is empty
% (This will only be the case the one or two first compilation runs!)
\ifx\mymainfile\@empty\else%
\old@tikzexternalize@opt@withname[#1]{#2}%
\fi%
}
\makeatother
Also note the -recorder
option that was added, as required by the currfile
package.
However, to me this hack feels somewhat clumsy. Hence, I would greatly appreciate any input from you guys!
Best Answer
You got that tikz uses
\jobname
to compose the command line that generates external pics. So redefining\jobname
and at the same time using tikz externalization is too much touble.A possible work-around:
\tikzsetfigurename{something}
to set basename for external tikz pictures;pdflatex
without-jobname output
everytime you change a tikzpicture, so it correctly generates figures;pdflatex -jobname output
because only checksums are computed without need of real jobname.It worked with my MWE:
with call sequence:
pdflatex -shell-escape main
(here figuressomethingN
are generated)pdflatex -shell-escape -jobname output main