PDF::Reuse allows you to add PDF content to existing PDF files. It's got some thin wrappers around the text writing routines and image handing routines, and expects you to do pretty much everything else that can be done by hand, allowing you to write native PDF code (don't let anyone tell you I didn't warn just how low level it is.)
There are several advantages to PDF::Reuse. Firstly, it produces efficently sized PDF files because it, as the name implies, it reuses data. And because of this reuse, and the fact that it's really really low level, it's quite nippy - orders of magnatude more so than some other PDF tools I'd care to mention - meaning it can easily create batches of thousands of documents while lesser PDF tools.
Oh, and finally it's free, as in speech and as beer. PDFLib (a much more fully featured module that has Perl bindings) costs a small fortune if you licence it commercially.
It's Christmas time again. You'd have thought I'd have noticed, what with the whole advent calendar thing and all, but as usual I've left my Christmas shopping to the last minute. No, later than that, almost the minute after that. So this year I've decided to get everyone virtual gifts that don't actually physically exist.
It'd still be nice to hand people a physical something though, so I came up with the idea of creating a little printout, a certificate of my gift. With my left side of my brain switched on, I fired up my Snazzy Mac Editing Package and create my work of art. Clipart. Bad fonts. The Lot. And then I save it as a PDF file.
And now I just need to put everyone's name, and what they've got on the certificate. How do I do that? Let's look at a basic example of the script.
#!/usr/bin/perl
# turn on perl's safety features use strict; use warnings;
# load the module use PDF::Reuse;
# create the output file prFile("output.pdf");
# insert the orignal file prForm("xmas.pdf");
# write some extra text prText(100,100, "Hello World"); # close the file (mandatory) prEnd();
The coordinates are based in postscript points, and are measured from the bottom left hand corner. This means that the bottom left corner is at (0,0), and the top right is at (595.27, 841,89), as I'm using A4 which is 210mm x 297mm, and there are 2.83 milimeters to each postscipt point.
PDF::Reuse has a vast array of functions that can be used to manipulate the output that you're creating. Based on the fact that PDF is a vector format it's able to do some quite amazing things - inserting scaled documents within documents, ripping images out of one document and placing that image in the current document, inserting javascript to script things like Acrobat, and more. I highly recommend reading the excellent
For now we'll concern outselves with the things that worry us the most - inserting fonts, and altering their size, face, and colour.
Any of the standard PDF fonts are avalible to you. These are
They can be invoked by simply using their name
prFont("TimesRoman"); prText(100,100,"Yes Ossifer, it was like that when I found it.");
Or by using any of the handy abriviation codes
prFont("TR"); prText(100,100,"Yes Ossifer, it was like that when I found it.");
You can use any font that is included in any of the documents that you've included by using the exact name that was used in that document. Which would be great news, it's let down by the fact that to save space most PDF creation software don't include the graphics for letters that arn't used in the orginal document, meaning you won't be able to use those letters in your document either. Oh, darn.
You can see what fonts, and what letters from those fonts, are present
in a document by using the reuseComponent_pl
script that's in the
PDF::Reuse distribution. It spits out a rather natty PDF
that shows all that info.
The size of the font, that is to say, the height of your font, is easy to set. Like everything else releated to the module it's defined in points:
prFontSize(24); prText(100,100,"CTRL-ALT-DELETE is my friend");
One of the most annoying this about using fonts with PDF::Reuse is that it has no facility for telling you how wide a row of text will be or for word-wrapping your text. If you need to print blocks of text you can either try a lot of trial and error, or you can use the one untasteful solution that I've found: Fixed width fonts.
Fixed width fonts are the same width for every charect. That means that unlike our proportioanlly spaced fonts that we're now used to, the skinny letter 'i' takes up the same width as the fat letter 'm'. It also means that each letter has a fixed width ratio to the height. For the built in font Courier, this ratio is 0.6 - the font is slightly less than two thirds the width that it is tall.
print "the string 'hello' is " . courier_width("hello",10) . "points wides when printed in 10pt courier\n";
sub courier_width { my $text = shift; my $pnts = shift; return length($text)*($pnts*0.6); }
This means we can now do things like print centred text:
prFontSize(10); my $string = "This is a LOW TEA warning. Act NOW!"; prText((595.27 - courier_width($string,10))/2,100,$string);
One of the most dramatic shocks to find out about PDF::Reuse is that it provides absolutly no way to chage the colour of the thing you're drawing into your PDF. The only way to do this is to output the correct PDF code by hand. Fortuantly that's not too difficult:
prAdd("1.0 1.0 1.0 rg"); # white
We use the prAdd
command that outputs directly to the PDF stream.
The three digits are the amount of red, green and blue that are in
the colour. By varying the amount, it's possible to get any colour
your computer can display. For example, having all red and no
other colour makes red:
prAdd("1.0 0.0 0.0 rg"); # red
Whereas full red, full green and no blue makes yellow.
prAdd("1.0 1.0 0.0 rg"); # yellow
So, for my list I want to create a single document that has all the
pages for all the gifts in it, as this way I don't have to have a
large collection of files about. The single document will also be a lot
more efficent because each file will require one copy of the original
xmas.pdf
file in it - and if we have one file per page, then that's
going to be a lot of copies of that orginal data we won't have to keep
around.
#!/usr/bin/perl
# turn on perl's safety features use strict; use warnings;
# load the module use PDF::Reuse;
# define constants for the size of things use constant WIDTH => 595.27; use constant HEIGHT => 841,89; use constant FONT_SIZE => 14; # read the people and gifts from STDIN and/or the file # passed on command line my %people = map { split /!/, $_ } (<>);
use Data::Dumper; print Dumper \%people;
# create the *one* output file prFile("output.pdf");
# turn on compression (make it small) prCompress(1);
# courier 10pt (this stays in effect for all pages) prFont("C"); prFontSize(FONT_SIZE);
# for each of the files foreach my $name (keys %people) { # insert the orignal file (this only includes the # data the first time it is called, and subsequent # calls just add the same data to each page) prForm("xmas.pdf");
# red! prAdd("1.0 0.0 0.0 rg");
# write their name centred prText((WIDTH-courier_width($name,FONT_SIZE))/2, 305, $name);
# write their gift centred below that prText((WIDTH-courier_width($people{$name},FONT_SIZE))/2, 248, $people{$name});
# next page prPage() }
# close the file (mandatory) prEnd();
#########
sub courier_width { my $text = shift; my $pnts = shift; return length($text)*($pnts*0.6); }