PDF Calendar Making in Python

So I wanted to have a calendar on paper that I could put on my fridge and see how much time I’ve been wasting. I know that there are more than enough programs out there that can accomplish this task, however I still wanted to make one myself. I was a bit inspired by this old Unix based program that would generate calendars in postscript (which I can not find anymore).

Overall Goals

So I have a few small goals with this project. I am mostly thinking that by making this project, as a side effect I will gain some knowledge in certain areas…

  • Gain more python programming experience. I do have some experience with python, but it is most with Jupyter notebooks as opposed stand alone scripts.
  • Learning about generating PDF files using PyFPDF
  • Learning about the python calendar object. I don’t want to learn the Doomsday algorithm… I would rather just use a piece of code that works.
  • Learn a bit about using Yaml files for passing in various parameters.

Python Calendar

There is a fair bit of information out there about Python’s calendar object. Calendar.py allows the creation of a plain calendar along with a text and html versions. For this project we just use the plain version and do some extra decorating ourselves.

Its pretty simple to create a calendar object, you just need to specify the starting day of the week; calendar.SUNDAY for North American style calendars and calendar.MONDAY for the European style (and yes, you can use any day, so you can start your calendars on a Wednesday if you really want).

import calendar

myCal = calendar.Calendar (calendar.SUNDAY)

Using a for loop, one can easily iterate over all of the days of the month (using the calendar.itermonthdays function). If the returned value has a value of zero, then the day is from the previous or next month and hence that day is to be used as a space (or is empty). Note that the month values go from 1 – 12.

for day in myCal.itermonthdays (YEAR, MONTH):
    if (day != 0):
        # Find the X and Y offsets
        # Set any PDF parameters (like fonts and colours)
        # 'Write' the day number to the PDF
    else:
        # is a day from the previous or next month...
        # treat it as a space

The text of the weekday and month names can also be obtained from the calendar object. Note that Monday is day 0 and Sunday is day 6… so when making the calendar it may need to be adjusted.

monthName = calendar.month_name [MONTH]

for i in range (0, 7):
    dayName = calendar.day_name [i]

PyFPDF

PyFPDF is a package used to create PDF files from a python program. Its pretty easy to write some text and draw some lines/shapes to a PDF file.

The basic usage is to create an FPDF instance, add a page, add some cells for text, rects and lines for rectangles and lines, and then finally write out the file using output.

First we create an instance… note that the page orientation, units (inches), and paper size is specified (yes I use letter not A4 paper).

from fpdf import FPDF

pdfFile = FPDF (orientation='L', unit='in', format='letter')

pdfFile.add_page ()

To write some text we specify a position, a font, and create a cell. The positions are specified from the top left corner of the page (in our case we are 2 inches from the left and 1 inch from the top). Next we set our font: helvetica (as if there is another choice) and in bold. And finally we create a box/cell (with width/height of 1/0.75 inches) that will contain the text (which will be centred).

pdfFile.set_xy (2, 1);
pdfFile.set_font ('helvetica', style='B', size=22)
pdfFile.cell (1, 0.75, txt='Abc 123', align='C')

Line drawing is pretty easy… you just need to specify the starting and ending coordinates (again the origin is located at the top left corner of the page, and x increases going left to right while y increases going from top to bottom).

The line in drawn using the current graphics states. The line width uses the same units as everything else (so inches), hence we need to specify a small number to have something reasonable. Colours are specified in a standard RGB format with values ranging from 0 to 255.

pdfFile.set_line_width (0.02)
pdfFile.set_draw_color (255, 128, 0)
pdfFile.line (1, 2, 9, 2)  # from (1,2) to (9,2) in inches

Shapes are created in a similar manor. The rect function is used to draw a box/square/rectangle and the ellipse function is for circles/ellipses. Slightly different that the line function, the position parameters are specified with the starting x & y position and the width/height. Finally the style specifies if the shape is to be filled in (style=’F’), or outlined (style=’D’) or both (style=’FD’).

pdfFile.set_fill_color (0, 128, 255)
pdfFile.rect (1, 2.5, 1, 0.75, style='F')  # again positions in inches
pdfFile.ellipse (3, 3.5, 1, 1, style='D')

Finally to create the PDF, that is to write it a file, one simply calls the output function.

pdfFile.output ('OUTPUT.pdf')

Implementation Details

The user needs to specify a lot of parameters (sizes, colours, offsets, …) so using a yaml file makes life quite easy. Basically the format is a simple key-value pair system which one entry per line. The yaml package does the parsing and returns a dictionary containing the values.

A few different calendar styles can be created by varying a few parameters:

  • Overall width and height
  • Month or full year
  • Portrait or Landscape
  • Text Colours, Line/Fill Colours
  • Horizontal/Vertical Lines
  • Using boxes or circles
  • Font family (times roman or helvetica) and font style (bold, italic)
  • Title alignment (center or left)
  • Month and weekday title proportions

The main steps in creating a calendar is as follows:

  • Get the user parameters
  • Create PDF and calendar object instances
  • Create the title and week day titles
  • Create horizontal/vertical lines, boxes, or circles
  • Iterate through the days and create the day numbers in the appropriate places
  • Write the PDF out to a file

Yearly calendars are created using the monthly calendar function along with a for loop and extra parameters for spacing.

For debugging, any cell function call can be ‘decorated’ by drawing a bounding box using the border parameter. Therefore, feedback of placement of various items can be seen visually.

Also, the graphics state functions are set for each drawing command. This may not be super efficient but overall things are drawn a handful number of times.

The code for this project can be found here…

Results

Some example results from the script can be seen (and downloaded) here…

Conclusion

I am happy that I can make PDF based calendars that I can put on my fridge. However I have a few issues:

  • I am not extremely happy with the code. It can be improved to make it a little more flexible to allow generation of a variety of calendar styles. However, its a cost-benefits type of thing… one has to wonder how much effort one needs to invest into something that will be used once a year
  • I wish the FPDF package had a bit more functionality; particularly control the kerning amount, being able of obtain/manipulate clipping paths, and saving and restoring graphic states.

 

No Comments

Add your comment