Creating a PDF with PHP can be relatively straightforward. However, knowing what library to use and how to deal with errors can help you cut back on the tedious work associated with generating a PDF. This excerpt from Peter MacIntyre's PHP: The Good Parts will walk you through the process and clue you in on a few tips along the way.
Adobe’s Portable Document Format (PDF) files have almost become the standard for preparing well-formatted documents. There are PDF readers/displayers for most web browsers, so there is no real excuse for not providing this kind of formatted document to your users if your web application demands its use. Standardized forms and statistical reports can all be drawn from a web system’s data, so it makes sense to format that data in a common layout.
There is a PHP add-on library that allows for the generation of dynamic PDF-formatted output. There are actually a few such PHP libraries out there, but we will look at one of the most widely used libraries, called FPDF. This library is also object-based and you can include it in your scripts the same way that you include the PHPMailer library.
To get started, here is a simple example of some code that will perform three simple tasks: create a blank PDF document, add some text to it, and display it:
require("../../fpdf/fpdf.php");
$pdf = new FPDF( );
$pdf->AddPage();
$pdf->SetFont('Arial','B',16);
$pdf->Cell(0,10,'PHP - The Good Parts!');
$pdf->Output();
As you can see from this code listing, after requiring the library
file, we instantiate an object of the FPDF class and
call it $pdf. Then we add a page with
the AddPage method, set our output
font, define the cell (location) for our string of output, and then—using
the Output method—display the PDF in
the browser. The actual browser output will look like that shown in Figure 8.1.
Note
When building these PDF pages in code you may get the following error:
“FPDF error: Some data has already been output, can’t send PDF file.”
This just means that there is already some output being sent to
the browser. You can either find and remove the extraneous output or
work around it by using the ob_start
and ob_end_flush function combination
at either end of your code listing.
There is a concept in FPDF called the document’s cell. This refers to a rectangular area on the page that you can create and control. The cell can have a height, width, border, and, of course, text. The basic syntax for the cell method is as follows:
Cell(float w [, float h [, string txt [, mixed border [, int ln [, string align [, int fill [, mixed link]]]]]]])
The options for the Cell method call are
width of the cell, height of the cell, text to be included in the cell,
cell border (if desired), a new line control, alignment of the text within
the cell, the cell’s fill color (if desired), and an HTML link if the text
is to be a link to a web resource.
Note
If you leave the cell width (first attribute) at 0, the cell will take the entire width of the defined page. This only really becomes apparent when you turn on the border (as shown in the following example) or if you want different-sized cells on the same plane. In the latter case, you would potentially see the text overlapping.
So, for example, if we want to change our original example to have a border and be aligned to the right, we would change the method call to the following:
$pdf->Cell(0,10,'PHP - The Good Parts!' ,1 ,0 ,'R');
This would produce the browser output shown in Figure 8.2.
The Cell method is the workhorse
for output onto a PDF document and is used extensively while generating
PDF documents with FPDF, so you would be well-served if you spent the time
needed to learn the ins and outs of this method.
The new line control option of this method (and other FPDF methods) is important to understand. It controls the positioning of the writing cursor after the method is completed. In the above case, it is set to 0, which means that the write cursor will stay on the same line when it is finished and any subsequent writing will also take place from the left margin, potentially causing overlapping of output. If, however, it is set to 1, the write cursor will move to the next line as defined by the height attribute of the previous method call.
There is another FPDF method that comes in handy when you are trying
to place separate data on the same output line, and it is called
SetX. This moves the
write cursor from the left margin by a set distance (we will talk about
the measurement attributes in the next section). This may sound a little
confusing, so let’s look at two simple examples with two Cell method calls each. The first example will
leave the write cursor on the same line, and the second example will move
it to the next line.
Here is the code and browser image (Figure 8.3) for the first example:
require("../../fpdf/fpdf.php");
$pdf = new FPDF( );
$pdf->AddPage();
$pdf->SetFont('Arial','B',16);
$pdf->Cell(10,10,'PHP - The Good Parts!', 0,0,'L');
$pdf->SetX(90);
$pdf->Cell(90,10,'Beware the Ides of March!', 1,0,'C');
$pdf->Output();
Note
You may want to disable your browser’s caching capabilities while you are developing and testing the layout of your FPDF PDFs because some browsers will not reload the page with changes if the changes are so small that they don’t register as such with the cache control.
And here is the code and browser output (Figure 8.4) for the second
example (we do not need to use the SetX
method here, as we are moving the write cursor to the following line as
part of the cell method call):
require("../../fpdf/fpdf.php");
$pdf = new FPDF( );
$pdf->AddPage();
$pdf->SetFont('Arial','B',16);
$pdf->Cell(10,10,'PHP - The Good Parts!', 0,1,'L');
$pdf->Cell(90,10,'Beware the Ides of March!', 1,0,'C');
$pdf->Output();
As mentioned earlier, there are different settings for the
measurement units on the PDF pages within FPDF. You can control them by sending parameters to the
constructor when you instantiate a new copy of the class. Previously,
you saw the $pdf = new FPDF( );
statement of instantiation, which creates an object with the default
attributes. You can send the following parameters into the
constructor:
The page orientation has the options of portrait (P) or landscape (L), portrait being the default.
The units of measurement have the options of point (pt), millimeter (mm), centimeter (cm), and inches (in), with millimeter as the default.
The page size has the options of A3, A4, A5, Letter, and Legal, with A4 as the default.
Here is a constructor call that defines portrait, inches, and letter layout:
$pdf = new FPDF('P', 'in', 'Letter' );
Note
You can even define a custom page layout, if you want, by sending an array of dimensions into the constructor in place of the third parameter. Business cards or special fliers, for instance, could have their own page dimensions. This constructor call will define a page that is 4 × 5 inches and landscape orientation:
$pdf = new FPDF('L', 'in', array(4,5));
Let’s take a look at object inheritance, or extension, in action.
Naturally, there is often a need for headers and footers
on a multipage PDF document. FPDF has empty header and footer methods,
and they are called automatically each time the AddPage method is called. Without extending
the class and adding content to our own methods of the same names,
however, nothing is visually apparent. So, let’s extend the class and
add custom page header and footer methods to the child class. Here is
the code:
require("../../fpdf/fpdf.php");
class myPDF extends FPDF {
public $title = "FPDF Sample Page Header";
//Page header method
function Header() {
$this->SetFont('Times','',12);
$w = $this->GetStringWidth($this->title)+150;
$this->SetDrawColor(0,0,180);
$this->SetFillColor(170,169,148);
$this->SetTextColor(0,0,255);
$this->SetLineWidth(1);
$this->Cell($w,9,$this->title,1,1,'C',1);
$this->Ln(10);
}
//Page footer method
function Footer() {
//Position at 1.5 cm from bottom
$this->SetY(-15);
$this->SetFont('Arial','I',8);
$this->Cell(0,10,'page footer -> Page '
.$this->PageNo().'/{nb}',0,0,'C');
}
}
$pdf = new myPDF('P', 'mm', 'Letter');
$pdf->AliasNbPages();
$pdf->AddPage();
$pdf->SetFont('Times','',24);
$pdf->Cell(0,0,'text at the top of the page',0,0,'L');
$pdf->ln(225);
$pdf->Cell(0,0,'text near page bottom',0,0,'C');
$pdf->AddPage();
$pdf->SetFont('Arial','B',15);
$pdf->Cell(0,0,'Top of page 2 after page header',0,1,'C');
$pdf->Output();
There are a number of other methods being called from within the extended header and footer methods. Some of this is included here to show you that the entire class is inherited and not just the header and footer methods. Also, some of the methods are used to show the difference between the header and footer areas distinctly. The full listing of methods and their uses can be found on the product web page under the “Manual” link. The image shown in Figure 8.5 is the result of the above code being executed within the browser. It is a picture of the bottom of one page (to show the footer) and the top of the next page (to show the header).
Note
You can suppress the header or footer on a certain page by
querying the value of the page number with the returned value from the
PageNo() method and
reacting appropriately. Make sure to use the AliasNbPages method before you add your
first page to the document so that FPDF can count the pages being
created.
You can also add image and link content to PDF files with the FPDF library. These links can be anchors within the PDF file itself or full URL resources on the Web. First, we will look at inserting images into the PDF file, then we will look at making links from either images or text.
To add an image to the document, simply use the image method. In the following code, we
will use the image method within the
header method to add a PHP logo to the page header
and remove the background fill color option from the
cell method call so that we can see the image. The
image parameters are the image filename, the x and y coordinates of the
image, and the width and height of the image:
function Header() {
$this->SetFont('Times','',12);
$w = $this->GetStringWidth($this->title)+150;
$this->SetTextColor(0,0,255);
$this->SetLineWidth(1);
$this->Image('phplogo.jpg',10,10.5,15,8.5);
$this->Cell($w,9,$this->title,1,1,'C');
$this->Ln(10);
}
The PDF document now looks like the image shown in Figure 8.6.
Now, to make this image link to the PHP home page, simply add the
URL to the last parameter of the image method,
skipping the type parameter with a
comma.
Note
It’s a good idea to save the URL text to a string variable and then use that in the method parameter listing; this will make it easier to read and possibly reuse if there are other links using the same URL at other places in your document.
The new linking method call now looks like this:
$php_url = "http://www.php.net" ;
$this->Image('phplogo.jpg',10,10.5,15,8.5,"",$php_url);
The image is now a clickable link image, as shown by the hover text in Figure 8.7.
The other kind of link that we can add to the PDF document is a link to another location within the document. This is the concept of a table of contents or an index. Creating an internal link is done in two parts. First, you define the origin for the link (the link itself), then you set the anchor (the destination that the link will take you to when you click it).
To set the origin of a link, use the AddLink() method. This
method returns a handle for use when creating the destination portion of
the link with the SetLink() method,
which takes the origin’s link handle as its parameter so that it can
perform the connection between the two items. Here is some sample code
that performs the creation of both the origin and the destination parts;
notice the use of the FPDF write method, which is
another way to send text to the document (as opposed to using the
cell method):
require("../../fpdf/fpdf.php");
$pdf = new FPDF();
//First page
$pdf->AddPage();
$pdf->SetFont('Times','',14);
$pdf->write(5,'For a link to page 2 - Click ');
$pdf->SetFont('','U');
$pdf->SetTextColor(0,0,255);
$link_to_pg2 = $pdf->AddLink();
$pdf->write(5,'here',$link_to_pg2);
$pdf->SetFont('');
//Second page
$pdf->AddPage();
$pdf->SetLink($link_to_pg2);
$pdf->Image('phplogo.jpg',10,10,30,0,'','http://www.php.net');
$pdf->ln(20);
$pdf->SetTextColor(1);
$pdf->Cell(0,5,'This is a link and a clickable image', 0, 1, 'L');
$pdf->SetFont('','U');
$pdf->SetTextColor(0,0,255);
$pdf->Write(5,'www.oreilly.com','http://www.oreilly.com');
$pdf->Output();
The browser outputs for both the link and the destination page for this code are shown in Figures 8.8 and 8.9.
The next feature that we’ll look at is making a watermark appear on the PDF document. This can be a nice addition to generated reports or sales brochures that you may want to create within a PHP application. Here is the code to create the watermark:
require("../../fpdf/fpdf.php");
$pdf = new FPDF( );
$pdf->AddPage();
$pdf->SetFont('Arial','B',16);
$pdf->SetXY(26,100);
$pdf->image('php_watermark.jpg');
$pdf->SetY(35);
$text = "This is sample text to show the watermark underneath it.";
for($i = 0; $i < 35; $i++) { $pdf->Cell(0,5,$text,0,1); }
$pdf->Output();
All that is really going on here is that we are moving the write
cursor around the page with the SetXY and SetY methods, and we have an image that is set
to a semitransparent shading level. There is really no difference here
from setting an image on the page, except that we are overwriting the
image with additional text. If this had not been a semitransparent
image, the text and the image would be garbled together and it would
look like a mess.
Make sure you add the image to the document first in the case of a watermark, as the last items sent to the document will overwrite anything previously sent. In Figure 8.10, if the text were sent out first, followed by the image, the image would overlay the text.
Now we want to really make FPDF earn its keep. Up to this point, we have only been sending static information to the PDFs being created. Let’s look at how to integrate a PDF document with database information drawn out by a query request. We will display that information in a nicely formatted table on the PDF itself, thus making the PDF dynamic in its content. The following code listing is a little long, but it is well commented and the highlights are discussed in the subsequent paragraphs:
require("../../fpdf/fpdf.php");
class PDF extends FPDF {
function BuildTable($header,$data) {
//Colors, line width and bold font
$this->SetFillColor(255,0,0);
$this->SetTextColor(255);
$this->SetDrawColor(128,0,0);
$this->SetLineWidth(.3);
$this->SetFont('','B');
//Header
// make an array for the column widths
$w=array(85,40,15);
// send the headers to the PDF document
for($i=0;$i<count($header);$i++)
$this->Cell($w[$i],7,$header[$i],1,0,'C',1);
$this->Ln();
//Color and font restoration
$this->SetFillColor(175);
$this->SetTextColor(0);
$this->SetFont('');
//now spool out the data from the $data array
$fill=true; // used to alternate row color backgrounds
foreach($data as $row)
{
$this->Cell($w[0],6,$row[0],'LR',0,'L',$fill);
// set colors to show a URL style link
$this->SetTextColor(0,0,255);
$this->SetFont('', 'U');
$this->Cell($w[1],6,$row[1],'LR',0,'L',$fill, 'http://www.oreilly.com');
// restore normal color settings
$this->SetTextColor(0);
$this->SetFont('');
$this->Cell($w[2],6,$row[2],'LR',0,'C',$fill);
$this->Ln();
// flips from true to false and vise versa
$fill =! $fill;
}
$this->Cell(array_sum($w),0,'','T');
}
}
//connect to database
$connection = mysql_connect("localhost","user", "password");
$db = "library";
mysql_select_db($db, $connection)
or die( "Could not open $db database");
$sql = 'SELECT * FROM books ORDER BY pub_year';
$result = mysql_query($sql, $connection)
or die( "Could not execute sql: $sql");
// build the data array from the database records.
While($row = mysql_fetch_array($result)) {
$data[] = array($row['title'], $row['ISBN'], $row['pub_year'] );
}
// start and build the PDF document
$pdf = new PDF();
//Column titles
$header=array('Book Title','ISBN','Year');
$pdf->SetFont('Arial','',14);
$pdf->AddPage();
// call the table creation method
$pdf->BuildTable($header,$data);
$pdf->Output();
In this code, we use the database connection and build two arrays
to send to the BuildTable() custom
method of this extended class. Inside the BuildTable() method, we set colors and font
attributes for the table header, then send out the headers based on the
first array passed in. An array called $w (for width) sets the column widths and is
used in the calls to the cell()
methods.
After the table header is sent out, we use the $data array that contains the database
information and walk through that array with a foreach loop. Notice here that the cell() method uses LR for its
border parameter. This refers to borders on the left and right of the
cell in question, thus effectively adding the sides to the table rows.
We also add a URL link to the second column just to show that it can be
done in connection with the table row construction. Finally, we use a
$fill variable to flip back and forth
so that the background color will alternate as the table is constructed
row by row.
The final call to the cell()
method in this BuildTable() method
draws the bottom of the table and closes off the columns.
The result of executing this code in a browser is shown in Figure 8.11.
Learn more about this topic from PHP: The Good Parts.
Get past all the hype about PHP and dig into the real power of the language. This book explores the most useful features of PHP and how they can speed up the web development process, and explains why the most commonly used PHP elements are often misused or misapplied. You'll learn which parts add strength to object-oriented programming, and how to use certain features to integrate your application with databases.

Help





