我想这有点类似于LaTeX 中的动画,但我想要的是这样的:
假设您有一个 .tex 文档,它使用一些宏\tester
作为参数。我想加载一个包,在其中我可以为这个 指定一些值\tester
,并让包自动循环遍历这些值,并为 的每个值生成单独的 PDF \tester
。最后,我希望在循环结束后在 shell 中运行一个“后续操作”。因此,只需指定一个\usepackage
类似:
\documentclass{article}
...
\usepackage[vartoken=\tester,varvals={0,1,2},postactioncode={ls *.pdf}]{repeat-build}
...
\begin{document}
... testing: \tester
\end{document}
...我希望循环自动运行,为\tester
=0、1 和 2 生成单独的 PDF。
已经有这样的包了吗?
答案1
我不确定是否存在这样的包;但我设法制作了一个,所以我会在这里发布它。(如果这样的包不存在,并且如果它有用,请随意修改并最终在 CTAN 上发布;不幸的是,我目前没有时间执行该过程,或者进行一般维护)
到目前为止我认为唯一的问题是变量声明有点复杂:
...
\ifx\tester\undefined{%
\global\protected\def\tester{aa} %
}\fi
\usepackage[vartoken=\tester,varvals={0,...,2},postactioncode={\pacode}]{repeat-build}
\unprotect\tester
\begin{document}
...
.tex 示例(如下)必须使用“shell escape”进行编译;因此,这是编译方法,以及 Linux 上的预期消息:
$ pdflatex -shell-escape repeat-build-test.tex
...
repeat-build: \rpbuild@jobname is undefined: first run!
repeat-build: vartoken defined as: macro:->\tester
repeat-build: varvals defined as: macro:->0,...,2
repeat-build: have {3} vaues in varvals
repeat-build: ... we got here - time to start loopin' ...
+ pdflatex -shell-escape -interaction=batchmode -jobname repeat-build-test-0 \makeatletter\gdef\rpbuild@jobname{repeat-build-test} \gdef\tester {0} \makeatother\input{repeat-build-test}
This is pdfTeX, Version 3.1415926-2.3-1.40.12 (TeX Live 2011)
\write18 enabled.
entering extended mode
+ pdflatex -shell-escape -interaction=batchmode -jobname repeat-build-test-1 \makeatletter\gdef\rpbuild@jobname{repeat-build-test} \gdef\tester {1} \makeatother\input{repeat-build-test}
This is pdfTeX, Version 3.1415926-2.3-1.40.12 (TeX Live 2011)
\write18 enabled.
entering extended mode
+ pdflatex -shell-escape -interaction=batchmode -jobname repeat-build-test-2 \makeatletter\gdef\rpbuild@jobname{repeat-build-test} \gdef\tester {2} \makeatother\input{repeat-build-test}
This is pdfTeX, Version 3.1415926-2.3-1.40.12 (TeX Live 2011)
\write18 enabled.
entering extended mode
repeat-build: post action code:
+ pwd
/path/to/src/tex/repeat-build
+ ls repeat-build-test-0.log repeat-build-test-1.log repeat-build-test-2.log repeat-build-test-0.aux repeat-build-test-1.aux repeat-build-test-2.aux
repeat-build-test-0.aux repeat-build-test-1.aux repeat-build-test-2.aux
repeat-build-test-0.log repeat-build-test-1.log repeat-build-test-2.log
+ rm -vrf repeat-build-test-0.log repeat-build-test-1.log repeat-build-test-2.log repeat-build-test-0.aux repeat-build-test-1.aux repeat-build-test-2.aux
removed `repeat-build-test-0.log' ...
removed `repeat-build-test-2.aux'
+ convert -density 150 repeat-build-test-0.pdf repeat-build-test-1.pdf repeat-build-test-2.pdf -crop 400x400+250+320 +repage -delay 5 -loop 0 repeat-build-test.gif
repeat-build: Finished. Due to the use of repeat-build,
will now exit without compiling the master document.
Comment the `\usepackage[...]{repeat-build}`
line in your document code to go back to normal
compilation behavior.
) )
No pages of output.
Transcript written on repeat-build-test.log.
...这是它生成的输出:
这是repeat-build-test.tex
:
\documentclass{article}
% declare iteration variable;
% wish it was easier (just \def\tester{...}), but:
% must have it \protected (eTex), so
% it arrives as token in the package
% code! (vartoken=\noexpand\tester
% in options [] doesn't work)
% with \ifx we do protection from subsequent runs;
% and because of it, we need \global inside!
\ifx\tester\undefined{%
\global\protected\def\tester{aa} %
}\fi
% write post action code for package;
% NOTE: \write18 in Linux is sh - not bash!
% (so cannot do expansion like `ls *.{log,aux}`;
% must do `ls *.log *.aux`)
% Latex will compact \pacode in single line; remember semicolons
% also, comment sh lines here with % - not # !!
\def\pacode{%
pwd; %
ls \jobname-*.log \jobname-*.aux; %
rm -vrf \jobname-*.log \jobname-*.aux; %
convert -density 150 *.pdf %
-crop 400x400+250+320 +repage %
-delay 5 -loop 0 %
\jobname.gif %
; %
%eog \jobname.gif ; % run viewer
}
% load package (also defs \unprotect)
\usepackage[vartoken=\tester,varvals={0,...,2},postactioncode={\pacode}]{repeat-build}
% now unprotect the iteration variable macro:
% (although here we don't really need to)
\unprotect\tester
\usepackage{xcolor} % \pagecolor
\pagecolor{yellow!15}
\begin{document}
\typeout{indoc tester: \tester}
\title{Test of repeat-build}
\author{sdaau}
\maketitle
\begin{abstract}
The abstract text goes here.
\end{abstract}
\section{Test section}
Current variable \verb!\tester! is: \tester
\end{document}
这是repeat-build.sty
:
\NeedsTeXFormat{LaTeX2e}[1994/06/01]
\ProvidesPackage{repeat-build}[2014/06/21 repeat-build]
\RequirePackage{xstring} % \StrLen
\RequirePackage{pgfkeys}
\RequirePackage{pgfopts} % \ProcessPgfOptions
\RequirePackage{pgffor} % \foreach
\ifx\pgfmathincluded\undefined %
\RequirePackage{pgfmath} % \pgfmathsetmacro
\fi
%\RequirePackage{trace} % debug
\def\rptypeout#1{\immediate\typeout{ repeat-build: #1}}
% \DeclareOption{vartoken}{
% \gdef{\vartoken}{...}
% }
\pgfkeys{
/rpbuild/.cd,
vartoken/.store in = \rpbuild@vartoken, % expands?!
vartoken/.default = \undefined,
% note: varvals is "undefined" if not mentioned in options [];
% becomes .default value if it is mentioned, but unset [varvals,]
% it doesn't help if .initial or .default are before .store in -
% - the varvals remains undef'd if not mentioned in options!
% note: even {0,...,0} has length 1; so just use {} to init that!
varvals/.initial = {},
varvals/.default = {},
varvals/.store in = \rpbuild@varvals,
% actually, this is how to set a default value for
% options not mentioned in the []: by using .initial
% and .get!
actioncode/.initial={%
% NOTE: \write18 in Linux is sh - not bash!
% (so cannot do expansion like `ls *.{log,aux}`;
% must do `ls *.log *.aux`)
% this is the sh script part only! Usable macros:
% \rpbuild@jobname - originating \jobname
% \rpbuild@iter - iterator (current value);
% \rpbuild@ind - numeric index for jobname
% \rpbuild@indlp - same as @ind, but (left) zero-padded
% \mybs - backslash escaping macro for shell
% do not use quotes printf '\rpbuild@jobname...' here;
% they will propagate inside the variable!
% also, comment bash lines here with % - not # !!
% ---
% this works - using printf in shell to left zero-pad @ind number:
%nnamecmd="printf \rpbuild@jobname-\myperc 0\rpbuild@lengthnumchars d \rpbuild@ind" ;
%nname=$($nnamecmd);
% with already zero-padded from Latex: @indlp:
nname="\rpbuild@jobname-\rpbuild@indlp" ;
%echo $nname ; % debug
tcmd="pdflatex
-shell-escape %\tikzexternalcheckshellescape %(avoid loading tikz)
%-halt-on-error % will disallow interaction that stops on errors - halts in sense of exit!
-interaction=batchmode % silent/quiet; will not halt on any error!
%-interaction=scrollmode % stops on some errors
%-interaction=errorstopmode % stops on all errors
-jobname "$nname" %
'% % (c0)
\string\makeatletter
\string\gdef\mybs\string\rpbuild@jobname{\rpbuild@jobname}
\string\gdef\mybs\rpbuild@vartoken{\rpbuild@iter}
%\string\typeout{INB4 INPUT \string\rpbuild@jobname}
\string\makeatother
\string\input{\rpbuild@jobname}%
'" ;
%echo "$tcmd" ;% | hexdump -C ; % debug
%set -x; % better for debug - inside the eval, to avoid repeated prints
eval "set -x; $tcmd" ; % execute
},
actioncode/.get = \rpbuild@actcode,
postactioncode/.store in = \rpbuild@postcode,
postactioncode/.default = {}, % (c1)
}
\def\unprotect#1{ %
% http://tex.stackexchange.com/a/57233/2595
\rptypeout{unprotecting: #1} %
\edef#1{\expandafter\strip@prefix\meaning#1} %
}
\gdef\rpbuild@exitallifneeded{}
% \ProcessOptions\relax
\ProcessPgfOptions{/rpbuild}\relax
% \leftpadzero{\totalcharslength}{\inputnumstring}{\outstrcmd}
% (isn't expandable itself, but returns into a macro that will be)
\def\leftpadzero#1#2#3{%
\xdef#3{#2} %initialize; %\xdef\rpbuild@tmpoutstr{#2}%
\StrLen{#3}[\rpbuild@tmpinnumchars]%
% z(ero)c(hars)toadd - total;
\pgfmathtruncatemacro{\rpbuild@tmpzctoadd}{#1-\rpbuild@tmpinnumchars}%
\ifnum\rpbuild@tmpzctoadd>0{%
% start the foreach from 1, for accurate count
\foreach \ix in {1,...,\rpbuild@tmpzctoadd}{%
\xdef#3{0#3}% concatenate a "0" from left
}%
}\fi%
%\rpbuild@tmpoutstr% return;
% no return here - now we have it in the #3 macro
} % end def \leftpadzero
% check first if we're running in the inner loop:
\ifx\rpbuild@jobname\undefined{%
\rptypeout{\string\r pbuild@jobname is undefined: first run!} %
% in these checks, in order to exit properly, must have:
% ... \expandafter \endinput } \fi ... ; else getting:
% (\end occurred inside a group at level 1) warning +
% ### simple group (level 1) entered at line 51 ({) ...
% Because of that, we cannot use \else - so the conditions
% are repeated in order to get console printouts!
% Though, below we can use \else, because that is where
% the \endinput is!
% check if we have vartoken
\ifx\rpbuild@vartoken\undefined{ %
\rptypeout{vartoken undefined; will not run!} %
\expandafter\endinput %
}\fi %
\ifx\rpbuild@vartoken\undefined\else{ %
\rptypeout{vartoken defined as: \meaning\rpbuild@vartoken} %
}\fi
% check if we have varvals
\ifx\rpbuild@varvals\undefined{ %
\rptypeout{varvals undefined; will not run!} %
\expandafter\endinput %
}\fi %
\ifx\rpbuild@varvals\undefined\else{ %
\rptypeout{varvals defined as: \meaning\rpbuild@varvals} %
}\fi
% check if we have range - by looping the
% argument, and calculating "last index"
\def\rpbuild@length{0}
\foreach \rpbuild@iter in \rpbuild@varvals{ %
\pgfmathtruncatemacro{\rpbuild@length}{\rpbuild@length+1} %
\global\let\rpbuild@length\rpbuild@length % must globalize!
}
\ifnum\rpbuild@length>0{%
\rptypeout{have {\rpbuild@length} vaues in varvals} %
}\else{%
\rptypeout{do not have vaues in varvals; will not run!} %
\expandafter\endinput %
}\fi
% "The control sequence \pdfshellescape is (only?) available in pdftex"
% http://tex.stackexchange.com/a/13253/2595
\ifnum\pdfshellescape=1{ %
% Yes, enabled
}\else{ %
% No, disabled
\rptypeout{** Sorry, you'll need to call with `pdflatex -shell-escape`} %
\rptypeout{** to have this work; will not run!} %
\expandafter\endinput %
}\fi
\rptypeout{ ... we got here - time to start loopin' ... } %
% \rpbuild@jobname is the master, originating \jobname,
% but also a signal that we're inside the loop!
\edef\rpbuild@jobname{\jobname}%
\StrLen{\rpbuild@length}[\rpbuild@lengthnumchars]
% http://tex.stackexchange.com/a/69294/2595
% must escape backslash like this, to have escaped linefeed
% character \n in \write18 output for the shell (raw ASCII)!
\begingroup
\catcode `~=11
\gdef\mytilde{~}
\catcode `\|=0
\catcode `\\=11
|gdef|LF{\n} % \LF becomes (ASCII) "\n" (verbatim char!)
|gdef|n{\n} % \n becomes (ASCII) "\n" (verbatim char!)
|gdef|ELF{\\n} % \ELF becomes (ASCII) "\\n" - escaped backslash for shell!
|endgroup
% get expandable ASCII macros
% cannot do it like this:
% \edef\mybs{\string\char092} \typeout{mybs \mybs}
% must use the uccode/uppercase trick (as in:
% http://tex.stackexchange.com/q/60951/#comment129115_60960 )
\bgroup
\uccode`A=37 % chr(37) percent sign %
\uccode`B=92 % chr(92) backslash \
\uppercase{
\gdef\myperc{A}
\gdef\mybs{B}
\gdef\mybsbs{BB}
%\typeout{==A==B==} % debug
}
\egroup
% run the loop with the action code
% since we will use shell-escape,
% we might as well use printf in the (ba)sh shell (Linux)
% to format the numbers added to the filename;
% but can also use a Latex function.
% also, note that line endings here will be gobbled by
% latex (replaced by space), so the whole \cmd will
% arrive to (ba)sh as a single line - remember semicolon delimiters!
% heading tab space will be gobbled too
% also note that \rp.. is seen by bash as "\r", so that
% must be escaped; with just \mybs nowork; in this case,
% have to also use single quotes (instead of double -
% in "\string\def\string\rpbuild@..")
\gdef\rpbuild@ind{0}
\gdef\rpbuild@indlp{0} %index, left padded
\foreach \rpbuild@iter in \rpbuild@varvals{ %
%\leftpadzero{5}{120}{\ret} %\typeout{ret \ret} % 00120
\leftpadzero{\rpbuild@lengthnumchars}{\rpbuild@ind}{\rpbuild@indlp} %\typeout{ret \rpbuild@indlp} %
% compose command(s)
% (below unused snippet left for dev reference):
% \edef\cmd{%
% % do not use quotes printf '\rpbuild@jobname...' here;
% % they will propagate inside the variable!
% % also, comment bash lines here with % - not # !!
% nnamecmd="printf \rpbuild@jobname-\myperc 0\rpbuild@lengthnumchars d \rpbuild@iter" ;
% nname=$($nnamecmd);
% echo $nname ;
% echo
% pdflatex
% -shell-escape %\tikzexternalcheckshellescape
% -halt-on-error
% -interaction=batchmode
% -jobname "$nname"
% '%
% \string\def\mybs\string\rpbuild@jobname{\rpbuild@jobname}
% \string\input{\rpbuild@jobname}%
% '
% } % \cmd
% execute
\immediate\write18{%
% \cmd% OK
\rpbuild@actcode% OK
}%
% increase count for file/jobname:
\pgfmathtruncatemacro{\rpbuild@ind}{\rpbuild@ind+1} %
\global\let\rpbuild@ind\rpbuild@ind % globalize
} % end \foreach
% do post action code, if any:
\ifx\rpbuild@postcode\undefined{%
\rptypeout{no post action code} %
}\else{%
\rptypeout{post action code:} %
\edef\cmd{
eval "set -x; \rpbuild@postcode" ; % execute
}
\immediate\write18{\cmd} %
}\fi
\rptypeout{%
Finished. Due to the use of repeat-build, ^^J
will now exit without compiling the master document. ^^J
Comment the `\string\usepackage[...]{repeat-build}` ^^J
line in your document code to go back to normal ^^J
compilation behavior. ^^J
} %
\gdef\rpbuild@exitallifneeded{\stop}
}%\fi % end \ifx\rpbuild@jobname\undefined
\else{%
%\ifx\rpbuild@jobname is not undefined!
\rptypeout{ > in loop, building \jobname ^^J
(repeat-build package will get out of the way now) ... } %
}\fi % end \ifx\rpbuild@jobname\undefined
% Latex2e \stop - exit prematurely/stop compilation
% *without* an error prompt;
\rpbuild@exitallifneeded %
\endinput
% (c0)
% escape trouble:
%\string\gdef\mybsbs\mybs\string\rpbuild@jobname{\rpbuild@jobname}
%\string\gdef\mybsbs\mybs\rpbuild@vartoken{\rpbuild@iter}
%\string\typeout{INB4 INPUT \mybsbs\mybs\rpbuild@jobname}
%% ! Use of \\ doesn't match its definition.
%% <write> INB4 INPUT \\r ... epeat-build-test
% is like this: ...
% (c1)
% do not use spaces in keys for usepackage;
% e.g. for below getting:
% ! Package pgfkeys Error: I do not know the key '/rpbuild/postactioncode'
%post action code/.store in = \rpbuild@postcode,
%post action code/.default = {},