%macro pageof(

      /* Specify a file or fileref.                    */

      /* Physical file names should be in quotes.      */
      /* Filerefs/DDnames should not be in quotes.     */

   pgm    =,        /* program file to execute         */
   output =,        /* output file                     */

      /* placement of page number text on print page   */

   just   = center,     /* left, center, or right      */
   topbot = bottom,     /* top or bottom               */
   psadd  = 2,          /* number of lines from text   */
   pagetext = Page PAGEX of PAGEN, /* page number text */
   cc     = !         /* character to use as form feed */

   );

      /* Initialize global system options.             */

   options number pageno=1 formdlim="&cc";


      /* Initialize column to begin reading            */
      /* original output.                              */

   %let strt_col = 1;


      /* Initialize system-specific files and options. */
      /* Determine the operating system with automatic */
      /* macro variable SYSSCP.                        */

   %if &sysscp=HP 800 %then
   %do;

      filename temp1 'potemp1.txt';
      filename temp2 'potemp2.txt';

   %end;

   %else
   %if &sysscp=VMS or &sysscp=OS2 or &sysscp=WIN %then
   %do;

         /* Do not require the user to press enter to  */
         /* leave the X window brought up by an X      */
         /* statement.                                 */

      options noxwait;
      filename temp1 'potemp1.txt';
      filename temp2 'potemp2.txt';

   %end;

   %else
   %if &sysscp=CMS %then
   %do;

      filename temp1 'potemp1 txt a';
      filename temp2 'potemp2 txt a';

      %let strt_col = %eval(&strt_col + 1);
      %let psadd=%eval(&psadd + 2);

   %end;

   %else
   %if &sysscp=OS %then
   %do;

      filename temp1 '&temp';

         /* Cause a short time delay between           */
         /* allocations because the time value is used */
         /* in the name of the temporary file.         */

      data _null_;
      run;

      filename temp2 '&temp';

      %let strt_col = %eval(&strt_col + 1);
      %let psadd=%eval(&psadd + 2);

   %end;


      /* Execute the user's program in a PRINTTO block */
      /* to save the output for post processing.       */

   proc printto print=temp1 new;
   %include &pgm;
   proc printto;
   run;


      /* Set page eject back to the system default.    */

   options formdlim='';


      /* Put the original LINESIZE= and PAGESIZE=      */
      /* settings into macro variables for later use   */
      /* by reading SASHELP.VOPTION and writing the    */
      /* values into macro variables.                  */

   data _null_;
      set sashelp.voption;
      where optname='LINESIZE' or optname='PAGESIZE';
      call symput(optname,setting);
   run;


      /* Route the output of a dummy PROC PRINT to the */
      /* TEMP2 file to generate the next page number.  */

   proc printto print=temp2 new;
   data _temp_;
      x=1;
   proc print;
      options number;
   proc printto;
   run;


      /* Read the first line of output to get the page */
      /* number. Store the number in a macro variable. */

   data _null_;
      infile temp2 truncover;
      input record $ 1-200;
      call symput('lastpage',trim(left(put(input(reverse
         (scan(reverse(record),1)),6.)-1,6.))));
      stop;
   run;

      /* Adjust options.                               */

   %let pagesize=%eval(&pagesize+&psadd);
   options nodate nonumber ps=&pagesize;


      /* Use macro logic to parse the text parameter   */
      /* and create the appropriate DATA step syntax   */
      /* to print the page number.                     */

      /* Find the position of PAGEX in the string.     */

   %let xpos=%index(%nrbquote(&pagetext),PAGEX);

      /* If PAGEX occurs at the beginning of the       */
      /* string, substitute this:                      */

   %if &xpos=1 %then
      %let maketext = trim(left(put(pagenum,6.))) ||
         "%substr(&pagetext,6)";

   %else
   %do;

      /* If PAGEX is not at the beginning of the       */
      /* string, substitute this:                      */

      %let maketext = "%substr(&pagetext,1,&xpos-1)" ||
         trim(left(put(pagenum,6.)));

      /* If PAGEX is not at the end of the string, add */
      /* the remainder of the string.                  */

      %if &xpos+4 le %length(&pagetext) %then
         %let maketext = &maketext ||
            "%substr(&pagetext,&xpos+5)";

   %end;

      /* Find the position of PAGEN.                   */

   %let npos = %index(&maketext,PAGEN);

      /* Substitute the total page count for PAGEN.    */

   %let maketext = %qsubstr(&maketext,1,
      &npos-1)%nrstr(&lastpage)%qsubstr(&maketext,
      &npos+5);


      /* Read in TEMP1 and check for carriage control. */
      /* Each time carriage control is encountered,    */
      /* read the current page number and then rewrite */
      /* it at the desired location along with the     */
      /* total page count. Otherwise write the original*/
      /* record.                                       */

   data _null_;
      infile temp1 length=len truncover eof=eof noprint;
      length text $ 200 next_cc $ 10;
      retain pagenum;

         /* Use the option NOTITLES. Since the input   */
         /* file already has titles, do not repeat     */
         /* printing of the titles.                    */

      file &output notitles n=ps line=cur_line

         /* If the user does not want output to go to  */
         /* the OUTPUT window, then use these options: */
         /* PRINT and OLD.                             */

      %if "%upcase(&output)" ne "PRINT" %then %do;
         print old
      %end;
      ;


         /* Read a line of output. Scan for carriage   */
         /* control and page number.                   */

      input @&strt_col record $varying200. len ;


         /* If the line begins with carriage control,  */
         /* read the page number and replace it        */
         /* with blanks.                               */

      if substr(record,2,10) eq
         "&cc&cc&cc&cc&cc&cc&cc&cc&cc&cc" then
         do;                   /* Page eject detected. */

               /* If the line is a carriage control    */
               /* line, read in the next (blank) line. */

            input;

               /* Read a line of output.               */

            input @&strt_col record $varying200. len ;

               /* Scan for the current page number.    */

            pagenum=reverse(scan(reverse(record),1));


               /* Replace the original page number with*/
               /* blanks because the page number will  */
               /* be printed in a different place.     */
               /* To do this, use the SUBSTR function  */
               /* on the left side of the equal sign.  */
               /* In this case SUBSTR works as a text  */
               /* replacement function. So, replace the*/
               /* text from the first character of the */
               /* page number to the end of the line.  */

            substr(record,
               length(trim(record)) - length(pagenum) +1,
               length(pagenum)) = ' ';


               /* If user requests page number text at */
               /* top of page and requests to increase */
               /* page size, then add &PSADD blank     */
               /* lines to the top of each page.       */

            if (&psadd gt 0) and
               ("%upcase(%substr(&topbot,1,1))" eq "T")
               then
               do;  /* Add blank lines to top of page. */

                  do line_num = 1 to %eval(&psadd);
                     put #line_num @1 ' ';
                  end;

               end; /* Add blank lines to top of page. */

      end;                     /* Page eject detected. */


         /* Write the record back to the file.         */

      if _n_ gt 1 then
         do;      /* Write regular record back to file.*/

            put @1 record $varying200. len @;
            if cur_line lt &pagesize then put;

         end;     /* Write regular record back to file.*/


         /* Look ahead to the next input line to       */
         /* determine if it has a carriage control     */
         /* character. If it does, write the page      */
         /* number on the current page.                */

      input @2 next_cc $10. @@;


         /* If look-ahead detects a page eject, write  */
         /* page number text.                          */

      if next_cc eq "&cc&cc&cc&cc&cc&cc&cc&cc&cc&cc"
         and pagenum ge 1 then
         do;                /* Write page number text. */

            /* Use macro logic to set the line and     */
            /* column placement for the page-numbering */
            /* text.                                   */

            /* Place text at top or bottom of the page.*/
            /* If T, place text on line 1.             */

         %if %substr(%upcase(&topbot),1,1) eq T %then
            %let line=1;

            /* Otherwise place text on last line       */
            /* (same as page size).                    */

         %else
            %let line=&pagesize;

            /* Place text at left, center, or right.   */

         %if %substr(%upcase(&just),1,1) eq L %then
            %let col=1;

         %else
         %if %substr(%upcase(&just),1,1) eq C %then
            %let col=(floor((&linesize-length(text))/2));

         %else
         %if %substr(%upcase(&just),1,1) eq R %then
            %let col=(&linesize-length(text)-1);

            /* Concatenate text for page numbering.    */

         text=%unquote(&maketext);
         put #&line @&col text;
         put _page_;

      end;                  /* Write page number text. */

      return;         /* End of main part of DATA step */

         /* To avoid a lost card problem, jump to end  */
         /* of data when eof is read in input file.    */

   eof:

         /* Concatenate text for page numbering.       */

      text=%unquote(&maketext);
      put #&line @&col text;

   run;                     /* End of entire DATA step */


      /* Delete temporary system files.                */

   %if &sysscp=HP 800 %then
   %do;

      X 'rm potemp1.txt potemp2.txt';

   %end;

   %else %if &sysscp=OS2 or &sysscp=WIN %then
   %do;

      X 'del potemp1.txt';
      X 'del potemp2.txt';

   %end;

   %else %if &sysscp=VMS %then
   %do;

      X 'del potemp1.txt;*';
      X 'del potemp2.txt;*';

   %end;

   %else %if &sysscp=CMS %then
   %do;

      X 'erase potemp1 txt a';
      X 'erase potemp2 txt a';

   %end;

      /* No action is needed for &SYSSCP=OS. Files are */
      /* deleted automatically when the SAS session    */
      /* ends.                                         */

      /* Set the page size and page number to the      */
      /* original settings.                            */

   options ps=%eval(&pagesize-&psadd)
      pageno=%eval(&lastpage+1);

%mend pageof;