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.
Overview
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:
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:
and a matching set of envelopes.
How the sausage gets made
I’ll take it one at a time…
Graphics
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:
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:
\documentclass{article}
\usepackage[margin=2cm,top=2cm,papersize={210mm,297mm},landscape,twoside=false]{geometry}
\usepackage[utf8]{inputenc}
\usepackage{wallpaper}
\renewcommand{\familydefault}{\sfdefault}
\usepackage{graphicx}
\setlength\parskip{0pt}
\setlength\parindent{0pt}
\pagestyle{empty}
\def\invitation#1#2{%
\begin{tabular}{p{0.5\textwidth}p{0.5\textwidth}}
&\begin{minipage}[t]{0.5\textwidth}
{\huge #1,}\\[2em]
{\LARGE
du bist zu meiner Geburtstagsparty eingeladen.\\
{\bfseries Wann:} Samstag, #2 14:30 -- ca. 17:00\\
{\bfseries Wo:} Ein spassiger Ort, Musterstrasse 1, Kloppenburg
\begin{center}\includegraphics{geo.png}\end{center}\vspace{1.5em}
{\bfseries Bitte gib mir bis zum 1.2.2022 Bescheid ob du kommst:
070 123 45 67} (iMessage, SMS, Signal, oder WhatsApp){\bfseries .}\\[2em]}
\rule{\textwidth}{0.4pt}\\[2em]
{\Large
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.
}
\end{minipage}\end{tabular}\newpage{}}
\begin{document}
\LLCornerWallPaper{1.0}{inv-kid1.png}
\invitation{Lieber Adam}{2.2.2022}
\invitation{Liebe Eva}{2.2.2022}
\end{document}
and for the envelopes:
\documentclass{article}
\usepackage[margin=1cm,top=1cm,papersize={114mm,162mm},landscape,twoside=false]{geometry}
\usepackage[utf8]{inputenc}
\usepackage{wallpaper}
\setlength\parskip{0pt}
\setlength\parindent{0pt}
\pagestyle{empty}
\def\envelope#1#2{%
\small\begin{tabular}{l}#1%
\end{tabular}\par{}\vspace{25mm}%
\begin{flushright}\Huge\begin{tabular}{l}#2%
\end{tabular}\end{flushright}\newpage{}}
\addtolength{\wpXoffset}{-5cm}
\addtolength{\wpYoffset}{-2cm}
\begin{document}
\CenterWallPaper{0.6}{env-kid1.png}
\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}
\end{document}
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 = Struct.new(:from, :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 = [
Invitation.new(kid1, "Adam First|Musterstrasse 5" + city, "Lieber Adam", date_kid1),
Invitation.new(kid1, "Eva Second|Musterstrasse 5" + city, "Liebe Eva", date_kid1),
]
kid2_invitations = [
Invitation.new(kid2, "Adam First|Musterstrasse 5" + city, "Lieber Adam", date_kid2),
Invitation.new(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
\begin{document}
EOS
out << "\\CenterWallPaper{0.6}{#{bg}}"
def texify(str)
str.gsub(/\|/, '\\\\\\\\').sub(/CZ$/, 'Czech Republic')
end
invitations.each do |invitation|
out << "\\envelope{#{texify(invitation.from)}}{#{texify(invitation.to)}}"
end
out << %s[\end{document}]
out.join("\n")
end
def invitations_for(bg, invitations)
out = []
out << <<-'EOS'
% snip -- most of that latex invite code from above
\begin{document}
EOS
out << "\\LLCornerWallPaper{1.0}{#{bg}}"
def texify(str)
str.gsub(/\|/, '\\\\\\\\').sub(/CZ$/, 'Czech Republic')
end
invitations.each do |invitation|
out << "\\invitation{#{texify(invitation.salutation)}}{#{texify(invitation.date)}}"
end
out << %s[\end{document}]
out.join("\n")
end
File.open('envelopes-kid1.tex', 'w') { |f|
f.puts envelopes_for('env-kid1.png', kid1_invitations) }
File.open('envelopes-kid2.tex', 'w') { |f|
f.puts envelopes_for('env-kid2.png', kid2_invitations) }
File.open('invitations-kid1.tex', 'w') { |f|
f.puts invitations_for('inv-kid1.png', kid1_invitations) }
File.open('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:
all:
bash gen-geo.sh
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
clean:
rm -f *.aux *.log *.tex *.pdf
Printing
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. :-)
-
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). ↩