From b8e982bd631b69893c6eaf6f58a9de25cadb8217 Mon Sep 17 00:00:00 2001 From: Christian Heller Date: Sun, 24 Sep 2023 00:19:28 +0200 Subject: [PATCH] In Bookmaker, allow arbitrary number of input files and respective page ranges. --- bookmaker.py | 403 +++++++++++++++++++++++++-------------------------- 1 file changed, 197 insertions(+), 206 deletions(-) diff --git a/bookmaker.py b/bookmaker.py index 2e5085f..eb6fd11 100755 --- a/bookmaker.py +++ b/bookmaker.py @@ -1,243 +1,234 @@ #!/usr/bin/env python3 -import pypdf +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("-i", "--input", dest="input_file", action="append", 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("-p", "--pages", dest="page_range", action="append", 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") parser.add_argument("-t", "--symmetry", dest="symmetry", action="store_true", help="alternate horizontal crops between odd and even pages") -parser.add_argument("-s", "--second", dest="second", help="append second file as input to append") parser.add_argument("-r", "--rotate", dest="rotate", help="rotate page of number by 90°") parser.add_argument("-m", "--margin", type=float, default=4.3, help="print margin (default 4.3)") args = parser.parse_args() -with open(args.input_file, 'rb') as file: - reader = pypdf.PdfReader(file) - # determine page range +# select pages from input files +pages_to_add = [] +opened_files = [] +for i, input_file in enumerate(args.input_file): + file = open(input_file, 'rb') + opened_files += [file] + reader = pypdf.PdfReader(file) start_page = 0 end_page = len(reader.pages) - if args.page_range: - start, end = args.page_range.split('-') + if args.page_range and len(args.page_range) > i: + start, end = args.page_range[i].split('-') if not (len(start) == 0 or start == "start"): - start_page = int(start) - 1 + 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) + print("read in %s page number %d" % (input_file, page_num+1)) - # add pages of second PDF - if args.second: - file2 = open(args.second, 'rb') - reader2 = pypdf.PdfReader(file2) - page_num = 1 - for page in reader2.pages: - pages_to_add += [page] - print("read second PDF's page number", page_num) - page_num += 1 +# rotate page canvas +if args.rotate: + page = pages_to_add[int(args.rotate) - 1] + 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)) - # rotate page canvas - if args.rotate: - page = pages_to_add[int(args.rotate) - 1] - 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)) +# normalize all pages to portrait A4 +for page in pages_to_add: + if "/Rotate" in page: + page.rotate(360 - page["/Rotate"]) + page.mediabox.left = 0 + page.mediabox.bottom = 0 + page.mediabox.top = a4_height + page.mediabox.right = a4_width + page.cropbox = page.mediabox - # normalize all pages to portrait A4 - for page in pages_to_add: - if "/Rotate" in page: - page.rotate(360 - page["/Rotate"]) - 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) - # 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) +writer = pypdf.PdfWriter() +if not args.nup4: + odd_page = True + for page in pages_to_add: + if args.symmetry and odd_page: + page.add_transformation(pypdf.Transformation().translate(tx=-crop_right, ty=-crop_bottom)) else: - zoom = max(zoom_horizontal, zoom_vertical) + 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) + odd_page = not odd_page +else: + n_pages_per_axis = 2 + points_per_mm = 2.83465 + printable_margin = args.margin * 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) - writer = pypdf.PdfWriter() - if not args.nup4: - odd_page = True - for page in pages_to_add: - if args.symmetry and odd_page: - page.add_transformation(pypdf.Transformation().translate(tx=-crop_right, ty=-crop_bottom)) + # 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: + if args.symmetry: + page.add_transformation(pypdf.Transformation().translate(tx=-crop_left)) else: - 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) - odd_page = not odd_page - else: - n_pages_per_axis = 2 - points_per_mm = 2.83465 - printable_margin = args.margin * 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: - if args.symmetry: - page.add_transformation(pypdf.Transformation().translate(tx=-crop_left)) - else: - page.add_transformation(pypdf.Transformation().translate(tx=-crop_right)) - elif i == 1 or i == 3: - if args.symmetry: - page.add_transformation(pypdf.Transformation().translate(tx=(a4_width / zoom - (a4_width - crop_left)))) - else: - 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)) + page.add_transformation(pypdf.Transformation().translate(tx=-crop_right)) + elif i == 1 or i == 3: + if args.symmetry: + page.add_transformation(pypdf.Transformation().translate(tx=(a4_width / zoom - (a4_width - crop_left)))) + else: + 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 + # 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_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) + 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 + if args.analyze or front_page: + c.save() + 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 +for file in opened_files: + file.close() - with open(args.output_file, 'wb') as output_file: - writer.write(output_file) - if args.second: - file2.close() +with open(args.output_file, 'wb') as output_file: + writer.write(output_file) -- 2.30.2