Welcome, guest | Sign In | My Account | Store | Cart

Taking a PDF file as input, include only the specified pages and optionally arrange several pages on a sheet of paper on output. Similar to the "psnup" program for Postscript.

Run it like: $./pdf-pages --pages=2-13,17-19 --nup=1 report.pdf The output file will be "report-pdf-pages.pdf".

Requires the "pdflatex" program.

Python, 114 lines
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
#!/usr/bin/env python

'''
Use the 'pdfpages' latex package to only include some pages and arrange several
on a sheet of paper.
Requires 'pdflatex' and the 'pdfpages' latex package.
'''

import sys
import os
import tempfile
import subprocess
import shutil
import optparse

TEMPLATE = r'''
\documentclass[%(paper)s]{article}
\usepackage{pdfpages}

\begin{document}
\includepdf[
    pages={%(pages)s},
    nup=%(nup)s,
    landscape=%(landscape)s,
    openright=%(open_right)s,
]{%(filename)s}
\end{document}
'''

def program_name():
    return os.path.basename(sys.argv[0])

def run_cmd_quiet(cmd_line_list):
    # use TemporaryFile for '> /dev/null' behavior
    r = subprocess.call(cmd_line_list, stdout=tempfile.TemporaryFile(mode='w'))
    if r != 0:
        raise Exception('%s returned %d' % cmd_line_list)

def bool_to_latex(v):
    if not isinstance(v, bool):
        return v
    if v:
        return 'true'
    else:
        return 'false'

def do_pdf_pages(filename, options):
    'output is in options.filename + ".pdf-pages.pdf"'
    filename = os.path.realpath(filename)
    tdir = tempfile.mkdtemp('-pdf-pages')
    if options.debug:
        sys.stderr.write('%s: working dir: "%s"\n' % (program_name(), tdir))
    try:
        old_dir = os.getcwd()
        os.chdir(tdir)
        
        # work around for using Unicode filenames in Tex
        os.link(filename, 'input.pdf')

        pdfpages_options = dict( (k, bool_to_latex(v)) 
                for (k, v) in options.__dict__.iteritems())
        pdfpages_options['filename'] = 'input.pdf'
        t = file('pdfpages.tex', 'w')
        t.write(TEMPLATE % pdfpages_options)
        t.close()
        if options.debug:
            sys.stderr.write(TEMPLATE % pdfpages_options)

        run_cmd_quiet(['pdflatex', 'pdfpages.tex'])
        shutil.move('pdfpages.pdf',
                    os.path.join(old_dir,
                    os.path.splitext(os.path.basename(filename))[0] + '-pdf-pages.pdf'))
    finally:
        if not options.debug:
            shutil.rmtree(tdir)

def main(args):
    op = optparse.OptionParser(usage='usage: %prog PDF_FILE', option_list=[
        optparse.Option('--paper', dest='paper', default='a4paper',
                    help='physical size of the paper to print on'),
        optparse.Option('-p', '--pages', dest='pages', default='-', # all pages
                    help='pages specification, inclusive, ex:1-3,17'),
        optparse.Option('-n', '--nup', dest='nup', default='1x2',
                    help='number of pages to put on a sheet, ex:1x2 or 2x2'),
        optparse.Option('--portrait',
                    action='store_false', dest='landscape',
                    default=True,
                    help='print in portrait mode'),
        optparse.Option('--open-left',
                    action='store_false', dest='open_right',
                    default=True,
                    help='insert blank page at start'),
        optparse.Option('--debug',
                    action='store', dest='debug',
                    default=0, type=int,
                    help='enable debugging'),
    ])

    (options, args) = op.parse_args(args)

    if len(args) != 1:
        op.print_help()
        sys.exit(1)

    filename = args[0]

    # 'b5' -> 'b5paper'
    if not options.paper.endswith('paper'):
        options.paper = options.paper.lower() + 'paper'

    do_pdf_pages(filename, options)

if __name__ == '__main__':
    main(sys.argv[1:])

Fixed the relative path handling bug kindly reported by keith.briggs AT bt.com The diff: <pre> --- pdf-pages.orig 2006-07-02 10:42:07.000000000 +0800 +++ pdf-pages 2006-07-02 10:29:23.000000000 +0800 @@ -46,6 +46,7 @@

def do_pdf_pages(filename, options): 'output is in options.filename + ".pdf-pages.pdf"' + filename = os.path.realpath(filename) tdir = tempfile.mkdtemp('-pdf-pages') if options.debug: sys.stderr.write('%s: working dir: "%s"\n' % (program_name(), tdir)) </pre>