Preliminary release of PDF::Cairo


Here. Needs a full test suite and a bunch of examples before I throw it up on Github/CPAN, but I’m pretty happy with the functionality.

I think if I were exploring another scripting language, I’d look for Cairo and FreeType support, and then I’d port this module over. A bit of work up front, but less hassle than dealing with the quirks and limitations of another implementation.

Stripped of comments and documentation, it’s just about 1,500 lines of code, half of which is in the main PDF::Cairo package. Quite a bit of it is PDF::API2 compatibility, which I could jettison when porting to another language, so it would be pretty quick.

Usage is quite straightforward:

use PDF::Cairo qw(cm);
$pdf = PDF::Cairo->new(
    file => "output.pdf",
    paper => "a4",
    landscape => 1,
);
$font = $pdf->loadfont('Times-Bold');
$pdf->move(cm(2), cm(4));
$pdf->fillcolor('red');
$pdf->setfont($font, 32);
$pdf->print("Hamburgefontsiv");
$image = $pdf->loadimage("logo.png");
$pdf->showimage($image, cm(5), cm(5),
    scale => 0.5, rotate => 45);
$pdf->write;

Honestly, the thing that took the longest was dredging up the schoolboy trig to get it to rotate images from the lower-left corner instead of the upper-left. Cairo’s inverted coordinate system makes sense on-screen, not so much in print.

I was very selective about what I pulled in from Pango. It’s a peculiar library that simultaneously gives you more control over text layout while removing control over font selection. You can precisely position each glyph in a Unicode string, but have no idea what it will look like on the page. Some of this it inherits from Fontconfig, which is a giant hairy mess of XML configuration files, but they’ve layered their own clunky parser on top of that.

For instance, on my Mac, I have four styles of the typeface family Hoefler Text installed: Regular, Italic, Black, and Black Italic. Using the Fontconfig tool fc-match, I can select them with the string ‘Hoefler Text’ optionally followed by ‘:bold’, ‘:italic’, or ‘:bold:italic’. Using Pango’s pango-list tool, I discover that the correct incantation there is to request ‘Hoefler Text Small-Caps’, with the words ‘Bold’ and ‘Italic’ optionally added in that order after ‘Text’.

Leave out the ‘Small-Caps’, and I get Verdana (well, technically I get Hoefler Text Ornaments, which then falls back to Verdana because that font doesn’t actually have any alphanumeric characters in it). Use it, and I get the right font, but without small caps. Try to request real small caps (which it has), using the variant='smallcaps' attribute, and I still don’t get small caps (despite the documentation, I’m not sure that works at all). I have no idea how to fix this, or if it’s even possible.

I made the Pango support optional, because it takes a full two seconds to initialize the first layout on my laptop. By comparison, my calendar generator takes less than a second to create a 10-year, 59-page calendar.

Obnoxious Bug!

Unfortunately, while I was porting one of my other old scripts to use as an example, libfreetype segfaulted in FT_Set_Transform. Didn’t matter what font I loaded, there was just something about that script that triggered it.

After some painstaking debugging, it looks like I have two workarounds. First, render the text as a Pango layout, which avoids calling Freetype directly from Cairo. Second, use the create_for_stream() method to initialize the PDF surface and explicitly save the file at the end, rather than use the standard create() method that takes a file name up front and auto-saves when the script exits. That last bit seems to be where the bug is being triggered.

Ironically, this is how my PDF::API2 compatibility methods work, so a direct port of the old script works fine, and it doesn’t crash until I try to update it to use what seemed to be the recommended method (pun intended).

For now, I’ve commented out the use of create(), and added an explicit write() method to dump the PDF file to disk. I’ll test again when Homebrew catches up to the current Cairo release.

5/17 Update

I’ve refreshed the tarball about a dozen times now, as I add examples and come across bugs and little niggling issues in the APIs. I’ve pretty much rewritten half of the Box module as I’ve started using it more aggressively in simplifying the calculations in my examples.

I’d say that I’m just about ready to stick a fork in it, write the test cases, and release, but then I thought of another epicycle: rendering SVG files as vectors. Currently, the only way to import existing graphical data is as fonts with FreeType or bitmapped images with Imagemagick’s convert tool.

I’ve used the custom font trick before to load illustrations into a program that didn’t support them, but I’d rather import SVG directly. Because ImageMagick supports rasterizing vector formats, I can already place an SVG into a PDF, but only as a bitmap.

But it turns out that librsvg is already designed to do exactly what I want: render an SVG into a Cairo context. There’s just not a maintained Perl module that links to it. I can’t say that I’ve always wanted to figure out how to write XS glue, but since my needs are very simple, I think I can dodge that bullet and create a simple API with SWIG.

Looking at the RSVG API, I think all I really need to do is:

  1. Pass a filename to rsvg_handle_new_from_file(), save the result.

  2. Pass that to rsvg_handle_render_cairo() along with a Cairo handle.

  3. Profit!

I’m not even sure I need rsvg_handle_free(); the memory should be reclaimed when the Perl variables go out of scope, or worst case, when the script exits.

Never mind…

It looks like SWIG is not going to be easier than XS. Oh, well.


Comments via Isso

Markdown formatting and simple HTML accepted.

Sometimes you have to double-click to enter text in the form (interaction between Isso and Bootstrap?). Tab is more reliable.