You get all your tables columns lined up almost
perfectly.
Then you realize one needs adjusting. And now you're
fighting with the GUI...
You try to find the one-pixel-wide place to put the mouse
to grab the line and move it. And then you move it and
everything else is disturbed. So you have to carefully
place each one as though you were building a house of
cards. If only you could just tell the word processor the
column proportions you want.
Or you have to make a table of regular dates. Every
Wednesday, say. So you sit there with the calendar open
in the corner, looking down the Wednesday column and
typing in each date. Next year you come to print a new
copy, only to realize the dates are all different and you
have to start again.
But hold on, isn't this what computers are for? To do the
work for us, not make us work. So why not tell the
computer exactly what we want, instead of poking at it
with the mouse and hoping it will work out what we meant?
That's where Cairo comes in. It's a library for drawing
vector graphics, and can create PDFs, SVGs, PS files,
among others.
We will be using C89, and our progam should compile with
either clang or gcc. Make sure cairo is installed,
obviously. Our example program will create a cleaning
rota to be signed whenever the cleaning is completed.
Let's create a makefile first to make things easier:
This is using BSD make, as you can probably tell. If
you're on linux the install 'bmake' to be able to use it,
and run bmake instead of make.
First of all let's create a blank A4 page; these are
210x297mm. cairo uses points as its main unit, so this
needs converting, it turns out to be 595.28x841.89
points.
The process is, a surface has to be created, and then a
cairo handle is created against the surface. So in our
example, the surface is a PDF, and then the handle is
used to draw onto it. Unfortunately the cairo headers use
some C99 but we can use pragmas to ignore it.
Compile and run this, and you've got a blank A4 PDF. Well
it's a start...
=========================================================
2. The Project
=========================================================
As mentioned before, the plan is to create a PDF of a
cleaning rota, with a space to sign for each Wednesday of
the month. From now on, only the drawing parts of the
code will be mentioned, but these should go where the
TODO comment is in the code. Let's start with a title.
Excellent! But it would be nice if the title was centred.
Fortunately cairo let's you find out how big some text
would be before you draw it, and you can use that to
position it. So instead of moving to 64.0, 64.0:
It would, of course, be better to keep the title in a
#define or variable to make the code less redundant.
Anyway this centres the text horizontally, and places it
such that the centre line of the text is at y-coordinate
64.0.
The next thing is to start drawing our actual rota. We'll
start with a border to spice things up (this is a joke).
The stroke colour was already set to black.
Now our PDF has a title and a rectangle. Exciting.
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
2.1 Date arithmetic
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
This section is not related to cairo and PDF generation
as such, but as part of the project we need to do some
date arithmetic.
The cleaning rota is supposed to show all the Wednesdays,
so the first thing is to find the first Wednesday of the
month and year.
We can use mktime to find out the weekday of a given date
by filling in the tm_year, tm_mon and tm_mday values. The
values are a little bit special, in that months are
counted from 0, that is January is month 0. The year is
based on 1900, so that year 2001 is 101. Similarly the
weekday, tm_wday starts from 0, which represents Sunday.
| Member | Interpretation |
|---------|-------------------|
| tm_year | Year - 1900 |
| tm_mon | Month [0-11] |
| tm_mday | Day [1-31] |
| tm_wday | Day of week [0-6] |
If we fill in the year and month, mktime will fill in the
rest for us. The offset of the weekday from our desired
day gives us the nearest matching weekday. If this ends
up negative, the nearest day was in the previous month,
so add 7 to the day of the month.