Search code examples
pdfghostscriptpostscript

How to add page numbers with their own overlay background to PDF using Ghostscript?


I want to find a way how to add overlay page numbers on PDF pages with motley background. The problem is that if I use a usual approach like this:

(pagecount.ps)

globaldict
/MyPageCount 1 put

<<
   /EndPage
   {
     exch pop 0 eq dup
     {
       /Helvetica 12 selectfont
       MyPageCount =string
       cvs dup stringwidth pop
       currentpagedevice

       /PageSize get 0 get exch
       sub 460 sub 810
       moveto show
       globaldict

       /MyPageCount MyPageCount 1 add put
     } if
   } bind
>> setpagedevice

Then gs -dNOSAFER -dBATCH -dNOPAUSE -sDEVICE=pdfwrite -o output.pdf -sDEVICE=pdfwrite -f pagecount.ps input.pdf

It works but page numbers previously seen on blank pages are hardly visible now due to motley background stuff on pages.

So I want some little white substrate to be drawn around numbers to obscure the area they occupy on a page but with the numbers themselves being visible.

One idea was to use annotations with \Rect:

(pagecount.ps, originally taken from How to add page numbers to Postscript/PDF)

globaldict
/MyPageCount 1 put

<<
   /EndPage
   {
     newpath [
       /Rect
       [ 20 dup moveto (Link on page1) false charpath pathbbox
       2 add 4 1 roll 2 add 4 1 roll 2 sub 4 1 roll 2 sub 4 1 roll
     newpath ]

     exch pop 0 eq dup
     {
       /Helvetica 12 selectfont
       MyPageCount =string
       cvs dup stringwidth pop
       currentpagedevice

       /PageSize get 0 get exch
       sub 460 sub 810
       moveto show
       globaldict

       /MyPageCount MyPageCount 1 add put
     } if
   } bind
>> setpagedevice

But it produces only a white page.

How to make page numbers with their own little background be drawn on PDF pages using Ghostscript?

UPD: I updated pagecount.ps according to advices and now I have right little background in right place but page numbers stopped to get drawn (Error: /nocurrentpoint in /--.endpage--).

New pagecount.ps:

globaldict
/MyPageCount 1 put

<<
   /EndPage
   {
     exch pop 0 eq dup
     {
       /Helvetica 12 selectfont
       MyPageCount =string
       cvs dup stringwidth pop
       currentpagedevice

       /PageSize get 0 get exch
       sub 460 sub 810
       moveto                              % move to text drawing position

       % lines new to pagecount.ps

       dup                                 % duplicate string on the stack
       true charpath flattenpath pathbbox  % consume the string and put coordinates of bounding box to stack

       newpath                             % start drawing bounding box
       3 index 3 index moveto              % copy llx and lly to the top of stack and move to them
       3 index 1 index lineto              % copy llx and ury to the top of stack and draw line to them
       1 index 1 index lineto              % copy urx and ury to the top of stack and draw line to them
       1 index 3 index lineto              % copy urx and lly to the top of stack and draw line to them
       3 index 3 index lineto              % copy llx and lly to the top of stack and draw line to them
       closepath

       pop pop pop pop                     % remove coordinates of bounding box from stack

       gsave
       1 0 0 setrgbcolor
       fill
       grestore
       stroke

       % end of new lines

show
       globaldict

       /MyPageCount MyPageCount 1 add put
     } if
   } bind
>> setpagedevice

UPD 2: I updated pagecount.ps to fix (Error: /nocurrentpoint in /--.endpage--). I also removed stroke command afted filling the rectangle. Now I have Error: /typecheck in /--.endpage-- Operand stack: 0 true 595 (1)

New pagecount.ps:

globaldict
/MyPageCount 1 put

<<
   /EndPage
   {
     exch pop 0 eq dup
     {
       /Helvetica 12 selectfont
       MyPageCount =string
       cvs dup stringwidth pop
       currentpagedevice

       /PageSize get 0 get exch
       sub 460 sub 810
       moveto                              % move to text drawing position

       % lines new to pagecount.ps

       dup                                 % duplicate string on the stack
       true charpath flattenpath pathbbox  % consume the string and put coordinates of bounding box to stack

       newpath                             % start drawing bounding box
       3 index 3 index moveto              % copy llx and lly to the top of stack and move to them
       3 index 1 index lineto              % copy llx and ury to the top of stack and draw line to them
       1 index 1 index lineto              % copy urx and ury to the top of stack and draw line to them
       1 index 3 index lineto              % copy urx and lly to the top of stack and draw line to them
       3 index 3 index lineto              % copy llx and lly to the top of stack and draw line to them
       closepath

       pop pop pop pop                     % remove coordinates of bounding box from stack

       gsave
       1 0 0 setrgbcolor
       fill
       grestore

       % end of new lines


       currentpagedevice

       /PageSize get 0 get exch
       sub 460 sub 810
       moveto show
       globaldict

       /MyPageCount MyPageCount 1 add put
     } if
   } bind
>> setpagedevice

Solution

  • Finally it works.

    I've made 2 variants of the script. The 1st one is just page numbering where numbers have obscuring background so that motley pdf stuff could not make the numbers less readable.

    Some clarification: sub 460 sub 710 below are related to page number text box and stand for x and y coordinates, respectively.

    1 0 0 setrgbcolor fill related to textbox overlay background colour (red here).

    1 0 0 setrgbcolor 5 setlinewidth related to textbox overlay contour colour and to its width

    0 0 0 setrgbcolor related to numbers colour

    /MyPageCount 16 put related to start page number

    See: enter image description here

    globaldict
    /MyPageCount 16 put
    
    <<
       /EndPage
       {
         exch pop 0 eq dup
         {
    
    /Helvetica 12 selectfont
    
    MyPageCount =string
           cvs
      dup stringwidth pop
           currentpagedevice
    
    
           /PageSize get 0 get exch
           sub 460 sub 710
    
    
           moveto                              % move to text drawing position
    
           % lines new to pagecount.ps
    
           dup                                 % duplicate string on the stack
    
    
           true charpath flattenpath pathbbox  % consume the string and put coordinates of bounding box to stack
    
    
           newpath                             % start drawing bounding box
           3 index 3 index moveto              % copy llx and lly to the top of stack and move to them
           3 index 1 index lineto              % copy llx and ury to the top of stack and draw line to them
           1 index 1 index lineto              % copy urx and ury to the top of stack and draw line to them
           1 index 3 index lineto              % copy urx and lly to the top of stack and draw line to them
           3 index 3 index lineto              % copy llx and lly to the top of stack and draw line to them
           closepath
    
                                % remove coordinates of bounding box from stack
    
           gsave
    
           1 0 0 setrgbcolor
    
    
           fill grestore 1 0 0 setrgbcolor
     5 setlinewidth stroke
    pop pop pop pop
    
           0 0 0 setrgbcolor
    
    
    dup stringwidth pop
           currentpagedevice
    
           /PageSize get 0 get exch
           sub 460 sub 710
           moveto
    
    
    
           % end of new lines
    
    
     show
    
           globaldict
    
           /MyPageCount MyPageCount 1 add put
         } if
       } bind
    >> setpagedevice
    

    The second one is a numbering scheme where numbers have auxilliary text and it all has obscuring background.

    See: enter image description here

    userdict begin
    %%EndProlog
    
    %%BeginSetup
    % The following encodes a few useful Unicode glyphs, if only a few are needed.
    % Based on https://stackoverflow.com/questions/54840594/show-unicode-characters-in-postscript
    % Usage: /Times-Roman /Times-Roman-Uni UniVec new-font-encoding
    
    /new-font-encoding { <<>> begin
        /newcodesandnames exch def
        /newfontname exch def
        /basefontname exch def
        /basefontdict basefontname findfont def     % Get the font dictionary on which to base the re-encoded version.
        /newfont basefontdict maxlength dict def    % Create a dictionary to hold the description for the re-encoded font.
        basefontdict
            { exch dup /FID ne                      % Copy all the entries in the base font dictionary to the new dictionary except for the FID field.
                { dup /Encoding eq
                    { exch dup length array copy    % Make a copy of the Encoding field.
                        newfont 3 1 roll put }
                    { exch newfont 3 1 roll put }
                    ifelse
                }
                { pop pop }                         % Ignore the FID pair.
                ifelse
            } forall
        newfont /FontName newfontname put           % Install the new name.
        newcodesandnames aload pop                  % Modify the encoding vector. First load the new encoding and name pairs onto the operand stack.
        newcodesandnames length 2 idiv
            { newfont /Encoding get 3 1 roll put}
            repeat                                  % For each pair on the stack, put the new name into the designated position in the encoding vector.
        newfontname newfont definefont pop          % Now make the re-encoded font description into a POSTSCRIPT font.
                                                    % Ignore the modified dictionary returned on the operand stack by the definefont operator.
    end} def
    
    /Helvetica /Helvetica-Uni [
        16#43  /afii08941        % ASCII 43 = C
        16#44  /club             % ASCII 44 = D
        16#45  /afii00208        % ASCII 45 = E
    
    
    ] new-font-encoding
    
    /Helv
    <<
       /FontType 0
       /FontMatrix [ 1 0 0 1 0 0 ]
       /FDepVector [
          /Helvetica findfont        % this is Font0
          /Helvetica-Uni findfont    % this is Font1
          ]
       /Encoding [ 0 1 ]
       /FMapType 3
    >> definefont pop
    
    globaldict
    /MyPageCount 16 put
    
    
    
    <<
       /EndPage
       {
         exch pop 0 eq dup
         {
    
    /Helv 12 selectfont
    
    (\377\001C\377\000\377\001D\377\000\377\001E\377\000Page )
    MyPageCount =string
           cvs concatstrings
      dup stringwidth pop
           currentpagedevice
    
    
           /PageSize get 0 get exch
           sub 460 sub 710
    
    
           moveto                              % move to text drawing position
    
           % lines new to pagecount.ps
    
           dup                                 % duplicate string on the stack
    
    
           true charpath flattenpath pathbbox  % consume the string and put coordinates of bounding box to stack
    
    
           newpath                             % start drawing bounding box
           3 index 3 index moveto              % copy llx and lly to the top of stack and move to them
           3 index 1 index lineto              % copy llx and ury to the top of stack and draw line to them
           1 index 1 index lineto              % copy urx and ury to the top of stack and draw line to them
           1 index 3 index lineto              % copy urx and lly to the top of stack and draw line to them
           3 index 3 index lineto              % copy llx and lly to the top of stack and draw line to them
           closepath
    
                                % remove coordinates of bounding box from stack
    
           gsave
    
           1 0 0 setrgbcolor
    
    
           fill grestore 1 0 0 setrgbcolor
     5 setlinewidth stroke
    pop pop pop pop
    
           0 0 0 setrgbcolor
    
    
    dup stringwidth pop
           currentpagedevice
    
           /PageSize get 0 get exch
           sub 460 sub 710
           moveto
    
    
    
           % end of new lines
    
    
     show
    
           globaldict
    
           /MyPageCount MyPageCount 1 add put
         } if
       } bind
    >> setpagedevice
    

    As you can see, I could insert [₤♣―Page 16] (by using /afii08941, /club, /afii00208) where [...16] is generated automatically on each page in increasing order.

    I used special symbols and the lines from userdict begin to >> definefont pop serve for this aim. You may remove these lines if you do not need special symbols in page numbering.

    Here is the table of symbols to insert some mnemonics, latin, greek, cyrillic and arabic symbols into Postscript output (PDFs in my case) https://root.cern/doc/v622/AdobeGlyphList_8h_source.html

    Special symbols also need remapping scheme, you have to write a definition for it:

    /Helvetica /Helvetica-Uni [
        16#43  /afii08941        % ASCII 43 = C
        16#44  /club             % ASCII 44 = D
        16#45  /afii00208        % ASCII 45 = E
    

    Then you can put special symbols on the top of Postscript stack and concatenate them with page numbering counter:

    (\377\001C\377\000\377\001D\377\000\377\001E\377\000Page )
    MyPageCount =string
           cvs concatstrings
    

    \377\001C\377\000 becomes ₤, \377\001D\377\000 becomes ♣. CD in bold.

    Note how gsave/grestore work here.

    KenS asked me in comments why I had used stroke in my script. It turns out that my page numbering looks like this without stroke: enter image description here

    That is, in this case, obsuring background is close-fitting and there are no margins between the numbers themselves and motley PDf stuff and numbers may seem to be interflowing into dark motley stuff in some points.

    So stroke draws a border around our auxilliary text and numbers. In my case, the border has the same color (red) as the auxilliary background so they look more readable.

    But if you remove gsave/grestore, stroke will not take effect and the resulting picture will look as the previous one. 5 setlinewidth sets a width of 5 pt's.

    P.S.

    The key in finding bugs of the original answer script was to use debugging pstack operator.

    Determine the last working edition then insert pstack after its 1st line and modify the script to check some risky operators. If it works, then move pstack after the 2st line and add another risky operator. Once your script is broken, compare pstack output from working and non-workings editions of your script.