Sometimes you need control over exactly how pages are rendered for printing. At times like those, HTML is not the best choice. PDF files give you complete control over how pages are rendered and how text, graphics, and images are rendered on the page. Sadly, APIs for building PDF files are not standard parts of the PHP toolkit. Now is the time to bring in a little help.
When you search the web for PDF support for PHP, the first thing you are likely to find is the commercial PDFLib library and its open source version, PDFLib-Lite. These are good libraries, but the commercial version is fairly expensive. The light version of the library is distributed only as source, and that restriction might be an issue if you try to install it in a hosted environment.
Another choice is the Free PDF library (FPDF), which is native PHP. It doesn't require any compilation, and it is completely free so you don't see watermarks as you do with an unlicensed version of PDFLib. This Free PDF library is what I use in this article.
To demonstrate building PDF files dynamically, you'll use scores from women's roller derby tournaments. These scores were mined from the web and converted into XML. Listing 1 shows an example of the XML data file.
Listing 1. The XML data
<events>
<event name='Beast of the East 2011'>
<game score1='88' team1='Toronto Gore-Gore Rollergirls'
team2='Montreal La Racaille' score2='11'/>
<game score1='58' team1='Toronto Death Track Dolls'
team2='Montreal Les Contrabanditas' score2='49'/>
...
</event>
<event name='Dustbowl Invitational 2011'>
...
</event>
<event name='The Great Yorkshire Showdown 2011'>
...
</event>
</events>
|
The root element for the XML is an events
tag. The data is grouped into events, where
each event holds a number of games. Within the events tag is a series of event tags,
within which are multiple game tags. These game tags include the names of each of the two teams playing and their scores during the game.
Listing 2 shows the PHP code that you use to read the XML.
Listing 2. getresults.php
<?php
function getResults() {
$xml = new DOMDocument();
$xml->load('events.xml');
$events = array();
foreach($xml->getElementsByTagName('event') as $event) {
$games = array();
foreach($event->getElementsByTagName('game') as $game) {
$games []= array( 'team1' => $game->getAttribute('team1'),
'score1' => $game->getAttribute('score1'),
'team2' => $game->getAttribute('team2'),
'score2' => $game->getAttribute('score2') );
}
$events []= array( 'name' => $event->getAttribute('name'),
'games' => $games );
}
return $events;
}
?>
|
This script implements a getResults function that reads the
XML file into a DOMDocument. DOM calls are then used to traverse all of the event and game tags to build an array of events. Within each element of the array is a hash table that includes the name of the event and an array of the games played. The structure is basically an in-memory version of the structure of the XML.
To test that this script works, you will build an HTML export page that uses the getResults function to read the file, then outputs the data as a
series of HTML tables. Listing 3 shows the PHP code for this test.
Listing 3. The results HTML page
<html><head><title>Event Results</title></head>
<body>
<?php
include_once('getresults.php');
$results = getResults();
foreach( $results as $event ) {
?>
<h1><?php echo( $event['name'] ) ?></h1>
<table>
<?php
foreach( $event['games'] as $game ) {
$s1 = (int)$game['score1'];
$s2 = (int)$game['score2'];
?>
<tr>
<td style="font-weight:<?php echo( ( $s1 > $s2 ) ? 'bold' : 'normal') ?>">
<?php echo( $game['team1'] ) ?></td>
<td><?php echo( $s1 ) ?></td>
<td style="font-weight:<?php echo( ( $s2 > $s1 ) ? 'bold' : 'normal') ?>">
<?php echo( $game['team2'] ) ?></td>
<td><?php echo( $s2 ) ?></td>
</tr>
<?php
}
?>
</table>
<?php
}
?>
</body></html>
|
With this code, the getresults.php, and the XML data file uploaded to the web server, you can look at the HTML result. which resembles Figure 1.
Figure 1. The derby results in HTML
This result even uses bold font for the winning teams to make it easy to see which team won which event.
With the data in hand, it's time to focus on building PDF files. The first step is to
download the FPDF library and install it in the same directory as the existing set of
application files. Actually, you can install it wherever you like as long it's in the
PHP library path. Keep track of where you put the fonts directory, as you will need to
set the 'FPDF_FONTPATH' as in Listing 4.
Listing 4. A PDF Hello World
<?php
define('FPDF_FONTPATH','/Library/WebServer/Documents/derby/font/');
require( 'fpdf.php' );
$pdf = new FPDF();
$pdf->SetFont('Arial','',72);
$pdf->AddPage();
$pdf->Cell(40,10,"Hello World!",15);
$pdf->Output();
?>
|
This script is literally a "Hello World" but as a PDF instead of HTML. The first thing the script does is set the location of the FPDF fonts directory using the define statement. It then brings in the FPDF library using the require statement. From there, the script creates an FPDF object, sets the font, adds a page, puts some text on the page using the Cell method, and outputs the PDF.
Figure 2 shows the result when everything works properly.
Figure 2. A Hello World in PDF
If you don't see a PDF, you will probably want to run the script on the command line to see if you are missing the fpdf.php file or if there is another issue.
Now that the PDF rendering works, it's time to merge it with the roller derby results file and see what you can generate dynamically. Listing 5 shows the first version of this merging.
Listing 5. The first version of the PDF displaying the results
<?php
define('FPDF_FONTPATH','/Library/WebServer/Documents/derby/font/');
require( 'fpdf.php' );
require( 'getresults.php' );
class PDF extends FPDF
{
function EventTable($event)
{
$this->Cell(40,10,$event['name'],15);
$this->Ln();
}
}
$pdf = new PDF();
$pdf->SetFont('Arial','',48);
foreach( getResults() as $event ) {
$pdf->AddPage();
$pdf->EventTable($event);
}
$pdf->Output();
?>
|
Instead of driving the FPDF class from the outside we extend the FPDF class with our own PDF subclass. Within that subclass, we create a new method called EventTable that builds a table of results for a given event. In this case, we start small and just put out the name of the event. That name is wrapped in a foreach loop at the bottom of the script that adds a page for each event, then invokes the EventTable method.
You can see the output for this script in Figure 3.
Figure 3. The first version of the dynamic PDF
Scrolling down the page shows that each of the events is on its own page. The next step from here is to start adding the results to the page.
No table structure is as easy as HTML when you are building PDF files. The way to build tables is to build a bunch of cells that have various widths, fonts, fill color, line color, and so on.
Listing 6 shows the addition code that sets up the header bar for the table.
Listing 6. Adding the results table header
<?php
define('FPDF_FONTPATH','/Library/WebServer/Documents/derby/font/');
require( 'fpdf.php' );
require( 'getresults.php' );
class PDF extends FPDF
{
function EventTable($event)
{
$this->SetFont('','B','24');
$this->Cell(40,10,$event['name'],15);
$this->Ln();
$this->SetXY( 10, 45 );
$this->SetFont('','B','10');
$this->SetFillColor(128,128,128);
$this->SetTextColor(255);
$this->SetDrawColor(92,92,92);
$this->SetLineWidth(.3);
$this->Cell(70,7,"Team 1",1,0,'C',true);
$this->Cell(20,7,"Score 1",1,0,'C',true);
$this->Cell(70,7,"Team 2",1,0,'C',true);
$this->Cell(20,7,"Score 2",1,0,'C',true);
$this->Ln();
}
}
$pdf = new PDF();
$pdf->SetFont('Arial','',10);
foreach( getResults() as $event ) {
$pdf->AddPage();
$pdf->EventTable($event);
}
$pdf->Output();
?>
|
The additional code here sets up the font, colors, and line width. Then it renders a
few cells with the four header columns. It then calls the Ln method, which is the equivalent of a carriage return to start a new line.
When you look at this script in the browser, you see something like Figure 4.
Figure 4. The pages with a header row for the table
In Figure 4, the headers are rendered in white text on a gray background. This format helps differentiate them from the data that is rendered below the headers. To render the game results, add the code in Listing 7.
Listing 7. Adding the full results table
<?php
define('FPDF_FONTPATH','/Library/WebServer/Documents/derby/font/');
require( 'fpdf.php' );
require( 'getresults.php' );
class PDF extends FPDF
{
function EventTable($event)
{
$this->SetFont('','B','24');
$this->Cell(40,10,$event['name'],15);
$this->Ln();
$this->SetFont('','B','10');
$this->SetFillColor(128,128,128);
$this->SetTextColor(255);
$this->SetDrawColor(92,92,92);
$this->SetLineWidth(.3);
$this->Cell(70,7,"Team 1",1,0,'C',true);
$this->Cell(20,7,"Score 1",1,0,'C',true);
$this->Cell(70,7,"Team 2",1,0,'C',true);
$this->Cell(20,7,"Score 2",1,0,'C',true);
$this->Ln();
$this->SetFillColor(224,235,255);
$this->SetTextColor(0);
$this->SetFont('');
$fill = false;
foreach($event['games'] as $game)
{
$this->SetFont('Times',((int)$game['score1']>(int)$game['score2'])?'BI':'');
$this->Cell(70,6,$game['team1'],'LR',0,'L',$fill);
$this->Cell(20,6,$game['score1'],'LR',0,'R',$fill);
$this->SetFont('Times',((int)$game['score1']<(int)$game['score2'])?'BI':'');
$this->Cell(70,6,$game['team2'],'LR',0,'L',$fill);
$this->Cell(20,6,$game['score2'],'LR',0,'R',$fill);
$this->Ln();
$fill =! $fill;
}
$this->Cell(180,0,'','T');
}
}
$pdf = new PDF();
$pdf->SetFont('Arial','',10);
foreach( getResults() as $event ) {
$pdf->AddPage();
$pdf->EventTable($event);
}
$pdf->Output();
?>
|
In addition to the header line, you have a foreach loop in
the EventTable method that iterates through each of the
games. Figure 5 shows the code for this.
Figure 5. The PDF with a table for the results
The $fill variable toggles to alternate the color of each row in the table. The names and scores of the winning teams are in a bold, italic font, which makes them really stand out. Also note that the font changes from Arial for the headers to Times for the game content.
To finish the example code, you'll want to add some graphics.
Adding images to a PDF is remarkably easy. To make it happen, first grab an image off the web. I grabbed the logo of one of the roller derby teams and stored it as a PNG. From there, I used the new code in Listing 8.
Listing 8. Adding a logo image
<?php
define('FPDF_FONTPATH','/Library/WebServer/Documents/derby/font/');
require( 'fpdf.php' );
require( 'getresults.php' );
class PDF extends FPDF
{
function EventTable($event)
{
$this->Image('logo.png',5,5,33);
$this->SetXY( 40, 15 );
$this->SetFont('','B','24');
$this->Cell(40,10,$event['name'],15);
$this->Ln();
$this->SetXY( 10, 45 );
$this->SetFont('','B','10');
$this->SetFillColor(128,128,128);
$this->SetTextColor(255);
$this->SetDrawColor(92,92,92);
$this->SetLineWidth(.3);
$this->Cell(70,7,"Team 1",1,0,'C',true);
$this->Cell(20,7,"Score 1",1,0,'C',true);
$this->Cell(70,7,"Team 2",1,0,'C',true);
$this->Cell(20,7,"Score 2",1,0,'C',true);
$this->Ln();
$this->SetFillColor(224,235,255);
$this->SetTextColor(0);
$this->SetFont('');
$fill = false;
foreach($event['games'] as $game)
{
$this->SetFont('Times',((int)$game['score1']>(int)$game['score2'])?'BI':'');
$this->Cell(70,6,$game['team1'],'LR',0,'L',$fill);
$this->Cell(20,6,$game['score1'],'LR',0,'R',$fill);
$this->SetFont('Times',((int)$game['score1']<(int)$game['score2'])?'BI':'');
$this->Cell(70,6,$game['team2'],'LR',0,'L',$fill);
$this->Cell(20,6,$game['score2'],'LR',0,'R',$fill);
$this->Ln();
$fill =! $fill;
}
$this->Cell(180,0,'','T');
}
}
$pdf = new PDF();
$pdf->SetFont('Arial','',10);
foreach( getResults() as $event ) {
$pdf->AddPage();
$pdf->EventTable($event);
}
$pdf->Output();
?>
|
The key method in Listing 8 is the Image
method, which takes a file name for the image, a location, and a width. All of the
additional parameters are optional so you specify only as much information as you want.
Some new calls to SetXY move the text and table around to appropriate positions to keep them from overwriting the image.
Figure 6 shows the output from this script.
Figure 6. The completed PDF with the logo images
With all the other methods provided by the library to render graphics, add flowing text, add hyperlinks, and manage page mechanics such as margins and orientation, you have complete control over your PDF files.
With the right tools, building PDF files in PHP is remarkably easy. This approach is ideal for printing invoices or tickets, or filling in forms, anything that requires exacting control over the layout of the content.
Learn
- The W3C: Visit a great site for standards, in particular the XML standard is relevant to this article.
- The PHP site: Explore the best reference for PHP that's available.
- More articles by this author (Jack Herrington, developerWorks, March 2005-current): Read articles about Ajax, JSON, PHP, XML, and other technologies.
- New to XML? Get the resources you need to learn XML.
- XML area on developerWorks: Find the resources you need to advance your skills in the XML arena. See the XML technical library for a wide range of technical articles and tips, tutorials, standards, and IBM Redbooks
- IBM XML certification: Find out how you can become an IBM-Certified Developer in XML and related technologies.
- developerWorks technical events and webcasts: Stay current with technology in these sessions.
- developerWorks on Twitter: Join today to follow developerWorks tweets.
- developerWorks podcasts: Listen to interesting interviews and discussions for software developers.
- developerWorks on-demand demos: Watch demos ranging from product installation and setup for beginners to advanced functionality for experienced developers.
Get products and technologies
- PDFLib: Try a commercial library for building PDF files.
- PDFLib-Lite:Download an open source version of the PDFLib 7 and explore its functionality subset.
- PECL package: Download an extension that wraps the PDFLib programming library for processing PDF on the fly.
- FPDF: Get the library used to generate PDF files in this article.
- IBM product evaluation versions: Download or explore the online trials in the IBM SOA Sandbox and get your hands on application development tools and middleware products from DB2®, Lotus®, Rational®, Tivoli®, and WebSphere®.
Discuss
- XML zone discussion forums: Participate in any of several XML-related discussions.
- The developerWorks community: Connect with other developerWorks users while exploring the developer-driven blogs, forums, groups, and wikis.





