This is a program I wrote some time back to do 2-up imposition from PDF. This is not a general purpose PostScript program, it will only work with Ghostscript (as it makes use of some GS internals) and only when the input is PDF, not PostScript.
You will have to modify it to do 4-up imposition. Don't worry about the scary licencing stuff, that's just because I copied the boilerplate from somewhere else.
You'll probably want to assume that all pages are the same size and orientation, unlike this code which allows for different orientations.
%!PS
% Copyright (C) 2011 Artifex Software, Inc. All rights reserved.
%
% This software is provided AS-IS with no warranty, either express or
% implied.
%
% This software is distributed under license and may not be copied,
% modified or distributed except as expressly authorized under the terms
% of the license contained in the file LICENSE in this distribution.
%
% For more information about licensing, please refer to
% http://www.ghostscript.com/licensing/. For information on
% commercial licensing, go to http://www.artifex.com/licensing/ or
% contact Artifex Software, Inc., 101 Lucas Valley Road #110,
% San Rafael, CA 94903, U.S.A., +1(415)492-9861.
%
% Make a PDF file '2-up'
% This program deliberately does NOT attempt to preserve metadata
% such as DEST links, bookmarks and so forth as these will be
% mostly incorrect after imposition.
%
% usage: gs -dNODISPLAY -sFile=____.pdf [-dVerbose] 2-up.ps
%
% Make, and open, a working dictionary to store stuff
%
/PDF_2UPDict 20 dict dup begin def
%
% Check the parameters to see they are present and of the correct type
%
/Usage {
( usage: gs -dNODISPLAY -q -sFile=____.pdf [-dVerbose] 2-up.ps\n) =
flush
quit
} bind def
/File where not {
(\n *** Missing source file. \(use -sFile=____.pdf\)\n) =
Usage
} {
pop
}ifelse
/Verbose where not {
/Verbose false def
}{
pop /Verbose true def
} ifelse
%%
%% This code is copied from pdf_main.ps, pdfshowpage_finish
%% sadly that routine always calls showpage, and we want that
%% to be under our control, so we have to duplicate the code
%% here. Not only that but it uses GS extensions which aren't
%% available outside of startup, so some things it simply can't
%% replicate. As a result some of the error handling is less
%% good.
%%
%% I plan to extend the PDF interpreter with two new
%% routines, pdfnoshowpage_finish and then have both
%% that and pdfshowpage_finish call pdfoptionalshowpage_finish
%% which will take a boolean determining whether to actually
%% call the showpage. At that time we'll alter this code.
%%
/draw_page_content { % <pagedict> pdfshowpage_finish -
save /PDFSave exch store
/PDFdictstackcount countdictstack store
/PDFexecstackcount count 2 sub store
(before exec) VMDEBUG
% set up color space substitution (this must be inside the page save)
pdfshowpage_setcspacesub
% Display the actual page contents.
8 dict begin
/BXlevel 0 def
/BMClevel 0 def
/OFFlevels 0 dict def
/BGDefault currentblackgeneration def
/UCRDefault currentundercolorremoval def
%****** DOESN'T HANDLE COLOR TRANSFER YET ******
/TRDefault currenttransfer def
matrix currentmatrix
2 dict
dictbeginpage setmatrix
/DefaultQstate qstate store
count 1 sub /pdfemptycount exch store
% If the page uses any transparency features, show it within
% a transparency group.
dup pageusestransparency dup /PDFusingtransparency exch def {
% Show the page within a PDF 1.4 device filter.
0 .pushpdf14devicefilter {
/DefaultQstate qstate store % device has changed -- reset DefaultQstate
% If the page has a Group, enclose contents in transparency group.
% (Adobe Tech Note 5407, sec 9.2)
dup /Group knownoget {
1 index /CropBox pget {
/CropBox exch
} {
1 index get_media_box pop /MediaBox exch
} ifelse
oforce_elems normrect_elems fix_empty_rect_elems 4 array astore .beginformgroup
showpagecontents
.endtransparencygroup
} {
showpagecontents
} ifelse
} stopped {
% abort the transparency device
.abortpdf14devicefilter
/DefaultQstate qstate store % device has changed -- reset DefaultQstate
stop
} if .poppdf14devicefilter
/DefaultQstate qstate store % device has changed -- reset DefaultQstate
} {
showpagecontents
} ifelse
.free_page_resources
% todo: mixing drawing ops outside the device filter could cause
% problems, for example with the pnga device.
end % scratch dict
% Some PDF files don't have matching q/Q (gsave/grestore) so we need
% to clean up any left over dicts from the dictstack
PDFdictstackcount //false
{ countdictstack 2 index le { exit } if
currentdict /n known not or
end
} loop
pop
count PDFexecstackcount sub { pop } repeat
Repaired % pass Repaired state around the restore
PDFSave restore
currentglobal pdfdict gcheck .setglobal
.setglobal
/Repaired exch def
} bind def
%%
%% First we open the PDF file
%%
File dup (r) file runpdfbegin pop
process_trailer_attrs
%%
%% FInd out how many pages are in teh PDF file
%%
/PDFPageCount pdfpagecount def
Verbose {(PageCount is ) print PageCount ==} if
%
% Set up our bookkeeping
%
% First get the size of the page from page 1 of the PDF file
% We assume that all PDF pages are the same size.
%
1 pdfgetpage get_any_box
exch pop dup 2 get exch 3 get
/PDFHeight exch def
/PDFWidth exch def
PDFWidth PDFHeight gt {
/PDFLandscape true def
}{
/PDFLandscape false def
}ifelse
Verbose{
(PDFHeight is ) print PDFHeight ==
(PDFWidth is ) print PDFWidth ==
(PDFLandscape is ) print PDFLandscape ==
} if
%
% Now get the page size of the current device. We will fit
% the PDF pages onto this using rotation and scaling as
% required.
%
currentpagedevice /PageSize get
dup 0 get /PageWidth exch def
1 get /PageHeight exch def
PageWidth PageHeight gt {
/PageLandscape true def
}{
/PageLandscape false def
}ifelse
Verbose{
(PageHeight is ) print PageHeight ==
(PageWidth is ) print PageWidth ==
(PageLandscape is ) print PageLandscape ==
} if
%
% Now figure out how best to fit the pages 2-up
%
PageLandscape PDFLandscape and {
%% Both landscape
/ScaleY PageHeight PDFWidth div
/ScaleX PageWidth 2 div PDFHeight div
ScaleX ScaleY lt {
/Scale ScaleX def
} {
/Scale ScaleY def
}ifelse
/Rotate 90
/OriginYTx PDFHeight neg def
/OriginXTx 0 def
/PageYTx PDFHeight def
/PageXTx 0 def
}{
PageLandscape {
%% Page is landscape, PDF is portrait
/ScaleY PageHeight PDFHeight div def
/ScaleX PageWidth 2 div PDFWidth div def
ScaleX ScaleY lt {
/Scale ScaleX def
} {
/Scale ScaleY def
}ifelse
/Rotate 0 def
/OriginXTx 0 def
/OriginYTx 0 def
/PageXTx PDFWidth def
/PageYTx 0 def
}{
PDFLandscape {
%% PDF is landscape, Page is portrait
/ScaleY PageHeight 2 div PDFHeight div def
/ScaleX PageWidth PDFWidth div def
ScaleX ScaleY lt {
/Scale ScaleX def
} {
/Scale ScaleY def
}ifelse
/Rotate 0 def
/OriginXTx 0 def
/OriginYTx PDFHeight def
/PageXTx 0 def
/PageYTx PDFHeight neg def
} {
%% Both portrait
/ScaleY PageHeight 2 div PDFWidth div def
/ScaleX PageWidth PDFHeight div def
ScaleX ScaleY lt {
/Scale ScaleX def
} {
/Scale ScaleY def
}ifelse
/Rotate 90 def
/OriginXTx 0 def
/OriginYTx PDFHeight neg def
/PageYTx 0 def
/PageXTx PDFWidth def
} ifelse
}ifelse
} ifelse
Verbose{
(ScaleX is ) print ScaleX ==
(ScaleY is ) print ScaleY ==
(Scale is ) print Scale ==
(Rotate is ) print Rotate ==
(OriginXTx is ) print OriginXTx ==
(OriginYTx is ) print OriginYTx ==
(PageXTx is ) print PageXTx ==
(PageYTx is ) print PageYTx ==
} if
%
% Starting at 0, count by 2, and stop at the last page, however
% account for the fact that the number of pages in the PDF
% file may not be even, in which case draw a final empty
%page
%
0 2 PDFPageCount 1 sub PDFPageCount 2 mod add {
%% loop counter
save %% save the state
exch %% exch the save state and the loop counter
Rotate rotate %%
Scale Scale scale %% set up our calculated CTM
OriginXTx OriginYTx translate %%
save %% and save this too
exch %% swap the save state and the loop counter
dup 1 add %% copy the loop counter, then add 1, stack: -save- -save- loop loop+1
Verbose {(Drawing page ) print dup ==} if
0 0 PageWidth PageHeight rectclip %% clip the page contents to the page size (in case of bleeds)
pdfgetpage %% get the page from the PDF file, stack: -save- -save- loop -dict-
dup /Page exch store %% save a copy of the page dict inside itself
pdfshowpage_init %% initialise the page
draw_page_content %% se above, draws the graphical objects, stack -save- -save- loop
exch %% swap back teh save object, stack: -save- loop -save-
restore %% restore back to our calculated CTM
PageXTx PageYTx translate %% Move to draw page 2
2 add dup PDFPageCount gt { %% If we have to draw an extra page, and this is it
Verbose {(Drawing extra page ) print dup ==} if
pop showpage %% pop the spare loop and draw an empty page, stack: -save-
}{
Verbose {(Drawing page ) print dup ==} if
0 0 PageWidth PageHeight rectclip
pdfgetpage
dup /Page exch store
pdfshowpage_init % <pagedict>
draw_page_content
showpage
} ifelse
restore %% restore back to the original CTM
} for
//runpdfend exec %% End the PDF file
end %% our working dictioanry