#!/usr/bin/env python3 import argparse import numpy as np from pypdf import PaperSize, PdfReader, PdfWriter, Transformation, PageObject from pypdf.generic import ArrayObject, FloatObject, NameObject from pypdf.annotations import Line import math def append_folio_A6(input_pdf, offset, size, append_to): # Handles edge case where folio has 1 page num_A5 = min(math.ceil(size / 4.0) * 2, size) writer = append_to w_offset = len(writer.pages) for i in range(num_A5): dpage = writer.add_blank_page(width=PaperSize.A5.height, height=PaperSize.A5.width) ipage = input_pdf.pages[i+offset] ipage.mediabox = dpage.mediabox if i % 2 == 0: # Even pages are filled right to left ipage.add_transformation(Transformation().translate(PaperSize.A6.width, 0)) dpage.merge_page(ipage) # Now for the fun part: work backwards to fill the remaining pages for j in range(size - num_A5): dpage = writer.pages[w_offset + num_A5 - 1 - j] ipage = input_pdf.pages[num_A5+j+offset] ipage.mediabox = dpage.mediabox if (num_A5 - 1 - j) % 2 == 1: ipage.add_transformation(Transformation().translate(PaperSize.A6.width, 0)) dpage.merge_page(ipage) return writer # Tries to find a partition as # num_sheets = max_folio_size * a + (max_folio_size - k) * b # where k is minimal. We let n = max_folio_size and L = num_sheets. # Note that when L > n^2, then k = 1 must succeed. def find_partition_mink(num_sheets, max_folio_size): max_a = num_sheets // max_folio_size for k in range(1, max_folio_size): # We want to solve a*k = L (mod n-k). This happens iff # a*k' = L' (mod m') where k,L,m are divided by their gcd. # Then, if k' and m' have a common factor, L' doesn't have it and there # are no solutions. Otherwise, there's one solution mod m'. g = math.gcd(max_folio_size - k, k, num_sheets) mprime = (max_folio_size - k)//g kprime = k//g Lprime = num_sheets//g if (math.gcd(kprime, mprime) > 1): continue a_mod_mprime = (pow(kprime, -1, mprime) * Lprime) % mprime tosub = (max_a - a_mod_mprime + mprime) % mprime # If we're a bit above, substract only a little. But a bit below implies # substracting almost mprime. candidate = max_a - tosub if (candidate >= 0): b = (num_sheets - max_folio_size * candidate)//(max_folio_size - k) return [max_folio_size] * candidate + [max_folio_size - k] * b def find_partition_greedy(num_sheets, max_folio_size): q = num_sheets//max_folio_size return [max_folio_size] * q + [num_sheets - q*max_folio_size] # Interprets input_pdf as a list of A6 pages, and appends to append_to with # A5 pages using the following rules: # a. Odd/even page pairs correspond to front/back pages of a single sheet, printed # with "mirror around **short** edge" # b. when grouped as consecutive sequences of max_folio_size or less, the printed # sheets can be folded in the middle and assembled as a folio def foliate_A6(input_pdf, strat_func, max_folio_size, append_to=PdfWriter()): doc_length = len(input_pdf.pages) writer = append_to num_sheets = math.ceil(doc_length/4.0) print("Using", num_sheets, "two-sided A5 sheets") L = [num_sheets] if max_folio_size > 0: L = strat_func(num_sheets, max_folio_size) if(np.sum(L) != num_sheets): print("[ERROR] strategy gave", np.sum(L), " A5 sheets") exit(1) folio_page_size = 0 current_page = 0 print("Folio sizes, in order") old_l = L[0] old_l_run = 0 for l in L: if l != old_l: print(old_l, "a5 sheet(s), x", old_l_run) old_l = l old_l_run = 1 else: old_l_run += 1 # l is a number of _sheets_, each containing 4 pages folio_page_size = min(doc_length - current_page, 4*l) append_folio_A6(input_pdf, current_page, folio_page_size, append_to) current_page += folio_page_size print(old_l, "a5 sheet(s), x", old_l_run) return writer # Take an A5 printing plan, with odd/even pages being understood # as front/back pairs of sheets, short edge mirroring. Produces an A4 printing plan # containing two A5 panels per sheet, long edge mirroring. def blit_to_A4(input_pdf, append_to=PdfWriter()): L = len(input_pdf.pages) writer = append_to for k in range(0, L, 2): if(k%4 == 0): # Open up a new sheet front_page = writer.add_blank_page(width=PaperSize.A4.width, height=PaperSize.A4.height) back_page = writer.add_blank_page(width=PaperSize.A4.width, height=PaperSize.A4.height) midline = Line(rect = (0, PaperSize.A4.height/2, PaperSize.A4.width, PaperSize.A4.height/2), p1=(10, PaperSize.A4.height/2), p2=(PaperSize.A4.width-10, PaperSize.A4.height/2)) midline.flags = 4 # print the midline midline[NameObject("/C")] = ArrayObject([FloatObject(0.5)]) writer.add_annotation(front_page, midline) front = input_pdf.pages[k] back = input_pdf.pages[k+1] if (k+1