From 37cb1840c626613623a7cee425eedcae910ad1fd Mon Sep 17 00:00:00 2001 From: Christian Heller Date: Wed, 20 Sep 2023 22:08:37 +0200 Subject: [PATCH] Add basic DIY book formatter script. --- bookmaker.py | 208 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 208 insertions(+) create mode 100755 bookmaker.py diff --git a/bookmaker.py b/bookmaker.py new file mode 100755 index 0000000..dd5b2e7 --- /dev/null +++ b/bookmaker.py @@ -0,0 +1,208 @@ +#!/usr/bin/env python3 +import pypdf +import argparse +import io +from reportlab.lib.pagesizes import A4 +a4_width, a4_height = A4 + +parser = argparse.ArgumentParser(description="build print-ready book PDF") +parser.add_argument("-i", "--input", dest="input_file", required=True, help="input PDF file") +parser.add_argument("-o", "--output", dest="output_file", required=True, help="output PDF file") +parser.add_argument("-p", "--pages", dest="page_range", help="page range, e.g., '3-end'") +parser.add_argument("-c", "--crop", dest="crop_range", help="crops left, bottom, right, top – e.g., '10,10,10,10'") +parser.add_argument("-n", "--nup4", dest="nup4", action='store_true', help="puts 4 input pages onto 1 output page") +parser.add_argument("-a", "--analyze", dest="analyze", action="store_true", help="print lines identifying spine, page borders") +args = parser.parse_args() + +with open(args.input_file, 'rb') as file: + reader = pypdf.PdfReader(file) + + # determine page range + start_page = 0 + end_page = len(reader.pages) + if args.page_range: + start, end = args.page_range.split('-') + if not (len(start) == 0 or start == "start"): + start_page = int(start) - 1 + if not (len(end) == 0 or end == "end"): + end_page = int(end) + pages_to_add = [] + for page_num in range(start_page, end_page): + page = reader.pages[page_num] + pages_to_add += [page] + print("read in page number", page_num+1) + + # normalize all pages to A4 + for page_num in range(start_page, end_page): + page = reader.pages[page_num] + page.mediabox.left = 0 + page.mediabox.bottom = 0 + page.mediabox.top = a4_height + page.mediabox.right = a4_width + page.cropbox = page.mediabox + + # determine page crop + crop_left, crop_bottom, crop_right, crop_top = 0, 0, 0, 0 + if args.crop_range: + crop_left, crop_bottom, crop_right, crop_top = [float(x) for x in args.crop_range.split(',')] + cropped_width = a4_width - crop_left - crop_right + cropped_height = a4_height - crop_bottom - crop_top + zoom = 1 + if args.crop_range: + zoom_horizontal = a4_width / (a4_width - crop_left - crop_right) + zoom_vertical = a4_height / (a4_height - crop_bottom - crop_top) + if (zoom_horizontal > 1 and zoom_vertical < 1) or (zoom_horizontal < 1 and zoom_vertical > 1): + print("Error: opposing zooms.") + exit(1) + elif zoom_horizontal + zoom_vertical > 2: + zoom = min(zoom_horizontal, zoom_vertical) + else: + zoom = max(zoom_horizontal, zoom_vertical) + + writer = pypdf.PdfWriter() + if not args.nup4: + for page in pages_to_add: + page.add_transformation(pypdf.Transformation().translate(tx=-crop_left, ty=-crop_bottom)) + page.add_transformation(pypdf.Transformation().scale(zoom, zoom)) + page.mediabox.right = cropped_width * zoom + page.mediabox.top = cropped_height * zoom + writer.add_page(page) + else: + n_pages_per_axis = 2 + points_per_mm = 2.83465 + printable_margin = 4.3 * points_per_mm + printable_scale = (a4_width - 2*printable_margin)/a4_width + spine_limit = 10 * points_per_mm + half_width = a4_width / n_pages_per_axis + half_height = a4_height / n_pages_per_axis + section_scale_factor = 1 / n_pages_per_axis + spine_part_of_page = (spine_limit / half_width) / printable_scale + bonus_shrink_factor = 1 - spine_part_of_page + new_page_order = [] + eight_pack = [] + mod_to_8 = len(pages_to_add) % 8 + if mod_to_8 > 0: + for _ in range(8 - mod_to_8): + new_page = pypdf.PageObject.create_blank_page(width=a4_width, height=a4_height) + pages_to_add += [new_page] + i = 0 + for page in pages_to_add: + if i == 0: + eight_pack = [] + eight_pack += [page] + i += 1 + if i == 8: + i = 0 + new_page_order += [eight_pack[3]] # page front, upper left + new_page_order += [eight_pack[0]] # page front, upper right + new_page_order += [eight_pack[7]] # page front, lower left + new_page_order += [eight_pack[4]] # page front, lower right + new_page_order += [eight_pack[1]] # page back, upper left + new_page_order += [eight_pack[2]] # page back, upper right + new_page_order += [eight_pack[5]] # page back, lower left + new_page_order += [eight_pack[6]] # page back, lower right + i = 0 + page_count = 0 + front_page = True + for page in new_page_order: + if i == 0: + new_page = pypdf.PageObject.create_blank_page(width=a4_width, height=a4_height) + + # in-section transformations: align pages on top, left-hand pages to left, right-hand to right + page.add_transformation(pypdf.Transformation().translate(ty=(a4_height / zoom - (a4_height - crop_top)))) + if i == 0 or i == 2: + page.add_transformation(pypdf.Transformation().translate(tx=-crop_left)) + elif i == 1 or i == 3: + 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 i == 2 or i == 3: + page.add_transformation(pypdf.Transformation().translate(ty=-2*printable_margin/printable_scale)) + + # outer section transformations + page.add_transformation(pypdf.Transformation().translate(ty=(1-bonus_shrink_factor)*a4_height)) + if i == 0 or i == 1: + y_section = a4_height + page.mediabox.bottom = half_height + page.mediabox.top = a4_height + if i == 2 or i == 3: + y_section = 0 + page.mediabox.bottom = 0 + page.mediabox.top = half_height + if i == 0 or i == 2: + x_section = 0 + page.mediabox.left = 0 + page.mediabox.right = half_width + if i == 1 or i == 3: + page.add_transformation(pypdf.Transformation().translate(tx=(1-bonus_shrink_factor)*a4_width)) + x_section = a4_width + page.mediabox.left = 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(section_scale_factor, section_scale_factor)) + new_page.merge_page(page) + page_count += 1 + print("merged page number", page_count) + i += 1 + if i > 3: + from reportlab.pdfgen import canvas + if args.analyze: + # borders + packet = io.BytesIO() + c = canvas.Canvas(packet, pagesize=A4) + c.setLineWidth(0.1) + c.line(0, a4_height, a4_width, a4_height) + c.line(0, half_height, a4_width, half_height) + c.line(0, 0, a4_width, 0) + c.line(0, a4_height, 0, 0) + c.line(half_width, a4_height, half_width, 0) + c.line(a4_width, a4_height, a4_width, 0) + c.save() + new_pdf = pypdf.PdfReader(packet) + new_page.merge_page(new_pdf.pages[0]) + printable_offset_x = printable_margin + printable_offset_y = printable_margin * a4_height / a4_width + new_page.add_transformation(pypdf.Transformation().scale(printable_scale, printable_scale)) + new_page.add_transformation(pypdf.Transformation().translate(tx=printable_offset_x, ty=printable_offset_y)) + x_left_spine_limit = half_width * bonus_shrink_factor + x_right_spine_limit = a4_width - x_left_spine_limit + if args.analyze or front_page: + packet = io.BytesIO() + c = canvas.Canvas(packet, pagesize=A4) + if args.analyze: + # # spine lines + c.setLineWidth(0.1) + c.line(x_left_spine_limit, a4_height, x_left_spine_limit, 0) + c.line(x_right_spine_limit, a4_height, x_right_spine_limit, 0) + if front_page: + c.setLineWidth(0.2) + cut_depth = 19.5 * points_per_mm + cut_width = 10.5 * points_per_mm + middle_point_depth = 4 * points_per_mm + + start_up_left_left_x = x_left_spine_limit - 0.5 * cut_width + start_up_left_right_x = x_left_spine_limit + 0.5 * cut_width + middle_point_up_left_y = half_height + middle_point_depth + end_point_up_left_y = half_height + cut_depth + c.line(start_up_left_right_x, half_height, x_left_spine_limit, end_point_up_left_y) + c.line(x_left_spine_limit, end_point_up_left_y, x_left_spine_limit, middle_point_up_left_y) + c.line(x_left_spine_limit, middle_point_up_left_y, start_up_left_left_x, half_height) + + start_down_right_left_x = x_right_spine_limit - 0.5 * cut_width + start_down_right_right_x = x_right_spine_limit + 0.5 * cut_width + middle_point_down_right_y = half_height - middle_point_depth + end_point_down_right_y = half_height - cut_depth + c.line(start_down_right_left_x, half_height, x_right_spine_limit, end_point_down_right_y) + c.line(x_right_spine_limit, end_point_down_right_y, x_right_spine_limit, middle_point_down_right_y) + c.line(x_right_spine_limit, middle_point_down_right_y, start_down_right_right_x, half_height) + + if args.analyze or front_page: + c.save() + # packet.seek(0) + new_pdf = pypdf.PdfReader(packet) + new_page.merge_page(new_pdf.pages[0]) + writer.add_page(new_page) + i = 0 + front_page = not front_page + + with open(args.output_file, 'wb') as output_file: + writer.write(output_file) -- 2.30.2