+ return start_page, end_page
+
+def parse_args():
+ parser = argparse.ArgumentParser(description=__doc__, epilog=help_epilogue, formatter_class=argparse.RawDescriptionHelpFormatter)
+ parser.add_argument("-i", "--input_file", action="append", required=True, help="input PDF file")
+ parser.add_argument("-o", "--output_file", required=True, help="output PDF file")
+ parser.add_argument("-p", "--page_range", action="append", help="page range, e.g., '2-9' or '3-end' or 'start-14'")
+ parser.add_argument("-c", "--crops", action="append", help="cm crops left, bottom, right, top – e.g., '10,10,10,10'; prefix with ':'-delimited page range to limit effect")
+ parser.add_argument("-r", "--rotate_page", type=int, action="append", help="rotate page of number by 90° (usable multiple times on same page!)")
+ parser.add_argument("-s", "--symmetry", action="store_true", help="alternate horizontal crops between odd and even pages")
+ parser.add_argument("-n", "--nup4", action='store_true', help="puts 4 input pages onto 1 output page, adds binding cut stencil")
+ parser.add_argument("-a", "--analyze", action="store_true", help="in --nup4, print lines identifying spine, page borders")
+ parser.add_argument("-m", "--print_margin", type=float, default=0.43, help="print margin for --nup4 in cm (default 0.43)")
+ args = parser.parse_args()
+
+ # some basic input validation
+ for filename in args.input_file:
+ if not os.path.isfile(filename):
+ raise HandledException("-i: %s is not a file" % filename)
+ try:
+ with open(filename, 'rb') as file:
+ pypdf.PdfReader(file)
+ except pypdf.errors.PdfStreamError:
+ raise HandledException("-i: cannot interpret %s as PDF file" % filename)
+ if args.page_range:
+ for p_string in args.page_range:
+ validate_page_range(p_string, "-p")
+ if len(args.page_range) > len(args.input_file):
+ raise HandledException("more -p arguments than -i arguments")
+ if args.crops:
+ for c_string in args.crops:
+ initial_split = c_string.split(':')
+ if len(initial_split) > 2:
+ raise HandledException("-c: cropping string has multiple ':': %s" % c_string)
+ page_range, crops = split_crops_string(c_string)
+ crops = crops.split(",")
+ if page_range:
+ validate_page_range(page_range, "-c")
+ if len(crops) != 4:
+ raise HandledException("-c: cropping should contain three ',': %s" % c_string)
+ for crop in crops:
+ try:
+ float(crop)
+ except ValueError:
+ raise HandledException("-c: non-number crop in %s" % c_string)
+ if args.rotate_page:
+ for r in args.rotate_page:
+ try:
+ int(r)
+ except ValueError:
+ raise HandledException("-r: non-integer value: %s" % r)
+ if r < 1:
+ raise HandledException("-r: value must not be <1: %s" % r)
+ try:
+ float(args.print_margin)
+ except ValueError:
+ raise HandledException("-m: non-float value: %s" % arg.print_margin)
+
+ return args
+
+def main():
+ args = parse_args()
+ if args.nup4:
+ try:
+ import reportlab.pdfgen.canvas
+ except ImportError:
+ raise HandledException("-n: need reportlab library installed for --nup4")
+
+ # select pages from input files