_
                      (_)
             ____ ____ _  ____ ___
            / ___) _  | |/ ___) _ \
           ( (__( ( | | | |  | |_| |
            \____)_||_|_|_|   \___/


Infuriating isn't it?

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.


=========================================================
1. Setup
=========================================================

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:

--- makefile --------------------------------------------

PROG=rota
SRCS=main.c
MAN=

PKGS=cairo

CFLAGS += -Wall -Wextra -pedantic -std=c89 -O2
COPTS != pkg-config --cflags ${PKGS}
LDADD != pkg-config --libs ${PKGS}

include <bsd.prog.mk>

---------------------------------------------------------

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.

--- main.c ----------------------------------------------

#pragma clang diagnostic push
#pragma diagnostic ignored "-Wc99-extensions"
#include <cairo.h>
#include <cairo-pdf.h>
#pragma clang diagnostic pop

#define PAGE_WIDTH 595.28
#define PAGE_HEIGHT 841.89

int
main(void)
{
   cairo_surface_t *surface;
   cairo_t *cr;

   surface = cairo_pdf_surface_create("/tmp/test.pdf",
       PAGE_WIDTH, PAGE_HEIGHT);
   cr = cairo_create(surface);
   /* TODO: draw some stuff */
   cairo_show_page(cr);
   cairo_destroy(cr);
   cairo_surface_destroy(surface);
   return 0;
}

---------------------------------------------------------

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.

---------------------------------------------------------

/* set things up */
cairo_set_source_rgb(cr, 0, 0, 0);
cairo_select_font_face(cr, "Linux Libertine",
   CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
cairo_set_font_size(cr, 28.0);

/* draw the title */
cairo_move_to(cr, 64.0, 64.0);
cairo_show_text(cr, "Cleaning Rota");

---------------------------------------------------------

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:

---------------------------------------------------------

cairo_text_extents_t textents;

cairo_text_extents(cr, "Cleaning Rota", &textents);
cairo_move_to(cr, PAGE_WIDTH/2 - textents.width/2,
   64 - textents.height/2);
cairo_show_text(cr, "Cleaning Rota");

---------------------------------------------------------

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.

---------------------------------------------------------

cairo_rectangle(cr, 16, 64 + textents.height + 8,
   PAGE_WIDTH - 16,
   PAGE_HEIGHT - 64 - 8 - textents.height - 16);
cairo_stroke(cr);

---------------------------------------------------------

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.

---------------------------------------------------------

struct tm tm;

memset(&tm, 0, sizeof(tm));
tm.tm_year  = START_YEAR;
tm.tm_mon   = START_MONTH;
tm.tm_mday  = 1;
mktime(&tm);
/* find nearest Wednesday */
tm.tm_mday += (3 - tm.tm_wday);
/* add 7 if we've underflowed into the previous month */
if (tm.tm_mday <= 0)
   tm.tm_mday += 7;

---------------------------------------------------------