3

I need to use ghostscript for concatenating several PDFs into a single pdf file. The help I need from you is what command do I have to use, in order to edit the output page, so that it fits 4 pages of the input files, into 1 of the output. What I am using so far is this command:

gs -dBATCH -dNOPAUSE -q -sDEVICE=pdfwrite \
   -sOutputFile=ALL.pdf plot_1.pdf plot_n.pdf

Input files are A4 landscape, and the output have to be the same format.

Thanks to all, Cristian

Kurt Pfeifle
  • 86,724
  • 23
  • 248
  • 345
Cristian
  • 33
  • 1
  • 3
  • Please, note that I am forced to use ghostscript - as I am doing this on my company's server and I may not have access to all options / files (but I do have ghostscript). Thanks – Cristian May 30 '15 at 11:35
  • You command wouldn't have worked anyway, because of your blanks around the `=` characters... – Kurt Pfeifle May 30 '15 at 12:29

2 Answers2

3

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
KenS
  • 30,202
  • 3
  • 34
  • 51
-1

What you want to achieve is not possible with Ghostscript on its own. There is no built-in parameter which would allow to do a 2-up or 4-up imposing of pages in the output.

It is however possible to write a PostScript program which would do that, and then feed this program along the original PDF to Ghostscript, which can then output the result.

I remember having seen that @KenS (who is a Ghostscript developer und who'll probably soon get also aware of this question) a while ago posted a similar PostScript program. You can maybe find it if you search here (or with similar parameters).

Community
  • 1
  • 1
Kurt Pfeifle
  • 86,724
  • 23
  • 248
  • 345
  • You can probably use ghostpdl/gs/lib/gsnup.ps, but I haven't actually tried it. – KenS May 30 '15 at 12:49
  • What is the simplest approach? Using ghostpdl/gs/lib/gsnup.ps (1st option) vs converting the pdf files into ps (with ghostscript?), then re-edit the page within ps and then save the final version into pdf (2nd option)? – Cristian May 30 '15 at 13:01
  • @KenS: Ah, I had forgotten about gsnup.ps... Anyway, running `gs -o out.pdf -sDEVICE=pdfwrite gsnup.ps -f in.pdf` results in scaled-down page contents where the content is pushed into lower right quadrant. So it scales, but doesn't nup. – Kurt Pfeifle May 30 '15 at 13:11