The 2003 Perl Advent Calendar
[about] | [archives] | [contact] | [home]

On the 18th day of Advent my True Language brought to me..
PDF::Reuse

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.

Formatting

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

  • tutorial
  • For now we'll concern outselves with the things that worry us the most - inserting fonts, and altering their size, face, and colour.

    Multiple Pages

    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);
      }

  • The PDF::Reuse tutorial
  • PDF::API2 - Another PDF manipulation lib
  • PDFLib - very handy commercial PDF manipulation lib