home · contact · privacy
Bookmaker: refactor cropping code.
authorChristian Heller <c.heller@plomlompom.de>
Sun, 24 Sep 2023 20:55:10 +0000 (22:55 +0200)
committerChristian Heller <c.heller@plomlompom.de>
Sun, 24 Sep 2023 20:55:10 +0000 (22:55 +0200)
bookmaker.py

index dc4b1cbc50c2b03af5f7d61a52dc3bdb4f82ed0e..33351329855601e48cd0c16321b5f2cf537a2bd9 100755 (executable)
@@ -63,6 +63,7 @@ import argparse
 import io
 import os
 import sys
+from collections import namedtuple
 
 def handled_error_exit(msg):
     print(f"ERROR: {msg}")
@@ -90,6 +91,8 @@ QUARTER_SCALE_FACTOR = 0.5
 PAGE_ORDER_FOR_NUP4 = (3,0,7,4,1,2,5,6)
 
 # some helpers
+PageCrop = namedtuple("PageCrop", ["left", "bottom", "right", "top"], defaults=[0,0,0,0])
+
 class HandledException(Exception):
     pass
 
@@ -263,25 +266,23 @@ def normalize_pages_to_A4(pages_to_add):
         page.cropbox = page.mediabox
 
 def collect_per_page_crops_and_zooms(args_crops, args_symmetry, pages_to_add):
-    crops_at_page = [(0,0,0,0)]*len(pages_to_add)
+    crop_at_page = [PageCrop()] * len(pages_to_add)
     zoom_at_page = [1]*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)
-            crop_left_cm, crop_bottom_cm, crop_right_cm, crop_top_cm = [float(x) for x in  crops.split(',')]
-            crop_left = crop_left_cm * POINTS_PER_CM
-            crop_bottom = crop_bottom_cm * POINTS_PER_CM
-            crop_right = crop_right_cm * POINTS_PER_CM
-            crop_top = crop_top_cm * POINTS_PER_CM
             prefix = "-c, -t" if args_symmetry else "-c"
             suffix = " (but alternating left and right crop between even and odd pages)" if args_symmetry else ""
-            print(f"{prefix}: to pages {start_page + 1} to {end_page} applying crops: left {crop_left_cm}cm, bottom {crop_bottom_cm}cm, right {crop_right_cm}cm, top {crop_top_cm}cm{suffix}")
-            cropped_width  = A4_WIDTH - crop_left - crop_right
-            cropped_height = A4_HEIGHT - crop_bottom - crop_top
+            page_crop_cm = PageCrop(*[x for x in crops.split(',')])
+            page_crop = PageCrop(*[float(x) * POINTS_PER_CM for x in page_crop_cm])
+            crop_listing = ", ".join([f"{key} {val}cm" for key, val in page_crop_cm._asdict().items()])
+            print(f"{prefix}: to pages {start_page + 1} to {end_page} applying crop: {crop_listing}{suffix}")
+            cropped_width  = A4_WIDTH - page_crop.left - page_crop.right
+            cropped_height = A4_HEIGHT - page_crop.bottom - page_crop.top
             zoom = 1
-            zoom_horizontal = A4_WIDTH / (A4_WIDTH - crop_left - crop_right)
-            zoom_vertical = A4_HEIGHT / (A4_HEIGHT - crop_bottom - crop_top)
+            zoom_horizontal = A4_WIDTH / (A4_WIDTH - page_crop.left - page_crop.right)
+            zoom_vertical = A4_HEIGHT / (A4_HEIGHT - page_crop.bottom - page_crop.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")
             elif zoom_horizontal + zoom_vertical > 2:
@@ -290,28 +291,56 @@ def collect_per_page_crops_and_zooms(args_crops, args_symmetry, pages_to_add):
                 zoom = max(zoom_horizontal, zoom_vertical)
             for page_num in range(start_page, end_page):
                 if args_symmetry and page_num % 2:
-                    crops_at_page[page_num] = (crop_right, crop_bottom, crop_left, crop_top)
+                    crop_at_page[page_num] = PageCrop(left=page_crop.right, right=page_crop.left, bottom=page_crop.bottom, top=page_crop.top)
                 else:
-                    crops_at_page[page_num] = (crop_left, crop_bottom, crop_right, crop_top)
+                    crop_at_page[page_num] = page_crop
                 zoom_at_page[page_num] = zoom
-    return crops_at_page, zoom_at_page
+    return crop_at_page, zoom_at_page
 
-def build_single_pages_output(writer, pages_to_add, crops_at_page, zoom_at_page):
+def build_single_pages_output(writer, pages_to_add, crop_at_page, zoom_at_page):
     print("building 1-input-page-per-output-page book")
     odd_page = True
     for i, page in enumerate(pages_to_add):
-        crop_left, crop_bottom, crop_right, crop_top = crops_at_page[i]
         zoom = zoom_at_page[i]
-        page.add_transformation(pypdf.Transformation().translate(tx=-crop_left, ty=-crop_bottom))
+        page.add_transformation(pypdf.Transformation().translate(tx=-crop_at_page[i].left, ty=-crop_at_page[i].bottom))
         page.add_transformation(pypdf.Transformation().scale(zoom, zoom))
-        cropped_width  = A4_WIDTH - crop_left - crop_right
-        cropped_height = A4_HEIGHT - crop_bottom - crop_top
+        cropped_width  = A4_WIDTH - crop_at_page[i].left - crop_at_page[i].right
+        cropped_height = A4_HEIGHT - crop_at_page[i].bottom - crop_at_page[i].top
         page.mediabox.right = cropped_width * zoom
         page.mediabox.top = cropped_height * zoom
         writer.add_page(page)
         odd_page = not odd_page
         print(f"built page number {i+1} (of {len(pages_to_add)})")
 
+def build_nup4_output(writer, pages_to_add, crop_at_page, zoom_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")
+    printable_margin = args_print_margin * POINTS_PER_CM
+    printable_scale = (A4_WIDTH - 2 * printable_margin)/A4_WIDTH
+    spine_part_of_page = (SPINE_LIMIT / A4_HALF_WIDTH) / printable_scale
+    bonus_shrink_factor = 1 - spine_part_of_page
+    pages_to_add, new_i_order = resort_pages_for_nup4(pages_to_add)
+    nup4_position = 0
+    page_count = 0
+    is_front_page = True
+    for i, page in enumerate(pages_to_add):
+        if nup4_position == 0:
+            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], zoom_at_page[corrected_i], bonus_shrink_factor, printable_margin, printable_scale, nup4_position)
+        nup4_outer_page_transform(page, bonus_shrink_factor, nup4_position)
+        new_page.merge_page(page)
+        page_count += 1
+        print(f"merged page number {page_count} (of {len(pages_to_add)})")
+        nup4_position += 1
+        if nup4_position > 3:
+            ornate_nup4(writer, args_analyze, is_front_page, new_page, printable_margin, printable_scale, bonus_shrink_factor, canvas_class)
+            writer.add_page(new_page)
+            nup4_position = 0
+            is_front_page = not is_front_page
+
 def resort_pages_for_nup4(pages_to_add):
     new_page_order = []
     new_i_order = []
@@ -331,13 +360,12 @@ def resort_pages_for_nup4(pages_to_add):
             n_eights += 1
     return new_page_order, new_i_order
 
-def nup4_inner_page_transform(page, crops, zoom, bonus_shrink_factor, printable_margin, printable_scale, nup4_position):
-    crop_left, crop_bottom, crop_right, crop_top = crops
-    page.add_transformation(pypdf.Transformation().translate(ty=(A4_HEIGHT / zoom - (A4_HEIGHT - crop_top))))
+def nup4_inner_page_transform(page, crop, zoom, bonus_shrink_factor, printable_margin, printable_scale, nup4_position):
+    page.add_transformation(pypdf.Transformation().translate(ty=(A4_HEIGHT / zoom - (A4_HEIGHT - crop.top))))
     if nup4_position == 0 or nup4_position == 2:
-        page.add_transformation(pypdf.Transformation().translate(tx=-crop_left))
+        page.add_transformation(pypdf.Transformation().translate(tx=-crop.left))
     elif nup4_position == 1 or nup4_position == 3:
-        page.add_transformation(pypdf.Transformation().translate(tx=(A4_WIDTH / zoom - (A4_WIDTH - crop_right))))
+        page.add_transformation(pypdf.Transformation().translate(tx=(A4_WIDTH / zoom - (A4_WIDTH - crop.right))))
     page.add_transformation(pypdf.Transformation().scale(zoom * bonus_shrink_factor, zoom * bonus_shrink_factor))
     if nup4_position == 2 or nup4_position == 3:
         page.add_transformation(pypdf.Transformation().translate(ty=-2*printable_margin/printable_scale))
@@ -425,38 +453,12 @@ def main():
     if args.nup4:
         pad_pages_to_multiple_of_8(pages_to_add)
     normalize_pages_to_A4(pages_to_add)
-    crops_at_page, zoom_at_page = collect_per_page_crops_and_zooms(args.crops, args.symmetry, pages_to_add)
+    crop_at_page, zoom_at_page = collect_per_page_crops_and_zooms(args.crops, args.symmetry, pages_to_add)
     writer = pypdf.PdfWriter()
-    if not args.nup4:
-        build_single_pages_output(writer, pages_to_add, crops_at_page, zoom_at_page)
+    if args.nup4:
+        build_nup4_output(writer, pages_to_add, crop_at_page, zoom_at_page, args.print_margin, args.analyze, Canvas)
     else:
-        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")
-        printable_margin = args.print_margin * POINTS_PER_CM
-        printable_scale = (A4_WIDTH - 2 * printable_margin)/A4_WIDTH
-        spine_part_of_page = (SPINE_LIMIT / A4_HALF_WIDTH) / printable_scale
-        bonus_shrink_factor = 1 - spine_part_of_page
-        pages_to_add, new_i_order = resort_pages_for_nup4(pages_to_add)
-        i = 0
-        page_count = 0
-        is_front_page = True
-        for j, page in enumerate(pages_to_add):
-            if i == 0:
-                new_page = pypdf.PageObject.create_blank_page(width=A4_WIDTH, height=A4_HEIGHT)
-            new_i = new_i_order[j]
-            nup4_inner_page_transform(page, crops_at_page[new_i], zoom_at_page[new_i], bonus_shrink_factor, printable_margin, printable_scale, i)
-            nup4_outer_page_transform(page, bonus_shrink_factor, i)
-            new_page.merge_page(page)
-            page_count += 1
-            print(f"merged page number {page_count} (of {len(pages_to_add)})")
-            i += 1
-            if i > 3:
-                ornate_nup4(writer, args.analyze, is_front_page, new_page, printable_margin, printable_scale, bonus_shrink_factor, Canvas)
-                writer.add_page(new_page)
-                i = 0
-                is_front_page = not is_front_page
+        build_single_pages_output(writer, pages_to_add, crop_at_page, zoom_at_page)
     for file in opened_files:
         file.close()
     with open(args.output_file, 'wb') as output_file: