Right now this is still unexposed to the users. Pretty-printing the foliating plan is somewhat of a problem. Hopefully they are always quite small.
149 lines
6.2 KiB
Python
Executable file
149 lines
6.2 KiB
Python
Executable file
#!/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
|
|
for l in L:
|
|
# 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
|
|
|
|
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<L) else PageObject.create_blank_page(None, PaperSize.A5.width, PaperSize.A5.height)
|
|
|
|
front.mediabox = front_page.mediabox
|
|
back.mediabox = back_page.mediabox
|
|
|
|
if(k%4 == 0): # Translate to bottom half of page
|
|
front.add_transformation(Transformation().translate(0, PaperSize.A5.width))
|
|
back.add_transformation(Transformation().translate(0, PaperSize.A5.width))
|
|
|
|
front_page.merge_page(front)
|
|
back_page.merge_page(back)
|
|
return writer
|
|
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("infile", help="input document, A6 pages")
|
|
parser.add_argument("outfile", help="output document")
|
|
parser.add_argument("-a5", help="output as a5, reverse on short edge",
|
|
action="store_true")
|
|
parser.add_argument("-fs", help="maximum folio size, in sheets",
|
|
action="store", default=-1, type=int)
|
|
args = parser.parse_args()
|
|
|
|
input_pdf = PdfReader(args.infile)
|
|
a5 = foliate_A6(input_pdf, find_partition_greedy, args.fs)
|
|
if (args.a5):
|
|
a5.write(args.outfile)
|
|
else:
|
|
a4 = blit_to_A4(a5)
|
|
a4.write(args.outfile)
|