I need to add a white rectangle and some text to the bottom left corner of each page of the PDF document using Ghostscript. To achieve this, I have created the following Postscript script:
<<
/EndPage
{
2 eq { pop false }
{
newpath
0 0 moveto
0 20 lineto
200 20 lineto
200 0 lineto
closepath
%%gsave
1 setgray
fill
%%grestore
1 setlinewidth
0 setgray
stroke
gsave
/Times-Roman 9 selectfont
30 5 moveto
(My text) show
grestore
true
} ifelse
} bind
>> setpagedevice
This works well when combined with a Ghostscript command:
gs -dBATCH -dNOPAUSE -sDEVICE=pdfwrite -sOutputFile=output.pdf my_script.ps input.pdf
However, if input.pdf is in landscape mode, then the white box and text are printed in the upper left corner and not the lower left. I can get it to work by adding:
90 rotate 0 -595 translate
but I can't determine when the pages are in landscape mode vs. portrait mode. I can get the page width and height, but even for landscape mode pages the width is smaller than the height. I tried the following but it fails:
/orient currentpagedevice /Orientation get def
I have been stuck with this for a while. Any help is greatly appreciated!
(Ghostscript version is 9.25)
[UPDATE]
To illustrate how the width is smaller than height for a page in landscape mode, here's the script.ps I am using: https://gist.github.com/irinkaa/9faadf30b3a5a381a0b621d72b712020
And here are the input.pdf and the output.pdf. As you can see, 612.0 - 792.0
is printed inside the output file, showing that width (612) < height (792).
When I re-run the same command on the output file, it prints the same width and height values, but the box is then placed properly in the lower left corner.
When I add the following to the script:
/orient currentpagedevice /Orientation get def
I get an error suggesting orientation isn't set (if I understand correctly):
Error: /undefined in --get--
Operand stack:
orient --dict:212/312(ro)(L)-- Orientation
Execution stack:
%interp_exit .runexec2 --nostringval-- --nostringval-- --nostringval-- 2 %stopped_push --nostringval-- --nostringval-- --nostringval-- false 1 %stopped_push 1999 1 3 %oparray_pop 1998 1 3 %oparray_pop 1982 1 3 %oparray_pop 1868 1 3 %oparray_pop --nostringval-- %errorexec_pop .runexec2 --nostringval-- --nostringval-- --nostringval-- 2 %stopped_push --nostringval--
Dictionary stack:
--dict:977/1684(ro)(G)-- --dict:0/20(G)-- --dict:80/200(L)--
Current allocation mode is local
Current file position is 151
GPL Ghostscript 9.25: Unrecoverable error, exit code 1
First you should upgrade your version of Ghostscript. 9.25 is old, and has security vulnerabilities.
Secondly you need to look at both the /Orientation and /PageSize entries in the page device dictionary. Not only that but you should use the PageSize to determine the translate you are using for your 'adjustment'. Unless you are in a fixed workflow (and that seems unlikely if you are receiving mixed orientation files) then you should not assume that the media is A4.
The Ghostscript PDF interpreter looks at the MediaBox on each page of the PDF file and resets the /PageSize in the page device dictionary to match the MediaBox for the page. It will (IIRC) never set the /Orientation, if the PDF page has a /Rotate entry then that gets applied to the MediaBox and the contents of the page.
So you really just need to look at the width and height of the requested media, which is given by the /PageSize array in the page device dictionary.
Now having said that....
You say that 'even for landscape mode pages the width is smaller than the height'. That seems unlikely to me, but in the absence of an example it's hard to tell. It also makes it hard for anyone to offer any kind of advice.
I'd suggest you upload an example somewhere, and post the URL here so we can look at the file.
Oh, and I'd really recommend that you don't send the output file to stdout. It may well be convenient for you but there are already certain features of the pdfwrite device which simply won't work if you do that (they require the output file to be seekable) and there may be more cases in future.
Your problem is execution order. The program in script.ps runs before the PDF file is interpreted, then the PDF file is interpreted.
When all your program is doing is setting an EndPage procedure in the page device dictionary that's not a problem, alterations to the page device dictionary are conservative, they accumulate unless specifically overwritten.
So the fact that during the course of interpreting the PDF file changes occur to the page device dictionary doesn't matter (unless that were somehow to alter the EndPage procedure).
But at the time your program runs, the page device dictionary /PageSize key has an associated value which is an array containing the default media size (because nothing has happened to change it yet). The PageSize entry won't be altered until the PDF file gets interpreted. This means that no matter what size media your PDF file used, your program will always return the default media size.
You need to know the actual PageSize at the time the EndPage procedure is executed. So you need to investigate the current PageSize as part of the EndPage procedure.
Something like:
<<
/EndPage
{
2 eq { pop false }
{
% Get the current page device dictionary and extract the PageSize
currentpagedevice /PageSize get
% Load the values from the array onto the stack
% and discard the array copy returned by the aload operator
aload pop
% If width < height (or equal, square page)
le {
% Handle a portrait page
} {
% Handle a landscape page
} ifelse
}ifelse
} bind
>> setpagedevice
Note that this avoids creating a dictionary entry to hold the page width and height. There are several reasons for doing this;
Firstly the width and height can be different for every page (particularly in a PDF file).
Secondly you don't (in your program) create your own dictionary to store these key/value pairs which means that you are using whatever dictionary is active at the time. While that's sort of acceptable the way you have it currently, because userdict will be active at the start of the program, you have no way to know which dictionary is on top of the dictionary stack when EndPage is called. So it's not safe to just poke values into whatever dictionary happens to be top, you might end up overwriting keys with the same name, which would lead to unpredictable side effects. Likewise (as per Orientation below) if the current dictionary doesn't contain those keys, you would get an undefined error. So you're getting away with this through luck right now.
Thirdly it's generally considered better practice in PostScript to use the stack for temporary storage, rather than creating key/value pairs in dictionaries.
For the latter two reasons I'd very strongly suggest that instead of creating a key called stringholder
(as your program currently does) in whatever dictionary is on top of the dictionary stack at the start of the program, and assuming it will be available during the EndPage procedure, you should instead simply create a temporary string by using 10 string
instead.
Eg:
/Times-Roman 9 selectfont
30 5 moveto
pagewidth
stringHolder cvs
show
would become:
/Times-Roman 9 selectfont
30 5 moveto
currentpagedevice /PageSize get 0 get
256 string cvs
show
10 digits is possibly a little small, 256 should be enough for anyone and the string will be garbage collected so it isn't like you are leaking memory or anything.
As regards Orientation; yes, you are correct, and as I said initially the PDF interpreter doesn't set Orientation in the page device dictionary. If you try to get
a key from a dictionary which doesn't contain that key then you get an undefined error. If you are uncertain whether a key exists in a dictionary you should check it first using the known
operator.
As noted in the comments below, it's possible to test the orientation of the CTM by using the transform
operator and the unit vector. If either or both of the co-ordinates resulting from transform
are negative then there is rotation involved in the CTM and by examining the sign of each co-ordinate we can determine which quadrant the rotation ends up in.
For the purposes of the /Rotate flag in PDF that's sufficient, as it can only be specified in 90 degree increments. Here is an example function which determines the rotation, and a simple piece of PostScript to exercise it:
%!PS
/R {
1 1 transform
0 ge {
0 ge {
(no rotation\n) print
} {
(90 degree ccw rotation\n) print
} ifelse
} {
0 ge {
(270 ccw rotation\n) print
} {
(180 ccw rotation\n) print
} ifelse
} ifelse
} bind def
R
gsave
90 rotate R
grestore
gsave
180 rotate R
grestore
gsave
270 rotate R
grestore
gsave
360 rotate R
grestore
It's possible to use this technique to decide if the original file has been rotated, and then choose to have the EndPage procedure behave differently.