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
The thumbnails slide
Note that if there are too many thumbnails, they nicely spill across several slides.
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(edit: Instead I wrote the titles and frame numbers of each frame that has a thumbnail.)totcount
package should help knowing the range of frames (pages) to include.\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 \frametitle
s, 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-\input
s 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:
- Overlay specifications:
- Generating the list of slides:
- Create a list of beamer slides
- How to get the current (sub)(sub)section name: What variable current section name? on latex-community.org
- Choose between the frame title, (sub)(sub)section and default string: What is the difference between \empty and @empty?
- Use a loop to generate a list for another (foreach) loop
- Alignment of the thumbnails (with their text): https://tex.stackexchange.com/a/39601/5699
- PDF hyperlinks to a given page
- File copy:
- File copy without expansion
- Flush latex file io buffers
- Include binary data
- How to copy contents of one file to another?
- My own question: \read: trailing spaces are removed, with two excellent answers
\write
non-printable ASCII characters to a file- In a desperate attempt, I wanted to change the
\jobname
half of the time, but it won't affect the PDF file name: How to influence the name of the pdf file created with pdfLaTeX (from within the source code)?
- Andrew Stacey's interview
\tracingall
(I keep forgetting the name of that one !)
Best Answer
You can test with the macro
\IfFileExist{filename}{then-do-this}{else-do-this}
whether a filefilename
exists or not and respond accordingly. If the filefilename
exists, thethen-do-this
branch is executed, otherwise theelse-do-this
branch. Replacethen-do-this
andelse-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):Now you only need to do (you can put this into a batch file):
The thumbnail were only included if the file
myslides-copy.pdf
exists. If not filemyslides.tex
compiles without included thumbnails.Update 2:
The reason for the error message you got
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 filemyslides.pdf
. Then copy the filemyslides.pdf
tomyslides-copy.pdf
to include the thumbnails frommyslides-copy.pdf
into a new compiled filemyslides.pdf
.