RFETN
RFETN

Reputation: 13

Using Ghostscript, How Do I Put Page Numbers on Combined PDF Files

Using only Ghostscript on a fairly locked down bare bones Linux machine, I need to combine three existing PDFs into a new PDF, put a static header on each page of the new PDF, put a static footer on each page of the new PDF, and number each of the pages (such as "Page 1257").

I have solutions to the first three problems. Ghostscript out of the box can easily combine multiple PDFs into a single new PDF. Tinkering with PostScript in the -c command line option, I can add the static header and footer to the new PDF. What I cannot yet do is get it to put page numbers on the new PDF.

This is the complex command line I now have:

gs -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -sOutputFile=final.pdf -c "<</EndPage {2 ne {200 0 0 setrgbcolor /NimbusSans-Bold 24 selectfont 240 2 moveto (Static Footer) show 240 773 moveto (Static Header) show 0 0 0 setrgbcolor /NimbusSans-Regular 12 selectfont 2 24 moveto (Page ) show true}{pop false} ifelse} >> setpagedevice" -f title.pdf parta.pdf partb.pdf

Removing the static header and footer pieces gets to a slightly simpler command line:

gs -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -sOutputFile=final.pdf -c "<</EndPage {2 ne {0 0 0 setrgbcolor /NimbusSans-Regular 12 selectfont 2 24 moveto (Page ) show true}{pop false} ifelse} >> setpagedevice" -f title.pdf parta.pdf partb.pdf

I have tried many things to get a page number to show up but they either crash Ghostscript or just keep showing the same page number on each page. The only other complication is the new PDF will be between 1,000 and 2,000 pages.

What I need is a good code example of how to make PostScript display an incrementing page number on each page of a PDF.

Upvotes: 1

Views: 2171

Answers (1)

KenS
KenS

Reputation: 31141

Its not terribly hard to count pages in PostScript. I expect you know most of the following already, but I'm going to take baby steps for the benefit of anyone else who comes across this later.

We start by creating a dictionary all our own where we can store stuff. We make sure this is defined in 'userdict' so we can always find it (userdict, like systemdict, is always available). THe name chosen should be nicely unique to prevent any other PostScript or PDF prgrams overwriting it!

userdict begin                  %% Make userdict the current dictionary
/My_working_Dict                %% Create a name for our dict (choose something unique)
10 dict                         %% create a 10-element dictionary
def                             %% def takes a key and value and puts them in the current dictionary
My_working_Dict begin           %% make our working dictionary the current one
/PageCount                      %% Make a name for our counter
0                               %% 0, this will be the initial value
def                             %% define PageCount as 0 in the current dictionary
end                             %% close the current dictionary (My_working_Dict)
end                             %% close the current dictionary (userdict)

There are more efficient ways to do this, but that's an easy method to describe and follow. From this point until we close the PostScript interpreter (or restore it back to an earlier state) userdict will contain a dictionary called My_working_Dict which will have a key called PageCount. The value associated with PageCount will be 0 initially, but we can change that.

You've defined an EndPage procedure like this:

<<
  /EndPage {
    2 ne {
      200 0 0 setrgbcolor
      /NimbusSans-Bold 24 selectfont
      240 2 moveto 
      (Static Footer) show
      240 773 moveto
      (Static Header) show
      0 0 0 setrgbcolor
      /NimbusSans-Regular 12 selectfont
      2 24 moveto
      (Page ) show
      true
    }
    {
      pop
      false
    } ifelse
  }
>> setpagedevice

Now when the EndPage procedure is called, there are two numbers on the stack, the topmost number is the 'reason code' and the next number is the count of previous showpage executions. Now you would think (reasonably) you could use that count for your page count, but unfortunately it gets reset to 0 on every 'setpagedevice' call, and the PDF interpreter calls setpagedevice on every page, because its possible for every page in a PDF file to be a adiffrent size, and setpagedevice is how we change the page size.

When we return from the EndPage procedure we must push a boolean on the stack which is either 'true' (send the page to the output) or 'false' (throw it away and do nothing).

So, your procedure tests the reason code to see why EndPage has been called. If its not '2' (device is being deactivated) then its either copypage or showpage, so you draw your desired additions on the page. If it is 2 then we just pop the count of pages and return 'false' so that we don't try to emit an extra page.

If it is 2 then you set the colour to RGB black (you could do 0 setgray instead) find the font NimbusSans-Bold, scale it to 24 point and set it as the current font. You then move to the position x=240, y = 2 (0,0 is bottom left and units are points, 1/72nd of an inch) and draw the text 'Static Footer' (NB parentheses are string delimiters in PostScript)

Then you move to the position x=240, y=773 and draw the text 'Static Header'.

You then redundantly set the colour again, you don't need to do that, it stays the same until you change it, and again find the font NimbusSans-Bold, this time scaling it to 12 points and selecting it as the current font. Finally you move to the position x=2, y=24 and draw the text 'Page '.

So all you need to do is extend that EndPage procedure so that it picks up the count of pages from our dictionary, converts it to a string, and draws the resulting string.

Something like :

userdict begin                  %% Make userdict the current dictionary
/My_working_Dict                %% Create a name for our dict (choose something unique)
10 dict                         %% create a 10-element dictionary
def                             %% def takes a key and value and puts them in the current dictionary
My_working_Dict begin           %% make our working dictionary the current one
/PageCount                      %% Make a name for our counter
0                               %% 0, this will be the initial value
def                             %% define PageCount as 0 in the current dictionary
end                             %% close the current dictionary (My_working_Dict)
end                             %% close the current dictionary (userdict)

<<
  /EndPage {
    2 ne {
      0 0 0 setrgbcolor
      /NimbusSans-Bold 24 selectfont
      240 2 moveto 
      (Static Footer) show
      240 773 moveto
      (Static Header) show
      0 0 0 setrgbcolor
      /NimbusSans-Regular 12 selectfont
      2 24 moveto
      (Page ) show

      userdict /My_working_Dict get   %% get My_working_dict from userdict (leaves My_working_dict on the operand stack
      dup                             %% duplicate the dictionary reference
      /PageCount get                  %% get PageCount from the dictionary on the stack
      1 add                           %% add one to the count
      /PageCount                      %% put the key on the stack
                                      %% stack now holds << >> int /PageCount
                                      %% where << >> is a reference to My_working_Dict, int is our new value for PageCount, and /PageCount is the key name we are using
      exch                            %% swap the topmost two stack items
                                      %% stack is now << >> /PageCount int
      put                             %% puts the top two items on the stack into the dictionary which is third on the stack.

      256 string                      %% Temporary string to hold the count
      userdict /My_working_Dict get   %% get My_working_dict from userdict (leaves My_working_dict on the operand stack
      /PageCount get                  %% get PageCount from the dictionary on the stack
      exch
      cvs                             %% Convert the top object on the stack into a string, storing the result in the second object down, whic must be a string
      show                            %% draw the string on the page using the current colour and font.
      true
    }
    {
      pop
      false
    } ifelse
  }
>> setpagedevice

You would then execute Ghostscript with :

gs -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -sOutputFile=final.pdf modifyPDF.ps title.pdf parta.pdf partb.pdf

Now I haven't actually tried this code, so bugs are entirely possible.

[Update 2]

This program is basically the same, but stores the variable in a dicitonary in global VM, stored in globaldict, in order to defeat save/restore.

globaldict begin                %% Make globaldict the current dictionary
currentglobal true setglobal    %% We need to make the VM allocation mode for the dictionary global
/My_working_Dict                %% Create a name for our dict (choose something unique)
10 dict                         %% create a 10-element dictionary
def                             %% def takes a key and value and puts them in the current dictionary
setglobal           %% put the VM allocation mode back
globaldict /My_working_Dict     %% Get the dictionary from globaldict
get begin                       %% make our working dictionary the current one
/PageCount                      %% Make a name for our counter
0                               %% 0, this will be the initial value
def                             %% define PageCount as 0 in the current dictionary
end                             %% close the current dictionary (My_working_Dict)
end                             %% close the current dictionary (globaldict)

<<
  /EndPage {
    2 ne {
      0 0 0 setrgbcolor
      /NimbusSans-Bold 24 selectfont
      240 2 moveto 
      (Static Footer) show
      240 773 moveto
      (Static Header) show
      0 0 0 setrgbcolor
      /NimbusSans-Regular 12 selectfont
      2 24 moveto
      (Page ) show

      globaldict /My_working_Dict get %% get My_working_dict from globaldict (leaves My_working_dict on the operand stack
      dup                             %% duplicate the dictionary reference
      /PageCount get                  %% get PageCount from the dictionary on the stack
      1 add                           %% add one to the count
      /PageCount                      %% put the key on the stack
                                      %% stack now holds << >> int /PageCount
                                      %% where << >> is a reference to My_working_Dict, int is our new value for PageCount, and /PageCount is the key name we are using
      exch                            %% swap the topmost two stack items
                                      %% stack is now << >> /PageCount int
      put                             %% puts the top two items on the stack into the dictionary which is third on the stack.
      globaldict /My_working_Dict get %% get My_working_dict from globaldict (leaves My_working_dict on the operand stack
      /PageCount get                  %% get PageCount from the dictionary on the stack
      256 string                      %% Temporary string to hold the count
      globaldict /My_working_Dict get %% get My_working_dict from globaldict (leaves My_working_dict on the operand stack
      /PageCount get                  %% get PageCount from the dictionary on the stack
      exch
      cvs                             %% Convert the top object on the stack into a string, storing the result in the second object down, whic must be a string
      show                            %% draw the string on the page using the current colour and font.
      true
    }
    {
      pop
      false
    } ifelse
  }
>> setpagedevice

I have tried this with the suppled example and it works for me.

Upvotes: 3

Related Questions