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:
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
#!/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");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");QR Code VCard
Encode a person’s contact information into a QR Code. Scan it with your phone’s camera to automatically populate a new contact with information.
#!/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 = qq {BEGIN:VCARD
VERSION:3.0
FN;CHARSET=UTF-8:Jane Doe
TEL;TYPE=phone,VOICE:(555) 555-1234
LABEL;CHARSET=UTF-8;TYPE=WORK:Work
ADR;CHARSET=UTF-8;TYPE=address:;;1234 Grand Ave;Anytown;AR;90210;United States
URL;type=homepage;CHARSET=UTF-8:http://www.google.com
EMAIL;PREF;INTERNET:jane\@google.com
END:VCARD};
$oGdB = GD::Barcode::QRcode->new($string,{
Ecc => 'L',
Version=>12,
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("vcard.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 (560KB)
#!/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,$state,$address,$phone,$fax,$staffer,$building) = 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,12);
$text->fillcolor('#000000');
$textwidth = $text->advancewidth("$firstname $lastname",%text_state); # Compute Width of text for formatting
$text->translate(170-($textwidth/2),100); # Use $textwidth to calculate position to center text
$text->text("$firstname $lastname");
$text->font($garamondregular,11);
$textwidth = $text->advancewidth("United States Senator",%text_state);
$text->translate(170-($textwidth/2),89);
$text->text("United States Senator");
$text->font($garamondregular,9);
$textwidth = $text->advancewidth("The State of $state",%text_state);
$text->translate(170-($textwidth/2),80);
$text->text("The State of $state");
$text->translate(14,24);
$text->text($building);
$text->translate(14,14);
$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
#!/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("Awesome 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();






