#!/usr/bin/env python3 import os import argparse from PyPDF2 import PageObject, PdfReader, PdfWriter, PaperSize, Transformation def main(): PROGRAM_NAME="bookletify" DESCRIPTION = """Rearranges the pages of a pdf in order to print it as is, so that you can take it, cut it in half and close it like you would with a book. The result will be the original pdf in the correct order, with the size of half a page. Especially useful to print pdfs to A5 size on A4 paper""" parser = argparse.ArgumentParser(prog=PROGRAM_NAME, description=DESCRIPTION) parser.add_argument('filename') parser.add_argument('-q', '--quiet', action='store_true', help='suppress stdout') parser.add_argument('-s', '--size', action='store', choices=['A0', 'A1', 'A2', 'A3', 'A4', 'A5', 'A6', 'A7', 'A8', 'C4'], default='A4', metavar='SIZE', help="""set final paper size. Possible values: 'A0', ..., 'A8' or 'C4'. Default value is 'A4'""") parser.add_argument('-b', '--binding-margin', default=10, type=float, metavar='MARGIN', help="""internal margin for the binding, expressed in millimeters relative to the final paper size (ie: half of the height of the paper size specified by --size). Set to 0 to disable. This setting is independent to the trim setting, ie: increasing or decreasing this margin does not trim any content on the final pdf, the content gets scaled down (or up) to fit to the final width minus the binding margin. Default value is 10""") parser.add_argument('-t', '--trim', default=[0,0,0,0], type=float, nargs=4, metavar='T', help="""margins to trim, expressed in millimeters relative to the original page size, as: top right bottom left. This setting is independent to the binding margin, ie: first the trim is applied, then the trimmed content gets scaled up (or down) to the full height and width, minus the binding margin. Changing the trim does not increase or decrease the final binding margin. A negative trim adds an empty border on that side. Default value is 0 0 0 0""") args = parser.parse_args() info = lambda text='', end='\n': print(text, end=end) if args.quiet: info = lambda text='', end='\n': None prog = lambda curr, max: info(f"\r{curr} / {max}", '\n' if curr == max else '') input_name = args.filename if not os.path.isfile(input_name): print(f'file {input_name} does not exist') exit(1) output_name = f'{input_name}-rearranged' if (input_name.endswith('.pdf')): output_name = f'{input_name[0:-4]}-rearranged.pdf' fullwidth = getattr(PaperSize, args.size).width fullheight = getattr(PaperSize, args.size).height smallwidth = fullheight / 2 smallheight = fullwidth # Convert mm to pixels at 72 dpi bindingwidth = args.binding_margin * 72.0 / 25.4 smallblank = PageObject.create_blank_page(width=smallwidth - bindingwidth, height=smallheight) tt = args.trim[0] * 72.0 / 25.4 tr = args.trim[1] * 72.0 / 25.4 tb = args.trim[2] * 72.0 / 25.4 tl = args.trim[3] * 72.0 / 25.4 # Read the input reader = PdfReader(input_name) n = len(reader.pages) pages = [] info("Reading...") for i in range(n): prog(i, n) pages.append(reader.pages[i]) prog(n,n) while(len(pages) % 4 != 0): pages.append(smallblank) output = PdfWriter() info("\nRearranging...") def get_scaling(page): sx = float(smallwidth - bindingwidth) / (float(page.mediabox.width) - tr - tl) sy = float(smallheight) / (float(page.mediabox.height) - tt - tb) return (sx, sy) m = len(pages) for i in range(int(m / 2)): prog(i, int(m / 2)) new_page = PageObject.create_blank_page(width=fullwidth, height=fullheight) if i % 2 == 0: (sx, sy) = get_scaling(pages[m-1-i]) t = Transformation().scale(sx, sy).rotate(-90).translate(-tb * sy, fullheight + tl * sx) pages[m-1-i].add_transformation(t) pages[m-1-i].cropbox.upper_right = (smallheight, fullheight) pages[m-1-i].cropbox.lower_left = (0, smallwidth + bindingwidth) new_page.merge_page(pages[m-1-i]) (sx, sy) = get_scaling(pages[i]) t = Transformation().scale(sx, sy).rotate(-90).translate(-tb * sy, smallwidth - bindingwidth + tl * sx) pages[i].add_transformation(t) pages[i].cropbox.upper_right = (smallheight, smallwidth - bindingwidth) pages[i].cropbox.lower_left = (0, 0) new_page.merge_page(pages[i]) else: (sx, sy) = get_scaling(pages[m-1-i]) t = Transformation().scale(sx, sy).rotate(90).translate(smallheight + tb * sy, smallwidth + bindingwidth - tl * sx) pages[m-1-i].add_transformation(t) pages[m-1-i].cropbox.upper_right = (smallheight, fullheight) pages[m-1-i].cropbox.lower_left = (0, smallwidth + bindingwidth) new_page.merge_page(pages[m-1-i]) (sx, sy) = get_scaling(pages[i]) t = Transformation().scale(sx, sy).rotate(90).translate(smallheight + tb * sy, 0 - tl * sx) pages[i].add_transformation(t) pages[i].cropbox.upper_right = (smallheight, smallwidth - bindingwidth) pages[i].cropbox.lower_left = (0, 0) new_page.merge_page(pages[i]) output.add_page(new_page) prog(int(m/2), int(m/2)) info("\nSaving...") output.write(output_name) info("\nDone!") if __name__ == "__main__": main()