Hostingheaderbarlogoj
Join InMotion Hosting for $3.49/mo & get a year on Tuts+ FREE (worth $180). Start today.
Advertisement

Generating PDFs with PHP

by
Gift

Want a free year on Tuts+ (worth $180)? Start an InMotion Hosting plan for $3.49/mo.

PDFs may well be the best format for distributing documents on the web. In today's tutorial, I'll show you how you can generated PDFs with PHP. Let's do it!


Getting up to Speed

There are a number of methods you can use to create PDFs with PHP. You could use the PDFlib library, but it's rather expensive, at least for commercial work. Today, we'll be working with the FPDF library, which is free for both personal and commercial use, and isn't hard to use at all.

To start, head over to the FPDF website and click downloads on the left. Underneath all the manuals, get the zip archive of version 1.6 (even though it's been a while since the last update, the forums on the site are still quite active). Unzip the files; all you need from this folder is the fpdf.php file and the fonts directory.

Let's do a "Hello, World!" example to get us up to speed. Of course, the first thing you'll have to do is require the library; then, we create a new instance of the FPDF class.

hello_world.php

 
	require('fpdf/fpdf.php'); 
 
	$pdf = new FPDF('P', 'pt', 'Letter');

Notice that we passed three values to the FPDF constructor; the first one is the orientation of our page; in our case, that's portrait (you could also do 'L'andscape). The next parameter is the unit type we want to use; we're choosing points, because that's what fonts will be measured in; we could have chosen mm, cm, or in. Finally, we have the paper format, which we've obviously set as letter; we could have set it Legal, A3, A4, or A5. All three of these parameters are option; their defaults are ('P', 'mm', 'A4').

Before we can start adding text to our document, we need to do two things: 1) add a page to the pdf, and 2) set the font.

 
	$pdf->AddPage(); 
	$pdf->SetFont('Arial', '', 12);

You can also use the AddPage whenever you want to put in a page break; first we set the font type; it can be one of the built-in font families (Courier, Helvetica/Arial, Times, Symbol, or ZapfDingbats) or one you added with the AddFont method. Next is the font format; we've set ours as regular, but we could have done 'B'old, 'I'talic, 'U'nderline, or any grouping of those ('BUI'). Finally, we set the font size in points.

Now we're ready to add some text. All the text you write in your PDF will be written within cells.

 
	$pdf->Cell(100, 16, "Hello, World!");

So what does this do? The first parameter is the width of the cell; the second is the height of it. Of course, the third is the text for the cell. There are quite a few other parameters, but first, let's see how this looks. To finish off the PDF, let's use the output method.

 
	$pdf->Output();

We could give this method two parameters, but for now we'll just leave them out. The way it is now, the PDF will be displayed when we visit the page. So let's do that right now! Here's what I get:


Good job! You've generated your first PDF. Let's go back to line that created the text cell and make a change:

 
	$pdf->Cell(100, 16, "Hello, World!", 1);

We've added a fourth parameter; it's the border. In our case, we've put a border all the way around. You can do a partital border with a string: "LB" would add borders on the left and bottom; "TLBR" is the same as 1.

If you reload the page now and you find that nothing happened, just add a random query string to the URL to force the refresh.

We're going to make a small reciept-generating script soon, but let's look at how to set a few more options first.


Full Screencast



SetFillColor / SetTextColor

You can fill text cells with a background color; we'll see how to do that in our app, but first you have to set the fill color. Just pass this function the red, green, and blue values (between 0 and 255). You can set the text color in the same way.

 
	$pdf->SetFillColor(36, 96, 84); 
	$pdf->SetTextColor(255);

If you want to set all three colors to the same value, just use one parameter.

SetLineWidth

When drawing border on cells, it's not hard to adjust the width of the border. Simply call this method, passing in a float; remember, you set the units your measuring in when you created the FPDF instance. When you start, borders are 0.2mm wide.

 
	$pdf->SetLineWidth(1);

SetAuthor / SetTitle / SetSubject / SetCreator / SetKeywords

These methods are for setting the a PDF's properties, the meta data. Each of them take a string with the appropriate content. They can also take an optional boolean, which determines the encoding: true is UTF-8 and false is ISO-8859-1.

 
	$pdf->SetAuthor("Collis and Cyan Ta'eed"); 
	$pdf->SetTitle("How to be a Rockstar Freelancer"); 
	$pdf->SetSubject("Freelancing"); 
	$pdf->SetKeywords("Freelance Brand Project Contract Marketing Clients"); 
	$pdf->SetCreator("EnvatoEdit"); // creator is usually the application that generated the PDF

SetMargins / SetTopMargin / SetLeftMargin / SetRightMargin

By default, your page margins are 1 cm. You can set the top, left, and right margins individually with their respective functions, or set them all at once.

 
	$pdf->SetMargins(40, 35, 20); 
	// is eqaul to 
	$pdf->SetTopMargins(40); 
	$pdf->SetLeftMargin(35); 
	$pdf->SetRightMargin(20);

But what about the bottom margin? I'm glad you asked that's taken care of by another function.

SetAutoPageBreak

This function takes a boolean, determining whether you want the library to add a page break when necessary or whether you want to do it manually. If the value is true (the default), you can add a second parameter defining how close you'd like to get before breaking. By default, this is 2 cm.

 
	$pdf->SetAutoPageBreak(false); // I'll have to add page breaks myself 
	$pdf->SetAutoPageBreak(true, 40); // Pages with auto-break, with a bottom margin of 40 pts.

Generating a Receipt

Now that we've got a some PDF-generating goodness under our belts, let's begin our little application. We'll start by creating an HTML form. When the user clicks 'submit,' we'll take the values from the form and use them to create a receipt for our customer.

First of all, a little HTML shell:

 
	<!DOCTYPE html> 
	<html> 
	<head> 
	    <meta charset='utf-8' /> 
	    <title>Invoice Form</title> 
	    <link rel="stylesheet" href="style.css" /> 
	</head> 
	<body> 
	<div id="wrap"><div> 
	    <h1>Checkout</h1> 
	    <form method="post" action="create_reciept.php"> 
 
	    </form> 
	</div></div>

You can see that we've set up a form, which will post to create_reciept.php, which we'll make soon. Let's add a fieldset and populate it with element for our customer's personal information.

index.html

 
	<fieldset> 
		<legend>Personal Information</legend> 
		<div class="col"> 
			<p> 
				<label for="name">Name</label> 
				<input type="text" name="name" value="John Doe" /> 
			</p> 
			<p> 
				<label for="email">Email Address</label> 
				<input type="text" name="email" value="joh@doe.com" /> 
			</p> 
		</div> 
		<div class="col"> 
			<p> 
				<label for="address">Address</label> 
				<input type="text" name="address" value="123 Main Street" /> 
			</p> 
			<p> 
				<label for="city">City</label> 
				<input type="text" name="city" value="Toronto" /> 
			</p> 
			<p> 
				<label for="province">Province</label> 
				<input type="text" name="province" value="Ontario" /> 
			</p> 
			<p> 
				<label for="postal_code">Postal Code</label> 
				<input type="text" name="postal_code" value="A1B 2C3" /> 
			</p> 
			<p> 
				<label for="country">Country</label><input type="text" name="country" value="Canada" /> 
			</p> 
	    </div> 
	</fieldset>

Take note of two things: we'll see the styling for .col when we get to the CSS; it will allow us to arrange these fields in two columns. Secondly, I've given all the inputs values, just to save us time later.

We'll add another fieldset for the products the customer is buying:

 
	<fieldset> 
	    <legend>Purchases</legend> 
	    <table> 
	        <thead> 
	            <tr><td>Product</td><td>Price</td></tr> 
	        <thead> 
	        <tbody> 
				<tr> 
					<td><input type='text' value='a neat product' name='product[]' /></td> 
					<td>$<input type='text' value='10.00' name='price[]' /></td> 
				</tr> 
				<tr> 
					<td><input type='text' value='something else cool' name='product[]' /></td> 
					<td>$<input type='text' value='9.99' name='price[]' /></td> 
				</tr> 
				<tr> 
					<td><input type='text' value='buy this too!' name='product[]' /></td> 
					<td>$<input type='text' value='17.85' name='price[]' /></td> 
				</tr> 
				<tr> 
					<td><input type='text' value='And finally this' name='product[]' /></td> 
					<td>$<input type='text' value='5.67' name='price[]' /></td> 
				</tr> 
	        </tbody> 
	    </table> 
	</fieldset>

I think the information we're showing here is appropriate for a table, so that's what we're using. Also, I've put a set of square brackets at the end of the names of the inputs; notice that we have a set of inputs named product[] and a set named price[]. These will be posted as arrays.

Of course, our form ends with a submit button.

 
	<button type="submit">Submit Order</button>

Now we can move on to the CSS. We won't go over this in detail, but it's nothing complcated at all. You can put it in style.css, which we linked to in the HTML.

style.css

 
	      body { 
	        font: 13px/1.5 'Helvetica Neue', Arial, 'Liberation Sans', FreeSans, sans-serif; /* 960.gs */ 
	        background:#ececec; 
	    } 
	    .col { 
	        width:50%; 
	        float:left; 
	    } 
	    fieldset { 
	        border:1px solid #474747; 
	        -moz-border-radius:5px; 
	        -webkit-border-radius:5px; 
	        margin-bottom:10px; 
	    } 
	    legend { 
	        background:#474747; 
	        color:#fff; 
	        padding:3px 15px; 
	        -moz-border-radius:5px; 
	        -webkit-border-radius:5px; 
	        font-weight:bold; 
	    } 
	    #wrap { 
	        width:820px; 
	        padding:20px; 
	        margin:20px auto; 
	        background:#fff; 
	        border:1px solid #ccc; 
	        -moz-border-radius:10px; 
	        -webkit-border-radius:10px; 
	    } 
	    p { 
	        padding:2px; 
	    } 
	    label { 
	        display:inline-block; 
	        width:100px; 
	        font-weight:bold; 
	    } 
	    label:after { 
	        content: ":"; 
	    } 
	    input { 
	        display:inline-block; 
	        width:200px; 
	        border:1px solid #ccc; 
	        padding:5px; 
	        margin:0; 
	        -moz-border-radius:5px; 
	        -webkit-border-radius:5px; 
	    } 
	    thead  td { 
	        text-align:center; 
	        font-weight:bold; 
	    } 
	    td + td > input { 
	        width:50px; 
	    } 
	    button { 
	        border:1px solid #474747; 
	        background:#ccc; 
	        -moz-border-radius:5px; 
	        -webkit-border-radius:5px; 
	        padding:10px; 
	        margin:10px; 
	        font-weight:bold; 
	        font-size:200%; 
	    } 
	    button:hover { 
	        background:#ececec; 
	        cursor:pointer; 
	    }

As you can see, we give most of the elements some minimalist styling; nothing earth-shattering, but it will be easy on the eyes. Here's what our form looks like:


So, when our customer fills in the form and clicks submit, the form data will be posted to create_reciept.php. So let's create the file right now!


The Receipt Generator

We're back to PHP now. First things first: get the library:

 
	require('fpdf/fpdf.php');

Next, instead of creating an instance of the FPDF class, we're going to extend it with a class of our own. This will allow us to do some nifty things. Watch closely now:

 
	class PDF_reciept extends FPDF { 
	    function __construct ($orientation = 'P', $unit = 'pt', $format = 'Letter', $margin = 40) { 
	        $this->FPDF($orientation, $unit, $format); 
	        $this->SetTopMargin($margin); 
	        $this->SetLeftMargin($margin); 
	        $this->SetRightMargin($margin); 
	        $this->SetAutoPageBreak(true, $margin); 
	    } 
	}

We've started off our new class by writing a constructor. We've given all the parameters default values so our constructor can be called without them. Inside the function, we call our parent class's constructor, and then set the margins.

Now we're on to two special functions: Header and Footer.

 
	function Header() { 
	    $this->SetFont('Arial', 'B', 20); 
	    $this->SetFillColor(36, 96, 84); 
	    $this->SetTextColor(225); 
	    $this->Cell(0, 30, "Nettuts+ Online Store", 0, 1, 'C', true); 
	}

This function will be run at the beginning of each page of our PDF. We first set the font, fill color, and text color. Then, we output our cell. Notice that we've set the width to 0. This is a special value; the cell will take up all the space to the right of the starting point (until it hits the right margin). Since we're starting at the left margin, we'll get a cell the entire width of the page (minus margins).

Now for the Footer function:

 
	function Footer() { 
	    $this->SetFont('Arial', '', 12); 
	    $this->SetTextColor(0); 
	    $this->SetXY(0,-60); 
	    $this->Cell(0, 20, "Thank you for shopping at Nettuts+!", 'T', 0, 'C'); 
	}

Again, we start by setting the font and text color; we can't be sure what those values will be when we come to the header, so we'll set them just to be sure. Next we set the X and Y coordinates. This sets the top left corner position of our next cell. We set X to 0, just to make sure we're at the beginning of the line. We set Y to -60; a negative number is relative to the bottom of the page, so this sets our cursor 60 pixels above the bottom.

Next, we create a cell; again, we're using the 0-width trick; we set the height to 20pts. We set the text, give it a top border, and center the text. That's it!

We'll come back to our class again, but for now, let's start building the receipt. After creating an instance of our class, adding a page, and setting the font, we can get down to business.

 
	$pdf = new PDF_receipt(); 
	$pdf->AddPage(); 
	$pdf->SetFont('Arial', '', 12);

This first thing we'll do is move our cursor; we can do this by using the SetY method.

 
	$pdf->SetY(100);

This doesn't move us down 100 points; is sets us 100 points from the top of the page. That doesn't include the margin, so we're 30pt down from the bottom of our header.

 
	$pdf->Cell(100, 13, "Ordered By"); 
	$pdf->SetFont('Arial, ''); 
	$pdf->Cell(100, 13, $_POST['name']);

We start by creating a cell that's 100pt wide and 13pt high, with the text of "Ordered By." Next, we reset the font to remove the bold. Then we create another cell, and hand it the name we got from our form. Of course, in a real project, you'll be checking those values before you use them.

Moving on, we'll add the date of purchase:

 
	$pdf->SetFont('Arial', 'B', 12); 
	$pdf->Cell(50, 13, 'Date'); 
	$pdf->SetFont('Arial', ''); 
	$pdf->Cell(100, 13, date('F j, Y'), 0, 1);

Again, we set the font to bold; after writing "Date," we remove the bold and print the current date, using PHP's date function. The format we've specified will give us the month name, day number followed by a comma, and the four-digit year. We put the border as 0 (the default) so we can get to the the line parameter. We set it as 1, which will put our position to the beginning of the next line; it's the equivalent of hitting 'enter' in a word processor.

Moving on . . .

 
	$pdf->SetX(140); 
	$pdf->SetFont('Arial', 'I'); 
	$pdf->Cell(200, 15, $_POST['address'], 0, 2); 
	$pdf->Cell(200, 15, $_POST['city'] . ', ' . $_POST['province'], 0, 2); 
	$pdf->Cell(200, 15, $_POST['postal_code'] . ' ' . $_POST['country']); 
	$pdf->Ln(100);

The SetX function does the same thing the SetY function does, except that it sets us out from the right. We're putting our position 100pt right of the left margin. Next, we italicize our font and begin outputting the address. All three lines of the address are 200pt wide and 15pt high. The important thing to notice here is that our line parameter is 2. This means that after each cell, we will move to the next line and 'tab in' to line up with the previous cell. Finally, we call the Ln method, which adds a line break. Without a parameter, it will move our position down the heigh of the last printed cell. We've passed it a parameter, so we're moving down 100pt.

Here's what we're looking like so far:


image : address / date

Now let's print out a table of the products our customer has purchased.

 
	$pdf->PriceTable($_POST['product'], $_POST['price']);

We've passed in the two arrays we got from our HTML file. But where's this PriceTable method coming from? We're going to make it right now, within our PDF_receipt class. That's another benefit of extending the class: we can capture potentially laborious functionality in a method.

 
	    function PriceTable($products, $prices) { 
	    $this->SetFont('Arial', 'B', 12); 
	    $this->SetTextColor(0); 
	    $this->SetFillColor(36, 140, 129); 
	    $this->SetLineWidth(1); 
	    $this->Cell(427, 25, "Item Description", 'LTR', 0, 'C', true); 
	    $this->Cell(100, 25, "Price", 'LTR', 1, 'C', true); 
 
	    $this->SetFont('Arial', ''); 
	    $this->SetFillColor(238); 
	    $this->SetLineWidth(0.2); 
	    $fill = false; 
 
	    for ($i = 0; $i < count($products); $i++) { 
	        $this->Cell(427, 20, $products[$i], 1, 0, 'L', $fill); 
	        $this->Cell(100, 20, '$' . $prices[$i], 1, 1, 'R', $fill); 
	        $fill = !$fill; 
	    } 
	    $this->SetX(367); 
	    $this->Cell(100, 20, "Total", 1); 
	    $this->Cell(100, 20, '$' . array_sum($prices), 1, 1, 'R'); 
	}

Here's the function. We start by setting our font, text color, fill color, and line width. Next we set up our header cells. The each have a border on the left, top, and right, centered text, and a fill.

Next, we remove the 'boldness' from the font and reset the fill color and line width. We also create a $fill variable, set to false. We'll use this to add zebra striping to our table.

The for-loop goes through the products and prices array (they should have the same number of items, based on the table in our HTML file). We create a cell for each entry, putting the product name under the "Item Description" header and the price under the "Price" header. They both have a border. The product is left-aligned, the price is right-aligned. Finally, they both use $fill, which is currently false. After each row, we switch the value of $fill to the opposite boolean, so the next row will have the grey background.

After we loop through all the purchases, we want to give the total price. We'll bump our cursor 367pt from the left edge of the page. Then we create a "Total" cell, and finally, we use the php function array_sum to add up all the prices in $prices. And that's the price table!

Moving back to the body of our PDF, we'll now skip down 50pt.

 
	$pdf->Ln(50);

So far, we've only used the Cell function to print onto our page. There are two other methods we can use as well: MultiCell and Write. Both these functions are for lines of text that are too long for a single full-width cell. Let's look at MultiCell first.

MultiCell is pretty similar to Cell, except that when it hits the width you've specified for the cell, it will create another one underneath it and continue writing. It will also break when it hits a '\n'. Because of this, it doesn't need a line property parameter. It also doesn't have the link parameter that Cell does. What? We didn't mention the link parameter yet? Well, Cell can take one more parameter, after the fill parameter: that's either a link string, or a link object pointing to another spot within the PDF, created with the AddLink function.

 
	$message = "Thank you for ordering at the Nettuts+ online store. Our policy is to ship your materials within two business days of purchase. On all orders over $20.00, we offer free 2-3 day shipping. If you haven't received your items in 3 busines days, let us know and we'll reimburse you 5%.\n\nWe hope you enjoy the items you have purchased. If you have any questions, you can email us at the following email address:"; 
	$pdf->MultiCell(0, 15, $message);

The text isn't too important, but it's too long too go on one line; plus, it has a few line breaks in it. Although we're not using them here, MultiCell also takes border, alignment, and fill parameters.

The Write method take only three parameters: a height, the text, and an optional link. There's no width parameter, because it will go to the right margin before it breaks the line. Although this isn't the best use of it, we'll use it to add a link:

 
	$pdf->SetFont('Arial', 'U', 12); 
	$pdf->SetTextColor(1, 162, 232); 
 
	$pdf->Write(13, "store@nettuts.com", "mailto:example@example.com");

Notice that we set the font and text color before writing, so that it looks like an link.

That's it for the PHP. Let's add one more feature: we'll submit our form with Ajax. Before heading back to index.html, make these changes to out call to Output:

 
	$pdf->Output('reciept.pdf', 'F');

The first parameter is the name of our file; the second one is what we want to do with the file, in this case, save it to the server. You could also force a download with 'D', return it as a string with 'S'. The default, outputting to the browser, is 'I' (for inline).

Back in the HTML, let's import jQuery and add our click handler to the submit button:

 
	<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.0/jquery.min.js"></script> 
	<script type="text/javascript"> 
	$('button').click(function () { 
	    $.post('create_reciept.php', $('form').serialize(), function () { 
	        $('div#wrap div').fadeOut( function () { 
	            $(this).empty().html("<h2>Thank you!</h2><p>Thank you for your order. Please <a href='reciept.pdf'>download your reciept</a>. </p>").fadeIn(); 
	        }); 
	    }); 
	    return false; 
	}); 
	</script>

When the button is clicked, we submit the form. On a successful return, we fade the second div out, empty its contents and reload it using the HTML function. That new HTML has the link for the receipt. Then we fade the div back in. Don't forget to return false to keep the form from submitting regularly.

Here's what we get


And here's our finished PDF.


And that's it, folks! Hit the comments with remarks or suggestions!

Advertisement