#!/usr/bin/env python """ pdfbook2 - transform pdf files to booklets This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . """ import sys import subprocess import os from optparse import OptionParser, OptionGroup, HelpFormatter import shutil #=============================================================================== # Create booklet for file $name #=============================================================================== def booklify( name, opts ): #------------------------------------------------------ Check if file exists print "\nProcessing", name if not os.path.isfile( name ): print "SKIP: file not found." return print "Getting bounds...", sys.stdout.flush() #---------------------------------------------------------- useful constants bboxName = "%%HiResBoundingBox:" tmpFile = ".crop-tmp.pdf" #------------------------------------------------- find min/max bounding box if opts.crop: p = subprocess.Popen( ["pdfcrop", "--verbose", "--resolution", repr( opts.resolution ), name, tmpFile], stdout = subprocess.PIPE, stderr = subprocess.PIPE ) p.wait() out, err = p.communicate() if len( err ) != 0: print err print "\n\nABORT: Problem getting bounds" sys.exit( 1 ) lines = out.splitlines() bboxes = [s[len( bboxName ) + 1:] for s in lines if s.startswith( bboxName )] bounds = [[float( x ) for x in bbox.split()] for bbox in bboxes ] minLOdd = min( [bound[0] for bound in bounds[::2] ] ) maxROdd = max( [bound[2] for bound in bounds[::2] ] ) minLEven = min( [bound[0] for bound in bounds[1::2] ] ) maxREven = max( [bound[2] for bound in bounds[1::2] ] ) minT = min( [bound[1] for bound in bounds ] ) maxB = max( [bound[3] for bound in bounds ] ) widthOdd = maxROdd - minLOdd widthEven = maxREven - minLEven maxWidth = max( widthOdd, widthEven ) minLOdd -= maxWidth - widthOdd maxREven += maxWidth - widthEven print "done" sys.stdout.flush() #--------------------------------------------- crop file to area of interest print "cropping...", sys.stdout.flush() p = subprocess.Popen( ["pdfcrop", "--bbox-odd", "{L} {T} {R} {B}".format( L = minLOdd - opts.innerMargin / 2, T = minT - opts.topMargin, R = maxROdd + opts.outerMargin, B = maxB + opts.outerMargin ), "--bbox-even", "{L} {T} {R} {B}".format( L = minLEven - opts.outerMargin, T = minT - opts.topMargin, R = maxREven + opts.innerMargin / 2, B = maxB + opts.outerMargin ), "--resolution", repr( opts.resolution ), name, tmpFile], stdout = subprocess.PIPE, stderr = subprocess.PIPE ) p.wait() out, err = p.communicate() if len( err ) != 0: print err print "\n\nABORT: Problem with cropping" sys.exit( 1 ) print "done" sys.stdout.flush() else: shutil.copy( name, tmpFile ) #-------------------------------------------------------- create the booklet print "create booklet...", sys.stdout.flush() pdfJamCallList = [ "pdfjam", "-O", "booklet=true", "--landscape", "--suffix=book", "-O", "signature=" + repr( opts.signature ), tmpFile ] # add option --paper to call if opts.paper is not None: pdfJamCallList.append( "--paper=" + opts.paper ) # add option --short-edge to call if opts.shortedge: # check if everyshi.sty exists as texlive recommends p = subprocess.Popen( ["kpsewhich", "everyshi.sty"], stdout = subprocess.PIPE, stderr = subprocess.PIPE ) p.wait() out, err = p.communicate() if len( out ) == 0: print "\n\nABORT: The everyshi.sty latex package is needed for short-edge." sys.exit( 1 ) else: pdfJamCallList.append( r"--preamble=\usepackage{everyshi}\makeatletter\EveryShipout{\ifodd\c@page\pdfpageattr{/Rotate 180}\fi}\makeatother" ) # run call to pdfJam to make booklet p = subprocess.Popen( pdfJamCallList, stdout = subprocess.PIPE, stderr = subprocess.PIPE ) p.wait() #-------------------------------------------- move file and remove temp file os.rename( tmpFile[:-4] + "-book.pdf", name[:-4] + "-book.pdf" ) os.remove( tmpFile ) print "done" sys.stdout.flush() #=============================================================================== # Help formatter #=============================================================================== class MyHelpFormatter ( HelpFormatter ): """Format help with indented section bodies. """ def __init__( self, indent_increment = 4, max_help_position = 16, width = None, short_first = 0 ): HelpFormatter.__init__( self, indent_increment, max_help_position, width, short_first ) def format_usage( self, usage ): return ( "USAGE\n\n%*s%s\n" ) % ( self.indent_increment, "", usage ) def format_heading( self, heading ): return "%*s%s\n\n" % ( self.current_indent, "", heading.upper() ) #=============================================================================== # main programm #=============================================================================== if __name__ == "__main__": #------------------------------------------------------------ useful strings usageString = "Usage: %prog [options] file1 [file2 ...]" versionString = """ %prog v1.2 (c) 2015 Johannes Neumann (http://www.neumannjo.de) licensed under GPLv3 (http://www.gnu.org/licenses/gpl-3.0) based on pdfbook by David Firth with help from Marco Pessotto\n""" defaultString = " (default: %default)" #------------------------------------------------- create commandline parser parser = OptionParser( usage = usageString, version = versionString, formatter = MyHelpFormatter( indent_increment = 4 ) ) generalGroup = OptionGroup( parser, "General" ) generalGroup.add_option( "-p", "--paper", dest = "paper", type = "str", action = "store", metavar = "STR", help = "Format of the output paper dimensions as latex keyword (e.g. a4paper, letterpaper, legalpaper, ...)" ) generalGroup.add_option( "-s", "--short-edge", dest = "shortedge", action = "store_true", help = "Format the booklet for short-edge double-sided printing", default = False ) generalGroup.add_option( "-n", "--no-crop", dest = "crop", action = "store_false", help = "Prevent the cropping to the content area", default = True ) parser.add_option_group( generalGroup ) marginGroup = OptionGroup( parser, "Margins" ) marginGroup.add_option( "-o", "--outer-margin", type = "int", default = 40, dest = "outerMargin", action = "store", metavar = "INT", help = "Defines the outer margin in the booklet" + defaultString ) marginGroup.add_option( "-i", "--inner-margin", type = "int", default = 150, dest = "innerMargin", action = "store", metavar = "INT", help = "Defines the inner margin between the pages in the booklet" + defaultString ) marginGroup.add_option( "-t", "--top-margin", type = "int", default = 30, dest = "topMargin", action = "store", metavar = "INT", help = "Defines the top margin in the booklet" + defaultString ) marginGroup.add_option( "-b", "--bottom-margin", type = "int", default = 30, metavar = "INT", dest = "bottomMargin", action = "store", help = "Defines the bottom margin in the booklet" + defaultString ) parser.add_option_group( marginGroup ) advancedGroup = OptionGroup( parser, "Advanced" ) advancedGroup.add_option( "--signature", dest = "signature", action = "store", type = "int", help = "Define the signature for the booklet handed to pdfjam, needs to be multiple of 4" + defaultString, default = 4, metavar = "INT" ) advancedGroup.add_option( "--signature*", dest = "signature", action = "store", type = "int", help = "Same as --signature", metavar = "INT" ) advancedGroup.add_option( "--resolution", dest = "resolution", action = "store", type = "int", help = "Resolution used by ghostscript in bp" + defaultString, metavar = "INT", default = 72 ) parser.add_option_group( advancedGroup ) opts, args = parser.parse_args() #------------------------------------ show help if started without arguments if len( args ) == 0: parser.print_version() parser.print_help() print "" sys.exit( 2 ) #------------------------------------------- run for each provided file name parser.print_version() for arg in args: booklify( arg, opts )