From: Christian Heller Date: Sun, 24 Sep 2023 20:55:10 +0000 (+0200) Subject: Bookmaker: refactor cropping code. X-Git-Url: https://plomlompom.com/repos/%7B%7B%20web_path%20%7D%7D/decks/%7B%7Bdb.prefix%7D%7D/static/blog?a=commitdiff_plain;h=298bd34592ff3dbe2f670931326b08ef0d508839;p=misc Bookmaker: refactor cropping code. --- diff --git a/bookmaker.py b/bookmaker.py index dc4b1cb..3335132 100755 --- a/bookmaker.py +++ b/bookmaker.py @@ -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: