% File `interactiveanimation.sty' % Copyright 2012--2013 Luis González, Javier Toro, Pedro Linares % This material is subject to the LaTeX Project Public License. See % http://www.ctan.org/tex-archive/help/Catalogue/licenses.lppl.html % for the details of that license. % This package provides an interface to create Portable Document % Format (PDF) files with branching and button controllable % animations. \NeedsTeXFormat{LaTeX2e} \ProvidesPackage{interactiveanimation}[2013/05/21] % Check if pdfTeX is running in PDF mode \RequirePackage{ifpdf} \ifpdf \else \PackageWarningNoLine{interactiveanimation}{% Loading aborted, because pdfTeX is not running in PDF mode% }% \expandafter\endinput \fi \RequirePackage{keyval} % Package options \DeclareOption*{% \PackageWarning{interactiveanimation}{% Unknown option `\CurrentOption'}} \ProcessOptions\relax %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % String and Integer Variables % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \def\inta@JSFile{javascript/DocLevel.js}% The name of JavaScript file \def\inta@JSCode{}% JavaScript code that will be executed at first level \def\inta@appearanceNames{}% Unique names for inserted images \def\inta@fields{}% PDF fields array \def\inta@buttonActions{}% Actions to be performed when a button is pressed \newcount\inta@numanims\inta@numanims=0% Number of inserted animation widgets \newcount\inta@numframes\inta@numframes=0% Number of inserted animation frames \newcount\inta@numbuttons\inta@numbuttons=0% Number of inserted control buttons \newcount\inta@numimages\inta@numimages=0% Number of inserted external images \newif\ifinta@animation\inta@animationfalse% Flag to check if is inside of an animation \newif\ifinta@aframe\inta@aframefalse% Flag to check if is inside of an aframe \newcount\inta@notequal \newcount\inta@I \newcount\inta@loopStart \newcount\inta@loopEnd \newcount\inta@step %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Internal Helper Functions % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % inserts string #2 at the end of #1 \def\inta@concat#1#2{% \expandafter\xdef \csname inta@#1\endcsname {\csname inta@#1\endcsname#2} } % add JavaScript code to te code that is being built \def\inta@addJS#1{% \inta@concat{JSCode}{#1} } % Add a field to the fields array \def\inta@addField{ \inta@concat{fields}{\the\pdflastannot\space 0 R } } % add appearance name for an inserted image; #1: name, #2: reference number \def\inta@addAP#1#2{ \inta@concat{appearanceNames}{ (#1) \the#2 0 R } } % Insert an action given in #2 for a button given in #1 \def\inta@addAction#1#2{% \inta@concat{buttonActions}{#1=#2,} } % Tests if argument is a non-negative number \def\inta@isNumber#1{% \ifnum9<1#1\space% Non-negative number 1% \else% Negative or invalid number 0% \fi } % Check if a string is empty or only contains spaces \def\inta@isEmpty#1{% \ifnum\pdfstrcmp{#1}{\space}=0 1 \else\ifnum\pdfstrcmp{#1}{\space\space}=0 1 \else\ifnum\pdfstrcmp{#1}{\@empty}=0 1 \else 0 \fi\fi\fi } % Extract option and argument from expression option=argument \def\inta@extractOptArg#1=#2\relax{% \xdef\inta@opt{#1} \xdef\inta@arg{#2} } % Removes spaces at the beginning of a string \def\inta@removeSpaces#1{% \if\space#1\else #1\fi } % Transform a number in a three digits number filling with zeros \def\inta@threeDigits#1{% \ifnum#1<10 00% \else\ifnum#1<100 0% \fi\fi \the#1% } % Insert an object given in #2 into the PDF and saves its reference in #1 \def\inta@insertObject#1#2{% \immediate\pdfobj{#2}% \expandafter\xdef \csname inta@obj:#1\endcsname {\the\pdflastobj \space 0 R} } % Insert an external image and saves its name in \inta@imageName. #1: file name, #2: page \def\inta@insertImage#1#2{ % Cheks if name and page are valid \edef\inta@filePage{#2} \IfFileExists{#1}% { \ifnum\inta@isEmpty{#2}=1 % if filePage not specified, first one is silently assumed \def\inta@filePage{1} \else\ifnum\inta@isNumber{#2}=0 % Not a valid number \PackageError{interactiveanimation}{% `#2' of file `\inta@fileName' is not a valid page number% }{ A page number must be a positive integer } \def\inta@filePage{1} \else\ifnum#2=0 % Zero-based numbering page \PackageWarning{interactiveanimation}{% Page numbers starts in number one% \MessageBreak% Page number 1 (first page) was assumed.% } \def\inta@filePage{1} \fi\fi\fi \@ifundefined{inta@img:#1,\inta@filePage}% Only inserts if was not previously inserted { \immediate\pdfximage% page \inta@filePage {#1} \expandafter\xdef \csname inta@img:#1,\inta@filePage\endcsname {image\inta@threeDigits{\inta@numimages}} \inta@addAP% {image\inta@threeDigits{\inta@numimages}}% {\pdflastximage} \global\advance\inta@numimages by 1 }{ } \xdef\inta@imageName{% \csname inta@img:#1,\inta@filePage\endcsname} }{% File is invalid or not specified \ifnum\inta@isEmpty{\inta@fileName}=0% Specified but invalid \PackageError{interactiveanimation}{% Could not find file `\inta@fileName'% } {} \fi } } % Refer an object previowsly inserted using \inta@insertObject \def\inta@refObj#1{% \csname inta@obj:#1\endcsname} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Initial PDF Setups % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % An AcroForm dictionary is inserted in the catalog, % containing the Fields array \pdfobj reserveobjnum \newcount\inta@fieldsnum \inta@fieldsnum=\pdflastobj \pdfcatalog{% /AcroForm << /Fields \the\inta@fieldsnum\space 0 R /NeedAppearances true >> } %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Command and Environment Definitions % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Inserts an animation widget \newcount\inta@animationWidth\newcount\inta@animationHeight \newdimen\inta@dimen\inta@dimen=0pt% Auxiliar dimension \newenvironment{animation}[2]{% Two arguments: width and height \ifinta@animation% if inside of another animation \PackageError{interactiveanimation}{% You can not define an animation inside% \MessageBreak% another one. } {} \fi \inta@animationtrue \xdef\inta@animationName{animation\the\inta@numanims} \inta@dimen=#1% Avoid printing measure, e.g. `pt' \divide\inta@dimen by 65536% Converts to pt \global\inta@animationWidth=\inta@dimen% store width \inta@dimen=#2 \divide\inta@dimen by 65536 \global\inta@animationHeight=\inta@dimen \strut\vskip #2% Displaying of widget start from lower left corner % inserts a dummy widget for reserving its dimentions and position \hbox{\pdfannot width #1 height #2 {% /Subtype /Widget /FT /Btn /Ff 65536 % Not necessary, but avoids some warnings /T (\inta@animationName) }} \inta@addField \inta@addJS{ var \inta@animationName = extendField(this.getField("\inta@animationName")); } }% { \inta@animationfalse \inta@addJS{\inta@animationName.displayFirstFrame();} % Process button's actions \@for \inta@tmp :=\inta@buttonActions\do% { \ifnum\pdfmatch{=}{\inta@tmp}=1\relax% Is an action \expandafter\inta@extractOptArg\inta@tmp\relax \def\inta@buttonName{\inta@opt} % Action = previous frame \ifnum\pdfstrcmp{previous}{\inta@arg}=0 \inta@addJS{ \inta@buttonName.targetAction = function() { \inta@animationName.displayPreviousFrame(); }; } % Action = next frame \else\ifnum\pdfstrcmp{next}{\inta@arg}=0 \inta@addJS{ \inta@buttonName.targetAction = function() { \inta@animationName.displayNextFrame(); }; } % Action = first frame \else\ifnum\pdfstrcmp{first}{\inta@arg}=0 \inta@addJS{ \inta@buttonName.targetAction = function() { \inta@animationName.displayFirstFrame(); }; } % Action = last frame \else\ifnum\pdfstrcmp{last}{\inta@arg}=0 \inta@addJS{ \inta@buttonName.targetAction = function() { \inta@animationName.displayLastFrame(); }; } % Action = given frame name \else\@ifundefined{inta@frm:\inta@arg}% undefined frame name { \PackageError{interactiveanimation}{% There is no frame named `\inta@arg'% }{% Please specify a valid frame name% Or use: `previous', `next', `first' or `last'} }{ \def\inta@frameNumber{\csname inta@frm:\inta@arg\endcsname} \inta@addJS{ \inta@buttonName.targetAction = function() { \inta@animationName.displayFrame("frame\inta@frameNumber");}; } }\fi\fi\fi\fi \fi } \global\advance\inta@numanims by 1 \gdef\inta@buttonActions{}% It is cleared just in case it will be used again } % Inserts a frame to be displayed by the animation widget \newenvironment{aframe}[2]{% Two arguments: frame name and image \ifinta@animation\else% if outside an animation \PackageError{interactiveanimation}{% You can not define an aframe outside% \MessageBreak% an animation. } {} \fi \ifinta@aframe% if inside another aframe \PackageError{interactiveanimation}{% You can not define an aframe inside% \MessageBreak% another one. } {} \fi \inta@aframetrue \xdef\inta@frameName{frame\the\inta@numframes} \vskip-\baselineskip \hbox{\pdfannot{% /Subtype /Widget /FT /Btn /Ff 65537% No user interaction /T (\inta@frameName) /BS << /W 0 >>% No border is drawn /MK << /BG [1]% White background color /IF << /S /A >> % Non-proportional scaling /TP 1 >>% Only image, no text caption }} \inta@addField \inta@addJS{% var \inta@frameName = extendField(this.getField("\inta@frameName")); \inta@animationName.addFrame(\inta@frameName); } % Animation frame image \inta@I=0 \gdef\inta@fileName{}\gdef\inta@filePage{} \@for \inta@tmp :=#2\do% Iterates each element in the comma separated list { \ifnum\inta@I=0% File name \edef\inta@fileName{\inta@tmp} \else\ifnum\inta@I=1% File page \edef\inta@filePage{\inta@removeSpaces{\inta@tmp}} \fi\fi \advance\inta@I by 1 } % Image is inserted, if it is all right associates it with frame \inta@insertImage{\inta@fileName}{\inta@filePage} \ifnum\inta@isEmpty{\inta@imageName}=0 \inta@addJS{% \inta@frameName.buttonSetIcon(this.getIcon("\inta@imageName")); \inta@frameName.buttonPosition = position.iconOnly; } \fi % Saves number frame for actual frame's name \expandafter\xdef\csname inta@frm:#1\endcsname {\the\inta@numframes} \global\advance\inta@numframes by 1 }% { \inta@aframefalse } % Definitions for keyval needed \define@key{inta}{position}{#1} % option=position \define@key{inta}{position}{% % Check if option has a valid argument \ifnum\pdfmatch{#1}{ left right above below center from }=1 \inta@addJS{\inta@buttonName .relatPosition = option.#1;} \else% if it is invalid, does not process option and display an error \PackageError{interactiveanimation}{% `#1' is not a valid position.}{% Valid positions are:% \MessageBreak% `left', `right', `above', `below' and `from'} \fi } % option=X \define@key{inta}{X}{% \ifnum\inta@isNumber{#1}=1% if is a non-negative integer % Defines the coordinate depending on the animation width % so the animation can be resized \inta@addJS{% \inta@buttonName.pointX = #1 * \inta@animationName.width / \the\inta@animationWidth; } \else\ifnum\pdfmatch{#1}{ start center end }=1 \inta@addJS{\inta@buttonName .positionX = option.#1;} \else% if it is invalid, does not process option and displays an error \PackageError{interactiveanimation}{% `#1' is not a valid X coordinate}{% Valid X coordinate are:% \MessageBreak% A non-negative integer, or `start', `center' or `end'} \fi\fi } % option=Y \define@key{inta}{Y}{% \ifnum\inta@isNumber{#1}=1% if is a non-negative integer \inta@addJS{% \inta@buttonName.pointY = #1 * \inta@animationName.height / \the\inta@animationHeight; } \else\ifnum\pdfmatch{#1}{ start center end }=1 \inta@addJS{\inta@buttonName .positionY = option.#1;} \else% if it is invalid, does not process option and displays an error \PackageError{interactiveanimation}{% `#1' is not a valid Y coordinate}{% Valid Y coordinate are:% \MessageBreak% A non-negative integer, or `start', `center' or `end'} \fi\fi } % option=scale \define@key{inta}{scale}[1]{% \inta@addJS{\inta@buttonName.scale(#1);} } % option=hidden \define@key{inta}{hidden}[true]{% \inta@addJS{\inta@buttonName.toggleHidden = true;} } % option=transparent \define@key{inta}{transparent}[true]{% \inta@addJS{ \inta@buttonName.transparent = true;} } % option=width \define@key{inta}{width}{% \inta@addJS{% \inta@buttonName.width = #1 * \inta@animationName.width / \the\inta@animationWidth; } } % option=height \define@key{inta}{height}{% \inta@addJS{% \inta@buttonName.height = #1 * \inta@animationName.height / \the\inta@animationHeight; } } % option=span \define@key{inta}{span}[1000]{% \inta@addJS{\inta@buttonName.timeSpan = #1;} } % option=keep \define@key{inta}{keep}[true]{% \inta@addJS{\inta@buttonName.keep = true;} } % Insert a button for interacting with the animation \newdimen\inta@buttonWidth \newcommand{\controlbutton}[4][]{% Four arguments: [animation], button's caption, destination frame and specification in a comma separated list \ifinta@animation\else% if outside an animation \PackageError{interactiveanimation}{% You cannot define a control button% \MessageBreak% outside an animation. } {} \fi \edef\inta@buttonName{button\the\inta@numbuttons} \setbox0=\hbox{ #2 \strut} \inta@buttonWidth=\wd0 \vskip-\baselineskip % Button is inserted \hbox{\pdfannot width \inta@buttonWidth{% /Subtype /Widget /FT /Btn% Button field /Ff 65536% Push button type /T (\inta@buttonName)% Button's name, accessible from JavaScript /BS << /S /B >>% Beveled border style /MK << /BG [0.6]% 60% white background color /CA (#2) >>% Caption /H /P% Push highlighting mode /A << /S /JavaScript /JS (% JavaScript action to be performed \inta@buttonName.buttonAction(); ) >> }\strut} \inta@addField \inta@addJS{ var \inta@buttonName = extendField(this.getField("\inta@buttonName")); \inta@buttonName.images = new Array(); } % If present, button's action is stored and will be processed at end of animation environment \ifnum\inta@isEmpty{#3}=0 \inta@addAction{\inta@buttonName}{#3} \else \inta@addJS{ \inta@buttonName.targetAction = function() { \inta@animationName.displayFrame("\inta@frameName");}; } \fi % Button's options are processed \setkeys{inta}{#4} % Button's image sequence is processed \def\inta@rangeStart{}\def\inta@rangeEnd{} \def\inta@fileName{} \inta@I=0 \@for \inta@tmp :=#1\do { \ifnum\inta@I=0% File name \edef\inta@fileName{\inta@tmp} \else\ifnum\inta@I=1% Start range \edef\inta@rangeStart{\inta@removeSpaces{\inta@tmp}} \else\ifnum\inta@I=2% End range \edef\inta@rangeEnd{\inta@removeSpaces{\inta@tmp}} \fi\fi\fi \advance\inta@I by 1 } % Check if ranges are specified and valid \ifnum\inta@isEmpty{\inta@fileName}=0 \ifnum\inta@isEmpty{\inta@rangeStart}=1 \def\inta@rangeStart{1} \def\inta@rangeEnd{1} \else\ifnum\inta@isNumber{\inta@rangeStart}=0 %\inta@insertImage will display the appropriate error \inta@insertImage{\inta@fileName}{\inta@rangeStart} \def\inta@rangeStart{1} \def\inta@rangeEnd{1} \fi\fi \ifnum\inta@isEmpty{\inta@rangeEnd}=1% if only one number was given, set range to (1,number) \edef\inta@rangeEnd{\inta@rangeStart} \def\inta@rangeStart{1} \else\ifnum\inta@isNumber{\inta@rangeEnd}=0 %\inta@insertImage will display the appropriate error \inta@insertImage{\inta@fileName}{\inta@rangeEnd} \edef\inta@rangeEnd{\inta@rangeStart} \def\inta@rangeStart{1} \fi\fi\fi % If was given an animation action for the button, all animation frames are inserted \ifnum\inta@isEmpty{\inta@fileName}=0 \inta@notequal=1% End loop flag \inta@loopStart=\inta@rangeStart \inta@loopEnd=\inta@rangeEnd\relax \ifnum\inta@loopStart>\inta@loopEnd% Descending order \inta@step=-1 \else \inta@step=1 \fi \inta@I=\inta@loopStart\loop \inta@insertImage{\inta@fileName}{\the\inta@I} \inta@addJS{ \inta@buttonName.addImage("\inta@imageName"); } \ifnum\inta@I=\inta@loopEnd \inta@notequal=0% Ends loop \fi \advance\inta@I by \inta@step \ifnum\inta@notequal=1\repeat \fi \inta@addJS{\inta@frameName.addButton(\inta@buttonName);} \global\advance\inta@numbuttons by 1 } % JavaScript code is inserted into the PDF \input\inta@JSFile\relax \AtEndDocument{\inta@insertObject{JavaScript}{ << /S /JavaScript /JS ( \inta@JSCode ) >> }} % A reference to the JavaScript code is inserted intho the names dictionary, % so the JavaScript actions perform when the document is opened \AtEndDocument{\pdfnames{ /JavaScript << /Names [ (interactiveanimation) \inta@refObj{JavaScript} ] >> }} % Appearance names dictionary is inserted into the names dictionary,% % so it is posible accessing inserted images from JavaScript \AtEndDocument{\pdfnames{ /AP << /Names [\inta@appearanceNames] >> }} % Array fields is inserted \AtEndDocument{\immediate\pdfobj useobjnum \inta@fieldsnum {% [\inta@fields]% }}