home · contact · privacy
Enforce code style of max 80 chars line width, consistent quotes choice.
authorPlom Heller <plom@plomlompom.com>
Sat, 4 Apr 2026 05:08:42 +0000 (07:08 +0200)
committerPlom Heller <plom@plomlompom.com>
Sat, 4 Apr 2026 05:08:42 +0000 (07:08 +0200)
bookmaker.py

index d524783abb562e3ed58a75fdadacb1af9a1ee7f8..ce6260d8748b16e9a636bf6295eadcfad2c18ff9 100755 (executable)
@@ -1,20 +1,24 @@
 #!/usr/bin/env python3
 """
-bookmaker.py is a helper for optimizing PDFs for the production of small self-printed, self-bound physical books.  Towards this goal it offers various PDF manipulation options that may also be used indepéndently and for other purposes.
+bookmaker.py helps optimizing PDFs for the production of small self-printed,
+self-bound physical books; towards this goal offers various PDF manipulation
+options that may also be used indepéndently and for other purposes.
 """
 import argparse
 import io
 import os
 import sys
 
+
 def handled_error_exit(msg):
-    print(f"ERROR: {msg}")
+    print(f'ERROR: {msg}')
     sys.exit(1)
 
+
 try:
     import pypdf
 except ImportError:
-    handled_error_exit("Can't run at all without pypdf installed.")
+    handled_error_exit('Can\'t run at all without pypdf installed.')
 
 # some general paper geometry constants
 POINTS_PER_CM = 10 * 72 / 25.4
@@ -30,7 +34,7 @@ CUT_WIDTH = 1.05 * POINTS_PER_CM
 MIDDLE_POINT_DEPTH = 0.4 * POINTS_PER_CM
 INNER_SPINE_MARGIN_PER_PAGE = 1 * POINTS_PER_CM
 QUARTER_SCALE_FACTOR = 0.5
-PAGE_ORDER_FOR_NUP4 = (3,0,7,4,1,2,5,6)
+PAGE_ORDER_FOR_NUP4 = (3, 0, 7, 4, 1, 2, 5, 6)
 
 
 class PageCrop:
@@ -46,8 +50,10 @@ class PageCrop:
         self.top = float(self.top_cm) * POINTS_PER_CM
         zoom_horizontal = A4_WIDTH / (A4_WIDTH - self.left - self.right)
         zoom_vertical = A4_HEIGHT / (A4_HEIGHT - self.bottom - self.top)
-        if (zoom_horizontal > 1 and zoom_vertical < 1) or (zoom_horizontal < 1 and zoom_vertical > 1):
-            raise HandledException("-c: crops would create opposing zoom directions")
+        if (zoom_horizontal > 1 and zoom_vertical < 1)\
+                or (zoom_horizontal < 1 and zoom_vertical > 1):
+            raise HandledException(
+                    '-c: crops would create opposing zoom directions')
         elif zoom_horizontal + zoom_vertical > 2:
             self.zoom = min(zoom_horizontal, zoom_vertical)
         else:
@@ -58,7 +64,8 @@ class PageCrop:
 
     @property
     def format_in_cm(self):
-        return f"left {self.left_cm}cm, bottom {self.bottom_cm}cm, right {self.right_cm}cm, top {self.top_cm}cm"
+        return (f'left {self.left_cm}cm, bottom {self.bottom_cm}cm, '
+                f'right {self.right_cm}cm, top {self.top_cm}cm')
 
     @property
     def remaining_width(self):
@@ -69,7 +76,10 @@ class PageCrop:
         return A4_HEIGHT - self.bottom - self.top
 
     def give_mirror(self):
-        return PageCrop(left_cm=self.right_cm, bottom_cm=self.bottom_cm, right_cm=self.left_cm, top_cm=self.top_cm)
+        return PageCrop(left_cm=self.right_cm,
+                        bottom_cm=self.bottom_cm,
+                        right_cm=self.left_cm,
+                        top_cm=self.top_cm)
 
 
 class Nup4Geometry:
@@ -77,8 +87,10 @@ class Nup4Geometry:
     def __init__(self, margin_cm):
         self.margin = margin_cm * POINTS_PER_CM
         self.shrink_for_margin = (A4_WIDTH - 2 * self.margin)/A4_WIDTH
-        # NB: We define spine size un-shrunk, but .shrink_for_spine is used with values shrunk for the margin, which we undo here.
-        spine_part_of_page = (INNER_SPINE_MARGIN_PER_PAGE / A4_HALF_WIDTH) / self.shrink_for_margin
+        # NB: We define spine size un-shrunk, but .shrink_for_spine is used
+        # with values shrunk for the margin, which we undo here.
+        spine_part_of_page = ((INNER_SPINE_MARGIN_PER_PAGE / A4_HALF_WIDTH)
+                              / self.shrink_for_margin)
         self.shrink_for_spine = 1 - spine_part_of_page
 
 
@@ -87,85 +99,138 @@ class HandledException(Exception):
 
 
 def parse_args():
-    help_epilogue = "See README.txt for detailed usage instructions, command examples, etc."
-    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("-k", "--keep_mediabox", action="store_true", help="keep non-A4 page shapes")
-    parser.add_argument("-m", "--print_margin", type=float, default=0.43, help="print margin for --nup4 in cm (default 0.43)")
+    help_epilogue = ('See README.txt for detailed usage instructions, '
+                     'command examples, etc.')
+    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('-k',
+                        '--keep_mediabox',
+                        action='store_true',
+                        help='keep non-A4 page shapes')
+    parser.add_argument('-m',
+                        '--print_margin',
+                        type=float,
+                        default=0.43,
+                        help='print margin for --nup4 in cm (default 0.43)')
     return parser.parse_args()
 
 
 def validate_inputs_first_pass(args):
     for filename in args.input_file:
         if not os.path.isfile(filename):
-            raise HandledException(f"-i: {filename} is not a file")
+            raise HandledException(f'-i: {filename} is not a file')
         try:
             with open(filename, 'rb') as file:
                 pypdf.PdfReader(file)
         except pypdf.errors.PdfStreamError:
-            raise HandledException(f"-i: cannot interpret {filename} as PDF file")
+            raise HandledException(
+                    f'-i: cannot interpret {filename} as PDF file')
     if args.page_range:
         for p_string in args.page_range:
-            validate_page_range(p_string, "-p")
+            validate_page_range(p_string, '-p')
         if len(args.page_range) > len(args.input_file):
-            raise HandledException("-p: more --page_range arguments than --input_file arguments")
+            raise HandledException(
+                '-p: more --page_range arguments than --input_file arguments')
     if args.crops:
         for c_string in args.crops:
             initial_split = c_string.split(':')
             if len(initial_split) > 2:
-                raise HandledException(f"-c: cropping string has multiple ':': {c_string}")
+                raise HandledException(
+                        f'-c: cropping string has multiple ":": {c_string}')
             page_range, crops = split_crops_string(c_string)
-            crops = crops.split(",")
+            crops = crops.split(',')
             if page_range:
-                validate_page_range(page_range, "-c")
+                validate_page_range(page_range, '-c')
             if len(crops) != 4:
-                raise HandledException(f"-c: cropping does not contain exactly three ',': {c_string}")
+                raise HandledException(
+                        '-c: cropping does not contain exactly three ",": '
+                        + c_string)
             for crop in crops:
                 try:
                     float(crop)
                 except ValueError:
-                    raise HandledException(f"-c: non-number crop in: {c_string}")
+                    raise HandledException(
+                            f'-c: non-number crop in: {c_string}')
     if args.rotate_page:
         for r in args.rotate_page:
             try:
                 int(r)
             except ValueError:
-                raise HandledException(f"-r: non-integer value: {r}")
+                raise HandledException(f'-r: non-integer value: {r}')
             if r < 1:
-                raise HandledException(f"-r: value must not be <1: {r}")
+                raise HandledException(f'-r: value must not be <1: {r}')
     try:
         float(args.print_margin)
     except ValueError:
-        raise HandledException(f"-m: non-float value: {arg.print_margin}")
+        raise HandledException(f'-m: non-float value: {args.print_margin}')
 
 
 def validate_page_range(p_string, err_msg_prefix):
-    prefix = f"{err_msg_prefix}: page range string"
+    prefix = f'{err_msg_prefix}: page range string'
     if '-' not in p_string:
-        raise HandledException(f"{prefix} lacks '-': {p_string}")
-    tokens = p_string.split("-")
+        raise HandledException(f'{prefix} lacks "-": {p_string}')
+    tokens = p_string.split('-')
     if len(tokens) > 2:
-        raise HandledException(f"{prefix} has too many '-': {p_string}")
+        raise HandledException(f'{prefix} has too many "-": {p_string}')
     for i, token in enumerate(tokens):
-        if token == "":
+        if token == '':
             continue
-        if i == 0 and token == "start":
+        if i == 0 and token == 'start':
             continue
-        if i == 1 and token == "end":
+        if i == 1 and token == 'end':
             continue
         try:
             int(token)
         except ValueError:
-            raise HandledException(f"{prefix} carries value neither integer, nor 'start', nor 'end': {p_string}")
+            raise HandledException(
+                    f'{prefix} carries value neither integer, '
+                    f'nor "start", nor "end": {p_string}')
         if int(token) < 1:
-            raise HandledException(f"{prefix} carries page number <1: {p_string}")
+            raise HandledException(
+                    f'{prefix} carries page number <1: {p_string}')
     start = -1
     end = -1
     try:
@@ -174,7 +239,8 @@ def validate_page_range(p_string, err_msg_prefix):
     except ValueError:
         pass
     if start > 0 and end > 0 and start > end:
-        raise HandledException(f"{prefix} has higher start than end value: {p_string}")
+        raise HandledException(
+                f'{prefix} has higher start than end value: {p_string}')
 
 
 def split_crops_string(c_string):
@@ -193,9 +259,9 @@ def parse_page_range(range_string, pages):
     end_page = len(pages)
     if range_string:
         start, end = range_string.split('-')
-        if not (len(start) == 0 or start == "start"):
+        if not (len(start) == 0 or start == 'start'):
             start_page = int(start) - 1
-        if not (len(end) == 0 or end == "end"):
+        if not (len(end) == 0 or end == 'ebd'):
             end_page = int(end)
     return start_page, end_page
 
@@ -215,36 +281,43 @@ def read_inputs_to_pagelist(args_input_file, args_page_range):
         for old_page_num in range(start_page, end_page):
             new_page_num += 1
             if old_page_num >= len(reader.pages):
-                page = pypdf.PageObject.create_blank_page(width=A4_WIDTH, height=A4_HEIGHT)
+                page = pypdf.PageObject.create_blank_page(width=A4_WIDTH,
+                                                          height=A4_HEIGHT)
             else:
                 page = reader.pages[old_page_num]
             pages_to_add += [page]
-            print(f"-i, -p: read in {input_file} page number {old_page_num+1} as new page {new_page_num}")
+            print(f'-i, -p: read in {input_file} page number {old_page_num+1} '
+                  f'as new page {new_page_num}')
     return pages_to_add, opened_files
 
 
 def validate_inputs_second_pass(args, pages_to_add):
     if args.crops:
         for c_string in args.crops:
-            page_range, _= split_crops_string(c_string)
+            page_range, _ = split_crops_string(c_string)
             if page_range:
                 start, end = parse_page_range(page_range, pages_to_add)
                 if end > len(pages_to_add):
-                     raise HandledException(f"-c: page range goes beyond number of pages we're building: {page_range}")
+                    raise HandledException(
+                            f'-c: page range goes beyond number of pages '
+                            f'we\'re building: {page_range}')
     if args.rotate_page:
         for r in args.rotate_page:
             if r > len(pages_to_add):
-                 raise HandledException(f"-r: page number beyond number of pages we're building: {r}")
+                raise HandledException('-r: page number beyond number of '
+                                       f'pages we\'re building: {r}')
 
 
 def rotate_pages(args_rotate_page, pages_to_add):
     if args_rotate_page:
         for rotate_page in args_rotate_page:
             page = pages_to_add[rotate_page - 1]
-            page.add_transformation(pypdf.Transformation().translate(tx=-A4_WIDTH/2, ty=-A4_HEIGHT/2))
+            page.add_transformation(pypdf.Transformation().translate(
+                tx=-A4_WIDTH/2, ty=-A4_HEIGHT/2))
             page.add_transformation(pypdf.Transformation().rotate(-90))
-            page.add_transformation(pypdf.Transformation().translate(tx=A4_WIDTH/2, ty=A4_HEIGHT/2))
-            print(f"-r: rotating (by 90°) page {rotate_page}")
+            page.add_transformation(pypdf.Transformation().translate(
+                tx=A4_WIDTH/2, ty=A4_HEIGHT/2))
+            print(f'-r: rotating (by 90°) page {rotate_page}')
 
 
 def pad_pages_to_multiple_of_8(pages_to_add):
@@ -252,15 +325,17 @@ def pad_pages_to_multiple_of_8(pages_to_add):
     if mod_to_8 > 0:
         old_len = len(pages_to_add)
         for _ in range(8 - mod_to_8):
-            new_page = pypdf.PageObject.create_blank_page(width=A4_WIDTH, height=A4_HEIGHT)
+            new_page = pypdf.PageObject.create_blank_page(
+                    width=A4_WIDTH, height=A4_HEIGHT)
             pages_to_add += [new_page]
-        print(f"-n: number of input pages {old_len} not required multiple of 8, padded to {len(pages_to_add)}")
+        print(f'-n: number of input pages {old_len} not required multiple '
+              f'of 8, padded to {len(pages_to_add)}')
 
 
-def normalize_pages_to_A4(pages_to_add):
+def normalize_pages_to_a4(pages_to_add):
     for page in pages_to_add:
-        if "/Rotate" in page:  # TODO: preserve rotation, but in canvas?
-            page.rotate(360 - page["/Rotate"])
+        if '/Rotate' in page:  # TODO: preserve rotation, but in canvas?
+            page.rotate(360 - page['/Rotate'])
         page.mediabox.left = 0
         page.mediabox.bottom = 0
         page.mediabox.top = A4_HEIGHT
@@ -268,16 +343,21 @@ def normalize_pages_to_A4(pages_to_add):
         page.cropbox = page.mediabox
 
 
-def collect_per_page_crops_and_zooms(args_crops, args_keep_mediabox, args_symmetry, pages_to_add):
+def collect_per_page_crops_and_zooms(args_crops,
+                                     args_keep_mediabox,
+                                     args_symmetry,
+                                     pages_to_add):
     crop_at_page = [PageCrop()] * len(pages_to_add)
     if args_crops:
         for c_string in args_crops:
             page_range, crops = split_crops_string(c_string)
             start_page, end_page = parse_page_range(page_range, pages_to_add)
-            prefix = "-c, -t" if args_symmetry else "-c"
-            suffix = " (but alternating left and right crop between even and odd pages)" if args_symmetry else ""
+            prefix = '-c, -t' if args_symmetry else '-c'
+            suffix = (' (but alternating left and right crop '
+                      'between even and odd pages)') if args_symmetry else ''
             page_crop = PageCrop(*[x for x in crops.split(',')])
-            print(f"{prefix}: to pages {start_page + 1} to {end_page} applying crop: {page_crop.format_in_cm}{suffix}")
+            print(f'{prefix}: to pages {start_page + 1} to {end_page} '
+                  f'applying crop: {page_crop.format_in_cm}{suffix}')
             for page_num in range(start_page, end_page):
                 if args_symmetry and page_num % 2:
                     crop_at_page[page_num] = page_crop.give_mirror()
@@ -285,33 +365,43 @@ def collect_per_page_crops_and_zooms(args_crops, args_keep_mediabox, args_symmet
                     crop_at_page[page_num] = page_crop
     elif args_keep_mediabox:
         for page_num, page in enumerate(pages_to_add):
-            crop_at_page[page_num] = PageCrop(page.mediabox.left / POINTS_PER_CM,
-                                              page.mediabox.bottom / POINTS_PER_CM,
-                                              (0.01 + A4_WIDTH - page.mediabox.right) / POINTS_PER_CM,
-                                              (0.01 + A4_HEIGHT - page.mediabox.top) / POINTS_PER_CM)
+            crop_at_page[page_num] = PageCrop(
+                    page.mediabox.left / POINTS_PER_CM,
+                    page.mediabox.bottom / POINTS_PER_CM,
+                    (0.01 + A4_WIDTH - page.mediabox.right) / POINTS_PER_CM,
+                    (0.01 + A4_HEIGHT - page.mediabox.top) / POINTS_PER_CM)
             if args_symmetry and not page_num % 2:
                 crop_at_page[page_num] = crop_at_page[page_num].give_mirror()
     return crop_at_page
 
 
 def build_single_pages_output(writer, pages_to_add, crop_at_page):
-    print("building 1-input-page-per-output-page book")
+    print('building 1-input-page-per-output-page book')
     odd_page = True
     for i, page in enumerate(pages_to_add):
-        page.add_transformation(pypdf.Transformation().translate(tx=-crop_at_page[i].left, ty=-crop_at_page[i].bottom))
-        page.add_transformation(pypdf.Transformation().scale(crop_at_page[i].zoom, crop_at_page[i].zoom))
-        page.mediabox.right = crop_at_page[i].remaining_width * crop_at_page[i].zoom
-        page.mediabox.top = crop_at_page[i].remaining_height * crop_at_page[i].zoom
+        page.add_transformation(pypdf.Transformation().translate(
+            tx=-crop_at_page[i].left, ty=-crop_at_page[i].bottom))
+        page.add_transformation(pypdf.Transformation().scale(
+            crop_at_page[i].zoom, crop_at_page[i].zoom))
+        page.mediabox.right\
+            = crop_at_page[i].remaining_width * crop_at_page[i].zoom
+        page.mediabox.top\
+            = crop_at_page[i].remaining_height * crop_at_page[i].zoom
         writer.add_page(page)
         odd_page = not odd_page
-        print(f"built page number {i+1} (of {len(pages_to_add)})")
+        print(f'built page number {i+1} (of {len(pages_to_add)})')
 
 
-def build_nup4_output(writer, pages_to_add, crop_at_page, args_print_margin, args_analyze, canvas_class):
-    print("-n: building 4-input-pages-per-output-page book")
-    print(f"-m: applying printable-area margin of {args_print_margin}cm")
+def build_nup4_output(writer,
+                      pages_to_add,
+                      crop_at_page,
+                      args_print_margin,
+                      args_analyze,
+                      canvas_class):
+    print('-n: building 4-input-pages-per-output-page book')
+    print(f'-m: applying printable-area margin of {args_print_margin}cm')
     if args_analyze:
-        print("-a: drawing page borders, spine limits")
+        print('-a: drawing page borders, spine limits')
     nup4_geometry = Nup4Geometry(args_print_margin)
     pages_to_add, new_i_order = resort_pages_for_nup4(pages_to_add)
     nup4_i = 0
@@ -319,16 +409,23 @@ def build_nup4_output(writer, pages_to_add, crop_at_page, args_print_margin, arg
     is_front_page = True
     for i, page in enumerate(pages_to_add):
         if nup4_i == 0:
-            new_page = pypdf.PageObject.create_blank_page(width=A4_WIDTH, height=A4_HEIGHT)
+            new_page = pypdf.PageObject.create_blank_page(
+                    width=A4_WIDTH, height=A4_HEIGHT)
         corrected_i = new_i_order[i]
-        nup4_inner_page_transform(page, crop_at_page[corrected_i], nup4_geometry, nup4_i)
+        nup4_inner_page_transform(
+                page, crop_at_page[corrected_i], nup4_geometry, nup4_i)
         nup4_outer_page_transform(page, nup4_geometry, nup4_i)
         new_page.merge_page(page)
         page_count += 1
-        print(f"merged page number {page_count} (of {len(pages_to_add)})")
+        print(f'merged page number {page_count} (of {len(pages_to_add)})')
         nup4_i += 1
         if nup4_i > 3:
-            ornate_nup4(writer, args_analyze, is_front_page, new_page, nup4_geometry, canvas_class)
+            ornate_nup4(writer,
+                        args_analyze,
+                        is_front_page,
+                        new_page,
+                        nup4_geometry,
+                        canvas_class)
             writer.add_page(new_page)
             nup4_i = 0
             is_front_page = not is_front_page
@@ -355,40 +452,55 @@ def resort_pages_for_nup4(pages_to_add):
 
 
 def nup4_inner_page_transform(page, crop, nup4_geometry, nup4_i):
-    page.add_transformation(pypdf.Transformation().translate(ty=(A4_HEIGHT / crop.zoom - (A4_HEIGHT - crop.top))))
+    page.add_transformation(pypdf.Transformation().translate(
+        ty=A4_HEIGHT / crop.zoom - (A4_HEIGHT - crop.top)))
     if nup4_i == 0 or nup4_i == 2:
-        page.add_transformation(pypdf.Transformation().translate(tx=-crop.left))
+        page.add_transformation(pypdf.Transformation().translate(
+            tx=-crop.left))
     elif nup4_i == 1 or nup4_i == 3:
-        page.add_transformation(pypdf.Transformation().translate(tx=(A4_WIDTH / crop.zoom - (A4_WIDTH - crop.right))))
-    page.add_transformation(pypdf.Transformation().scale(crop.zoom * nup4_geometry.shrink_for_spine, crop.zoom * nup4_geometry.shrink_for_spine))
+        page.add_transformation(pypdf.Transformation().translate(
+            tx=A4_WIDTH / crop.zoom - (A4_WIDTH - crop.right)))
+    page.add_transformation(pypdf.Transformation().scale(
+        crop.zoom * nup4_geometry.shrink_for_spine,
+        crop.zoom * nup4_geometry.shrink_for_spine))
     if nup4_i == 2 or nup4_i == 3:
-        page.add_transformation(pypdf.Transformation().translate(ty=-2*nup4_geometry.margin/nup4_geometry.shrink_for_margin))
+        page.add_transformation(pypdf.Transformation().translate(
+            ty=-2*nup4_geometry.margin/nup4_geometry.shrink_for_margin))
 
 
 def nup4_outer_page_transform(page, nup4_geometry, nup4_i):
-    page.add_transformation(pypdf.Transformation().translate(ty=(1-nup4_geometry.shrink_for_spine)*A4_HEIGHT))
+    page.add_transformation(pypdf.Transformation().translate(
+        ty=(1-nup4_geometry.shrink_for_spine)*A4_HEIGHT))
     if nup4_i == 0 or nup4_i == 1:
         y_section = A4_HEIGHT
         page.mediabox.bottom = A4_HALF_HEIGHT
-        page.mediabox.top    = A4_HEIGHT
+        page.mediabox.top = A4_HEIGHT
     if nup4_i == 2 or nup4_i == 3:
         y_section = 0
         page.mediabox.bottom = 0
-        page.mediabox.top  =  A4_HALF_HEIGHT
+        page.mediabox.top = A4_HALF_HEIGHT
     if nup4_i == 0 or nup4_i == 2:
         x_section = 0
-        page.mediabox.left   = 0
-        page.mediabox.right  = A4_HALF_WIDTH
+        page.mediabox.left = 0
+        page.mediabox.right = A4_HALF_WIDTH
     if nup4_i == 1 or nup4_i == 3:
-        page.add_transformation(pypdf.Transformation().translate(tx=(1-nup4_geometry.shrink_for_spine)*A4_WIDTH))
+        page.add_transformation(pypdf.Transformation().translate(
+            tx=(1-nup4_geometry.shrink_for_spine)*A4_WIDTH))
         x_section = A4_WIDTH
-        page.mediabox.left   = A4_HALF_WIDTH
-        page.mediabox.right  = A4_WIDTH
-    page.add_transformation(pypdf.Transformation().translate(tx=x_section, ty=y_section))
-    page.add_transformation(pypdf.Transformation().scale(QUARTER_SCALE_FACTOR, QUARTER_SCALE_FACTOR))
-
-
-def ornate_nup4(writer, args_analyze, is_front_page, new_page, nup4_geometry, canvas_class):
+        page.mediabox.left = A4_HALF_WIDTH
+        page.mediabox.right = A4_WIDTH
+    page.add_transformation(pypdf.Transformation().translate(
+        tx=x_section, ty=y_section))
+    page.add_transformation(pypdf.Transformation().scale(QUARTER_SCALE_FACTOR,
+                                                         QUARTER_SCALE_FACTOR))
+
+
+def ornate_nup4(writer,
+                args_analyze,
+                is_front_page,
+                new_page,
+                nup4_geometry,
+                canvas_class):
     if args_analyze:
         # borders
         packet = io.BytesIO()
@@ -405,8 +517,10 @@ def ornate_nup4(writer, args_analyze, is_front_page, new_page, nup4_geometry, ca
         new_page.merge_page(new_pdf.pages[0])
     printable_offset_x = nup4_geometry.margin
     printable_offset_y = nup4_geometry.margin * A4_HEIGHT / A4_WIDTH
-    new_page.add_transformation(pypdf.Transformation().scale(nup4_geometry.shrink_for_margin, nup4_geometry.shrink_for_margin))
-    new_page.add_transformation(pypdf.Transformation().translate(tx=printable_offset_x, ty=printable_offset_y))
+    new_page.add_transformation(pypdf.Transformation().scale(
+        nup4_geometry.shrink_for_margin, nup4_geometry.shrink_for_margin))
+    new_page.add_transformation(pypdf.Transformation().translate(
+        tx=printable_offset_x, ty=printable_offset_y))
     x_left_spine_limit = A4_HALF_WIDTH * nup4_geometry.shrink_for_spine
     x_right_spine_limit = A4_WIDTH - x_left_spine_limit
     if args_analyze or is_front_page:
@@ -430,8 +544,8 @@ def ornate_nup4(writer, args_analyze, is_front_page, new_page, nup4_geometry, ca
 def draw_cut(canvas, x_spine_limit, direction):
     outer_start_x = x_spine_limit - 0.5 * CUT_WIDTH * direction
     inner_start_x = x_spine_limit + 0.5 * CUT_WIDTH * direction
-    middle_point_y =  A4_HALF_HEIGHT + MIDDLE_POINT_DEPTH * direction
-    end_point_y =  A4_HALF_HEIGHT + CUT_DEPTH * direction
+    middle_point_y = A4_HALF_HEIGHT + MIDDLE_POINT_DEPTH * direction
+    end_point_y = A4_HALF_HEIGHT + CUT_DEPTH * direction
     canvas.line(inner_start_x, A4_HALF_HEIGHT, x_spine_limit, end_point_y)
     canvas.line(x_spine_limit, end_point_y, x_spine_limit, middle_point_y)
     canvas.line(x_spine_limit, middle_point_y, outer_start_x, A4_HALF_HEIGHT)
@@ -444,18 +558,26 @@ def main():
         try:
             from reportlab.pdfgen.canvas import Canvas
         except ImportError:
-            raise HandledException("-n: need reportlab.pdfgen.canvas installed for --nup4")
-    pages_to_add, opened_files = read_inputs_to_pagelist(args.input_file, args.page_range)
+            raise HandledException(
+                    '-n: need reportlab.pdfgen.canvas installed for --nup4')
+    pages_to_add, opened_files = read_inputs_to_pagelist(args.input_file,
+                                                         args.page_range)
     validate_inputs_second_pass(args, pages_to_add)
     rotate_pages(args.rotate_page, pages_to_add)
     if args.nup4:
         pad_pages_to_multiple_of_8(pages_to_add)
     if not args.keep_mediabox:
-        normalize_pages_to_A4(pages_to_add)
-    crop_at_page = collect_per_page_crops_and_zooms(args.crops, args.keep_mediabox, args.symmetry, pages_to_add)
+        normalize_pages_to_a4(pages_to_add)
+    crop_at_page = collect_per_page_crops_and_zooms(
+            args.crops, args.keep_mediabox, args.symmetry, pages_to_add)
     writer = pypdf.PdfWriter()
     if args.nup4:
-        build_nup4_output(writer, pages_to_add, crop_at_page, args.print_margin, args.analyze, Canvas)
+        build_nup4_output(writer,
+                          pages_to_add,
+                          crop_at_page,
+                          args.print_margin,
+                          args.analyze,
+                          Canvas)
     else:
         build_single_pages_output(writer, pages_to_add, crop_at_page)
     for file in opened_files:
@@ -464,7 +586,7 @@ def main():
         writer.write(output_file)
 
 
-if __name__ == "__main__":
+if __name__ == '__main__':
     try:
         main()
     except HandledException as e: