Saturday, November 7, 2009

13.2 Producing PDF











 < Day Day Up > 









13.2 Producing PDF







In this

section, we show you how to use



R&OS Ltd's

free PHP PDF creation library (we refer to this as

pdf-php throughout this section). The library

has two advantages over other approaches: it's free

and it doesn't require any additional PHP

configuration. What's more, it's

powerful and you can do most things you need, including producing

tables containing results from database queries and inserting images

into a document.





You can find out more about pdf-php from http://www.ros.co.nz/pdf and you can download

the source, documentation, and get involved in the project at

http://sourceforge.net/projects/pdf-php/.

Instructions for installing pdf-php are included in Appendix A through Appendix C.









13.2.1 Hello, world





Example 13-1





shows a simple PHP example.









Example 13-1. A simple example that produces Hello, world on an A4 page


<?php

require "class.ezpdf.php";



// Create a new PDF document

$doc =& new Cezpdf( );



// Add text to the document

$doc->ezText("Hello, world");



// Output the document

$doc->ezStream( );

?>









This PHP example produces the PDF document shown in Figure 13-1.









Figure 13-1. The PDF document produced by Example 13-1







The base class for producing PDF files is

class.pdf.php

,

but we've used its extension

class.ezpdf.php



in our simple example. The extension has several useful utilities for

document creation and it still includes all the methods of the base

class, and so we recommend always using it instead.





In Example 13-1, it's assumed both

class files are in the same directory as the example code (or in a

directory set by the include_path directive in

your php.ini file). In addition,

it's assumed that the fonts are in a subdirectory of

the example directory named fonts; the fonts are

part of the pdf-php install package.





The constructor Cezpdf(

)


creates a new PDF document with the

default A4 paper size and the default portrait orientation. The

=& operator creates an instance of a class,

and returns a reference to the instance (and not the whole object

itself). The =& operator is a faster, more

memory efficient alternative to the = operator.

It's discussed in Chapter 4.





The ezText( )

method adds text to the

document at the current cursor position (which defaults to the

top-left corner; there are, however, top and left margins of 30

points, where a point is 1/72 of an inch), with the current font

(which defaults to Helvetica), and the current font size (which

defaults to 12). The ezStream(

)


method cleans up and then creates the

PDF output and sends it to the browser; this is a similar approach to

templates (as described in Chapter 7), where a

document is first prepared and later output using a method.





In many browsers, when a PDF document arrives as part of an HTTP

response, a window containing the PDF document will automatically

appear. However, in some browsers, the arrival of a PDF document will

cause a dialog box to pop up that asks whether to save or open the

PDF file. If you choose open and have a PDF viewer correctly

installed, you'll see the output. Now is a good time

to get yourself a PDF viewer, as you'll need it

throughout this chapter.





You might be wondering what the content of a

PDF

file looks like. The answer is that it's an ASCII

text file that contains instructions on how to render and present the

document content; however, it can be compressed, and so you might

find it isn't always readable with a text editor.

For a document containing graphics, the text can be a complicated

list of instructions about lines and points. However, for simple text

that's rendered using a font, it's

basically human-readable text that you could conceivably edit. For

example, here's the part of the file output from

Example 13-1 that creates the Hello, world message:





7 0 obj

<<

/Length 55 >>

stream



BT 30.000 800.330 Td /F1 10.0 Tf (Hello, world) Tj ET

endstream

endobj













13.2.2 A Full-Featured Document





Example 13-2



shows a more complex

example that makes use of many of the features of the pdf-php

library. The example prints the first two pages of Lewis

Carroll's Alice's

Adventures in Wonderland
in a two-column format, with a

title on the first page and an image from the book.









Example 13-2. Formatting Alice's Adventures in Wonderland for printing


<?php

require "class.ezpdf.php";

require "alice.inc";



// Create a new PDF document

$doc =& new Cezpdf( );



// Use the Helvetica font for the headings

$doc->selectFont("./fonts/Helvetica.afm");



// Output the book heading and author

$doc->ezText("<u>Alice's Adventures in Wonderland</u>", 24,

array("justification"=>"center"));

$doc->ezText("by Lewis Carroll", 20, array("justification"=>"center"));



// Create a little bit of space

$doc->ezSetDy(-10);



// Output the chapter title

$doc->ezText(Chapter 1: Down the Rabbit-Hole", 18,

array("justification"=>"center"));



// Number the pages

$doc->ezStartPageNumbers(320, 15, 8,"",

"{PAGENUM} of {TOTALPAGENUM} pages");



// Create a little bit of space

$doc->ezSetDy(-30);



// Switch to two-column mode

$doc->ezColumnsStart(array("num"=>2, "gap"=>15));



// Use the Times-Roman font for the text

$doc->selectFont("./fonts/Times-Roman.afm");



// Include an image with a caption

$doc->ezImage("rabbit.jpg", "", "", "none");

$doc->ezText("<b>White Rabbit checking watch</b>",

12,array("justification"=>"center"));



// Create a little bit of space

$doc->ezSetDy(-10);



// Add chapter text to the document

$doc->ezText($text,10,array("justification"=>"full"));



// Output the document

$doc->ezStream( );

?>









The first page is shown rendered by the xpdf viewer in Figure 13-2.









Figure 13-2. The output of Example 13-2 shown in the xpdf viewer







The following code fragment from Example 13-2 sets

the font:





// Use the Helvetica font for the headings

$doc->selectFont("./fonts/Helvetica.afm");







We use Helvetica for the headings, and Times-Roman for the body of

the document.



The

available fonts are in the subdirectory fonts in the pdf-php install

package, and are passed to the selectFont(

)


method using their path and the full

file name. The font name must include the .afm

extension, and only .afm format files are

supported; however, there are free utilities, such as

t1utils and ttf2pt1, that

convert other font formats (such as .ttf) into

.afm files.





This next fragment outputs the headings:





// Output the book heading and author

$doc->ezText("<u>Alice's Adventures in Wonderland</u>", 24,

array("justification"=>"center"));

$doc->ezText("by Lewis Carroll", 20, array("justification"=>"center"));







The ezText( )

method has three parameters,

but only the first is mandatory. The first parameter is the text to

add to the document, and it can include simple HTML-like markup

elements such as <u> for underline and

<b> for bold. Text is output by the method,

and followed with a carriage return in the same way as

echo or print in PHP. The

second parameter is the font size to use (the default is 12), and the

third parameter is an array of options. In this example,

we've set the justification

parameter to center, but it can also be set to

left, right, or

full; we use full for the text.

The complete list of options is described later in Section 13.3





The ezSetDy( )

method is used to create space

between text and images. For example, the following fragment moves

the cursor down the page by 10 points:





// Create a little bit of space

$doc->ezSetDy(-10);







A negative value is downwards, and a positive value is upwards. In

PDF, the bottom-left-hand corner of a page is coordinate X=0, Y=0,

and the top-right has the maximum X and Y values. For an A4 page, the

top-right corner has a point value of X=595.28 and Y=841.89, and for

US letter of X=612.00 and Y=792.00.





The class includes the useful ezStartPageNumbers(

)


method for numbering pages. We use it

as follows:





// Number the pages

$doc->ezStartPageNumbers(320, 15, 8, "",

"{PAGENUM} of {TOTALPAGENUM} pages");







The first two parameters are the X and Y coordinates of where to put

the page number text, and the third parameter is the font size to

use; the first three parameters are mandatory. The optional fourth

parameter can be set to left or

right, and indicates whether to put the text to

the left or right of the X coordinate; by default, the text is

written to the left of the X coordinate. The optional fifth parameter

specifies how to present the page numbering; by default, it is

{PAGENUM} of {TOTALPAGENUM} but

we've set it to {PAGENUM} of {TOTALPAGENUM}

pages
to get strings such as 1 of 2

pages
. The optional sixth parameter is a page number and,

if it is supplied, the current page is numbered beginning with the

number.





We've presented the text of the book in a two column

newspaper-like format. This is achieved by calling the

ezColumnsStart(

)


method as follows:





// Switch to two-column mode

$doc->ezColumnsStart(array("num"=>2, "gap"=>15));







The method takes one optional parameter. The parameter is an array

that specifies the number of columns and the gap in points between

the columns. If the parameter is omitted, the number of columns

defaults to 2 and the gap to 10. The method ezColumnsStop(

)
stops multi-column mode, but we don't

use it here because we're working with only one

chapter.





To include an image in the text, you can use the ezImage(

)


method. We use it to include a

picture of the white rabbit after the headings:





$doc->ezImage("rabbit.jpg", "", "", "none");







A great feature of this method is that it doesn't

require any additional configuration: you don't need

to install any graphics libraries (such as GD) and

it'll work on all platforms without modification.

The method takes six parameters. The first parameter is a mandatory

image file path and name, and only JPEG and PNG format images are

supported. All of the remaining parameters are optional. The second

parameter is the amount of padding in points to place around the

image, and it defaults to 5. The third parameter is the width of the

image, and the default is the image's actual width.

The fourth parameter is a resize value that controls how the image

fits in a column, and we've used

none so that the image isn't

resized at all. The fifth parameter specifies justification, and can

be set to left, right, or

center with a default of

center. The sixth parameter is a border to place

around the image, and defaults to none. More

details on all parameters are provided in Section 13.3.





After we've finished with headings and images, the

following fragment includes the text of the book into the PDF

document:





$doc->ezText($text,10,array("justification"=>"full"));







The variable $text contains the text of the book.

It is set in the

alice.inc

include file. Here are the first few lines

of alice.inc:





<?php

$text = "Alice was beginning to get very tired of sitting by her sister on

the bank, and of having nothing to do: once or twice she had peeped into

the book her sister was reading, but it had no pictures or conversations

in it, ... ";







Carriage returns and whitespace characters are preserved in the

output. So, for example, a carriage return creates a new line in the

PDF file; this is unlike HTML, where whitespace is ignored. The book

text itself is sourced from the Project Gutenberg homepage at

http://gutenberg.net.















13.2.3 A Database Example





Example 13-3

shows a script that produces a page

containing one customer's details from a

customer table. The

customer



table is discussed in Chapter 5 and created

with the following CREATE TABLE statement:





CREATE TABLE customer (

cust_id int(5) NOT NULL,

surname varchar(50),

firstname varchar(50),

initial char(1),

title_id int(3),

address varchar(50),

city varchar(50),

state varchar(20),

zipcode varchar(10),

country_id int(4),

phone varchar(15),

birth_date char(10),

PRIMARY KEY (cust_id)

) type=MyISAM;







The example also uses the titles lookup table

that contains title_id values and titles (such as

Mr. and Miss), and the countries lookup table

that contains country_id values and country names.

The output of Example 13-3 is shown in Figure 13-3.









Example 13-3. Producing customer information from the customer table


<?php

require "class.ezpdf.php";

require "db.inc";



$query = "SELECT * FROM customer, titles, countries

WHERE customer.title_id = titles.title_id

AND customer.country_id = countries.country_id

AND cust_id = 1";



if (!($connection = @ mysql_connect($hostName, $username, $password)))

die("Could not connect to database");



if (!(mysql_selectdb($databaseName, $connection)))

showerror( );



if (!($result = @ mysql_query($query, $connection)))

showerror( );



$row = mysql_fetch_array($result);



// Construct the title and name

$name = "{$row["title"]} {$row["firstname"]}";

if (!empty($row["initial"]))

$name .= " {$row["initial"]} ";

$name .= "{$row["surname"]}";



// Create a new PDF document

$doc =& new Cezpdf( );



// Use the Helvetica font

$doc->selectFont("./fonts/Helvetica.afm");



// Create a heading

$doc->ezText("<u>Customer Details for {$name}</u>",

14, array("justification"=>"center"));



// Create a little bit of space

$doc->ezSetDy(-15);



// Set up an array of customer information

$table = array(

array("Details"=>"Title and name",

"Value"=>$name),

array("Details"=>"Address",

"Value"=>"{$row["address"]} {$row["city"]} {$row["zipcode"]}"),

array("Details"=>"State and country",

"Value"=>"{$row["state"]} {$row["country"]}"),

array("Details"=>"Telephone",

"Value"=>$row["phone"]),

array("Details"=>"Date of birth",

"Value"=>$row["birth_date"]));



$doc->ezTable($table);



// Output the document

$doc->ezStream( );

?>









The database processing in Example 13-3 is similar to

that of most examples in previous chapters. The script queries and

retrieves the customer details for customer #1, including the

customer's title and country. The results are stored

in the array $row, and the array is then used as

the source of data for the PDF document.









Figure 13-3. The customer details page output by Example 13-3







The use of the pdf-php library is also similar to that in our

previous examples, with the exception that the customer details are

shown in a table using the ezTable(

)


method. The ezTable(

)
method is a flexible tool that allows you to present

data in different table styles and to configure the column headings,

column widths, shading, borders, and alignment.





In this example, we only use the basic features of the

ezTable( ) method. First, we've

created an array that contains the data we want to display in the

table:





  // Set up an array of customer information

$table = array(

array("Details"=>"Title and name",

"Value"=>$name),

array("Details"=>"Address",

"Value"=>"{$row["address"]} {$row["city"]} {$row["zipcode"]}"),

array("Details"=>"State and country",

"Value"=>"{$row["state"]} {$row["country"]}"),

array("Details"=>"Telephone",

"Value"=>$row["phone"]),

array("Details"=>"Date of birth",

"Value"=>$row["birth_date"]));







The array contains five elements, each of which is itself an array.

Each of these five inner arrays has two associatively-labeled

elements: Details and Value.

The Details element holds as a row label value

such as Title and name, and the

Value element holds the data that matches the

label.





The PDF table itself is created with the fragment:





$doc->ezTable($table);







The method creates a table with the number of rows equal to the

number of elements in the array (in our example, five rows). The

number of columns in the table is equal to the number of elements in

the inner arrays and, in our example, they each have two elements. By

default, column headings are taken from the associative-access keys,

and the data in the tables comes from the values. The default mode is

to create a bordered table with shading in every second row, and to

center the table in the output.











13.2.4 Creating a Report





Example 13-4

shows a script that produces a more

complex purchase report. The report is a table that lists the

customers in the winestore database, the number of orders

they've placed, the number of bottles of wine

they've bought, and the total dollar value of their

purchases. We also show totals at the end of each page, and an

overall total on the final page.









Example 13-4. A script to produce a customer purchasing report


<?php

require "class.ezpdf.php";

require "db.inc";



// Do the querying to produce the customer report

$query = "SELECT customer.cust_id, surname, firstname,

SUM(qty), SUM(price), MAX(order_id)

FROM customer, items

WHERE customer.cust_id = items.cust_id

GROUP BY customer.cust_id";



if (!($connection = @ mysql_connect($hostName, $username, $password)))

die("Could not connect to database");



if (!(mysql_selectdb($databaseName, $connection)))

showerror( );



if (!($result = @ mysql_query($query, $connection)))

showerror( );



// Now, create a new PDF document

$doc =& new Cezpdf( );



// Use the Helvetica font

$doc->selectFont("./fonts/Helvetica.afm");



// Number the pages

$doc->ezStartPageNumbers(320, 15, 8);



// Set up running totals and an empty array for the output

$counter = 0;

$table = array( );

$totalOrders = 0;

$totalBottles = 0;

$totalAmount = 0;



// Get the query rows, and put them in the table

while ($row = mysql_fetch_array($result))

{

// Counts the total number of rows output

$counter++;



// Add current query row to the array of customer information

$table[] = array(

"Customer #"=>$row["cust_id"],

"Name"=> "{$row["surname"]}, {$row["firstname"]}",

"Orders Placed"=>$row["MAX(order_id)"],

"Total Bottles"=>$row["SUM(qty)"],

"Total Amount"=>"\${$row["SUM(price)"]}");



// Update running totals

$totalOrders += $row["MAX(order_id)"];

$totalBottles += $row["SUM(qty)"];

$totalAmount += $row["SUM(price)"];

}



// Today's date is used in the table heading

$date = date("d M Y");



// Right-justify the numeric columns

$options = array("cols" =>

array("Total Amount" =>

array("justification" => "right"),

"Total Bottles" =>

array("justification" => "right"),

"Orders Placed" =>

array("justification" => "right")));



// Output the table with a heading

$doc->ezTable($table, "", "Customer Order Report for {$date}",

$options);



$doc->ezSetDy(-10);



// Show totals

$doc->ezText("Total customers: {$counter}");

$doc->ezText("Total orders: {$totalOrders}");

$doc->ezText("Total bottles: {$totalBottles}");

$doc->ezText("Total amount: \${$totalAmount}");



// Output the document

$doc->ezStream( );

?>









The first page of the output of Example 13-4 is shown

in Figure 13-4 and the final page of the output in

Figure 13-5.









Figure 13-4. The first page of output from Example 13-4











Figure 13-5. The final page of output from Example 13-4







The report makes use of two tables, the customer

table shown in the previous section and an items

table that's described in more detail in Chapter 5. The items table is

created with the following statement:





CREATE TABLE items (

cust_id int(5) NOT NULL,

order_id int(5) NOT NULL,

item_id int(3) NOT NULL,

wine_id int(4) NOT NULL,

qty int(3),

price decimal(5,2),

PRIMARY KEY (cust_id,order_id,item_id)

) type=MyISAM;







We only use the order_id (which is used to count

how many orders each customer has placed), qty

(quantity of wine ordered in bottles), and price

(per bottle price) attributes from the items

table in this section.





We use the following query in our report:





$query = "SELECT customer.cust_id, surname, firstname, 

SUM(qty), SUM(price), MAX(order_id)

FROM customer, items

WHERE customer.cust_id = items.cust_id

GROUP BY customer.cust_id";







The query groups each customer's items together by

his unique cust_id, and we then discover the

customer's name, cust_id, the sum

of bottles sold using SUM(qty), the total value of

the sales using SUM(price), and the number of

orders placed using MAX(order_id). We run the

query using the usual MySQL functions, and then retrieve each row of

the results and add it to the PDF document as a table row.





We use the class constructor and the SelectFont(

)
, ezStartPageNumbers( ),

ezText( ), ezSetDy( ), and

ezStream( ) methods in the same way as in the

previous three sections.





Data comes from multiple rows in our database query results and is

displayed using the ezTable( ) method in this

example. To create the table, we first initialize an empty array

using:





$table = array( );







Then, for each row in the table, we add an element to that array that

is itself an array that contains five elements:





// Add to the array of customer information

$table[] = array(

"Customer #"=>$row["cust_id"],

"Name"=> "{$row["surname"]}, {$row["firstname"]}",

"Orders Placed"=>$row["MAX(order_id)"],

"Total Bottles"=>$row["SUM(qty)"],

"Total Amount"=>"\${$row["SUM(price)"]}");







The associative-access labels (Customer #,

Name, Orders Placed,

Total Bottles, and Total

Amount
) are used as the column headings for the table, and

the customer data from the query is used to populate the rows.





The table itself is then output with the following fragment:





// Output the table

$doc->ezTable($table, "", "Customer Order Report for {$date}", $options);







We use the optional third parameter that adds a title to the table.

This is output on the first page of output. The fourth optional

parameter is also used in this example to right-justify the numeric

columns (so that the differences in magnitude are obvious and so the

decimal points line up). To do this, we create a nested array:





// Right-justify the numeric columns

$options = array("cols" =>

array("Total Amount" =>

array("justification" => "right"),

"Total Bottles" =>

array("justification" => "right"),

"Orders Placed" =>

array("justification" => "right")));







The outer array contains one element, with the associative key

'cols', and this indicates the option

we're setting (you can set more than 15 different

options for a table). It contains as a value another array that

contains as keys the names of the three columns we want to configure

('Total Amount', 'Total

Bottles
', and 'Orders Placed'). Each of

these three elements has as its value yet another array, this time

with the column setting we want to change as the key

('justification') and what we want to set it to

('right'). This complex options parameter is

discussed in more detail in Section 13.3.





Finally, with the pages of tables complete, overall totals of

customers, orders, bottles, and sales are added using

ezText( ) and the whole document is output using

ezStream( ).























     < Day Day Up > 



    No comments:

    Post a Comment