[Tex/LaTex] Javascript application in latex

javascript

I would like to know if it is possible to add an interactive app created with Javascript to a Latex document.

I would like to add some of the Javascripts that appear here.

Like that one:

enter image description here

EDIT

I can have the .js document, so I would like how to attach a .js to Latex.

Any idea? Any help?

Best Answer

This ports the card shuffling example to LaTeX and PDF, using PDF Layers (package ocgx2) and JavaScript. Acrobat Reader is required for display.

Update: Extended example with chart that plots King-diamond position versus riffle count.

Warning: It takes about 2h to compile the code twice using pdflatex due to the large number of OCGs (around 21k) that is created. Moreover, for pdflatex to run successfully, the main_memory must be increased in the format, or lualatex to be used instead. The (original) example without the chart compiles much faster (second code box).

enter image description here

\documentclass[a4paper,landscape]{article}

\usepackage{tikz,color,calc}
\usepackage{media9} %\mediabutton
\usepackage{xsavebox,ocgx2}

%suit symbols from https://tex.stackexchange.com/a/9643
\DeclareSymbolFont{extraup}{U}{zavm}{m}{n}
\DeclareMathSymbol{\varheart}{\mathalpha}{extraup}{86}
\DeclareMathSymbol{\vardiamond}{\mathalpha}{extraup}{87}

%typesets playing card; args: value, suite, colour
\makeatletter
\def\mycard#1#2#3{%
  \fbox{\rule[-4ex]{0pt}{5ex}\makebox[3.5ex][l]{#1$\color{#3}\@nameuse{#2}$}}}
\makeatother

%initialisations (JavaScript) on page-open (/O <<...>>), reset on page-close
\usepackage{ifluatex}
\ifluatex\def\pdfpageattr{\pdfvariable pageattr}\fi
\begingroup
\edef\x{\endgroup
  \pdfpageattr{
    \the\pdfpageattr
    /AA <<
      /O << /S/JavaScript /JS (%
%        console.show();
        console.clear();
        %riffle top card
        var riffle=function(){
          for(var i=0;i<52;i++) oldOrder[i]=order[i];
          if(order[0]!=51){
            %remove current card from top
            var top=order.shift();
            %insert `top' at new random position
            order.splice(1+Math.random()*order.length, 0, top);
            %new King Diamond position
            KdiamondPos=order.indexOf(51);
            ++riffleCount;
            console.println(riffleCount+' '+KdiamondPos);
          }
        };
        var update=function(){
          %update OCG visibility
          for(tablePos=0;tablePos<52;tablePos++){
            try{card[tablePos][oldOrder[tablePos]].state=false;}catch(e){}
            try{card[tablePos][order[tablePos]].state=true;}catch(e){}
          }
          try{pix[riffleCount][KdiamondPos].state=true;}catch(e){}
        };
        var reset=function(){
          console.clear();
          try{app.clearInterval(myIntA);}catch(e){}
          try{app.clearInterval(myIntB);}catch(e){}
          for(var i=0;i<52;i++) {oldOrder[i]=order[i]; order[i]=i;}
          riffleCount=0; KdiamondPos=51;
          for(var j=0;j<52;j++){
            for(var i=0;i<350;i++){
              try{pix[i][j].state=false;}catch(e){}
            }
          }
          update(); 
        };
        %card[<table position>][<stack position>]
        var card=new Array(52);
        for(var i=0;i<52;i++){card[i]=new Array(52);}
        %pix[<riffle count>][<stack position>]
        var pix=new Array(350);
        for(var i=0;i<350;i++){pix[i]=new Array(52);}
        ocg=this.getOCGs(this.pageNum);
        for(var i in ocg){
          ocgName=ocg[i].name.split('-');
          if(ocgName[2]=='card')
            card[ocgName[0]][ocgName[1]]=ocg[i];
          else % 'pix'
            pix[ocgName[0]][ocgName[1]]=ocg[i];
        }%
        %initial order
        var order=new Array(52); for(var i=0;i<52;i++) order[i]=i;
        var oldOrder=new Array(52);
        var riffleCount=0, KdiamondPos=51;
      ) >>
      /C << /S/JavaScript /JS (reset();) >>
    >>
  }
}
\x

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\begin{document}\parindent=0pt%
%
% build chart
\xsavebox{Pix}[0.317mm][c]{\tikz\fill (0,0) circle [radius=0.5mm];}
\parbox{\linewidth}{\offinterlineskip%
  $\uparrow$ K$\color{red}\vardiamond$ position\\[0.5ex]
  \makebox[111mm]{\hrulefill}\\
    \foreach \j in {0,...,51}{%
      \foreach \i in {0,...,349}{%
         \expandafter\ifnum\numexpr\j+\i\relax<51
           \phantom{\thePix}%
         \else%  
           \begin{ocg}{\i-\j-pix}{\i-\j-pix}{off}%
             \thePix%
           \end{ocg}%
         \fi% 
      }\\
    }%
  \makebox[111mm]{\hrulefill}\\[0.5ex]
  \makebox[111mm][r]{riffle count $\rightarrow$}%
  \begin{ocg}{0-51-pix}{0-51-pix}{on}\end{ocg}%
}\\[1ex]

%typeset playing cards into xsaveboxes
\foreach \suit/\suitcol in {%
  spadesuit/black,clubsuit/black,varheart/red,vardiamond/red}{%
  \fboxrule=0.5pt\fboxsep=3pt%
  \foreach \val in {A,2,3,...,10,J,Q,K}{%
    \xsavebox{\val-\suit}{\mycard{\val}{\suit}{\suitcol}}%
  }%
}%
\xsavebox{K-vardiamond}{%
  \fboxsep=1.5pt\fboxrule=2pt\mycard{K}{vardiamond}{red}}%
%
%create layered array of cards: 13x4x52=2704 OCGs!
\foreach \j in {0,...,3}{%
  \foreach \i in {0,...,12}{%
    \fboxrule=0pt\fboxsep=3.5pt%
    \makebox[\widthof{\xusebox{K-vardiamond}}][l]{%
      \foreach \suit/\jj in {spadesuit/0,clubsuit/1,varheart/2,vardiamond/3}{%
        \foreach \val/\ii in {%
          A/0,2/1,3/2,4/3,5/4,6/5,7/6,8/7,9/8,10/9,J/10,Q/11,K/12}{%
          \xdef\tablePos{\the\numexpr\j*13+\i\relax}%
          \xdef\stackPos{\the\numexpr\jj*13+\ii\relax}%
          \begin{ocg}{\tablePos-\stackPos-card}{\tablePos-\stackPos-card}{%
            \expandafter\ifnum\numexpr\tablePos-\stackPos\relax=0 on%
            \else off\fi%
          }%
            \makebox[0pt][l]{\xusebox{\val-\suit}}%
          \end{ocg}%
        }%
      }%
    }~%
  }\\%
}\\[1ex]
\mediabutton[
  jsaction={riffle();update();}
]{\fbox{\strut Riffle}}
\mediabutton[
  jsaction={
    var myIntA=app.setInterval(
      'riffle();update();if(riffleCount\%10==0||order[0]==51)
        app.clearInterval(myIntA);',100);
  }
]{\fbox{\strut Riffle$\times$10}}
\mediabutton[
  jsaction={
    var myIntB=app.setInterval(
      'riffle();update();if(order[0]==51) app.clearInterval(myIntB);',100);
  }
]{\fbox{\strut Auto Riffle}}
\mediabutton[
  jsaction={reset();}
]{\fbox{\strut Reset}}

\end{document}

Original example without chart:

enter image description here

\documentclass[a4paper,landscape]{article}

\usepackage{pgffor,color,calc}
\usepackage{media9} %\mediabutton
\usepackage{xsavebox,ocgx2}

%suit symbols from https://tex.stackexchange.com/a/9643
\DeclareSymbolFont{extraup}{U}{zavm}{m}{n}
\DeclareMathSymbol{\varheart}{\mathalpha}{extraup}{86}
\DeclareMathSymbol{\vardiamond}{\mathalpha}{extraup}{87}

%typesets playing card; args: value, suite, colour
\makeatletter
\def\mycard#1#2#3{%
  \fbox{\rule[-4ex]{0pt}{5ex}\makebox[3.5ex][l]{#1$\color{#3}\@nameuse{#2}$}}}
\makeatother

%initialisations (JavaScript) on page-open (/O <<...>>)
\usepackage{ifluatex}
\ifluatex\def\pdfpageattr{\pdfvariable pageattr}\fi
\begingroup
\edef\x{\endgroup
  \pdfpageattr{
    \the\pdfpageattr
    /AA << /O << /S/JavaScript /JS (%
      console.show();
      console.clear();
      %card[<table position>][<stack position>]
      var card=new Array(52);
      for(var i=0;i<52;i++){card[i]=new Array(52);}
      ocg=this.getOCGs(this.pageNum);
      for(var i in ocg){
        ocgName=ocg[i].name.split('-');
        card[ocgName[0]][ocgName[1]]=ocg[i];
      }%
      %initial order
      var order=new Array(52);
      var oldOrder=new Array(52);
      for(i=0;i<52;i++) order[i]=i;
      var riffleCount=0;
      %riffle top card
      var riffle=function(){
        for(i=0;i<52;i++) oldOrder[i]=order[i];
        if(order[0]!=51){
          %remove current card from top
          var top=order.shift();
          %insert `top' at new random position
          order.splice(1+Math.random()*order.length, 0, top);
          console.println(++riffleCount);
        }
      };
      var update=function(){
        %update OCG visibility
        for(tablePos=0;tablePos<52;tablePos++){
          card[tablePos][oldOrder[tablePos]].state=false;
          card[tablePos][order[tablePos]].state=true;
        }
      };
      var reset=function(){
        for(i=0;i<52;i++) {oldOrder[i]=order[i]; order[i]=i;}
        update(); riffleCount=0;
        console.clear();
      };
    ) >> >>
  }
}
\x

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\begin{document}\parindent=0pt%
%
%typeset playing cards into xsaveboxes
\foreach \suit/\suitcol in {%
  spadesuit/black,clubsuit/black,varheart/red,vardiamond/red}{%
  \fboxrule=0.5pt\fboxsep=3pt%
  \foreach \val in {A,2,3,...,10,J,Q,K}{%
    \xsavebox{\val-\suit}{\mycard{\val}{\suit}{\suitcol}}%
  }%
}%
\xsavebox{K-vardiamond}{%
  \fboxsep=1.5pt\fboxrule=2pt\mycard{K}{vardiamond}{red}}%
%
%create layered array of cards: 13x4x52=2704 OCGs!
\foreach \j in {0,...,3}{%
  \foreach \i in {0,...,12}{%
    \fboxrule=0pt\fboxsep=3.5pt%
    \makebox[\widthof{\xusebox{K-vardiamond}}][l]{%
      \foreach \suit/\jj in {spadesuit/0,clubsuit/1,varheart/2,vardiamond/3}{%
        \foreach \val/\ii in {%
          A/0,2/1,3/2,4/3,5/4,6/5,7/6,8/7,9/8,10/9,J/10,Q/11,K/12}{%
          \xdef\tablePos{\the\numexpr\j*13+\i\relax}%
          \xdef\stackPos{\the\numexpr\jj*13+\ii\relax}%
          \begin{ocg}{\tablePos-\stackPos}{\tablePos-\stackPos}{%
            \expandafter\ifnum\numexpr\tablePos-\stackPos\relax=0 on%
            \else off\fi%
          }%
            \makebox[0pt][l]{\xusebox{\val-\suit}}%
          \end{ocg}%
        }%
      }%
    }~%
  }\\%
}\\[1ex]
\mediabutton[
  jsaction={riffle();update();}
]{\fbox{\strut Riffle}}
\mediabutton[
  jsaction={
    var myIntA=app.setInterval(
      'riffle();update();if(riffleCount\%10==0||order[0]==51)
        app.clearInterval(myIntA);',100);
  }
]{\fbox{\strut Riffle$\times$10}}
\mediabutton[
  jsaction={
    var myIntA=app.setInterval(
      'riffle();update();if(order[0]==51) app.clearInterval(myIntA);',100);
  }
]{\fbox{\strut Auto Riffle}}
\mediabutton[
  jsaction={reset();}
]{\fbox{\strut Reset}}

\end{document}