Commits

Anonymous committed 2ed935e

First checkin of version 0.4.1 after moving to Mercurial.

Comments (0)

Files changed (45)

+syntax: glob
+.DS_Store
+*.pyc
+include ez_setup.py
+include test_*.py
+include samples/*.py
+include samples/*.pdf
+include README.*
+Metadata-Version: 1.0
+Name: pdfnup
+Version: 0.4.1
+Summary: Layout multiple pages per sheet of a PDF document.
+Home-page: http://www.dinu-gherman.net/
+Author: Dinu Gherman
+Author-email: gherman@darwin.in-berlin.de
+License: GPL 3
+Download-URL: http://www.dinu-gherman.net/tmp/pdfnup-0.4.1.tar.gz
+Description: `Pdfnup` is a Python module and command-line tool for layouting multiple
+        pages per sheet of a PDF document. Using it you can take a PDF document
+        and create a new PDF document from it where each page contains a number
+        of minimized pages from the original PDF file.
+        
+        Right now `pdfnup` should be used on documents with all pages the same
+        size, and half square page numbers per sheet work best on paper sizes
+        of the ISO A series.
+        
+        Basically, `pdfnup` wrapps `pyPdf <http://pypi.python.org/pypi/pyPdf>`_,
+        a package written by Mathieu Fenniak, which does not provide tools like
+        this for using the core functionality easily from the command-line or
+        from a Python module. `Pdfnup` itself was much inspired from some code
+        written by Henning von Bargen - thanks, Henning!
+        
+        This release provides full support for file objects and StringIO objects
+        for input as well as output documents and fixes a nasty buglet in the
+        command-line invocation script.
+        
+        
+        Features
+        ++++++++
+        
+        - save minimized pages of a given PDF document in a new PDF document
+        - place n pages per sheet, with n being square or half square
+        - customize layout order, both horizontally and vertically
+        - turn rotated pages to make them all have the same format
+        - allow patterns for output files
+        - supports file-like objects for input and output documents
+        - install a Python module named ``pdfnup.py``
+        - install a Python command-line script named ``pdfnup``
+        - provide a Unittest test suite
+        
+        
+        Examples
+        ++++++++
+        
+        You can use `pdfnup` as a Python module e.g. like in the following
+        interactive Python session::
+        
+        >>> from pdfnup import generateNup
+        >>>
+        >>> generateNup("file.pdf", 8, verbose=True)
+        written: file-8up.pdf
+        >>>
+        >>> generateNup("file.pdf", 8, dirs="LD", verbose=True)
+        written: file-8up.pdf
+        >>>
+        >>> f = open("file.pdf")
+        >>> generateNup(f, 8, outPathPatternOrFile="out-%(n)dup.pdf", verbose=True)
+        written: out-8up.pdf
+        
+        In addition there is a script named ``pdfnup``, which can be used
+        more easily from the system command-line like this (you can see many
+        more examples when typing ``pdfnup -h`` on the command-line)::
+        
+        $ pdfnup -V file.pdf
+        written: file-4up.pdf
+        $ pdfnup -V -n 8 file.pdf
+        written: file-8up.pdf
+        $ pdfnup -V -n 8 -l LD file.pdf
+        written: file-8up.pdf
+        $ pdfnup -V -n 9 /path/file[12].pdf
+        written: /path/file1-9up.pdf
+        written: /path/file2-9up.pdf
+        $ pdfnup -V -n 8 -o "%(dirname)s/foo.pdf" /path/file.pdf
+        written: /path/foo.pdf
+        
+Keywords: PDF,minimizig pages
+Platform: Posix
+Platform: Windows
+Classifier: Development Status :: 4 - Beta
+Classifier: Environment :: Console
+Classifier: Intended Audience :: End Users/Desktop
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: GNU General Public License (GPL)
+Classifier: Operating System :: MacOS :: MacOS X
+Classifier: Operating System :: POSIX
+Classifier: Operating System :: Microsoft :: Windows
+Classifier: Programming Language :: Python
+Classifier: Topic :: Utilities
+Classifier: Topic :: Printing
+<?xml version="1.0" encoding="utf-8" ?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<meta name="generator" content="Docutils 0.5: http://docutils.sourceforge.net/" />
+<title>Pdfnup</title>
+<meta name="author" content="Dinu Gherman &lt;gherman&#64;darwin.in-berlin.de&gt;" />
+<meta name="date" content="2009-07-06" />
+<meta name="copyright" content="GNU Public Licence v3 (GPLv3)" />
+<style type="text/css">
+
+/*
+:Author: David Goodger (goodger@python.org)
+:Id: $Id: html4css1.css 5196 2007-06-03 20:25:28Z wiemann $
+:Copyright: This stylesheet has been placed in the public domain.
+
+Default cascading style sheet for the HTML output of Docutils.
+
+See http://docutils.sf.net/docs/howto/html-stylesheets.html for how to
+customize this style sheet.
+*/
+
+/* used to remove borders from tables and images */
+.borderless, table.borderless td, table.borderless th {
+  border: 0 }
+
+table.borderless td, table.borderless th {
+  /* Override padding for "table.docutils td" with "! important".
+     The right padding separates the table cells. */
+  padding: 0 0.5em 0 0 ! important }
+
+.first {
+  /* Override more specific margin styles with "! important". */
+  margin-top: 0 ! important }
+
+.last, .with-subtitle {
+  margin-bottom: 0 ! important }
+
+.hidden {
+  display: none }
+
+a.toc-backref {
+  text-decoration: none ;
+  color: black }
+
+blockquote.epigraph {
+  margin: 2em 5em ; }
+
+dl.docutils dd {
+  margin-bottom: 0.5em }
+
+/* Uncomment (and remove this text!) to get bold-faced definition list terms
+dl.docutils dt {
+  font-weight: bold }
+*/
+
+div.abstract {
+  margin: 2em 5em }
+
+div.abstract p.topic-title {
+  font-weight: bold ;
+  text-align: center }
+
+div.admonition, div.attention, div.caution, div.danger, div.error,
+div.hint, div.important, div.note, div.tip, div.warning {
+  margin: 2em ;
+  border: medium outset ;
+  padding: 1em }
+
+div.admonition p.admonition-title, div.hint p.admonition-title,
+div.important p.admonition-title, div.note p.admonition-title,
+div.tip p.admonition-title {
+  font-weight: bold ;
+  font-family: sans-serif }
+
+div.attention p.admonition-title, div.caution p.admonition-title,
+div.danger p.admonition-title, div.error p.admonition-title,
+div.warning p.admonition-title {
+  color: red ;
+  font-weight: bold ;
+  font-family: sans-serif }
+
+/* Uncomment (and remove this text!) to get reduced vertical space in
+   compound paragraphs.
+div.compound .compound-first, div.compound .compound-middle {
+  margin-bottom: 0.5em }
+
+div.compound .compound-last, div.compound .compound-middle {
+  margin-top: 0.5em }
+*/
+
+div.dedication {
+  margin: 2em 5em ;
+  text-align: center ;
+  font-style: italic }
+
+div.dedication p.topic-title {
+  font-weight: bold ;
+  font-style: normal }
+
+div.figure {
+  margin-left: 2em ;
+  margin-right: 2em }
+
+div.footer, div.header {
+  clear: both;
+  font-size: smaller }
+
+div.line-block {
+  display: block ;
+  margin-top: 1em ;
+  margin-bottom: 1em }
+
+div.line-block div.line-block {
+  margin-top: 0 ;
+  margin-bottom: 0 ;
+  margin-left: 1.5em }
+
+div.sidebar {
+  margin: 0 0 0.5em 1em ;
+  border: medium outset ;
+  padding: 1em ;
+  background-color: #ffffee ;
+  width: 40% ;
+  float: right ;
+  clear: right }
+
+div.sidebar p.rubric {
+  font-family: sans-serif ;
+  font-size: medium }
+
+div.system-messages {
+  margin: 5em }
+
+div.system-messages h1 {
+  color: red }
+
+div.system-message {
+  border: medium outset ;
+  padding: 1em }
+
+div.system-message p.system-message-title {
+  color: red ;
+  font-weight: bold }
+
+div.topic {
+  margin: 2em }
+
+h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
+h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
+  margin-top: 0.4em }
+
+h1.title {
+  text-align: center }
+
+h2.subtitle {
+  text-align: center }
+
+hr.docutils {
+  width: 75% }
+
+img.align-left {
+  clear: left }
+
+img.align-right {
+  clear: right }
+
+ol.simple, ul.simple {
+  margin-bottom: 1em }
+
+ol.arabic {
+  list-style: decimal }
+
+ol.loweralpha {
+  list-style: lower-alpha }
+
+ol.upperalpha {
+  list-style: upper-alpha }
+
+ol.lowerroman {
+  list-style: lower-roman }
+
+ol.upperroman {
+  list-style: upper-roman }
+
+p.attribution {
+  text-align: right ;
+  margin-left: 50% }
+
+p.caption {
+  font-style: italic }
+
+p.credits {
+  font-style: italic ;
+  font-size: smaller }
+
+p.label {
+  white-space: nowrap }
+
+p.rubric {
+  font-weight: bold ;
+  font-size: larger ;
+  color: maroon ;
+  text-align: center }
+
+p.sidebar-title {
+  font-family: sans-serif ;
+  font-weight: bold ;
+  font-size: larger }
+
+p.sidebar-subtitle {
+  font-family: sans-serif ;
+  font-weight: bold }
+
+p.topic-title {
+  font-weight: bold }
+
+pre.address {
+  margin-bottom: 0 ;
+  margin-top: 0 ;
+  font-family: serif ;
+  font-size: 100% }
+
+pre.literal-block, pre.doctest-block {
+  margin-left: 2em ;
+  margin-right: 2em }
+
+span.classifier {
+  font-family: sans-serif ;
+  font-style: oblique }
+
+span.classifier-delimiter {
+  font-family: sans-serif ;
+  font-weight: bold }
+
+span.interpreted {
+  font-family: sans-serif }
+
+span.option {
+  white-space: nowrap }
+
+span.pre {
+  white-space: pre }
+
+span.problematic {
+  color: red }
+
+span.section-subtitle {
+  /* font-size relative to parent (h1..h6 element) */
+  font-size: 80% }
+
+table.citation {
+  border-left: solid 1px gray;
+  margin-left: 1px }
+
+table.docinfo {
+  margin: 2em 4em }
+
+table.docutils {
+  margin-top: 0.5em ;
+  margin-bottom: 0.5em }
+
+table.footnote {
+  border-left: solid 1px black;
+  margin-left: 1px }
+
+table.docutils td, table.docutils th,
+table.docinfo td, table.docinfo th {
+  padding-left: 0.5em ;
+  padding-right: 0.5em ;
+  vertical-align: top }
+
+table.docutils th.field-name, table.docinfo th.docinfo-name {
+  font-weight: bold ;
+  text-align: left ;
+  white-space: nowrap ;
+  padding-left: 0 }
+
+h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
+h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
+  font-size: 100% }
+
+ul.auto-toc {
+  list-style-type: none }
+
+</style>
+</head>
+<body>
+<div class="document" id="pdfnup">
+<h1 class="title">Pdfnup</h1>
+<h2 class="subtitle" id="layout-multiple-pages-per-sheet-of-a-pdf-document">Layout multiple pages per sheet of a PDF document.</h2>
+<table class="docinfo" frame="void" rules="none">
+<col class="docinfo-name" />
+<col class="docinfo-content" />
+<tbody valign="top">
+<tr><th class="docinfo-name">Author:</th>
+<td>Dinu Gherman &lt;<a class="reference external" href="mailto:gherman&#64;darwin.in-berlin.de">gherman&#64;darwin.in-berlin.de</a>&gt;</td></tr>
+<tr class="field"><th class="docinfo-name">Homepage:</th><td class="field-body"><a class="reference external" href="http://www.dinu-gherman.net/">http://www.dinu-gherman.net/</a></td>
+</tr>
+<tr><th class="docinfo-name">Version:</th>
+<td>Version 0.4.1</td></tr>
+<tr><th class="docinfo-name">Date:</th>
+<td>2009-07-06</td></tr>
+<tr><th class="docinfo-name">Copyright:</th>
+<td>GNU Public Licence v3 (GPLv3)</td></tr>
+</tbody>
+</table>
+<!-- -*- mode: rst -*- -->
+<div class="section" id="about">
+<h1>About</h1>
+<p><cite>Pdfnup</cite> is a Python module and command-line tool for layouting multiple
+pages per sheet of a PDF document. Using it you can take a PDF document
+and create a new PDF document from it where each page contains a number
+of minimized pages from the original PDF file.</p>
+<p>Right now <cite>pdfnup</cite> should be used on documents with all pages the same
+size, and half square page numbers per sheet work best on paper sizes
+of the ISO A series.</p>
+<p>Basically, <cite>pdfnup</cite> wrapps <a class="reference external" href="http://pypi.python.org/pypi/pyPdf">pyPdf</a>,
+a package written by Mathieu Fenniak, which does not provide tools like
+this for using the core functionality easily from the command-line or
+from a Python module. <cite>Pdfnup</cite> itself was much inspired from some code
+written by Henning von Bargen - thanks, Henning!</p>
+<p>This release provides full support for file objects and StringIO objects
+for input as well as output documents and fixes a nasty buglet in the
+command-line invocation script.</p>
+</div>
+<div class="section" id="features">
+<h1>Features</h1>
+<ul class="simple">
+<li>save minimized pages of a given PDF document in a new PDF document</li>
+<li>place n pages per sheet, with n being square or half square</li>
+<li>customize layout order, both horizontally and vertically</li>
+<li>turn rotated pages to make them all have the same format</li>
+<li>allow patterns for output files</li>
+<li>supports file-like objects for input and output documents</li>
+<li>install a Python module named <tt class="docutils literal"><span class="pre">pdfnup.py</span></tt></li>
+<li>install a Python command-line script named <tt class="docutils literal"><span class="pre">pdfnup</span></tt></li>
+<li>provide a Unittest test suite</li>
+</ul>
+</div>
+<div class="section" id="examples">
+<h1>Examples</h1>
+<p>You can use <cite>pdfnup</cite> as a Python module e.g. like in the following
+interactive Python session:</p>
+<pre class="literal-block">
+&gt;&gt;&gt; from pdfnup import generateNup
+&gt;&gt;&gt;
+&gt;&gt;&gt; generateNup(&quot;file.pdf&quot;, 8, verbose=True)
+written: file-8up.pdf
+&gt;&gt;&gt;
+&gt;&gt;&gt; generateNup(&quot;file.pdf&quot;, 8, dirs=&quot;LD&quot;, verbose=True)
+written: file-8up.pdf
+&gt;&gt;&gt;
+&gt;&gt;&gt; f = open(&quot;file.pdf&quot;)
+&gt;&gt;&gt; generateNup(f, 8, outPathPatternOrFile=&quot;out-%(n)dup.pdf&quot;, verbose=True)
+written: out-8up.pdf
+</pre>
+<p>In addition there is a script named <tt class="docutils literal"><span class="pre">pdfnup</span></tt>, which can be used
+more easily from the system command-line like this (you can see
+more examples when typing <tt class="docutils literal"><span class="pre">pdfnup</span> <span class="pre">-h</span></tt> on the command-line):</p>
+<pre class="literal-block">
+$ pdfnup -V file.pdf
+written: file-4up.pdf
+$ pdfnup -V -n 8 file.pdf
+written: file-8up.pdf
+$ pdfnup -V -n 8 -l LD file.pdf
+written: file-8up.pdf
+$ pdfnup -V -n 9 /path/file[12].pdf
+written: /path/file1-9up.pdf
+written: /path/file2-9up.pdf
+$ pdfnup -V -n 8 -o &quot;%(dirname)s/foo.pdf&quot; /path/file.pdf
+written: /path/foo.pdf
+</pre>
+</div>
+<div class="section" id="installation">
+<h1>Installation</h1>
+<p>There are two ways to install <cite>pdfnup</cite>, depending on whether you have
+the <cite>easy_install</cite> command available on your system or not.</p>
+<div class="section" id="using-easy-install">
+<h2>1. Using <cite>easy_install</cite></h2>
+<p>With the <cite>easy_install</cite> command on your system and a working internet
+connection you can install <cite>pdfnup</cite> with only one command in a terminal:</p>
+<pre class="literal-block">
+$ easy_install pdfnup
+</pre>
+<p>If the <cite>easy_install</cite> command is not available to you and you want to
+install it before installing <cite>pdfnup</cite>, you might want to go to the
+<a class="reference external" href="http://peak.telecommunity.com/DevCenter/EasyInstall">Easy Install homepage</a>
+and follow the <a class="reference external" href="http://peak.telecommunity.com/DevCenter/EasyInstall#installing-easy-install">instructions there</a>.</p>
+</div>
+<div class="section" id="manual-installation">
+<h2>2. Manual installation</h2>
+<p>Alternatively, you can install the <cite>pdfnup</cite> tarball after downloading
+the file <tt class="docutils literal"><span class="pre">pdfnup-0.4.1.tar.gz</span></tt> and decompressing it with the following
+command:</p>
+<pre class="literal-block">
+$ tar xfz pdfnup-0.4.1.tar.gz
+</pre>
+<p>Then change into the newly created directory <tt class="docutils literal"><span class="pre">pdfnup</span></tt> and install
+<cite>pdfnup</cite> by running the following command:</p>
+<pre class="literal-block">
+$ python setup.py install
+</pre>
+<p>This will install a Python module file named <tt class="docutils literal"><span class="pre">pdfnup.py</span></tt> in the
+<tt class="docutils literal"><span class="pre">site-packages</span></tt> subfolder of your Python interpreter and a script
+tool named <tt class="docutils literal"><span class="pre">pdfnup</span></tt> in your <tt class="docutils literal"><span class="pre">bin</span></tt> directory, usually in
+<tt class="docutils literal"><span class="pre">/usr/local/bin</span></tt>.</p>
+</div>
+</div>
+<div class="section" id="dependencies">
+<h1>Dependencies</h1>
+<p><cite>Pdfnup</cite> depends on <cite>pyPdf</cite> which, if missing, will miraculously be
+installed together with <cite>pdfnup</cite> if you have a working internet
+connection during the installation procedure. If for whatever reason
+<cite>pyPdf</cite> cannot be installed, <cite>pdfnup</cite> should still install fine.
+In this case you'll get a warning when trying to run <cite>pdfnup</cite>.</p>
+<p>Starting with version 0.3.1 <cite>pdfnup</cite> no longer needs the <a class="reference external" href="http://www.reportlab.org/downloads.html">ReportLab
+toolkit</a> to be installed,
+except for running the Python script <cite>genpdf.py</cite> generating the
+initial PDF files for the test suite (but even those are included
+in the distribution).</p>
+</div>
+<div class="section" id="testing">
+<h1>Testing</h1>
+<p>The <cite>pdfnup</cite> tarball distribution contains a Unittest test suite
+in the file <tt class="docutils literal"><span class="pre">test_pdfnup.py</span></tt> which can be run like shown in the
+following lines on the system command-line:</p>
+<pre class="literal-block">
+$ tar xfz pdfnup-0.4.1.tar.gz
+$ cd pdfnup-0.4.1
+$ python test_pdfnup.py
+...........
+----------------------------------------------------------------------
+Ran 11 tests in 21.658s
+
+OK
+</pre>
+</div>
+<div class="section" id="bug-reports">
+<h1>Bug reports</h1>
+<p>Please report bugs and patches to Dinu Gherman &lt;<a class="reference external" href="mailto:gherman&#64;darwin.in-berlin.de">gherman&#64;darwin.in-berlin.de</a>&gt;.
+Don't forget to include information about the operating system and Python
+versions being used.</p>
+</div>
+</div>
+</body>
+</html>
+.. -*- mode: rst -*-
+
+========
+Pdfnup
+========
+
+---------------------------------------------------------------------
+Layout multiple pages per sheet of a PDF document.
+---------------------------------------------------------------------
+
+:Author:     Dinu Gherman <gherman@darwin.in-berlin.de>
+:Homepage:   http://www.dinu-gherman.net/
+:Version:    Version 0.4.1
+:Date:       2009-07-06
+:Copyright:  GNU Public Licence v3 (GPLv3)
+
+
+About
+-----
+
+`Pdfnup` is a Python module and command-line tool for layouting multiple 
+pages per sheet of a PDF document. Using it you can take a PDF document 
+and create a new PDF document from it where each page contains a number
+of minimized pages from the original PDF file. 
+
+Right now `pdfnup` should be used on documents with all pages the same 
+size, and half square page numbers per sheet work best on paper sizes
+of the ISO A series.
+
+Basically, `pdfnup` wrapps `pyPdf <http://pypi.python.org/pypi/pyPdf>`_, 
+a package written by Mathieu Fenniak, which does not provide tools like 
+this for using the core functionality easily from the command-line or 
+from a Python module. `Pdfnup` itself was much inspired from some code 
+written by Henning von Bargen - thanks, Henning!
+
+This release provides full support for file objects and StringIO objects
+for input as well as output documents and fixes a nasty buglet in the 
+command-line invocation script.
+
+
+Features
+--------
+
+- save minimized pages of a given PDF document in a new PDF document
+- place n pages per sheet, with n being square or half square
+- customize layout order, both horizontally and vertically
+- turn rotated pages to make them all have the same format 
+- allow patterns for output files
+- supports file-like objects for input and output documents
+- install a Python module named ``pdfnup.py``
+- install a Python command-line script named ``pdfnup``
+- provide a Unittest test suite
+
+
+Examples
+--------
+
+You can use `pdfnup` as a Python module e.g. like in the following
+interactive Python session::
+
+    >>> from pdfnup import generateNup
+    >>>
+    >>> generateNup("file.pdf", 8, verbose=True)
+    written: file-8up.pdf
+    >>>
+    >>> generateNup("file.pdf", 8, dirs="LD", verbose=True)
+    written: file-8up.pdf
+    >>>
+    >>> f = open("file.pdf")
+    >>> generateNup(f, 8, outPathPatternOrFile="out-%(n)dup.pdf", verbose=True)
+    written: out-8up.pdf
+
+In addition there is a script named ``pdfnup``, which can be used 
+more easily from the system command-line like this (you can see 
+more examples when typing ``pdfnup -h`` on the command-line)::
+
+    $ pdfnup -V file.pdf
+    written: file-4up.pdf
+    $ pdfnup -V -n 8 file.pdf
+    written: file-8up.pdf
+    $ pdfnup -V -n 8 -l LD file.pdf
+    written: file-8up.pdf
+    $ pdfnup -V -n 9 /path/file[12].pdf  
+    written: /path/file1-9up.pdf
+    written: /path/file2-9up.pdf
+    $ pdfnup -V -n 8 -o "%(dirname)s/foo.pdf" /path/file.pdf
+    written: /path/foo.pdf
+
+
+Installation
+------------
+
+There are two ways to install `pdfnup`, depending on whether you have
+the `easy_install` command available on your system or not.
+
+1. Using `easy_install`
+++++++++++++++++++++++++
+
+With the `easy_install` command on your system and a working internet 
+connection you can install `pdfnup` with only one command in a terminal::
+
+  $ easy_install pdfnup
+
+If the `easy_install` command is not available to you and you want to
+install it before installing `pdfnup`, you might want to go to the 
+`Easy Install homepage <http://peak.telecommunity.com/DevCenter/EasyInstall>`_ 
+and follow the `instructions there <http://peak.telecommunity.com/DevCenter/EasyInstall#installing-easy-install>`_.
+
+2. Manual installation
++++++++++++++++++++++++
+
+Alternatively, you can install the `pdfnup` tarball after downloading 
+the file ``pdfnup-0.4.1.tar.gz`` and decompressing it with the following 
+command::
+
+  $ tar xfz pdfnup-0.4.1.tar.gz
+
+Then change into the newly created directory ``pdfnup`` and install 
+`pdfnup` by running the following command::
+
+  $ python setup.py install
+  
+This will install a Python module file named ``pdfnup.py`` in the 
+``site-packages`` subfolder of your Python interpreter and a script 
+tool named ``pdfnup`` in your ``bin`` directory, usually in 
+``/usr/local/bin``.
+
+
+Dependencies
+------------
+
+`Pdfnup` depends on `pyPdf` which, if missing, will miraculously be 
+installed together with `pdfnup` if you have a working internet 
+connection during the installation procedure. If for whatever reason 
+`pyPdf` cannot be installed, `pdfnup` should still install fine. 
+In this case you'll get a warning when trying to run `pdfnup`.
+
+Starting with version 0.3.1 `pdfnup` no longer needs the `ReportLab 
+toolkit <http://www.reportlab.org/downloads.html>`_ to be installed, 
+except for running the Python script `genpdf.py` generating the 
+initial PDF files for the test suite (but even those are included
+in the distribution).
+
+
+Testing
+-------
+
+The `pdfnup` tarball distribution contains a Unittest test suite 
+in the file ``test_pdfnup.py`` which can be run like shown in the 
+following lines on the system command-line::
+ 
+  $ tar xfz pdfnup-0.4.1.tar.gz
+  $ cd pdfnup-0.4.1
+  $ python test_pdfnup.py
+  ...........
+  ----------------------------------------------------------------------
+  Ran 11 tests in 21.658s
+
+  OK
+
+
+Bug reports
+-----------
+
+Please report bugs and patches to Dinu Gherman <gherman@darwin.in-berlin.de>. 
+Don't forget to include information about the operating system and Python 
+versions being used.
+#!python
+"""Bootstrap setuptools installation
+
+If you want to use setuptools in your package's setup.py, just include this
+file in the same directory with it, and add this to the top of your setup.py::
+
+    from ez_setup import use_setuptools
+    use_setuptools()
+
+If you want to require a specific version of setuptools, set a download
+mirror, or use an alternate download directory, you can do so by supplying
+the appropriate options to ``use_setuptools()``.
+
+This file can also be run as a script to install or upgrade setuptools.
+"""
+import sys
+DEFAULT_VERSION = "0.6c9"
+DEFAULT_URL     = "http://pypi.python.org/packages/%s/s/setuptools/" % sys.version[:3]
+
+md5_data = {
+    'setuptools-0.6b1-py2.3.egg': '8822caf901250d848b996b7f25c6e6ca',
+    'setuptools-0.6b1-py2.4.egg': 'b79a8a403e4502fbb85ee3f1941735cb',
+    'setuptools-0.6b2-py2.3.egg': '5657759d8a6d8fc44070a9d07272d99b',
+    'setuptools-0.6b2-py2.4.egg': '4996a8d169d2be661fa32a6e52e4f82a',
+    'setuptools-0.6b3-py2.3.egg': 'bb31c0fc7399a63579975cad9f5a0618',
+    'setuptools-0.6b3-py2.4.egg': '38a8c6b3d6ecd22247f179f7da669fac',
+    'setuptools-0.6b4-py2.3.egg': '62045a24ed4e1ebc77fe039aa4e6f7e5',
+    'setuptools-0.6b4-py2.4.egg': '4cb2a185d228dacffb2d17f103b3b1c4',
+    'setuptools-0.6c1-py2.3.egg': 'b3f2b5539d65cb7f74ad79127f1a908c',
+    'setuptools-0.6c1-py2.4.egg': 'b45adeda0667d2d2ffe14009364f2a4b',
+    'setuptools-0.6c2-py2.3.egg': 'f0064bf6aa2b7d0f3ba0b43f20817c27',
+    'setuptools-0.6c2-py2.4.egg': '616192eec35f47e8ea16cd6a122b7277',
+    'setuptools-0.6c3-py2.3.egg': 'f181fa125dfe85a259c9cd6f1d7b78fa',
+    'setuptools-0.6c3-py2.4.egg': 'e0ed74682c998bfb73bf803a50e7b71e',
+    'setuptools-0.6c3-py2.5.egg': 'abef16fdd61955514841c7c6bd98965e',
+    'setuptools-0.6c4-py2.3.egg': 'b0b9131acab32022bfac7f44c5d7971f',
+    'setuptools-0.6c4-py2.4.egg': '2a1f9656d4fbf3c97bf946c0a124e6e2',
+    'setuptools-0.6c4-py2.5.egg': '8f5a052e32cdb9c72bcf4b5526f28afc',
+    'setuptools-0.6c5-py2.3.egg': 'ee9fd80965da04f2f3e6b3576e9d8167',
+    'setuptools-0.6c5-py2.4.egg': 'afe2adf1c01701ee841761f5bcd8aa64',
+    'setuptools-0.6c5-py2.5.egg': 'a8d3f61494ccaa8714dfed37bccd3d5d',
+    'setuptools-0.6c6-py2.3.egg': '35686b78116a668847237b69d549ec20',
+    'setuptools-0.6c6-py2.4.egg': '3c56af57be3225019260a644430065ab',
+    'setuptools-0.6c6-py2.5.egg': 'b2f8a7520709a5b34f80946de5f02f53',
+    'setuptools-0.6c7-py2.3.egg': '209fdf9adc3a615e5115b725658e13e2',
+    'setuptools-0.6c7-py2.4.egg': '5a8f954807d46a0fb67cf1f26c55a82e',
+    'setuptools-0.6c7-py2.5.egg': '45d2ad28f9750e7434111fde831e8372',
+    'setuptools-0.6c8-py2.3.egg': '50759d29b349db8cfd807ba8303f1902',
+    'setuptools-0.6c8-py2.4.egg': 'cba38d74f7d483c06e9daa6070cce6de',
+    'setuptools-0.6c8-py2.5.egg': '1721747ee329dc150590a58b3e1ac95b',
+}
+
+import sys, os
+try: from hashlib import md5
+except ImportError: from md5 import md5
+
+def _validate_md5(egg_name, data):
+    if egg_name in md5_data:
+        digest = md5(data).hexdigest()
+        if digest != md5_data[egg_name]:
+            print >>sys.stderr, (
+                "md5 validation of %s failed!  (Possible download problem?)"
+                % egg_name
+            )
+            sys.exit(2)
+    return data
+
+def use_setuptools(
+    version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir,
+    download_delay=15
+):
+    """Automatically find/download setuptools and make it available on sys.path
+
+    `version` should be a valid setuptools version number that is available
+    as an egg for download under the `download_base` URL (which should end with
+    a '/').  `to_dir` is the directory where setuptools will be downloaded, if
+    it is not already available.  If `download_delay` is specified, it should
+    be the number of seconds that will be paused before initiating a download,
+    should one be required.  If an older version of setuptools is installed,
+    this routine will print a message to ``sys.stderr`` and raise SystemExit in
+    an attempt to abort the calling script.
+    """
+    was_imported = 'pkg_resources' in sys.modules or 'setuptools' in sys.modules
+    def do_download():
+        egg = download_setuptools(version, download_base, to_dir, download_delay)
+        sys.path.insert(0, egg)
+        import setuptools; setuptools.bootstrap_install_from = egg
+    try:
+        import pkg_resources
+    except ImportError:
+        return do_download()       
+    try:
+        pkg_resources.require("setuptools>="+version); return
+    except pkg_resources.VersionConflict, e:
+        if was_imported:
+            print >>sys.stderr, (
+            "The required version of setuptools (>=%s) is not available, and\n"
+            "can't be installed while this script is running. Please install\n"
+            " a more recent version first, using 'easy_install -U setuptools'."
+            "\n\n(Currently using %r)"
+            ) % (version, e.args[0])
+            sys.exit(2)
+        else:
+            del pkg_resources, sys.modules['pkg_resources']    # reload ok
+            return do_download()
+    except pkg_resources.DistributionNotFound:
+        return do_download()
+
+def download_setuptools(
+    version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir,
+    delay = 15
+):
+    """Download setuptools from a specified location and return its filename
+
+    `version` should be a valid setuptools version number that is available
+    as an egg for download under the `download_base` URL (which should end
+    with a '/'). `to_dir` is the directory where the egg will be downloaded.
+    `delay` is the number of seconds to pause before an actual download attempt.
+    """
+    import urllib2, shutil
+    egg_name = "setuptools-%s-py%s.egg" % (version,sys.version[:3])
+    url = download_base + egg_name
+    saveto = os.path.join(to_dir, egg_name)
+    src = dst = None
+    if not os.path.exists(saveto):  # Avoid repeated downloads
+        try:
+            from distutils import log
+            if delay:
+                log.warn("""
+---------------------------------------------------------------------------
+This script requires setuptools version %s to run (even to display
+help).  I will attempt to download it for you (from
+%s), but
+you may need to enable firewall access for this script first.
+I will start the download in %d seconds.
+
+(Note: if this machine does not have network access, please obtain the file
+
+   %s
+
+and place it in this directory before rerunning this script.)
+---------------------------------------------------------------------------""",
+                    version, download_base, delay, url
+                ); from time import sleep; sleep(delay)
+            log.warn("Downloading %s", url)
+            src = urllib2.urlopen(url)
+            # Read/write all in one block, so we don't create a corrupt file
+            # if the download is interrupted.
+            data = _validate_md5(egg_name, src.read())
+            dst = open(saveto,"wb"); dst.write(data)
+        finally:
+            if src: src.close()
+            if dst: dst.close()
+    return os.path.realpath(saveto)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+def main(argv, version=DEFAULT_VERSION):
+    """Install or upgrade setuptools and EasyInstall"""
+    try:
+        import setuptools
+    except ImportError:
+        egg = None
+        try:
+            egg = download_setuptools(version, delay=0)
+            sys.path.insert(0,egg)
+            from setuptools.command.easy_install import main
+            return main(list(argv)+[egg])   # we're done here
+        finally:
+            if egg and os.path.exists(egg):
+                os.unlink(egg)
+    else:
+        if setuptools.__version__ == '0.0.1':
+            print >>sys.stderr, (
+            "You have an obsolete version of setuptools installed.  Please\n"
+            "remove it from your system entirely before rerunning this script."
+            )
+            sys.exit(2)
+
+    req = "setuptools>="+version
+    import pkg_resources
+    try:
+        pkg_resources.require(req)
+    except pkg_resources.VersionConflict:
+        try:
+            from setuptools.command.easy_install import main
+        except ImportError:
+            from easy_install import main
+        main(list(argv)+[download_setuptools(delay=0)])
+        sys.exit(0) # try to force an exit
+    else:
+        if argv:
+            from setuptools.command.easy_install import main
+            main(argv)
+        else:
+            print "Setuptools version",version,"or greater has been installed."
+            print '(Run "ez_setup.py -U setuptools" to reinstall or upgrade.)'
+
+def update_md5(filenames):
+    """Update our built-in md5 registry"""
+
+    import re
+
+    for name in filenames:
+        base = os.path.basename(name)
+        f = open(name,'rb')
+        md5_data[base] = md5(f.read()).hexdigest()
+        f.close()
+
+    data = ["    %r: %r,\n" % it for it in md5_data.items()]
+    data.sort()
+    repl = "".join(data)
+
+    import inspect
+    srcfile = inspect.getsourcefile(sys.modules[__name__])
+    f = open(srcfile, 'rb'); src = f.read(); f.close()
+
+    match = re.search("\nmd5_data = {\n([^}]+)}", src)
+    if not match:
+        print >>sys.stderr, "Internal error!"
+        sys.exit(2)
+
+    src = src[:match.start(1)] + repl + src[match.end(1):]
+    f = open(srcfile,'w')
+    f.write(src)
+    f.close()
+
+
+if __name__=='__main__':
+    if len(sys.argv)>2 and sys.argv[1]=='--md5update':
+        update_md5(sys.argv[2:])
+    else:
+        main(sys.argv[1:])
+
+
+
+
+
+
+#!/bin/env/python
+# -*- coding: utf-8 -*-
+
+"""Layout multiple pages per sheet of a PDF document.
+
+Pdfnup is a Python command-line tool and module for layouting multiple 
+pages per sheet of a PDF document. Using it you can take a PDF document 
+and create a new PDF document from it where each page contains a number
+of minimized pages from the original PDF file. 
+
+This module can be considered a sample tool for the excellent
+package pyPdf by Mathieu Fenniak, see http://pybrary.net/pyPdf.
+
+For further information please look into the file README.txt!
+"""
+
+
+import re
+import sys
+import getopt
+import os.path
+
+from pdfnup import *
+
+
+__version__ = "0.4.1"
+__license__ = "GPL 3"
+__author__ = "Dinu Gherman"
+__date__ = "2009-07-06"
+
+
+# command-line usage stuff
+
+def _showVersion():
+    "Print version message and terminate."
+
+    prog = os.path.basename(sys.argv[0])
+    print "%s %s" % (prog, __version__)
+    sys.exit()
+
+
+def _showUsage():
+    "Print usage message and terminate."
+
+    prog = os.path.basename(sys.argv[0])
+    copyrightYear = __date__[:__date__.find("-")]
+    args = (prog, __version__, __author__, copyrightYear, __license__)
+    print "%s v. %s, Copyleft by %s, %s (%s)" % args 
+    print "Make multiple pages per sheet into a new PDF file."
+    print "USAGE: %s [options] file1 [file2...]" % prog
+    print """\
+OPTIONS:
+  -h --help          Prints this usage message and exits.
+  -v --version       Prints version number and exits.
+  -V --verbose       Prints path of generated PDF file.
+  -n NUM             Number of pages per sheet (default: 4),
+                     n must be a square or half square number, e.g. 16 or 8.
+  -l --layout DESC   Layout descriptor composed of two different letters 
+                     from "RLDU", e.g. ewith the following meaning:
+                       RD first Right then Down (default),
+                       UL first Up then Left, etc. (all combinations allowed).
+  -o --output FILE   Set output path (incl. some patterns).
+
+EXAMPLES:
+  %(prog)s -n 2 file.pdf       # 2 pages per sheet
+  %(prog)s -n 8 file.pdf       # 8 pages per sheet
+  %(prog)s -n 8 -l LD file.pdf # 8 pages per sheet, first left then down
+
+COPYLEFT:
+  see http://www.gnu.org/copyleft/gpl.html
+""" % {"prog": prog}
+
+    sys.exit()
+
+
+def _main():
+    "Main for command-line usage."
+
+    try:
+        longOpts = "help version layout= output=".split()
+        opts, args = getopt.getopt(sys.argv[1:], "hvn:l:o:V", longOpts)
+    except getopt.GetoptError:
+        print "ERROR"
+        _showUsage()
+    
+    stopOptions = "-v --version -h --help --verbose"
+    stopOptions = [key for (key, val) in opts if key in stopOptions]
+    if len(args) == 0 and len(stopOptions) == 0:
+        _showUsage()
+
+    layoutDesc = "RD"
+    numPagePerSheet = 4
+    outputPat = None
+    verbose = False
+    for key, val in opts:
+        if key in ("-h", "--help"):
+            _showUsage()
+        elif key in ("-v", "--version"):
+            _showVersion()
+        elif key in ("-V", "--verbose"):
+            verbose = True
+        elif key in ("-n",):
+            numPagePerSheet = int(val)
+        elif key in ("-o", "--output"):
+            outputPat = val
+        elif key in ("-l", "--layout"):
+            layoutDesc = val
+
+    # determine paths of input files
+    paths = [a for a in args if os.path.exists(a)]
+    
+    for path in paths:    
+        generateNup(path, numPagePerSheet, 
+            outPathPatternOrFile=outputPat, dirs=layoutDesc, verbose=verbose)
+    
+    
+if __name__ == '__main__':    
+    _main()
+#!/bin/env/python
+# -*- coding: utf-8 -*-
+
+"""Layout multiple pages per sheet of a PDF document.
+
+Pdfnup is a Python module and command-line tool for layouting multiple 
+pages per sheet of a PDF document. Using it you can take a PDF document 
+and create a new PDF document from it where each page contains a number
+of minimized pages from the original PDF file. 
+
+This can be considered a sample tool for the excellent package pyPdf by 
+Mathieu Fenniak, see http://pybrary.net/pyPdf.
+
+For further information please look into the file README.txt!
+"""
+
+
+import os
+import sys
+import math
+import zlib
+import base64
+import types
+try:
+    from cStringIO import StringIO
+except ImportError:
+    from StringIO import StringIO
+
+
+try:
+    from pyPdf import PdfFileWriter, PdfFileReader
+    from pyPdf.pdf import PageObject, ImmutableSet, ContentStream
+    from pyPdf.generic import \
+        NameObject, DictionaryObject, ArrayObject, FloatObject
+except ImportError:
+    _MSG = "Please install pyPdf first, see http://pybrary.net/pyPdf"
+    raise RuntimeError(_MSG)
+
+
+__version__ = "0.4.1"
+__license__ = "GPL 3"
+__author__ = "Dinu Gherman"
+__date__ = "2009-07-06"
+
+
+# one empty A4 page as base64-encoded zipped PDF file
+_mtA4PdfZip64 = """\
+eJyVVV1z2joQfWeG/7CTDjcwbfA3mLY3MwFCyrRpUyDpbUOmI2wB7hiJWnKT9KF/oH3re39rVzI2
+DrQPNzMO8mp1ztmjlVy76A+OrKZTrdR+/vj+C0Z0zRP5iszgjDKaEElDwBQIeZCuKJOwlHL91DBu
+b2+bic6NyawZ8BUCwGGXiCgYcCbF4VMIYiKEWtyPAhlxRpJ7qFYsMIHPPqn0yZKCkISFJAlhrlZB
+WKRWK8+fgzGwwMYFIzg+rlYoCzcrDwdWmWByv6aWokV8e4uvIy9o/IXKKCAZICqkOm6UJsA4ZQEP
+I7YA413ETpiI8oCafE1WVElR43E6k8gGhuZUkYl+1Zg7Ii/Igj7QqQIo0dlK1JHdonuIRZUbbVW6
+4jinYUS6/A6uwcR39Xgdr2m3vRYOfddq+n7HhxuVe0EStU+tfO2ICp4mARWgDVU6rXzuIuHBmEqE
+NVCerobeSfU7XKGybjHqFaMh3Og6EZhLbA8txpgkhGmCzVxmiq5ux5SRW3akRySJ+QJNcbem9PNe
+G3EuM0vepDKOGNbgF9IR+5yHyHIp6GvOaB4U29IzFTnHrhCvLGTI5hxVeLkKRXqSyiVPoE4YZ/cr
+noqGAu0llKjt6qvi67Zp+mbH9i3PdCxbJ7yk97c8CQXUGxuPwzSgiLM9W387RI1Ni32igYR6ysSa
+BtE8oqGemEQypios1SBs7FXU2m02gSW1tsau1X7IhNK8z1LVDFpzhHqvwdFn7ebhDoo9nnaZZ4yA
+ZIVE7R0ioSc25ziKJVqAfXYy7g2HvteneL70ng1iNDJ7zZhfUbaQS2iZmjdHOSPrt0vz9N/BE/Ny
++sKJDq74+9Vn4/xx9+qD88/V0G043WdPhLh8vzo8mp2dHsiDyYeGfPTu47dj1J7DPCzELxdS9Fi1
+4pfbILPJ3JpSJJZ9uUvovFoxoYP/ij9oeZ7jwbyIWRZarGdYEbPNzl7Madt7sZa7n9f29/E6HXc3
+ZmFwL+aYJQ6ZkCimSVbxsI8eQK30MVj8+WNwdISXF3YINutfW1pBXdfZ1DHNqe26/eOpbTvx1Pba
+U7ttv8WBNTUtGx8HH6v2XwP+V/YNqF1F0eoEe8Wth1cHuPnbOPpKobPpJ5LIbLMsx1PfvdrpG7z6
+fgMis+cW
+"""
+_mtA4Pdf = zlib.decompress(base64.decodestring(_mtA4PdfZip64))
+
+
+def isSquare(n):
+    "Is this a square number?"
+    
+    s = math.sqrt(n)
+    lower, upper = math.floor(s), math.ceil(s)
+    
+    return lower == upper
+
+
+def isHalfSquare(n):
+    "Is this a square number, divided by 2?"
+    
+    return isSquare(n * 2)
+
+
+def calcScalingFactors(w, h, wp, hp):
+    wp, hp = map(float, (wp, hp))
+    
+    if w == None:
+        xscale = h/hp
+        yscale = h/hp
+    elif h == None:
+        xscale = w/wp
+        yscale = w/wp
+    else:
+        xscale = w/wp
+        yscale = h/hp
+        
+    return xscale, yscale
+
+    
+def calcRects(pageSize, numTiles, dirs="RD"):
+    "Return list of sub rects for some rect."
+    
+    allowdDirs = [x+y for x in "RL" for y in "UD"]
+    allowdDirs += [y+x for x in "RL" for y in "UD"]
+    assert dirs in allowdDirs
+
+    width, height = pageSize
+    n = numTiles
+    xDir, yDir = dirs
+
+    if isSquare(n):
+        s = math.sqrt(n)
+        w, h = float(width)/float(s), float(height)/float(s)
+        if "R" in dirs:
+            xr = range(0, int(s))
+        elif "L" in dirs:
+            xr = range(int(s)-1, -1, -1)
+        if "D" in dirs:
+            yr = range(int(s)-1, -1, -1)
+        elif "U" in dirs:
+            yr = range(0, int(s))
+        xs = [i*w for i in xr]
+        ys = [j*h for j in yr]
+    elif isHalfSquare(n):
+        # should issue a warning for page ratios different from 1:sqr(2) 
+        s = math.sqrt(2*n)
+        if width > height:
+            w, h = float(width)/float(s), float(height)/float(s)*2
+            if "R" in dirs:
+                xr = range(0, int(s))
+            elif "L" in dirs:
+                xr = range(int(s)-1, -1, -1)
+            if "D" in dirs:
+                yr = range(int(s/2)-1, -1, -1)
+            elif "U" in dirs:
+                yr = range(0, int(s/2))
+            xs = [i*w for i in xr]
+            ys = [j*h for j in yr]
+        else:
+            w, h = float(width)/float(s)*2, float(height)/float(s)
+            if "R" in dirs:
+                xr = range(0, int(s/2))
+            elif "L" in dirs:
+                xr = range(int(s/2)-1, -1, -1)
+            if "D" in dirs:
+                yr = range(int(s)-1, -1, -1)
+            elif "U" in dirs:
+                yr = range(0, int(s))
+            xs = [i*w for i in xr]
+            ys = [j*h for j in yr]
+
+    # decide order (first x, then y or first y then x)
+    if dirs in "RD LD RU LU".split():
+        rects = [(x, y, w, h) for y in ys for x in xs]
+    elif dirs in "DR DL UR UL".split():
+        rects = [(x, y, w, h) for x in xs for y in ys]
+
+    return rects
+
+
+def exP1multiN(pdf, newPageSize, n):
+    "Extract page 1 of a PDF file, copy it n times resized."
+    
+    # create a file-like buffer object containing PDF code
+    buf = StringIO()
+    buf.write(pdf)
+
+    # extract first page and resize it as desired
+    srcReader = PdfFileReader(buf)
+    page1 = srcReader.getPage(0)
+    page1.mediaBox.upperRight = newPageSize
+
+    # create output and copy the first page n times
+    output = PdfFileWriter()
+    for i in range(n):
+        output.addPage(page1)
+    
+    # create a file-like buffer object to hold the new PDF code
+    buf2 = StringIO()
+    output.write(buf2)
+    buf2.seek(0)
+    
+    return buf2
+
+
+def isFileLike(obj):
+    "Is this a file-like object?"
+    
+    if type(obj) == types.FileType:
+        return True
+    if set("read seek close".split()).issubset(set(dir(obj))):
+        return True
+
+    return False
+    
+    
+def generateNup(inPathOrFile, n, outPathPatternOrFile=None, dirs="RD", 
+        verbose=False):
+    """Generate a N-up document version.
+    
+    If outPathPatternOrFile is None, the output will be written 
+    in a file named after the input file.
+    """    
+
+    assert isSquare(n) or isHalfSquare(n)
+
+    ipof = inPathOrFile
+    oppof = outPathPatternOrFile
+
+    if isFileLike(ipof):
+        inFile = ipof
+        if oppof is None:
+            raise AssertionError, "Must specify output for file input!"
+        elif isFileLike(oppof):
+            outFile = oppof
+        elif type(oppof) in types.StringTypes:
+            outPath = oppof
+            outFile = open(outPath, "wb")
+    elif type(ipof) in types.StringTypes:
+        inFile = open(ipof, "rb")
+        if isFileLike(oppof):
+            outFile = oppof
+        elif oppof is None or type(oppof) in types.StringTypes:
+            if oppof is None:
+                oppof = "%(dirname)s/%(base)s-%(n)dup%(ext)s"
+            aDict = {
+                "dirname": os.path.dirname(inPathOrFile) or ".", 
+                "basename": os.path.basename(inPathOrFile), 
+                "base": os.path.basename(os.path.splitext(inPathOrFile)[0]), 
+                "ext": os.path.splitext(inPathOrFile)[1],
+                "n":n, 
+            }
+            outPath = oppof % aDict
+            outPath = os.path.normpath(outPath)
+            outFile = open(outPath, "wb")
+        
+    # get info about source document
+    docReader = PdfFileReader(inFile)
+    numPages = docReader.getNumPages()
+    oldPageSize = docReader.getPage(0).mediaBox.upperRight
+
+    # create empty output document buffer
+    if isSquare(n):
+        newPageSize = oldPageSize
+    elif isHalfSquare(n):
+        newPageSize = oldPageSize[1], oldPageSize[0]
+    np = numPages / n + numPages % n
+    buf = exP1multiN(_mtA4Pdf, newPageSize, np)
+ 
+    # calculate mini page areas
+    rects = calcRects(newPageSize, n, dirs)
+        
+    # combine
+    ops = []
+    newPageNum = -1
+    for i in range(numPages):
+        if i % n == 0:
+            newPageNum += 1
+        op = (inPathOrFile, i, (0, 0, None, None), i/n, rects[i % n])
+        ops.append(op)
+
+    srcr = srcReader = PdfFileReader(inFile)
+    srcPages = [srcr.getPage(i) for i in range(srcr.getNumPages())]
+
+    if type(oppof) in types.StringTypes:
+        outFile = open(outPath, "rb")
+    outr = outReader = PdfFileReader(buf)
+    outPages = [outr.getPage(i) for i in range(outr.getNumPages())]
+    output = PdfFileWriter()
+
+    mapping = {}
+    for op in ops:
+        dummy, dummy, dummy, destPageNum, dummy = op
+        if destPageNum not in mapping:
+            mapping[destPageNum] = []
+        mapping[destPageNum].append(op) 
+        
+    IS = ImmutableSet
+    PO, AO, DO, NO = PageObject, ArrayObject, DictionaryObject, NameObject
+
+    for destPageNum, ops in mapping.items():
+        for op in ops:
+            inPathOrFile, srcPageNum, srcRect, destPageNum, destRect = op
+            page2 = srcPages[srcPageNum]
+            page1 = outPages[destPageNum]
+            pageWidth, pageHeight = page2.mediaBox.upperRight
+            destX, destY, destWidth, destHeight = destRect
+            xScale, yScale = calcScalingFactors(
+                destWidth, destHeight, pageWidth, pageHeight)
+            
+            newResources = DO()
+            rename = {}
+            orgResources = page1["/Resources"].getObject()
+            page2Resources = page2["/Resources"].getObject()
+        
+            names = "ExtGState Font XObject ColorSpace Pattern Shading"
+            for res in names.split():
+                res = "/" + res
+                new, newrename = PO._mergeResources(orgResources, 
+                    page2Resources, res)
+                if new:
+                    newResources[NO(res)] = new
+                    rename.update(newrename)
+        
+            newResources[NO("/ProcSet")] = AO(
+                IS(orgResources.get("/ProcSet", AO()).getObject()).union(
+                    IS(page2Resources.get("/ProcSet", AO()).getObject())
+                )
+            )
+        
+            newContentArray = AO()
+            orgContent = page1["/Contents"].getObject()
+            newContentArray.append(PO._pushPopGS(orgContent, page1.pdf))
+            page2Content = page2['/Contents'].getObject()
+            page2Content = PO._contentStreamRename(page2Content, rename, 
+                page1.pdf)
+            page2Content = ContentStream(page2Content, page1.pdf)
+            page2Content.operations.insert(0, [[], "q"])
+            
+            # handle rotation
+            try:
+                rotation = page2["/Rotate"].getObject()
+            except KeyError:
+                rotation = 0
+            if rotation in (180, 270):
+                dw, dh = destWidth, destHeight
+                arr = [-xScale, 0, 0, -yScale, destX+dw, destY+dh]
+            elif rotation in (0, 90):
+                arr = [xScale, 0, 0, yScale, destX, destY]
+            else:
+                # treat any other (illegal) rotation as 0
+                arr = [xScale, 0, 0, yScale, destX, destY]
+
+            arr = [FloatObject(str(x)) for x in arr]
+            page2Content.operations.insert(1, [arr, "cm"])
+            page2Content.operations.append([[], "Q"])
+            newContentArray.append(page2Content)
+            page1[NO('/Contents')] = ContentStream(newContentArray, page1.pdf)
+            page1[NO('/Resources')] = newResources            
+
+        output.addPage(page1)
+
+    if type(oppof) in types.StringTypes:
+        outFile = open(outPath, "wb")
+    output.write(outFile)
+    
+    if verbose:
+        if type(oppof) in types.StringTypes:
+            print "written: %s" % outPath
+        elif isFileLike:
+            print "written to file-like input parameter"

samples/genpdf.py

+#!/usr/bin/env python
+# _*_ coding: UTF-8 _*_
+
+"Generate test PDF documents for pdfnup."
+
+from reportlab.lib.colors import black, white, pink, lightblue
+from reportlab.lib.pagesizes import A4, legal, landscape
+from reportlab.pdfgen.canvas import Canvas
+
+
+# not used anymore
+def genTestFile(path, numPages):
+    "Generate a PDF doc with *very* big page numbers on all pages."
+    
+    size = landscape(A4)
+    canv = Canvas(path, pagesize=size)
+    for i in range(numPages):
+        canv.setFont("Helvetica", size[1]*1.2)
+        x, y = size[0]/2.0, size[1]*0.1
+        text = u"%s" % i
+        if i % 2 == 1:
+            canv.setStrokeColor(black)
+            canv.setFillColor(black)
+            canv.rect(0, 0, size[0], size[1], fill=True)
+        if i % 2 == 1:
+            canv.setFillColor(white)
+        else:
+            canv.setFillColor(black)
+        canv.drawCentredString(x, y, text) 
+        canv.showPage()
+    canv.save() 
+
+
+def generateNumberedPages(numPages, pageSize, orientation, bgColor, outPath):
+    "Generate a 10 page document with one big number per page."
+    
+    if orientation == "landscape":
+        pageSize = landscape(pageSize)
+    canv = Canvas(outPath, pagesize=pageSize)
+
+    for i in range(numPages):
+        canv.setFont("Helvetica", 500)
+        text = u"%s" % i
+        if i % 2 == 0:
+            canv.setStrokeColor(bgColor)
+            canv.setFillColor(bgColor)
+            canv.rect(0, 0, pageSize[0], pageSize[1], stroke=True, fill=True)
+            canv.setFillColor(black)
+        elif i % 2 == 1:
+            canv.setStrokeColor(black)
+            canv.setFillColor(black)
+            canv.rect(0, 0, pageSize[0], pageSize[1], stroke=True, fill=True)
+            canv.setFillColor(bgColor)
+        if orientation == "portrait":
+            canv.drawCentredString(pageSize[0]/2.0, pageSize[1]*0.3, u"%s" % i) 
+        elif orientation == "landscape":
+            canv.drawCentredString(pageSize[0]/2.0, pageSize[1]*0.21, u"%s" % i) 
+        canv.showPage()
+        
+    canv.save() 
+
+
+if __name__ == '__main__':
+    gnp = generateNumberedPages
+    gnp(50, A4, "landscape", pink, "test-a4-l.pdf")
+    gnp(50, A4, "portrait", lightblue, "test-a4-p.pdf")
+    gnp(50, legal, "portrait", lightblue, "test-legal-p.pdf")

Binary file added.

+%PDF-1.3
+1 0 obj
+<<
+/Kids [ 4 0 R 5 0 R 6 0 R 7 0 R ]
+/Type /Pages
+/Count 4
+>>
+endobj
+2 0 obj
+<<
+/Producer (Python PDF Library \055 http\072\057\057pybrary\056net\057pyPdf\057)
+>>
+endobj
+3 0 obj
+<<
+/Type /Catalog
+/Pages 1 0 R
+>>
+endobj
+4 0 obj
+<<
+/Contents 8 0 R
+/Parent 1 0 R
+/Resources <<
+/Font <<
+/F1 9 0 R
+>>
+/ProcSet [ /Text /ImageC /ImageB /PDF /ImageI ]
+>>
+/Trans <<
+>>
+/Rotate 0
+/MediaBox [ 0 0 841.8898 595.2756 ]
+/Type /Page
+>>
+endobj
+5 0 obj
+<<
+/Contents 10 0 R
+/Parent 1 0 R
+/Resources <<
+/Font <<
+/F1 9 0 R
+>>
+/ProcSet [ /Text /ImageC /ImageB /PDF /ImageI ]
+>>
+/Trans <<
+>>
+/Rotate 0
+/MediaBox [ 0 0 841.8898 595.2756 ]
+/Type /Page
+>>
+endobj
+6 0 obj
+<<
+/Contents 11 0 R
+/Parent 1 0 R
+/Resources <<
+/Font <<
+/F1 9 0 R
+>>
+/ProcSet [ /Text /ImageC /ImageB /PDF /ImageI ]
+>>
+/Trans <<
+>>
+/Rotate 0
+/MediaBox [ 0 0 841.8898 595.2756 ]
+/Type /Page
+>>
+endobj
+7 0 obj
+<<
+/Contents 12 0 R
+/Parent 1 0 R
+/Resources <<
+/Font <<
+/F1 9 0 R
+>>
+/ProcSet [ /Text /ImageC /ImageB /PDF /ImageI ]
+>>
+/Trans <<
+>>
+/Rotate 0
+/MediaBox [ 0 0 841.8898 595.2756 ]
+/Type /Page
+>>
+endobj
+8 0 obj
+<<
+/Length 3677
+>>
+stream
+q
+q
+q
+q
+q
+q
+q
+q
+q
+q
+q
+q
+q
+q
+q
+q
+1 0 0 1 0 0 cm
+BT
+/F1 12 Tf
+14.4 TL
+ET
+Q
+q
+0.25 0 0 0.25 0.0 446.4567 cm
+1 0 0 1 0 0 cm
+BT
+/F1 12 Tf
+14.4 TL
+ET
+BT
+/F1 500 Tf
+600 TL
+ET
+1 0.752941 0.796078 RG
+1 0.752941 0.796078 rg
+n
+0 0 841.8898 595.2756 re
+B*
+0 0 0 rg
+BT
+1 0 0 1 281.9449 125.0079 Tm
+(0) Tj
+T*
+ET
+Q
+Q
+q
+0.25 0 0 0.25 210.47245 446.4567 cm
+1 0 0 1 0 0 cm
+BT
+/F1 12 Tf
+14.4 TL
+ET
+BT
+/F1 500 Tf
+600 TL
+ET
+0 0 0 RG
+0 0 0 rg
+n
+0 0 841.8898 595.2756 re
+B*
+1 0.752941 0.796078 rg
+BT
+1 0 0 1 281.9449 125.0079 Tm
+(1) Tj
+T*
+ET
+Q
+Q
+q
+0.25 0 0 0.25 420.9449 446.4567 cm
+1 0 0 1 0 0 cm
+BT
+/F1 12 Tf
+14.4 TL
+ET
+BT
+/F1 500 Tf
+600 TL
+ET
+1 0.752941 0.796078 RG
+1 0.752941 0.796078 rg
+n
+0 0 841.8898 595.2756 re
+B*
+0 0 0 rg
+BT
+1 0 0 1 281.9449 125.0079 Tm
+(2) Tj
+T*
+ET
+Q
+Q
+q
+0.25 0 0 0.25 631.41735 446.4567 cm
+1 0 0 1 0 0 cm
+BT
+/F1 12 Tf
+14.4 TL
+ET
+BT
+/F1 500 Tf
+600 TL
+ET
+0 0 0 RG
+0 0 0 rg
+n
+0 0 841.8898 595.2756 re
+B*
+1 0.752941 0.796078 rg
+BT
+1 0 0 1 281.9449 125.0079 Tm
+(3) Tj
+T*
+ET
+Q
+Q
+q
+0.25 0 0 0.25 0.0 297.6378 cm
+1 0 0 1 0 0 cm
+BT
+/F1 12 Tf
+14.4 TL
+ET
+BT
+/F1 500 Tf
+600 TL
+ET
+1 0.752941 0.796078 RG
+1 0.752941 0.796078 rg
+n
+0 0 841.8898 595.2756 re
+B*
+0 0 0 rg
+BT
+1 0 0 1 281.9449 125.0079 Tm
+(4) Tj
+T*
+ET
+Q
+Q
+q
+0.25 0 0 0.25 210.47245 297.6378 cm
+1 0 0 1 0 0 cm
+BT
+/F1 12 Tf
+14.4 TL
+ET
+BT
+/F1 500 Tf
+600 TL
+ET
+0 0 0 RG
+0 0 0 rg
+n
+0 0 841.8898 595.2756 re
+B*
+1 0.752941 0.796078 rg
+BT
+1 0 0 1 281.9449 125.0079 Tm
+(5) Tj
+T*
+ET
+Q
+Q
+q
+0.25 0 0 0.25 420.9449 297.6378 cm
+1 0 0 1 0 0 cm
+BT
+/F1 12 Tf
+14.4 TL
+ET
+BT
+/F1 500 Tf
+600 TL
+ET
+1 0.752941 0.796078 RG
+1 0.752941 0.796078 rg
+n
+0 0 841.8898 595.2756 re
+B*
+0 0 0 rg
+BT
+1 0 0 1 281.9449 125.0079 Tm
+(6) Tj
+T*
+ET
+Q
+Q
+q
+0.25 0 0 0.25 631.41735 297.6378 cm
+1 0 0 1 0 0 cm
+BT
+/F1 12 Tf
+14.4 TL
+ET
+BT
+/F1 500 Tf
+600 TL
+ET
+0 0 0 RG
+0 0 0 rg
+n
+0 0 841.8898 595.2756 re
+B*
+1 0.752941 0.796078 rg
+BT
+1 0 0 1 281.9449 125.0079 Tm
+(7) Tj
+T*
+ET
+Q
+Q
+q
+0.25 0 0 0.25 0.0 148.8189 cm
+1 0 0 1 0 0 cm
+BT
+/F1 12 Tf
+14.4 TL
+ET
+BT
+/F1 500 Tf
+600 TL
+ET
+1 0.752941 0.796078 RG
+1 0.752941 0.796078 rg
+n
+0 0 841.8898 595.2756 re
+B*
+0 0 0 rg
+BT
+1 0 0 1 281.9449 125.0079 Tm
+(8) Tj
+T*
+ET
+Q
+Q
+q
+0.25 0 0 0.25 210.47245 148.8189 cm
+1 0 0 1 0 0 cm
+BT
+/F1 12 Tf
+14.4 TL
+ET
+BT
+/F1 500 Tf
+600 TL
+ET
+0 0 0 RG
+0 0 0 rg
+n
+0 0 841.8898 595.2756 re
+B*
+1 0.752941 0.796078 rg
+BT
+1 0 0 1 281.9449 125.0079 Tm
+(9) Tj
+T*
+ET
+Q
+Q
+q
+0.25 0 0 0.25 420.9449 148.8189 cm
+1 0 0 1 0 0 cm
+BT
+/F1 12 Tf
+14.4 TL
+ET
+BT
+/F1 500 Tf
+600 TL
+ET
+1 0.752941 0.796078 RG
+1 0.752941 0.796078 rg
+n
+0 0 841.8898 595.2756 re
+B*
+0 0 0 rg
+BT
+1 0 0 1 142.9449 125.0079 Tm
+(10) Tj
+T*
+ET
+Q
+Q
+q
+0.25 0 0 0.25 631.41735 148.8189 cm
+1 0 0 1 0 0 cm
+BT
+/F1 12 Tf
+14.4 TL
+ET
+BT
+/F1 500 Tf
+600 TL
+ET
+0 0 0 RG
+0 0 0 rg
+n
+0 0 841.8898 595.2756 re
+B*
+1 0.752941 0.796078 rg
+BT
+1 0 0 1 142.9449 125.0079 Tm
+(11) Tj
+T*
+ET
+Q
+Q
+q
+0.25 0 0 0.25 0.0 0.0 cm
+1 0 0 1 0 0 cm
+BT
+/F1 12 Tf
+14.4 TL
+ET
+BT
+/F1 500 Tf
+600 TL
+ET
+1 0.752941 0.796078 RG
+1 0.752941 0.796078 rg
+n
+0 0 841.8898 595.2756 re
+B*
+0 0 0 rg
+BT
+1 0 0 1 142.9449 125.0079 Tm
+(12) Tj
+T*
+ET
+Q
+Q
+q
+0.25 0 0 0.25 210.47245 0.0 cm
+1 0 0 1 0 0 cm
+BT
+/F1 12 Tf
+14.4 TL
+ET
+BT
+/F1 500 Tf
+600 TL
+ET
+0 0 0 RG
+0 0 0 rg
+n
+0 0 841.8898 595.2756 re
+B*
+1 0.752941 0.796078 rg
+BT
+1 0 0 1 142.9449 125.0079 Tm
+(13) Tj
+T*
+ET
+Q
+Q
+q
+0.25 0 0 0.25 420.9449 0.0 cm
+1 0 0 1 0 0 cm
+BT
+/F1 12 Tf
+14.4 TL
+ET
+BT
+/F1 500 Tf
+600 TL
+ET
+1 0.752941 0.796078 RG
+1 0.752941 0.796078 rg
+n
+0 0 841.8898 595.2756 re
+B*
+0 0 0 rg
+BT
+1 0 0 1 142.9449 125.0079 Tm
+(14) Tj
+T*
+ET
+Q
+Q
+q
+0.25 0 0 0.25 631.41735 0.0 cm
+1 0 0 1 0 0 cm
+BT
+/F1 12 Tf
+14.4 TL
+ET
+BT
+/F1 500 Tf
+600 TL
+ET
+0 0 0 RG
+0 0 0 rg
+n
+0 0 841.8898 595.2756 re
+B*
+1 0.752941 0.796078 rg
+BT
+1 0 0 1 142.9449 125.0079 Tm
+(15) Tj
+T*
+ET
+Q
+
+endstream
+endobj
+9 0 obj
+<<
+/Encoding /WinAnsiEncoding
+/Type /Font
+/Name /F1
+/BaseFont /Helvetica
+/Subtype /Type1
+>>
+endobj
+10 0 obj
+<<
+/Length 3687
+>>
+stream
+q
+q
+q
+q
+q
+q
+q
+q
+q
+q
+q
+q
+q
+q
+q
+q
+1 0 0 1 0 0 cm
+BT
+/F1 12 Tf
+14.4 TL
+ET
+Q
+q
+0.25 0 0 0.25 0.0 446.4567 cm
+1 0 0 1 0 0 cm
+BT
+/F1 12 Tf
+14.4 TL
+ET
+BT
+/F1 500 Tf
+600 TL
+ET
+1 0.752941 0.796078 RG
+1 0.752941 0.796078 rg
+n
+0 0 841.8898 595.2756 re
+B*
+0 0 0 rg
+BT
+1 0 0 1 142.9449 125.0079 Tm
+(16) Tj
+T*
+ET
+Q
+Q
+q
+0.25 0 0 0.25 210.47245 446.4567 cm
+1 0 0 1 0 0 cm
+BT
+/F1 12 Tf
+14.4 TL
+ET
+BT
+/F1 500 Tf
+600 TL
+ET
+0 0 0 RG
+0 0 0 rg
+n
+0 0 841.8898 595.2756 re
+B*
+1 0.752941 0.796078 rg
+BT
+1 0 0 1 142.9449 125.0079 Tm
+(17) Tj
+T*
+ET
+Q
+Q
+q
+0.25 0 0 0.25 420.9449 446.4567 cm
+1 0 0 1 0 0 cm
+BT
+/F1 12 Tf
+14.4 TL
+ET
+BT
+/F1 500 Tf
+600 TL
+ET
+1 0.752941 0.796078 RG
+1 0.752941 0.796078 rg
+n
+0 0 841.8898 595.2756 re
+B*
+0 0 0 rg
+BT
+1 0 0 1 142.9449 125.0079 Tm
+(18) Tj
+T*
+ET
+Q
+Q
+q
+0.25 0 0 0.25 631.41735 446.4567 cm
+1 0 0 1 0 0 cm
+BT
+/F1 12 Tf
+14.4 TL
+ET
+BT
+/F1 500 Tf
+600 TL
+ET
+0 0 0 RG
+0 0 0 rg
+n
+0 0 841.8898 595.2756 re
+B*
+1 0.752941 0.796078 rg
+BT
+1 0 0 1 142.9449 125.0079 Tm
+(19) Tj
+T*
+ET
+Q
+Q
+q
+0.25 0 0 0.25 0.0 297.6378 cm
+1 0 0 1 0 0 cm
+BT
+/F1 12 Tf
+14.4 TL
+ET
+BT
+/F1 500 Tf
+600 TL
+ET
+1 0.752941 0.796078 RG
+1 0.752941 0.796078 rg
+n
+0 0 841.8898 595.2756 re
+B*
+0 0 0 rg
+BT
+1 0 0 1 142.9449 125.0079 Tm
+(20) Tj
+T*
+ET
+Q
+Q
+q
+0.25 0 0 0.25 210.47245 297.6378 cm
+1 0 0 1 0 0 cm
+BT
+/F1 12 Tf
+14.4 TL
+ET
+BT
+/F1 500 Tf
+600 TL
+ET
+0 0 0 RG
+0 0 0 rg
+n
+0 0 841.8898 595.2756 re
+B*
+1 0.752941 0.796078 rg
+BT
+1 0 0 1 142.9449 125.0079 Tm
+(21) Tj
+T*
+ET
+Q
+Q
+q
+0.25 0 0 0.25 420.9449 297.6378 cm
+1 0 0 1 0 0 cm
+BT
+/F1 12 Tf
+14.4 TL
+ET
+BT
+/F1 500 Tf
+600 TL
+ET
+1 0.752941 0.796078 RG
+1 0.752941 0.796078 rg
+n
+0 0 841.8898 595.2756 re
+B*
+0 0 0 rg
+BT
+1 0 0 1 142.9449 125.0079 Tm
+(22) Tj
+T*
+ET
+Q
+Q
+q
+0.25 0 0 0.25 631.41735 297.6378 cm
+1 0 0 1 0 0 cm
+BT
+/F1 12 Tf
+14.4 TL
+ET
+BT
+/F1 500 Tf
+600 TL
+ET
+0 0 0 RG
+0 0 0 rg
+n
+0 0 841.8898 595.2756 re
+B*
+1 0.752941 0.796078 rg
+BT
+1 0 0 1 142.9449 125.0079 Tm
+(23) Tj
+T*
+ET
+Q
+Q
+q
+0.25 0 0 0.25 0.0 148.8189 cm
+1 0 0 1 0 0 cm
+BT
+/F1 12 Tf
+14.4 TL
+ET
+BT
+/F1 500 Tf
+600 TL
+ET
+1 0.752941 0.796078 RG
+1 0.752941 0.796078 rg
+n
+0 0 841.8898 595.2756 re
+B*
+0 0 0 rg
+BT
+1 0 0 1 142.9449 125.0079 Tm