← Back to Coffee now

An easy-to-print dated A5 planner: the script

Coffee nowOpen original ↗

With a lot of hobbies, there’s always the bit that’s the chore. Maybe washing your brushes if you’re painting, figuring out code if you’re blogging, weighing the ingredients if you’re baking (endless washing if you’re running?). With making paper planners, it’s the dating, printing, cutting and punching.

Designing your layout is fun; adding the stickers and writing in your plans or journalling is fun. Dating, printing, cutting and punching, just uggg. So anything that can make your life easier there is definitely a massive win.

Back when I (Clare) was last talking about my making my 2022 planner, I had a design where each week went over two spreads, with the first spread being specific to each week (i.e. containing calendars and dates), and the second spread being generic and repeated each week. However, since then, I’ve simplified things and now want to print a planner with only one spread per week.

The trouble is this means every page of the planner is unique, and then, unless you have a duplex printer that does ‘booklet printing’, or you print on paper already at the size of your final planner (neither of which is true for me), you can’t simply make the planner pages in the order you eventually will have your pages. For each of my A4 sheets of paper, I need the four A5 planner pages to go in the order - front left, page 4; front right, page 1; back left, page 2; back right, page 3.

This is definitely more clear with a diagram:

diagram of page order for booklet printing of a planner

So there’s a chore before you even get to the printing - re-ordering your pages, by hand. This is extra tedious, and very error prone as you lose your place as you’re going along, and have to try and work out what’s on the back of what - probably more than once.

So I had to ask James to step in here with a script. One that didn’t just add the dates to my design but also ordered the pages; however, before we could actually get onto the writing and using of such a script, we needed templates for my actual design that would work for that.

Making the SVG planner templates

As my final planner is A5, creating the templates as A5 pages (well a little bit smaller than A5 to allow for a margin), rather than as A4 spreads, is much more flexible given that it means pages can be reordered easily if e.g. more page styles need to be added in the future.

Then page design is separated from the ordering of the pages into the overall planner (i.e. we don’t need to worry about whether the page will be on the left or right side at the point we design the template).

At the moment, I only have three A5 page styles that are repeated for the year:

  • an A5 monthly summary that always becomes a right-hand page - dated elements are a month calendar grid and the name of the month
  • an A5 left-hand week page - this also has a month calendar and a header saying what week it is
  • an A5 right-hand page that has a header with the date and a dated box for each day of the week

A rough sketch (page 1 is on the back of page 2) on this is below:

diagram of three templates as would be used in the planner

The first three pages of the planner as it would be for 2023. Page 4 would be a repeat of page 2 but for week 2 and so on.

All the pages are in the SVG vector format - they are created in Inkscape. However, what you don’t see when looking at SVGs in graphics software is they are actually just text (xml), with that text describing various aspects of the images in a well-documented way (see e.g. this mdn tutorial).

Because the files are text, it is then easy to use simple scripts to modify them through a find-and-replace strategy.

So the next step is, instead of having ‘January’ etc., to use placeholder ‘strings’ for all the dated elements (circled in red in diagram above) that need to be changed each week. However, it’s important that the strings chosen don’t occur elsewhere in the SVG, so to reduce the chance of matching something not intended, we used the convention (inspired by the python string format syntax) of enclosing the items to replace in curly braces.

Screenshot of Inkscape showing an example A5 template with placeholder text

Example A5 template before the find-replace

One extra issue is that the number of rows in the month calendar grids varies depending on the number of weeks in the month, which means the geometry of the surrounding elements needs to be tweaked to accommodate that.

To overcome this, in the case of the month summary, we have made three templates for each of the three options (months can extend over 4, 5 or 6 weeks). This works OK for now, but isn’t the easiest to maintain as any updates need to be made on all three copies. The other template with a calender grid has an area that is an OK size to just about allow for the 6-week grid.

Once, you have your templates, you then need the script to run on them. The rest of the post explains the script to the world, just in case anyone else can be inspired by it.

Automating the planner creation

To automate the repetitive parts of replacing the placeholder values with the appropriate date information for each month, and ordering the A5 pages into A4 spreads, I (James) wrote a Python script.

The planner generation script:

  1. performs a find-and-replace on the date-related elements to create all the A5 individual SVG pages for a year
  2. combines the A5 page SVGs into A4 SVGs
  3. creates a PDF from the SVGs that can be sent to a standard single-sided A4 printer without further intervention

Code for each step is described below.

1. Create a year’s worth of A5 SVGs

The script works through the weeks in the year creating each of the A5 SVGs in the order that they will appear in the finished planner. For each page, it picks the appropriate template, does the find-replace and writes the resulting file.

Some of the replacements need to be updated each time the script creates the pages for a new week, others (e.g. the replacements for the calendar grid) are the same for a whole month. As the script steps through the weeks, it identifies when it has reached the start of a new month and updates the per-month replacements before generating the start of month pages.

Determining what values to use for the replacements needs to take into account the calendar for the year. For example, we need to figure out which month each week is in and what dates correspond to each day of the week. Fortunately, the python standard library includes the datetime module, which is used to take care of much of the work of dealing with dates. This leaves us free to forget about the rhyme for remembering the number of days in each month and whether or not it’s a leap year.

The following snippet shows how the script determines the replacements for a calendar grid of a given month. It creates a date object representing the first day of the month and subtracts the appropriate number of days to find the first day of the grid, which is the Monday of the first week (e.g. if the first of the month is a Wednesday it subtracts 2 days). Then it loops over the 42 grid positions advancing the date by 1 day each time it goes through the loop. In each case, it can check if this date is within the month (dates at the start and end of the grid can be in the preceding or following months) and if so, set up the replacement with the correct the day of the month.

# Setup replacements for calendar grid. SVG templates contain grids like
#  M    T    W    T    F    S    S
# {1}  {2}  {3}  {4}  {5}  {6}  {7}
# ...
# {36} {37} {38} {39} {40} {41} {42}
#
# These replacements are used to replace the days of the month (or empty string
# if the day is outside the current month)

# We will want to know the number of weeks in the month. Use this set to keep
# track of them.
weeks_in_month = set()

# Iterate over the 42 days starting from first day in the grid
first_day_of_month = datetime.date(year,month,1)
first_day_of_grid = first_day_of_month - datetime.timedelta(days=first_day_of_month.isocalendar().weekday - 1)

for day in range(0,42):
    date = first_day_of_grid + datetime.timedelta(days=day)
        if date.month != month:
        # replace dates that are outside current month with empty string
    template_page_manager.replacements['{'+ str(day+1) + '}'] = ''  
    else:
        template_page_manager.replacements['{'+ str(day+1) + '}'] = str(date.day)
        weeks_in_month.add(date.isocalendar().week)

Similar code is used for the other placeholder replacements.

2. Create combined A4 SVG pages

To create the A4 spreads for printing, we wanted to arrange two of the A5 pages side by side to form the A4 page. The easiest way I found to do that was to create an SVG that referred to each of the A5 pages using an internal SVG image tag.

When combining them to form an A4 spread, padding is added to position the templates approximately centrally within each half of the spread.

Starting at the beginning, you take four pages at a time and use those to create two A4 SVGs optionally reordering the pages as shown above to put the 4th and 1st page on the front and 2nd and 3rd pages on the back (i.e. the arrangement shown in top diagram above). That part is taken care of by the following code:

if args.reorder:
    # Reorder pages for printing 
    first_a4_right  = a5_pages.pop(0)
    second_a4_left  = a5_pages.pop(0)
    second_a4_right = a5_pages.pop(0)
    first_a4_left   = a5_pages.pop(0)
else:
    # Leave pages in order
    first_a4_left   = a5_pages.pop(0)
    first_a4_right  = a5_pages.pop(0)
    second_a4_left  = a5_pages.pop(0)
    second_a4_right = a5_pages.pop(0)

For each of the A4 spreads, the script generates a rather minimal SVG file containing only the two A5 SVG images plus a couple of short lines that we wanted to mark the centreline of the spread to aid trimming. The contents of one of these SVGs looks like this:

<svg version="1.1" width="297mm" height="210mm" xmlns="http://www.w3.org/2000/svg">
<image x="5.5mm"   y="5mm" width="135mm" height="200mm" href="../../planner_files_2023/a5_pages/002_week_pad_week1.svg"/>
<image x="156.5mm" y="5mm" width="135mm" height="200mm" href="../../planner_files_2023/a5_pages/003_week_daylist_week1.svg"/>
<line x1="50%" x2="50%" y1="90%" y2="91%" stroke="black" stroke-width="0.5"/>
<line x1="50%" x2="50%" y1="9%" y2="10%" stroke="black" stroke-width="0.5"/>
</svg>

This SVG isn’t generated using a template, but directly written by the following python function:

# Function to create an A4 svg that includes to two 135 by 200 mm (slightly smaller than A5)
# pages as svg images:
def write_a4_svg(left_a5, right_a5, a4_filename):
    with open(a4_filename,'w') as output_svg:
        output_svg.write('<svg version="1.1" width="297mm" height="210mm" xmlns="http://www.w3.org/2000/svg">\n')
        if left_a5 is not None:
          output_svg.write(f'<image x="5.5mm"   y="5mm" width="135mm" height="200mm" href="../../{left_a5}"/>\n')
        if right_a5 is not None:
           output_svg.write(f'<image x="156.5mm" y="5mm" width="135mm" height="200mm" href="../../{right_a5}"/>\n')
        # Add two short lines to mark the centerline to aid cutting
        output_svg.write('<line x1="50%" x2="50%" y1="90%" y2="91%" stroke="black" stroke-width="0.5"/>\n')
        output_svg.write('<line x1="50%" x2="50%" y1="9%" y2="10%" stroke="black" stroke-width="0.5"/>\n')
        output_svg.write('</svg>\n')

Which is called once for each of the A4 spreads:

a4_filename = (os.path.join(template_page_manager.a4_svgs_dir, f'{a4_num:03d}.svg'))
a4_svg_files.append(a4_filename)
write_a4_svg(left_a5=first_a4_left, right_a5=first_a4_right, a4_filename=a4_filename)
a4_num += 1
a4_filename = (os.path.join(template_page_manager.a4_svgs_dir, f'{a4_num:03d}.svg'))
a4_svg_files.append(a4_filename)
write_a4_svg(left_a5=second_a4_left, right_a5=second_a4_right, a4_filename=a4_filename)  
a4_num += 1

Note that for general use, this seems a bit clunky as this SVG requires the image SVGs to be in the correct relative locations. It might be easier to manage if everything were combined into a single file. Probably that could be achieved by combining elements from the SVGs into a single file and using the <use> element to arrange them. For our use case, this simple way using image is OK as the next step is to convert these SVG files into PDFs…

3. Creating the final PDF

For printing, we wanted to convert these SVG files into PDF to make them portable for printing. We also wanted combine all the A4 PDFs into a single multipage file to make the printing process less tedious.

To do this, we used external tools (run from the script using the python subprocess module). There are probably many other options that could work, but we used CairoSVG for the conversion and Ghostscript for combining PDFs.

Convert all the A4 SVGs into PDFs using cairosvg:

for a4_svg in a4_svg_files:
    a4_pdf = os.path.basename(a4_svg)[0:-3] + 'pdf'
    a4_pdf = os.path.join(template_page_manager.a4_pdfs_dir,a4_pdf)
    a4_pdf_files.append(a4_pdf)
    cmd = f'cairosvg -o {a4_pdf} {a4_svg}'
    logging.debug(f"Executing command: {cmd}")
    subprocess.run(cmd, shell=True)

This processes each SVG using commands like:

cairosvg -o planner_files_2023/a4_pdfs/004.pdf planner_files_2023/a4_svgs/004.svg

Create a final output by merging all these PDFs into a single file using Ghostscript:

merged_pdf_file = os.path.join(template_page_manager.year_dir,f'merged_year_{year}.pdf')
cmd = f'gs -dBATCH -dNOPAUSE -q -sDEVICE=pdfwrite -dAutoRotatePages=/None -sOutputFile={merged_pdf_file} {" ".join(a4_pdf_files)}'
logging.debug(f"Executing command: {cmd}")
subprocess.run(cmd, shell=True)

The command run here is something like:

gs -dBATCH -dNOPAUSE -q -sDEVICE=pdfwrite -dAutoRotatePages=/None -sOutputFile=planner_files_2023/merged_year_2023.pdf planner_files_2023/a4_pdfs/001.pdf planner_files_2023/a4_pdfs/002.pdf planner_files_2023/a4_pdfs/003.pdf planner_files_2023/a4_pdfs/004.pdf ... planner_files_2023/a4_pdfs/064.pdf

For what it’s worth, in principle Inkscape itself could be used in a command line mode to create the PDFs. However, on my system, opening files from the command line with Inkscape seems broken (probably due to this known issue).

Screenshot of inkscape showing an example A5 template

Final PDF after find and replace and combining pages

Making your own planner

As planner layouts are quite personal, these scripts are’t very configurable (e.g. we’re only working with a Monday-start week, being from the UK), but if you were thinking of trying the same for your own layout, you’d first create templates with whatever art, dot grids and fonts etc. you want with in SVG format, and then tweak the find-and-replace done by the script.

It would also be possible to update the script to add extra templates (e.g. say you want 3 or 4 pages per week or an end of month retrospective page).

Gotchas and future improvements

While following this flow, I found a few oddities when viewing the Inkscape-created SVGs in other programs (I tried browsers such as firefox/chrome as well as cairosvg for converting into PDF).

Although in principle SVG is a standard, so should look the same everywhere (assuming these were valid SVGs - I didn’t actually check), I found that some elements could fail to show or display slightly differently in some viewers.

Rather than fully investigate those, I updated the templates by removing and redrawing those elements. Along the way I also found a few duplicated elements in the SVGs. It could be that some of these issues stem from the rather old version of Inkscape that we’d used a few years ago to first create some of these SVG drawings.

It would definitely be possible to go further and automate more of the geometrical elements of the planner (the dot grids, corner markers etc.) probably using something like drawsvg.


Extra shots

Comments (0)

Comments (0)

No comments yet.