Generating birthday party invitations

Problem statement

Just like every year, I needed to generate and print birthday invitations for our kids’ birthday parties.

I thought it’s a rather straightforward exercise in a few open-source tools. But… I was told that what’s straightforward for me… well, looks like magic to others.

In this post I’ll walk through my workflow and share as much as I can. But to protect all the innocents, I’ll skip the actual names/dates.


The task is straightforward: party invitations for two birthday parties. Because we have some style, I’ve made it a bit more difficult for myself and also designed the envelopes for the invitations.

Overall workflow:

original layout

Now that I’ve put it in a graph, it looks rather intimidating. But it isn’t, honest!

And at the end you get invites:

invite kid1 invite kid2

and a matching set of envelopes.

How the sausage gets made

I’ll take it one at a time…


The graphics part starts with hunting for some free clipart pictures to use that particular year. Usually searching in duckduckgo or google images with “free something clipart” (e.g. “free monkey clipart” or “free trampoline clipart”) gets me plenty of usable pictures (for personal use).

Since I was lazy, this year I simplified things by carefully using layers in gimp:

gimp layers

So I managed to put both kids into the same gimp project. It doesn’t hurt that they’ve chosen the same venue for their party this year, which made the rest also easier.

But basically – the gimp project is a 300dpi A5 image, some background, some text using a decent font, and the cliparts arranged around. Still, an hour or two to get it all down.

QR code

This one’s super easy, barely an inconvenience:

$ apt install qrencode
$ qrencode -s 3 -l L -o "geo.png" "geo:47.37510465,8.50891113"

gets me geo.png with the geo coordinates. Scanning the QR triggers the phone’s native GPS (maps) app1.

LaTeX input

If you don’t know already, LaTeX is a powerful typesetting system. Usually used for things like scientific (math) papers, Master’s theses and the like. But the true power of it lies in its textual input. Which allows simple programmatic generation of that LaTex source. And that lends itself to various hackery.

And the final output is a beautiful *.pdf (one invite or envelope per page), ready to be printed.

Since I’m lazy by nature, I recycle the same basic LaTeX source(s) over and over.

So for example, the LaTeX source for the invites is:



    {\huge #1,}\\[2em]
    du bist zu meiner Geburtstagsparty eingeladen.\\

    {\bfseries Wann:} Samstag, #2 14:30 -- ca. 17:00\\
    {\bfseries Wo:} Ein spassiger Ort, Musterstrasse 1, Kloppenburg

    {\bfseries Bitte gib mir bis zum 1.2.2022 Bescheid ob du kommst:
    070 123 45 67} (iMessage, SMS, Signal, oder WhatsApp){\bfseries .}\\[2em]}


    Auch die Eltern dürfen gerne bleiben. Es wird ein paar Erfrischungen und einen
    Sitzplatz geben.\\

    Und wenn Sie weitere Fragen haben, stehen wir Ihnen gerne zur Verfügung.


\invitation{Lieber Adam}{2.2.2022}
\invitation{Liebe Eva}{2.2.2022}

and for the envelopes:





\envelope{Kid1 Jirků\\Musterstrasse 1\\8000 Zürich}{Adam First\\Musterstrasse 5\\8000 Zürich}
\envelope{Kid1 Jirků\\Musterstrasse 1\\8000 Zürich}{Eva Second\\Musterstrasse 5\\8000 Zürich}

Is that a paragon of elegant LaTex code? No way. I’m little more than a newbie.

Does it work, though? Absolutely!

LaTeX generator

So wrapping all that LaTeX goodness into a Ruby script gives me the generator that takes a list of invited kids, and spits out the appropriate *.tex files… is beyond trivial:

#!/usr/bin/env ruby
# encoding: utf-8

Invitation =, :to, :salutation, :date)
city = "|8000 Zürich"
local_ = "Jirků|Musterstrasse 1" + city
kid2 = "Kid2 " + local_
kid1 = "Kid1 " + local_
date_kid2 = '1.2.2022'
date_kid1 = '2.2.2022'

kid1_invitations = [, "Adam First|Musterstrasse 5" + city, "Lieber Adam", date_kid1),, "Eva Second|Musterstrasse 5" + city, "Liebe Eva", date_kid1),

kid2_invitations = [, "Adam First|Musterstrasse 5" + city, "Lieber Adam", date_kid2),, "Eva Second|Musterstrasse 5" + city, "Liebe Eva", date_kid2),

def envelopes_for(bg, invitations)
  out = []
  out << <<-'EOS'
% snip -- most of that latex envelope code from above
  out << "\\CenterWallPaper{0.6}{#{bg}}"
  def texify(str)
    str.gsub(/\|/, '\\\\\\\\').sub(/CZ$/, 'Czech Republic')
  invitations.each do |invitation|
    out << "\\envelope{#{texify(invitation.from)}}{#{texify(}}"
  out << %s[\end{document}]

def invitations_for(bg, invitations)
  out = []
  out << <<-'EOS'
% snip -- most of that latex invite code from above
  out << "\\LLCornerWallPaper{1.0}{#{bg}}"
  def texify(str)
    str.gsub(/\|/, '\\\\\\\\').sub(/CZ$/, 'Czech Republic')
  invitations.each do |invitation|
    out << "\\invitation{#{texify(invitation.salutation)}}{#{texify(}}"
  out << %s[\end{document}]
end'envelopes-kid1.tex', 'w') { |f|
  f.puts envelopes_for('env-kid1.png', kid1_invitations) }'envelopes-kid2.tex', 'w') { |f|
  f.puts envelopes_for('env-kid2.png', kid2_invitations) }'invitations-kid1.tex', 'w') { |f|
  f.puts invitations_for('inv-kid1.png', kid1_invitations) }'invitations-kid2.tex', 'w') { |f|
  f.puts invitations_for('inv-kid2.png', kid2_invitations) }

Again, is that the greatest Ruby code known to man? Nah. It gets the job done, though.

Makefile to tie it together

To orchestrate the whole thing, there’s a Makefile (for GNU make) to run the whole shebang:

	ruby generate.rb
	pdfcslatex invitations-kid1.tex
	pdfcslatex invitations-kid2.tex
	pdfcslatex envelopes-kid1.tex
	pdfcslatex envelopes-kid2.tex
	pdftk envelopes-kid1.pdf cat 1-endwest output envelopes-kid1.rot.pdf
	pdftk envelopes-kid2.pdf cat 1-endwest output envelopes-kid2.rot.pdf
	rm -f *.aux *.log
	cat howtoprint.txt

	rm -f *.aux *.log *.tex *.pdf


The printing on C6 envelopes on our HP MFP M477fdw printer is fun in itself, so the pdftk command is there to flip the envelope PDFs by 90° CCW and the end of that Makefile displays the hard won instructions for Acrobat Reader:

# how to print on HP MFP M477fdw
# 1) use the *.rot.pdf
# 2) put in envelopes as shown in the tray2 picture (rotated 90deg ccw)
# 3) Printer Properties: MediaType=Envelope, MediaSize=Custom, PaperFeed=Tray2
# 4) No "Page Scaling", "Choose Paper source by PDF page size", "Use custom..",
#    Orientation=Portrait
# 5) Print.

Closing words

The entire source for all of this is on github.

If you end up using it… send me a preview? I’m curious. :-)

  1. Btw, similar invocation works for Wi-Fi credentials for guests: qrencode -s 6 -l H -o "wifi.png" "WIFI:T:WPA2;S:<SSID>;P:<PSWD>;;" (among other things).