% \iffalse %<*copyright> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% acromemory.sty package, %% %% Copyright (C) 2006--2017 D. P. Story %% %% dpstory@acrotex.net %% %% %% %% This program can redistributed and/or modified under %% %% the terms of the LaTeX Project Public License %% %% Distributed from CTAN archives in directory %% %% macros/latex/base/lppl.txt; either version 1.2 of %% %% the License, or (at your option) any later version. %% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % %\NeedsTeXFormat{LaTeX2e} %\ProvidesPackage{acromemory} % [2017/02/23 v1.1 AcroMemory (dps)] %<*driver> \documentclass{ltxdoc} \usepackage[colorlinks,hyperindex]{hyperref} \pdfstringdefDisableCommands{\let\\\textbackslash}% \EnableCrossrefs \CodelineIndex \OnlyDescription % comment out for implementation details \begin{document} \GetFileInfo{acromemory.sty} \title{The \texttt{AcroMemory} Package\texorpdfstring{\\}{: } A member of the AeB Pro family} \author{D. P. Story\\ Email: \texttt{storyd@owc.edu}} \date{processed \today} \maketitle \tableofcontents \let\Email\texttt \DocInput{acromemory.dtx} \PrintIndex \end{document} % % \fi % \section{What is the AeB Pro Family?} % Through the years, I have tried to make my AeB software % ({Acro\negthinspace\TeX} eDucation Bundle) compatible with % \textsf{pdftex} and \textsf{dvipdfm}; however, during that time, % I've developed a number of techniques that require the use of % Acrobat and distiller. Therefore, I have set off in a new direction and % will be publishing a new line of {\LaTeX} packages, one that require the use % of Acrobat. % % The current package, \textsf{AcroMemory}, requires the use of Acrobat Pro~7.0 or later. % \section{Introduction} % % At the instigation of my erstwhile friend, J\"{u}ergen, I present to you \textsf{AcroMemory}, and % for the life of me, I can't remember why. % % Oh, yes, \textsf{AcroMemory} is a memory game in which you find the matching tiles. There are two versions % ---available as options of this package---for your enjoyment, % \texttt{acromemory1} and \texttt{acromemory2} (the default). % \begin{itemize} % \item \texttt{acromemory1}: Here you have a single game board, a rectangular region divided % by rows and columns. The total number of tiles should % be even, each tile should have a matching twin. The % game begins with all the tiles hidden. the user clicks a tile, % then another. If the tiles do not match, they become become hidden again % (you did remember the position of those tiles, didn't you?); otherwise, % they remain visible and are now read-only. The game is complete when the user, with % a lot of time on his/her hands, matches all tiles. There is a running tabulation kept % on the number of tries. There is also a button which resets the game and randomizes the % tiles. % \item \texttt{acromemory2}: For this game you have two identical rectangular images subdivided in%to tiles % (or slices) arrayed in rows and columns. The tiles for % one of the two images has been randomly re-arranged. The object of the game is to find all the % matching tiles by choosing a tiles from one image, and tile from the other image. As in the % first case, if the selected tiles do not match, they are hidden after an short interval of time % (you did remember the position of those tiles, didn't you?); otherwise, they remain visible and % are now read-only. The game is over when all tiles are matched, when this occurs, end-of-game % special effects occur that will dazzle the senses. There is an option to view a small image to % help you locate the matching tiles on the non-randomized; useful if the image is complex. There % is no reset button at this time, to play again, the user must close and open the document. % \end{itemize} % The demo files are \texttt{acromemory1.tex} and \texttt{acromemory2.tex}. These files show how to % lay out the various elements of this package. % % \section{Creating the Image Tiles} % % There are a couple of ways you can package your image tiles: (1) place all tiles (all of identical size) % into a single PDF and use the \texttt{iconfile} option to tell AcroMemory you are using this method; (2) % each image file is a separate PDF. In the latter case, there is a naming convention that must be followed; % the files must be named \texttt{\_01.pdf}, \texttt{\_02.pdf}, \dots, % \texttt{\_10.pdf}, \texttt{\_11.pdf}, \dots. You'll note the consistent two digit % numbering system is used. % For \texttt{acromemory2}, slicing of the image is at the very heart % of this game. You can slice an image in to rectangular tiles using % any of several applications: \textsf{Adobe Illustrator}, % \textsf{Photoshop} and \textsf{ImageReady}, for example. But these are % expensive applications and you have already bought \textsf{Acrobat Pro}. For this purpose, % AeB Pro provides the \textsf{AcroSlicing} batch sequence. % % \begin{macrocode} %<*package> \RequirePackage{xkeyval} % \end{macrocode} % \begin{macro}{acromemory1} % One playing board, where you try to match identical icons. % \begin{macrocode} \DeclareOptionX{acromemory1}{\acromemoryitrue} % \end{macrocode} % \end{macro} % \begin{macro}{acromemory2} % Two playing boards, one board randomized the other not. Try to find the matching icons, % one from each of the two boards. % \begin{macrocode} \DeclareOptionX{acromemory2}{\acromemoryifalse} % \end{macrocode} % \end{macro} % \begin{macro}{acromemory2} % Two playing boards, one board randomized the other not. Try to find the matching icons, % one from each of the two boards. % \begin{macrocode} \DeclareOptionX{iconfile}{\def\iconsInOneFile{true}} \def\iconsInOneFile{false} % \end{macrocode} % \end{macro} % \begin{macro}{includehelp} % Only valid when \texttt{acromemory2} is taken, this option allows you to provide a % figure showing the completed puzzle. % \begin{macrocode} \DeclareOptionX{includehelp}{\includehelptrue} % \end{macrocode} % \end{macro} % \begin{macrocode} \newif\ifincludehelp \includehelpfalse \newif\ifacromemoryi \acromemoryifalse \ProcessOptionsX \RequirePackage{graphicx} \RequirePackage{comment} \ifacromemoryi \def\RanIdentifier{\@gobble} \includecomment{acromemory1} \excludecomment{acromemory2} \excludecomment{needhelp} \else \def\RanIdentifier{R\@gobble} \includecomment{acromemory2} \excludecomment{acromemory1} \ifincludehelp \includecomment{needhelp} \else \excludecomment{needhelp} \fi \fi % \end{macrocode} % \section{Main Macro Code} % When setting up your AcroMemory document, you need to provide this package with % certain information, as listed below. % \begin{macro}{\theTotalTiles} % \begin{macro}{\theNumRows} % \begin{macro}{\theNumCols} % \begin{macro}{\theImportPath} % \begin{macro}{\theIconExt} % \begin{macro}{\theTeXImageWidth} % \begin{macro}{\provideDimensions} %\begin{itemize} % \item \cs{theTotalTiles}: The total number of tiles in the game board. For example, % \verb!\theTotalTiles{20}!. This parameter is required. % \item \cs{theNumRows}: The number of rows in the game board. For example, % \verb!\theNumRows{5}!. This parameter is required. % \item \cs{theNumCols}: The number of columns in the game board. For example, % \verb!\theNumCols{4}!. This parameter is required. % \item \cs{theImportPath}: The import path to the basename of the image. The path % should use the path specification as defined in the PDF Ref, and the file name should % have no extension. For example, \verb!\theImportPath{myFig/myimages}!. Required. % There is an optional argument that is typically used when the iconfile is in effect % with the acromemory2 option, and an image of the game board is different from the % path given by the optional argument; for example, %\begin{verbatim} % \theImportPath[dpsweb/dpsweb]{dpsweb/dpsweb_package} %\end{verbatim} % The required argument points the packaged icons, the optional argument points to % a file showing the entire image % \item \cs{theIconExt}: The extension of the image file(s), Required if different % from \texttt{pdf}. % \item \cs{theTeXImageWidth}: The scaled width of the rectangular game board. The % game board will be rescaled so that its width is equal to the value specified % by the argument of this command, e.g., \verb!\theTeXImageWidth{2in}!. % \item\cs{provideDimensions}: If the dimension of the game board is known, the width % and height can be entered with this command using the two parameters. For example, % \verb!\provideDimensions{2in}{2.5in}! (width, height). %\end{itemize} % \begin{macrocode} \def\theTotalTiles#1{\def\nTotalTiles{#1}} \def\theNumRows#1{\def\nRows{#1}} \def\theNumCols#1{\def\nCols{#1}} \newcommand{\theImportPath}[2][]{% \ifacromemoryi \def\imageImportPath{#2} \def\importpath{#2} \else \def\argi{#1}\ifx\argi\@empty \def\imageImportPath{#2} \def\importpath{#2} \else \def\imageImportPath{#1} \def\importpath{#2} \fi \fi } \def\theIconExt#1{\def\iconsExt{#1}} \def\iconsExt{pdf} \def\theTeXImageWidth#1{\def\texImageWidth{#1}% {\dimen0 = \texImageWidth \xdef\imageWidth{\strip@pt\dimen0 }}% } \newcommand{\provideDimensions}[3][]{% \def\argi{#1}\ifx\argi\@empty {\dimen0 = #2 \xdef\trueImageWidth{\strip@pt\dimen0 }% \dimen0 = #3 \xdef\trueImageHeight{\strip@pt\dimen0 }}% \else \def\trueImageWidth{#1}\def\trueImageHeight{#2}% \fi \def\bProvideDimen{true}% } \def\bProvideDimen{false}% \def\trueImageWidth{0}\def\trueImageHeight{0}% % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \begin{macro}{\bDebug} % A debugging command. When executed in the preamble, more is written to the Acrobat console % as the document is opened the first time, also, the icons are initially visible so you can % see the layout, and quickly play the game. This was used in development extensively to help % develop the JavaScript. % \begin{macrocode} \def\bDebug{\def\memDebug{true}} \def\memDebug{false} % \end{macrocode} % \end{macro} % \begin{macro}{\helpImage} % \begin{macro}{\rolloverHelpButton} % \begin{macro}{\setHelpImageWidth} % \begin{macro}{\theHelpCaption} % When \texttt{acromemory2} options and the \texttt{includehelp} % options are taken, these commands are available. The command % \cs{helpImage} will contain an icon of the puzzle, and it width % is set by the command \cs{setHelpImageWidth}. The image is normally % hidden until the user rolls over the \cs{rolloverHelpButton}. The % icons appears with an caption under it, the content of the caption % can be entered using \cs{theHelpCaption}. % \begin{macrocode} \newcommand{\helpImage}[1][]{{% \ifincludehelp{\setbox0 = \hbox{% \includegraphics[draft,width=\helpImageWidth]% {\imageImportPath}}%[\Ff\FfReadOnly] \dimen0=\ht0 \advance\dimen0by14bp\ht0=\dimen0 \pushButton[\BC{}\BG{}\S{S}#1]{memoryhelp}{\the\wd0}{\the\ht0}}\fi }} \newcommand{\rolloverHelpButton}[3][]{% \ifincludehelp \pushButton[\CA{Help}\BC{0 0 1}\BG{0.89 0.9 0.9} \AA{\AAMouseEnter{\JS{% var f = this.getField("memoryhelp");\r oIcon = f.buttonGetIcon(1);\r f.buttonPosition = position.iconTextV;\r f.buttonSetIcon(oIcon,0);\r f.buttonSetCaption({cCaption: "\helpCaption"});\r f.textColor=color.blue;\r }}% \AAMouseExit{\JS{% var f = this.getField("memoryhelp");\r f.buttonPosition = position.iconOnly;\r f.buttonSetIcon(nullIcon,0); }}}#1]{checkhelp}{#2}{#3}% \fi } \newcommand{\setHelpImageWidth}[1]{\def\helpImageWidth{#1}} \def\helpImageWidth{1in} \def\theHelpCaption#1{\def\helpCaption{#1}} \theHelpCaption{A little help} % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \begin{macro}{\messageBox} % A message text field, as the user works the puzzle, the progress is reported % to this field. % \begin{macrocode} \newcommand{\messageBox}[3][]{% \textField[#1\Ff\FfMultiline]{MsgBox}{#2}{#3}} % \end{macrocode} % \end{macro} % \begin{macro}{\playItAgain} % For the \texttt{acromemory1} option, this button can be placed to reset % the game board, the icons are rearranged hand hidden again. % \begin{macrocode} \newcommand{\playItAgain}[3][]{\ifacromemoryi{\small \pushButton[\CA{Play again}#1\A{\JS{playagain();}}]% {playAgain}{#2}{#3}}% \fi } % \end{macrocode} % \end{macro} % \begin{macro}{\ulCornerHere} % \begin{macro}{\LulCornerHere} % \begin{macro}{\RulCornerHere} % \begin{macro}{\reserveSpaceByDimension} % \begin{macro}{\reserveSpaceByFile} % This group of buttons appear together. \cs{ulCornerHere} is used to % set the upper left corner of the game board in the case of the % \texttt{acromemory1} option; while \cs{LulCornerHere} and \cs{RulCornerHere} % do the same thing for the \texttt{acromemory2} option. One of the two commands % \cs{reserveSpaceByDimension} or \cs{reserveSpaceByFile} immediately follow % the `corner' commands. % \begin{macrocode} \def\ulCornerHere{\makebox[0pt][l]% {\pushButton[\autoCenter{n}]{ulcorner}{0pt}{0pt}% \pushButton[\autoCenter{n}\BC{}\BG{}\FB{true}\I{null}\TP{1}]{nullIconBtn}{0pt}{0pt}% }} \def\LulCornerHere{\makebox[0pt][l]% {\pushButton[\autoCenter{n}]{Lulcorner}{0pt}{0pt}% \pushButton[\autoCenter{n}\BC{}\BG{}\FB{true}\I{null}\TP{1}]{nullIconBtn}{0pt}{0pt}% }} \def\RulCornerHere{\makebox[0pt][l]% {\pushButton[\autoCenter{n}]{Rulcorner}{0pt}{0pt}}} % \end{macrocode} % The two arguments are $\texttt{\#1} = \mbox{width}$ and $\texttt{\#2} = \mbox{height}$. % \begin{macrocode} \def\reserveSpaceByDimension#1#2{% \hbox{\ifpreview\setlength\fboxrule{0.4pt}\setlength\fboxsep{0pt}% \@tempdima=#1\advance\@tempdima by-\fboxrule \@tempdimb=#2\advance\@tempdimb by-\fboxrule \fbox{\parbox[t][\@tempdimb][t]{\@tempdima}{\kern0pt\hfill\vfill}}% \else\parbox[t][#2][t]{#1}{\kern0pt\hfill\vfill}\fi}% } % \end{macrocode} % The optional argument can be used to insert a file that has the same aspect ratio as % the puzzle, the default is the one specified by the optional argument of % \cs{theImportPath}, which, if not specified, is the same as the required argument % of \cs{theImportPath} % \begin{macrocode} \newcommand{\reserveSpaceByFile}[1][\imageImportPath]% {{\setbox0=\hbox{\includegraphics[draft,width=\texImageWidth]{#1}}% \reserveSpaceByDimension{\wd0}{\ht0}}} % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \section{Document JavaScript for \textsf{AcroMemory}} % Most of the work of this package is done with document JavaScript, and here % it is. %\par\medskip\noindent %\StopEventually{JavaScript listing suppressed, % comment out \cs{OnlyDescription} at the beginning of this file to see % the JavaScript.} % \begin{macrocode} \begin{insDLJS*}[_MemLoaded]{memjs} \begin{newsegment}{AcroMemory 1: Global Data and Initialization} // Global Data: _MemLoaded=true; var randomDPS = new Array(\nTotalTiles+1); var imageNames = new Array(); imageNames.push("null"); var dpsl = randomDPS.length; var timeout = 10; var shutdown, rAE; var ok2Continue = true; var nRows = \nRows; var nCols = \nCols; var nCorrect = 0; var nAttempts = 0; for (i=1; i<=\nTotalTiles; i++) randomDPS[i]=i; % \end{macrocode} % We get the push button with a null icon (nullIconBtn) We get the null icon object % from it. This technique eliminates the previous need for the Acrobat application when % viewing the game. % \changes{v1.1}{2017/02/23}{use f.buttonGetIcon to get null icon object} % \begin{macrocode} var f=this.getField("nullIconBtn"); var nullIcon=f.buttonGetIcon(); var debug = \memDebug; \end{newsegment} \begin{acromemory1} \begin{newsegment}{AcroMemory 2: Initialize Pic Names} var nttl = \nTotalTiles/2; for ( var i = 1; i <= nttl; i++) { imageNames.push("pic"+i +".0"); imageNames.push("pic"+i +".1"); } var currentChoice = ""; var currentTile1 = 0; var currentTile2 = 0; var currentIconName = ""; \end{newsegment} \end{acromemory1} \begin{acromemory2} \begin{newsegment}{AcroMemory 2: Initialize Pic Names} var nttl = \nTotalTiles; for ( var i = 1; i <= nttl; i++) { imageNames.push("pic."+i); } var LcurrentChoice = 0; var LcurrentTile = 0; var RcurrentChoice = 0; var RcurrentTile = 0; \end{newsegment} \end{acromemory2} \begin{newsegment}{AcroMemory 3: Bubble Sort} // Clear DPS: function clearDPS() { for ( var i=1; i<=\nTotalTiles; i++ ) { var f = this.getField("Mem\RanIdentifier button."+i); f.buttonSetIcon(nullIcon); } } // Mixup DPS: function mixupDPS() { var i, rand; for (i=1; i<= \nTotalTiles; i++) { var rand = Math.random(); rand *= dpsl*dpsl; rand = Math.ceil(rand); rand = rand \% dpsl; if (rand == 0 ) rand = 1; temp = randomDPS[i]; randomDPS[i]=randomDPS[rand]; randomDPS[rand]=temp; } } // Show DPS: function showDPS() { for ( var i=1; i<=\nTotalTiles; i++ ) { var oIcon = this.getIcon(imageNames[randomDPS[i]]); var f = this.getField("Mem\RanIdentifier button."+i); f.buttonSetIcon(oIcon); } } // Sortout DPS: begin bubble sort function sortoutDPS() { outerLoop(randomDPS.length-1); } function outerLoop(i) { if ( ok2Continue && (i >= 0) ) shutdown = app.setTimeOut("app.clearTimeOut(shutdown); innerLoop("+i+",1);", timeout); } function innerLoop(i,j) { if ( j <= i ) { if (randomDPS[j-1] > randomDPS[j]) { var temp = randomDPS[j-1]; randomDPS[j-1] = randomDPS[j]; randomDPS[j] = temp; var oIcon = this.getIcon(imageNames[randomDPS[j-1]]); var f = this.getField("Mem\RanIdentifier button."+(j-1)); f.buttonSetIcon(oIcon); var oIcon = this.getIcon(imageNames[randomDPS[j]]); var f = this.getField("Mem\RanIdentifier button."+j); f.buttonSetIcon(oIcon); } j++ if ( ok2Continue ) shutdown = app.setTimeOut("app.clearTimeOut(shutdown); innerLoop("+i+","+j+");", timeout); } else { i--; outerLoop(i); } } function randomizePuzzle() { mixupDPS(); for ( var i=1; i<=\nTotalTiles; i++) { var g = this.getField("Mem\RanIdentifier button."+i); var oIcon = this.getIcon(imageNames[randomDPS[i]]); g.buttonSetIcon(oIcon,1); if (debug) g.buttonSetIcon(oIcon,0); } } \end{newsegment} \begin{acromemory1} \begin{newsegment}{AcroMemory 4: Tile Processing} function selectTile() // right side randomly arranged { var f = event.target; var oIcon = f.buttonGetIcon(1); f.buttonSetIcon(oIcon,0); var fname = f.name; var re1 = /Membutton\.(\d+)/; var index = re1.exec(fname); if (debug) console.println("index = " + index[1]); var thisiconName = imageNames[randomDPS[index[1]]]; if (debug) console.println("thisiconName = " + thisiconName); var re = /pic(\d+)\.(\d)/; var image = re.exec(thisiconName) if (debug) console.println("selected: " + image[1] + "." + image[2]); if ( currentChoice == "" ) { currentChoice = fname; currentTile1=image[1]; currentTile2=image[2]; currentIconName = thisiconName; return; } if ( (image[1] == currentTile1) && (image[2] != currentTile2) ) { // right choice nCorrect++; nAttempts++ f.readonly = true; var g = this.getField(currentChoice); g.readonly = true; reportProgress(nCorrect,nAttempts); resetCounters(); } else { // wrong choice nAttempts++ reportProgress(nCorrect,nAttempts); rAE = app.setTimeOut("resetAfterError(\""+currentChoice+"\",\""+fname+"\")", 1000); resetCounters(); } } function resetCounters () { currentChoice = ""; currentTile1 = 0; currentTile2 = 0; currentIconName = ""; } function resetAfterError(l,r) { try { app.clearTimeOut(rAE); } catch(e) {}; var f = this.getField(l); var g = this.getField(r); if (!debug) g.buttonSetIcon(nullIcon,0); // g.strokeColor=color.black; if (!debug) f.buttonSetIcon(nullIcon,0); // f.strokeColor=color.black; } function executePostGameEffects() {return;} function playagain() { for ( var i=1; i<=\nTotalTiles; i++) { var g = this.getField("Membutton."+i); g.buttonSetIcon(nullIcon,0); } g = this.getField("Membutton"); g.readonly=false; resetCounters(); nCorrect = 0; nAttempts = 0; reportProgress(nCorrect,nAttempts); randomizePuzzle(); } \end{newsegment} \end{acromemory1} \begin{acromemory2} \begin{newsegment}{AcroMemory 4: Tile Processing} function selectRandomTile(nCnt,n) // right side randomly arranged { if ( RcurrentChoice != 0 ) return; RcurrentChoice = nCnt; RcurrentTile = n; nAttempts++; var f = event.target; f.strokeColor = ["RGB", 0, .6, 0]; var oIcon = f.buttonGetIcon(1); f.buttonSetIcon(oIcon,0); if ( LcurrentChoice != 0 ) { if (debug) console.println("LcurrentChoice = " + LcurrentChoice + ", RcurrentChoice = " + RcurrentChoice); if ( LcurrentChoice == nCnt ) {// right answer // need to make right side hidden and readonly // need to make this button readonly var g = this.getField("MemLbutton."+LcurrentChoice); g.strokeColor=color.transparent; g.readonly = true; f.strokeColor=color.transparent; f.readonly = true; if (++nCorrect == \nTotalTiles ) // game complete executePostGameEffects(); reportProgress(nCorrect,nAttempts); resetCounters(); } else { // wrong answer // need to set current choices back to zero reportProgress(nCorrect,nAttempts); rAE = app.setTimeOut("resetAfterError("+LcurrentTile+","+RcurrentTile+")", 1000); resetCounters(); } } } function selectNonRandomTile(nCnt,n) // left side, arranged in natural order { if ( LcurrentChoice != 0 ) return; LcurrentChoice = nCnt; LcurrentTile = n; var f = event.target; f.strokeColor = ["RGB", 0, .6, 0]; var oIcon = f.buttonGetIcon(1); f.buttonSetIcon(oIcon,0); if ( RcurrentChoice != 0 ) { if (debug) console.println("LcurrentChoice = " + LcurrentChoice + ", RcurrentChoice = " + RcurrentChoice); if ( RcurrentChoice == nCnt ) {// right answer // need to make right side hidden and readonly // need to make this button readonly var g = this.getField("MemRbutton."+RcurrentTile); g.strokeColor=color.transparent; g.readonly = true; f.readonly = true; f.strokeColor=color.transparent; if (++nCorrect == \nTotalTiles ) // game complete executePostGameEffects(); reportProgress(nCorrect,nAttempts); resetCounters(); } else { // wrong answer // need to set current choices back to zero reportProgress(nCorrect,nAttempts); rAE = app.setTimeOut("resetAfterError("+LcurrentTile+","+RcurrentTile+")", 1000); resetCounters(); } } } function resetCounters () { LcurrentChoice = 0; RcurrentChoice = 0; LcurrentTile = 0; RcurrentTile = 0; } function resetAfterError(l,r) { try { app.clearTimeOut(rAE); } catch(e) {}; var f = this.getField("MemLbutton."+l); var g = this.getField("MemRbutton."+r); if (!debug) g.buttonSetIcon(nullIcon,0); g.strokeColor=color.black; if (!debug) f.buttonSetIcon(nullIcon,0); f.strokeColor=color.black; } function executePostGameEffects() { sortoutDPS(); var fL = this.getField("MemLbutton.1"); var fR = this.getField("MemRbutton.1"); var LulCorner = fL.rect; var RulCorner = fR.rect; var mWidth = LulCorner[2]-LulCorner[0]; var mHeight = LulCorner[1]-LulCorner[3]; var nCnt = 0; for ( var i=0; i % \end{macrocode} % \Finale \endinput