Build PDF files dynamically with PHP

Easily control the format and content of PDF files with PHP

Walk through the entire process of building PDF files dynamically using PHP. Experiment with open source tools, such as the Free PDF library (FPDF) or PDFLib-Lite, and PHP code for control of the PDF format of your content.

Share:

Jack D. Herrington, Senior Software Engineer, Fortify Software, Inc.

Photo of Jack HerringtonJack Herrington is an engineer, author, and presenter who lives and works in the Bay Area. You can keep up with his work and his writing at http://jackherrington.com.



28 June 2011

Also available in Chinese Russian Japanese

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.

Frequently used acronyms

  • API: Application programming interface
  • DOM: Document Object Mode
  • HTML: HyperText Markup Language
  • PDF: Portable Document Format
  • W3C: World Wide Web Consortium
  • XML: Extensible Markup Language

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
Screen capture of 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.

Building the PDF

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
Screen capture of 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
Screen capture of the first version of the dynamic PDF showing the 'Beast of the East 2011' heading

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.


Building a results table

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
Screen capture of 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
Screen capture of the PDF with a table for the results with teams and scores

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.


Dressing it up with 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
Screen capture of 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.


Conclusion

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.

Resources

Learn

Get products and technologies

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into XML on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=XML, Open source
ArticleID=682110
ArticleTitle=Build PDF files dynamically with PHP
publish-date=06282011