From 76ceb6dfb82bd9841659a17a5d514ac994461ecf Mon Sep 17 00:00:00 2001 From: Francesco Baldino Date: Wed, 24 May 2023 02:00:50 +0200 Subject: [PATCH] updated bookletify to automatically merge 2 pages per sheet --- bookletify.py | 181 ++++++++++++++++++++++++-------------------------- 1 file changed, 86 insertions(+), 95 deletions(-) diff --git a/bookletify.py b/bookletify.py index 4739346..9072585 100755 --- a/bookletify.py +++ b/bookletify.py @@ -1,72 +1,50 @@ #!/usr/bin/env python3 import os -import sys -from math import ceil -from PyPDF2 import PdfReader, PdfWriter +import argparse +from PyPDF2 import PageObject, PdfReader, PdfWriter, PaperSize, Transformation def main(): - def print_help(): - print('bookletify') - print() - print('USAGE') - print(' bookletify [OPTIONS] file') - print() - print('DESCRIPTION') - print(' Rearranges the pages of a pdf in order to print it as is (double') - print(' sided, flipped on the short edge) so that you can take it, cut ') - print(' in half and close it like you would do with a book. The result ') - print(' will be the original pdf in the correct order, with the size of ') - print(' half a page') - print(' Especially useful to print pdfs to A5 size on A4 paper') - print() - print('OPTIONS') - print(' -h print help') - print(' -q do not print anything to stdout') - - def parse_argv(): - opts = { - "h": False, - "q": False, - } - filename = None - - if len(sys.argv) < 2: - opts["h"] = True - - for arg in sys.argv[1:]: - if arg.startswith('-'): - for opt in opts: - if opt in arg: - opts[opt] = True - elif not filename: - filename = arg - - return (opts, filename) - - def info(text = '', end='\n'): - if not opts["q"]: - print(text, end=end) - - def prog(curr, max_val): - # Overrides the current terminal line with a progressbar filled at curr/max_val - perc = float(curr) / float(max_val) - remaining = f'{curr:{len(str(max_val))}d}/{max_val}' - (cols, _) = os.get_terminal_size() - max_width = cols - len(remaining) - 3 - - size = min(max_width, max_val, 80) - bar = int(perc * size) - - end = '\n' if curr == max_val else '' - info("\r|" + ("\u2588" * bar) + "_" * (size - bar) + "| " + remaining, end) - - - (opts, input_name) = parse_argv() - if opts["h"] or not input_name: - print_help() - exit(0) + 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. Default value is 10""") + + 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) @@ -75,47 +53,60 @@ def main(): 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 / float(25.4) + smallblank = PageObject.create_blank_page(width=smallwidth - bindingwidth, height=smallheight) # Read the input reader = PdfReader(input_name) - - # Write the result to a file - middle = PdfWriter() - info("Loading...") n = len(reader.pages) - # Set m as the smallest multiple of 4 bigger or equal to n - m = ceil(n / 4) * 4 + pages = [] + info("Reading...") for i in range(n): - prog(i, m) - middle.add_page(reader.pages[i]) - - for i in range(m - n): - prog(n + i, m) - middle.add_blank_page() - - prog(m, m) + prog(i, n) + pages.append(reader.pages[i]) + prog(n,n) + + while(len(pages) % 4 != 0): + pages.append(smallblank) + output = PdfWriter() - info() - info("Rearranging...") - for i in range(int(m / 4)): - mod = i * 2 - prog(i * 4, m) - output.add_page(middle.pages[m - 1 - mod]) - prog(i * 4 + 1, m) - output.add_page(middle.pages[mod]) - prog(i * 4 + 2, m) - output.add_page(middle.pages[1 + mod]) - prog(i * 4 + 3, m) - output.add_page(middle.pages[m - 2 - mod]) - prog(m, m) - - info() - info("Saving...") + info("\nRearranging...") + + def merge_srt_page(base, over, angle, tx, ty): + sx = float(smallwidth - bindingwidth) / float(over.mediabox.width) + sy = float(smallheight) / float(over.mediabox.height) + t = Transformation().scale(sx, sy).rotate(angle). translate(tx, ty) + over.add_transformation(t) + base.merge_page(over) + + 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: + merge_srt_page(new_page, pages[m - 1 - i], -90, 0, fullheight) + merge_srt_page(new_page, pages[i], - 90, 0, smallwidth - bindingwidth) + pass + else: + merge_srt_page(new_page, pages[m - 1 - i], 90, smallheight, smallwidth + bindingwidth) + merge_srt_page(new_page, pages[i], 90, smallheight, 0) + + output.add_page(new_page) + prog(int(m/2), int(m/2)) + + info("\nSaving...") output.write(output_name) - info("Done!") - info() + info("\nDone!") if __name__ == "__main__": main() + +