X-Git-Url: https://plomlompom.com/repos/%7B%7B%20web_path%20%7D%7D/decks/%7B%7Bdeck_id%7D%7D/cards/%7B%7Bcard_id%7D%7D/form?a=blobdiff_plain;f=bookmaker.py;h=8df8814afbaeb496245d0b1be3e8af9aa178dc9e;hb=04f76905a053fc4d1a56fc3382ba171faa1056bf;hp=868866e9e57f426e07c4fe6a23bff817c0148a4f;hpb=da088503e631df30fac79fdf2e82874654c96489;p=misc diff --git a/bookmaker.py b/bookmaker.py index 868866e..8df8814 100755 --- a/bookmaker.py +++ b/bookmaker.py @@ -65,7 +65,7 @@ import os import sys def handled_error_exit(msg): - print("ERROR:", msg) + print(f"ERROR: {msg}") sys.exit(1) try: @@ -73,27 +73,32 @@ try: except ImportError: handled_error_exit("Can't run at all without pypdf installed.") -# some constants +# some general paper geometry constants POINTS_PER_CM = 10 * 72 / 25.4 A4_WIDTH = 21 * POINTS_PER_CM A4_HEIGHT = 29.7 * POINTS_PER_CM A4 = (A4_WIDTH, A4_HEIGHT) + +# constants specifically for --nup4 +A4_HALF_WIDTH = A4_WIDTH / 2 +A4_HALF_HEIGHT = A4_HEIGHT / 2 CUT_DEPTH = 1.95 * POINTS_PER_CM CUT_WIDTH = 1.05 * POINTS_PER_CM MIDDLE_POINT_DEPTH = 0.4 * POINTS_PER_CM SPINE_LIMIT = 1 * POINTS_PER_CM +QUARTER_SCALE_FACTOR = 0.5 # some helpers class HandledException(Exception): pass def validate_page_range(p_string, err_msg_prefix): - err_msg = "%s: invalid page range string: %s" % (err_msg_prefix, p_string) + prefix = f"{err_msg_prefix}: page range string" if '-' not in p_string: - raise HandledException("%s: page range string lacks '-': %s" % (err_msg_prefix, p_string)) + raise HandledException(f"{prefix} lacks '-': {p_string}") tokens = p_string.split("-") if len(tokens) > 2: - raise HandledException("%s: page range string has too many '-': %s" % (err_msg_prefix, p_string)) + raise HandledException(f"{prefix} has too many '-': {p_string}") for i, token in enumerate(tokens): if token == "": continue @@ -104,9 +109,9 @@ def validate_page_range(p_string, err_msg_prefix): try: int(token) except ValueError: - raise HandledException("%s: page range string carries values that are neither integer, nor 'start', nor 'end': %s" % (err_msg_prefix, p_string)) + raise HandledException(f"{prefix} carries value neither integer, nor 'start', nor 'end': {p_string}") if int(token) < 1: - raise HandledException("%s: page range string may not carry page numbers <1: %s" % (err_msg_prefix, p_string)) + raise HandledException(f"{prefix} carries page number <1: {p_string}") start = -1 end = -1 try: @@ -115,7 +120,7 @@ def validate_page_range(p_string, err_msg_prefix): except ValueError: pass if start > 0 and end > 0 and start > end: - raise HandledException("%s: page range starts higher than it ends: %s" % (err_msg_prefix, p_string)) + raise HandledException(f"{prefix} has higher start than end value: {p_string}") def split_crops_string(c_string): initial_split = c_string.split(':') @@ -138,6 +143,15 @@ def parse_page_range(range_string, pages): end_page = int(end) return start_page, end_page +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 + 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) + def parse_args(): 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") @@ -154,45 +168,45 @@ def parse_args(): # some basic input validation for filename in args.input_file: if not os.path.isfile(filename): - raise HandledException("-i: %s is not a file" % filename) + 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("-i: cannot interpret %s as PDF file" % filename) + 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") if len(args.page_range) > len(args.input_file): - raise HandledException("more -p arguments than -i 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("-c: cropping string has multiple ':': %s" % c_string) + raise HandledException(f"-c: cropping string has multiple ':': {c_string}") page_range, crops = split_crops_string(c_string) crops = crops.split(",") if page_range: validate_page_range(page_range, "-c") if len(crops) != 4: - raise HandledException("-c: cropping should contain three ',': %s" % c_string) + raise HandledException(f"-c: cropping does not contain exactly three ',': {c_string}") for crop in crops: try: float(crop) except ValueError: - raise HandledException("-c: non-number crop in %s" % 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("-r: non-integer value: %s" % r) + raise HandledException(f"-r: non-integer value: {r}") if r < 1: - raise HandledException("-r: value must not be <1: %s" % r) + raise HandledException(f"-r: value must not be <1: {r}") try: float(args.print_margin) except ValueError: - raise HandledException("-m: non-float value: %s" % arg.print_margin) + raise HandledException(f"-m: non-float value: {arg.print_margin}") return args @@ -217,12 +231,12 @@ def main(): range_string = args.page_range[i] start_page, end_page = parse_page_range(range_string, reader.pages) if end_page > len(reader.pages): # no need to test start_page cause start_page > end_page is checked above - raise HandledException("-p: page range goes beyond pages of input file: %s" % range_string) + raise HandledException(f"-p: page range goes beyond pages of input file: {range_string}") for old_page_num in range(start_page, end_page): new_page_num += 1 page = reader.pages[old_page_num] pages_to_add += [page] - print("-i, -p: read in %s page number %d as new page %d" % (input_file, old_page_num+1, new_page_num)) + print(f"-i, -p: read in {input_file} page number {old_page_num+1} as new page {new_page_num}") # we can do some more input validations now that we know how many pages output should have if args.crops: @@ -231,11 +245,11 @@ def main(): if page_range: start, end = parse_page_range(page_range, pages_to_add) if end > len(pages_to_add): - raise HandledException("-c: page range goes beyond number of pages we're building: %s" % page_range) + raise HandledException(f"-c: page range goes beyond number of pages we're building: {page_range}") if args.rotate_page: for r in args.rotate_page: if r > len(pages_to_add): - raise HandledException("-r: page number beyond number of pages we're building: %d" % r) + raise HandledException(f"-r: page number beyond number of pages we're building: {r}") # rotate page canvas (as opposed to using PDF's /Rotate command) if args.rotate_page: @@ -244,13 +258,13 @@ def main(): 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("-r: rotating (by 90°) page", rotate_page) + print(f"-r: rotating (by 90°) page {rotate_page}") # if necessary, pad pages to multiple of 8 if args.nup4: mod_to_8 = len(pages_to_add) % 8 if mod_to_8 > 0: - print("-n: number of input pages %d not multiple of 8, padding to that" % len(pages_to_add)) + print(f"-n: number of input pages {len(pages_to_add)} not multiple of 8, padding to that") 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] @@ -277,10 +291,9 @@ def main(): crop_bottom = crop_bottom_cm * POINTS_PER_CM crop_right = crop_right_cm * POINTS_PER_CM crop_top = crop_top_cm * POINTS_PER_CM - if args.symmetry: - print("-c, -t: to pages %d to %d applying crops: left %.2fcm, bottom %.2fcm, right %.2fcm, top %.2fcm (but alternating left and right crop between even and odd pages)" % (start_page + 1, end_page, crop_left_cm, crop_bottom_cm, crop_right_cm, crop_top_cm)) - else: - print("-c: to pages %d to %d applying crops: left %.2fcm, bottom %.2fcm, right %.2fcm, top %.2fcm" % (start_page + 1, end_page, crop_left_cm, crop_bottom_cm, crop_right_cm, crop_top_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 zoom = 1 @@ -315,20 +328,16 @@ def main(): page.mediabox.top = cropped_height * zoom writer.add_page(page) odd_page = not odd_page - print("built page number %d (of %d)" % (i+1, len(pages_to_add))) + print(f"built page number {i+1} (of {len(pages_to_add)})") else: print("-n: building 4-input-pages-per-output-page book") - print("-m: applying printable-area margin of %.2fcm" % args.print_margin) + print(f"-m: applying printable-area margin of {args.print_margin}cm") if args.analyze: print("-a: drawing page borders, spine limits") - n_pages_per_axis = 2 printable_margin = args.print_margin * POINTS_PER_CM - printable_scale = (A4_WIDTH - 2*printable_margin)/A4_WIDTH - 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 + 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 new_page_order = [] new_i_order = [] @@ -383,26 +392,26 @@ def main(): 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.bottom = A4_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 + page.mediabox.top = A4_HALF_HEIGHT if i == 0 or i == 2: x_section = 0 page.mediabox.left = 0 - page.mediabox.right = half_width + page.mediabox.right = A4_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.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(section_scale_factor, section_scale_factor)) + page.add_transformation(pypdf.Transformation().scale(QUARTER_SCALE_FACTOR, QUARTER_SCALE_FACTOR)) new_page.merge_page(page) page_count += 1 - print("merged page number %d (of %d)" % (page_count, len(pages_to_add))) + print(f"merged page number {page_count} (of {len(pages_to_add)})") i += 1 if i > 3: if args.analyze: @@ -411,10 +420,10 @@ def main(): c = reportlab.pdfgen.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, A4_HALF_HEIGHT, A4_WIDTH, A4_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_HALF_WIDTH, A4_HEIGHT, A4_HALF_WIDTH, 0) c.line(A4_WIDTH, A4_HEIGHT, A4_WIDTH, 0) c.save() new_pdf = pypdf.PdfReader(packet) @@ -423,34 +432,20 @@ def main(): 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 + x_left_spine_limit = A4_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 = reportlab.pdfgen.canvas.Canvas(packet, pagesize=A4) if args.analyze: - # # spine lines + # 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) + 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 upper left - 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) - # cut lower right - 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) + draw_cut(c, x_left_spine_limit, (1)) + draw_cut(c, x_right_spine_limit, (-1)) if args.analyze or front_page: c.save() new_pdf = pypdf.PdfReader(packet)