Beamer 覆盖、tikz 外部和自定义文件名

Beamer 覆盖、tikz 外部和自定义文件名

这是来自使用 tikz 外部功能与 beamer \only。安德鲁的回答非常出色,几乎完美地完成了任务。

\tikzsetnextfilename当我尝试与它一起使用时,我发现边缘很粗糙。

我为什么要这么做?因为在 beamer 中,可能需要重新排序幻灯片,或添加覆盖等。在这种情况下,即使图片没有改变,大多数图片也需要重新生成。

问题是什么?下面的 M(n)WE 展示了使用 的通常方式\tikzsetnextfilename

\documentclass{beamer}

\usepackage{tikz}

\usetikzlibrary{external}

\makeatletter
\tikzset{
  beamer externalizing/.style={%
    execute at end picture={%
      \tikzifexternalizing{%
        \ifbeamer@anotherslide
        \pgfexternalstorecommand{\string\global\string\beamer@anotherslidetrue}%
        \fi
      }{}%
    }%
  },
  external/optimize=false
}
\makeatother

\tikzset{every picture/.style={beamer externalizing}}

\tikzexternalize

\begin{document}

\begin{frame}
  \tikzsetnextfilename{figure}
  \only<1>{Image 1:}
  \only<2>{Image 2:}
  \begin{tikzpicture}
    \only<1>{\node {Overlay 1};}
    \only<2>{\node {Overlay 2};}
  \end{tikzpicture}
\end{frame}

\end{document}

这不管用。我对 latex、beamer 和 tikz 机制不够熟悉,但我怀疑问题在于tikz externalize尝试生成具有相同文件名的两张图片。

事实上,下面的代码片段确实有效。

\documentclass{beamer}

\usepackage{tikz}

\usetikzlibrary{external}

\makeatletter
\tikzset{
  beamer externalizing/.style={%
    execute at end picture={%
      \tikzifexternalizing{%
        \ifbeamer@anotherslide
        \pgfexternalstorecommand{\string\global\string\beamer@anotherslidetrue}%
        \fi
      }{}%
    }%
  },
  external/optimize=false
}
\makeatother

\tikzset{every picture/.style={beamer externalizing}}

\tikzexternalize

\begin{document}

\begin{frame}
  \only<1>{\tikzsetnextfilename{figure-1}}
  \only<2>{\tikzsetnextfilename{figure-2}}
  \only<1>{Image 1:}
  \only<2>{Image 2:}
  \begin{tikzpicture}
    \only<1>{\node {Overlay 1};}
    \only<2>{\node {Overlay 2};}
  \end{tikzpicture}
\end{frame}

\end{document}

现在它还不够完善,比如需要精确知道每张图片需要多少个叠加层。

因此问题是:有没有更好、更自然的方法来实现这一点?如果没有,有没有办法让这种方法对用户透明(这样用户就不需要自己计算覆盖层了)?

答案1

您可以使用以下代码自动将覆盖编号添加到文件名中访问 beamer 中的当前覆盖编号。如果您只想对某些图片执行此操作,请使用如下方法:

  \tikzsetnextfilename{figure-\overlaynumber}

要将其添加到每张图片中,您可以将其作为前缀的一部分:

\tikzexternalize[prefix=picture-\overlaynumber-]

使用后缀表示法, ( \tikzset{external/figure name/.add={}{-\overlaynumber}}) 似乎不起作用,因为figure name被 覆盖\tikzsetnextfilename。如果要自动添加后缀,则必须破解\tikzsetnextfilename

\let\orig@tikzsetnextfilename=\tikzsetnextfilename
\renewcommand\tikzsetnextfilename[1]{\orig@tikzsetnextfilename{#1-\overlaynumber}}

(在\makeatletter ... \makeatother钻头内部)。

尽管这种方法最为黑客化,但最终却得到了最漂亮的文件名,因此我选择了这种方法。

\documentclass{beamer}
%\url{https://tex.stackexchange.com/q/119428/86}
\usepackage{tikz}

\usetikzlibrary{external}

\makeatletter
\newcommand*{\overlaynumber}{\number\beamer@slideinframe}
\tikzset{
  beamer externalizing/.style={%
    execute at end picture={%
      \tikzifexternalizing{%
        \ifbeamer@anotherslide
        \pgfexternalstorecommand{\string\global\string\beamer@anotherslidetrue}%
        \fi
      }{}%
    }%
  },
  external/optimize=false
}
\let\orig@tikzsetnextfilename=\tikzsetnextfilename
\renewcommand\tikzsetnextfilename[1]{\orig@tikzsetnextfilename{#1-\overlaynumber}}
\makeatother

\tikzset{every picture/.style={beamer externalizing}}

\tikzexternalize

\begin{document}

\begin{frame}
  \tikzsetnextfilename{figure}
  \only<1>{Image 1:}
  \only<2>{Image 2:}
  \begin{tikzpicture}
    \only<1>{\node {Overlay 1};}
    \only<2>{\node {Overlay 2};}
  \end{tikzpicture}
\end{frame}

\end{document}

答案2

作为 Andrew 出色回答的扩展,可以采用他的解决方案来克服原始方法使用带有 tikz 外部的 beamer 叠加的主要瓶颈。原始解决方案需要全局禁用 tikz 外部优化(即external/optimize=false在序言中设置)。因此,在外部化任何 tikzpicture 期间,文档中的所有其他 tikzpicture 也会排版,即使它们最终将被丢弃。对于包含许多 tikzpicture 的文档,这会花费大量的编译时间。

作为替代方案,我们可以使用\tikzsetnextfilename宏来本地禁用 tikz 外部优化,仅针对要外部化的 tikzpicture 和所有具有相同基本名称的先前 tikzpicture。这将大大加快具有许多 tikzpicture 的演示文稿的速度。

\documentclass{beamer}

\usepackage{tikz}
\usetikzlibrary{external}

\makeatletter
    \newcommand*{\overlaynumber}{\number\beamer@slideinframe}
    \tikzset{
      beamer externalizing/.style={%
        execute at end picture={%
          \tikzifexternalizing{%
            \ifbeamer@anotherslide
            \pgfexternalstorecommand{\string\global\string\beamer@anotherslidetrue}%
            \fi
          }{}%
        }%
      },
      every picture/.style={beamer externalizing}
    }
    
    % remove the `-\overlaynumber` suffix from the jobname
    % the name is stored in `\job@no@suffix` and the number in `\job@suffix@number`
    \def\strip@suffix#1{%
        \begingroup%
        \edef\@tempa{#1-}%
        \expandafter\endgroup%
        \expandafter\@strip@suffix\@tempa\relax%
    }
    \def\@strip@suffix#1-#2\relax{%
        \ifx\relax#2\relax%
            \edef\job@suffix@number{#1}%
            \def\next{\relax}%
        \else%
            \expandafter\ifx\expandafter\relax\job@no@suffix\relax%
                \def\job@no@suffix{#1}%
            \else%
                \edef\job@no@suffix{\job@no@suffix-#1}%
            \fi%
            \def\next{\@strip@suffix#2\relax}%
        \fi%
        \next%
    }
    % check if the next tikzpicture is the to-be-externalized one
    % or has the same basename and precedes it
    \def\if@is@current@pic#1{%
        \begingroup%
        \def\job@no@suffix{}%
        \strip@suffix\pgfactualjobname%
        \ifnum\job@suffix@number<\overlaynumber\relax%
            \def\job@no@suffix{}%
        \fi%
        \edef\current@pic@basename{#1}%
        \edef\current@pic@basename{%
            \expandafter\detokenize\expandafter{\current@pic@basename}%
        }%
        \ifx\job@no@suffix\current@pic@basename%
            \expandafter\endgroup\expandafter\@firstoftwo%
        \else%
            \expandafter\endgroup\expandafter\@secondoftwo%
        \fi%
    }
    
    % add a `-\overlaynumber` suffix to the next tikzpicture name
    % and disable tikz external optimization where needed
    \let\orig@tikzsetnextfilename=\tikzsetnextfilename
    \renewcommand\tikzsetnextfilename[1]{%
        \orig@tikzsetnextfilename{#1-\overlaynumber}%
        \tikzifexternalizing{
            \if@is@current@pic{\tikzexternal@filenameprefix#1}{%
                \tikzset{external/optimize=false}%
            }{%
                \tikzset{external/optimize=true}%
            }%
        }{}%
    }
\makeatother

\tikzexternalize[only named=true]

\begin{document}

\begin{frame}
    \tikzsetnextfilename{figure}
    \only<1>{Image 1:}
    \only<2>{Image 2:}
    \begin{tikzpicture}
        \only<1>{\node {Overlay 1};}
        \only<2>{\node {Overlay 2};}
    \end{tikzpicture}
\end{frame}

\end{document}

答案3

我对解决方案做了一些优化https://tex.stackexchange.com/a/119440,主要是为了避免在不改变的情况下多次生成相同的图形。不幸的是,它不是很自动化,仍然需要一些手动输入。

\usepackage{etoolbox}
\makeatletter
\newcounter{tikznamebeamer@tempcnt}
\def\tikznamebeamer@finalidx{}
\newcommand{\tikznamebeamer}[2][figure]{%
    \def\tikznamebeamer@slidesetidx{%
        \setcounter{tikznamebeamer@tempcnt}{0}%
        \let\tikznamebeamer@finalidx=0%
        \renewcommand*{\do}[1]{%
            \stepcounter{tikznamebeamer@tempcnt}%
            \let\tikznamebeamer@templist=\relax%
            \forcsvlist{\listadd\tikznamebeamer@templist}{####1}%
            \xifinlist{\number\beamer@slideinframe}{\tikznamebeamer@templist}{%
                \edef\tikznamebeamer@finalidx{\thetikznamebeamer@tempcnt}%
            }{}%
        }%
        \docsvlist{#2}%
    }%
    \tikznamebeamer@slidesetidx%
    \tikzsetnextfilename{#1-\tikznamebeamer@finalidx}%
}
\makeatother

这个想法是指定图形不变的框架集。因此,在 5 张幻灯片的框架中,如果我们有以下内容:

\tikznamebeamer[my-graph]{{2,5},4}
\begin{tikzpicture}
\tikzset{
    every node/.style={draw, circle}
}
\node (1) at (0, 0) {1};
\node (2) at (1cm, 0) {2};
\node (3) at (0, 1cm) {3};
\node (4) at (1cm, 1cm) {4};

\draw[visible on=<{2,5}>, ->] (1) to (2);
\draw[->] (1) to (3);
\draw[visible on=<4>,->] (2) to (3);
\draw[->] (3) to (4);
\end{tikzpicture}

\tikznamebeamer[my-graph]{{2,5},4}指定自定义文件名应以“my-graph”为前缀,并指定 3 组幻灯片:{1,3}、{2,5} 和 {4}。请注意,任何未指定的幻灯片都会分组在一起,因此为 {1,3}。

对于每个集合,我们对外部化使用相同的文件名,以便可以重复使用。在此示例中,将为 {1,3} 生成“my-graph-0.pdf”,为 {2,5} 生成“my-graph-1.pdf”,为 {4} 生成“my-graph-2.pdf”。

一个小缺点是你需要手动指定哪些幻灯片是相同的。但我还没有找到一种自动的方法来做到这一点。

这实际上是我一生中编写的第一段严肃的低级 LaTeX 代码,所以我真的希望更有知识的人可以清理它并使其符合惯用语言。

相关内容