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.
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.
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:
Pass a filename to rsvg_handle_new_from_file()
, save the result.
Pass that to rsvg_handle_render_cairo()
along with a Cairo
handle.
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.
It looks like SWIG is not going to be easier than XS. Oh, well.
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.