4

I have two 36" by 48" posters (LaTeX) that I want to append into a single 72" by 48" poster (stack vertically).

Browsing around SO and GS documentation, I have no clue (I'm not a CLI wizard). How can I do this?

(Also, the process should not compress the raster images lossily, because this will be printed 2400*1200 DPI.)

Kurt Pfeifle
  • 86,724
  • 23
  • 248
  • 345
Simon Kuang
  • 3,870
  • 4
  • 27
  • 53
  • 1
    Your question is off-topic on StackOverflow as it's not related to programming. – Eugene Mayevski 'Callback Mar 14 '15 at 17:56
  • This is called 'imposition' and while it is possible with Ghostscript you would need to do some PostScript programming to get it to work. The command line is insufficient her,e you would actually need to write a PostScript program. There are imposition tools for PDF available, you might be better to use one. – KenS Mar 15 '15 at 16:23
  • I can show you how to append 2 different 36''x48'' PDF posters into one single 72''x48'' PDF poster -- with the help of ***LaTeX***. From the dimensions you give, you want the two originals apppear side-by-side, right? -- It is only about 10 lines of LaTeX code, but I'd need a link to your 2 original posters. Can you provide it? – Kurt Pfeifle Mar 31 '15 at 13:51

2 Answers2

4

Since the OP didn't provide (a link to) the original input posters, this answer will proceed in three steps:

  1. Create 2 dummy posters as input for step 3
  2. Create a LaTeX document which embeds the 2 dummy posters
  3. Run pdflatex to create a PDF from the LaTeX document in step 2

Step 1: Create 2 dummy posters (with size of 36in x 48in)

I've created two different dummy posters as PDF to show you how to do it with LaTeX. (That implies: you need to have at least a basic LaTeX installation on your system, including the pdflatex utility.)

These two dummies I created with the help of Ghostscript. Since for Ghostscript's pdfwrite device 1in == 72pt == 720pixels, the commands are like this (because 36in == 2592pt == 25920pixels and 48in == 3456pt == 34560pixels):

gs -o poster1.pdf                   \
   -g25920x34560                    \
   -sDEVICE=pdfwrite                \
   -c " /Helvetica-Bold findfont"   \
   -c " 500 scalefont"              \
   -c " setfont"                    \
   -c " 50 2000 moveto"             \
   -c " (POSTER 1) show"            \
   -c " 1 0 0 setrgbcolor"          \
   -c " 10 setlinewidth"            \
   -c " 20 20 2552 3416 rectstroke" \
   -c " showpage" 

gs -o poster2.pdf                   \
   -g25920x34560                    \
   -sDEVICE=pdfwrite                \
   -c " /Helvetica-Bold findfont"   \
   -c " 600 scalefont"              \
   -c " setfont"                    \
   -c " 50 2000 moveto"             \
   -c " (Poster 1) show"            \
   -c " 1 0 0 setrgbcolor"          \
   -c " 10 setlinewidth"            \
   -c " 20 20 2552 3416 rectstroke" \
   -c " showpage" 

Here are 2 screenshots showing these "posters":

Step 2: Create a little LaTeX program to be run with pdflatex

There is a LaTeX package called 'pdfpages' which can insert PDF pages into LaTeX documents, but which also can create "n-up" layouts from PDF pages. (In addition to your basic LaTeX installation you need that package too.)

So here is a small LaTeX program you can use. Save it as 2up-poster.tex:

\documentclass{article}
\usepackage{pdfpages}
\usepackage[paperwidth=72in, paperheight=48in]{geometry}
\pagestyle{plain}                                 % Don't use page numbers

\begin{document}
      \setlength\voffset{+0.0in}                  % adj. vert. offset as needed
      \setlength\hoffset{+0.0in}                  % adj. horiz. offset as needed
      \includepdfmerge[nup=2x1,
                       noautoscale=true,          % set "false" if larger inputs
                       frame=false,               % set "true" for frames
                       templatesize={36in}{48in}] % adjust as needed
                       {poster1.pdf,poster2.pdf}  % modify for file names
\end{document}

Step 3: Run pdflatex

Now you can run the following command to create your composed poster:

pdflatex 2up-poster.tex

This will create a PDF file named 2up-poster.pdf.

The result is here as a screenshot:

Kurt Pfeifle
  • 86,724
  • 23
  • 248
  • 345
  • One problem: even with `frame=false` there is a tiny (but visible) gap between the pages and a perfect merge is not achieved. I looked at the `pdfpages` [docs](http://ctan.altspu.ru/macros/latex/contrib/pdfpages/pdfpages.pdf) but found no way to fix this. – kontextify Aug 17 '18 at 09:18
  • @kontextify: I strongly opine that this effect is due to an artifact on your original PDF pages. But I could only verify or falsify my opinion if I had a link to a sample of your input PDF(s). – Kurt Pfeifle Aug 17 '18 at 13:16
0

This Ghostscript program (taken from this answer) does PDF page stitching (a.k.a. imposition).

I used it like this:

gs -dBATCH -dNOPAUSE -sDEVICE=pdfwrite -sFile=twopage.pdf -sOutputFile=onepage.pdf imposition.ps

No LaTeX toolchain required -- just the gs interpreter.

%!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
      /OriginYTy 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
ulidtko
  • 14,740
  • 10
  • 56
  • 88
  • Note: this requires both pages to already be in a single input PDF file (which may be inconvenient). – kontextify Aug 17 '18 at 09:15
  • @kontextify I think I used `pdfunite` from the Poppler suite to produce the two-page PDF from two single-page PDFs. It's trivial to throw together a bash script combining `pdfunite` and the `gs` script into a single step. – ulidtko Aug 17 '18 at 10:20