Table of Contents
I’ve long been a user of PDF::API2, a module available for Perl. I use it everyday at work and along with a few other modules, it has made Perl an invaluable tool for me. Being able to generate PDF’s for reports and documents allow for a bevy of automation shortcuts where traditional Data Merging, or “variable imaging,” would be impractical and at times impossible to integrate into existing e-commerce or ERP platforms.
To run this code on your own computer, you will need to download a Perl interpreter. If you’re on Windows, I would recommend http://strawberryperl.com/.
Once Perl is installed, launch ‘CPAN’ to install any necessary modules, for example from the CPAN prompt:
cpan> install PDF::API2
Simple Letter sized PDF
#!/usr/bin/perl
use PDF::API2;
# Create a blank PDF file
$pdf = PDF::API2->new();
# Add a blank page
$page = $pdf->page();
# Define Letter-sized page measured in points.
# Hint: There are 72 points in an inch.
$page->mediabox(8.5*72,11*72);
# Define a text object for the current page
$text = $page->text();
# Load core Font
$font = $pdf->corefont('Times-Roman');
# Define which font to use and point size
$text->font($font,12);
# Font color
$text->fillcolor('#000000');
# Text you want
$string = "Your text here";
# Move cursor relative to bottom-left of document, measured in points
$text->translate(100,700);
# Write text
$text->text($string);
$pdf->saveas("Letter.pdf");
Add a picture and text
JPEGs are supported as well as PNGs with transparency.
The example below has no image formatting, but the back-end Perl script for this example does center the image and size it down to fit, but that’s outside the scope of this simple example.
#!/usr/bin/perl
use PDF::API2;
$pdf = PDF::API2->new();
$page = $pdf->page();
$page->mediabox(8.5*72,11*72);
$text = $page->text();
$font = $pdf->corefont('Times-Roman');
$text->font($font,12);
$text->fillcolor('#000000');
$string = "Your string here";
$text->translate(100,700);
$text->text($string);
# A graphics object for the page
$gfx = $page->gfx();
# The image on your computer
$image = $pdf->image_jpeg("YOUR IMAGE.JPG");
# Your Image, Position X, Position Y, Width, Height
$gfx->image($image,200,100,$image->width,$image->height);
$pdf->saveas("Picture_Letter.pdf");
Note: Using a large image will take several seconds to upload, so give it a moment after you hit “Create PDF.”
Add content to an existing PDF document
This is great for automating online forms for customers to fill out on your website that can then be used for printing, record-keeping, or emailing to other parties.
In this example, Page 1 of the IRS 1040 form is opened and a persons first and last names are filled in and saved as a new document.
A custom font file, Raleway, is also used.
#!/usr/bin/perl
use PDF::API2;
$pdf = PDF::API2->open("IRS 2018 F1040.PDF");
$page = $pdf->openpage(1);
$page->mediabox(8.5*72,11*72);
$text = $page->text();
$raleway = $pdf->ttfont('raleway-normal.ttf');
$text->font($raleway,12);
$text->fillcolor('#000000');
$firstname = "JOHN F";
$lastname = "KENNEDY";
$text->translate(58,700);
$text->text($firstname);
$text->translate(250,700);
$text->text($lastname);
$pdf->saveas("1040.pdf");
Barcodes
Code 39
Basic shapes and lines are possible through the ‘gfx’ object. Here, the first four lines define the line, and the last line actually draws the line.
$gfx->strokecolor('#000000');
$gfx->linewidth($thickness);
$gfx->move($x1,$y1);
$gfx->line($x2,$y2);
$gfx->stroke;
This really isn’t an example of PDF::API2, but rather an example of how existing Perl tools can be used to create custom documents. Here, the module GD::Barcode::Code39 is used to encode a custom string into a standard ‘3 of 9’ type barcode.
GD::Barcode::Code39 itself will only produce the 1’s and 0’s necessary for the barcode. A ‘foreach’ loop then iterates through the binary string and PDF::API2 will be used to draw either a black line (#000000) or a white line (#FFFFFF), producing a barcode.
You can download an app for your phone to scan the barcode. Read the CODE39 Wikipedia page for valid characters and more information.
#!/usr/bin/perl
use PDF::API2;
use GD::Barcode::Code39;
$pdf = PDF::API2->new();
$page = $pdf->page();
$page->mediabox(3.5*72,2*72);
$gfx = $page->gfx();
$text = $page->text();
$times = $pdf->corefont('Times-Roman');
$string = "YOUR BARCODE TEXT NUMBERS AND UPPERCASE LETTERS ONLY";
# Spaces and characters -$%./+ are also valid.
# Un-comment the two lines below to Regex a valid string
# $safe_characters = 'A-Z0-9 -$%./+';
# $string =~ s/[^$safe_characters]//g;
$barcode = GD::Barcode::Code39->new("*$string*");
$barcodestring = $barcode->barcode();
$text->translate(20,85);
$text->font($times,12);
$text->text($string);
# Prime some initial variables for the barcode
$offset_x = 20; # X start position
$offset_y = 70; # Y start position
$barthickness = .5; # The thickness of each line
$barsize=10; # The height of the barcode
#####################################
foreach $char (split //, $barcodestring) {
$gfx->linewidth($barthickness);
$color = $char == 1 ? '#000000' : '#ffffff';
$gfx->strokecolor($color);
$gfx->move($offset_x,$offset_y);
$gfx->line($offset_x,$offset_y + $barsize);
$gfx->stroke;
$offset_x = $offset_x + $barthickness;
}
$pdf->saveas("barcode.pdf");
2D Datamatrix Barcode
#!/usr/bin/perl
use PDF::API2;
use Barcode::DataMatrix;
$pdf = PDF::API2->new();
$page = $pdf->page();
$page->mediabox(3.5*72,2*72);
$gfx = $page->gfx();
$string = "TOMMY";
$barcodex = 25;
$barcodey = 100;
$size = 2;
$mat = Barcode::DataMatrix->new(encoding_mode => "ASCII",size => "24x24")->barcode($string);
$gfx->fillcolor('#000000');
for my $i (0 .. $#$mat) {
for my $j (0 .. $#{$mat->[$i]}) {
if ($mat->[$i][$j]) {
$gfx->rect($barcodex+$size*$j, $barcodey-$size*$i, $size, $size);
}
}
}
# The above code written differently and easier to understand
#
#foreach $row (0 .. 23){
# foreach $col (0 .. 23){
# if($mat->[$row][$col]){
# $gfx->rect($barcodex+$size*$col,$barcodey-$size*$row,$size,$size);
# $gfx->fill();
# }
# }
#}
$gfx->fill();
$pdf->saveas("datamatrix.pdf");
QR Code
This one comes in handy a lot. Be careful with your “Version=>?” numbers. QR Codes will encode up to a certain number of bits depending on this version number. The example below is “Version=>4” which will hold up to 512 bits.
Exceeding the number of bits will error out, but at the time of this writing the “GD::Barcode::QRcode” module has a bug that prevents handling the error cleanly. Read more.
#!/usr/bin/perl
use PDF::API2;
use GD::Barcode::QRcode;
$pdf = PDF::API2->new();
$page = $pdf->page();
$page->mediabox(4*72,4*72);
$gfx = $page->gfx();
$string = 'https://www.tommycoolman.com';
$oGdB = GD::Barcode::QRcode->new($string,{
Ecc => 'M',
Version=>4,
ModuleSize => 2
});
$mat = $oGdB->barcode();
@matrix = split(/\n/,$mat);
$size = 1;
$offset_x=20;
$offset_y=180;
$x=0;
$y=0;
foreach $row (@matrix){
foreach $col (split //, $row){
$color = $col == 1 ? '#000000' : '#ffffff';
$gfx->fillcolor($color);
$gfx->rect($offset_x+$x, $offset_y+$y, $size, $size);
$gfx->fill();
$x+=$size;
}
$x=0;
$y-=$size;
}
$pdf->saveas("qrcode.pdf");
Data Driven PDF, Templates
The below program will load a spreadsheet with the contact information for US Senators. The program will then open a ‘template’ business card and use the spreadsheet data to populate the business card and save it as a new file.
Note that in several places I am calling $text->advancewidth() and storing the result. This variable is used for text justification.
Download the ZIP file here (540KB)
#!/usr/bin/perl
use PDF::API2;
use Text::ParseWords;
open(FILE,"US Senators Business Contact.csv");
@inputfile = <FILE>;
close(FILE);
shift @inputfile; # Remove CSV header
# Load Senators Template File
$template = PDF::API2->open("US Senators Business Card Template.pdf");
$newpdf_senators = PDF::API2->new();
$garamondregular = $newpdf_senators->ttfont("garamond-regular.ttf");
$garamondbold = $newpdf_senators->ttfont("garamond-bold.ttf");
$garamonditalic = $newpdf_senators->ttfont("garamond-italic.ttf");
$count=1;
foreach $senator (@inputfile){
chomp($senator);
($title,$firstname,$lastname,$party,$state,$address,$phone,$fax,$staffer) = quotewords(',',0,$senator);
# Import page from <TEMPLATE>, <SOURCE PAGE>, <DESTINATION PAGE>... And return a Page
$page = $newpdf_senators->import_page($template, 1, $count);
$text = $page->text();
$text->font($garamondbold,11);
$text->fillcolor('#000000');
$textwidth = $text->advancewidth("$firstname $lastname",%text_state); # Compute Width of text for formatting
$text->translate(163-($textwidth/2),100); # Use $textwidth to calculate position to center text
$text->text("$firstname $lastname");
$text->font($garamondregular,10);
$textwidth = $text->advancewidth("United States Senator",%text_state);
$text->translate(163-($textwidth/2),86);
$text->text("United States Senator");
$textwidth = $text->advancewidth("United States Senate",%text_state);
$text->translate(55-($textwidth/2),28);
$text->text("United States Senate");
$text->font($garamondregular,8);
$textwidth = $text->advancewidth("The State of $state",%text_state);
$text->translate(55-($textwidth/2),18);
$text->text("The State of $state");
$textwidth = $text->advancewidth($address,%text_state);
$text->translate(238-$textwidth,34);
$text->text($address);
$textwidth = $text->advancewidth("Phone: $phone",%text_state);
$text->translate(238-$textwidth,24);
$text->text("Phone: $phone");
$textwidth = $text->advancewidth("Fax: $fax",%text_state);
$text->translate(238-$textwidth,14);
$text->text("Fax: $fax");
print "$count - Processed $firstname $lastname\n";
$count++;
}
print "Saving PDF...\n";
$newpdf_senators->saveas("new.pdf");
Useful things to know
Print All the Core Fonts
#!/usr/bin/perl
# Print text with corefont() typefaces.
use PDF::API2;
$pdf = PDF::API2->new();
$page = $pdf->page();
# Core Adobe Fonts
@adobe = (
"Courier",
"Courier-Bold",
"Courier-BoldOblique",
"Courier-Oblique",
"Helvetica",
"Helvetica-Bold",
"Helvetica-BoldOblique",
"Helvetica-Oblique",
"Symbol",
"Times-Bold",
"Times-BoldItalic",
"Times-Italic",
"Times-Roman",
"ZapfDingbats"
);
# Core Windows Fonts
@windows = (
"Georgia",
"Georgia,Bold",
"Georgia,BoldItalic",
"Georgia,Italic",
"Verdana",
"Verdana,Bold",
"Verdana,BoldItalic",
"Verdana,Italic",
"Webdings",
"Wingdings"
);
$page->mediabox(3.5*72,2*72);
$text = $page->text();
$y = 130;
$text->fillcolor('#000000');
foreach $font (@adobe){
$font = $pdf->corefont($font);
$text->font($font,8);
$text->translate(20,$y);
$text->text("Don't Panic");
$y -= 9;
}
$y = 130;
foreach $font (@windows){
$font = $pdf->corefont($font);
$text->font($font,8);
$text->translate(120,$y);
$text->text("Don't Panic");
$y -= 9;
}
$pdf->saveas("babelfish.pdf");
Text Transformations
#!/usr/bin/perl
use PDF::API2;
$pdf = PDF::API2->new();
$page = $pdf->page();
$page->mediabox(4*72,2.5*72);
$text = $page->text();
$times = $pdf->corefont('Times-Bold');
$arial = $pdf->corefont('Arial');
$text->font($times,12);
$text->fillcolor('#000000');
$text->word_spacing(10);
$text->transform(
-translate => [30,20],
-rotate => 90);
$text->text("This text is rotated!");
$text->transform(
translate => [60, 100],
rotate => 35,
scale => [.5, .5],
skew => [0, 30],
);
$text->text("This text is rotated and skewed!");
$text->fillcolor('#0000FF'); # Fill Color
$text->stroke_color('#FF0000'); # Stroke Color
$text->linewidth(0.3); # Stroke Width
$text->word_spacing(0);
# Three different text 'render' types
$text->translate(80,85);$text->render(0);$text->text("Fill Text");
$text->translate(80,70);$text->render(1);$text->text("Stroke Text");
$text->translate(80,55);$text->render(2);$text->text("Fill and Stroke");
# Easy way for bold italic using stroke and skew.
# No need to load a separate font file.
$text->fillcolor('#000000');
$text->stroke_color('#000000');
$text->transform(
translate => [80, 40],
skew => [0, 15],
);
$text->render(2);
$text->text("Bold Italic");
# Justification
$text->render(0);
$text->font($arial,6);
$text->translate(220,125);$text->text_center("this text is centered");
$text->translate(220,118);$text->text_center("along the same vertical");
$text->translate(220,111);$text->text_center("this was a haiku");
$text->translate(220,50);$text->text_right("this text");
$text->translate(220,43);$text->text_right("is right");
$text->translate(220,36);$text->text_right("justified");
$pdf->saveas("text.pdf");
PANTONE/Spot Colors
Define PANTONE colors that are compatible with Adobe Illustrator. This is useful when working in a commercial printing environment.
#!/usr/bin/perl
use PDF::API2;
$pdf = PDF::API2->new();
$pdf->mediabox(3.5*72,2*72);
$page = $pdf->page();
$gfx = $page->gfx();
$warmred = $pdf->colorspace('spot', 'PANTONE Warm Red C', '#FF4438');
$reflexblue = $pdf->colorspace('spot', 'PANTONE Reflex Blue C', '#001689');
$black = $pdf->colorspace('spot', 'PANTONE Black C', '#000000');
$font = $pdf->corefont("Arial Bold");
$gfx->fill_color($warmred, 0.5);
$gfx->stroke_color($black, 1.0);
$gfx->linewidth(0.25);
$gfx->render(2);
$gfx->textlabel(14,100,$font,12,"PANTONE Warm Red C [50% Screen]");
$gfx->fill_color($reflexblue, 1.0);
$gfx->textlabel(14,80,$font,12, "PANTONE Reflex Blue C [100% Screen]");
$pdf->save('Pantone.pdf');
Get Dimensions
#!/usr/bin/perl
use PDF::API2;
use Data::Dumper;
$pdf = PDF::API2->open("YOURFILE.PDF");
$page = $pdf->openpage(1);
%boundaries = $page->boundaries();
print Dumper %boundaries;
$bleed = $boundaries{'trim'}[0];
$mediaWidth = $boundaries{'media'}[3];
$mediaHeight = $boundaries{'media'}[2];
$width = $mediaWidth - ($bleed * 2);
$height = $mediaHeight - ($bleed * 2);
$width /= 72;
$height /= 72;
$bleed /= 72;
$mediaWidth /= 72;
$mediaHeight /=72;
$bleed = sprintf("%.4f", $bleed);
$mediaWidth = sprintf("%.4f", $mediaHeight);
$mediaHeight = sprintf("%.4f", $mediaWidth);
print "$width\" x $height\", $bleed\" Bleed (Media: $mediaWidth\" x $mediaHeight\")\n";
Adding PDF Properties
$pdf->author("Perl Ninja");
$pdf->title("Awsome Perl Notes");
$pdf->keywords("Perl PDF PDF::API2");
$pdf->creator("Custom Perl PDF Script Engine v1.0");
$pdf->producer("PDF::API2 Strawberry Perl");
$pdf->created("D:20220101103000+00'00");
$pdf->modified("D:20220101103000+00'00");
Calling the function with no parameters will return the current value.
$author = $pdf->author();
Sample Projects
ID Cards with QR VCard
This example creates 60 employee ID badges based on a spreadsheet. Each person’s photo is loaded, along with their contact information and an ID badge is created that includes a VCard.
Most smartphone cameras can automatically detect a QR VCard and allow you to save directly to contacts.
Download the ZIP file here (~1,600KB)
See the final PDF (~5MB)
Multithreaded PDF Creation
I’ve created millions (literally) of PDF files for things ranging from health insurance documents to tax-related paperwork. Perl is by no means the fastest method, but there are few options when you need to combine and automate database access, record processing, and PDF generation. If you find yourself processing tens of thousands of records for PDF generation then multi-core processing may be an option for you.
This is a simple project that generates 1000 fake band posters using 8 threads. A random fake name is rendered along with three random images. The benefits of parallel processing will truly come into play when you have to render pages (plural) of unique text for each PDF you’re creating.
Download the ZIP file here (~3,500KB)