[Tex/LaTex] Thumbnails of other frames in beamer

beamergraphicshyperrefrecursion

I would like to make a presentation with beamer, and have a few frames (at the start or the end) containing thumbnails of the other frames, hyperlinked to them, with something like 4×3 or 5×4 thumbnails per slide.

The aim would be to allow easy and quick navigation (just press the home or end key of the keyboard, and click on the frame you want to go to).

Result of take 3

The whole document

Animated version of the 28 frames generated by the code below

The thumbnails slide

Note that if there are too many thumbnails, they nicely spill across several slides.

The slide 11, with the first part of the thumbnails

The slide 12, with the second part of the thumbnails

thumbs-3.tex

Compile with:

rm -f thumbs-3-copy.pdf
cp thumbs-3.pdf thumbs-3-copy.pdf
pdflatex thumbs-3.tex

Source:

\documentclass{beamer}
\usepackage{thumbs-3}

\begin{document}

\showthumbs[Welcome]

\begin{frame}\Huge 1\end{frame}

\begin{frame}{With frame title}\Huge 2\end{frame}

\section{Example slides}
\begin{frame}\Huge 3\end{frame}

\subsection{Subsection slides}
\begin{frame}\Huge 4\only<2>{.1}\end{frame}

\subsubsection{Subsubsection slides}
\begin{frame}\Huge 5\end{frame}

\begin{frame}{Subsubsection slide with frame title}\Huge 6\end{frame}

\def\thumbframetitle{Title just for thumbnail}
\begin{frame}{Subsubsection slide with frame title and thumbnail title}\Huge 6\end{frame}
\def\thumbframetitle{}% Stop using a special frame title for thumbnails.

\showthumbs

\section{Random slides}
% Generate some random slides
\begin{onethumb}[2]
  \foreach \i in {7,...,10} {
    \begin{frame}
      \begin{center}
        \begin{tikzpicture}
          \foreach \c in {0,...,3} {
            \pgfmathsetmacro\r{0.6*rnd+0.3}
            \pgfmathsetmacro\g{0.6*rnd+0.3}
            \pgfmathsetmacro\b{0.6*rnd+0.3}
            \definecolor{CircleColor}{rgb}{\r,\g,\b}
            \node[circle, fill=CircleColor, minimum size=rnd*2cm] at (rnd*5cm-2.5cm, rnd*4cm-2cm) {};
          }
          \node at (0,0) {\Huge\i};
          \path[use as bounding box] (-3.5cm,-3cm) rectangle (3.5cm,3cm);
        \end{tikzpicture}
      \end{center}
    \end{frame}
  }
\end{onethumb}

\foreach \i in {11,...,18} {
  \begin{frame}{Random slide \i}\Huge\i\end{frame}
}

\begin{frame}[allowframebreaks]{Testing allowframebreaks}
  \begin{itemize}
  \item A
  \foreach \i in {1,...,20} {
    \item Very \i
  }
  \item Long
  \item List
  \end{itemize}
\end{frame}

\showthumbs[Any questions?]

\end{document}

thumbs-3.sty

\usepackage{morewrites}
\usepackage{graphicx}
\usepackage{tikz}
\usepackage{etextools}
% https://tex.stackexchange.com/q/40297/5699
\newcommand\addtofrml[1]{%
  \write\@auxout{\noexpand\@writefile{frml}{\noexpand #1}}%
}
\newcommand{\loadframelist}{\ifx\thumbtitles\undefined\xdef\thumbtitles{}\fi\@starttoc{frml}}
\newcounter{thumbid}
\setcounter{thumbid}{0}
\newif\ifaddthumb
\newif\ifaddcomma\addcommafalse
\def\testaddthumb{\addthumbtrue}
% Hook at the end of each frame (could work if hooked at the beginning I guess).
\let\thumbframetitle\empty

\setbeamertemplate{background}{%
  \addtocounter{thumbid}{1}%
  \testaddthumb%
  \ifaddthumb%
  % Get either the frametitle, current (sub)(sub)section title, or just "Frame 42"
  \xdef\thumbtitle{}%
  \ifx\thumbtitle\empty\ifx\thumbframetitle\empty\else\xdef\thumbtitle{\thumbframetitle}\fi\fi%
  \ifx\thumbtitle\empty\ifx\beamer@frametitle\empty\else\xdef\thumbtitle{\beamer@frametitle}\fi\fi%
  \ifx\thumbtitle\empty\ifx\subsubsecname\undefined\else\xdef\thumbtitle{\subsubsecname}\fi\fi%
  \ifx\thumbtitle\empty\ifx\subsecname\undefined\else\xdef\thumbtitle{\subsecname}\fi\fi%
  \ifx\thumbtitle\empty\ifx\secname\undefined\else\xdef\thumbtitle{\secname}\fi\fi%
  \ifx\thumbtitle\empty \xdef\thumbtitle{Frame \arabic{thumbid}}\fi%
  % Append ",42/Frame Title" to \thumbtitles
  \ifaddcomma%
  \addtofrml{%
    \xdef\noexpand\thumbtitles{\noexpand\thumbtitles,\arabic{thumbid}/\thumbtitle}%
  }%
  \else%
  \addtofrml{%
    \xdef\noexpand\thumbtitles{\arabic{thumbid}/\thumbtitle}%
  }%
  \global\addcommatrue%
  \fi%
  \fi%
}

% Should take an overlay specification, but I don't know how to handle these.
\newenvironment{onethumb}[1][1]{%
  \gdef\onethumbcount{0}%
  \gdef\onethumbgoal{#1}%
  \gdef\testaddthumb{%
    \count@=\onethumbcount%
    \advance\count@ by 1%
    \xdef\onethumbcount{\the\count@}%
    \ifnum\onethumbcount=\onethumbgoal\relax\addthumbtrue\else\addthumbfalse\fi%
  }%
}{%
  \gdef\testaddthumb{\addthumbtrue}%
}

\newenvironment{nothumbs}{
  \gdef\testaddthumb{\addthumbfalse}
}{
  \gdef\testaddthumb{\addthumbtrue}
}

\newcommand\showthumbs[1][]{
  {
    \setbeamertemplate{frametitle continuation}{}
    \begin{nothumbs}
      \begin{frame}[allowframebreaks]{#1}
        \pgfmathsetmacro\thumbwidth{0.2*\linewidth}
        \pgfmathsetmacro\thumbheight{\thumbwidth/\paperwidth*\paperheight}
        \loadframelist
        \begin{center}
          \foreach \thid/\thtitle in \thumbtitles {%
            \hyperlink{page.\thid}{%
              % https://tex.stackexchange.com/a/39601/5699
              \begin{tikzpicture}[baseline={([yshift={-\ht\strutbox}]current bounding box.north)}]
                \node[draw, inner sep=0pt] (thumb) {
                  \IfFileExists{\jobname-copy.pdf}{%
                    \includegraphics[width=\thumbwidth pt, height=\thumbheight pt, page=\thid] {\jobname-copy.pdf}
                  }{%
                    \begin{minipage}[t][\thumbheight pt]{\thumbwidth pt}\vfill No thumbnail\vfill\end{minipage}
                  }%
                };
                \node[anchor=north, font=\tiny, inner xsep=0pt, inner ysep=0.1cm, yshift=-0.05cm, text width=\thumbwidth pt, align=center] at (thumb.south) {\thtitle};
              \end{tikzpicture}%
            } % space here, outside of hyperlink
          }%
        \end{center}
      \end{frame}
    \end{nothumbs}
  }
}

Take 1

Here's a first (a posteriori, added long after the question was asked) attempt :

  • Use tikz's \foreach loop to produce a series of \includegraphics including pages of the PDF generated by the previous compilation.
  • The totcount package should help knowing the range of frames (pages) to include.(edit: Instead I wrote the titles and frame numbers of each frame that has a thumbnail.)
  • \hyperlink{page.42}{\includegraphics...} should turn those thumbnails to hyperlinks (from this TeX.SX answer).

thumbs-1.tex:

\documentclass{beamer}
\usepackage{thumbs-1}

\begin{document}

\showthumbs inserts one or more frames containing thumbnails of the other frames, hyperlinked to the actual frame. It can be called at the beginning of the document:

\showthumbs

Some slides with and without \frametitles, inside and outside of various levels of (sub)(sub)sections:

\begin{frame}\Huge 1\end{frame}
\begin{frame}{With frame title}\Huge 2\end{frame}
\section{Example slides}
\begin{frame}\Huge 3\end{frame}
\subsection{Subsection slides}

A frame with an ovelay:

\begin{frame}\Huge 4\only<2>{.1}\end{frame}
\subsubsection{Subsubsection slides}
\begin{frame}\Huge 5\end{frame}
\begin{frame}{Subsubsection slide with frame title}\Huge 6\end{frame}
\section{Random slides}

\showthumbs can be called multiple times, and in the middle of the document:

\showthumbs

Some random slides, with a single thumbnail (for the second in the group):

\begin{onethumb}[2]
  \foreach \i in {7,...,10} {
    \begin{frame}
      \begin{center}
        \begin{tikzpicture}
          \foreach \c in {0,...,3} {
            \pgfmathsetmacro\r{0.6*rnd+0.3}
            \pgfmathsetmacro\g{0.6*rnd+0.3}
            \pgfmathsetmacro\b{0.6*rnd+0.3}
            \definecolor{CircleColor}{rgb}{\r,\g,\b}
            \node[circle, fill=CircleColor, minimum size=rnd*2cm] at (rnd*5cm-2.5cm, rnd*4cm-2cm) {};
          }
          \node at (0,0) {\Huge\i};
          \path[use as bounding box] (-3.5cm,-3cm) rectangle (3.5cm,3cm);
        \end{tikzpicture}
      \end{center}
    \end{frame}
  }
\end{onethumb}

Other dummy slides, with one thumbnail each:

\foreach \i in {11,...,20} {
  \begin{frame}{Dummy slide \i}\Huge\i\end{frame}
}

\showthumbs can be called multiple times, and at the end of the document:

\showthumbs

\end{document}

thumbs-1.sty:

\usepackage{graphicx}
\usepackage{tikz}
\usepackage{etextools}

Using information from this question, write a list of frame numbers and frame titles to the aux file:

\newcommand\addtofrml[1]{%
  \write\@auxout{\noexpand\@writefile{frml}{\noexpand #1}}%
}
\newcommand{\loadframelist}{\ifx\thumbtitles\undefined\xdef\thumbtitles{}\fi\@starttoc{frml}}
\newcounter{thumbid}
\setcounter{thumbid}{0}
\newif\ifaddthumb
\def\testaddthumb{\addthumbtrue}

Hook at the end of each frame (could work if hooked at the beginning I guess):

\setbeamertemplate{background}{%
  \addtocounter{thumbid}{1}%
  \testaddthumb%
  \ifaddthumb%

The thumbails are labeled with the \frametitle, susubsection title, subsection title, section title or "Frame ", in that order of preference:

  \xdef\thumbtitle{%
    \ifx\beamer@frametitle\empty%
    \ifx\insertsubsubsectionhead\empty%
    \ifx\insertsubsectionhead\empty%
    \ifx\insertsectionhead\empty%
    Frame \arabic{thumbid}%
    \else\insertsectionhead\fi%
    \else\insertsubsectionhead\fi%
    \else\insertsubsubsectionhead\fi%
    \else\beamer@frametitle\fi%
  }%

Append ,42/Frame Title to the \thumbtitles macro:

  \ifaddcomma
  \addtofrml{%
    \xdef\noexpand\thumbtitles{\noexpand\thumbtitles,\arabic{thumbid}/\thumbtitle}%
  }%
  \else
  \addtofrml{%
    \xdef\noexpand\thumbtitles{\arabic{thumbid}/\thumbtitle}%
  }%
  \global\addcommatrue
  \fi
  \fi%
}

\begin{onethumb}[42]…\end{onethumb} allows one to have just one thumbnail (of the 42th frame in the group, one by default) for a group of frames. This should really use beamer's overlay specification, to allow having a thumbnail for frames 2 to 4 and 6 by typing \begin{onethumb}<2-4,6>…\end{onethumb}, but I haven't implemented that yet.

\newenvironment{onethumb}[1][5]{%
  \gdef\onethumbcount{0}%
  \gdef\onethumbgoal{#1}%
  \gdef\testaddthumb{%
    \count@=\onethumbcount%
    \advance\count@ by 1%
    \xdef\onethumbcount{\the\count@}%
    \ifnum\onethumbcount=\onethumbgoal\relax\addthumbtrue\else\addthumbfalse\fi%
  }%
}{%
  \def\testaddthumb{\addthumbtrue}%
}

\begin{nothumbs}…\end{nothumbs} disables the creation of thumbnails for the enclosed frames.

\newenvironment{nothumbs}{
  \def\testaddthumb{\addthumbfalse}
}{
  \def\testaddthumb{\addthumbtrue}
}

\def\showthumbs{

The frames containig the thumbnails, generated by \showthumbs, are enclosed in \begin{nothumbs}…\end{nothumbs}, otherwise after the second compilation, they would contain a copy of themselves, and more recursively at each compilation, so the PDF would grow larger and larger.

  \begin{nothumbs}
    \begin{frame}[allowframebreaks]
      \pgfmathsetmacro\thumbwidth{0.2*\linewidth}
      \pgfmathsetmacro\thumbheight{\thumbwidth/\paperwidth*\paperheight}
      \loadframelist
      \begin{center}

Loop over the \thumbtitles macro defined in the .aux/.frml file.

        \foreach \thid/\thtitle in \thumbtitles {%
          \hyperlink{page.\thid}{%

Adjust vertical alignment of the tikzpicture containing the thumbnail and its label using this answer:

            \begin{tikzpicture}[baseline={([yshift={-\ht\strutbox}]current bounding box.north)}]

The framed and scaled thumbnail:

              \node[draw, inner sep=0pt] (thumb) {%
                \includegraphics[width=\thumbwidth pt, height=\thumbheight pt, page=\thid] {\jobname.pdf}%
              };

The label, below the thumbnail:

              \node[anchor=north, font=\tiny, inner xsep=0pt, inner ysep=0.1cm, yshift=-0.05cm, text width=\thumbwidth pt, align=center] at (thumb.south) {\thtitle};
            \end{tikzpicture}%

Insert a space between each thumbnail, but only one (the rest of the lines that matter swpace-wise end with a %). The space must be outside the \hyperlink, as explaind in this answer to my follow-up question.

          } % space here, outside of hyperlink
        }%
      \end{center}
    \end{frame}
  \end{nothumbs}
}

What I don't know is how to run the \includegraphics commands only if the PDF exists, like during first compilation. Running the \includegraphics commands then would cause an error, and no PDF would be generated because of the error… Chicken and egg problem 🙂 .

I'd appreciate a solution to this last problem (running \includegraphics commands only if the PDF exists), or any other approach to automatically generating those slides.

Take 2

With this answer, I can run \includegraphics commands conditionnally.

I simply replace this code (at the end of the .sty):

\node[draw, inner sep=0pt] (thumb) {%
  \includegraphics[width=\thumbwidth pt, height=\thumbheight pt, page=\thid] {\jobname.pdf}%
};

By this one:

\node[draw, inner sep=0pt] (thumb) {%
  \IfFileExists{\jobname.pdf}{%
    \includegraphics[width=\thumbwidth pt, height=\thumbheight pt, page=\thid] {\jobname.pdf}%
  }{%
    \begin{minipage}[t][\thumbheight pt]{\thumbwidth pt}\vfill No thumbnail\vfill\end{minipage}%
  }%
};

I now have another problem: When pdflatex is generating the document, it overwrites the pdf from which \includegraphics will try to read.

MWE:

\documentclass{beamer}
\usepackage{graphicx}
\usepackage{tikz}
\begin{document}

\begin{frame}\Huge 1\end{frame}
\begin{frame}\Huge 2\end{frame}
\begin{frame}\Huge 3\end{frame}
\begin{frame}\Huge 4\end{frame}

\begin{frame}
  \foreach \i in {1,...,4} {
    \IfFileExists{\jobname.pdf}{%
      \fbox{\includegraphics[width=0.2\linewidth, page=\i] {\jobname.pdf}}%
    }{No thumbnail}
  }
\end{frame}

\end{document}

When I run the above example, I get the following error, since \jobname.pdf has already been opend for writing and truncated by pdflatex:

Error: PDF file is damaged - attempting to reconstruct xref table...
Error: Couldn't find trailer dictionary
Error: Couldn't read xref table

!pdfTeX error: pdflatex (file ./thumbs.pdf): xpdf: reading PDF image failed

Take 3

If instead, I manually copy \jobname.pdf to \jobname-copy.pdf (edit: changed .copy.pdf to -copy.pdf because it faild for me sometimes) after each compilation, and use the following code in the .sty, it works fine:

\node[draw, inner sep=0pt] (thumb) {%
  \IfFileExists{\jobname-copy.pdf}{%
    \includegraphics[width=\thumbwidth pt, height=\thumbheight pt, page=\thid] {\jobname-copy.pdf}%
  }{%
    \begin{minipage}[t][\thumbheight pt]{\thumbwidth pt}\vfill No thumbnail\vfill\end{minipage}%
  }%
};

Compilation needs to be done this way:

rm -f thumbs-3-copy.pdf
cp thumbs-3.pdf thumbs-3-copy.pdf
pdflatex thumbs-3.tex

When the number of frames in the document decreases, during the next (second) compilation, the \includegraphics shall fail, preventing the generation of the PDF. Then, the thumbs-3-copy.pdf file is removed, and a new copy fails to be created (because the PDF generation failed). So at the next (third) compilation, since the thumbs-3-copy.pdf file is absent, no\includegraphics is attempted, and the generation succeeds. At the next (fourth) compilation, the thumbnails are finally created.

If you don't understand the above, try reducing the number of frames from 5 to 2 (for example), without running the rm command: compilation will fail permanently. With the rm, everything is settled after something like 4 compilations.

Take 4

I tried to automatically copy the pdf (using this TeX.SX question's answers), but even if I run the copy just after \documentclass{beamer}, the copy is still a truncated file (the code breaks if I move the copy code before the documentclass, but I don't think it will make a difference). Here's the code doing the copy:

\documentclass{beamer}
\newread\in%
\openin\in=\jobname.pdf%
\newwrite\out%
\immediate\openout\out\jobname-copy.pdf%
\endlinechar-1%
\loop \unless\ifeof\in%
        \readline\in to\l%
        \immediate\write\out{\l}%
\repeat%
\immediate\closeout\out%
\closein\in%
\usepackage{graphicx}
\usepackage{tikz}
\begin{document}

\begin{frame}\Huge 1\end{frame}
\begin{frame}\Huge 2\end{frame}
\begin{frame}\Huge 3\end{frame}
\begin{frame}\Huge 4\end{frame}

\begin{frame}
  \foreach \i in {1,...,4} {
    \IfFileExists{\jobname-copy.pdf}{\fbox{\includegraphics[width=0.2\linewidth, page=\i] {\jobname-copy.pdf}}}{No thumbnail}
  }
\end{frame}

\end{document}

So I'd like to know if it's possible to tell (La)TeX to backup the old PDF file before generating a new one, using only (La)TeX code.

Take 5

We thought this wasn't possible, as pdflatex had already opened and truncated the file by the time it is copied, which results in a copy containing a single newline byte.

We were wrong.

pdflatex doesn't open the output file until it has something to do with it, and it won't open it before the \documentclass{beamer} (actually, it doesn't even open it after a \documentclass{article}).

So, by moving the copy code above the \documentclass{beamer}, and by adding \endlinechar13 to prevent beamer from breaking, one can copy the pdf file.

\newread\in%
\openin\in=\jobname.pdf%
\newwrite\out%
\immediate\openout\out\jobname-copy.pdf%
\endlinechar-1%
\loop \unless\ifeof\in%
\readline\in to\l%
\immediate\write\out{\l}%
\repeat%
\immediate\closeout\out%
\closein\in%
\endlinechar13%
\documentclass{beamer}
\usepackage{graphicx}
\usepackage{tikz}
\begin{document}

\begin{frame}\Huge 1\end{frame}

\end{document}

However, the copied PDF is damaged, because when \read reads a line, it drops any trailing spaces, which are important in this case.

Take 6

I asked how to read from a file including trailing spaces, but the answers are disapointing~:

  • No hope with plain TeX.
  • Pdf(La)Tex provides \pdffiledump, which solves the reading problem, but the way non-ASCII bytes are written (when you write to the copy) is implementation-dependant.
  • One can perform the copy in lua, when using Lua(La)Tex, but not everybody uses Lua(La)Tex.

Conclusion

So it seems the best solution when using \includegraphics is either to copy the PDF manually, or use Lua(La)Tex (wich is the future, but not as widespread as it "should" I guess, see this very interesting interview of Andrew Stacey on TeX.SX's [http://tex.blogoverflow.com/](community blog)).

I have provided as an answer below a proof of concept that re-\inputs the tex file, and generates the thumbnails without relying on the PDF from a previous compilation, but it's much more fragile than the \includegraphics version.

References

(Non-exhaustive) list of stuff I used/didn't use while working on this:

Best Answer

You can test with the macro \IfFileExist{filename}{then-do-this}{else-do-this} whether a file filename exists or not and respond accordingly. If the file filename exists, the then-do-this branch is executed, otherwise the else-do-this branch. Replace then-do-this and else-do-this with your own code.

In your new given MWE I would delete thr part to copy the pdf file and change the part to build the thumbnail slide to (supposing you have 4 thumbnails to include):

\IfFileExists{\jobname-copy.pdf}{%
\begin{frame}
  \foreach \i in {1,...,4} {
    \fbox{\includegraphics[width=0.2\linewidth, page=\i] {\jobname-copy.pdf}}
  }
\end{frame}
}{\typeout{No Thumbnails included}}

Now you only need to do (you can put this into a batch file):

pdflatex myslides.tex
pdflatex myslides.tex
cp myslides.pdf myslides-copy.pdf
pdflatex myslides.tex

The thumbnail were only included if the file myslides-copy.pdf exists. If not file myslides.tex compiles without included thumbnails.

Update 2:

The reason for the error message you got

Error: PDF file is damaged - attempting to reconstruct xref table...

is simple: you want to copy the file myslides.pdf you have already opened to compile it. That's not possible. So you have to first compile the presentation to get the frames and to close this file myslides.pdf. Then copy the file myslides.pdf to myslides-copy.pdf to include the thumbnails from myslides-copy.pdf into a new compiled file myslides.pdf.