Browse Source

it works

master
Holger Frey 6 years ago
parent
commit
5a0693825a
  1. 63
      Makefile
  2. 0
      pdftools/__init__.py
  3. 89
      pdftools/cli.py
  4. 101
      pdftools/formfill.py
  5. BIN
      pdftools/logo_imtek.png
  6. 141
      pdftools/nameplate.py
  7. 26
      pdftools/ruler.py
  8. 44
      poetry.lock
  9. 36
      pyproject.toml
  10. 43
      readme.md

63
Makefile

@ -0,0 +1,63 @@
.PHONY: clean clean-test clean-pyc clean-build help
.DEFAULT_GOAL := help
define BROWSER_PYSCRIPT
import os, webbrowser, sys
try:
from urllib import pathname2url
except:
from urllib.request import pathname2url
webbrowser.open("file://" + pathname2url(os.path.abspath(sys.argv[1])))
endef
export BROWSER_PYSCRIPT
define PRINT_HELP_PYSCRIPT
import re, sys
for line in sys.stdin:
match = re.match(r'^([a-zA-Z_-]+):.*?## (.*)$$', line)
if match:
target, help = match.groups()
print("%-20s %s" % (target, help))
endef
export PRINT_HELP_PYSCRIPT
BROWSER := python -c "$$BROWSER_PYSCRIPT"
help:
@python -c "$$PRINT_HELP_PYSCRIPT" < $(MAKEFILE_LIST)
clean: clean-build clean-pyc clean-test ## remove all build, test, coverage and Python artifacts
clean-build: ## remove build artifacts
rm -fr build/
rm -fr dist/
rm -fr .eggs/
find . -name '*.egg-info' -exec rm -fr {} +
find . -name '*.egg' -exec rm -f {} +
clean-pyc: ## remove Python file artifacts
find . -name '*.pyc' -exec rm -f {} +
find . -name '*.pyo' -exec rm -f {} +
find . -name '*~' -exec rm -f {} +
find . -name '__pycache__' -exec rm -fr {} +
clean-test: ## remove test and coverage artifacts
rm -fr .tox/
rm -f .coverage
rm -fr htmlcov/
lint: ## check style with flake8
black pdftools tests
flake8 pdftools tests
test: ## run tests quickly with the default Python
py.test -x --disable-warnings
coverage: ## check code coverage with the default Python
coverage run --source pdftools -m pytest
coverage report -m
coverage html
$(BROWSER) htmlcov/index.html

0
pdftools/__init__.py

89
pdftools/cli.py

@ -0,0 +1,89 @@
import click
from reportlab.lib.units import mm
from . import nameplate
from . import ruler
@click.command()
@click.argument(
"attendees",
type=click.Path(
exists=True,
file_okay=True,
dir_okay=False,
writable=False,
readable=True,
),
)
@click.argument(
"logo",
type=click.Path(
exists=True,
file_okay=True,
dir_okay=False,
writable=False,
readable=True,
),
required=False,
)
@click.option(
"-s",
"--size",
type=click.INT,
required=False,
default=10,
show_default="10 mm",
)
@click.option(
"-y",
"--adjust_y",
type=click.INT,
required=False,
default=0,
show_default=" 0 mm",
)
@click.option(
"-x",
"--adjust_x",
type=click.INT,
required=False,
default=0,
show_default=" 0 mm",
)
def nameplates(attendees, logo, size, adjust_x, adjust_y):
""" creates a pdf with name plate cards
The attendees file should be a tab separated text file with no headers. Put
the company name in the first column, the given name in the second and the
last name in the third column, e.g:
Hochimin Enterprizes \\t Jane \\t Doe \\n
The positioning of the optional logo file can be controlled with the
size and adjustment options.
"""
if logo is not None:
logo = nameplate.Logo(logo, size * mm, adjust_x=adjust_x, adjust_y=adjust_y)
npc = nameplate.NamePlateCards(logo)
npc.generate(attendees, show=True)
@click.command()
@click.argument(
"pdf",
type=click.Path(
exists=True,
file_okay=True,
dir_okay=False,
writable=False,
readable=True,
),
)
def addruler(pdf):
""" adds a blue ruler overlay to a pdf file """
ruler.ruler_overlay(pdf)

101
pdftools/formfill.py

@ -0,0 +1,101 @@
import pdfrw
import subprocess
import itertools
import tempfile
from pathlib import Path
from reportlab.pdfgen import canvas
from reportlab.lib.units import mm
from reportlab.lib.pagesizes import A4
def parse_text_file(text_file, ignore_first_line=True):
with open(text_file) as fh:
if ignore_first_line:
next(fh)
lines = [line.strip() for line in fh]
parts = [line.split("\t") for line in lines if line]
return [tuple(map(str.strip, p)) for p in parts]
def fill(original_form, draw_function, values, output_file="filled.pdf"):
p = Path(output_file)
if p.exists():
p.unlink()
with tempfile.TemporaryFile() as overlay_fh:
with tempfile.TemporaryFile() as multipage_fh:
# first create the pages with the values at the right positions
# this must be done first, because we need to know the number of
# pages
c = canvas.Canvas(overlay_fh, pagesize=A4)
for items in values:
draw_function(c, items)
c.showPage()
c.save()
overlay_fh.seek(0)
olay = pdfrw.PdfReader(overlay_fh)
# create a temporary pdf that has as many pages as the filled in
# values
original_form = pdfrw.PdfReader(original_form)
writer = pdfrw.PdfWriter()
for i in range(len(olay.pages)):
writer.addpages(original_form.pages)
writer.write(multipage_fh)
multipage_fh.seek(0)
# merge the overlay and the multipage form
form = pdfrw.PdfReader(multipage_fh)
for form_page, overlay_page in zip(form.pages, olay.pages):
merge_obj = pdfrw.PageMerge()
filled = merge_obj.add(overlay_page)[0]
pdfrw.PageMerge(form_page).add(filled).render()
# write the combined file to the output
pdfrw.PdfWriter().write(output_file, form)
subprocess.run(["open", output_file])
if __name__=="__main__":
attend = parse_text_file("teilnehmertabelle.txt")
def create_overlay(c, item):
company, addr, name = item
c.setFont("Helvetica", 11)
c.drawString( 75*mm, 234*mm, "Universität Freiburg, IMTEK, CPI")
c.drawString(118*mm, 225*mm, "20126 N")
c.drawString( 58*mm, 216*mm, "VDK-Bestimmung in Jungbier")
if company.startswith("Wissenschaftsförderung"):
c.setFont("Helvetica", 10)
c.drawString( 35*mm, 204*mm, f"{company}, {addr}")
if company.startswith("Wissenschaftsförderung"):
c.setFont("Helvetica", 11)
c.drawString( 35*mm, 191*mm, f"19.02.2019")
c.drawString(113*mm, 191*mm, f"10:30 Uhr")
c.drawString(150*mm, 191*mm, f"16:00 Uhr")
c.drawString( 25.8*mm, 177.75*mm, f"X")
c.drawString( 35*mm, 169*mm, name)
c.drawString( 35*mm, 156*mm, "Teilname Kickoff-Meeting")
c.drawString( 37.15*mm, 134.35*mm, f"X")
c.drawString( 54*mm, 134*mm, "1")
c.drawString( 24*mm, 82*mm, "Freiburg, den 19.02.2019")
c.setFont("Helvetica-Bold", 12)
c.drawString( 117*mm, 100*mm, "1.000")
#fill("form.pdf", create_overlay, attend)
ruler_overlay("form.pdf")

BIN
pdftools/logo_imtek.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

141
pdftools/nameplate.py

@ -0,0 +1,141 @@
import subprocess
import itertools
from pathlib import Path
from PIL import Image
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import A4
from reportlab.lib.units import mm
class Logo:
def __init__(self, path, target_height, adjust_x=0, adjust_y=0):
self.path = path
self.height = target_height
self.width = self._scale_width(target_height)
self.adjust_x = adjust_x
self.adjust_y = adjust_y
self.size = {"width": self.width, "height": self.height}
def _scale_width(self, height):
img = Image.open(self.path)
img_w2h = img.width / img.height
return height * img_w2h
_tmp_pos = [
# upper card
39 + 114,
# lower card
39,
]
y_positions = itertools.cycle([rp * mm for rp in _tmp_pos])
y_height = 50 * mm
y_padding = 5 * mm
x_pos = 55 * mm
x_width = 100 * mm
x_padding = 10 * mm
false_and_true = itertools.cycle([False, True])
imtek_logo_path = Path(__file__).parent / "logo_imtek.png"
class NamePlateCards:
def __init__(
self,
partner_logo=None,
partner_color=None,
name_size=24,
company_size=12,
debug=False,
):
self.imtek_logo = Logo(imtek_logo_path, 10 * mm)
self.imtek_color = self._color_from_rgb(23, 17, 117)
self.partner_logo = partner_logo
if partner_color is None:
self.partner_color = self._guess_partner_color()
else:
self.partner_color = self._color_from_rgb(partner_color)
self.name_size = name_size
self.company_size = company_size
self.debug = debug
self._canvas = None
def generate(self, attendees_file, output_file="output.pdf", show=True):
attendees = self.parse_attendees(attendees_file)
for page_break, entry in zip(false_and_true, attendees):
self.draw_card(*entry)
if page_break:
self.canvas.showPage()
self.canvas.save()
if show:
subprocess.run(["open", output_file])
@property
def canvas(self):
if self._canvas is None:
self._canvas = canvas.Canvas("output.pdf", pagesize=A4)
return self._canvas
def _color_from_rgb(self, *args):
return tuple(x / 255 for x in args)
def _guess_partner_color(self):
if self.partner_logo is None:
return (0, 0, 0)
img = Image.open(self.partner_logo.path)
colors = sorted(img.getcolors(img.width * img.height), reverse=True)
if colors is None:
return (0, 0, 0)
else:
return self._color_from_rgb(*colors[0][1])
def parse_attendees(self, attendees_file):
with open(attendees_file, "r") as fh:
lines = [l.strip() for l in fh]
splited = [line.split("\t") for line in lines if line]
return [(c.strip(), f.strip(), l.strip()) for c, f, l in splited]
def box_helper(self):
if self.debug:
self.canvas.setStrokeColorRGB(1, 0, 0)
self.canvas.rect(0, 0, x_width, y_height)
def draw_card(self, company, first_name, last_name):
self.canvas.saveState()
y_pos = next(y_positions)
self.canvas.translate(x_pos, y_pos)
self.box_helper()
self.draw_field(company, first_name, last_name)
# next translate is relative to the first
self.canvas.translate(x_width, y_height * 2)
self.canvas.rotate(180)
self.box_helper()
self.draw_field(company, first_name, last_name)
self.canvas.restoreState()
def draw_field(self, company, first_name, last_name):
self.draw_logo(self.imtek_logo, "left")
if self.partner_logo is not None:
self.draw_logo(self.partner_logo, "right")
self.canvas.setFillColorRGB(*self.imtek_color)
self.canvas.setFont("Helvetica-Bold", self.name_size)
self.canvas.drawString(x_padding, y_padding + 18 * mm, first_name)
self.canvas.drawString(x_padding, y_padding + 8 * mm, last_name)
self.canvas.setFillColorRGB(*self.partner_color)
self.canvas.setFont("Helvetica-Bold", self.company_size)
self.canvas.drawString(x_padding, y_padding, company)
def draw_logo(self, img, position="left"):
if position == "left":
x_left = x_padding
else:
x_left = x_width - x_padding - img.width
self.canvas.drawImage(
img.path,
x_left + img.adjust_x,
y_height - y_padding - img.height + img.adjust_y,
**img.size,
mask="auto"
)

26
pdftools/ruler.py

@ -0,0 +1,26 @@
from pathlib import Path
from reportlab.lib.units import mm
from . import formfill
def ruler_overlay(original_form):
def ruler(c, item):
c.setStrokeColorRGB(0, 0, 1)
c.setFillColorRGB(0, 0, 1)
c.setLineWidth(0.5)
c.setFont("Helvetica", 8)
x_grid = [x*mm for x in range(10, 210, 10)]
y_grid = [y*mm for y in range(10, 297, 10)]
c.grid(x_grid, y_grid)
for x in range(10, 210, 10):
c.drawString((x-2)*mm, 5*mm, str(x))
for y in range(10, 297, 10):
c.drawString(4*mm, (y-0.5)*mm, str(y))
ofp = Path(original_form)
out_path = ofp.parent / f"{ofp.stem}_ruler.pdf"
formfill.fill(original_form, ruler, [1], output_file=out_path)

44
poetry.lock

@ -0,0 +1,44 @@
[[package]]
category = "main"
description = "Composable command line interface toolkit"
name = "click"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "7.0"
[[package]]
category = "main"
description = "PDF file reader/writer library"
name = "pdfrw"
optional = false
python-versions = "*"
version = "0.4"
[[package]]
category = "main"
description = "Python Imaging Library (Fork)"
name = "pillow"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "5.4.1"
[[package]]
category = "main"
description = "The Reportlab Toolkit"
name = "reportlab"
optional = false
python-versions = "*"
version = "3.5.13"
[package.dependencies]
pillow = ">=4.0.0"
[metadata]
content-hash = "02a2ad8ff93804f7fa328d882f2dae2863ffa491831651d312cc368638333ddc"
python-versions = "^3.7"
[metadata.hashes]
click = ["2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", "5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"]
pdfrw = ["0dc0494a0e6561b268542b28ede2280387c2728114f117d3bb5d8e4787b93ef4", "758289edaa3b672e9a1a67504be73c18ec668d4e5b9d5ac9cbc0dc753d8d196b"]
pillow = ["01a501be4ae05fd714d269cb9c9f145518e58e73faa3f140ddb67fae0c2607b1", "051de330a06c99d6f84bcf582960487835bcae3fc99365185dc2d4f65a390c0e", "07c35919f983c2c593498edcc126ad3a94154184899297cc9d27a6587672cbaa", "0ae5289948c5e0a16574750021bd8be921c27d4e3527800dc9c2c1d2abc81bf7", "0b1efce03619cdbf8bcc61cfae81fcda59249a469f31c6735ea59badd4a6f58a", "0cf0208500df8d0c3cad6383cd98a2d038b0678fd4f777a8f7e442c5faeee81d", "163136e09bd1d6c6c6026b0a662976e86c58b932b964f255ff384ecc8c3cefa3", "18e912a6ccddf28defa196bd2021fe33600cbe5da1aa2f2e2c6df15f720b73d1", "24ec3dea52339a610d34401d2d53d0fb3c7fd08e34b20c95d2ad3973193591f1", "267f8e4c0a1d7e36e97c6a604f5b03ef58e2b81c1becb4fccecddcb37e063cc7", "3273a28734175feebbe4d0a4cde04d4ed20f620b9b506d26f44379d3c72304e1", "39fbd5d62167197318a0371b2a9c699ce261b6800bb493eadde2ba30d868fe8c", "4132c78200372045bb348fcad8d52518c8f5cfc077b1089949381ee4a61f1c6d", "4baab2d2da57b0d9d544a2ce0f461374dd90ccbcf723fe46689aff906d43a964", "4c678e23006798fc8b6f4cef2eaad267d53ff4c1779bd1af8725cc11b72a63f3", "4d4bc2e6bb6861103ea4655d6b6f67af8e5336e7216e20fff3e18ffa95d7a055", "505738076350a337c1740a31646e1de09a164c62c07db3b996abdc0f9d2e50cf", "5233664eadfa342c639b9b9977190d64ad7aca4edc51a966394d7e08e7f38a9f", "52e2e56fc3706d8791761a157115dc8391319720ad60cc32992350fda74b6be2", "5337ac3280312aa065ed0a8ec1e4b6142e9f15c31baed36b5cd964745853243f", "5ccd97e0f01f42b7e35907272f0f8ad2c3660a482d799a0c564c7d50e83604d4", "5d95cb9f6cced2628f3e4de7e795e98b2659dfcc7176ab4a01a8b48c2c2f488f", "634209852cc06c0c1243cc74f8fdc8f7444d866221de51125f7b696d775ec5ca", "75d1f20bd8072eff92c5f457c266a61619a02d03ece56544195c56d41a1a0522", "7eda4c737637af74bac4b23aa82ea6fbb19002552be85f0b89bc27e3a762d239", "801ddaa69659b36abf4694fed5aa9f61d1ecf2daaa6c92541bbbbb775d97b9fe", "825aa6d222ce2c2b90d34a0ea31914e141a85edefc07e17342f1d2fdf121c07c", "87fe838f9dac0597f05f2605c0700b1926f9390c95df6af45d83141e0c514bd9", "9c215442ff8249d41ff58700e91ef61d74f47dfd431a50253e1a1ca9436b0697", "a3d90022f2202bbb14da991f26ca7a30b7e4c62bf0f8bf9825603b22d7e87494", "a631fd36a9823638fe700d9225f9698fb59d049c942d322d4c09544dc2115356", "a6523a23a205be0fe664b6b8747a5c86d55da960d9586db039eec9f5c269c0e6", "a756ecf9f4b9b3ed49a680a649af45a8767ad038de39e6c030919c2f443eb000", "ac036b6a6bac7010c58e643d78c234c2f7dc8bb7e591bd8bc3555cf4b1527c28", "b117287a5bdc81f1bac891187275ec7e829e961b8032c9e5ff38b70fd036c78f", "ba04f57d1715ca5ff74bb7f8a818bf929a204b3b3c2c2826d1e1cc3b1c13398c", "ba6ef2bd62671c7fb9cdb3277414e87a5cd38b86721039ada1464f7452ad30b2", "c8939dba1a37960a502b1a030a4465c46dd2c2bca7adf05fa3af6bea594e720e", "cd878195166723f30865e05d87cbaf9421614501a4bd48792c5ed28f90fd36ca", "cee815cc62d136e96cf76771b9d3eb58e0777ec18ea50de5cfcede8a7c429aa8", "d1722b7aa4b40cf93ac3c80d3edd48bf93b9208241d166a14ad8e7a20ee1d4f3", "d7c1c06246b05529f9984435fc4fa5a545ea26606e7f450bdbe00c153f5aeaad", "db418635ea20528f247203bf131b40636f77c8209a045b89fa3badb89e1fcea0", "e1555d4fda1db8005de72acf2ded1af660febad09b4708430091159e8ae1963e", "e9c8066249c040efdda84793a2a669076f92a301ceabe69202446abb4c5c5ef9", "e9f13711780c981d6eadd6042af40e172548c54b06266a1aabda7de192db0838", "f0e3288b92ca5dbb1649bd00e80ef652a72b657dc94989fa9c348253d179054b", "f227d7e574d050ff3996049e086e1f18c7bd2d067ef24131e50a1d3fe5831fbc", "f62b1aeb5c2ced8babd4fbba9c74cbef9de309f5ed106184b12d9778a3971f15", "f71ff657e63a9b24cac254bb8c9bd3c89c7a1b5e00ee4b3997ca1c18100dac28", "fc9a12aad714af36cf3ad0275a96a733526571e52710319855628f476dcb144e"]
reportlab = ["069f684cd0aaa518a27dc9124aed29cee8998e21ddf19604e53214ec8462bdd7", "09b68ec01d86b4b120456b3f3202570ec96f57624e3a4fc36f3829323391daa4", "0c32be9a406172c29ea20ff55a709ccac1e7fb09f15aba67cb7b455fd1d3dbe0", "233196cf25e97cfe7c452524ea29d9a4909f1cb66599299233be1efaaaa7a7a3", "2b5e4533f3e5b962835a5ce44467e66d1ecc822761d1b508077b5087a06be338", "2e860bcdace5a558356802a92ae8658d7e5fdaa00ded82e83a3f2987c562cb66", "3546029e63a9a9dc24ee38959eb417678c2425b96cd27b31e09e216dafc94666", "4452b93f9c73b6b70311e7d69082d64da81b38e91bfb4766397630092e6da6fd", "528c74a1c6527d1859c2c7a64a94a1cba485b00175162ea23699ae58a1e94939", "6116e750f98018febc08dfee6df20446cf954adbcfa378d2c703d56c8864aff3", "6b2b3580c647d75ef129172cb3da648cdb24566987b0b59c5ebb80ab770748d6", "727b5f2bed08552d143fc99649b1863c773729f580a416844f9d9967bb0a1ae8", "74c24a3ec0a3d4f8acb13a07192f45bdb54a1cc3c2286241677e7e8bcd5011fa", "98ccd2f8b4f8636db05f3f14db0b471ad6bb4b66ae0dc9052c4822b3bd5d6a7d", "a5905aa567946bc938b489a7249c7890c3fd3c9b7b5680dece5bc551c2ddbe0d", "acbb7f676b8586b770719e9683eda951fdb38eb7970d46fcbf3cdda88d912a64", "b5e30f865add48cf880f1c363eb505b97f2f7baaa88c155f87a335a76515a3e5", "be2a7c33a2c28bbd3f453ffe4f0e5200b88c803a097f4cf52d69c6b53fad7a8f", "c356bb600f59ac64955813d6497a08bfd5d0c451cb5829b61e3913d0ac084e26", "c7ec4ae2393beab584921b1287a04e94fd98c28315e348362d89b85f4b464546", "d476edc831bb3e9ebd04d1403abaf3ea57b3e4c2276c91a54fdfb6efbd3f9d97", "db059e1a0691c872784062421ec51848539eb4f5210142682e61059a5ca7cc55", "dd423a6753509ab14a0ac1b5be39d219c8f8d3781cce3deb4f45eda31969b5e8", "ed9b7c0d71ce6fe2b31c6cde530ad8238632b876a5d599218739bda142a77f7c", "f0a2465af4006f97b05e1f1546d67d3a3213d414894bf28be7f87f550a7f4a55", "f20bfe26e57e8e1f575a9e0325be04dd3562db9f247ffdd73b5d4df6dec53bc2", "f3463f2cb40a1b515ac0133ba859eca58f53b56760da9abb27ed684c565f853c", "facc3c9748ab1525fb8401a1223bce4f24f0d6aa1a9db86c55db75777ccf40f9"]

36
pyproject.toml

@ -0,0 +1,36 @@
[tool.poetry]
name = "pdftools"
version = "0.1.0"
description = "Custom tools for pdf related stuff"
authors = ["Holger Frey <frey@imtek.de>"]
license = "Beerware"
[tool.poetry.dependencies]
python = "^3.7"
reportlab = "^3.5"
pdfrw = "^0.4.0"
click = "^7.0"
[tool.poetry.dev-dependencies]
[build-system]
requires = ["poetry>=0.12"]
build-backend = "poetry.masonry.api"
[tool.poetry.scripts]
nameplates = 'pdftools.cli:nameplates'
addruler = 'pdftools.cli:addruler'
[tool.black]
line-length = 79
py37 = true
include = '\.pyi?$'
exclude = '''
/(
\.git
| \.tox
| \.venv
| build
| dist
)/
'''

43
readme.md

@ -0,0 +1,43 @@
PDF helpers for the work at IMTEK
=================================
This package provides some cli commands:
- `nameplates` generate a pdf with nameplates
- `addruler` adds a blue ruler to a pdf
nameplates
----------
Usage: nameplates [OPTIONS] ATTENDEES [LOGO]
creates a pdf with name plate cards
The attendees file should be a tab separated text file with no headers.
Put the company name in the first column, the given name in the second and
the last name in the third column, e.g:
Hochimin Enterprizes \t Jane \t Doe \n
The positioning of the optional logo file can be controlled with the size
and adjustment options.
Options:
-s, --size INTEGER [default: (10 mm)]
-y, --adjust_y INTEGER [default: ( 0 mm)]
-x, --adjust_x INTEGER [default: ( 0 mm)]
--help Show this message and exit.
addruler
--------
Usage: addruler [OPTIONS] PDF
adds a blue ruler overlay to a pdf file
Options:
--help Show this message and exit.
Loading…
Cancel
Save