Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
154 changes: 126 additions & 28 deletions paperbackup.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,33 +28,18 @@
#

import os
import logging
import re
import sys
import hashlib
import subprocess
import qrencode
import argparse
from tempfile import mkstemp
from datetime import datetime
from PIL import Image
from pyx import *

# constants for the size and layout of the barcodes on page
max_bytes_in_barcode = 140
barcodes_per_page = 6
barcode_height = 8
barcode_x_positions = [1.5, 11, 1.5, 11, 1.5, 11]
barcode_y_positions = [18.7, 18.7, 10, 10, 1.2, 1.2]
text_x_offset = 0
text_y_offset = 8.2

plaintext_maxlinechars = 73

# the paperformat to use, activate the one you want
paperformat_obj = document.paperformat.A4
paperformat_str = "A4"
# paperformat_obj=document.paperformat.Letter
# paperformat_str="Letter"


def create_barcode(chunkdata):
version, size, im = qrencode.encode(chunkdata,
Expand All @@ -68,17 +53,110 @@ def finish_page(pdf, canv, pageno):
pdf.append(document.page(canv, paperformat=paperformat_obj,
fittosize=0, centered=0))


# main code

if len(sys.argv) != 2:
raise RuntimeError('Usage {} FILENAME.asc'.format(sys.argv[0]))
parser = argparse.ArgumentParser(description='Generate a PDF with barcodes for a given file.')
parser.add_argument('-c', dest='columns', nargs=1, default=[4], type=int,
help='number of columns per page (default: 4)')
parser.add_argument('-d', dest='debug', action='store_true',
help='debug output')
parser.add_argument('-g', dest='gap', nargs=1, default=[2], type=int,
help='minimum gap (%%, default: 2)')
parser.add_argument('-r', dest='rows', nargs=1, default=[5], type=int,
help='number of rows per page (default: 5)')
parser.add_argument('-s', dest='paper_size', nargs=1, default=['a4'],
help='paper size: a4 or letter (default: a4)')
parser.add_argument('input_file', nargs=1,
help='file to process (perhaps base64-encoded)')

args = parser.parse_args()

if not args.input_file:
parser.print_help()
sys.exit()

input_file = args.input_file[0]

if args.debug:
logging.basicConfig(level=logging.DEBUG)
else:
logging.basicConfig(level=logging.CRITICAL)

input_path = sys.argv[1]
if not os.path.isfile(input_path):
raise RuntimeError('File {} not found'.format(input_path))
just_filename = os.path.basename(input_path)
# constants for the size and layout of the barcodes on page
max_bytes_in_barcode = 140

with open(input_path) as inputfile:
# page margins
top_margin = 2.2
right_margin = 1.2
left_margin = 1.5
bottom_margin = 1.5

paper_size = args.paper_size[0].lower()

if paper_size == 'a4':
paperformat_obj = document.paperformat.A4
paperformat_str = "A4"
# paper size in cm
paper_width = 21
paper_height = 29.7
else:
paperformat_obj=document.paperformat.Letter
paperformat_str="Letter"
# paper size in cm
paper_width = 21.6
paper_height = 27.9

logging.info('Paper size: {0}'.format(paper_size))

# number of barcode rows/columns per page (4/5 by default)
barcode_cols = args.columns[0]
barcode_rows = args.rows[0]

cell_width = (paper_width - left_margin - right_margin) / barcode_cols
cell_height = (paper_height - top_margin - bottom_margin) / barcode_rows

# fix "X"
logging.info('Cell dimensions: {0:.2f}×{1:.2f} cm'.format(cell_width, cell_height))

gap_perc = args.gap[0]

if cell_width <= cell_height:
horizontal_gap = gap_perc * cell_width / 100
barcode_height = cell_width - horizontal_gap
vertical_gap = cell_height - barcode_height
else:
vertical_gap = gap_perc * cell_height / 100
barcode_height = cell_height - vertical_gap
horizontal_gap = cell_width - barcode_height

logging.info('Horizontal gap: {0:.2f} cm'.format(horizontal_gap))
logging.info('Vertical gap: {0:.2f} cm'.format(vertical_gap))
logging.info('Barcode height/width: {0:.2f} cm'.format(barcode_height))


barcode_x_positions = [left_margin + (x * (horizontal_gap + barcode_height)) for x in range(barcode_cols)] * barcode_rows
barcode_y_positions = list()
[barcode_y_positions.extend(barcode_cols * [bottom_margin + (x * (vertical_gap + barcode_height))]) for x in range(barcode_rows)]
barcode_y_positions.reverse()
barcodes_per_page = barcode_rows * barcode_cols
text_x_offset = 0
text_y_offset = barcode_height + 0.2
logging.info('Barcode x positions: {0}'.format(barcode_x_positions))
logging.info('Barcode y positions: {0}'.format(barcode_y_positions))

# align to top margin
content_top = max(barcode_y_positions) + text_y_offset
header_content_gap = paper_height - top_margin - content_top
barcode_y_positions = [x + header_content_gap for x in barcode_y_positions]

plaintext_maxlinechars = 73

if not os.path.isfile(input_file):
raise RuntimeError('File {} not found'.format(input_file))
just_filename = os.path.basename(input_file)

with open(input_file) as inputfile:
ascdata = inputfile.read()

# only allow some harmless characters
Expand All @@ -87,7 +165,8 @@ def finish_page(pdf, canv, pageno):
allowedchars = re.compile(r"^[A-Za-z0-9/=+:., #@!()\n-]*")
allowedmatch = allowedchars.match(ascdata)
if allowedmatch.group() != ascdata:
raise RuntimeError('Illegal char found at %d >%s<'
raise RuntimeError('Illegal char found at %d >%s<.\n'
'Maybe you want to base64-encode the file first?'
% (len(allowedmatch.group()),
ascdata[len(allowedmatch.group())]))

Expand All @@ -99,13 +178,18 @@ def finish_page(pdf, canv, pageno):
for char in list(ascdata):
if len(chunkdata)+1 > max_bytes_in_barcode:
# chunk is full -> create barcode from it
logging.debug('Creating barcode no {0}'.format(len(barcode_blocks) + 1))
logging.debug('Chunkdata: {0}'.format(chunkdata))
barcode_blocks.append(create_barcode(chunkdata))
chunkdata = "^" + str(len(barcode_blocks)+1) + " "

chunkdata += char

# handle the last, non filled chunk too
barcode_blocks.append(create_barcode(chunkdata))
if len(chunkdata) > len(str(len(barcode_blocks))) + 2:
logging.debug('Creating barcode no {0}'.format(len(barcode_blocks) + 1))
logging.debug('Chunkdata: {0}'.format(chunkdata))
barcode_blocks.append(create_barcode(chunkdata))

# init PyX
unit.set(defaultunit="cm")
Expand All @@ -114,6 +198,18 @@ def finish_page(pdf, canv, pageno):
# place barcodes on pages
pgno = 0 # page number
ppos = 0 # position id on page

if len(just_filename) > 19:
font_size = text.size.tiny
elif len(just_filename) > 15:
font_size = text.size.small
elif len(just_filename) > 10:
font_size = text.size.normal
else:
font_size = text.size.Large

logging.debug('Font size for QR labels: {0}'.format(font_size.size))

c = canvas.canvas()
for bc in range(len(barcode_blocks)):
# page full?
Expand All @@ -126,7 +222,8 @@ def finish_page(pdf, canv, pageno):
c.text(barcode_x_positions[ppos] + text_x_offset,
barcode_y_positions[ppos] + text_y_offset,
"%s (%i/%i)" % (text.escapestring(just_filename),
bc+1, len(barcode_blocks)))
bc+1, len(barcode_blocks)),
[font_size])
c.insert(bitmap.bitmap(barcode_x_positions[ppos],
barcode_y_positions[ppos],
barcode_blocks[bc], height=barcode_height))
Expand All @@ -141,7 +238,7 @@ def finish_page(pdf, canv, pageno):

# prepare plain text output
fd, temp_text_path = mkstemp('.ps', 'text_', '.')
input_file_modification = datetime.fromtimestamp(os.path.getmtime(input_path)).strftime("%Y-%m-%d %H:%M:%S")
input_file_modification = datetime.fromtimestamp(os.path.getmtime(input_file)).strftime("%Y-%m-%d %H:%M:%S")

# split lines on plaintext_maxlinechars - ( checksum_size + separator size)
splitat=plaintext_maxlinechars - 8
Expand Down Expand Up @@ -193,6 +290,7 @@ def finish_page(pdf, canv, pageno):
# use "enscript" to create postscript with the plaintext
p = subprocess.Popen(
["enscript", "-p"+temp_text_path, "-f", "Courier12",
"-t" + just_filename,
"-M" + paperformat_str, "--header",
just_filename + "|" + input_file_modification + "|Page $%"],
stdout=subprocess.PIPE, stdin=subprocess.PIPE)
Expand Down