Our first born is about to arrive. Instead of writing out all the birth announcements by hand I decided to write a little python script that creates a PDF that matches sticker sheets (Herma 8632 format). It exactly took me one laptop battery charge to create this script. I refactored a bit later to be a bit more OO.


#!/usr/bin/env python3

from fpdf import FPDF
import sys
import os

# Very simple CSV to Herma 8632

# CSV expected in following format (comma separated , WITH header):
#
#   Name, Relation, Street + number, ZIP, City, Country
#   John Doe, friends, Stationstraat 12, 1234 DD, Duckcity, NL


def main():

  args = InputParser(sys.argv)

  addresses = CSVParser(args.inputFile)
  hermaTemplate = Herma()

  hermaTemplate.drawAddressesOnHerma(addresses.data)
  hermaTemplate.save(args.outputFile)

# ------------------------------------------------------------------

## Class Definitions
class InputParser:
  def __init__(self, args) -> None:
    self.args = args
    if len(args) != 3:
      self.help()
      exit(1)

    self.input = args[1]
    self.output = args[2]

    if not os.path.exists(self.input):
      print(f"Inputfile \"{self.input}\" does not exist!")
      exit(1)

  def help(self):
    print(f"Use python3 {self.args[0]} input.csv output.pdf")

  @property
  def inputFile(self):
    return self.input

  @property
  def outputFile(self):
    return self.output

class CSVParser:
  def __init__(self, filename) -> None:
    self.addresses = []
    self.filename = filename
    self.parse()

  def parse(self):
    # TODO: File present checking
    with open(self.filename) as f:
      data = f.readlines()
      for d in data[1:]:
        split = d.split(',')
        address = "\n".join([split[0], split[2], split[3], split[4], split[5]])
        self.addresses.append(address)

  @property
  def data(self):
    return self.addresses

class Herma:
  def __init__(self) -> None:
    self.pdf = FPDF()
    self.pdf.add_page()
    self.pdf.set_auto_page_break(False)
    self.pdf.set_font("Times", size = 10)

    self.CELL_WIDTH = 63.5
    self.CELL_HEIGHT = 38.1
    self.LEFT_PAGE_MARGIN = 7.21
    self.TOP_PAGE_MARGIN = 15.15
    self.BETWEEN_CELL_SPACE = 2.54

    self.row = 0
    self.col = 0

    self.MAX_ROWS = 7
    self.MAX_COLS = 3

  def drawAddressesOnHerma(self, addresses):
    for a in addresses:
      x,y = self._selectCell(self.row, self.col)
      self._drawAddress(x, y, a)
      self._autoIncrement()


  def _drawAddress(self, x, y, address):
    self.pdf.set_xy(x, y+10)

    for l in address.split('\n'):
      self.pdf.set_x(x)
      self.pdf.cell(w = self.CELL_WIDTH, h = 5, ln = 1, txt = l, align= "C")


  def _selectCell(self, row, col):
    x = self.LEFT_PAGE_MARGIN + (col % self.MAX_COLS) * (self.BETWEEN_CELL_SPACE + self.CELL_WIDTH)
    y = self.TOP_PAGE_MARGIN + (row % self.MAX_ROWS * self.CELL_HEIGHT)

    self.pdf.set_xy(x, y)
    self.pdf.cell(w = self.CELL_WIDTH, h = self.CELL_HEIGHT, txt = "", border = 0)

    return x,y

  def _autoIncrement(self):
    self.col += 1

    if self.col % self.MAX_COLS == 0:
      self.row += 1
      if self.row % self.MAX_ROWS == 0:
        self.pdf.add_page()
        self.col = 0
        self.row = 0

  def save(self, filename):
    self.pdf.output(filename)
    print(f"Please find your file here: {filename}")

main()