Why does tikz produce output in plain tex when it does not in latex?

Short answer:

  • \input tikz loads the file tikz.tex, while \usepackage{tikz} loads the file tikz.sty. Those files are different, and in turn load different files.
  • In particular, pgfrcs.tex has \input pgfutil-plain.def while pgfrcs.sty has \input pgfutil-latex.def
  • The code in pgfutil-plain.def has \openout which results in the creation of a whatsit node (and hence “output”, and the DVI file), while the code in pgfutil-latex.def piggybacks on LaTeX's aux file, which does \immediate\openout instead.

How to isolate the issue

(This section just describes how the above was arrived at.)

This is what I tried. By looking in the log file (or using kpsewhich), we can find the specific file that is being input, in the two cases. We can copy that file over to the current directory, and start editing it into something minimal: remove as much from it as possible, while the files still compile and exhibit the difference. They will probably input other files; copy them as well and repeat.

By doing this recursively, I was able to reduce the two cases to the following:

% tex testtex.tex                        
This is TeX, Version 3.14159265 (TeX Live 2017) (preloaded format=tex)
(./testtex.tex (./tikz.tex (./pgf.tex (./pgfcore.tex (./pgfsys.tex (./pgfrcs.tex (./pgfutil-common.tex) (./pgfutil-plain.def)) (./pgfsys.code.tex))))) [1] )
Output written on testtex.dvi (1 page, 196 bytes).
Transcript written on testtex.log.

where most of the files simply \input the others as indicated in the log line above. The nontrivial files (after minimizing) are pgfrcs.tex:

\input pgfutil-common.tex
\input pgfutil-plain.def

pgfutil-common.tex:

\catcode`\@=11\relax

% Which format is loaded?
\newif\ifpgfutil@format@is@latex
\newif\ifpgfutil@format@is@plain

\let\pgfutil@aux@read@hook=\relax

\newtoks\pgfutil@everybye

pgfutil-plain.def:

\pgfutil@format@is@plaintrue

% The aux files, needed for reading back coordinates
\def\pgfutil@aux@read@hook{
  \csname newwrite\endcsname\pgfutil@auxout
  \csname openout\endcsname\pgfutil@auxout\jobname.pgf
}

pgfsys.code.tex:

% Read aux file in plain and context mode:
\pgfutil@aux@read@hook

Summary

The file pgfrcs.tex has \input pgfutil-plain.def while the file pgfrcs.sty has \input pgfutil-latex.def, and those two .def files contain substantial differences.

In particular, in the plain TeX case, at minimum we're running the following:

\catcode`\@=11\relax

%%%%%%%%%% From pgfutil-plain.def < pgfrcs.tex < pgfsys.tex < pgfcore.tex < pgf.tex < tikz.tex
% The aux files, needed for reading back coordinates
\def\pgfutil@aux@read@hook{
  \csname newwrite\endcsname\pgfutil@auxout
  \csname openout\endcsname\pgfutil@auxout\jobname.pgf
}

%%%%%%%%%% From pgfsys.code.tex < pgfsys.tex < pgfcore.tex < pgf.tex < tikz.tex
\pgfutil@aux@read@hook

\end

and that's already enough to result in the creation of the .dvi file.

(The answer of @egreg explains the further contents of the .dvi file—the PGF-related specials, that are needed to go into the PDF dictionary on every page—but not why a page is shipped out in the first place. To check this, we can simply remove the line \csname openout\endcsname\pgfutil@auxout\jobname.pgf from pgfutil-plain.def, and make absolutely no other changes, and see that your original testtex.tex does not result in the creation of a DVI file, even though the stuff that @egreg showed is still present in pgfutil-plain.def.)

What the above shows is that we can find the difference already between:

\input pgfsys
\end

(in plain TeX) and (in LaTeX):

\documentclass{article}
\usepackage{pgfsys}
\begin{document}
\end{document}

Also, here's an even more minimal plain-TeX file that results in the creation of a DVI file:

\newwrite\outfile
\openout\outfile\jobname.pgf
\end

In the LaTeX case (in pgfutil-latex.def), there is a similar \AtBeginShipout and everything, but there's no \openout. Instead, it piggybacks on LaTeX's \@auxout. That one is defined, in latex.ltx (the definition of \document), using \immediate\openout\@mainaux\jobname.aux. And we can see that simply adding \immediate to our earlier minimal file does not result in the creation of a DVI file:

\newwrite\outfile
\immediate\openout\outfile\jobname.pgf
\end

At some point, \pgfutil@abe is executed, which issues \unhbox.

Here's the justification:

272 \AtBeginShipout{% 
273   \setbox\AtBeginShipoutBox=\vbox{% 
274     \setbox0=\hbox{% 
275       \begingroup   
276                 % the boxes \pgfutil@abe ("every page") and \pgfutil@abb ("current page")
277                 % are used to generate pdf objects / dictionaries which are
278                 % required for the graphics which are somewhere in the "real"
279                 % page content.
280                 % BUT: these pdf objects MUST NOT be affected by text layout
281                 % shifts! Consequently, we have to undo \hoffset and \voffset
282                 % (which are h/v shifts to the page layout).
283                 %
284                 % Note that this of importance for shadings. To be more
285                 % specific: try out shadings with standalone (which uses
286                 % \hoffset) and with xdvipdfmx (which appears to be more
287                 % fragile than pdflatex) - they break unless we undo \hoffset
288                 % and \voffset.
289                 \ifdim\hoffset=0pt \else \hskip-\hoffset\fi
290         \pgfutil@abe 
291         \unhbox\pgfutil@abb 
292         \pgfutil@abc 
293         \global\let\pgfutil@abc\pgfutil@empty 
294                 \ifdim\hoffset=0pt \else \hskip+\hoffset\fi
295       \endgroup 
296     }%