|
|
|
@ -1,72 +1,50 @@
|
|
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
|
|
|
|
|
import os
|
|
|
|
|
import sys
|
|
|
|
|
from math import ceil
|
|
|
|
|
from PyPDF2 import PdfReader, PdfWriter
|
|
|
|
|
import argparse
|
|
|
|
|
from PyPDF2 import PageObject, PdfReader, PdfWriter, PaperSize, Transformation
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
|
def print_help():
|
|
|
|
|
print('bookletify')
|
|
|
|
|
print()
|
|
|
|
|
print('USAGE')
|
|
|
|
|
print(' bookletify [OPTIONS] file')
|
|
|
|
|
print()
|
|
|
|
|
print('DESCRIPTION')
|
|
|
|
|
print(' Rearranges the pages of a pdf in order to print it as is (double')
|
|
|
|
|
print(' sided, flipped on the short edge) so that you can take it, cut ')
|
|
|
|
|
print(' in half and close it like you would do with a book. The result ')
|
|
|
|
|
print(' will be the original pdf in the correct order, with the size of ')
|
|
|
|
|
print(' half a page')
|
|
|
|
|
print(' Especially useful to print pdfs to A5 size on A4 paper')
|
|
|
|
|
print()
|
|
|
|
|
print('OPTIONS')
|
|
|
|
|
print(' -h print help')
|
|
|
|
|
print(' -q do not print anything to stdout')
|
|
|
|
|
|
|
|
|
|
def parse_argv():
|
|
|
|
|
opts = {
|
|
|
|
|
"h": False,
|
|
|
|
|
"q": False,
|
|
|
|
|
}
|
|
|
|
|
filename = None
|
|
|
|
|
|
|
|
|
|
if len(sys.argv) < 2:
|
|
|
|
|
opts["h"] = True
|
|
|
|
|
|
|
|
|
|
for arg in sys.argv[1:]:
|
|
|
|
|
if arg.startswith('-'):
|
|
|
|
|
for opt in opts:
|
|
|
|
|
if opt in arg:
|
|
|
|
|
opts[opt] = True
|
|
|
|
|
elif not filename:
|
|
|
|
|
filename = arg
|
|
|
|
|
|
|
|
|
|
return (opts, filename)
|
|
|
|
|
|
|
|
|
|
def info(text = '', end='\n'):
|
|
|
|
|
if not opts["q"]:
|
|
|
|
|
print(text, end=end)
|
|
|
|
|
|
|
|
|
|
def prog(curr, max_val):
|
|
|
|
|
# Overrides the current terminal line with a progressbar filled at curr/max_val
|
|
|
|
|
perc = float(curr) / float(max_val)
|
|
|
|
|
remaining = f'{curr:{len(str(max_val))}d}/{max_val}'
|
|
|
|
|
(cols, _) = os.get_terminal_size()
|
|
|
|
|
max_width = cols - len(remaining) - 3
|
|
|
|
|
|
|
|
|
|
size = min(max_width, max_val, 80)
|
|
|
|
|
bar = int(perc * size)
|
|
|
|
|
|
|
|
|
|
end = '\n' if curr == max_val else ''
|
|
|
|
|
info("\r|" + ("\u2588" * bar) + "_" * (size - bar) + "| " + remaining, end)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(opts, input_name) = parse_argv()
|
|
|
|
|
if opts["h"] or not input_name:
|
|
|
|
|
print_help()
|
|
|
|
|
exit(0)
|
|
|
|
|
|
|
|
|
|
PROGRAM_NAME="bookletify"
|
|
|
|
|
DESCRIPTION = """Rearranges the pages of a pdf in order to print it as is,
|
|
|
|
|
so that you can take it, cut it in half and close it like you would
|
|
|
|
|
with a book. The result will be the original pdf in the correct order,
|
|
|
|
|
with the size of half a page. Especially useful to print pdfs to A5
|
|
|
|
|
size on A4 paper"""
|
|
|
|
|
|
|
|
|
|
parser = argparse.ArgumentParser(
|
|
|
|
|
prog=PROGRAM_NAME,
|
|
|
|
|
description=DESCRIPTION
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
parser.add_argument('filename')
|
|
|
|
|
parser.add_argument('-q', '--quiet',
|
|
|
|
|
action='store_true',
|
|
|
|
|
help='suppress stdout')
|
|
|
|
|
parser.add_argument('-s', '--size',
|
|
|
|
|
action='store',
|
|
|
|
|
choices=['A0', 'A1', 'A2', 'A3', 'A4', 'A5', 'A6', 'A7', 'A8', 'C4'],
|
|
|
|
|
default='A4',
|
|
|
|
|
metavar='SIZE',
|
|
|
|
|
help="""set final paper size. Possible values: 'A0',
|
|
|
|
|
..., 'A8' or 'C4'. Default value is 'A4'""")
|
|
|
|
|
|
|
|
|
|
parser.add_argument('-b', '--binding-margin',
|
|
|
|
|
default=10,
|
|
|
|
|
type=float,
|
|
|
|
|
metavar='MARGIN',
|
|
|
|
|
help="""internal margin for the binding, expressed in
|
|
|
|
|
millimeters. Default value is 10""")
|
|
|
|
|
|
|
|
|
|
args = parser.parse_args()
|
|
|
|
|
|
|
|
|
|
info = lambda text='', end='\n': print(text, end=end)
|
|
|
|
|
if args.quiet:
|
|
|
|
|
info = lambda text='', end='\n': None
|
|
|
|
|
|
|
|
|
|
prog = lambda curr, max: info(f"\r{curr} / {max}", '\n' if curr == max else '')
|
|
|
|
|
|
|
|
|
|
input_name = args.filename
|
|
|
|
|
if not os.path.isfile(input_name):
|
|
|
|
|
print(f'file {input_name} does not exist')
|
|
|
|
|
exit(1)
|
|
|
|
@ -76,46 +54,59 @@ def main():
|
|
|
|
|
output_name = f'{input_name[0:-4]}-rearranged.pdf'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fullwidth = getattr(PaperSize, args.size).width
|
|
|
|
|
fullheight = getattr(PaperSize, args.size).height
|
|
|
|
|
smallwidth = fullheight / 2
|
|
|
|
|
smallheight = fullwidth
|
|
|
|
|
# Convert mm to pixels at 72 dpi
|
|
|
|
|
bindingwidth = args.binding_margin * 72 / float(25.4)
|
|
|
|
|
smallblank = PageObject.create_blank_page(width=smallwidth - bindingwidth, height=smallheight)
|
|
|
|
|
|
|
|
|
|
# Read the input
|
|
|
|
|
reader = PdfReader(input_name)
|
|
|
|
|
|
|
|
|
|
# Write the result to a file
|
|
|
|
|
middle = PdfWriter()
|
|
|
|
|
info("Loading...")
|
|
|
|
|
n = len(reader.pages)
|
|
|
|
|
# Set m as the smallest multiple of 4 bigger or equal to n
|
|
|
|
|
m = ceil(n / 4) * 4
|
|
|
|
|
pages = []
|
|
|
|
|
|
|
|
|
|
info("Reading...")
|
|
|
|
|
for i in range(n):
|
|
|
|
|
prog(i, m)
|
|
|
|
|
middle.add_page(reader.pages[i])
|
|
|
|
|
prog(i, n)
|
|
|
|
|
pages.append(reader.pages[i])
|
|
|
|
|
prog(n,n)
|
|
|
|
|
|
|
|
|
|
for i in range(m - n):
|
|
|
|
|
prog(n + i, m)
|
|
|
|
|
middle.add_blank_page()
|
|
|
|
|
while(len(pages) % 4 != 0):
|
|
|
|
|
pages.append(smallblank)
|
|
|
|
|
|
|
|
|
|
prog(m, m)
|
|
|
|
|
|
|
|
|
|
output = PdfWriter()
|
|
|
|
|
info()
|
|
|
|
|
info("Rearranging...")
|
|
|
|
|
for i in range(int(m / 4)):
|
|
|
|
|
mod = i * 2
|
|
|
|
|
prog(i * 4, m)
|
|
|
|
|
output.add_page(middle.pages[m - 1 - mod])
|
|
|
|
|
prog(i * 4 + 1, m)
|
|
|
|
|
output.add_page(middle.pages[mod])
|
|
|
|
|
prog(i * 4 + 2, m)
|
|
|
|
|
output.add_page(middle.pages[1 + mod])
|
|
|
|
|
prog(i * 4 + 3, m)
|
|
|
|
|
output.add_page(middle.pages[m - 2 - mod])
|
|
|
|
|
prog(m, m)
|
|
|
|
|
|
|
|
|
|
info()
|
|
|
|
|
info("Saving...")
|
|
|
|
|
info("\nRearranging...")
|
|
|
|
|
|
|
|
|
|
def merge_srt_page(base, over, angle, tx, ty):
|
|
|
|
|
sx = float(smallwidth - bindingwidth) / float(over.mediabox.width)
|
|
|
|
|
sy = float(smallheight) / float(over.mediabox.height)
|
|
|
|
|
t = Transformation().scale(sx, sy).rotate(angle). translate(tx, ty)
|
|
|
|
|
over.add_transformation(t)
|
|
|
|
|
base.merge_page(over)
|
|
|
|
|
|
|
|
|
|
m = len(pages)
|
|
|
|
|
for i in range(int(m / 2)):
|
|
|
|
|
prog(i, int(m / 2))
|
|
|
|
|
new_page = PageObject.create_blank_page(width=fullwidth, height=fullheight)
|
|
|
|
|
if i % 2 == 0:
|
|
|
|
|
merge_srt_page(new_page, pages[m - 1 - i], -90, 0, fullheight)
|
|
|
|
|
merge_srt_page(new_page, pages[i], - 90, 0, smallwidth - bindingwidth)
|
|
|
|
|
pass
|
|
|
|
|
else:
|
|
|
|
|
merge_srt_page(new_page, pages[m - 1 - i], 90, smallheight, smallwidth + bindingwidth)
|
|
|
|
|
merge_srt_page(new_page, pages[i], 90, smallheight, 0)
|
|
|
|
|
|
|
|
|
|
output.add_page(new_page)
|
|
|
|
|
prog(int(m/2), int(m/2))
|
|
|
|
|
|
|
|
|
|
info("\nSaving...")
|
|
|
|
|
output.write(output_name)
|
|
|
|
|
info("Done!")
|
|
|
|
|
info()
|
|
|
|
|
info("\nDone!")
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
main()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|